feat(backend): 会话安全加固 + 授权实时 phase + 首次使用自动试用
会话安全(jti 轮换 / 重用检测 / 改密吊销 / 禁用即时下线 / 清理 / 失败登录落库): - refresh token 轮换 jti + token-family 重用检测,旧 token 重放即吊销整条会话 - 改密码、停用用户即时吊销其全部活跃会话(revoked_by 审计) - 中间件 session JOIN user 校验,禁用/删除用户带 token 请求返回 401 USER_DISABLED - 新增 login_attempts 失败登录落库 + 会话保留期清理 goroutine 授权实时 phase + 心跳回带: - LicenseGuard 改为按当前 DB 实时计算 phase(30s 每店缓存),续费/过期/被改 ~30s 内对写操作生效,无需重登 - /auth/ping 回带授权概况(ShopInfoView,与 /license/info 同构),客户端一次心跳即刷新横幅/门禁 首次使用自动试用 + code-review 修复: - 门店首次登录/续期无有效授权时自动签发 30 天 trial(快路径无锁 Count,仅首用走 FOR UPDATE 事务) - ShopInfo 区分「确无授权」与瞬时 DB 错误,避免误降级 - trial 签发后改为在事务提交后再失效 phase 缓存(修复早于提交的竞态) - 存量无 sid token 续期纳入显式上限,legacy 会话不再游离于并发配额之外 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -123,7 +123,7 @@ func TestForceLogout_RevokesSession(t *testing.T) {
|
||||
require.Len(t, views, 1)
|
||||
assert.True(t, views[0].Online)
|
||||
|
||||
require.NoError(t, svc.ForceLogout(shop.ID, views[0].ID))
|
||||
require.NoError(t, svc.ForceLogout(shop.ID, views[0].ID, 0))
|
||||
|
||||
_, err = svc.RefreshTokens(pair.RefreshToken)
|
||||
assert.ErrorIs(t, err, ErrSessionRevoked)
|
||||
@@ -148,7 +148,7 @@ func TestForceLogout_TenantIsolation(t *testing.T) {
|
||||
require.Len(t, views, 1)
|
||||
|
||||
// 用 shopB 的 shopID 尝试下线 shopA 的会话 → 找不到
|
||||
err = svc.ForceLogout(shopB.ID, views[0].ID)
|
||||
err = svc.ForceLogout(shopB.ID, views[0].ID, 0)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user