feat: add recovery code functionality and device management

This commit is contained in:
shuaiplus
2026-03-01 08:49:35 +08:00
committed by Shuai
parent 8852127743
commit 8641df3cff
15 changed files with 995 additions and 63 deletions
+36 -1
View File
@@ -19,6 +19,8 @@ import {
handleChangePassword,
handleGetTotpStatus,
handleSetTotpStatus,
handleGetTotpRecoveryCode,
handleRecoverTwoFactor,
} from './handlers/accounts';
// Cipher handlers
@@ -68,7 +70,15 @@ import { handleSync } from './handlers/sync';
// Setup handlers
import { handleSetupStatus } from './handlers/setup';
import { handleKnownDevice, handleGetDevices, handleUpdateDeviceToken } from './handlers/devices';
import {
handleKnownDevice,
handleGetAuthorizedDevices,
handleGetDevices,
handleRevokeAllTrustedDevices,
handleRevokeTrustedDevice,
handleDeleteDevice,
handleUpdateDeviceToken
} from './handlers/devices';
// Import handler
import { handleCiphersImport } from './handlers/import';
@@ -310,6 +320,10 @@ export async function handleRequest(request: Request, env: Env): Promise<Respons
return handlePrelogin(request, env);
}
if ((path === '/identity/accounts/recover-2fa' || path === '/api/accounts/recover-2fa') && method === 'POST') {
return handleRecoverTwoFactor(request, env);
}
// Config endpoint (no auth required for basic config)
// Bitwarden clients call GET "/config" (relative to the API base URL).
// They also tolerate different casing, but their response models use PascalCase.
@@ -467,6 +481,10 @@ export async function handleRequest(request: Request, env: Env): Promise<Respons
if (method === 'PUT' || method === 'POST') return handleSetTotpStatus(request, env, userId);
}
if ((path === '/api/accounts/totp/recovery-code' || path === '/api/two-factor/get-recover') && method === 'POST') {
return handleGetTotpRecoveryCode(request, env, userId);
}
// Revision date endpoint
if (path === '/api/accounts/revision-date' && method === 'GET') {
return handleGetRevisionDate(request, env, userId);
@@ -666,6 +684,23 @@ export async function handleRequest(request: Request, env: Env): Promise<Respons
return handleGetDevices(request, env, userId);
}
if (path === '/api/devices/authorized') {
if (method === 'GET') return handleGetAuthorizedDevices(request, env, userId);
if (method === 'DELETE') return handleRevokeAllTrustedDevices(request, env, userId);
}
const authorizedDeviceMatch = path.match(/^\/api\/devices\/authorized\/([^/]+)$/i);
if (authorizedDeviceMatch && method === 'DELETE') {
const deviceIdentifier = decodeURIComponent(authorizedDeviceMatch[1]);
return handleRevokeTrustedDevice(request, env, userId, deviceIdentifier);
}
const deleteDeviceMatch = path.match(/^\/api\/devices\/([^/]+)$/i);
if (deleteDeviceMatch && method === 'DELETE') {
const deviceIdentifier = decodeURIComponent(deleteDeviceMatch[1]);
return handleDeleteDevice(request, env, userId, deviceIdentifier);
}
// Admin endpoints
if (path === '/api/admin/users' && method === 'GET') {
return handleAdminListUsers(request, env, currentUser);