浏览器免重登:HttpOnly cookie + 服务端 session 表 + 设备名持久化
- 新增 web_sessions 表(sqlc):refresh_token 以 AES-256-GCM 落盘,密钥取自 CDROP_SESSION_SECRET(env、不在库内);cookie 仅存不透明随机串,表 id 存其 SHA-256,故 DB 单独泄露既换不出可用 cookie、也解不出 token - /auth/exchange 建 session 并下发 HttpOnly; Secure; SameSite=Lax; Path=/api/auth cookie,响应体不再回传 refresh_token - /auth/refresh 改为 cookie 驱动(7 天滑动失活),同时即开机静默免重登; 新增 /auth/logout(删 session + 清 cookie)、/auth/device(写设备名入 session) - device_name 随 session 存,开机 refresh 带回前端,抗 iOS PWA 存储清除; setup / 改名时 syncWebDeviceName 推送服务端 - striped per-session 锁串行化同会话并发 refresh,防一次性 refresh_token 被 花两次而把用户从所有 tab 登出 - 前端 store 移除 refreshToken 字段(长寿命凭据彻底不进 JS);main.tsx 首屏前 bootstrapAuth 用 cookie 静默续期,命中直接进已登录态、避免登录页闪现 - config:prod 强制 CDROP_SESSION_SECRET,缺失拒启动 - 桌面端不受影响(自有 loopback flow + Go keyring,不碰这两个端点)
This commit is contained in:
@@ -64,7 +64,7 @@ func (a *Authenticator) Middleware(next http.Handler) http.Handler {
|
||||
return
|
||||
}
|
||||
|
||||
deviceName := sanitizeDeviceName(r.Header.Get("X-Device-Name"))
|
||||
deviceName := SanitizeDeviceName(r.Header.Get("X-Device-Name"))
|
||||
deviceType := normalizeDeviceType(r.Header.Get("X-Device-Type"))
|
||||
if claims.Scoped() {
|
||||
// The backend authoritatively knows a scoped token is the iOS
|
||||
@@ -284,14 +284,14 @@ func DeriveHS256Key(secret string) []byte {
|
||||
return sum[:]
|
||||
}
|
||||
|
||||
// sanitizeDeviceName enforces the global ASCII-only device-name policy. Device
|
||||
// SanitizeDeviceName enforces the global ASCII-only device-name policy. Device
|
||||
// names ride in the X-Device-Name HTTP header, which can't carry non-ASCII
|
||||
// reliably (and browser fetch rejects such header values outright), so the name
|
||||
// is restricted to printable ASCII everywhere. Here we keep only printable ASCII
|
||||
// (0x20–0x7E), trim, and cap the length as a server-side backstop; clients also
|
||||
// validate the name up front for a clear error. Empty after sanitising → the
|
||||
// caller falls back to a UA-derived default.
|
||||
func sanitizeDeviceName(raw string) string {
|
||||
func SanitizeDeviceName(raw string) string {
|
||||
var b strings.Builder
|
||||
for _, r := range raw {
|
||||
if r >= 0x20 && r <= 0x7E {
|
||||
|
||||
@@ -483,8 +483,8 @@ func TestSanitizeDeviceName(t *testing.T) {
|
||||
{"Mac Book", "MacBook"}, // NBSP dropped
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := sanitizeDeviceName(c.in); got != c.want {
|
||||
t.Errorf("sanitizeDeviceName(%q) = %q, want %q", c.in, got, c.want)
|
||||
if got := SanitizeDeviceName(c.in); got != c.want {
|
||||
t.Errorf("SanitizeDeviceName(%q) = %q, want %q", c.in, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user