fix(auth): revoke current access token session

This commit is contained in:
shuaiplus
2026-07-06 13:49:06 +08:00
parent fd46dffc34
commit 2df43ccdb0
4 changed files with 49 additions and 0 deletions
+29
View File
@@ -163,6 +163,30 @@ function withWebRefreshCookie(request: Request, response: Response, refreshToken
}); });
} }
async function revokePresentedAccessTokenSession(request: Request, env: Env, storage: StorageService): Promise<void> {
const authHeader = request.headers.get('Authorization');
if (!authHeader) return;
const auth = new AuthService(env);
const verified = await auth.verifyAccessTokenWithUser(authHeader);
if (!verified) return;
const deviceIdentifier = String(verified.payload.did || '').trim();
if (deviceIdentifier) {
const nextSessionStamp = generateUUID();
await storage.rotateDeviceSessionStamp(verified.user.id, deviceIdentifier, nextSessionStamp);
await storage.deleteRefreshTokensByDevice(verified.user.id, deviceIdentifier);
AuthService.invalidateDeviceCache(verified.user.id, deviceIdentifier);
return;
}
verified.user.securityStamp = generateUUID();
verified.user.updatedAt = new Date().toISOString();
await storage.saveUser(verified.user);
await storage.deleteRefreshTokensByUserId(verified.user.id);
AuthService.invalidateUserCache(verified.user.id);
}
function buildPreloginResponse( function buildPreloginResponse(
email: string, email: string,
kdfType: number, kdfType: number,
@@ -1010,6 +1034,11 @@ export async function handlePrelogin(request: Request, env: Env): Promise<Respon
// RFC 7009 allows returning 200 even if token is unknown. // RFC 7009 allows returning 200 even if token is unknown.
export async function handleRevocation(request: Request, env: Env): Promise<Response> { export async function handleRevocation(request: Request, env: Env): Promise<Response> {
const storage = new StorageService(env.DB); const storage = new StorageService(env.DB);
try {
await revokePresentedAccessTokenSession(request, env, storage);
} catch {
// RFC 7009 revocation is best-effort and should not reveal token state.
}
let body: Record<string, string>; let body: Record<string, string>;
const contentType = request.headers.get('content-type') || ''; const contentType = request.headers.get('content-type') || '';
+14
View File
@@ -97,6 +97,20 @@ export async function touchDeviceLastSeen(
return Number(result.meta.changes ?? 0) > 0; return Number(result.meta.changes ?? 0) > 0;
} }
export async function rotateDeviceSessionStamp(
db: D1Database,
userId: string,
deviceIdentifier: string,
sessionStamp: string
): Promise<boolean> {
const now = new Date().toISOString();
const result = await db
.prepare('UPDATE devices SET session_stamp = ?, updated_at = ? WHERE user_id = ? AND device_identifier = ?')
.bind(sessionStamp, now, userId, deviceIdentifier)
.run();
return Number(result.meta.changes ?? 0) > 0;
}
export async function updateDeviceKeys( export async function updateDeviceKeys(
db: D1Database, db: D1Database,
userId: string, userId: string,
+5
View File
@@ -109,6 +109,7 @@ import {
isKnownDevice as getKnownStoredDevice, isKnownDevice as getKnownStoredDevice,
isKnownDeviceByEmail as getKnownStoredDeviceByEmail, isKnownDeviceByEmail as getKnownStoredDeviceByEmail,
saveTrustedTwoFactorDeviceToken as saveStoredTrustedDeviceToken, saveTrustedTwoFactorDeviceToken as saveStoredTrustedDeviceToken,
rotateDeviceSessionStamp as rotateStoredDeviceSessionStamp,
touchDeviceLastSeen as touchStoredDeviceLastSeen, touchDeviceLastSeen as touchStoredDeviceLastSeen,
upsertDevice as saveStoredDevice, upsertDevice as saveStoredDevice,
updateDeviceName as updateStoredDeviceName, updateDeviceName as updateStoredDeviceName,
@@ -761,6 +762,10 @@ export class StorageService {
return findStoredDevice(this.db, userId, deviceIdentifier); return findStoredDevice(this.db, userId, deviceIdentifier);
} }
async rotateDeviceSessionStamp(userId: string, deviceIdentifier: string, sessionStamp: string): Promise<boolean> {
return rotateStoredDeviceSessionStamp(this.db, userId, deviceIdentifier, sessionStamp);
}
async updateDeviceKeys( async updateDeviceKeys(
userId: string, userId: string,
deviceIdentifier: string, deviceIdentifier: string,
+1
View File
@@ -402,6 +402,7 @@ export async function revokeCurrentSession(session: SessionState | null): Promis
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded',
...(session?.accessToken ? { Authorization: `Bearer ${session.accessToken}` } : {}),
...(session?.authMode === 'web-cookie' ? { [WEB_SESSION_HEADER]: '1' } : {}), ...(session?.authMode === 'web-cookie' ? { [WEB_SESSION_HEADER]: '1' } : {}),
}, },
body: body.toString(), body: body.toString(),