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>
53 lines
2.1 KiB
Go
53 lines
2.1 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// NewCORS 返回一个 CORS 中间件:为白名单 Origin(精确匹配)补 CORS 响应头,并直接
|
|
// 应答 OPTIONS 预检。Web 用户中心(app.yanmeiai.com,浏览器)跨域调用 /v1/*,浏览器
|
|
// 会先发预检、并校验 Access-Control-Allow-Origin;原生移动/桌面客户端不是浏览器、
|
|
// 不受 CORS 约束,故此前无需 CORS。
|
|
//
|
|
// Origin 白名单来自 CORS_ORIGINS(逗号分隔),缺省含 usercenter 的正式域与 pages.dev。
|
|
// 认证走 Authorization: Bearer(非 cookie),所以不需要 Allow-Credentials。
|
|
func NewCORS() func(http.Handler) http.Handler {
|
|
raw := os.Getenv("CORS_ORIGINS")
|
|
if raw == "" {
|
|
// 用户中心已迁到主站子路径 pangolin.yanmeiai.com/user/,其 origin 即主站域名
|
|
// (旧 app.yanmeiai.com 已停用)。pages.dev 为 CF Pages 直连兜底。
|
|
raw = "https://pangolin.yanmeiai.com,https://pangolin-site.pages.dev"
|
|
}
|
|
allowed := map[string]bool{}
|
|
for _, o := range strings.Split(raw, ",") {
|
|
o = strings.TrimSpace(o)
|
|
if o != "" {
|
|
allowed[o] = true
|
|
}
|
|
}
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
origin := r.Header.Get("Origin")
|
|
if origin != "" && allowed[origin] {
|
|
h := w.Header()
|
|
h.Set("Access-Control-Allow-Origin", origin)
|
|
h.Add("Vary", "Origin")
|
|
h.Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
|
|
// X-Refresh-Token:静默续期/登出把 refresh token 放在此自定义头里
|
|
// (header token 方案,见 web/usercenter/lib/api/http.ts)。不放行则浏览器
|
|
// 预检拦截 /v1/auth/refresh → 登录后一刷新即掉线(login 不带此头故不受影响)。
|
|
h.Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Refresh-Token")
|
|
h.Set("Access-Control-Max-Age", "600")
|
|
}
|
|
// 预检请求直接 204(不落到业务路由,避免 /v1/... 的 OPTIONS 404)。
|
|
if r.Method == http.MethodOptions {
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|