feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
package handler
import (
2026-04-07 12:08:16 +08:00
"context"
"crypto/subtle"
"errors"
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
"net/http"
2026-04-07 12:08:16 +08:00
"os"
"strings"
"time"
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
"github.com/gin-gonic/gin"
2026-04-07 12:08:16 +08:00
apierrors "github.com/user-management-system/internal/pkg/errors"
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
"github.com/user-management-system/internal/service"
)
2026-04-07 12:08:16 +08:00
// newBackgroundCtx 创建用于后台 goroutine 的带超时独立 context( 与请求 context 无关)
func newBackgroundCtx ( timeoutSec int ) ( context . Context , context . CancelFunc ) {
return context . WithTimeout ( context . Background ( ) , time . Duration ( timeoutSec ) * time . Second )
}
2026-04-18 20:48:11 +08:00
// ActivateEmailRequest 邮箱激活请求
type ActivateEmailRequest struct {
Token string ` json:"token" binding:"required" `
}
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
// AuthHandler handles authentication requests
type AuthHandler struct {
authService * service . AuthService
}
2026-05-28 15:19:13 +08:00
const (
refreshTokenCookieName = "ums_refresh_token"
sessionPresenceCookieName = "ums_session_present"
)
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
// NewAuthHandler creates a new AuthHandler
func NewAuthHandler ( authService * service . AuthService ) * AuthHandler {
return & AuthHandler { authService : authService }
}
2026-05-28 15:19:13 +08:00
func isSecureRequest ( c * gin . Context ) bool {
if c == nil || c . Request == nil {
return false
}
if c . Request . TLS != nil {
return true
}
return strings . EqualFold ( c . GetHeader ( "X-Forwarded-Proto" ) , "https" )
}
func ( h * AuthHandler ) setSessionCookies ( c * gin . Context , resp * service . LoginResponse ) {
if c == nil || resp == nil || strings . TrimSpace ( resp . RefreshToken ) == "" || h == nil || h . authService == nil {
return
}
maxAge := int ( h . authService . RefreshTokenTTLSeconds ( ) )
secure := isSecureRequest ( c )
http . SetCookie ( c . Writer , & http . Cookie {
Name : refreshTokenCookieName ,
Value : resp . RefreshToken ,
Path : "/" ,
HttpOnly : true ,
Secure : secure ,
SameSite : http . SameSiteLaxMode ,
MaxAge : maxAge ,
} )
http . SetCookie ( c . Writer , & http . Cookie {
Name : sessionPresenceCookieName ,
Value : "1" ,
Path : "/" ,
HttpOnly : false ,
Secure : secure ,
SameSite : http . SameSiteLaxMode ,
MaxAge : maxAge ,
} )
}
func clearCookie ( c * gin . Context , name string ) {
if c == nil {
return
}
http . SetCookie ( c . Writer , & http . Cookie {
Name : name ,
Value : "" ,
Path : "/" ,
HttpOnly : name == refreshTokenCookieName ,
Secure : isSecureRequest ( c ) ,
SameSite : http . SameSiteLaxMode ,
MaxAge : - 1 ,
Expires : time . Unix ( 0 , 0 ) ,
} )
}
func clearSessionCookies ( c * gin . Context ) {
clearCookie ( c , refreshTokenCookieName )
clearCookie ( c , sessionPresenceCookieName )
}
2026-04-11 21:23:52 +08:00
// Register 用户注册
// @Summary 用户注册
// @Description 用户注册新账号,支持用户名+密码或手机号注册
// @Tags 认证
// @Accept json
// @Produce json
// @Param request body service.RegisterRequest true "注册请求"
// @Success 201 {object} Response{data=service.UserInfo} "注册成功"
// @Failure 400 {object} Response{code=int,message=string} "请求参数错误"
// @Failure 409 {object} Response{code=int,message=string} "用户已存在"
// @Router /api/v1/auth/register [post]
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
func ( h * AuthHandler ) Register ( c * gin . Context ) {
var req struct {
Username string ` json:"username" binding:"required" `
Email string ` json:"email" `
Phone string ` json:"phone" `
Password string ` json:"password" binding:"required" `
Nickname string ` json:"nickname" `
}
if err := c . ShouldBindJSON ( & req ) ; err != nil {
c . JSON ( http . StatusBadRequest , gin . H { "error" : err . Error ( ) } )
return
}
registerReq := & service . RegisterRequest {
Username : req . Username ,
Email : req . Email ,
Phone : req . Phone ,
Password : req . Password ,
Nickname : req . Nickname ,
}
userInfo , err := h . authService . Register ( c . Request . Context ( ) , registerReq )
if err != nil {
handleError ( c , err )
return
}
2026-04-08 20:06:54 +08:00
c . JSON ( http . StatusCreated , gin . H {
"code" : 0 ,
"message" : "success" ,
"data" : userInfo ,
} )
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
}
2026-04-11 21:23:52 +08:00
// Login 用户登录
// @Summary 用户登录
// @Description 用户使用账号密码登录,支持多种认证方式(用户名/邮箱/手机号)
// @Tags 认证
// @Accept json
// @Produce json
// @Param request body service.LoginRequest true "登录请求"
// @Success 200 {object} Response{data=service.LoginResponse} "登录成功"
// @Failure 400 {object} Response{code=int,message=string} "请求参数错误"
// @Failure 401 {object} Response{code=int,message=string} "认证失败"
// @Failure 429 {object} Response{code=int,message=string} "登录尝试过多"
// @Router /api/v1/auth/login [post]
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
func ( h * AuthHandler ) Login ( c * gin . Context ) {
var req struct {
2026-04-07 12:08:16 +08:00
Account string ` json:"account" `
Username string ` json:"username" `
Email string ` json:"email" `
Phone string ` json:"phone" `
Password string ` json:"password" `
DeviceID string ` json:"device_id" `
DeviceName string ` json:"device_name" `
DeviceBrowser string ` json:"device_browser" `
DeviceOS string ` json:"device_os" `
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
}
if err := c . ShouldBindJSON ( & req ) ; err != nil {
c . JSON ( http . StatusBadRequest , gin . H { "error" : err . Error ( ) } )
return
}
loginReq := & service . LoginRequest {
2026-04-07 12:08:16 +08:00
Account : req . Account ,
Username : req . Username ,
Email : req . Email ,
Phone : req . Phone ,
Password : req . Password ,
DeviceID : req . DeviceID ,
DeviceName : req . DeviceName ,
DeviceBrowser : req . DeviceBrowser ,
DeviceOS : req . DeviceOS ,
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
}
clientIP := c . ClientIP ( )
resp , err := h . authService . Login ( c . Request . Context ( ) , loginReq , clientIP )
if err != nil {
handleError ( c , err )
2026-04-18 14:50:25 +08:00
return
}
2026-05-28 15:19:13 +08:00
h . setSessionCookies ( c , resp )
2026-04-18 14:50:25 +08:00
c . JSON ( http . StatusOK , gin . H {
"code" : 0 ,
"message" : "success" ,
"data" : resp ,
} )
}
// VerifyTOTPAfterPasswordLogin 完成密码登录后的TOTP验证
// @Summary TOTP验证( 密码登录后)
// @Description 当登录返回requires_totp=true时, 使用此接口完成TOTP验证
// @Tags 认证
// @Accept json
// @Produce json
// @Param request body TOTPVerifyRequest true "TOTP验证请求"
// @Success 200 {object} Response{data=service.LoginResponse} "验证成功"
// @Failure 400 {object} Response "请求参数错误"
// @Failure 401 {object} Response "TOTP验证失败"
// @Router /api/v1/auth/login/totp-verify [post]
func ( h * AuthHandler ) VerifyTOTPAfterPasswordLogin ( c * gin . Context ) {
var req struct {
2026-05-28 15:19:13 +08:00
UserID int64 ` json:"user_id" binding:"required" `
Code string ` json:"code" binding:"required" `
DeviceID string ` json:"device_id" `
TempToken string ` json:"temp_token" binding:"required" `
2026-04-18 14:50:25 +08:00
}
if err := c . ShouldBindJSON ( & req ) ; err != nil {
c . JSON ( http . StatusBadRequest , gin . H { "code" : 400 , "message" : err . Error ( ) } )
return
}
2026-05-28 15:19:13 +08:00
resp , err := h . authService . VerifyTOTPAfterPasswordLogin ( c . Request . Context ( ) , req . UserID , req . Code , req . DeviceID , req . TempToken )
2026-04-18 14:50:25 +08:00
if err != nil {
handleError ( c , err )
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
return
}
2026-05-28 15:19:13 +08:00
h . setSessionCookies ( c , resp )
2026-04-08 20:06:54 +08:00
c . JSON ( http . StatusOK , gin . H {
"code" : 0 ,
"message" : "success" ,
"data" : resp ,
} )
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
}
2026-04-11 21:23:52 +08:00
// Logout 用户登出
// @Summary 用户登出
// @Description 使当前 access_token 和 refresh_token 失效
// @Tags 认证
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param request body service.LogoutRequest false "登出请求( token可从header获取) "
// @Success 200 {object} Response{code=int,message=string} "登出成功"
// @Router /api/v1/auth/logout [post]
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
func ( h * AuthHandler ) Logout ( c * gin . Context ) {
2026-04-07 12:08:16 +08:00
var req struct {
AccessToken string ` json:"access_token" `
RefreshToken string ` json:"refresh_token" `
}
// 允许 body 为空(仅凭 Authorization header 里的 access_token 注销也可以)
_ = c . ShouldBindJSON ( & req )
// 如果 body 里没有 access_token, 则从 Authorization header 中取
if req . AccessToken == "" {
if bearer := c . GetHeader ( "Authorization" ) ; len ( bearer ) > 7 {
req . AccessToken = bearer [ 7 : ] // 去掉 "Bearer "
}
}
2026-05-28 15:19:13 +08:00
if req . RefreshToken == "" {
if cookie , err := c . Request . Cookie ( refreshTokenCookieName ) ; err == nil {
req . RefreshToken = cookie . Value
}
}
2026-04-07 12:08:16 +08:00
username , _ := c . Get ( "username" )
usernameStr , _ := username . ( string )
logoutReq := & service . LogoutRequest {
AccessToken : req . AccessToken ,
RefreshToken : req . RefreshToken ,
}
2026-05-28 15:19:13 +08:00
if err := h . authService . Logout ( c . Request . Context ( ) , usernameStr , logoutReq ) ; err != nil {
handleError ( c , err )
return
}
clearSessionCookies ( c )
2026-04-07 12:08:16 +08:00
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
c . JSON ( http . StatusOK , gin . H { "message" : "logged out" } )
}
2026-04-11 21:23:52 +08:00
// RefreshToken 刷新访问令牌
// @Summary 刷新访问令牌
// @Description 使用 refresh_token 获取新的 access_token
// @Tags 认证
// @Accept json
// @Produce json
// @Param request body RefreshTokenRequest true "刷新令牌请求"
// @Success 200 {object} Response{data=service.LoginResponse} "刷新成功"
// @Failure 400 {object} Response{code=int,message=string} "请求参数错误"
// @Failure 401 {object} Response{code=int,message=string} "refresh_token无效或已过期"
2026-05-30 21:29:24 +08:00
// @Router /api/v1/auth/refresh [post]
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
func ( h * AuthHandler ) RefreshToken ( c * gin . Context ) {
var req struct {
2026-05-28 15:19:13 +08:00
RefreshToken string ` json:"refresh_token" `
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
}
2026-05-28 15:19:13 +08:00
_ = c . ShouldBindJSON ( & req )
if strings . TrimSpace ( req . RefreshToken ) == "" {
if cookie , err := c . Request . Cookie ( refreshTokenCookieName ) ; err == nil {
req . RefreshToken = cookie . Value
}
}
if strings . TrimSpace ( req . RefreshToken ) == "" {
c . JSON ( http . StatusBadRequest , gin . H { "error" : "refresh_token is required" } )
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
return
}
resp , err := h . authService . RefreshToken ( c . Request . Context ( ) , req . RefreshToken )
if err != nil {
2026-05-28 15:19:13 +08:00
clearSessionCookies ( c )
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
handleError ( c , err )
return
}
2026-05-28 15:19:13 +08:00
h . setSessionCookies ( c , resp )
2026-04-08 20:06:54 +08:00
c . JSON ( http . StatusOK , gin . H {
"code" : 0 ,
"message" : "success" ,
"data" : resp ,
} )
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
}
2026-04-11 21:23:52 +08:00
// GetUserInfo 获取当前用户信息
// @Summary 获取当前用户信息
// @Description 获取已登录用户的详细信息
// @Tags 认证
// @Produce json
// @Security BearerAuth
// @Success 200 {object} Response{data=service.UserInfo} "用户信息"
// @Failure 401 {object} Response{code=int,message=string} "未认证"
// @Router /api/v1/auth/userinfo [get]
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
func ( h * AuthHandler ) GetUserInfo ( c * gin . Context ) {
userID , ok := getUserIDFromContext ( c )
if ! ok {
c . JSON ( http . StatusUnauthorized , gin . H { "error" : "unauthorized" } )
return
}
userInfo , err := h . authService . GetUserInfo ( c . Request . Context ( ) , userID )
if err != nil {
handleError ( c , err )
return
}
2026-04-08 20:06:54 +08:00
c . JSON ( http . StatusOK , gin . H {
"code" : 0 ,
"message" : "success" ,
"data" : userInfo ,
} )
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
}
2026-04-11 21:23:52 +08:00
// GetCSRFToken 获取CSRF令牌
// @Summary 获取CSRF令牌
// @Description 由于系统使用JWT Bearer Token认证, 不存在CSRF风险, 返回空token
// @Tags 认证
// @Produce json
2026-05-30 21:29:24 +08:00
// @Success 200 {object} Response{data=CSRFTokenResponse} "CSRF token( 为空) "
2026-04-11 21:23:52 +08:00
// @Router /api/v1/auth/csrf-token [get]
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
func ( h * AuthHandler ) GetCSRFToken ( c * gin . Context ) {
2026-04-07 12:08:16 +08:00
// 系统使用 JWT Bearer Token 认证, Bearer Token 不会被浏览器自动携带(非 cookie)
// 因此不存在传统意义上的 CSRF 风险,此端点返回空 token 作为兼容响应
c . JSON ( http . StatusOK , gin . H {
"csrf_token" : "" ,
"note" : "JWT Bearer Token authentication; CSRF protection not required" ,
} )
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
}
2026-04-11 21:23:52 +08:00
// GetAuthCapabilities 获取认证能力
// @Summary 获取系统认证能力
// @Description 返回系统支持的认证方式和配置( 如是否需要邮件激活、是否支持OAuth等)
// @Tags 认证
// @Produce json
// @Success 200 {object} Response{data=service.AuthCapabilities} "认证能力配置"
// @Router /api/v1/auth/capabilities [get]
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
func ( h * AuthHandler ) GetAuthCapabilities ( c * gin . Context ) {
2026-04-11 10:27:29 +08:00
ctx := c . Request . Context ( )
caps := h . authService . GetAuthCapabilities ( ctx )
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
c . JSON ( http . StatusOK , gin . H {
2026-04-11 10:27:29 +08:00
"code" : 0 ,
"message" : "success" ,
"data" : caps ,
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
} )
}
2026-04-11 21:23:52 +08:00
// OAuthLogin OAuth登录初始化
// @Summary OAuth登录初始化
// @Description 发起OAuth登录流程( 当前未配置)
// @Tags OAuth
// @Produce json
// @Param provider path string true "OAuth提供商( 如 github, google) "
// @Success 200 {object} Response "OAuth未配置"
// @Router /api/v1/auth/oauth/{provider} [get]
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
func ( h * AuthHandler ) OAuthLogin ( c * gin . Context ) {
provider := c . Param ( "provider" )
2026-05-28 15:19:13 +08:00
c . JSON ( http . StatusServiceUnavailable , gin . H { "code" : http . StatusServiceUnavailable , "message" : "OAuth login is not configured" , "data" : gin . H { "provider" : provider } } )
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
}
2026-04-11 21:23:52 +08:00
// OAuthCallback OAuth回调
// @Summary OAuth回调处理
// @Description 处理OAuth provider回调( 当前未配置)
// @Tags OAuth
// @Produce json
// @Param provider path string true "OAuth提供商"
// @Success 200 {object} Response "OAuth未配置"
// @Router /api/v1/auth/oauth/{provider}/callback [get]
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
func ( h * AuthHandler ) OAuthCallback ( c * gin . Context ) {
2026-05-28 15:19:13 +08:00
c . JSON ( http . StatusServiceUnavailable , gin . H { "code" : http . StatusServiceUnavailable , "message" : "OAuth callback is not configured" } )
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
}
2026-04-11 21:23:52 +08:00
// OAuthExchange OAuth令牌交换
// @Summary OAuth令牌交换
// @Description 使用OAuth code交换access_token( 当前未配置)
// @Tags OAuth
// @Accept json
// @Produce json
// @Param provider path string true "OAuth提供商"
// @Success 200 {object} Response "OAuth未配置"
2026-05-30 21:29:24 +08:00
// @Router /api/v1/auth/oauth/exchange [post]
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
func ( h * AuthHandler ) OAuthExchange ( c * gin . Context ) {
2026-05-28 15:19:13 +08:00
c . JSON ( http . StatusServiceUnavailable , gin . H { "code" : http . StatusServiceUnavailable , "message" : "OAuth exchange is not configured" } )
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
}
2026-04-11 21:23:52 +08:00
// GetEnabledOAuthProviders 获取已启用的OAuth提供商
// @Summary 获取OAuth提供商列表
// @Description 返回系统已配置并启用的OAuth提供商列表
// @Tags OAuth
// @Produce json
2026-05-30 21:29:24 +08:00
// @Success 200 {object} Response{data=OAuthProvidersResponse} "提供商列表"
2026-04-11 21:23:52 +08:00
// @Router /api/v1/auth/oauth/providers [get]
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
func ( h * AuthHandler ) GetEnabledOAuthProviders ( c * gin . Context ) {
2026-04-11 13:37:39 +08:00
c . JSON ( http . StatusOK , gin . H { "code" : 0 , "message" : "success" , "data" : gin . H { "providers" : [ ] string { } } } )
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
}
2026-04-11 21:23:52 +08:00
// ActivateEmail 激活邮箱
// @Summary 激活用户邮箱
// @Description 使用邮箱激活token激活用户账号
// @Tags 邮箱认证
2026-04-18 20:48:11 +08:00
// @Accept json
2026-04-11 21:23:52 +08:00
// @Produce json
2026-04-18 20:48:11 +08:00
// @Param request body ActivateEmailRequest true "激活请求"
2026-04-11 21:23:52 +08:00
// @Success 200 {object} Response "激活成功"
// @Failure 400 {object} Response "token缺失"
// @Failure 401 {object} Response "token无效或已过期"
// @Router /api/v1/auth/activate-email [post]
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
func ( h * AuthHandler ) ActivateEmail ( c * gin . Context ) {
2026-04-18 20:48:11 +08:00
var req ActivateEmailRequest
if err := c . ShouldBindJSON ( & req ) ; err != nil {
2026-04-11 13:37:39 +08:00
c . JSON ( http . StatusBadRequest , gin . H { "code" : 400 , "message" : "token is required" } )
2026-04-07 12:08:16 +08:00
return
}
2026-04-18 20:48:11 +08:00
if err := h . authService . ActivateEmail ( c . Request . Context ( ) , req . Token ) ; err != nil {
2026-04-07 12:08:16 +08:00
handleError ( c , err )
return
}
2026-04-11 13:37:39 +08:00
c . JSON ( http . StatusOK , gin . H { "code" : 0 , "message" : "email activated successfully" } )
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
}
2026-04-11 21:23:52 +08:00
// ResendActivationEmail 重发激活邮件
// @Summary 重发激活邮件
// @Description 重新发送账号激活邮件(防枚举:无论邮箱是否注册都返回成功)
// @Tags 邮箱认证
// @Accept json
// @Produce json
// @Param request body ResendActivationRequest true "邮箱地址"
// @Success 200 {object} Response "激活邮件已发送(如果邮箱已注册)"
// @Failure 400 {object} Response "邮箱格式错误"
2026-05-30 21:29:24 +08:00
// @Router /api/v1/auth/resend-activation [post]
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
func ( h * AuthHandler ) ResendActivationEmail ( c * gin . Context ) {
2026-04-07 12:08:16 +08:00
var req struct {
Email string ` json:"email" binding:"required,email" `
}
if err := c . ShouldBindJSON ( & req ) ; err != nil {
2026-04-11 13:37:39 +08:00
c . JSON ( http . StatusBadRequest , gin . H { "code" : 400 , "message" : err . Error ( ) } )
2026-04-07 12:08:16 +08:00
return
}
if err := h . authService . ResendActivationEmail ( c . Request . Context ( ) , req . Email ) ; err != nil {
handleError ( c , err )
return
}
// 防枚举:无论邮箱是否存在,统一返回成功
2026-04-11 13:37:39 +08:00
c . JSON ( http . StatusOK , gin . H { "code" : 0 , "message" : "activation email sent if address is registered" } )
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
}
2026-04-11 21:23:52 +08:00
// SendEmailCode 发送邮箱验证码
// @Summary 发送邮箱验证码
// @Description 发送邮箱登录验证码(防枚举:无论邮箱是否注册都返回成功)
// @Tags 邮箱认证
// @Accept json
// @Produce json
// @Param request body SendEmailCodeRequest true "邮箱地址"
// @Success 200 {object} Response "验证码已发送"
// @Failure 400 {object} Response "邮箱格式错误"
// @Router /api/v1/auth/send-email-code [post]
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
func ( h * AuthHandler ) SendEmailCode ( c * gin . Context ) {
2026-04-07 12:08:16 +08:00
var req struct {
Email string ` json:"email" binding:"required,email" `
}
if err := c . ShouldBindJSON ( & req ) ; err != nil {
2026-04-11 13:37:39 +08:00
c . JSON ( http . StatusBadRequest , gin . H { "code" : 400 , "message" : err . Error ( ) } )
2026-04-07 12:08:16 +08:00
return
}
// SendEmailLoginCode 内部会忽略未注册邮箱(防枚举),始终返回 ok
if err := h . authService . SendEmailLoginCode ( c . Request . Context ( ) , req . Email ) ; err != nil {
handleError ( c , err )
return
}
2026-04-11 13:37:39 +08:00
c . JSON ( http . StatusOK , gin . H { "code" : 0 , "message" : "验证码已发送" } )
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
}
2026-04-11 21:23:52 +08:00
// LoginByEmailCode 使用邮箱验证码登录
// @Summary 邮箱验证码登录
// @Description 使用邮箱和验证码完成登录
// @Tags 邮箱认证
// @Accept json
// @Produce json
// @Param request body LoginByEmailCodeRequest true "登录请求"
// @Success 200 {object} Response{data=service.LoginResponse} "登录成功"
// @Failure 400 {object} Response "请求参数错误"
// @Failure 401 {object} Response "验证码错误或已过期"
2026-05-30 21:29:24 +08:00
// @Router /api/v1/auth/login/email-code [post]
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
func ( h * AuthHandler ) LoginByEmailCode ( c * gin . Context ) {
2026-04-07 12:08:16 +08:00
var req struct {
Email string ` json:"email" binding:"required,email" `
Code string ` json:"code" binding:"required" `
DeviceID string ` json:"device_id" `
DeviceName string ` json:"device_name" `
DeviceBrowser string ` json:"device_browser" `
DeviceOS string ` json:"device_os" `
}
if err := c . ShouldBindJSON ( & req ) ; err != nil {
2026-04-11 13:37:39 +08:00
c . JSON ( http . StatusBadRequest , gin . H { "code" : 400 , "message" : err . Error ( ) } )
2026-04-07 12:08:16 +08:00
return
}
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
2026-04-07 12:08:16 +08:00
clientIP := c . ClientIP ( )
resp , err := h . authService . LoginByEmailCode ( c . Request . Context ( ) , req . Email , req . Code , clientIP )
if err != nil {
handleError ( c , err )
return
}
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
2026-04-07 12:08:16 +08:00
// 异步注册设备(不阻塞主流程)
// 注意:必须用 context.WithTimeout(context.Background()) 而非 c.Request.Context()
// gin 在 c.JSON 返回后会回收 context, goroutine 中引用会得到已取消的 context
if req . DeviceID != "" && resp != nil && resp . User != nil {
loginReq := & service . LoginRequest {
DeviceID : req . DeviceID ,
DeviceName : req . DeviceName ,
DeviceBrowser : req . DeviceBrowser ,
DeviceOS : req . DeviceOS ,
}
userID := resp . User . ID
go func ( ) {
devCtx , cancel := newBackgroundCtx ( 5 )
defer cancel ( )
h . authService . BestEffortRegisterDevicePublic ( devCtx , userID , loginReq )
} ( )
}
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
2026-05-28 15:19:13 +08:00
h . setSessionCookies ( c , resp )
2026-04-08 20:06:54 +08:00
c . JSON ( http . StatusOK , gin . H {
"code" : 0 ,
"message" : "success" ,
"data" : resp ,
} )
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
}
2026-04-11 21:23:52 +08:00
// BootstrapAdmin 引导初始化管理员
// @Summary 引导初始化管理员账号
// @Description 在系统未配置管理员时, 创建第一个管理员账号( 需要BOOTSTRAP_SECRET)
// @Tags 系统初始化
// @Accept json
// @Produce json
// @Security BootstrapSecret
// @Param X-Bootstrap-Secret header string true "引导密钥"
// @Param request body BootstrapAdminRequest true "管理员信息"
// @Success 201 {object} Response{data=service.UserInfo} "管理员创建成功"
// @Failure 401 {object} Response "引导密钥无效"
// @Failure 403 {object} Response "引导初始化未授权"
// @Router /api/v1/auth/bootstrap-admin [post]
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
func ( h * AuthHandler ) BootstrapAdmin ( c * gin . Context ) {
2026-04-07 12:08:16 +08:00
// P0 修复: BootstrapAdmin 端点需要 bootstrap secret 验证
bootstrapSecret := os . Getenv ( "BOOTSTRAP_SECRET" )
if bootstrapSecret == "" {
2026-04-11 13:37:39 +08:00
c . JSON ( http . StatusForbidden , gin . H { "code" : 403 , "message" : "引导初始化未授权" } )
2026-04-07 12:08:16 +08:00
return
}
providedSecret := c . GetHeader ( "X-Bootstrap-Secret" )
if providedSecret == "" {
2026-04-11 13:37:39 +08:00
c . JSON ( http . StatusUnauthorized , gin . H { "code" : 401 , "message" : "缺少引导密钥" } )
2026-04-07 12:08:16 +08:00
return
}
// 使用恒定时间比较防止时序攻击
if subtle . ConstantTimeCompare ( [ ] byte ( providedSecret ) , [ ] byte ( bootstrapSecret ) ) != 1 {
2026-04-11 13:37:39 +08:00
c . JSON ( http . StatusUnauthorized , gin . H { "code" : 401 , "message" : "引导密钥无效" } )
2026-04-07 12:08:16 +08:00
return
}
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
var req struct {
Username string ` json:"username" binding:"required" `
Email string ` json:"email" binding:"required" `
Password string ` json:"password" binding:"required" `
}
if err := c . ShouldBindJSON ( & req ) ; err != nil {
2026-04-11 13:37:39 +08:00
c . JSON ( http . StatusBadRequest , gin . H { "code" : 400 , "message" : err . Error ( ) } )
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
return
}
bootstrapReq := & service . BootstrapAdminRequest {
Username : req . Username ,
Email : req . Email ,
Password : req . Password ,
}
clientIP := c . ClientIP ( )
resp , err := h . authService . BootstrapAdmin ( c . Request . Context ( ) , bootstrapReq , clientIP )
if err != nil {
handleError ( c , err )
return
}
2026-05-28 15:19:13 +08:00
h . setSessionCookies ( c , resp )
2026-04-08 20:06:54 +08:00
c . JSON ( http . StatusCreated , gin . H {
"code" : 0 ,
"message" : "success" ,
"data" : resp ,
} )
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
}
2026-04-11 21:23:52 +08:00
// SendEmailBindCode 发送邮箱绑定验证码
// @Summary 发送邮箱绑定验证码
// @Description 发送验证码到邮箱以绑定邮箱(当前未配置)
// @Tags 邮箱绑定
// @Accept json
// @Produce json
// @Success 200 {object} Response "功能未配置"
2026-05-30 21:29:24 +08:00
// @Router /api/v1/users/me/bind-email/code [post]
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
func ( h * AuthHandler ) SendEmailBindCode ( c * gin . Context ) {
2026-05-28 15:19:13 +08:00
c . JSON ( http . StatusServiceUnavailable , gin . H { "code" : http . StatusServiceUnavailable , "message" : "email binding is not configured" } )
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
}
2026-04-11 21:23:52 +08:00
// BindEmail 绑定邮箱
// @Summary 绑定邮箱
// @Description 使用邮箱验证码绑定账号(当前未配置)
// @Tags 邮箱绑定
// @Accept json
// @Produce json
// @Success 200 {object} Response "功能未配置"
2026-05-30 21:29:24 +08:00
// @Router /api/v1/users/me/bind-email [post]
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
func ( h * AuthHandler ) BindEmail ( c * gin . Context ) {
2026-05-28 15:19:13 +08:00
c . JSON ( http . StatusServiceUnavailable , gin . H { "code" : http . StatusServiceUnavailable , "message" : "email binding is not configured" } )
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
}
2026-04-11 21:23:52 +08:00
// UnbindEmail 解绑邮箱
// @Summary 解绑邮箱
// @Description 解绑账号关联的邮箱(当前未配置)
// @Tags 邮箱绑定
// @Accept json
// @Produce json
// @Success 200 {object} Response "功能未配置"
2026-05-30 21:29:24 +08:00
// @Router /api/v1/users/me/bind-email [delete]
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
func ( h * AuthHandler ) UnbindEmail ( c * gin . Context ) {
2026-05-28 15:19:13 +08:00
c . JSON ( http . StatusServiceUnavailable , gin . H { "code" : http . StatusServiceUnavailable , "message" : "email binding is not configured" } )
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
}
2026-04-11 21:23:52 +08:00
// SendPhoneBindCode 发送手机绑定验证码
// @Summary 发送手机绑定验证码
// @Description 发送验证码到手机以绑定手机号(当前未配置)
// @Tags 手机绑定
// @Accept json
// @Produce json
// @Success 200 {object} Response "功能未配置"
2026-05-30 21:29:24 +08:00
// @Router /api/v1/users/me/bind-phone/code [post]
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
func ( h * AuthHandler ) SendPhoneBindCode ( c * gin . Context ) {
2026-05-28 15:19:13 +08:00
c . JSON ( http . StatusServiceUnavailable , gin . H { "code" : http . StatusServiceUnavailable , "message" : "phone binding is not configured" } )
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
}
2026-04-11 21:23:52 +08:00
// BindPhone 绑定手机号
// @Summary 绑定手机号
// @Description 使用手机验证码绑定账号(当前未配置)
// @Tags 手机绑定
// @Accept json
// @Produce json
// @Success 200 {object} Response "功能未配置"
2026-05-30 21:29:24 +08:00
// @Router /api/v1/users/me/bind-phone [post]
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
func ( h * AuthHandler ) BindPhone ( c * gin . Context ) {
2026-05-28 15:19:13 +08:00
c . JSON ( http . StatusServiceUnavailable , gin . H { "code" : http . StatusServiceUnavailable , "message" : "phone binding is not configured" } )
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
}
2026-04-11 21:23:52 +08:00
// UnbindPhone 解绑手机号
// @Summary 解绑手机号
// @Description 解绑账号关联的手机号(当前未配置)
// @Tags 手机绑定
// @Accept json
// @Produce json
// @Success 200 {object} Response "功能未配置"
2026-05-30 21:29:24 +08:00
// @Router /api/v1/users/me/bind-phone [delete]
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
func ( h * AuthHandler ) UnbindPhone ( c * gin . Context ) {
2026-05-28 15:19:13 +08:00
c . JSON ( http . StatusServiceUnavailable , gin . H { "code" : http . StatusServiceUnavailable , "message" : "phone binding is not configured" } )
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
}
2026-04-11 21:23:52 +08:00
// GetSocialAccounts 获取社交账号列表
// @Summary 获取已绑定的社交账号列表
// @Description 获取当前用户绑定的第三方社交账号列表
// @Tags 社交账号
// @Produce json
// @Security BearerAuth
// @Success 200 {object} Response "社交账号列表"
2026-05-30 21:29:24 +08:00
// @Router /api/v1/users/me/social-accounts [get]
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
func ( h * AuthHandler ) GetSocialAccounts ( c * gin . Context ) {
2026-04-11 13:37:39 +08:00
c . JSON ( http . StatusOK , gin . H { "code" : 0 , "message" : "success" , "data" : gin . H { "accounts" : [ ] interface { } { } } } )
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
}
2026-04-11 21:23:52 +08:00
// BindSocialAccount 绑定社交账号
// @Summary 绑定社交账号
// @Description 绑定第三方社交账号到当前用户(当前未配置)
// @Tags 社交账号
// @Accept json
// @Produce json
// @Success 200 {object} Response "功能未配置"
2026-05-30 21:29:24 +08:00
// @Router /api/v1/users/me/bind-social [post]
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
func ( h * AuthHandler ) BindSocialAccount ( c * gin . Context ) {
2026-05-28 15:19:13 +08:00
c . JSON ( http . StatusServiceUnavailable , gin . H { "code" : http . StatusServiceUnavailable , "message" : "social binding is not configured" } )
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
}
2026-04-11 21:23:52 +08:00
// UnbindSocialAccount 解绑社交账号
// @Summary 解绑社交账号
// @Description 解绑当前用户关联的第三方社交账号(当前未配置)
// @Tags 社交账号
// @Accept json
// @Produce json
// @Success 200 {object} Response "功能未配置"
2026-05-30 21:29:24 +08:00
// @Router /api/v1/users/me/bind-social/{provider} [delete]
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
func ( h * AuthHandler ) UnbindSocialAccount ( c * gin . Context ) {
2026-05-28 15:19:13 +08:00
c . JSON ( http . StatusServiceUnavailable , gin . H { "code" : http . StatusServiceUnavailable , "message" : "social binding is not configured" } )
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
}
func ( h * AuthHandler ) SupportsEmailCodeLogin ( ) bool {
2026-04-07 12:08:16 +08:00
return h . authService . HasEmailCodeService ( )
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
}
func getUserIDFromContext ( c * gin . Context ) ( int64 , bool ) {
userID , exists := c . Get ( "user_id" )
if ! exists {
return 0 , false
}
id , ok := userID . ( int64 )
return id , ok
}
2026-05-28 20:30:24 +08:00
func getUsernameFromContext ( c * gin . Context ) ( string , bool ) {
username , exists := c . Get ( "username" )
if ! exists {
return "" , false
}
usernameStr , ok := username . ( string )
return usernameStr , ok
}
2026-04-07 12:08:16 +08:00
// handleError 将 error 转换为对应的 HTTP 响应。
// 优先识别 ApplicationError, 其次通过关键词推断业务错误类型, 兜底返回 500。
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
func handleError ( c * gin . Context , err error ) {
2026-04-07 12:08:16 +08:00
if err == nil {
return
}
// 优先尝试 ApplicationError( 内置 HTTP 状态码)
var appErr * apierrors . ApplicationError
if errors . As ( err , & appErr ) {
2026-04-11 11:22:10 +08:00
c . JSON ( int ( appErr . Code ) , gin . H { "code" : appErr . Code , "message" : appErr . Message } )
2026-04-07 12:08:16 +08:00
return
}
// 对普通 errors.New 按关键词推断语义,但只返回通用错误信息给客户端
2026-04-11 11:22:10 +08:00
httpCode := classifyErrorMessage ( err . Error ( ) )
c . JSON ( httpCode , gin . H { "code" : httpCode , "message" : "服务器内部错误" } )
2026-04-07 12:08:16 +08:00
}
// classifyErrorMessage 通过错误信息关键词推断 HTTP 状态码,避免业务错误被 500 吞掉
func classifyErrorMessage ( msg string ) int {
lower := strings . ToLower ( msg )
switch {
case contains ( lower , "not found" , "不存在" , "找不到" ) :
return http . StatusNotFound
case contains ( lower , "already exists" , "已存在" , "已注册" , "duplicate" ) :
return http . StatusConflict
case contains ( lower , "unauthorized" , "invalid token" , "token" , "令牌" , "未认证" ) :
return http . StatusUnauthorized
case contains ( lower , "forbidden" , "permission" , "权限" , "禁止" ) :
return http . StatusForbidden
case contains ( lower , "invalid" , "required" , "must" , "cannot be empty" , "不能为空" ,
"格式" , "参数" , "密码不正确" , "incorrect" , "wrong" , "too short" , "too long" ,
"已失效" , "expired" , "验证码不正确" , "不能与" ) :
return http . StatusBadRequest
case contains ( lower , "locked" , "too many" , "账号已被锁定" , "rate limit" ) :
return http . StatusTooManyRequests
default :
return http . StatusInternalServerError
}
}
// contains 检查 s 是否包含 keywords 中的任意一个
func contains ( s string , keywords ... string ) bool {
for _ , kw := range keywords {
if strings . Contains ( s , kw ) {
return true
}
}
return false
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
}