fix: close auth, permission, contract and e2e review blockers
This commit is contained in:
@@ -30,11 +30,74 @@ type AuthHandler struct {
|
||||
authService *service.AuthService
|
||||
}
|
||||
|
||||
const (
|
||||
refreshTokenCookieName = "ums_refresh_token"
|
||||
sessionPresenceCookieName = "ums_session_present"
|
||||
)
|
||||
|
||||
// NewAuthHandler creates a new AuthHandler
|
||||
func NewAuthHandler(authService *service.AuthService) *AuthHandler {
|
||||
return &AuthHandler{authService: authService}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// Register 用户注册
|
||||
// @Summary 用户注册
|
||||
// @Description 用户注册新账号,支持用户名+密码或手机号注册
|
||||
@@ -130,6 +193,7 @@ func (h *AuthHandler) Login(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
h.setSessionCookies(c, resp)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
@@ -150,21 +214,23 @@ func (h *AuthHandler) Login(c *gin.Context) {
|
||||
// @Router /api/v1/auth/login/totp-verify [post]
|
||||
func (h *AuthHandler) VerifyTOTPAfterPasswordLogin(c *gin.Context) {
|
||||
var req struct {
|
||||
UserID int64 `json:"user_id" binding:"required"`
|
||||
Code string `json:"code" binding:"required"`
|
||||
DeviceID string `json:"device_id"`
|
||||
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"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.authService.VerifyTOTPAfterPasswordLogin(c.Request.Context(), req.UserID, req.Code, req.DeviceID)
|
||||
resp, err := h.authService.VerifyTOTPAfterPasswordLogin(c.Request.Context(), req.UserID, req.Code, req.DeviceID, req.TempToken)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
h.setSessionCookies(c, resp)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
@@ -197,6 +263,12 @@ func (h *AuthHandler) Logout(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
if req.RefreshToken == "" {
|
||||
if cookie, err := c.Request.Cookie(refreshTokenCookieName); err == nil {
|
||||
req.RefreshToken = cookie.Value
|
||||
}
|
||||
}
|
||||
|
||||
username, _ := c.Get("username")
|
||||
usernameStr, _ := username.(string)
|
||||
|
||||
@@ -204,7 +276,11 @@ func (h *AuthHandler) Logout(c *gin.Context) {
|
||||
AccessToken: req.AccessToken,
|
||||
RefreshToken: req.RefreshToken,
|
||||
}
|
||||
_ = h.authService.Logout(c.Request.Context(), usernameStr, logoutReq)
|
||||
if err := h.authService.Logout(c.Request.Context(), usernameStr, logoutReq); err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
clearSessionCookies(c)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "logged out"})
|
||||
}
|
||||
@@ -222,20 +298,28 @@ func (h *AuthHandler) Logout(c *gin.Context) {
|
||||
// @Router /api/v1/auth/refresh-token [post]
|
||||
func (h *AuthHandler) RefreshToken(c *gin.Context) {
|
||||
var req struct {
|
||||
RefreshToken string `json:"refresh_token" binding:"required"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
_ = 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"})
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.authService.RefreshToken(c.Request.Context(), req.RefreshToken)
|
||||
if err != nil {
|
||||
clearSessionCookies(c)
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
h.setSessionCookies(c, resp)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
@@ -315,7 +399,7 @@ func (h *AuthHandler) GetAuthCapabilities(c *gin.Context) {
|
||||
// @Router /api/v1/auth/oauth/{provider} [get]
|
||||
func (h *AuthHandler) OAuthLogin(c *gin.Context) {
|
||||
provider := c.Param("provider")
|
||||
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "OAuth not configured", "data": gin.H{"provider": provider}})
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"code": http.StatusServiceUnavailable, "message": "OAuth login is not configured", "data": gin.H{"provider": provider}})
|
||||
}
|
||||
|
||||
// OAuthCallback OAuth回调
|
||||
@@ -327,7 +411,7 @@ func (h *AuthHandler) OAuthLogin(c *gin.Context) {
|
||||
// @Success 200 {object} Response "OAuth未配置"
|
||||
// @Router /api/v1/auth/oauth/{provider}/callback [get]
|
||||
func (h *AuthHandler) OAuthCallback(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "OAuth not configured"})
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"code": http.StatusServiceUnavailable, "message": "OAuth callback is not configured"})
|
||||
}
|
||||
|
||||
// OAuthExchange OAuth令牌交换
|
||||
@@ -340,7 +424,7 @@ func (h *AuthHandler) OAuthCallback(c *gin.Context) {
|
||||
// @Success 200 {object} Response "OAuth未配置"
|
||||
// @Router /api/v1/auth/oauth/{provider}/exchange [post]
|
||||
func (h *AuthHandler) OAuthExchange(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "OAuth not configured"})
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"code": http.StatusServiceUnavailable, "message": "OAuth exchange is not configured"})
|
||||
}
|
||||
|
||||
// GetEnabledOAuthProviders 获取已启用的OAuth提供商
|
||||
@@ -481,6 +565,7 @@ func (h *AuthHandler) LoginByEmailCode(c *gin.Context) {
|
||||
}()
|
||||
}
|
||||
|
||||
h.setSessionCookies(c, resp)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
@@ -545,6 +630,7 @@ func (h *AuthHandler) BootstrapAdmin(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
h.setSessionCookies(c, resp)
|
||||
c.JSON(http.StatusCreated, gin.H{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
@@ -561,7 +647,7 @@ func (h *AuthHandler) BootstrapAdmin(c *gin.Context) {
|
||||
// @Success 200 {object} Response "功能未配置"
|
||||
// @Router /api/v1/auth/email/bind/send [post]
|
||||
func (h *AuthHandler) SendEmailBindCode(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "email bind not configured"})
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"code": http.StatusServiceUnavailable, "message": "email binding is not configured"})
|
||||
}
|
||||
|
||||
// BindEmail 绑定邮箱
|
||||
@@ -573,7 +659,7 @@ func (h *AuthHandler) SendEmailBindCode(c *gin.Context) {
|
||||
// @Success 200 {object} Response "功能未配置"
|
||||
// @Router /api/v1/auth/email/bind [post]
|
||||
func (h *AuthHandler) BindEmail(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "email bind not configured"})
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"code": http.StatusServiceUnavailable, "message": "email binding is not configured"})
|
||||
}
|
||||
|
||||
// UnbindEmail 解绑邮箱
|
||||
@@ -585,7 +671,7 @@ func (h *AuthHandler) BindEmail(c *gin.Context) {
|
||||
// @Success 200 {object} Response "功能未配置"
|
||||
// @Router /api/v1/auth/email/unbind [post]
|
||||
func (h *AuthHandler) UnbindEmail(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "email unbind not configured"})
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"code": http.StatusServiceUnavailable, "message": "email binding is not configured"})
|
||||
}
|
||||
|
||||
// SendPhoneBindCode 发送手机绑定验证码
|
||||
@@ -597,7 +683,7 @@ func (h *AuthHandler) UnbindEmail(c *gin.Context) {
|
||||
// @Success 200 {object} Response "功能未配置"
|
||||
// @Router /api/v1/auth/phone/bind/send [post]
|
||||
func (h *AuthHandler) SendPhoneBindCode(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "phone bind not configured"})
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"code": http.StatusServiceUnavailable, "message": "phone binding is not configured"})
|
||||
}
|
||||
|
||||
// BindPhone 绑定手机号
|
||||
@@ -609,7 +695,7 @@ func (h *AuthHandler) SendPhoneBindCode(c *gin.Context) {
|
||||
// @Success 200 {object} Response "功能未配置"
|
||||
// @Router /api/v1/auth/phone/bind [post]
|
||||
func (h *AuthHandler) BindPhone(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "phone bind not configured"})
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"code": http.StatusServiceUnavailable, "message": "phone binding is not configured"})
|
||||
}
|
||||
|
||||
// UnbindPhone 解绑手机号
|
||||
@@ -621,7 +707,7 @@ func (h *AuthHandler) BindPhone(c *gin.Context) {
|
||||
// @Success 200 {object} Response "功能未配置"
|
||||
// @Router /api/v1/auth/phone/unbind [post]
|
||||
func (h *AuthHandler) UnbindPhone(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "phone unbind not configured"})
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"code": http.StatusServiceUnavailable, "message": "phone binding is not configured"})
|
||||
}
|
||||
|
||||
// GetSocialAccounts 获取社交账号列表
|
||||
@@ -645,7 +731,7 @@ func (h *AuthHandler) GetSocialAccounts(c *gin.Context) {
|
||||
// @Success 200 {object} Response "功能未配置"
|
||||
// @Router /api/v1/auth/social/bind [post]
|
||||
func (h *AuthHandler) BindSocialAccount(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "social binding not configured"})
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"code": http.StatusServiceUnavailable, "message": "social binding is not configured"})
|
||||
}
|
||||
|
||||
// UnbindSocialAccount 解绑社交账号
|
||||
@@ -657,7 +743,7 @@ func (h *AuthHandler) BindSocialAccount(c *gin.Context) {
|
||||
// @Success 200 {object} Response "功能未配置"
|
||||
// @Router /api/v1/auth/social/unbind [post]
|
||||
func (h *AuthHandler) UnbindSocialAccount(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "social unbinding not configured"})
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"code": http.StatusServiceUnavailable, "message": "social binding is not configured"})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) SupportsEmailCodeLogin() bool {
|
||||
|
||||
Reference in New Issue
Block a user