mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 21:00:41 +00:00
feat: add overlap grace period for refresh tokens to handle concurrent requests
This commit is contained in:
@@ -6,6 +6,9 @@
|
|||||||
// Refresh token lifetime in milliseconds.
|
// Refresh token lifetime in milliseconds.
|
||||||
// 刷新令牌有效期(毫秒)。
|
// 刷新令牌有效期(毫秒)。
|
||||||
refreshTokenTtlMs: 30 * 24 * 60 * 60 * 1000,
|
refreshTokenTtlMs: 30 * 24 * 60 * 60 * 1000,
|
||||||
|
// Grace window for previous refresh token after rotation (ms).
|
||||||
|
// 刷新令牌轮换后的旧令牌宽限窗口(毫秒)。
|
||||||
|
refreshTokenOverlapGraceMs: 60 * 1000,
|
||||||
// Refresh token random byte length.
|
// Refresh token random byte length.
|
||||||
// 刷新令牌随机字节长度。
|
// 刷新令牌随机字节长度。
|
||||||
refreshTokenRandomBytes: 32,
|
refreshTokenRandomBytes: 32,
|
||||||
|
|||||||
@@ -244,8 +244,12 @@ export async function handleToken(request: Request, env: Env): Promise<Response>
|
|||||||
return identityErrorResponse('Invalid refresh token', 'invalid_grant', 400);
|
return identityErrorResponse('Invalid refresh token', 'invalid_grant', 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Revoke old refresh token (prevent reuse)
|
// Keep a short overlap window for old refresh token to absorb
|
||||||
await storage.deleteRefreshToken(refreshToken);
|
// concurrent refresh requests from multiple client contexts.
|
||||||
|
await storage.constrainRefreshTokenExpiry(
|
||||||
|
refreshToken,
|
||||||
|
Date.now() + LIMITS.auth.refreshTokenOverlapGraceMs
|
||||||
|
);
|
||||||
|
|
||||||
const { accessToken, user } = result;
|
const { accessToken, user } = result;
|
||||||
const newRefreshToken = await auth.generateRefreshToken(user.id);
|
const newRefreshToken = await auth.generateRefreshToken(user.id);
|
||||||
|
|||||||
@@ -643,6 +643,26 @@ export class StorageService {
|
|||||||
await this.db.prepare('DELETE FROM refresh_tokens WHERE token = ?').bind(tokenKey).run();
|
await this.db.prepare('DELETE FROM refresh_tokens WHERE token = ?').bind(tokenKey).run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keep a short overlap window for rotated refresh token to reduce
|
||||||
|
// multi-context refresh races (e.g. browser extension popup/background).
|
||||||
|
// Expiry is only tightened, never extended.
|
||||||
|
async constrainRefreshTokenExpiry(token: string, maxExpiresAtMs: number): Promise<void> {
|
||||||
|
const tokenKey = await this.refreshTokenKey(token);
|
||||||
|
|
||||||
|
await this.db.prepare(
|
||||||
|
'UPDATE refresh_tokens ' +
|
||||||
|
'SET expires_at = CASE WHEN expires_at > ? THEN ? ELSE expires_at END ' +
|
||||||
|
'WHERE token = ?'
|
||||||
|
).bind(maxExpiresAtMs, maxExpiresAtMs, tokenKey).run();
|
||||||
|
|
||||||
|
// Best-effort legacy plaintext support for older rows.
|
||||||
|
await this.db.prepare(
|
||||||
|
'UPDATE refresh_tokens ' +
|
||||||
|
'SET expires_at = CASE WHEN expires_at > ? THEN ? ELSE expires_at END ' +
|
||||||
|
'WHERE token = ?'
|
||||||
|
).bind(maxExpiresAtMs, maxExpiresAtMs, token).run();
|
||||||
|
}
|
||||||
|
|
||||||
private async trustedTwoFactorTokenKey(token: string): Promise<string> {
|
private async trustedTwoFactorTokenKey(token: string): Promise<string> {
|
||||||
const digest = await this.sha256Hex(token);
|
const digest = await this.sha256Hex(token);
|
||||||
return `sha256:${digest}`;
|
return `sha256:${digest}`;
|
||||||
|
|||||||
Reference in New Issue
Block a user