allow tokens to be embeded in links, expire password tokens
This commit is contained in:
parent
cef06f864b
commit
cf851b5021
2
main.go
2
main.go
@ -14,6 +14,7 @@ var tpl *template.Template
|
|||||||
var cookieHandler = securecookie.New(
|
var cookieHandler = securecookie.New(
|
||||||
securecookie.GenerateRandomKey(64),
|
securecookie.GenerateRandomKey(64),
|
||||||
securecookie.GenerateRandomKey(32))
|
securecookie.GenerateRandomKey(32))
|
||||||
|
var passwordTokenSet map[string]bool
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
Conf, _ = LoadConfig()
|
Conf, _ = LoadConfig()
|
||||||
@ -54,6 +55,7 @@ func main() {
|
|||||||
Conf.MaxID = i
|
Conf.MaxID = i
|
||||||
log.Printf("Max employeeNumber set to %v\n", Conf.MaxID)
|
log.Printf("Max employeeNumber set to %v\n", Conf.MaxID)
|
||||||
}
|
}
|
||||||
|
passwordTokenSet = make(map[string]bool)
|
||||||
log.Printf("Guildgate starting on %v\n", Conf.Port)
|
log.Printf("Guildgate starting on %v\n", Conf.Port)
|
||||||
if Conf.Tls {
|
if Conf.Tls {
|
||||||
log.Printf("Starting TLS\n")
|
log.Printf("Starting TLS\n")
|
||||||
|
7
reset.go
7
reset.go
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"gopkg.in/gomail.v2"
|
"gopkg.in/gomail.v2"
|
||||||
)
|
)
|
||||||
@ -23,7 +24,7 @@ func resetLookup(res http.ResponseWriter, req *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Printf("Found user %v, generating password token\n", uname)
|
log.Printf("Found user %v, generating password token\n", uname)
|
||||||
token, err := generateToken(uname)
|
token, err := generatePasswordToken(uname)
|
||||||
fmt.Println(token)
|
fmt.Println(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error generating password token %v\n", err)
|
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")
|
token := req.FormValue("token")
|
||||||
newPass := req.FormValue("new_password")
|
newPass := req.FormValue("new_password")
|
||||||
|
|
||||||
user, err := validateToken(token)
|
user, err := validateToken(token, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error validing password reset token: %v\n", err)
|
log.Printf("Error validing password reset token: %v\n", err)
|
||||||
http.Redirect(res, req, "/reset/error", 302)
|
http.Redirect(res, req, "/reset/error", 302)
|
||||||
@ -74,10 +75,12 @@ func sendMail(recp string, uname string, token string) error {
|
|||||||
Recipient string
|
Recipient string
|
||||||
Name string
|
Name string
|
||||||
Token string
|
Token string
|
||||||
|
TokenURL string
|
||||||
}{
|
}{
|
||||||
Recipient: recp,
|
Recipient: recp,
|
||||||
Name: uname,
|
Name: uname,
|
||||||
Token: token,
|
Token: token,
|
||||||
|
TokenURL: url.QueryEscape(token),
|
||||||
}
|
}
|
||||||
|
|
||||||
m := gomail.NewMessage()
|
m := gomail.NewMessage()
|
||||||
|
@ -47,7 +47,7 @@ func signup(res http.ResponseWriter, req *http.Request) {
|
|||||||
|
|
||||||
if Conf.Secret != "" && Conf.Secret != secret {
|
if Conf.Secret != "" && Conf.Secret != secret {
|
||||||
//Checking it as a token
|
//Checking it as a token
|
||||||
_, err := validateToken(secret)
|
_, err := validateToken(secret, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Bad secret entered: %v\n", err)
|
log.Printf("Bad secret entered: %v\n", err)
|
||||||
genericErrorPage(res, "User Creation Failure", "Unregistered", false, "Invalid Secret Token.", "to create account")
|
genericErrorPage(res, "User Creation Failure", "Unregistered", false, "Invalid Secret Token.", "to create account")
|
||||||
|
@ -19,9 +19,10 @@
|
|||||||
<td>Confirm Password:</td>
|
<td>Confirm Password:</td>
|
||||||
<td><input type="password" name="confirm_password" id="confirm_password" onchange="check()"/></td>
|
<td><input type="password" name="confirm_password" id="confirm_password" onchange="check()"/></td>
|
||||||
<td><span id='message'></span></td>
|
<td><span id='message'></span></td>
|
||||||
|
<td><input type="checkbox" onclick="showPass()">Show Passwords</td>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Secret:</td>
|
<td>Secret Token:</td>
|
||||||
<td><input type="password" placeholder="secret" name="secret"></td>
|
<td><input type="password" value="{{.Secret}}" name="secret"></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><input type="submit" value="Submit"></td>
|
<td><input type="submit" value="Submit"></td>
|
||||||
@ -45,6 +46,20 @@ function check() {
|
|||||||
} else {
|
} else {
|
||||||
document.getElementById('message').innerHTML = "Passwords don't match";
|
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";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
{{ template "footer" .}}
|
{{ template "footer" .}}
|
||||||
|
@ -6,4 +6,7 @@ Hi {{.Name}},
|
|||||||
Token:
|
Token:
|
||||||
{{.Token}}
|
{{.Token}}
|
||||||
|
|
||||||
|
You may also use the following link:
|
||||||
|
http://localhost/reset/form?token={{.TokenURL}}
|
||||||
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<form action="/reset/form" method="POST" novalidate>
|
<form action="/reset/form" method="POST" novalidate>
|
||||||
<div>
|
<div>
|
||||||
<label>Password Token:</label>
|
<label>Password Token:</label>
|
||||||
<input type="text" name="token">
|
<input type="text" name="token" value="{{.Token}}">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label>New Password:</label>
|
<label>New Password:</label>
|
||||||
@ -17,6 +17,9 @@
|
|||||||
<input type="password" name="confirm_password" id="confirm_password" onchange="check()"/>
|
<input type="password" name="confirm_password" id="confirm_password" onchange="check()"/>
|
||||||
<span id='message'></span>
|
<span id='message'></span>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" onclick="showPass()">Show Passwords
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<input type="submit" value="Reset">
|
<input type="submit" value="Reset">
|
||||||
</div>
|
</div>
|
||||||
@ -38,6 +41,20 @@ function check() {
|
|||||||
document.getElementById('message').innerHTML = "Passwords don't match";
|
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";
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{{ template "footer" .}}
|
{{ template "footer" .}}
|
||||||
|
@ -7,5 +7,7 @@
|
|||||||
<textarea id="token_area" name="generated_token" rows="4" cols="50">
|
<textarea id="token_area" name="generated_token" rows="4" cols="50">
|
||||||
{{ .Token }}
|
{{ .Token }}
|
||||||
</textarea>
|
</textarea>
|
||||||
|
<p>You can also share this link: <a href="http://localhost:8080/register?secret={{.TokenURL}}">http://localhost:8080/register?secret={{.TokenURL}}</a>
|
||||||
|
</p>
|
||||||
{{template "footer" .}}
|
{{template "footer" .}}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
30
token.go
30
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(
|
token, err := jwt.ParseWithClaims(
|
||||||
strings.TrimSpace(tok),
|
strings.TrimSpace(tok),
|
||||||
&tokenClaim{},
|
&tokenClaim{},
|
||||||
@ -49,6 +67,16 @@ func validateToken(tok string) (string, error) {
|
|||||||
if claims.ExpiresAt < time.Now().UTC().Unix() {
|
if claims.ExpiresAt < time.Now().UTC().Unix() {
|
||||||
return "", errors.New("Token has expired")
|
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)
|
log.Printf("Valid token received; sponsored by %v\n", claims.Sponsor)
|
||||||
return claims.Sponsor, nil
|
return claims.Sponsor, nil
|
||||||
}
|
}
|
||||||
|
21
web.go
21
web.go
@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
func profilePage(res http.ResponseWriter, req *http.Request) {
|
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) {
|
func resetPageBack(res http.ResponseWriter, req *http.Request) {
|
||||||
log.Println("GET /reset/form")
|
log.Println("GET /reset/form")
|
||||||
u := getUserName(req)
|
u := getUserName(req)
|
||||||
|
token := ""
|
||||||
if u != "" {
|
if u != "" {
|
||||||
http.Redirect(res, req, "/", 302) //TODO create password change form, direct to that
|
http.Redirect(res, req, "/", 302) //TODO create password change form, direct to that
|
||||||
} else {
|
} else {
|
||||||
|
keys, ok := req.URL.Query()["token"]
|
||||||
|
if !ok || len(keys[0]) < 1 {
|
||||||
|
token = ""
|
||||||
|
} else {
|
||||||
|
token = keys[0]
|
||||||
|
}
|
||||||
data := struct {
|
data := struct {
|
||||||
Title string
|
Title string
|
||||||
Username string
|
Username string
|
||||||
LoggedIn bool
|
LoggedIn bool
|
||||||
|
Token string
|
||||||
}{
|
}{
|
||||||
"Reset Password",
|
"Reset Password",
|
||||||
"Unregistered",
|
"Unregistered",
|
||||||
false,
|
false,
|
||||||
|
token,
|
||||||
}
|
}
|
||||||
tpl.ExecuteTemplate(res, "reset_password_page_back", data)
|
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) {
|
func signupPage(res http.ResponseWriter, req *http.Request) {
|
||||||
log.Println("GET /register")
|
log.Println("GET /register")
|
||||||
u := getUserName(req)
|
u := getUserName(req)
|
||||||
|
secret := ""
|
||||||
if u != "" {
|
if u != "" {
|
||||||
http.Redirect(res, req, "/", 302)
|
http.Redirect(res, req, "/", 302)
|
||||||
} else {
|
} else {
|
||||||
|
keys, ok := req.URL.Query()["secret"]
|
||||||
|
if !ok || len(keys[0]) < 1 {
|
||||||
|
secret = ""
|
||||||
|
} else {
|
||||||
|
secret = keys[0]
|
||||||
|
}
|
||||||
data := struct {
|
data := struct {
|
||||||
Title string
|
Title string
|
||||||
Username string
|
Username string
|
||||||
LoggedIn bool
|
LoggedIn bool
|
||||||
|
Secret string
|
||||||
}{
|
}{
|
||||||
"Register",
|
"Register",
|
||||||
"Unregistered",
|
"Unregistered",
|
||||||
false,
|
false,
|
||||||
|
secret,
|
||||||
}
|
}
|
||||||
tpl.ExecuteTemplate(res, "register", data)
|
tpl.ExecuteTemplate(res, "register", data)
|
||||||
}
|
}
|
||||||
@ -212,11 +231,13 @@ func tokenPage(res http.ResponseWriter, req *http.Request) {
|
|||||||
Username string
|
Username string
|
||||||
LoggedIn bool
|
LoggedIn bool
|
||||||
Token string
|
Token string
|
||||||
|
TokenURL string
|
||||||
}{
|
}{
|
||||||
"Token Generation",
|
"Token Generation",
|
||||||
u,
|
u,
|
||||||
true,
|
true,
|
||||||
token,
|
token,
|
||||||
|
url.QueryEscape(token),
|
||||||
}
|
}
|
||||||
tpl.ExecuteTemplate(res, "token", data)
|
tpl.ExecuteTemplate(res, "token", data)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user