Browse Source

auto update script executed

Toby Chui 1 year ago
parent
commit
5f21c9bd32
11 changed files with 867 additions and 73 deletions
  1. 32 16
      api.go
  2. 2 1
      go.mod
  3. 4 37
      go.sum
  4. 34 9
      main.go
  5. 11 5
      mod/aroz/aroz.go
  6. 478 0
      mod/auth/auth.go
  7. 53 0
      mod/auth/router.go
  8. 1 3
      mod/dynamicproxy/redirection/handler.go
  9. 4 0
      reverseproxy.go
  10. 2 2
      web/components/redirection.html
  11. 246 0
      web/login.html

+ 32 - 16
api.go

@@ -1,6 +1,10 @@
 package main
 
-import "net/http"
+import (
+	"net/http"
+
+	"imuslab.com/arozos/ReverseProxy/mod/auth"
+)
 
 /*
 	API.go
@@ -10,24 +14,36 @@ import "net/http"
 */
 
 func initAPIs() {
+	authRouter := auth.NewManagedHTTPRouter(auth.RouterOption{
+		AuthAgent:   authAgent,
+		RequireAuth: !(*noauth || handler.IsUsingExternalPermissionManager()),
+		DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
+			http.Error(w, "401 - Unauthorized", http.StatusUnauthorized)
+		},
+	})
+
+	//Register the standard web services urls
+	fs := http.FileServer(http.Dir("./web"))
+	http.Handle("/", fs)
+
 	//Reverse proxy
-	http.HandleFunc("/enable", ReverseProxyHandleOnOff)
-	http.HandleFunc("/add", ReverseProxyHandleAddEndpoint)
-	http.HandleFunc("/status", ReverseProxyStatus)
-	http.HandleFunc("/list", ReverseProxyList)
-	http.HandleFunc("/del", DeleteProxyEndpoint)
-	http.HandleFunc("/setIncoming", HandleIncomingPortSet)
-	http.HandleFunc("/useHttpsRedirect", HandleUpdateHttpsRedirect)
+	authRouter.HandleFunc("/enable", ReverseProxyHandleOnOff)
+	authRouter.HandleFunc("/add", ReverseProxyHandleAddEndpoint)
+	authRouter.HandleFunc("/status", ReverseProxyStatus)
+	authRouter.HandleFunc("/list", ReverseProxyList)
+	authRouter.HandleFunc("/del", DeleteProxyEndpoint)
+	authRouter.HandleFunc("/setIncoming", HandleIncomingPortSet)
+	authRouter.HandleFunc("/useHttpsRedirect", HandleUpdateHttpsRedirect)
 
 	//TLS / SSL config
-	http.HandleFunc("/cert/tls", handleToggleTLSProxy)
-	http.HandleFunc("/cert/upload", handleCertUpload)
-	http.HandleFunc("/cert/list", handleListCertificate)
-	http.HandleFunc("/cert/checkDefault", handleDefaultCertCheck)
-	http.HandleFunc("/cert/delete", handleCertRemove)
+	authRouter.HandleFunc("/cert/tls", handleToggleTLSProxy)
+	authRouter.HandleFunc("/cert/upload", handleCertUpload)
+	authRouter.HandleFunc("/cert/list", handleListCertificate)
+	authRouter.HandleFunc("/cert/checkDefault", handleDefaultCertCheck)
+	authRouter.HandleFunc("/cert/delete", handleCertRemove)
 
 	//Redirection config
-	http.HandleFunc("/redirect/list", handleListRedirectionRules)
-	http.HandleFunc("/redirect/add", handleAddRedirectionRule)
-	http.HandleFunc("/redirect/delete", handleDeleteRedirectionRule)
+	authRouter.HandleFunc("/redirect/list", handleListRedirectionRules)
+	authRouter.HandleFunc("/redirect/add", handleAddRedirectionRule)
+	authRouter.HandleFunc("/redirect/delete", handleDeleteRedirectionRule)
 }

+ 2 - 1
go.mod

@@ -4,7 +4,8 @@ go 1.16
 
 require (
 	github.com/boltdb/bolt v1.3.1
+	github.com/gorilla/sessions v1.2.1
 	github.com/gorilla/websocket v1.4.2
 	github.com/oschwald/geoip2-golang v1.8.0
-	golang.org/x/crypto v0.7.0
+	golang.org/x/sys v0.6.0 // indirect
 )

+ 4 - 37
go.sum

@@ -3,6 +3,10 @@ github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx2
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
+github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
+github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
+github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
 github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/oschwald/geoip2-golang v1.8.0 h1:KfjYB8ojCEn/QLqsDU0AzrJ3R5Qa9vFlx3z6SLNcKTs=
