mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 13:00:39 +00:00
226 lines
6.3 KiB
TypeScript
226 lines
6.3 KiB
TypeScript
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<Response> {
|
|
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<Response> {
|
|
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<Response> {
|
|
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<Response> {
|
|
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<Response> {
|
|
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<Response> {
|
|
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 });
|
|
}
|