import { Env, User, ProfileResponse, DEFAULT_DEV_SECRET } from '../types'; import { StorageService } from '../services/storage'; import { AuthService } from '../services/auth'; import { jsonResponse, errorResponse } from '../utils/response'; import { generateUUID } from '../utils/uuid'; function jwtSecretUnsafeReason(env: Env): 'missing' | 'default' | 'too_short' | null { const secret = (env.JWT_SECRET || '').trim(); if (!secret) return 'missing'; if (secret === DEFAULT_DEV_SECRET) return 'default'; if (secret.length < 32) return 'too_short'; return null; } // POST /api/accounts/register (only used from setup page, not client) export async function handleRegister(request: Request, env: Env): Promise { const storage = new StorageService(env.VAULT); // Enforce safe JWT_SECRET before allowing first registration. const unsafe = jwtSecretUnsafeReason(env); if (unsafe) { const message = unsafe === 'missing' ? 'JWT_SECRET is not set' : unsafe === 'default' ? 'JWT_SECRET is using the default/sample value. Please change it.' : 'JWT_SECRET must be at least 32 characters'; return errorResponse(message, 400); } // Check if already registered const isRegistered = await storage.isRegistered(); if (isRegistered) { return errorResponse('Registration is closed', 403); } let body: { email?: string; name?: string; masterPasswordHash?: string; masterPasswordHint?: string; key?: string; kdf?: number; kdfIterations?: number; kdfMemory?: number; kdfParallelism?: number; keys?: { publicKey?: string; encryptedPrivateKey?: string; }; }; try { body = await request.json(); } catch { return errorResponse('Invalid JSON', 400); } const email = body.email?.toLowerCase(); const name = body.name || email; const masterPasswordHash = body.masterPasswordHash; const key = body.key; const privateKey = body.keys?.encryptedPrivateKey; const publicKey = body.keys?.publicKey; if (!email || !masterPasswordHash || !key) { return errorResponse('Email, masterPasswordHash, and key are required', 400); } if (!privateKey || !publicKey) { return errorResponse('Private key and public key are required', 400); } // Create user const user: User = { id: generateUUID(), email: email, name: name || email, masterPasswordHash: masterPasswordHash, key: key, privateKey: privateKey, publicKey: publicKey, kdfType: body.kdf ?? 0, kdfIterations: body.kdfIterations ?? 600000, kdfMemory: body.kdfMemory, kdfParallelism: body.kdfParallelism, securityStamp: generateUUID(), createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }; await storage.saveUser(user); await storage.setRegistered(); return jsonResponse({ success: true }, 200); } // GET /api/accounts/profile export async function handleGetProfile(request: Request, env: Env, userId: string): Promise { const storage = new StorageService(env.VAULT); const user = await storage.getUserById(userId); if (!user) { return errorResponse('User not found', 404); } const profile: ProfileResponse = { id: user.id, name: user.name, email: user.email, emailVerified: true, premium: true, premiumFromOrganization: false, usesKeyConnector: false, masterPasswordHint: null, culture: 'en-US', twoFactorEnabled: false, key: user.key, privateKey: user.privateKey, accountKeys: null, securityStamp: user.securityStamp || user.id, organizations: [], providers: [], providerOrganizations: [], forcePasswordReset: false, avatarColor: null, creationDate: user.createdAt, object: 'profile', }; return jsonResponse(profile); } // PUT /api/accounts/profile export async function handleUpdateProfile(request: Request, env: Env, userId: string): Promise { const storage = new StorageService(env.VAULT); const user = await storage.getUserById(userId); if (!user) { return errorResponse('User not found', 404); } let body: { name?: string; masterPasswordHint?: string }; try { body = await request.json(); } catch { return errorResponse('Invalid JSON', 400); } if (body.name) { user.name = body.name; } user.updatedAt = new Date().toISOString(); await storage.saveUser(user); return handleGetProfile(request, env, userId); } // POST /api/accounts/keys export async function handleSetKeys(request: Request, env: Env, userId: string): Promise { const storage = new StorageService(env.VAULT); const user = await storage.getUserById(userId); if (!user) { return errorResponse('User not found', 404); } let body: { key?: string; encryptedPrivateKey?: string; publicKey?: string; }; try { body = await request.json(); } catch { return errorResponse('Invalid JSON', 400); } if (body.key) user.key = body.key; if (body.encryptedPrivateKey) user.privateKey = body.encryptedPrivateKey; if (body.publicKey) user.publicKey = body.publicKey; user.updatedAt = new Date().toISOString(); await storage.saveUser(user); return handleGetProfile(request, env, userId); } // GET /api/accounts/revision-date export async function handleGetRevisionDate(request: Request, env: Env, userId: string): Promise { const storage = new StorageService(env.VAULT); const revisionDate = await storage.getRevisionDate(userId); // Return as milliseconds timestamp (Bitwarden format) const timestamp = new Date(revisionDate).getTime(); return jsonResponse(timestamp); } // POST /api/accounts/verify-password export async function handleVerifyPassword(request: Request, env: Env, userId: string): Promise { const storage = new StorageService(env.VAULT); const user = await storage.getUserById(userId); if (!user) { return errorResponse('User not found', 404); } let body: { masterPasswordHash?: string }; try { body = await request.json(); } catch { return errorResponse('Invalid JSON', 400); } if (!body.masterPasswordHash) { return errorResponse('masterPasswordHash is required', 400); } if (body.masterPasswordHash !== user.masterPasswordHash) { return errorResponse('Invalid password', 400); } return new Response(null, { status: 200 }); }