mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-08-04 22:40:11 +00:00
fix(auth): revoke current access token session
This commit is contained in:
@@ -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') || '';
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
Reference in New Issue
Block a user