feat(auth): add password reset #178
This commit was merged in pull request #180.
This commit is contained in:
139
service/auth.go
139
service/auth.go
@@ -7,8 +7,10 @@ import (
|
||||
"database/sql"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/mail"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -149,12 +151,32 @@ func HandleSignUpVerifyResponsePage(db *sql.DB) http.HandlerFunc {
|
||||
func HandleChangePasswordPage(db *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
isPasswordReset := r.URL.Query().Has("token")
|
||||
|
||||
user := utils.GetUserFromSession(db, r)
|
||||
if user == nil {
|
||||
if user == nil && !isPasswordReset {
|
||||
utils.DoRedirect(w, r, "/auth/signin")
|
||||
} else {
|
||||
userComp := UserInfoComp(user)
|
||||
comp := auth.ChangePasswordComp()
|
||||
comp := auth.ChangePasswordComp(isPasswordReset)
|
||||
err := template.Layout(comp, userComp).Render(r.Context(), w)
|
||||
if err != nil {
|
||||
utils.LogError("Failed to render change password page", err)
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HandleResetPasswordPage(db *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
user := utils.GetUserFromSession(db, r)
|
||||
if user != nil {
|
||||
utils.DoRedirect(w, r, "/auth/signin")
|
||||
} else {
|
||||
userComp := UserInfoComp(nil)
|
||||
comp := auth.ResetPasswordComp()
|
||||
err := template.Layout(comp, userComp).Render(r.Context(), w)
|
||||
if err != nil {
|
||||
utils.LogError("Failed to render change password page", err)
|
||||
@@ -402,6 +424,7 @@ func HandleVerifyResendComp(db *sql.DB) http.HandlerFunc {
|
||||
|
||||
func HandleChangePasswordComp(db *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
user := utils.GetUserFromSession(db, r)
|
||||
if user == nil {
|
||||
utils.DoRedirect(w, r, "/auth/signin")
|
||||
@@ -453,6 +476,118 @@ func HandleChangePasswordComp(db *sql.DB) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func HandleActualResetPasswordComp(db *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
pageUrl, err := url.Parse(r.Header.Get("HX-Current-URL"))
|
||||
if err != nil {
|
||||
utils.LogError("Could not get current URL", err)
|
||||
utils.TriggerToast(w, r, "error", "Internal Server Error")
|
||||
return
|
||||
}
|
||||
|
||||
token := pageUrl.Query().Get("token")
|
||||
if token == "" {
|
||||
utils.TriggerToast(w, r, "error", "No token")
|
||||
return
|
||||
}
|
||||
|
||||
newPass := r.FormValue("new-password")
|
||||
|
||||
err = checkPassword(newPass)
|
||||
if err != nil {
|
||||
utils.TriggerToast(w, r, "error", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
userId uuid.UUID
|
||||
salt []byte
|
||||
)
|
||||
|
||||
err = db.QueryRow(`
|
||||
SELECT u.user_uuid, salt
|
||||
FROM user_token t
|
||||
INNER JOIN user u ON t.user_uuid = u.user_uuid
|
||||
WHERE t.token = ?
|
||||
AND t.type = 'password_reset'
|
||||
AND t.expires_at > datetime()
|
||||
`, token).Scan(&userId, &salt)
|
||||
if err != nil {
|
||||
slog.Warn("Could not get user from token: " + err.Error())
|
||||
utils.TriggerToast(w, r, "error", "Invalid token")
|
||||
return
|
||||
}
|
||||
|
||||
_, err = db.Exec("DELETE FROM user_token WHERE token = ? AND type = 'password_reset'", token)
|
||||
if err != nil {
|
||||
utils.LogError("Could not delete token", err)
|
||||
utils.TriggerToast(w, r, "error", "Internal Server Error")
|
||||
return
|
||||
}
|
||||
|
||||
passHash := getHashPassword(newPass, salt)
|
||||
|
||||
_, err = db.Exec("UPDATE user SET password = ? WHERE user_uuid = ?", passHash, userId)
|
||||
if err != nil {
|
||||
utils.LogError("Could not update password", err)
|
||||
utils.TriggerToast(w, r, "error", "Internal Server Error")
|
||||
return
|
||||
}
|
||||
|
||||
utils.TriggerToast(w, r, "success", "Password changed")
|
||||
}
|
||||
}
|
||||
func HandleResetPasswordComp(db *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
email := r.FormValue("email")
|
||||
if email == "" {
|
||||
utils.TriggerToast(w, r, "error", "Please enter an email")
|
||||
return
|
||||
}
|
||||
|
||||
var b []byte = make([]byte, 32)
|
||||
_, err := rand.Reader.Read(b)
|
||||
if err != nil {
|
||||
utils.LogError("Could not generate token", err)
|
||||
return
|
||||
}
|
||||
token := base64.StdEncoding.EncodeToString(b)
|
||||
|
||||
res, err := db.Exec(`
|
||||
INSERT INTO user_token (user_uuid, type, token, created_at, expires_at)
|
||||
SELECT user_uuid, 'password_reset', ?, datetime(), datetime('now', '+15 minute')
|
||||
FROM user
|
||||
WHERE email = ?
|
||||
`, token, email)
|
||||
if err != nil {
|
||||
utils.LogError("Could not insert token", err)
|
||||
utils.TriggerToast(w, r, "error", "Internal Server Error")
|
||||
return
|
||||
}
|
||||
|
||||
i, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
utils.LogError("Could not get rows affected", err)
|
||||
utils.TriggerToast(w, r, "error", "Internal Server Error")
|
||||
return
|
||||
}
|
||||
|
||||
if i != 0 {
|
||||
var mail strings.Builder
|
||||
err = tempMail.ResetPassword(token).Render(context.Background(), &mail)
|
||||
if err != nil {
|
||||
utils.LogError("Could not render reset password email", err)
|
||||
utils.TriggerToast(w, r, "error", "Internal Server Error")
|
||||
return
|
||||
}
|
||||
utils.SendMail(email, "Reset Password", mail.String())
|
||||
}
|
||||
|
||||
utils.TriggerToast(w, r, "info", "If the email exists, an email has been sent")
|
||||
}
|
||||
}
|
||||
func sendVerificationEmail(db *sql.DB, userId string, email string) {
|
||||
|
||||
var token string
|
||||
|
||||
Reference in New Issue
Block a user