mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-30 08:22:34 +00:00
Merge pull request #7052 from ouchihao/codex/issue-2912-transient-auth-failures
fix(auth): preserve sessions during temporary service failures
This commit is contained in:
@@ -176,7 +176,11 @@ func validateJWTForAdmin(
|
||||
// 从数据库获取用户
|
||||
user, err := userService.GetByID(c.Request.Context(), claims.UserID)
|
||||
if err != nil {
|
||||
AbortWithError(c, 401, "USER_NOT_FOUND", "User not found")
|
||||
if errors.Is(err, service.ErrUserNotFound) {
|
||||
AbortWithError(c, 401, "USER_NOT_FOUND", "User not found")
|
||||
} else {
|
||||
AbortWithError(c, 500, "INTERNAL_ERROR", "Failed to load user")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,11 @@ func jwtAuth(
|
||||
// 从数据库获取最新的用户信息
|
||||
user, err := userService.GetByID(c.Request.Context(), claims.UserID)
|
||||
if err != nil {
|
||||
AbortWithError(c, 401, "USER_NOT_FOUND", "User not found")
|
||||
if errors.Is(err, service.ErrUserNotFound) {
|
||||
AbortWithError(c, 401, "USER_NOT_FOUND", "User not found")
|
||||
} else {
|
||||
AbortWithError(c, 500, "INTERNAL_ERROR", "Failed to load user")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ type stubJWTUserRepo struct {
|
||||
func (r *stubJWTUserRepo) GetByID(_ context.Context, id int64) (*service.User, error) {
|
||||
u, ok := r.users[id]
|
||||
if !ok {
|
||||
return nil, errors.New("user not found")
|
||||
return nil, service.ErrUserNotFound
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
@@ -232,6 +232,67 @@ func TestJWTAuth_TamperedToken(t *testing.T) {
|
||||
require.Equal(t, "INVALID_TOKEN", body.Code)
|
||||
}
|
||||
|
||||
func TestJWTAuth_UserLookupErrors(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
cfg := &config.Config{JWT: config.JWTConfig{Secret: "test-secret", ExpireHour: 1}}
|
||||
authSvc := service.NewAuthService(nil, nil, nil, nil, cfg, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
token, err := authSvc.GenerateToken(context.Background(), &service.User{ID: 1, Role: service.RoleAdmin})
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
err error
|
||||
status int
|
||||
code string
|
||||
}{
|
||||
{"missing user", service.ErrUserNotFound, http.StatusUnauthorized, "USER_NOT_FOUND"},
|
||||
{"database timeout", context.DeadlineExceeded, http.StatusInternalServerError, "INTERNAL_ERROR"},
|
||||
{"database failure", errors.New("database unavailable"), http.StatusInternalServerError, "INTERNAL_ERROR"},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
userRepo := &stubUserRepo{getByID: func(context.Context, int64) (*service.User, error) {
|
||||
return nil, tc.err
|
||||
}}
|
||||
userSvc := service.NewUserService(userRepo, nil, nil, nil)
|
||||
for _, route := range []struct {
|
||||
name string
|
||||
handler gin.HandlerFunc
|
||||
websocket bool
|
||||
}{
|
||||
{"user", gin.HandlerFunc(NewJWTAuthMiddleware(authSvc, userSvc, nil, nil)), false},
|
||||
{"admin", gin.HandlerFunc(NewAdminAuthMiddleware(authSvc, userSvc, nil, nil)), false},
|
||||
{"admin websocket", gin.HandlerFunc(NewAdminAuthMiddleware(authSvc, userSvc, nil, nil)), true},
|
||||
} {
|
||||
t.Run(route.name, func(t *testing.T) {
|
||||
called := false
|
||||
router := gin.New()
|
||||
router.Use(route.handler)
|
||||
router.GET("/protected", func(c *gin.Context) {
|
||||
called = true
|
||||
c.Status(http.StatusOK)
|
||||
})
|
||||
req := httptest.NewRequest(http.MethodGet, "/protected", nil)
|
||||
if route.websocket {
|
||||
req.Header.Set("Upgrade", "websocket")
|
||||
req.Header.Set("Connection", "Upgrade")
|
||||
req.Header.Set("Sec-WebSocket-Protocol", "sub2api-admin, jwt."+token)
|
||||
} else {
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
}
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, tc.status, w.Code)
|
||||
var body ErrorResponse
|
||||
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &body))
|
||||
require.Equal(t, tc.code, body.Code)
|
||||
require.False(t, called)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestJWTAuth_UserNotFound(t *testing.T) {
|
||||
// 使用 user ID=1 的 token,但 repo 中没有该用户
|
||||
fakeUser := &service.User{
|
||||
|
||||
Reference in New Issue
Block a user