feat: password reset

This commit is contained in:
Krispire Admin
2026-06-02 18:37:08 +05:30
parent ac95a857bd
commit cf3a49458a
8 changed files with 303 additions and 39 deletions
+70
View File
@@ -53,6 +53,15 @@ type verifyReq struct {
Token string `json:"token"`
}
type setEmailReq struct {
Email string `json:"email"`
}
type changePasswordReq struct {
CurrentPassword string `json:"current_password"`
NewPassword string `json:"new_password"`
}
// --- handlers ---
// SignUp creates a user, plants the session cookie, returns the user.
@@ -172,6 +181,39 @@ func (h *AuthHandler) ResetPassword(w http.ResponseWriter, r *http.Request) {
response.JSON(w, http.StatusOK, map[string]bool{"ok": true})
}
// ChangePassword — protected — replaces the caller's password after
// verifying the current one. The new password bumps token_version, which
// invalidates every previously-issued JWT for this user. We immediately
// re-issue a fresh session cookie so the caller's current device stays
// logged in; every other device gets signed out on next request.
func (h *AuthHandler) ChangePassword(w http.ResponseWriter, r *http.Request) {
uid := middleware.UserID(r.Context())
var req changePasswordReq
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
response.Error(w, http.StatusBadRequest, "invalid JSON body")
return
}
// current_password is optional — accounts with no password on file
// (OAuth-only signups setting their first one) skip it. The service
// enforces verification when a hash exists.
v := validator.New()
validator.Required(v, "new_password", req.NewPassword)
validator.MinLength(v, "new_password", req.NewPassword, 8)
if !v.Valid() {
response.Error(w, http.StatusBadRequest, v.Error())
return
}
res, err := h.svc.ChangePassword(r.Context(), uid, req.CurrentPassword, req.NewPassword)
if err != nil {
h.renderAuthErr(w, err)
return
}
auth.Set(w, res.Token, res.Expires, h.cookies)
response.JSON(w, http.StatusOK, res.User)
}
// VerifyEmail flips email_verified_at after the user clicks the link.
func (h *AuthHandler) VerifyEmail(w http.ResponseWriter, r *http.Request) {
var req verifyReq
@@ -190,6 +232,34 @@ func (h *AuthHandler) VerifyEmail(w http.ResponseWriter, r *http.Request) {
response.JSON(w, http.StatusOK, map[string]bool{"ok": true})
}
// SetEmail — protected — sets (or replaces) the caller's email address.
// Used by OAuth-signup users who landed without one, and by anyone wanting
// to change their address. The new value is stored as unverified and a
// verification link is mailed out immediately.
func (h *AuthHandler) SetEmail(w http.ResponseWriter, r *http.Request) {
uid := middleware.UserID(r.Context())
var req setEmailReq
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
response.Error(w, http.StatusBadRequest, "invalid JSON body")
return
}
v := validator.New()
validator.Required(v, "email", req.Email)
validator.Email(v, "email", req.Email)
if !v.Valid() {
response.Error(w, http.StatusBadRequest, v.Error())
return
}
u, err := h.svc.SetEmail(r.Context(), uid, req.Email)
if err != nil {
h.renderAuthErr(w, err)
return
}
response.JSON(w, http.StatusOK, u)
}
// ResendVerification — protected — refires the verification email for the
// currently-logged-in user.
func (h *AuthHandler) ResendVerification(w http.ResponseWriter, r *http.Request) {