84448b3064
Deploy Server / deploy-server (push) Successful in 3m3s
用户中心用 header token 方案:access token 只存内存、refresh token 存 localStorage,页面刷新后靠 POST /v1/auth/refresh 带 X-Refresh-Token 头静默续期 (web/usercenter/lib/api/http.ts)。 但 CORS Allow-Headers 只放行 Content-Type/Authorization,浏览器预检发现 X-Refresh-Token 不在白名单 → 拦截真正的 refresh 请求 → api.refresh() 抛网络错 → setAuthed(false) → 甩回登录页。表现:登录能成功(不带该头),但一刷新就掉线, 用量/续费等需登录态的页面全打不开。 修:Allow-Headers 加 X-Refresh-Token。加回归断言(cors_test)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
54 lines
1.8 KiB
Go
54 lines
1.8 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestCORS(t *testing.T) {
|
|
t.Setenv("CORS_ORIGINS", "https://pangolin.yanmeiai.com")
|
|
mw := NewCORS()
|
|
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) })
|
|
h := mw(next)
|
|
|
|
// 允许的 Origin:补 Allow-Origin,普通请求继续。
|
|
r := httptest.NewRequest("POST", "/v1/auth/login", nil)
|
|
r.Header.Set("Origin", "https://pangolin.yanmeiai.com")
|
|
w := httptest.NewRecorder()
|
|
h.ServeHTTP(w, r)
|
|
if got := w.Header().Get("Access-Control-Allow-Origin"); got != "https://pangolin.yanmeiai.com" {
|
|
t.Fatalf("allowed origin: want header, got %q", got)
|
|
}
|
|
if w.Code != 200 {
|
|
t.Fatalf("non-preflight should reach next, got %d", w.Code)
|
|
}
|
|
|
|
// OPTIONS 预检:204,不落到业务。
|
|
r = httptest.NewRequest("OPTIONS", "/v1/auth/login", nil)
|
|
r.Header.Set("Origin", "https://pangolin.yanmeiai.com")
|
|
w = httptest.NewRecorder()
|
|
h.ServeHTTP(w, r)
|
|
if w.Code != http.StatusNoContent {
|
|
t.Fatalf("preflight: want 204, got %d", w.Code)
|
|
}
|
|
if w.Header().Get("Access-Control-Allow-Methods") == "" {
|
|
t.Fatalf("preflight missing Allow-Methods")
|
|
}
|
|
// 静默续期/登出把 refresh token 放 X-Refresh-Token 头,必须在允许头里,
|
|
// 否则浏览器预检拦截 /v1/auth/refresh → 登录后一刷新即掉线。
|
|
if ah := w.Header().Get("Access-Control-Allow-Headers"); !strings.Contains(ah, "X-Refresh-Token") {
|
|
t.Fatalf("preflight Allow-Headers must include X-Refresh-Token, got %q", ah)
|
|
}
|
|
|
|
// 未白名单 Origin:不补 Allow-Origin。
|
|
r = httptest.NewRequest("POST", "/v1/auth/login", nil)
|
|
r.Header.Set("Origin", "https://evil.example.com")
|
|
w = httptest.NewRecorder()
|
|
h.ServeHTTP(w, r)
|
|
if got := w.Header().Get("Access-Control-Allow-Origin"); got != "" {
|
|
t.Fatalf("disallowed origin should get no header, got %q", got)
|
|
}
|
|
}
|