96 lines
3.1 KiB
Go
96 lines
3.1 KiB
Go
package auth
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"hyapp-admin-server/internal/config"
|
|
authcore "hyapp-admin-server/internal/service"
|
|
)
|
|
|
|
func TestRefreshUsesRootCookieAndClearsLegacyCookiePath(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
store := newFakeAuthStore(t)
|
|
handler := testAuthHandler(store)
|
|
login, err := handler.service.Login(LoginInput{Username: "admin", Password: "secret", IP: "127.0.0.1", UserAgent: "browser"})
|
|
if err != nil {
|
|
t.Fatalf("login: %v", err)
|
|
}
|
|
router := gin.New()
|
|
router.POST("/api/v1/auth/refresh", handler.Refresh)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/refresh", nil)
|
|
req.AddCookie(&http.Cookie{Name: refreshCookieName, Value: login.RefreshToken})
|
|
rec := httptest.NewRecorder()
|
|
router.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("refresh status = %d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
setCookies := rec.Result().Cookies()
|
|
if !hasCookieWithPath(setCookies, refreshCookieName, refreshCookiePath) {
|
|
t.Fatalf("refresh must set root refresh cookie: %+v", setCookies)
|
|
}
|
|
if !hasExpiredCookieWithPath(setCookies, refreshCookieName, legacyRefreshCookiePath) {
|
|
t.Fatalf("refresh must clear legacy path cookie: %+v", setCookies)
|
|
}
|
|
}
|
|
|
|
func TestRefreshTriesDuplicateRefreshCookiesUntilOneWorks(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
store := newFakeAuthStore(t)
|
|
handler := testAuthHandler(store)
|
|
first, err := handler.service.Login(LoginInput{Username: "admin", Password: "secret", IP: "127.0.0.1", UserAgent: "browser"})
|
|
if err != nil {
|
|
t.Fatalf("login: %v", err)
|
|
}
|
|
refreshed, err := handler.service.Refresh(first.RefreshToken, LoginInput{IP: "127.0.0.1", UserAgent: "browser"})
|
|
if err != nil {
|
|
t.Fatalf("prime refresh: %v", err)
|
|
}
|
|
router := gin.New()
|
|
router.POST("/api/v1/auth/refresh", handler.Refresh)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/refresh", nil)
|
|
req.Header.Set("Cookie", refreshCookieName+"="+first.RefreshToken+"; "+refreshCookieName+"="+refreshed.RefreshToken)
|
|
rec := httptest.NewRecorder()
|
|
router.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("refresh status = %d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
if strings.Contains(rec.Body.String(), "刷新会话已失效") {
|
|
t.Fatalf("refresh should skip stale duplicate cookie: %s", rec.Body.String())
|
|
}
|
|
}
|
|
|
|
func testAuthHandler(store *fakeAuthStore) *Handler {
|
|
cfg := config.Config{RefreshTokenTTL: 7 * 24 * time.Hour, RefreshCookieSameSite: "lax"}
|
|
return &Handler{
|
|
service: NewService(store, authcore.NewAuthService("test-secret", time.Hour), cfg),
|
|
cfg: cfg,
|
|
}
|
|
}
|
|
|
|
func hasCookieWithPath(cookies []*http.Cookie, name string, path string) bool {
|
|
for _, cookie := range cookies {
|
|
if cookie.Name == name && cookie.Path == path && cookie.MaxAge >= 0 && cookie.Value != "" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func hasExpiredCookieWithPath(cookies []*http.Cookie, name string, path string) bool {
|
|
for _, cookie := range cookies {
|
|
if cookie.Name == name && cookie.Path == path && cookie.MaxAge < 0 {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|