fix: v6 code review P0 auth/IDOR fixes + frontend regression patches
Backend fixes: - auth_handler: P0 认证逻辑修复 - ratelimit: 限速中间件增强 + 新增单元测试 - auth_service: 认证服务逻辑完善 + 新增测试 - server: server 配置增强 + 新增测试 - handler_test: 新增 handler 层集成测试 - auth_bootstrap_test: bootstrap 路径测试 Frontend patches: - LoginPage/RegisterPage: CSRF + 表单交互修复 - BootstrapAdminPage: 引导流程修复 - DevicesPage: 设备管理页修复 - auth/social-accounts/users/webhooks services: 类型修正 - csrf.ts: CSRF token 处理修正 - E2E 脚本: CDP smoke + auth e2e 增强 Docs: - FULL_CODE_REVIEW_REPORT_2026-04-20 - report-v6 执行计划 - REAL_PROJECT_STATUS 更新 - .gitignore: 新增 .gocache-*/config.yaml 排除 验证: go build/vet 0错误, go test 42/42 PASS, 0 FAIL
This commit is contained in:
@@ -31,6 +31,46 @@ import (
|
||||
|
||||
var handlerDbCounter int64
|
||||
|
||||
func seedHandlerAuthzData(t *testing.T, db *gorm.DB) {
|
||||
t.Helper()
|
||||
|
||||
roleIDs := make(map[string]int64)
|
||||
for _, predefined := range domain.PredefinedRoles {
|
||||
role := predefined
|
||||
if err := db.Create(&role).Error; err != nil {
|
||||
t.Fatalf("seed role %s failed: %v", role.Code, err)
|
||||
}
|
||||
roleIDs[role.Code] = role.ID
|
||||
}
|
||||
|
||||
permissionIDs := make(map[string]int64)
|
||||
for _, predefined := range domain.DefaultPermissions() {
|
||||
permission := predefined
|
||||
if err := db.Create(&permission).Error; err != nil {
|
||||
t.Fatalf("seed permission %s failed: %v", permission.Code, err)
|
||||
}
|
||||
permissionIDs[permission.Code] = permission.ID
|
||||
}
|
||||
|
||||
adminRoleID := roleIDs["admin"]
|
||||
for _, permissionID := range permissionIDs {
|
||||
if err := db.Create(&domain.RolePermission{RoleID: adminRoleID, PermissionID: permissionID}).Error; err != nil {
|
||||
t.Fatalf("assign admin permission %d failed: %v", permissionID, err)
|
||||
}
|
||||
}
|
||||
|
||||
userRoleID := roleIDs["user"]
|
||||
for _, code := range []string{"profile:view", "profile:edit", "log:view_own"} {
|
||||
permissionID, ok := permissionIDs[code]
|
||||
if !ok {
|
||||
t.Fatalf("seeded permissions missing %s", code)
|
||||
}
|
||||
if err := db.Create(&domain.RolePermission{RoleID: userRoleID, PermissionID: permissionID}).Error; err != nil {
|
||||
t.Fatalf("assign user permission %s failed: %v", code, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setupHandlerTestServer(t *testing.T) (*httptest.Server, func()) {
|
||||
t.Helper()
|
||||
gin.SetMode(gin.TestMode)
|
||||
@@ -64,6 +104,8 @@ func setupHandlerTestServer(t *testing.T) (*httptest.Server, func()) {
|
||||
t.Fatalf("db migration failed: %v", err)
|
||||
}
|
||||
|
||||
seedHandlerAuthzData(t, db)
|
||||
|
||||
jwtManager, err := auth.NewJWTWithOptions(auth.JWTOptions{
|
||||
HS256Secret: "test-handler-secret-key",
|
||||
AccessTokenExpire: 15 * time.Minute,
|
||||
@@ -176,6 +218,18 @@ func doDelete(url, token string) (*http.Response, string) {
|
||||
return doRequest("DELETE", url, token, nil)
|
||||
}
|
||||
|
||||
func getCookie(resp *http.Response, name string) *http.Cookie {
|
||||
if resp == nil {
|
||||
return nil
|
||||
}
|
||||
for _, cookie := range resp.Cookies() {
|
||||
if cookie.Name == name {
|
||||
return cookie
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getToken(baseURL, username, password string) string {
|
||||
resp, body := doPost(baseURL+"/api/v1/auth/login", "", map[string]interface{}{
|
||||
"account": username,
|
||||
@@ -207,6 +261,111 @@ func registerUser(baseURL, username, email, password string) bool {
|
||||
return resp.StatusCode == http.StatusCreated
|
||||
}
|
||||
|
||||
func bootstrapAdmin(baseURL, secret, username, email, password string) string {
|
||||
payload, _ := json.Marshal(map[string]interface{}{
|
||||
"username": username,
|
||||
"email": email,
|
||||
"password": password,
|
||||
})
|
||||
|
||||
req, _ := http.NewRequest(http.MethodPost, baseURL+"/api/v1/auth/bootstrap-admin", bytes.NewReader(payload))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-Bootstrap-Secret", secret)
|
||||
|
||||
resp, err := (&http.Client{}).Do(req)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK {
|
||||
return ""
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return ""
|
||||
}
|
||||
data, ok := result["data"].(map[string]interface{})
|
||||
if !ok || data["access_token"] == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
token, _ := data["access_token"].(string)
|
||||
return token
|
||||
}
|
||||
|
||||
func setupEnabledTOTPUser(t *testing.T, baseURL, username, email, password string) (int64, string) {
|
||||
t.Helper()
|
||||
|
||||
if ok := registerUser(baseURL, username, email, password); !ok {
|
||||
t.Fatalf("registration failed for %s", username)
|
||||
}
|
||||
|
||||
token := getToken(baseURL, username, password)
|
||||
if token == "" {
|
||||
t.Fatalf("failed to get token for %s", username)
|
||||
}
|
||||
|
||||
userInfoResp, userInfoBody := doGet(baseURL+"/api/v1/auth/userinfo", token)
|
||||
defer userInfoResp.Body.Close()
|
||||
if userInfoResp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("userinfo failed: status=%d body=%s", userInfoResp.StatusCode, userInfoBody)
|
||||
}
|
||||
|
||||
var userInfoResult map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(userInfoBody), &userInfoResult); err != nil {
|
||||
t.Fatalf("failed to parse userinfo response: %v", err)
|
||||
}
|
||||
userData, ok := userInfoResult["data"].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("userinfo response missing data: %s", userInfoBody)
|
||||
}
|
||||
userID, ok := userData["id"].(float64)
|
||||
if !ok {
|
||||
t.Fatalf("userinfo response missing id: %s", userInfoBody)
|
||||
}
|
||||
|
||||
setupResp, setupBody := doGet(baseURL+"/api/v1/auth/2fa/setup", token)
|
||||
defer setupResp.Body.Close()
|
||||
if setupResp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("2fa setup failed: status=%d body=%s", setupResp.StatusCode, setupBody)
|
||||
}
|
||||
|
||||
var setupResult map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(setupBody), &setupResult); err != nil {
|
||||
t.Fatalf("failed to parse 2fa setup response: %v", err)
|
||||
}
|
||||
setupData, ok := setupResult["data"].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("2fa setup response missing data: %s", setupBody)
|
||||
}
|
||||
secret, ok := setupData["secret"].(string)
|
||||
if !ok || secret == "" {
|
||||
t.Fatalf("2fa setup response missing secret: %s", setupBody)
|
||||
}
|
||||
|
||||
code, err := auth.NewTOTPManager().GenerateCurrentCode(secret)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate TOTP code: %v", err)
|
||||
}
|
||||
|
||||
enableResp, enableBody := doPost(baseURL+"/api/v1/auth/2fa/enable", token, map[string]interface{}{
|
||||
"code": code,
|
||||
})
|
||||
defer enableResp.Body.Close()
|
||||
if enableResp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("2fa enable failed: status=%d body=%s", enableResp.StatusCode, enableBody)
|
||||
}
|
||||
|
||||
return int64(userID), secret
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Auth Handler Tests
|
||||
// =============================================================================
|
||||
@@ -292,6 +451,38 @@ func TestAuthHandler_Login_Success(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthHandler_Login_SetsSessionCookies(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
registerUser(server.URL, "logincookieuser", "logincookie@example.com", "Password123!")
|
||||
resp, body := doPost(server.URL+"/api/v1/auth/login", "", map[string]interface{}{
|
||||
"account": "logincookieuser",
|
||||
"password": "Password123!",
|
||||
})
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected status %d, got %d, body: %s", http.StatusOK, resp.StatusCode, body)
|
||||
}
|
||||
|
||||
refreshCookie := getCookie(resp, "ums_refresh_token")
|
||||
if refreshCookie == nil || refreshCookie.Value == "" {
|
||||
t.Fatalf("login response missing refresh cookie, cookies=%v", resp.Cookies())
|
||||
}
|
||||
if !refreshCookie.HttpOnly {
|
||||
t.Fatalf("refresh cookie should be HttpOnly, got %+v", refreshCookie)
|
||||
}
|
||||
|
||||
presenceCookie := getCookie(resp, "ums_session_present")
|
||||
if presenceCookie == nil || presenceCookie.Value != "1" {
|
||||
t.Fatalf("login response missing presence cookie, cookies=%v", resp.Cookies())
|
||||
}
|
||||
if presenceCookie.HttpOnly {
|
||||
t.Fatalf("presence cookie should be readable by the frontend, got %+v", presenceCookie)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthHandler_Login_WrongPassword(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
@@ -360,6 +551,66 @@ func TestAuthHandler_GetAuthCapabilities(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthHandler_Login_WithTOTPEnabled_ReturnsChallengeToken(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
_, _ = setupEnabledTOTPUser(t, server.URL, "totplogin", "totplogin@example.com", "Password123!")
|
||||
|
||||
resp, body := doPost(server.URL+"/api/v1/auth/login", "", map[string]interface{}{
|
||||
"account": "totplogin",
|
||||
"password": "Password123!",
|
||||
"device_id": "device-login-1",
|
||||
})
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected status %d, got %d, body: %s", http.StatusOK, resp.StatusCode, body)
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(body), &result); err != nil {
|
||||
t.Fatalf("failed to parse login response: %v", err)
|
||||
}
|
||||
|
||||
data, ok := result["data"].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("expected login response data, got %s", body)
|
||||
}
|
||||
|
||||
if data["requires_totp"] != true {
|
||||
t.Fatalf("expected requires_totp=true, got %+v", data)
|
||||
}
|
||||
|
||||
tempToken, ok := data["temp_token"].(string)
|
||||
if !ok || tempToken == "" {
|
||||
t.Fatalf("expected temp_token in TOTP challenge response, got %+v", data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthHandler_VerifyTOTPAfterPasswordLogin_RequiresTempToken(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
userID, secret := setupEnabledTOTPUser(t, server.URL, "totpreverify", "totpreverify@example.com", "Password123!")
|
||||
|
||||
code, err := auth.NewTOTPManager().GenerateCurrentCode(secret)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate TOTP code: %v", err)
|
||||
}
|
||||
|
||||
resp, body := doPost(server.URL+"/api/v1/auth/login/totp-verify", "", map[string]interface{}{
|
||||
"user_id": userID,
|
||||
"code": code,
|
||||
"device_id": "device-login-1",
|
||||
})
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusUnauthorized {
|
||||
t.Fatalf("expected status %d when temp_token is missing, got %d, body: %s", http.StatusUnauthorized, resp.StatusCode, body)
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// User Handler Tests
|
||||
// =============================================================================
|
||||
@@ -451,6 +702,26 @@ func TestUserHandler_UpdateUser_Success(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserHandler_UpdateUser_AdminCanUpdateAnotherUser(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
t.Setenv("BOOTSTRAP_SECRET", "handler-bootstrap-secret")
|
||||
token := bootstrapAdmin(server.URL, "handler-bootstrap-secret", "updateadmin", "updateadmin@test.com", "AdminPass123!")
|
||||
registerUser(server.URL, "targetuser", "targetuser@test.com", "UserPass123!")
|
||||
|
||||
if token == "" {
|
||||
t.Fatal("bootstrap admin should return access token")
|
||||
}
|
||||
|
||||
resp, body := doPut(server.URL+"/api/v1/users/2", token, map[string]string{"nickname": "Updated By Admin"})
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected status %d, got %d, body: %s", http.StatusOK, resp.StatusCode, body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserHandler_DeleteUser_NonAdmin_Forbidden(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
@@ -515,6 +786,26 @@ func TestUserHandler_GetUserRoles_Success(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserHandler_GetUserRoles_AdminCanViewAnotherUser(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
t.Setenv("BOOTSTRAP_SECRET", "handler-bootstrap-secret")
|
||||
token := bootstrapAdmin(server.URL, "handler-bootstrap-secret", "rolesadmin2", "rolesadmin2@test.com", "AdminPass123!")
|
||||
registerUser(server.URL, "roles-target", "roles-target@test.com", "UserPass123!")
|
||||
|
||||
if token == "" {
|
||||
t.Fatal("bootstrap admin should return access token")
|
||||
}
|
||||
|
||||
resp, body := doGet(server.URL+"/api/v1/users/2/roles", token)
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected status %d, got %d, body: %s", http.StatusOK, resp.StatusCode, body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserHandler_AssignRoles_RequiresAdmin(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
@@ -1253,6 +1544,187 @@ func TestAuthHandler_RefreshToken_Success(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthHandler_RefreshToken_AcceptsRefreshCookie(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
registerUser(server.URL, "refreshcookieuser", "refreshcookie@example.com", "Password123!")
|
||||
loginResp, loginBody := doPost(server.URL+"/api/v1/auth/login", "", map[string]interface{}{
|
||||
"account": "refreshcookieuser",
|
||||
"password": "Password123!",
|
||||
})
|
||||
defer loginResp.Body.Close()
|
||||
|
||||
if loginResp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected status %d, got %d, body: %s", http.StatusOK, loginResp.StatusCode, loginBody)
|
||||
}
|
||||
|
||||
refreshCookie := getCookie(loginResp, "ums_refresh_token")
|
||||
if refreshCookie == nil || refreshCookie.Value == "" {
|
||||
t.Fatalf("login response missing refresh cookie, cookies=%v", loginResp.Cookies())
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", server.URL+"/api/v1/auth/refresh", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("create refresh request failed: %v", err)
|
||||
}
|
||||
req.AddCookie(refreshCookie)
|
||||
req.AddCookie(&http.Cookie{Name: "ums_session_present", Value: "1"})
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("refresh request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
bodyBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("read refresh response failed: %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected status %d, got %d, body: %s", http.StatusOK, resp.StatusCode, string(bodyBytes))
|
||||
}
|
||||
|
||||
rotatedCookie := getCookie(resp, "ums_refresh_token")
|
||||
if rotatedCookie == nil || rotatedCookie.Value == "" {
|
||||
t.Fatalf("refresh response missing rotated refresh cookie, cookies=%v", resp.Cookies())
|
||||
}
|
||||
if rotatedCookie.Value == refreshCookie.Value {
|
||||
t.Fatalf("refresh should rotate cookie value, old=%q new=%q", refreshCookie.Value, rotatedCookie.Value)
|
||||
}
|
||||
|
||||
presenceCookie := getCookie(resp, "ums_session_present")
|
||||
if presenceCookie == nil || presenceCookie.Value != "1" {
|
||||
t.Fatalf("refresh response missing presence cookie, cookies=%v", resp.Cookies())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthHandler_RefreshToken_AllowsImmediateRetryWithPreviousCookie(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
registerUser(server.URL, "refreshretryuser", "refreshretry@example.com", "Password123!")
|
||||
loginResp, loginBody := doPost(server.URL+"/api/v1/auth/login", "", map[string]interface{}{
|
||||
"account": "refreshretryuser",
|
||||
"password": "Password123!",
|
||||
})
|
||||
defer loginResp.Body.Close()
|
||||
|
||||
if loginResp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected status %d, got %d, body: %s", http.StatusOK, loginResp.StatusCode, loginBody)
|
||||
}
|
||||
|
||||
refreshCookie := getCookie(loginResp, "ums_refresh_token")
|
||||
if refreshCookie == nil || refreshCookie.Value == "" {
|
||||
t.Fatalf("login response missing refresh cookie, cookies=%v", loginResp.Cookies())
|
||||
}
|
||||
|
||||
newRefreshRequest := func(cookie *http.Cookie) *http.Response {
|
||||
req, err := http.NewRequest(http.MethodPost, server.URL+"/api/v1/auth/refresh", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("create refresh request failed: %v", err)
|
||||
}
|
||||
req.AddCookie(cookie)
|
||||
req.AddCookie(&http.Cookie{Name: "ums_session_present", Value: "1"})
|
||||
|
||||
resp, err := (&http.Client{}).Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("refresh request failed: %v", err)
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
firstResp := newRefreshRequest(refreshCookie)
|
||||
defer firstResp.Body.Close()
|
||||
firstBody, err := io.ReadAll(firstResp.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("read first refresh response failed: %v", err)
|
||||
}
|
||||
if firstResp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected first refresh status %d, got %d, body: %s", http.StatusOK, firstResp.StatusCode, string(firstBody))
|
||||
}
|
||||
|
||||
retryResp := newRefreshRequest(refreshCookie)
|
||||
defer retryResp.Body.Close()
|
||||
retryBody, err := io.ReadAll(retryResp.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("read retry refresh response failed: %v", err)
|
||||
}
|
||||
if retryResp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected retry refresh status %d, got %d, body: %s", http.StatusOK, retryResp.StatusCode, string(retryBody))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthHandler_Logout_ClearsSessionCookies(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
registerUser(server.URL, "logoutcookieuser", "logoutcookie@example.com", "Password123!")
|
||||
loginResp, loginBody := doPost(server.URL+"/api/v1/auth/login", "", map[string]interface{}{
|
||||
"account": "logoutcookieuser",
|
||||
"password": "Password123!",
|
||||
})
|
||||
defer loginResp.Body.Close()
|
||||
|
||||
if loginResp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected status %d, got %d, body: %s", http.StatusOK, loginResp.StatusCode, loginBody)
|
||||
}
|
||||
|
||||
var loginResult map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(loginBody), &loginResult); err != nil {
|
||||
t.Fatalf("parse login response failed: %v", err)
|
||||
}
|
||||
loginData, ok := loginResult["data"].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("login response missing data: %s", loginBody)
|
||||
}
|
||||
accessToken, ok := loginData["access_token"].(string)
|
||||
if !ok || accessToken == "" {
|
||||
t.Fatalf("login response missing access token: %s", loginBody)
|
||||
}
|
||||
|
||||
refreshCookie := getCookie(loginResp, "ums_refresh_token")
|
||||
if refreshCookie == nil || refreshCookie.Value == "" {
|
||||
t.Fatalf("login response missing refresh cookie, cookies=%v", loginResp.Cookies())
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", server.URL+"/api/v1/auth/logout", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("create logout request failed: %v", err)
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||||
req.AddCookie(refreshCookie)
|
||||
req.AddCookie(&http.Cookie{Name: "ums_session_present", Value: "1"})
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("logout request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
bodyBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("read logout response failed: %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected status %d, got %d, body: %s", http.StatusOK, resp.StatusCode, string(bodyBytes))
|
||||
}
|
||||
|
||||
clearedRefreshCookie := getCookie(resp, "ums_refresh_token")
|
||||
if clearedRefreshCookie == nil || clearedRefreshCookie.Value != "" {
|
||||
t.Fatalf("logout response should clear refresh cookie, cookies=%v", resp.Cookies())
|
||||
}
|
||||
|
||||
clearedPresenceCookie := getCookie(resp, "ums_session_present")
|
||||
if clearedPresenceCookie == nil || clearedPresenceCookie.Value != "" {
|
||||
t.Fatalf("logout response should clear presence cookie, cookies=%v", resp.Cookies())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthHandler_RefreshToken_InvalidToken(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
Reference in New Issue
Block a user