mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 21:00:41 +00:00
feat: add permanent trust functionality for devices with corresponding API and UI updates
This commit is contained in:
@@ -6,6 +6,8 @@ import { errorResponse, jsonResponse } from '../utils/response';
|
||||
import { readKnownDeviceProbe } from '../utils/device';
|
||||
import { generateUUID } from '../utils/uuid';
|
||||
|
||||
const PERMANENT_TRUST_EXPIRES_AT_MS = Date.UTC(2099, 11, 31, 23, 59, 59);
|
||||
|
||||
function normalizeIdentifier(value: string | null | undefined): string {
|
||||
return String(value || '').trim();
|
||||
}
|
||||
@@ -268,6 +270,29 @@ export async function handleRevokeTrustedDevice(
|
||||
return jsonResponse({ success: true, removed });
|
||||
}
|
||||
|
||||
// POST /api/devices/authorized/:deviceIdentifier/permanent
|
||||
// Upgrades an existing active 2FA remember-token record to permanent trust.
|
||||
export async function handleTrustDevicePermanently(
|
||||
request: Request,
|
||||
env: Env,
|
||||
userId: string,
|
||||
deviceIdentifier: string
|
||||
): Promise<Response> {
|
||||
void request;
|
||||
const normalized = String(deviceIdentifier || '').trim();
|
||||
if (!normalized) return errorResponse('Invalid device identifier', 400);
|
||||
|
||||
const storage = new StorageService(env.DB);
|
||||
const updated = await storage.updateTrustedTwoFactorTokensExpiryByDevice(userId, normalized, PERMANENT_TRUST_EXPIRES_AT_MS);
|
||||
if (!updated) return errorResponse('Device is not currently trusted', 409);
|
||||
|
||||
return jsonResponse({
|
||||
success: true,
|
||||
updated,
|
||||
trustedUntil: new Date(PERMANENT_TRUST_EXPIRES_AT_MS).toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
// DELETE /api/devices/:deviceIdentifier
|
||||
export async function handleDeleteDevice(
|
||||
request: Request,
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
handleDeactivateDevice,
|
||||
handleRevokeAllTrustedDevices,
|
||||
handleRevokeTrustedDevice,
|
||||
handleTrustDevicePermanently,
|
||||
handleDeleteAllDevices,
|
||||
handleDeleteDevice,
|
||||
handleUpdateDeviceName,
|
||||
@@ -44,6 +45,12 @@ export async function handleAuthenticatedDeviceRoute(
|
||||
return handleRevokeTrustedDevice(request, env, userId, deviceIdentifier);
|
||||
}
|
||||
|
||||
const permanentAuthorizedDeviceMatch = path.match(/^\/api\/devices\/authorized\/([^/]+)\/permanent$/i);
|
||||
if (permanentAuthorizedDeviceMatch && method === 'POST') {
|
||||
const deviceIdentifier = decodeURIComponent(permanentAuthorizedDeviceMatch[1]);
|
||||
return handleTrustDevicePermanently(request, env, userId, deviceIdentifier);
|
||||
}
|
||||
|
||||
const deleteDeviceMatch = path.match(/^\/api\/devices\/([^/]+)$/i);
|
||||
if (deleteDeviceMatch && method === 'GET') {
|
||||
const deviceIdentifier = decodeURIComponent(deleteDeviceMatch[1]);
|
||||
|
||||
@@ -233,6 +233,21 @@ export async function deleteTrustedTwoFactorTokensByUserId(db: D1Database, userI
|
||||
return Number(result.meta.changes ?? 0);
|
||||
}
|
||||
|
||||
export async function updateTrustedTwoFactorTokensExpiryByDevice(
|
||||
db: D1Database,
|
||||
userId: string,
|
||||
deviceIdentifier: string,
|
||||
expiresAtMs: number
|
||||
): Promise<number> {
|
||||
const now = Date.now();
|
||||
await db.prepare('DELETE FROM trusted_two_factor_device_tokens WHERE expires_at < ?').bind(now).run();
|
||||
const result = await db
|
||||
.prepare('UPDATE trusted_two_factor_device_tokens SET expires_at = ? WHERE user_id = ? AND device_identifier = ? AND expires_at >= ?')
|
||||
.bind(expiresAtMs, userId, deviceIdentifier, now)
|
||||
.run();
|
||||
return Number(result.meta.changes ?? 0);
|
||||
}
|
||||
|
||||
export async function saveTrustedTwoFactorDeviceToken(
|
||||
db: D1Database,
|
||||
trustedTokenKey: TrustedTokenKeyFn,
|
||||
|
||||
@@ -96,6 +96,7 @@ import {
|
||||
upsertDevice as saveStoredDevice,
|
||||
updateDeviceName as updateStoredDeviceName,
|
||||
updateDeviceKeys as updateStoredDeviceKeys,
|
||||
updateTrustedTwoFactorTokensExpiryByDevice as updateStoredTrustedTokensExpiryByDevice,
|
||||
} from './storage-device-repo';
|
||||
import {
|
||||
ensureUsedAttachmentDownloadTokenTable as ensureStoredAttachmentTokenTable,
|
||||
@@ -614,6 +615,10 @@ export class StorageService {
|
||||
return deleteStoredTrustedTokensByUserId(this.db, userId);
|
||||
}
|
||||
|
||||
async updateTrustedTwoFactorTokensExpiryByDevice(userId: string, deviceIdentifier: string, expiresAtMs: number): Promise<number> {
|
||||
return updateStoredTrustedTokensExpiryByDevice(this.db, userId, deviceIdentifier, expiresAtMs);
|
||||
}
|
||||
|
||||
// --- Trusted 2FA remember tokens (device-bound) ---
|
||||
|
||||
async saveTrustedTwoFactorDeviceToken(
|
||||
|
||||
Reference in New Issue
Block a user