feat: Implement TOTP-based two-factor authentication

- Added TOTP support for two-factor authentication in user profiles and login flows.
- Introduced device management endpoints to handle known devices and their registration.
- Enhanced database schema to include devices and trusted two-factor tokens.
- Updated response handling to include two-factor token in successful login responses.
- Modified registration and login pages to guide users through enabling TOTP.
- Improved device identification and management utilities for better user experience.
This commit is contained in:
shuaiplus
2026-02-20 15:59:55 +08:00
parent 2b6852fb7f
commit 363a029618
15 changed files with 695 additions and 119 deletions
+55 -26
View File
@@ -3,39 +3,68 @@ import { handleRequest } from './router';
import { StorageService } from './services/storage';
import { applyCors, jsonResponse } from './utils/response';
// Per-isolate flags. Each Worker isolate may have its own copy of these flags.
// initializeDatabase() only validates schema presence, so retries are cheap.
let dbInitialized = false;
let dbInitError: string | null = null;
let dbInitPromise: Promise<void> | null = null;
function shouldSkipDatabaseInit(request: Request): boolean {
const url = new URL(request.url);
const path = url.pathname;
const method = request.method;
if (method === 'OPTIONS') return true;
if (method === 'GET' && (path === '/favicon.ico' || path === '/favicon.svg')) return true;
if (method === 'GET' && path === '/.well-known/appspecific/com.chrome.devtools.json') return true;
if (method === 'GET' && path.startsWith('/icons/')) return true;
if (path.startsWith('/notifications/')) return true;
if (method === 'GET' && (path === '/config' || path === '/api/config' || path === '/api/version')) return true;
return false;
}
async function ensureDatabaseInitialized(env: Env): Promise<void> {
if (dbInitialized) return;
if (!dbInitPromise) {
dbInitPromise = (async () => {
const storage = new StorageService(env.DB);
await storage.initializeDatabase();
dbInitialized = true;
dbInitError = null;
})()
.catch((error: unknown) => {
console.error('Failed to initialize database:', error);
dbInitError = error instanceof Error ? error.message : 'Unknown database initialization error';
})
.finally(() => {
dbInitPromise = null;
});
}
await dbInitPromise;
}
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
// Auto-initialize database on first request
if (!dbInitialized) {
try {
const storage = new StorageService(env.DB);
await storage.initializeDatabase();
dbInitialized = true;
dbInitError = null;
} catch (error) {
console.error('Failed to initialize database:', error);
dbInitError = error instanceof Error ? error.message : 'Unknown database initialization error';
}
}
void ctx;
const requiresDatabase = !shouldSkipDatabaseInit(request);
if (dbInitError) {
const resp = jsonResponse(
{
error: 'Database not initialized',
error_description: dbInitError,
ErrorModel: {
Message: dbInitError,
Object: 'error',
if (requiresDatabase) {
await ensureDatabaseInitialized(env);
if (dbInitError) {
const resp = jsonResponse(
{
error: 'Database not initialized',
error_description: dbInitError,
ErrorModel: {
Message: dbInitError,
Object: 'error',
},
},
},
500
);
return applyCors(request, resp);
500
);
return applyCors(request, resp);
}
}
const resp = await handleRequest(request, env);