mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 21:00:41 +00:00
feat: enhance user registration and authentication flow, improve attachment handling, and strengthen security measures
This commit is contained in:
@@ -27,12 +27,6 @@ export async function handleRegister(request: Request, env: Env): Promise<Respon
|
||||
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;
|
||||
@@ -88,7 +82,11 @@ export async function handleRegister(request: Request, env: Env): Promise<Respon
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
await storage.saveUser(user);
|
||||
const created = await storage.createFirstUser(user);
|
||||
if (!created) {
|
||||
return errorResponse('Registration is closed', 403);
|
||||
}
|
||||
|
||||
await storage.setRegistered();
|
||||
|
||||
return jsonResponse({ success: true }, 200);
|
||||
@@ -200,6 +198,7 @@ export async function handleGetRevisionDate(request: Request, env: Env, userId:
|
||||
// POST /api/accounts/verify-password
|
||||
export async function handleVerifyPassword(request: Request, env: Env, userId: string): Promise<Response> {
|
||||
const storage = new StorageService(env.DB);
|
||||
const auth = new AuthService(env);
|
||||
const user = await storage.getUserById(userId);
|
||||
|
||||
if (!user) {
|
||||
@@ -217,7 +216,8 @@ export async function handleVerifyPassword(request: Request, env: Env, userId: s
|
||||
return errorResponse('masterPasswordHash is required', 400);
|
||||
}
|
||||
|
||||
if (body.masterPasswordHash !== user.masterPasswordHash) {
|
||||
const valid = await auth.verifyPassword(body.masterPasswordHash, user.masterPasswordHash);
|
||||
if (!valid) {
|
||||
return errorResponse('Invalid password', 400);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Env, Attachment } from '../types';
|
||||
import { Env, Attachment, DEFAULT_DEV_SECRET } from '../types';
|
||||
import { StorageService } from '../services/storage';
|
||||
import { jsonResponse, errorResponse } from '../utils/response';
|
||||
import { generateUUID } from '../utils/uuid';
|
||||
@@ -210,6 +210,11 @@ export async function handlePublicDownloadAttachment(
|
||||
cipherId: string,
|
||||
attachmentId: string
|
||||
): Promise<Response> {
|
||||
const secret = (env.JWT_SECRET || '').trim();
|
||||
if (!secret || secret.length < 32 || secret === DEFAULT_DEV_SECRET) {
|
||||
return errorResponse('Server configuration error', 500);
|
||||
}
|
||||
|
||||
const url = new URL(request.url);
|
||||
const token = url.searchParams.get('token');
|
||||
|
||||
|
||||
@@ -68,10 +68,12 @@ export async function handleGetCiphers(request: Request, env: Env, userId: strin
|
||||
? ciphers
|
||||
: ciphers.filter(c => !c.deletedAt);
|
||||
|
||||
const attachmentsByCipher = await storage.getAttachmentsByCipherIds(filteredCiphers.map(c => c.id));
|
||||
|
||||
// Get attachments for all ciphers
|
||||
const cipherResponses = [];
|
||||
for (const cipher of filteredCiphers) {
|
||||
const attachments = await storage.getAttachmentsByCipher(cipher.id);
|
||||
const attachments = attachmentsByCipher.get(cipher.id) || [];
|
||||
cipherResponses.push(cipherToResponse(cipher, attachments));
|
||||
}
|
||||
|
||||
|
||||
@@ -32,12 +32,7 @@ export async function handleToken(request: Request, env: Env): Promise<Response>
|
||||
return identityErrorResponse('Email and password are required', 'invalid_request', 400);
|
||||
}
|
||||
|
||||
const user = await storage.getUser(email);
|
||||
if (!user) {
|
||||
return identityErrorResponse('Username or password is incorrect. Try again', 'invalid_grant', 400);
|
||||
}
|
||||
|
||||
// Check if login is rate limited (only after confirming user exists)
|
||||
// Check login lockout before user lookup to reduce user-enumeration signal
|
||||
const loginCheck = await rateLimit.checkLoginAttempt(email);
|
||||
if (!loginCheck.allowed) {
|
||||
return identityErrorResponse(
|
||||
@@ -47,6 +42,12 @@ export async function handleToken(request: Request, env: Env): Promise<Response>
|
||||
);
|
||||
}
|
||||
|
||||
const user = await storage.getUser(email);
|
||||
if (!user) {
|
||||
await rateLimit.recordFailedLogin(email);
|
||||
return identityErrorResponse('Username or password is incorrect. Try again', 'invalid_grant', 400);
|
||||
}
|
||||
|
||||
const valid = await auth.verifyPassword(passwordHash, user.masterPasswordHash);
|
||||
if (!valid) {
|
||||
// Record failed login attempt
|
||||
|
||||
@@ -14,6 +14,7 @@ export async function handleSync(request: Request, env: Env, userId: string): Pr
|
||||
|
||||
const ciphers = await storage.getAllCiphers(userId);
|
||||
const folders = await storage.getAllFolders(userId);
|
||||
const attachmentsByCipher = await storage.getAttachmentsByCipherIds(ciphers.map(c => c.id));
|
||||
|
||||
// Build profile response
|
||||
const profile: ProfileResponse = {
|
||||
@@ -43,7 +44,7 @@ export async function handleSync(request: Request, env: Env, userId: string): Pr
|
||||
// Build cipher responses with attachments
|
||||
const cipherResponses: CipherResponse[] = [];
|
||||
for (const cipher of ciphers) {
|
||||
const attachments = await storage.getAttachmentsByCipher(cipher.id);
|
||||
const attachments = attachmentsByCipher.get(cipher.id) || [];
|
||||
cipherResponses.push(cipherToResponse(cipher, attachments));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user