mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 21:00:41 +00:00
feat: implement user and device cache invalidation in AuthService
This commit is contained in:
@@ -526,6 +526,7 @@ export async function handleChangePassword(request: Request, env: Env, userId: s
|
||||
user.updatedAt = new Date().toISOString();
|
||||
await storage.saveUser(user);
|
||||
await storage.deleteRefreshTokensByUserId(user.id);
|
||||
AuthService.invalidateUserCache(user.id);
|
||||
await storage.createAuditLog({
|
||||
id: generateUUID(),
|
||||
actorUserId: user.id,
|
||||
@@ -587,6 +588,7 @@ export async function handleSetTotpStatus(request: Request, env: Env, userId: st
|
||||
user.updatedAt = new Date().toISOString();
|
||||
await storage.saveUser(user);
|
||||
await storage.deleteRefreshTokensByUserId(user.id);
|
||||
AuthService.invalidateUserCache(user.id);
|
||||
return jsonResponse({ enabled: true, recoveryCode: user.totpRecoveryCode, object: 'twoFactor' });
|
||||
}
|
||||
|
||||
@@ -601,6 +603,7 @@ export async function handleSetTotpStatus(request: Request, env: Env, userId: st
|
||||
user.updatedAt = new Date().toISOString();
|
||||
await storage.saveUser(user);
|
||||
await storage.deleteRefreshTokensByUserId(user.id);
|
||||
AuthService.invalidateUserCache(user.id);
|
||||
return jsonResponse({ enabled: false, object: 'twoFactor' });
|
||||
}
|
||||
|
||||
@@ -708,6 +711,7 @@ export async function handleRecoverTwoFactor(request: Request, env: Env): Promis
|
||||
user.updatedAt = new Date().toISOString();
|
||||
await storage.saveUser(user);
|
||||
await storage.deleteRefreshTokensByUserId(user.id);
|
||||
AuthService.invalidateUserCache(user.id);
|
||||
await rateLimit.clearLoginAttempts(recoverLimitKey);
|
||||
|
||||
return jsonResponse({
|
||||
@@ -801,6 +805,7 @@ async function apiKey(request: Request, env: Env, userId: string, rotate: boolea
|
||||
}
|
||||
user.updatedAt = new Date().toISOString();
|
||||
await storage.saveUser(user);
|
||||
AuthService.invalidateUserCache(user.id);
|
||||
}
|
||||
|
||||
return jsonResponse({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Env, User, Invite } from '../types';
|
||||
import { AuthService } from '../services/auth';
|
||||
import { StorageService } from '../services/storage';
|
||||
import { jsonResponse, errorResponse } from '../utils/response';
|
||||
import { generateUUID } from '../utils/uuid';
|
||||
@@ -222,6 +223,7 @@ export async function handleAdminSetUserStatus(
|
||||
if (nextStatus === 'banned') {
|
||||
await storage.deleteRefreshTokensByUserId(target.id);
|
||||
}
|
||||
AuthService.invalidateUserCache(target.id);
|
||||
await writeAuditLog(storage, actorUser.id, 'admin.user.status', 'user', target.id, {
|
||||
status: nextStatus,
|
||||
});
|
||||
@@ -280,6 +282,7 @@ export async function handleAdminDeleteUser(
|
||||
|
||||
await storage.deleteRefreshTokensByUserId(target.id);
|
||||
await storage.deleteUserById(target.id);
|
||||
AuthService.invalidateUserCache(target.id);
|
||||
await writeAuditLog(storage, actorUser.id, 'admin.user.delete', 'user', target.id, {
|
||||
email: target.email,
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Device, DevicePendingAuthRequest, DeviceResponse, ProtectedDeviceResponse as ProtectedDeviceWireResponse } from '../types';
|
||||
import { Env } from '../types';
|
||||
import { getOnlineUserDevices, notifyUserLogout } from '../durable/notifications-hub';
|
||||
import { AuthService } from '../services/auth';
|
||||
import { StorageService } from '../services/storage';
|
||||
import { errorResponse, jsonResponse } from '../utils/response';
|
||||
import { readKnownDeviceProbe } from '../utils/device';
|
||||
@@ -309,6 +310,7 @@ export async function handleDeleteDevice(
|
||||
await storage.deleteRefreshTokensByDevice(userId, normalized);
|
||||
const deleted = await storage.deleteDevice(userId, normalized);
|
||||
if (deleted) {
|
||||
AuthService.invalidateDeviceCache(userId, normalized);
|
||||
notifyUserLogout(env, userId, normalized);
|
||||
}
|
||||
return jsonResponse({ success: deleted });
|
||||
@@ -352,6 +354,7 @@ export async function handleDeleteAllDevices(request: Request, env: Env, userId:
|
||||
user.securityStamp = generateUUID();
|
||||
user.updatedAt = new Date().toISOString();
|
||||
await storage.saveUser(user);
|
||||
AuthService.invalidateUserCache(userId);
|
||||
notifyUserLogout(env, userId, null);
|
||||
return jsonResponse({ success: true, removedTrusted, removedSessions: removedSessions ?? 0, removedDevices });
|
||||
}
|
||||
@@ -483,6 +486,7 @@ export async function handleDeactivateDevice(
|
||||
await storage.deleteRefreshTokensByDevice(userId, normalized);
|
||||
const deleted = await storage.deleteDevice(userId, normalized);
|
||||
if (deleted) {
|
||||
AuthService.invalidateDeviceCache(userId, normalized);
|
||||
notifyUserLogout(env, userId, normalized);
|
||||
}
|
||||
return jsonResponse({ success: deleted });
|
||||
|
||||
@@ -32,6 +32,17 @@ function resolveTotpSecret(userSecret: string | null): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
async function resolveDeviceSession(
|
||||
storage: StorageService,
|
||||
userId: string,
|
||||
deviceInfo: ReturnType<typeof readAuthRequestDeviceInfo>
|
||||
): Promise<{ identifier: string; sessionStamp: string } | null> {
|
||||
if (!deviceInfo.deviceIdentifier) return null;
|
||||
const existingDevice = await storage.getDevice(userId, deviceInfo.deviceIdentifier);
|
||||
const sessionStamp = String(existingDevice?.sessionStamp || '').trim() || generateUUID();
|
||||
return { identifier: deviceInfo.deviceIdentifier, sessionStamp };
|
||||
}
|
||||
|
||||
function shouldUseWebSession(request: Request): boolean {
|
||||
return String(request.headers.get('X-NodeWarden-Web-Session') || '').trim() === '1';
|
||||
}
|
||||
@@ -320,10 +331,7 @@ export async function handleToken(request: Request, env: Env): Promise<Response>
|
||||
}
|
||||
|
||||
// Persist device only after successful password + (optional) 2FA verification.
|
||||
const deviceSession =
|
||||
deviceInfo.deviceIdentifier
|
||||
? { identifier: deviceInfo.deviceIdentifier, sessionStamp: generateUUID() }
|
||||
: null;
|
||||
const deviceSession = await resolveDeviceSession(storage, user.id, deviceInfo);
|
||||
if (deviceSession) {
|
||||
await storage.upsertDevice(
|
||||
user.id,
|
||||
@@ -413,10 +421,7 @@ export async function handleToken(request: Request, env: Env): Promise<Response>
|
||||
}
|
||||
|
||||
// Persist device only after successful client credential verification.
|
||||
const deviceSession =
|
||||
deviceInfo.deviceIdentifier
|
||||
? { identifier: deviceInfo.deviceIdentifier, sessionStamp: generateUUID() }
|
||||
: null;
|
||||
const deviceSession = await resolveDeviceSession(storage, user.id, deviceInfo);
|
||||
if (deviceSession) {
|
||||
await storage.upsertDevice(
|
||||
user.id,
|
||||
|
||||
Reference in New Issue
Block a user