diff --git a/database/database.go b/database/database.go index 77e103d4..9e3913e8 100644 --- a/database/database.go +++ b/database/database.go @@ -30,6 +30,8 @@ var ( versionK = []byte("version") // feeXPub is the extended public key used for collecting VSP fees. feeXPubK = []byte("feeXPub") + // cookieSecret is the secret key for initializing the cookie store. + cookieSecretK = []byte("cookieSecret") // privatekey is the private key. privateKeyK = []byte("privatekey") // lastaddressindex is the index of the last address used for fees. @@ -103,6 +105,18 @@ func CreateNew(dbFile, feeXPub string) error { return err } + // Generate a secret key for initializing the cookie store. + log.Info("Generating cookie secret") + secret := make([]byte, 32) + _, err = rand.Read(secret) + if err != nil { + return err + } + err = vspBkt.Put(cookieSecretK, secret) + if err != nil { + return err + } + log.Info("Storing extended public key") // Store fee xpub err = vspBkt.Put(feeXPubK, []byte(feeXPub)) @@ -228,3 +242,16 @@ func (vdb *VspDatabase) GetFeeXPub() (string, error) { return feeXPub, err } + +func (vdb *VspDatabase) GetCookieSecret() ([]byte, error) { + var cookieSecret []byte + err := vdb.db.View(func(tx *bolt.Tx) error { + vspBkt := tx.Bucket(vspBktK) + + cookieSecret = vspBkt.Get(cookieSecretK) + + return nil + }) + + return cookieSecret, err +} diff --git a/database/database_test.go b/database/database_test.go index eeabf235..367d8d22 100644 --- a/database/database_test.go +++ b/database/database_test.go @@ -56,4 +56,4 @@ func TestDatabase(t *testing.T) { } // TODO: Add tests for CountTickets, GetUnconfirmedTickets, GetPendingFees, -// GetUnconfirmedFees. +// GetUnconfirmedFees, GetAllTickets. diff --git a/database/ticket.go b/database/ticket.go index 985d9e6d..9307bdbb 100644 --- a/database/ticket.go +++ b/database/ticket.go @@ -227,3 +227,24 @@ func (vdb *VspDatabase) GetUnconfirmedFees() ([]Ticket, error) { return tickets, err } + +func (vdb *VspDatabase) GetAllTickets() ([]Ticket, error) { + var tickets []Ticket + err := vdb.db.View(func(tx *bolt.Tx) error { + ticketBkt := tx.Bucket(vspBktK).Bucket(ticketBktK) + + return ticketBkt.ForEach(func(k, v []byte) error { + var ticket Ticket + err := json.Unmarshal(v, &ticket) + if err != nil { + return fmt.Errorf("could not unmarshal ticket: %v", err) + } + + tickets = append(tickets, ticket) + + return nil + }) + }) + + return tickets, err +} diff --git a/go.mod b/go.mod index 58e0b45e..d40c5d15 100644 --- a/go.mod +++ b/go.mod @@ -4,16 +4,17 @@ go 1.13 require ( decred.org/dcrwallet v1.2.3-0.20200519180100-f1aa4c354e05 - github.com/decred/dcrd/blockchain/stake/v3 v3.0.0-20200528191943-a2771b8ce693 - github.com/decred/dcrd/chaincfg/v3 v3.0.0-20200528191943-a2771b8ce693 + github.com/decred/dcrd/blockchain/stake/v3 v3.0.0-20200607041702-62fa0661bd57 + github.com/decred/dcrd/chaincfg/v3 v3.0.0-20200607041702-62fa0661bd57 github.com/decred/dcrd/dcrec v1.0.0 - github.com/decred/dcrd/dcrutil/v3 v3.0.0-20200528191943-a2771b8ce693 - github.com/decred/dcrd/hdkeychain/v3 v3.0.0-20200528191943-a2771b8ce693 + github.com/decred/dcrd/dcrutil/v3 v3.0.0-20200607041702-62fa0661bd57 + github.com/decred/dcrd/hdkeychain/v3 v3.0.0-20200607041702-62fa0661bd57 github.com/decred/dcrd/rpc/jsonrpc/types/v2 v2.0.1-0.20200527025017-6fc98347d984 - github.com/decred/dcrd/txscript/v3 v3.0.0-20200528191943-a2771b8ce693 + github.com/decred/dcrd/txscript/v3 v3.0.0-20200607041702-62fa0661bd57 github.com/decred/dcrd/wire v1.3.0 github.com/decred/slog v1.0.0 github.com/gin-gonic/gin v1.6.3 + github.com/gorilla/sessions v1.2.0 github.com/jessevdk/go-flags v1.4.0 github.com/jrick/bitset v1.0.0 github.com/jrick/logrotate v1.0.0 diff --git a/go.sum b/go.sum index cf2e1a7f..8d4dd28a 100644 --- a/go.sum +++ b/go.sum @@ -20,8 +20,8 @@ github.com/decred/dcrd/addrmgr v1.1.0/go.mod h1:exghL+0+QeVvO4MXezWJ1C2tcpBn3ngf github.com/decred/dcrd/blockchain/stake/v2 v2.0.2/go.mod h1:o2TT/l/YFdrt15waUdlZ3g90zfSwlA0WgQqHV9UGJF4= github.com/decred/dcrd/blockchain/stake/v3 v3.0.0-20200215031403-6b2ce76f0986/go.mod h1:aDL94kcVJfaaJP+acWUJrlK7g7xEOqTSiFe6bSN3yRQ= github.com/decred/dcrd/blockchain/stake/v3 v3.0.0-20200311044114-143c1884e4c8/go.mod h1:4zE60yDWlfCDtmqnyP5o1k1K0oyhNn3Tvqo6F93/+RU= -github.com/decred/dcrd/blockchain/stake/v3 v3.0.0-20200528191943-a2771b8ce693 h1:2Wbj27h04yq4l8KSqCJkZQnmWiKfUI4kNYaUms5oSnY= -github.com/decred/dcrd/blockchain/stake/v3 v3.0.0-20200528191943-a2771b8ce693/go.mod h1:4zE60yDWlfCDtmqnyP5o1k1K0oyhNn3Tvqo6F93/+RU= +github.com/decred/dcrd/blockchain/stake/v3 v3.0.0-20200607041702-62fa0661bd57 h1:4/glIgVrylnAAYlpfgBmxg8dX9DHWJu/tlBagcNiXz4= +github.com/decred/dcrd/blockchain/stake/v3 v3.0.0-20200607041702-62fa0661bd57/go.mod h1:4zE60yDWlfCDtmqnyP5o1k1K0oyhNn3Tvqo6F93/+RU= github.com/decred/dcrd/blockchain/standalone v1.1.0 h1:yclvVGEY09Gf8A4GSAo+NCtL1dW2TYJ4OKp4+g0ICI0= github.com/decred/dcrd/blockchain/standalone v1.1.0/go.mod h1:6K8ZgzlWM1Kz2TwXbrtiAvfvIwfAmlzrtpA7CVPCUPE= github.com/decred/dcrd/blockchain/v3 v3.0.0-20200311044114-143c1884e4c8/go.mod h1:R9rIXU8kEJVC9Z4LAlh9bo9hiT3a+ihys3mCrz4PVao= @@ -34,8 +34,8 @@ github.com/decred/dcrd/chaincfg/v3 v3.0.0-20200215015031-3283587e6add/go.mod h1: github.com/decred/dcrd/chaincfg/v3 v3.0.0-20200215023918-6247af01d5e3/go.mod h1:v4oyBPQ/ZstYCV7+B0y6HogFByW76xTjr+72fOm66Y8= github.com/decred/dcrd/chaincfg/v3 v3.0.0-20200215031403-6b2ce76f0986/go.mod h1:v4oyBPQ/ZstYCV7+B0y6HogFByW76xTjr+72fOm66Y8= github.com/decred/dcrd/chaincfg/v3 v3.0.0-20200311044114-143c1884e4c8/go.mod h1:v4oyBPQ/ZstYCV7+B0y6HogFByW76xTjr+72fOm66Y8= -github.com/decred/dcrd/chaincfg/v3 v3.0.0-20200528191943-a2771b8ce693 h1:zTb6LJaZpYAWItJbo/XY8howiwCceEfWzHZbI06PRNI= -github.com/decred/dcrd/chaincfg/v3 v3.0.0-20200528191943-a2771b8ce693/go.mod h1:OHbKBa6UZZOXCU1Y8f9Ta3O+GShto7nB1O0nuEutKq4= +github.com/decred/dcrd/chaincfg/v3 v3.0.0-20200607041702-62fa0661bd57 h1:agpmzwuPv/iaePlk/2MxXPU6Zz14qy2LY1kWmRCBdAQ= +github.com/decred/dcrd/chaincfg/v3 v3.0.0-20200607041702-62fa0661bd57/go.mod h1:OHbKBa6UZZOXCU1Y8f9Ta3O+GShto7nB1O0nuEutKq4= github.com/decred/dcrd/connmgr/v3 v3.0.0-20200311044114-143c1884e4c8/go.mod h1:mvIMJsrOEngogmVrq+tdbPIZchHVgGnVBZeNwj1cW6E= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= @@ -61,14 +61,14 @@ github.com/decred/dcrd/dcrutil/v3 v3.0.0-20200215015031-3283587e6add/go.mod h1:C github.com/decred/dcrd/dcrutil/v3 v3.0.0-20200215023918-6247af01d5e3/go.mod h1:48ZLpNNrRIYfqYxmvzMgOZrnTZUU3aTJveWtamCkOxo= github.com/decred/dcrd/dcrutil/v3 v3.0.0-20200215031403-6b2ce76f0986/go.mod h1:jFxEd2LWDLvrWlrIiyx9ZGTQjvoFHZ0OVfBdyIX7jSw= github.com/decred/dcrd/dcrutil/v3 v3.0.0-20200311044114-143c1884e4c8/go.mod h1:/CDBC1SOXKrmihavgXviaTr6eVZSAWKQqEbRmacDxgg= -github.com/decred/dcrd/dcrutil/v3 v3.0.0-20200528191943-a2771b8ce693 h1:6Sz3pijZe2cyY8y1hhTmeQDz6Bg+KheIA5Hkks1n8QY= -github.com/decred/dcrd/dcrutil/v3 v3.0.0-20200528191943-a2771b8ce693/go.mod h1:85NtF/fmqL2UDf0/gLhTHG/m/0HQHwG+erQKkwWW27A= +github.com/decred/dcrd/dcrutil/v3 v3.0.0-20200607041702-62fa0661bd57 h1://D7EuW5XY8Me8uOHH2PH9APsdUQ0cm3KIyXXIcMYGc= +github.com/decred/dcrd/dcrutil/v3 v3.0.0-20200607041702-62fa0661bd57/go.mod h1:85NtF/fmqL2UDf0/gLhTHG/m/0HQHwG+erQKkwWW27A= github.com/decred/dcrd/gcs/v2 v2.0.0/go.mod h1:3XjKcrtvB+r2ezhIsyNCLk6dRnXRJVyYmsd1P3SkU3o= github.com/decred/dcrd/gcs/v2 v2.0.2-0.20200312171759-0a8cc56a776e h1:tBOk2P8F9JyRUSp0iRTs4nYEBro1FKBDIbg/UualLWw= github.com/decred/dcrd/gcs/v2 v2.0.2-0.20200312171759-0a8cc56a776e/go.mod h1:JJGd1m0DrFgV4J2J8HKNB9YVkM06ewQHT6iINis39Z4= github.com/decred/dcrd/hdkeychain/v3 v3.0.0-20200421213827-b60c60ffe98b/go.mod h1:qKN0WzeSEEZ4fUBsTwKzOPkLP7GqSM6jBUm5Auq9mrM= -github.com/decred/dcrd/hdkeychain/v3 v3.0.0-20200528191943-a2771b8ce693 h1:8MKtyq4+zxRC/SEtJuF4+qB+Svd5vvHZeG9kbHxg9wY= -github.com/decred/dcrd/hdkeychain/v3 v3.0.0-20200528191943-a2771b8ce693/go.mod h1:qKN0WzeSEEZ4fUBsTwKzOPkLP7GqSM6jBUm5Auq9mrM= +github.com/decred/dcrd/hdkeychain/v3 v3.0.0-20200607041702-62fa0661bd57 h1:fGahtE/RfIBYlguw7tG11g1RBMBqqaZKVWFYlccYbX4= +github.com/decred/dcrd/hdkeychain/v3 v3.0.0-20200607041702-62fa0661bd57/go.mod h1:qKN0WzeSEEZ4fUBsTwKzOPkLP7GqSM6jBUm5Auq9mrM= github.com/decred/dcrd/rpc/jsonrpc/types/v2 v2.0.0/go.mod h1:c5S+PtQWNIA2aUakgrLhrlopkMadcOv51dWhCEdo49c= github.com/decred/dcrd/rpc/jsonrpc/types/v2 v2.0.1-0.20200527025017-6fc98347d984 h1:xfdiilBsDinOLbglqzHH98fOO1iBtVrpSHMVpNK/2lg= github.com/decred/dcrd/rpc/jsonrpc/types/v2 v2.0.1-0.20200527025017-6fc98347d984/go.mod h1:c5S+PtQWNIA2aUakgrLhrlopkMadcOv51dWhCEdo49c= @@ -76,8 +76,8 @@ github.com/decred/dcrd/txscript/v2 v2.1.0/go.mod h1:XaJAVrZU4NWRx4UEzTiDAs86op1m github.com/decred/dcrd/txscript/v3 v3.0.0-20200215023918-6247af01d5e3/go.mod h1:ATMA8K0SOo+M9Wdbr6dMnAd8qICJi6pXjGLlKsJc99E= github.com/decred/dcrd/txscript/v3 v3.0.0-20200215031403-6b2ce76f0986/go.mod h1:KsDS7McU1yFaCYR9LCIwk6YnE15YN3wJUDxhKdFqlsc= github.com/decred/dcrd/txscript/v3 v3.0.0-20200421213827-b60c60ffe98b/go.mod h1:vrm3R/AesmA9slTf0rFcwhD0SduAJAWxocyaWVi8dM0= -github.com/decred/dcrd/txscript/v3 v3.0.0-20200528191943-a2771b8ce693 h1:8QJvuZnGXKs1x2VftugOOApMyARYH4tnu4n4o+JASZo= -github.com/decred/dcrd/txscript/v3 v3.0.0-20200528191943-a2771b8ce693/go.mod h1:vrm3R/AesmA9slTf0rFcwhD0SduAJAWxocyaWVi8dM0= +github.com/decred/dcrd/txscript/v3 v3.0.0-20200607041702-62fa0661bd57 h1:UaIGTrn1bpdZVgULdkwxZhULw5T5Jm3+OPJDw4+/p5c= +github.com/decred/dcrd/txscript/v3 v3.0.0-20200607041702-62fa0661bd57/go.mod h1:vrm3R/AesmA9slTf0rFcwhD0SduAJAWxocyaWVi8dM0= github.com/decred/dcrd/wire v1.3.0 h1:X76I2/a8esUmxXmFpJpAvXEi014IA4twgwcOBeIS8lE= github.com/decred/dcrd/wire v1.3.0/go.mod h1:fnKGlUY2IBuqnpxx5dYRU5Oiq392OBqAuVjRVSkIoXM= github.com/decred/go-socks v1.1.0/go.mod h1:sDhHqkZH0X4JjSa02oYOGhcGHYp12FsY1jQ/meV8md0= @@ -105,6 +105,10 @@ github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ= +github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= diff --git a/webapi/admin.go b/webapi/admin.go new file mode 100644 index 00000000..056f4859 --- /dev/null +++ b/webapi/admin.go @@ -0,0 +1,69 @@ +package webapi + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/gorilla/sessions" +) + +// adminPage is the handler for "GET /admin". The admin template will be +// rendered if the current session is authenticated as an admin, otherwise the +// login template will be rendered. +func adminPage(c *gin.Context) { + session := c.MustGet("session").(*sessions.Session) + admin := session.Values["admin"] + + if admin == nil { + c.HTML(http.StatusUnauthorized, "login.html", gin.H{}) + return + } + + tickets, err := db.GetAllTickets() + if err != nil { + log.Errorf("GetAllTickets error: %v", err) + c.String(http.StatusInternalServerError, "Error getting tickets from db") + return + } + + c.HTML(http.StatusOK, "admin.html", gin.H{ + "Tickets": tickets, + }) +} + +// adminLogin is the handler for "POST /admin". If a valid password is provided, +// the current session will be authenticated as an admin. +func adminLogin(c *gin.Context) { + password := c.PostForm("password") + + if password != cfg.AdminPass { + log.Warnf("Failed login attempt from %s", c.ClientIP()) + c.HTML(http.StatusUnauthorized, "login.html", gin.H{ + "IncorrectPassword": true, + }) + return + } + + setAdminStatus(true, c) +} + +// adminLogout is the handler for "POST /admin/logout". The current session will +// have its admin authentication removed. +func adminLogout(c *gin.Context) { + setAdminStatus(nil, c) +} + +// setAdminStatus stores the authentication status of the current session. +func setAdminStatus(admin interface{}, c *gin.Context) { + session := c.MustGet("session").(*sessions.Session) + session.Values["admin"] = admin + err := session.Save(c.Request, c.Writer) + if err != nil { + log.Errorf("Error saving session: %v", err) + c.String(http.StatusInternalServerError, "Error saving session") + return + } + + c.Redirect(http.StatusFound, "/admin") + c.Abort() +} diff --git a/webapi/middleware.go b/webapi/middleware.go index 7b765514..65647998 100644 --- a/webapi/middleware.go +++ b/webapi/middleware.go @@ -2,16 +2,51 @@ package webapi import ( "net/http" + "strings" "github.com/decred/vspd/rpc" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" + "github.com/gorilla/sessions" ) type ticketHashRequest struct { TicketHash string `json:"tickethash" binding:"required"` } +// withSession middleware adds a gorilla session to the request context for +// downstream handlers to make use of. Sessions are used by admin pages to +// maintain authentication status. +func withSession(store *sessions.CookieStore) gin.HandlerFunc { + return func(c *gin.Context) { + session, err := store.Get(c.Request, "vspd-session") + if err != nil { + // "value is not valid" occurs if the cookie secret changes. This is + // common during development (eg. when using the test harness) but + // it should not occur in production. + if strings.Contains(err.Error(), "securecookie: the value is not valid") { + log.Warn("Cookie secret has changed. Generating new session.") + + // Persist the newly generated session. + err = store.Save(c.Request, c.Writer, session) + if err != nil { + log.Errorf("Error saving session: %v", err) + c.String(http.StatusInternalServerError, "Error saving session") + c.Abort() + return + } + } else { + log.Errorf("Session error: %v", err) + c.String(http.StatusInternalServerError, "Error getting session") + c.Abort() + return + } + } + + c.Set("session", session) + } +} + // withDcrdClient middleware adds a dcrd client to the request // context for downstream handlers to make use of. func withDcrdClient() gin.HandlerFunc { diff --git a/webapi/templates/admin.html b/webapi/templates/admin.html new file mode 100644 index 00000000..415aeb15 --- /dev/null +++ b/webapi/templates/admin.html @@ -0,0 +1,41 @@ +{{ template "header" . }} + +
+ +
+ + + + + + + + + + + + + + + + + {{ range .Tickets }} + + + + + + + + + + + + + + + {{ end }} +
HashCommitmentAddressFeeAddressIndexFeeAddressFeeAmountFeeExpirationConfirmedVoteChoicesVotingWIFFeeTxHexFeeTxHashFeeConfirmed
{{ printf "%.10s" .Hash }}...{{ printf "%.10s" .CommitmentAddress }}...{{ printf "%d" .FeeAddressIndex }}{{ printf "%.10s" .FeeAddress }}...{{ printf "%f" .FeeAmount }}{{ printf "%d" .FeeExpiration }}{{ printf "%t" .Confirmed }}{{ printf "%.10s" .VoteChoices }}...{{ printf "%.10s" .VotingWIF }}...{{ printf "%.10s" .FeeTxHex }}...{{ printf "%.10s" .FeeTxHash }}...{{ printf "%t" .FeeConfirmed }}
+ + + diff --git a/webapi/templates/header.html b/webapi/templates/header.html new file mode 100644 index 00000000..c45105a0 --- /dev/null +++ b/webapi/templates/header.html @@ -0,0 +1,45 @@ +{{define "header"}} + + + + + + + vspd + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +{{end}} diff --git a/webapi/templates/homepage.html b/webapi/templates/homepage.html index bd1daa5b..5b98a01f 100644 --- a/webapi/templates/homepage.html +++ b/webapi/templates/homepage.html @@ -1,55 +1,16 @@ - - - - - - - vspd - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Total tickets:{{ .TotalTickets }}
Fee confirmed tickets:{{ .FeeConfirmedTickets }}
VSP Fee:{{ .VSPFee }}% of vote reward
Network:{{ .Network }}
Support:{{ .SupportEmail }}
Pubkey:{{ printf "%x" .PubKey }}
VSP closed:{{ .VspClosed }}
-

Last updated: {{.UpdateTime}}

- - \ No newline at end of file +{{ template "header" . }} + + + + + + + + + +
Total tickets:{{ .TotalTickets }}
Fee confirmed tickets:{{ .FeeConfirmedTickets }}
VSP Fee:{{ .VSPFee }}% of vote reward
Network:{{ .Network }}
Support:{{ .SupportEmail }}
Pubkey:{{ printf "%x" .PubKey }}
VSP closed:{{ .VspClosed }}
+ +

Last updated: {{ .UpdateTime }}

+ + + diff --git a/webapi/templates/login.html b/webapi/templates/login.html new file mode 100644 index 00000000..7962681f --- /dev/null +++ b/webapi/templates/login.html @@ -0,0 +1,13 @@ +{{ template "header" . }} + +
+ + +
+ +{{ if .IncorrectPassword }} +

Incorrect password

+{{ end }} + + + diff --git a/webapi/webapi.go b/webapi/webapi.go index 5f2bc6d1..7e792234 100644 --- a/webapi/webapi.go +++ b/webapi/webapi.go @@ -11,11 +11,11 @@ import ( "sync" "time" + "github.com/decred/dcrd/chaincfg/v3" "github.com/decred/vspd/database" "github.com/decred/vspd/rpc" - - "github.com/decred/dcrd/chaincfg/v3" "github.com/gin-gonic/gin" + "github.com/gorilla/sessions" ) type Config struct { @@ -83,6 +83,12 @@ func Start(ctx context.Context, requestShutdownChan chan struct{}, shutdownWg *s return fmt.Errorf("failed to initialize fee address generator: %v", err) } + // Get the secret key used to initialize the cookie store. + cookieSecret, err := vdb.GetCookieSecret() + if err != nil { + return fmt.Errorf("GetCookieSecret error: %v", err) + } + // Create TCP listener. var listenConfig net.ListenConfig listener, err := listenConfig.Listen(ctx, "tcp", listen) @@ -92,7 +98,7 @@ func Start(ctx context.Context, requestShutdownChan chan struct{}, shutdownWg *s log.Infof("Listening on %s", listen) srv := http.Server{ - Handler: router(debugMode), + Handler: router(debugMode, cookieSecret), ReadTimeout: 5 * time.Second, // slow requests should not hold connections opened WriteTimeout: 60 * time.Second, // hung responses must die } @@ -155,7 +161,7 @@ func Start(ctx context.Context, requestShutdownChan chan struct{}, shutdownWg *s return nil } -func router(debugMode bool) *gin.Engine { +func router(debugMode bool, cookieSecret []byte) *gin.Engine { // With release mode enabled, gin will only read template files once and cache them. // With release mode disabled, templates will be reloaded on the fly. if !debugMode { @@ -180,7 +186,7 @@ func router(debugMode bool) *gin.Engine { router.Static("/public", "webapi/public/") // These routes have no extra middleware. They can be accessed by anybody. - router.GET("/", homepage) + router.GET("", homepage) router.GET("/api/vspinfo", vspInfo) // These API routes access dcrd and they need authentication. @@ -191,6 +197,16 @@ func router(debugMode bool) *gin.Engine { feeOnly.GET("/ticketstatus", ticketStatus) feeOnly.POST("/payfee", payFee) + // Create a cookie store for persisting admin session information. + cookieStore := sessions.NewCookieStore(cookieSecret) + + admin := router.Group("/admin").Use( + withSession(cookieStore), + ) + admin.GET("", adminPage) + admin.POST("", adminLogin) + admin.POST("/logout", adminLogout) + // These API routes access dcrd and the voting wallets, and they need // authentication. both := router.Group("/api").Use(