From cf851b5021db9308b7b0d14e2b258bcbc513b47c Mon Sep 17 00:00:00 2001 From: Steve Date: Tue, 2 Mar 2021 14:26:58 -0500 Subject: [PATCH] allow tokens to be embeded in links, expire password tokens --- main.go | 2 ++ reset.go | 7 +++++-- session.go | 2 +- templates/register.html | 19 +++++++++++++++++-- templates/reset_pass.eml | 3 +++ templates/reset_password_back.html | 19 ++++++++++++++++++- templates/token.html | 2 ++ token.go | 30 +++++++++++++++++++++++++++++- web.go | 21 +++++++++++++++++++++ 9 files changed, 98 insertions(+), 7 deletions(-) diff --git a/main.go b/main.go index b8e33b1..c7b8eae 100644 --- a/main.go +++ b/main.go @@ -14,6 +14,7 @@ var tpl *template.Template var cookieHandler = securecookie.New( securecookie.GenerateRandomKey(64), securecookie.GenerateRandomKey(32)) +var passwordTokenSet map[string]bool func main() { Conf, _ = LoadConfig() @@ -54,6 +55,7 @@ func main() { Conf.MaxID = i log.Printf("Max employeeNumber set to %v\n", Conf.MaxID) } + passwordTokenSet = make(map[string]bool) log.Printf("Guildgate starting on %v\n", Conf.Port) if Conf.Tls { log.Printf("Starting TLS\n") diff --git a/reset.go b/reset.go index bdb344d..163861a 100644 --- a/reset.go +++ b/reset.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "net/http" + "net/url" "gopkg.in/gomail.v2" ) @@ -23,7 +24,7 @@ func resetLookup(res http.ResponseWriter, req *http.Request) { return } log.Printf("Found user %v, generating password token\n", uname) - token, err := generateToken(uname) + token, err := generatePasswordToken(uname) fmt.Println(token) if err != nil { log.Printf("Error generating password token %v\n", err) @@ -44,7 +45,7 @@ func reset(res http.ResponseWriter, req *http.Request) { token := req.FormValue("token") newPass := req.FormValue("new_password") - user, err := validateToken(token) + user, err := validateToken(token, true) if err != nil { log.Printf("Error validing password reset token: %v\n", err) http.Redirect(res, req, "/reset/error", 302) @@ -74,10 +75,12 @@ func sendMail(recp string, uname string, token string) error { Recipient string Name string Token string + TokenURL string }{ Recipient: recp, Name: uname, Token: token, + TokenURL: url.QueryEscape(token), } m := gomail.NewMessage() diff --git a/session.go b/session.go index 8d9db32..95f1015 100644 --- a/session.go +++ b/session.go @@ -47,7 +47,7 @@ func signup(res http.ResponseWriter, req *http.Request) { if Conf.Secret != "" && Conf.Secret != secret { //Checking it as a token - _, err := validateToken(secret) + _, err := validateToken(secret, false) if err != nil { log.Printf("Bad secret entered: %v\n", err) genericErrorPage(res, "User Creation Failure", "Unregistered", false, "Invalid Secret Token.", "to create account") diff --git a/templates/register.html b/templates/register.html index 6eb140a..0ef0c5d 100644 --- a/templates/register.html +++ b/templates/register.html @@ -19,9 +19,10 @@ Confirm Password: + Show Passwords - Secret: - + Secret Token: + @@ -45,6 +46,20 @@ function check() { } else { document.getElementById('message').innerHTML = "Passwords don't match"; } +} +function showPass() { + var x = document.getElementById("password"); + if (x.type === "password") { + x.type = "text"; + } else { + x.type = "password"; + } + var x = document.getElementById("confirm_password"); + if (x.type === "password") { + x.type = "text"; + } else { + x.type = "password"; + } } {{ template "footer" .}} diff --git a/templates/reset_pass.eml b/templates/reset_pass.eml index e80ab39..cd52c85 100644 --- a/templates/reset_pass.eml +++ b/templates/reset_pass.eml @@ -6,4 +6,7 @@ Hi {{.Name}}, Token: {{.Token}} + You may also use the following link: + http://localhost/reset/form?token={{.TokenURL}} + {{end}} diff --git a/templates/reset_password_back.html b/templates/reset_password_back.html index a700039..5cf69fe 100644 --- a/templates/reset_password_back.html +++ b/templates/reset_password_back.html @@ -6,7 +6,7 @@
- +
@@ -17,6 +17,9 @@
+
+ Show Passwords +
@@ -38,6 +41,20 @@ function check() { document.getElementById('message').innerHTML = "Passwords don't match"; } } +function showPass() { + var x = document.getElementById("password"); + if (x.type === "password") { + x.type = "text"; + } else { + x.type = "password"; + } + var x = document.getElementById("confirm_password"); + if (x.type === "password") { + x.type = "text"; + } else { + x.type = "password"; + } +} {{ template "footer" .}} diff --git a/templates/token.html b/templates/token.html index 7b05cf7..5e63f15 100644 --- a/templates/token.html +++ b/templates/token.html @@ -7,5 +7,7 @@ +

You can also share this link: http://localhost:8080/register?secret={{.TokenURL}} +

{{template "footer" .}} {{ end }} diff --git a/token.go b/token.go index 9de2dbf..529695b 100644 --- a/token.go +++ b/token.go @@ -31,7 +31,25 @@ func generateToken(sponsor string) (string, error) { } } -func validateToken(tok string) (string, error) { +func generatePasswordToken(sponsor string) (string, error) { + claim := tokenClaim{ + Sponsor: sponsor, + StandardClaims: jwt.StandardClaims{ + ExpiresAt: time.Now().UTC().Unix() + 3600, + Issuer: "GuildGate", + }, + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claim) + signedToken, err := token.SignedString([]byte(Conf.Secret)) + if err != nil { + return "", err + } else { + passwordTokenSet[signedToken] = true + return signedToken, nil + } +} + +func validateToken(tok string, pass bool) (string, error) { token, err := jwt.ParseWithClaims( strings.TrimSpace(tok), &tokenClaim{}, @@ -49,6 +67,16 @@ func validateToken(tok string) (string, error) { if claims.ExpiresAt < time.Now().UTC().Unix() { return "", errors.New("Token has expired") } + if pass { + if !passwordTokenSet[tok] { + return "", errors.New("Password token already used") + } else { + log.Printf("Valid Password token received; sponsored by %v\n", claims.Sponsor) + delete(passwordTokenSet, tok) + return claims.Sponsor, nil + } + } + log.Printf("Valid token received; sponsored by %v\n", claims.Sponsor) return claims.Sponsor, nil } diff --git a/web.go b/web.go index 98c8e03..7aa5896 100644 --- a/web.go +++ b/web.go @@ -3,6 +3,7 @@ package main import ( "log" "net/http" + "net/url" ) func profilePage(res http.ResponseWriter, req *http.Request) { @@ -124,17 +125,26 @@ func resetPageFront(res http.ResponseWriter, req *http.Request) { func resetPageBack(res http.ResponseWriter, req *http.Request) { log.Println("GET /reset/form") u := getUserName(req) + token := "" if u != "" { http.Redirect(res, req, "/", 302) //TODO create password change form, direct to that } else { + keys, ok := req.URL.Query()["token"] + if !ok || len(keys[0]) < 1 { + token = "" + } else { + token = keys[0] + } data := struct { Title string Username string LoggedIn bool + Token string }{ "Reset Password", "Unregistered", false, + token, } tpl.ExecuteTemplate(res, "reset_password_page_back", data) } @@ -152,17 +162,26 @@ func resetErrorPage(res http.ResponseWriter, req *http.Request) { func signupPage(res http.ResponseWriter, req *http.Request) { log.Println("GET /register") u := getUserName(req) + secret := "" if u != "" { http.Redirect(res, req, "/", 302) } else { + keys, ok := req.URL.Query()["secret"] + if !ok || len(keys[0]) < 1 { + secret = "" + } else { + secret = keys[0] + } data := struct { Title string Username string LoggedIn bool + Secret string }{ "Register", "Unregistered", false, + secret, } tpl.ExecuteTemplate(res, "register", data) } @@ -212,11 +231,13 @@ func tokenPage(res http.ResponseWriter, req *http.Request) { Username string LoggedIn bool Token string + TokenURL string }{ "Token Generation", u, true, token, + url.QueryEscape(token), } tpl.ExecuteTemplate(res, "token", data) }