feat(auth): use one time token instead of user id #158 #162

Merged
tim merged 1 commits from 158-one-time-token into master 2024-09-09 21:04:08 +00:00
3 changed files with 53 additions and 12 deletions

View File

@@ -0,0 +1,11 @@
-- E.G. email-verifications, password-resets, unsubscribe-from-newsletter etc.
CREATE TABLE user_token (
user_uuid TEXT NOT NULL UNIQUE,
type TEXT NOT NULL,
token TEXT NOT NULL UNIQUE PRIMARY KEY,
created_at DATETIME NOT NULL,
expires_at DATETIME
);

View File

@@ -78,20 +78,38 @@ func HandleSignUpVerifyPage(db *sql.DB) http.HandlerFunc {
func HandleSignUpVerifyResponsePage(db *sql.DB) http.HandlerFunc { func HandleSignUpVerifyResponsePage(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
code := r.URL.Query().Get("code") token := r.URL.Query().Get("token")
if code == "" { if token == "" {
utils.DoRedirect(w, r, "/auth/verify") utils.DoRedirect(w, r, "/auth/verify")
return return
} }
userId, err := uuid.Parse(code) result, err := db.Exec(`
UPDATE user
SET email_verified = true, email_verified_at = datetime()
WHERE user_uuid = (
SELECT user_uuid
FROM user_token
WHERE type = "email_verify"
AND token = ?
);
`, token)
if err != nil { if err != nil {
utils.DoRedirect(w, r, "/auth/verify") slog.Error("Could not update user: " + err.Error())
return
}
i, err := result.RowsAffected()
if err != nil {
slog.Error("Could not get rows affected: " + err.Error())
return return
} }
_, err = db.Exec("UPDATE user SET email_verified = true, email_verified_at = datetime() WHERE user_uuid = ?", userId) if i == 0 {
utils.DoRedirect(w, r, "/") utils.DoRedirect(w, r, "/")
} else {
utils.DoRedirect(w, r, "/auth/signin")
}
} }
} }
@@ -268,19 +286,31 @@ func HandleVerifyResendComp(db *sql.DB) http.HandlerFunc {
} }
func sendVerificationEmail(db *sql.DB, r *http.Request, userId string, email string) { func sendVerificationEmail(db *sql.DB, r *http.Request, userId string, email string) {
registerComp := tempMail.Register(userId) var b []byte = make([]byte, 32)
_, err := rand.Reader.Read(b)
if err != nil {
slog.Error("Could not generate token: " + err.Error())
return
}
token := base64.StdEncoding.EncodeToString(b)
var writer strings.Builder _, err = db.Exec("INSERT INTO user_token (user_uuid, type, token, created_at) VALUES (?, 'email_verify', ?, datetime())", userId, token)
if err != nil {
slog.Error("Could not insert token: " + err.Error())
return
}
registerComp.Render(r.Context(), &writer) var w strings.Builder
utils.SendMail(email, "Welcome to ME-FIT", writer.String()) tempMail.Register(token).Render(r.Context(), &w)
utils.SendMail(email, "Welcome to ME-FIT", w.String())
} }
func tryCreateSessionAndSetCookie(r *http.Request, w http.ResponseWriter, db *sql.DB, user_uuid uuid.UUID) bool { func tryCreateSessionAndSetCookie(r *http.Request, w http.ResponseWriter, db *sql.DB, user_uuid uuid.UUID) bool {
var session_id_bytes []byte = make([]byte, 32) var session_id_bytes []byte = make([]byte, 32)
_, err := rand.Reader.Read(session_id_bytes) _, err := rand.Reader.Read(session_id_bytes)
if err != nil { if err != nil {
slog.Error("Could not generate session ID: %v", err) slog.Error("Could not generate session ID: " + err.Error())
auth.Error("Internal Server Error").Render(r.Context(), w) auth.Error("Internal Server Error").Render(r.Context(), w)
return false return false
} }

View File

@@ -12,7 +12,7 @@ templ Register(mailCode string) {
</head> </head>
<body> <body>
<h4>Thank you for Sign Up!</h4> <h4>Thank you for Sign Up!</h4>
<p>Click <a href={ templ.URL(utils.BaseUrl + "/auth/verify-email?code=" + mailCode) }>here</a> to verify your account.</p> <p>Click <a href={ templ.URL(utils.BaseUrl + "/auth/verify-email?token=" + mailCode) }>here</a> to verify your account.</p>
<p>Kind regards</p> <p>Kind regards</p>
</body> </body>
</html> </html>