131 lines
4.1 KiB
Go
131 lines
4.1 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/identity"
|
|
)
|
|
|
|
type PasswordChangeSessionIssuer interface {
|
|
Change(context.Context, identity.PasswordChangeCommand) (identity.Session, error)
|
|
}
|
|
|
|
type PasswordChangeConfig struct {
|
|
SessionSecret, CookieSecure, PublicBaseURL string
|
|
}
|
|
|
|
type authPasswordChangeHandler struct {
|
|
config PasswordChangeConfig
|
|
authorizer *PlatformAuthorizer
|
|
changer PasswordChangeSessionIssuer
|
|
}
|
|
|
|
func NewAuthPasswordChangeHandler(config PasswordChangeConfig, authorizer *PlatformAuthorizer, changer PasswordChangeSessionIssuer) (http.Handler, error) {
|
|
if authorizer == nil || changer == nil || strings.TrimSpace(config.SessionSecret) == "" {
|
|
return nil, fmt.Errorf("auth/password/change: authorizer, password changer, and session secret are required")
|
|
}
|
|
return &authPasswordChangeHandler{config: config, authorizer: authorizer, changer: changer}, nil
|
|
}
|
|
|
|
func (handler *authPasswordChangeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/api/auth/password/change" {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
if r.Method != http.MethodPost {
|
|
w.Header().Set("Allow", http.MethodPost)
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
session, err := handler.authorizer.Authorize(r, PlatformApp)
|
|
if err != nil {
|
|
writePasswordChangeError(w, err)
|
|
return
|
|
}
|
|
var body struct{ CurrentPassword, NewPassword, ConfirmPassword any }
|
|
if json.NewDecoder(r.Body).Decode(&body) != nil {
|
|
writePasswordChangeError(w, &identity.PasswordChangeError{Reason: identity.PasswordChangeInvalidInput})
|
|
return
|
|
}
|
|
current := passwordChangeString(body.CurrentPassword)
|
|
next := passwordChangeString(body.NewPassword)
|
|
confirm := passwordChangeString(body.ConfirmPassword)
|
|
if current == "" || next == "" || confirm == "" {
|
|
writePasswordChangeError(w, &identity.PasswordChangeError{Reason: identity.PasswordChangeInvalidInput})
|
|
return
|
|
}
|
|
if next != confirm {
|
|
writePasswordJSON(w, http.StatusBadRequest, map[string]any{"error": "两次输入的新密码不一致。"})
|
|
return
|
|
}
|
|
nextSession, err := handler.changer.Change(r.Context(), identity.PasswordChangeCommand{AccountID: session.User.ID, CurrentPassword: current, NewPassword: next})
|
|
if err != nil {
|
|
writePasswordChangeError(w, err)
|
|
return
|
|
}
|
|
raw, err := json.Marshal(nextSession)
|
|
if err != nil {
|
|
writePasswordChangeError(w, err)
|
|
return
|
|
}
|
|
signed, err := identity.Sign(raw, handler.config.SessionSecret)
|
|
if err != nil {
|
|
writePasswordChangeError(w, err)
|
|
return
|
|
}
|
|
secure := identity.ResolveSecureCookie(handler.config.CookieSecure, handler.config.PublicBaseURL, absoluteRequestURL(r))
|
|
writes, err := identity.SetSessionCookies(signed, time.Unix(nextSession.ExpiresAt, 0).UTC(), secure)
|
|
if err != nil {
|
|
writePasswordChangeError(w, err)
|
|
return
|
|
}
|
|
payload, err := json.Marshal(map[string]any{"ok": true, "user": passwordPublicUser(nextSession.User)})
|
|
if err != nil {
|
|
writePasswordChangeError(w, err)
|
|
return
|
|
}
|
|
for _, write := range writes {
|
|
http.SetCookie(w, transportCookie(write))
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write(payload)
|
|
}
|
|
|
|
func passwordChangeString(value any) string {
|
|
text, ok := value.(string)
|
|
if !ok {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(text)
|
|
}
|
|
|
|
func writePasswordChangeError(w http.ResponseWriter, err error) {
|
|
status, message := http.StatusInternalServerError, "服务器内部错误。"
|
|
var authErr *PlatformAuthError
|
|
if errors.As(err, &authErr) {
|
|
status, message = authErr.Status, authErr.Message
|
|
}
|
|
var changeErr *identity.PasswordChangeError
|
|
if errors.As(err, &changeErr) {
|
|
status = http.StatusBadRequest
|
|
switch changeErr.Reason {
|
|
case identity.PasswordChangeNotFound:
|
|
status, message = http.StatusNotFound, "账号不存在或已停用。"
|
|
case identity.PasswordChangeCurrentIncorrect:
|
|
message = "当前密码不正确。"
|
|
case identity.PasswordChangeInvalidNewPassword:
|
|
message = "新密码至少需要 8 位。"
|
|
default:
|
|
message = "当前密码、新密码和确认密码不能为空。"
|
|
}
|
|
}
|
|
writePasswordJSON(w, status, map[string]any{"error": message})
|
|
}
|