@@ -17,46 +21,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.7.3/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
-golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
-golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
-golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220804214406-8e32c043e418/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
-golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
-golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

+ 34 - 9
main.go

@@ -1,6 +1,8 @@
 package main
 
 import (
+	"flag"
+	"fmt"
 	"log"
 	"net/http"
 	"os"
@@ -9,14 +11,23 @@ import (
 	"time"
 
 	"imuslab.com/arozos/ReverseProxy/mod/aroz"
+	"imuslab.com/arozos/ReverseProxy/mod/auth"
 	"imuslab.com/arozos/ReverseProxy/mod/database"
 	"imuslab.com/arozos/ReverseProxy/mod/dynamicproxy/redirection"
 	"imuslab.com/arozos/ReverseProxy/mod/tlscert"
 )
 
+//General flags
+var noauth = flag.Bool("noauth", false, "Disable authentication for management interface")
+var showver = flag.Bool("version", false, "Show version of this server")
+
 var (
+	name    = "Yutoriprox"
+	version = "2.0"
+
 	handler        *aroz.ArozHandler
 	sysdb          *database.Database
+	authAgent      *auth.AuthAgent
 	tlsCertManager *tlscert.Manager
 	redirectTable  *redirection.RuleTable
 )
@@ -27,7 +38,7 @@ func SetupCloseHandler() {
 	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
 	go func() {
 		<-c
-		log.Println("\r- Shutting down ReverseProxy")
+		log.Println("\r- Shutting down " + name)
 
 		sysdb.Close()
 
@@ -38,11 +49,11 @@ func SetupCloseHandler() {
 func main() {
 	//Start the aoModule pipeline (which will parse the flags as well). Pass in the module launch information
 	handler = aroz.HandleFlagParse(aroz.ServiceInfo{
-		Name:        "ReverseProxy",
-		Desc:        "Basic reverse proxy listener",
+		Name:        name,
+		Desc:        "Dynamic Reverse Proxy Server",
 		Group:       "Network",
 		IconPath:    "reverseproxy/img/small_icon.png",
-		Version:     "0.3",
+		Version:     version,
 		StartDir:    "reverseproxy/index.html",
 		SupportFW:   true,
 		LaunchFWDir: "reverseproxy/index.html",
@@ -50,11 +61,11 @@ func main() {
 		InitFWSize:  []int{1080, 580},
 	})
 
-	//Register the standard web services urls
-	fs := http.FileServer(http.Dir("./web"))
+	if *showver {
+		fmt.Println(name + " - Version " + version)
+		os.Exit(0)
+	}
 
-	http.Handle("/", fs)
-	initAPIs()
 	SetupCloseHandler()
 
 	//Create database
@@ -66,6 +77,17 @@ func main() {
 	//Create tables for the database
 	sysdb.NewTable("settings")
 
+	//Create an auth agent
+	sessionKey, err := auth.GetSessionKey(sysdb)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	authAgent = auth.NewAuthenticationAgent(name, []byte(sessionKey), sysdb, true, func(w http.ResponseWriter, r *http.Request) {
+		//Not logged in. Redirecting to login page
+		http.Redirect(w, r, "/login.html", http.StatusTemporaryRedirect)
+	})
+
 	//Create a TLS certificate manager
 	tlsCertManager, err = tlscert.NewManager("./certs")
 	if err != nil {
@@ -78,12 +100,15 @@ func main() {
 		panic(err)
 	}
 
+	//Initiate management interface APIs
+	initAPIs()
+
 	//Start the reverse proxy server in go routine
 	go func() {
 		ReverseProxtInit()
 	}()
 
-	time.Sleep(300 * time.Millisecond)
+	time.Sleep(500 * time.Millisecond)
 	//Any log println will be shown in the core system via STDOUT redirection. But not STDIN.
 	log.Println("ReverseProxy started. Visit control panel at http://localhost" + handler.Port)
 	err = http.ListenAndServe(handler.Port, nil)

+ 11 - 5
mod/aroz/aroz.go

@@ -9,6 +9,7 @@ import (
 	"os"
 )
 
+//To be used with arozos system
 type ArozHandler struct {
 	Port            string
 	restfulEndpoint string
@@ -33,14 +34,14 @@ type ServiceInfo struct {
 
 //This function will request the required flag from the startup paramters and parse it to the need of the arozos.
 func HandleFlagParse(info ServiceInfo) *ArozHandler {
-	var infoRequestMode = flag.Bool("info", false, "Show information about this subservice")
-	var port = flag.String("port", ":8000", "The default listening endpoint for this subservice")
-	var restful = flag.String("rpt", "http://localhost:8080/api/ajgi/interface", "The RESTFUL Endpoint of the parent")
+	var infoRequestMode = flag.Bool("info", false, "Show information about this program in JSON")
+	var port = flag.String("port", ":8000", "Management web interface listening port")
+	var restful = flag.String("rpt", "", "Reserved")
 	//Parse the flags
 	flag.Parse()
-	if *infoRequestMode == true {
+	if *infoRequestMode {
 		//Information request mode
-		jsonString, _ := json.Marshal(info)
+		jsonString, _ := json.MarshalIndent(info, "", " ")
 		fmt.Println(string(jsonString))
 		os.Exit(0)
 	}
@@ -58,6 +59,11 @@ func (a *ArozHandler) GetUserInfoFromRequest(w http.ResponseWriter, r *http.Requ
 	return username, token
 }
 
+func (a *ArozHandler) IsUsingExternalPermissionManager() bool {
+	return !(a.restfulEndpoint == "")
+}
+
+//Request gateway interface for advance permission sandbox control
 func (a *ArozHandler) RequestGatewayInterface(token string, script string) (*http.Response, error) {
 	resp, err := http.PostForm(a.restfulEndpoint,
 		url.Values{"token": {token}, "script": {script}})

+ 478 - 0
mod/auth/auth.go

@@ -0,0 +1,478 @@
+package auth
+
+/*
+
+	author: tobychui
+*/
+
+import (
+	"crypto/rand"
+	"crypto/sha512"
+	"errors"
+	"net/http"
+	"net/mail"
+	"strings"
+
+	"encoding/hex"
+	"log"
+
+	"github.com/gorilla/sessions"
+	db "imuslab.com/arozos/ReverseProxy/mod/database"
+	"imuslab.com/arozos/ReverseProxy/mod/utils"
+)
+
+type AuthAgent struct {
+	//Session related
+	SessionName             string
+	SessionStore            *sessions.CookieStore
+	Database                *db.Database
+	LoginRedirectionHandler func(http.ResponseWriter, *http.Request)
+}
+
+type AuthEndpoints struct {
+	Login         string
+	Logout        string
+	Register      string
+	CheckLoggedIn string
+	Autologin     string
+}
+
+//Constructor
+func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database, allowReg bool, loginRedirectionHandler func(http.ResponseWriter, *http.Request)) *AuthAgent {
+	store := sessions.NewCookieStore(key)
+	err := sysdb.NewTable("auth")
+	if err != nil {
+		log.Println("Failed to create auth database. Terminating.")
+		panic(err)
+	}
+
+	//Create a new AuthAgent object
+	newAuthAgent := AuthAgent{
+		SessionName:             sessionName,
+		SessionStore:            store,
+		Database:                sysdb,
+		LoginRedirectionHandler: loginRedirectionHandler,
+	}
+
+	//Return the authAgent
+	return &newAuthAgent
+}
+
+func GetSessionKey(sysdb *db.Database) (string, error) {
+	sysdb.NewTable("auth")
+	sessionKey := ""
+	if !sysdb.KeyExists("auth", "sessionkey") {
+		key := make([]byte, 32)
+		rand.Read(key)
+		sessionKey = string(key)
+		sysdb.Write("auth", "sessionkey", sessionKey)
+		log.Println("[Auth] New authentication session key generated")
+	} else {
+		log.Println("[Auth] Authentication session key loaded from database")
+		err := sysdb.Read("auth", "sessionkey", &sessionKey)
+		if err != nil {
+			return "", errors.New("database read error. Is the database file corrupted?")
+		}
+	}
+	return sessionKey, nil
+}
+
+//This function will handle an http request and redirect to the given login address if not logged in
+func (a *AuthAgent) HandleCheckAuth(w http.ResponseWriter, r *http.Request, handler func(http.ResponseWriter, *http.Request)) {
+	if a.CheckAuth(r) {
+		//User already logged in
+		handler(w, r)
+	} else {
+		//User not logged in
+		a.LoginRedirectionHandler(w, r)
+	}
+}
+
+//Handle login request, require POST username and password
+func (a *AuthAgent) HandleLogin(w http.ResponseWriter, r *http.Request) {
+
+	//Get username from request using POST mode
+	username, err := utils.PostPara(r, "username")
+	if err != nil {
+		//Username not defined
+		log.Println("[Auth] Someone trying to login with username: " + username)
+		utils.SendErrorResponse(w, "Username not defined or empty.")
+		return
+	}
+
+	//Get password from request using POST mode
+	password, err := utils.PostPara(r, "password")
+	if err != nil {
+		//Password not defined
+		utils.SendErrorResponse(w, "Password not defined or empty.")
+		return
+	}
+
+	//Get rememberme settings
+	rememberme := false
+	rmbme, _ := utils.PostPara(r, "rmbme")
+	if rmbme == "true" {
+		rememberme = true
+	}
+
+	//Check the database and see if this user is in the database
+	passwordCorrect, rejectionReason := a.ValidateUsernameAndPasswordWithReason(username, password)
+	//The database contain this user information. Check its password if it is correct
+	if passwordCorrect {
+		//Password correct
+		// Set user as authenticated
+		a.LoginUserByRequest(w, r, username, rememberme)
+
+		//Print the login message to console
+		log.Println(username + " logged in.")
+		utils.SendOK(w)
+	} else {
+		//Password incorrect
+		log.Println(username + " login request rejected: " + rejectionReason)
+
+		utils.SendErrorResponse(w, rejectionReason)
+		return
+	}
+}
+
+func (a *AuthAgent) ValidateUsernameAndPassword(username string, password string) bool {
+	succ, _ := a.ValidateUsernameAndPasswordWithReason(username, password)
+	return succ
+}
+
+//validate the username and password, return reasons if the auth failed
+func (a *AuthAgent) ValidateUsernameAndPasswordWithReason(username string, password string) (bool, string) {
+	hashedPassword := Hash(password)
+	var passwordInDB string
+	err := a.Database.Read("auth", "passhash/"+username, &passwordInDB)
+	if err != nil {
+		//User not found or db exception
+		log.Println("[Auth] " + username + " login with incorrect password")
+		return false, "Invalid username or password"
+	}
+
+	if passwordInDB == hashedPassword {
+		return true, ""
+	} else {
+		return false, "Invalid username or password"
+	}
+}
+
+//Login the user by creating a valid session for this user
+func (a *AuthAgent) LoginUserByRequest(w http.ResponseWriter, r *http.Request, username string, rememberme bool) {
+	session, _ := a.SessionStore.Get(r, a.SessionName)
+
+	session.Values["authenticated"] = true
+	session.Values["username"] = username
+	session.Values["rememberMe"] = rememberme
+
+	//Check if remember me is clicked. If yes, set the maxage to 1 week.
+	if rememberme {
+		session.Options = &sessions.Options{
+			MaxAge: 3600 * 24 * 7, //One week
+			Path:   "/",
+		}
+	} else {
+		session.Options = &sessions.Options{
+			MaxAge: 3600 * 1, //One hour
+			Path:   "/",
+		}
+	}
+	session.Save(r, w)
+}
+
+//Handle logout, reply OK after logged out. WILL NOT DO REDIRECTION
+func (a *AuthAgent) HandleLogout(w http.ResponseWriter, r *http.Request) {
+	username, err := a.GetUserName(w, r)
+	if username != "" {
+		log.Println(username + " logged out.")
+	}
+	// Revoke users authentication
+	err = a.Logout(w, r)
+	if err != nil {
+		utils.SendErrorResponse(w, "Logout failed")
+		return
+	}
+
+	w.Write([]byte("OK"))
+}
+
+func (a *AuthAgent) Logout(w http.ResponseWriter, r *http.Request) error {
+	session, err := a.SessionStore.Get(r, a.SessionName)
+	if err != nil {
+		return err
+	}
+	session.Values["authenticated"] = false
+	session.Values["username"] = nil
+	session.Save(r, w)
+	return nil
+}
+
+//Get the current session username from request
+func (a *AuthAgent) GetUserName(w http.ResponseWriter, r *http.Request) (string, error) {
+	if a.CheckAuth(r) {
+		//This user has logged in.
+		session, _ := a.SessionStore.Get(r, a.SessionName)
+		return session.Values["username"].(string), nil
+	} else {
+		//This user has not logged in.
+		return "", errors.New("user not logged in")
+	}
+}
+
+//Get the current session user email from request
+func (a *AuthAgent) GetUserEmail(w http.ResponseWriter, r *http.Request) (string, error) {
+	if a.CheckAuth(r) {
+		//This user has logged in.
+		session, _ := a.SessionStore.Get(r, a.SessionName)
+		username := session.Values["username"].(string)
+		userEmail := ""
+		err := a.Database.Read("auth", "email/"+username, &userEmail)
+		if err != nil {
+			return "", err
+		}
+
+		return userEmail, nil
+	} else {
+		//This user has not logged in.
+		return "", errors.New("user not logged in")
+	}
+}
+
+//Check if the user has logged in, return true / false in JSON
+func (a *AuthAgent) CheckLogin(w http.ResponseWriter, r *http.Request) {
+	if a.CheckAuth(r) {
+		utils.SendJSONResponse(w, "true")
+	} else {
+		utils.SendJSONResponse(w, "false")
+	}
+}
+
+//Handle new user register. Require POST username, password, group.
+func (a *AuthAgent) HandleRegister(w http.ResponseWriter, r *http.Request, callback func(string, string)) {
+	//Get username from request
+	newusername, err := utils.PostPara(r, "username")
+	if err != nil {
+		utils.SendErrorResponse(w, "Missing 'username' paramter")
+		return
+	}
+
+	//Get password from request
+	password, err := utils.PostPara(r, "password")
+	if err != nil {
+		utils.SendErrorResponse(w, "Missing 'password' paramter")
+		return
+	}
+
+	//Get email from request
+	email, err := utils.PostPara(r, "email")
+	if err != nil {
+		utils.SendErrorResponse(w, "Missing 'email' paramter")
+		return
+	}
+
+	_, err = mail.ParseAddress(email)
+	if err != nil {
+		utils.SendErrorResponse(w, "Invalid or malformed email")
+		return
+	}
+
+	//Ok to proceed create this user
+	err = a.CreateUserAccount(newusername, password, email)
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+
+	//Do callback if exists
+	if callback != nil {
+		callback(newusername, email)
+	}
+
+	//Return to the client with OK
+	utils.SendOK(w)
+	log.Println("[Auth] New user " + newusername + " added to system.")
+}
+
+//Handle new user register without confirmation email. Require POST username, password, group.
+func (a *AuthAgent) HandleRegisterWithoutEmail(w http.ResponseWriter, r *http.Request, callback func(string, string)) {
+	//Get username from request
+	newusername, err := utils.PostPara(r, "username")
+	if err != nil {
+		utils.SendErrorResponse(w, "Missing 'username' paramter")
+		return
+	}
+
+	//Get password from request
+	password, err := utils.PostPara(r, "password")
+	if err != nil {
+		utils.SendErrorResponse(w, "Missing 'password' paramter")
+		return
+	}
+
+	//Ok to proceed create this user
+	err = a.CreateUserAccount(newusername, password, "")
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+
+	//Do callback if exists
+	if callback != nil {
+		callback(newusername, "")
+	}
+
+	//Return to the client with OK
+	utils.SendOK(w)
+	log.Println("[Auth] New user " + newusername + " added to system.")
+}
+
+//Check authentication from request header's session value
+func (a *AuthAgent) CheckAuth(r *http.Request) bool {
+	session, err := a.SessionStore.Get(r, a.SessionName)
+	if err != nil {
+		return false
+	}
+	// Check if user is authenticated
+	if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
+		return false
+	}
+	return true
+}
+
+//Handle de-register of users. Require POST username.
+//THIS FUNCTION WILL NOT CHECK FOR PERMISSION. PLEASE USE WITH PERMISSION HANDLER
+func (a *AuthAgent) HandleUnregister(w http.ResponseWriter, r *http.Request) {
+	//Check if the user is logged in
+	if !a.CheckAuth(r) {
+		//This user has not logged in
+		utils.SendErrorResponse(w, "Login required to remove user from the system.")
+		return
+	}
+
+	//Get username from request
+	username, err := utils.PostPara(r, "username")
+	if err != nil {
+		utils.SendErrorResponse(w, "Missing 'username' paramter")
+		return
+	}
+
+	err = a.UnregisterUser(username)
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+
+	//Return to the client with OK
+	utils.SendOK(w)
+	log.Println("[Auth] User " + username + " has been removed from the system.")
+}
+
+func (a *AuthAgent) UnregisterUser(username string) error {
+	//Check if the user exists in the system database.
+	if !a.Database.KeyExists("auth", "passhash/"+username) {
+		//This user do not exists.
+		return errors.New("this user does not exists")
+	}
+
+	//OK! Remove the user from the database
+	a.Database.Delete("auth", "passhash/"+username)
+	a.Database.Delete("auth", "email/"+username)
+	return nil
+}
+
+//Get the number of users in the system
+func (a *AuthAgent) GetUserCounts() int {
+	entries, _ := a.Database.ListTable("auth")
+	usercount := 0
+	for _, keypairs := range entries {
+		if strings.Contains(string(keypairs[0]), "passhash/") {
+			//This is a user registry
+			usercount++
+		}
+	}
+
+	if usercount == 0 {
+		log.Println("There are no user in the database.")
+	}
+	return usercount
+}
+
+//List all username within the system
+func (a *AuthAgent) ListUsers() []string {
+	entries, _ := a.Database.ListTable("auth")
+	results := []string{}
+	for _, keypairs := range entries {
+		if strings.Contains(string(keypairs[0]), "passhash/") {
+			username := strings.Split(string(keypairs[0]), "/")[1]
+			results = append(results, username)
+		}
+	}
+	return results
+}
+
+//Check if the given username exists
+func (a *AuthAgent) UserExists(username string) bool {
+	userpasswordhash := ""
+	err := a.Database.Read("auth", "passhash/"+username, &userpasswordhash)
+	if err != nil || userpasswordhash == "" {
+		return false
+	}
+	return true
+}
+
+//Update the session expire time given the request header.
+func (a *AuthAgent) UpdateSessionExpireTime(w http.ResponseWriter, r *http.Request) bool {
+	session, _ := a.SessionStore.Get(r, a.SessionName)
+	if session.Values["authenticated"].(bool) {
+		//User authenticated. Extend its expire time
+		rememberme := session.Values["rememberMe"].(bool)
+		//Extend the session expire time
+		if rememberme {
+			session.Options = &sessions.Options{
+				MaxAge: 3600 * 24 * 7, //One week
+				Path:   "/",
+			}
+		} else {
+			session.Options = &sessions.Options{
+				MaxAge: 3600 * 1, //One hour
+				Path:   "/",
+			}
+		}
+		session.Save(r, w)
+		return true
+	} else {
+		return false
+	}
+}
+
+//Create user account
+func (a *AuthAgent) CreateUserAccount(newusername string, password string, email string) error {
+	//Check user already exists
+	if a.UserExists(newusername) {
+		return errors.New("user with same name already exists")
+	}
+
+	key := newusername
+	hashedPassword := Hash(password)
+	err := a.Database.Write("auth", "passhash/"+key, hashedPassword)
+	if err != nil {
+		return err
+	}
+
+	if email != "" {
+		err = a.Database.Write("auth", "email/"+key, email)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+//Hash the given raw string into sha512 hash
+func Hash(raw string) string {
+	h := sha512.New()
+	h.Write([]byte(raw))
+	return hex.EncodeToString(h.Sum(nil))
+}

+ 53 - 0
mod/auth/router.go

@@ -0,0 +1,53 @@
+package auth
+
+import (
+	"errors"
+	"log"
+	"net/http"
+)
+
+type RouterOption struct {
+	AuthAgent     *AuthAgent
+	RequireAuth   bool                                     //This router require authentication
+	DeniedHandler func(http.ResponseWriter, *http.Request) //Things to do when request is rejected
+
+}
+
+type RouterDef struct {
+	option    RouterOption
+	endpoints map[string]func(http.ResponseWriter, *http.Request)
+}
+
+func NewManagedHTTPRouter(option RouterOption) *RouterDef {
+	return &RouterDef{
+		option:    option,
+		endpoints: map[string]func(http.ResponseWriter, *http.Request){},
+	}
+}
+
+func (router *RouterDef) HandleFunc(endpoint string, handler func(http.ResponseWriter, *http.Request)) error {
+	//Check if the endpoint already registered
+	if _, exist := router.endpoints[endpoint]; exist {
+		log.Println("WARNING! Duplicated registering of web endpoint: " + endpoint)
+		return errors.New("endpoint register duplicated")
+	}
+
+	authAgent := router.option.AuthAgent
+
+	//OK. Register handler
+	http.HandleFunc(endpoint, func(w http.ResponseWriter, r *http.Request) {
+		//Check authentication of the user
+		if router.option.RequireAuth {
+			authAgent.HandleCheckAuth(w, r, func(w http.ResponseWriter, r *http.Request) {
+				handler(w, r)
+			})
+		} else {
+			handler(w, r)
+		}
+
+	})
+
+	router.endpoints[endpoint] = handler
+
+	return nil
+}

+ 1 - 3
mod/dynamicproxy/redirection/handler.go

@@ -1,7 +1,6 @@
 package redirection
 
 import (
-	"fmt"
 	"log"
 	"net/http"
 	"strings"
@@ -34,14 +33,13 @@ func (t *RuleTable) HandleRedirect(w http.ResponseWriter, r *http.Request) {
 		}
 		if rr.ForwardChildpath {
 			//Remove the first / in the path
-			redirectTarget += r.URL.Path[1:]
+			redirectTarget += r.URL.Path[1:] + "?" + r.URL.RawQuery
 		}
 
 		if !strings.HasPrefix(redirectTarget, "http://") && !strings.HasPrefix(redirectTarget, "https://") {
 			redirectTarget = "http://" + redirectTarget
 		}
 
-		fmt.Println(redirectTarget)
 		http.Redirect(w, r, redirectTarget, rr.StatusCode)
 	} else {
 		//Invalid usage

+ 4 - 0
reverseproxy.go

@@ -7,6 +7,7 @@ import (
 	"path/filepath"
 	"strconv"
 	"strings"
+	"time"
 
 	"imuslab.com/arozos/ReverseProxy/mod/dynamicproxy"
 	"imuslab.com/arozos/ReverseProxy/mod/utils"
@@ -85,6 +86,9 @@ func ReverseProxtInit() {
 	*/
 
 	//Start Service
+	//Not sure why but delay must be added if you have another
+	//reverse proxy server in front of this service
+	time.Sleep(300 * time.Millisecond)
 	dynamicProxyRouter.StartProxyService()
 	log.Println("Dynamic Reverse Proxy service started")
 

+ 2 - 2
web/components/redirection.html

@@ -46,8 +46,8 @@
       </div>
       <div class="ui message">
         <p>Append the current pathname after the redirect destination</p>
-        <i class="check square outline icon"></i> old.example.com<b>/blog</b> <i class="long arrow alternate right icon" style="margin-left: 1em;"></i> new.example.com<b>/blog</b> <br>
-        <i class="square outline icon"></i> Disabled old.example.com<b>/blog</b> <i class="long arrow alternate right icon" style="margin-left: 1em;"></i> new.example.com
+        <i class="check square outline icon"></i> old.example.com<b>/blog?post=13</b> <i class="long arrow alternate right icon" style="margin-left: 1em;"></i> new.example.com<b>/blog?post=13</b> <br>
+        <i class="square outline icon"></i> Disabled old.example.com<b>/blog?post=13</b> <i class="long arrow alternate right icon" style="margin-left: 1em;"></i> new.example.com
       </div>
     </div>
     <div class="grouped fields">

+ 246 - 0
web/login.html

@@ -0,0 +1,246 @@
+<!DOCTYPE HTML>
+<html>
+    <head>
+    <meta charset="UTF-8">
+    <meta name="robots" content="noindex" />
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+    <link rel="author" href="humans.txt"/>
+    <title>Login</title>
+    <link rel="stylesheet" href="script/semantic/semantic.min.css">
+    <script type="application/javascript" src="script/jquery.min.js"></script>
+    <script type="application/javascript" src="script/semantic/semantic.min.js"></script>
+    <link rel="preconnect" href="https://fonts.googleapis.com">
+    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@300;400;500;700;900&display=swap" rel="stylesheet"> 
+    <link rel="stylesheet" href="script/main.css">
+    <style>
+    @media only screen and (max-height: 1000px) {
+        .leftPictureFrame {
+        height:auto !important;
+        }
+    }
+
+    .leftPictureFrame{
+        position:fixed;
+        top:0px;
+        left:0px;
+        min-width:calc(100% - 500px);
+        min-height:100%;
+        background-color:#faf7eb;
+        background-image:url("img/authbg.jpg");
+        -webkit-background-size: cover;
+        -moz-background-size: cover;
+        -o-background-size: cover;
+        background-size: cover;
+        background-repeat: no-repeat, no-repeat;
+        background-position:bottom left;
+    }
+
+    .rightLoginFrame{
+        position:fixed;
+        top:0;
+        right:0;
+        height:100%;
+        width:500px;
+        background-color:white;
+        z-index:100%;
+        padding-left: 30px;
+        padding-right: 20px;
+    }
+
+    .fullHeightImage{
+        height:100% !important;
+        position:relative;
+        left:-20px;
+        
+    }
+
+    .bottombar{
+        position:absolute;
+        bottom:1em;
+        left:0;
+        padding-left: 20px;
+        width:100%;
+    }
+
+    #animationFrame{
+        position:absolute;
+        bottom:0px;
+        width:100%;
+    }
+
+    .textbox{
+        margin-bottom:15px;
+    }
+
+    .themecolor{
+        background-color: #5fa0d9 !important;
+    }
+
+    .subthemecolor{
+        background-color: #99d0f2 !important;
+    }
+
+    #loginbtn{
+        color:white !important;
+        margin-top:2em;
+    }
+
+    .oauthbtn{
+        color:white !important;
+        margin-top:1em;
+    }
+
+    </style>
+    </head>
+    <body>
+        <div class="leftPictureFrame">
+            
+        </div>
+        <div id="loginInterface" class="rightLoginFrame">
+            <br><br><br>
+            <img class="ui medium image" src="vendor/img/banner.png">
+
+            <div class="ui borderless basic segment">
+                <p><i class="key icon"></i> Login with your username & password <span class="hostname"></span></p>
+                <br>
+                <div class="ui fluid input textbox">
+                    <input id="username" type="text" placeholder="Username">
+                </div>
+                <div class="ui fluid input textbox">
+                    <input id="magic" type="password" placeholder="Password">
+                </div>
+
+                <div class="ui checkbox">
+                    <input id="rmbme" type="checkbox">
+                    <label for="rmbme">Remember Me</label>
+                </div>
+                
+                <br>
+                <button id="loginbtn" class="ui button loginbtn themecolor" >Login</button><br>
+                <div class="ui breadcrumb" style="margin-top:12px;">
+                    <a class="section signup" style="cursor:pointer;" href="/register.html">Register</a>
+                </div>
+                <p style="margin-top:18px;color:#ff7a70; display:none;font-size:1.2em;"><i class="remove icon"></i><span id="errmsg">Error. Incorrect username or password.</span></p>
+               
+            </div>
+           
+            <div class="bottombar">
+                © <span class="hostname"></span> <span class="thisyear"></span><br>
+                <small style="font-size: 80%">Request: <span id="requestTime"></span></small>
+            </div>
+        </div>
+        
+    <script>
+        var redirectionAddress = "/";
+        var loginAddress = "/api/auth/login";
+        var isMobile = false; //initiate as false
+        // device detection
+        if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(navigator.userAgent) 
+            || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(navigator.userAgent.substr(0,4))) { 
+            isMobile = true;
+        }  
+
+        if (isMobile){
+            //Full screen the login panel
+            $("#loginInterface").css("width","100%");
+        }
+
+        $(document).ready(function(){
+            var currentdate = new Date(); 
+            var datetime = currentdate.getDate() + "/"
+                + (currentdate.getMonth()+1)  + "/" 
+                + currentdate.getFullYear() + " "  
+                + currentdate.getHours() + ":"  
+                + currentdate.getMinutes() + ":" 
+                + currentdate.getSeconds();
+            $("#requestTime").text(datetime);
+            //Check if the user already logged in
+            $.get("system/auth/checkLogin",function(data){
+                try{
+                    if (data === true || data.trim() == "true"){
+                    //User already logged in. Redirect to target page.
+                    if (redirectionAddress == ""){
+                        //Redirect back to index
+                        window.location.href = "/";
+                    }else{
+                        console.log(data);
+                        //window.location.href = redirectionAddress;
+                    }
+                }
+                }catch(ex){
+                    //Assume not logged in
+                    console.log(data);
+                }
+                
+            });
+            
+        });
+
+        //Event handlers for buttons
+        $("#loginbtn").on("click",function(){
+            login();
+        });
+
+        $("input").on("keydown",function(event){
+            if (event.keyCode === 13) {
+                event.preventDefault();
+                if ($(this).attr("id") == "magic"){
+                    login();
+                }else{
+                    //Fuocus to password field
+                    $("#magic").focus();
+                }
+                
+            }
+        });
+
+        //Login system with the given username and password
+        function login(){
+            var username = $("#username").val();
+            var magic = $("#magic").val();
+            var rmbme = document.getElementById("rmbme").checked;
+            $("input").addClass('disabled');
+            $.post(loginAddress, {"username": username, "password": magic, "rmbme": rmbme}).done(function(data){
+                if (data.error !== undefined){
+                    //Something went wrong during the login
+                    $("#errmsg").text(data.error);
+                    $("#errmsg").parent().stop().finish().slideDown('fast').delay(5000).slideUp('fast');
+                }else if(data.redirect !== undefined){
+                    //LDAP Related Code
+                    window.location.href = data.redirect;
+                }else{
+                    //Login succeed
+                    if (redirectionAddress == ""){
+                        //Redirect back to index
+                        window.location.href = "./";
+                    }else{
+                        window.location.href = redirectionAddress;
+                    }
+                }
+                $("input").removeClass('disabled');
+            });
+
+        }
+
+        function get(name){
+            if(name=(new RegExp('[?&]'+encodeURIComponent(name)+'=([^&]*)')).exec(location.search))
+                return decodeURIComponent(name[1]);
+        }
+
+        $(".thisyear").text(new Date().getFullYear());
+
+        function updateRenderElements(){
+            if (window.innerHeight < 520){
+                $(".bottombar").hide();
+            }else{
+                $(".bottombar").show();
+            }
+        }
+        updateRenderElements();
+        $(window).on("resize", function(){
+            updateRenderElements();
+        });
+    </script>
+    </body>
+</html>