feat: complete remaining Go backend modules
This commit is contained in:
1 parent
cea2751dc5
commit
aef5a97165
145 files changed
+18376
-199
No files matched your search
@@ -0,0 +1,130 @@
|
||||
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})
|
||||
}
|
||||
Reference in new issue
Block a user