Commit c8e741d4 authored by Julien Schröter's avatar Julien Schröter Committed by Julien Schröter

Create site to let user reset password without knowing the old one

parent 1468866a
Pipeline #2052 passed with stage
in 8 minutes and 9 seconds
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Akamu Account Password Reset</title>
<style>
body {
width: 80%;
margin: 20px auto;
color: #555;
}
body,
form input {
font: 18px/20px -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
Helvetica Neue, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji,
Segoe UI Symbol;
}
.error {
color: #c00;
}
.success {
color: #0a0;
}
input[type="password"] {
margin: 10px 0;
padding: 5px 2px;
max-width: 350px;
width: 100%;
box-sizing: border-box;
border: 1px solid #ccc;
}
input[type="submit"] {
padding: 7px 12px;
background: #05d7d7;
border: 1px solid #ccc;
border-radius: 3px;
font-weight: bold;
color: #fff;
text-shadow: 0 0 1px #026464;
}
</style>
</head>
<body>
<img src="https://akamu.de/img/logo.svg" height="50px" width="64px" />
<div style="width: 100%; margin: 30px auto">
{{if .Done}}
<b class="success">Your password reset was successful.</b>
<p>You can log in with your new password in the App.</p>
{{else if .Valid}}
<b>Set a new password.</b>
{{range $_, $e := .Errors}}
<p class="error">{{ $e }}</p>
{{ end }}
<form method="POST">
<p>
<label for="password">Password:</label><br />
<input
type="password"
name="password"
id="password"
required
autofocus
/>
</p>
<p>
<label for="confirm-password">Confirm Password:</label><br />
<input
type="password"
name="confirm-password"
id="confirm-password"
required
/>
</p>
<input type="submit" value="Reset Password" />
</form>
{{else if .ExpiredTime}}
<b class="error">Your link has expired.</b>
<p>
Your link has expired since your request is older than 24h.<br />
Just create a new request to reset your password.
</p>
{{else if .ExpiredUsed}}
<b class="error">You already updated your password.</b>
<p>
Your link has expired since you already set a new password.<br />
If you did not change your password since you requested a password
reset, please contact
<a href="mailto:support@akamu.de">support@akamu.de</a>.
</p>
{{else if .Failure}}
<b class="error">An error occurred.</b>
<p>
Please try to reset your password again.<br />
If it repeatedly failes, contact the support at
<a href="mailto:support@akamu.de">support@akamu.de</a>.
</p>
{{else}}
<b class="error">Your request seems to be invalid.</b>
<p>
Check that you called the link as in your email.<br />
Get additional help from
<a href="mailto:support@akamu.de">support@akamu.de</a>.
</p>
{{ end }}
</div>
</body>
</html>
......@@ -15,6 +15,7 @@ import (
"github.com/getsentry/raven-go"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"gitlab.akamu.de/akamu/game-server-go/schemas"
)
......@@ -372,6 +373,88 @@ func ForgotPassword(repository UserQuery, tokenFactory otp.TokenFactory, templat
}
}
func GetResetPassword(repository UserQuery, tokenFactory otp.TokenFactory) gin.HandlerFunc {
return func(ctx *gin.Context) {
token, ok := ctx.GetQuery("token")
if !ok {
ctx.HTML(http.StatusBadRequest, "forgotPassword.tmpl", nil)
return
}
// Validate and get payload from token
userID, hash, err := tokenFactory.ParsePasswordResetToken(token)
if err == otp.ErrTokenExpired {
ctx.HTML(http.StatusGone, "forgotPassword.tmpl", gin.H{"ExpiredTime": true})
return
} else if err != nil {
ctx.HTML(http.StatusBadRequest, "forgotPassword.tmpl", nil)
return
}
// Check if user already updated the password
if cred, err := repository.SelectCredentialsByField(FieldID, userID); err != nil {
ctx.HTML(http.StatusInternalServerError, "forgotPassword.tmpl", gin.H{"Failure": true})
return
} else if cred.PasswordCipher != hash {
ctx.HTML(http.StatusGone, "forgotPassword.tmpl", gin.H{"ExpiredUsed": true})
return
}
ctx.HTML(http.StatusOK, "forgotPassword.tmpl", gin.H{"Valid": true})
}
}
func PostResetPassword(repository UserQuery, tokenFactory otp.TokenFactory) gin.HandlerFunc {
return func(ctx *gin.Context) {
token, ok := ctx.GetQuery("token")
if !ok {
ctx.HTML(http.StatusBadRequest, "forgotPassword.tmpl", nil)
return
}
userID, hash, err := tokenFactory.ParsePasswordResetToken(token)
if err == otp.ErrTokenExpired {
ctx.HTML(http.StatusGone, "forgotPassword.tmpl", gin.H{"ExpiredTime": true})
return
} else if err != nil {
ctx.HTML(http.StatusBadRequest, "forgotPassword.tmpl", nil)
return
}
var formData struct {
Password string `form:"password" binding:"required,password"`
Confirm string `form:"confirm-password" binding:"required,eqfield=Password"`
}
if err := ctx.ShouldBind(&formData); err != nil {
errs := err.(validator.ValidationErrors)
ctx.HTML(http.StatusBadRequest, "forgotPassword.tmpl", gin.H{"Valid": true, "Errors": errs.Translate(endpoint.GetTranslator())})
return
}
// Check that the password has not been updated since creation of the token
if cred, err := repository.SelectCredentialsByField(FieldID, userID); err != nil {
raven.CaptureError(err, nil)
ctx.HTML(http.StatusInternalServerError, "forgotPassword.tmpl", gin.H{"Failure": true})
return
} else if cred.PasswordCipher != hash {
ctx.HTML(http.StatusGone, "forgotPassword.tmpl", gin.H{"ExpiredUsed": true})
return
}
// Update password
if err := repository.UpdatePassword(userID, formData.Password); err != nil {
raven.CaptureError(err, nil)
ctx.HTML(http.StatusInternalServerError, "forgotPassword.tmpl", gin.H{"Failure": true})
return
}
ctx.HTML(http.StatusOK, "forgotPassword.tmpl", gin.H{"Done": true})
}
}
func sendRegistrationVerification(tokenFactory otp.TokenFactory, templatemail *sendmail.TemplateMail, userID uint32, username, email string) error {
// Create token
token, err := tokenFactory.CreateConfirmRegistrationToken(userID, email)
......
......@@ -279,6 +279,10 @@ func SetupRoutes(courseRepository course.CourseQuery, flashcardRepository flashc
ctx.Header("Allow", "POST")
ctx.Status(200)
})
router.GET("/forgotpassword/reset", user.GetResetPassword(userRepository, tokenFactory))
router.POST("/forgotpassword/reset", user.PostResetPassword(userRepository, tokenFactory))
router.PATCH("/refreshjwt", authMiddleware.RefreshHandler)
router.OPTIONS("/refreshjwt", func(ctx *gin.Context) {
ctx.Header("Allow", "PATCH")
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment