- auth/refresh.go: RefreshStore(Issue/Rotate/Revoke/RevokeAll/RevokeByUser), SHA-256哈希256-bit随机串, 单次旋转事务(并发重放fail-closed) - 迁移0008_refresh_tokens(id/user_id/role/token_hash UNIQUE/expires_at/revoked_at + 索引) - gateway: POST /v1/auth/refresh(旋转/续期) + /v1/auth/logout(单/全设备); login响应增refreshToken - config: AUTH_REFRESH_TTL(720h)/AUTH_REFRESH_ROTATE_DISABLED; decode.go 支持time.Duration - rotate-jwt成功后RevokeAll(响应refreshTokensRevoked), revoke-keys不动refresh - 14测试+真实二进制e2e, build/vet/test绿+交叉编译 - Docs/api-client v0.25 + api-reference §2.3/2.4 同步
26 lines
1.2 KiB
SQL
26 lines
1.2 KiB
SQL
-- 0008_refresh_tokens: opaque refresh tokens backing POST /api/v1/auth/refresh
|
|
-- (B2). The long-lived credential that renews the 24h access token, so a client
|
|
-- can stay signed in without storing the account password.
|
|
--
|
|
-- Only the SHA-256 hash of a token is stored (token_hash); the plaintext is
|
|
-- returned once at login/refresh and never persisted or logged. revoked_at is
|
|
-- NULL while a grant is live; expiry is validated in Go after the hash lookup.
|
|
-- The canonical schema is also re-issued idempotently by auth.NewRefreshStore so
|
|
-- an in-memory store used by tests stays self-contained; this migration is the
|
|
-- authoritative path for an existing database.
|
|
CREATE TABLE IF NOT EXISTS refresh_tokens (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL,
|
|
role TEXT NOT NULL,
|
|
token_hash TEXT NOT NULL UNIQUE,
|
|
expires_at TEXT NOT NULL,
|
|
revoked_at TEXT,
|
|
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
|
|
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_refresh_tokens_user_id
|
|
ON refresh_tokens (user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_refresh_tokens_expires_at
|
|
ON refresh_tokens (expires_at);
|