mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-18 00:46:45 +00:00
feat(syncServer): enhance user authentication with async bcrypt operations, add security headers, and implement health check endpoint
This commit is contained in:
parent
585020aa3e
commit
da1e4dcada
6 changed files with 32 additions and 6 deletions
|
|
@ -11,6 +11,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@fastify/cors": "^11.1.0",
|
||||
"@fastify/helmet": "^13.0.2",
|
||||
"@fastify/rate-limit": "^10.3.0",
|
||||
"@fastify/static": "^8.3.0",
|
||||
"bcryptjs": "^3.0.3",
|
||||
|
|
|
|||
|
|
@ -120,7 +120,8 @@ export const apiRoutes = async (fastify: FastifyInstance): Promise<void> => {
|
|||
}
|
||||
const { email, password } = parseResult.data;
|
||||
|
||||
const result = loginUser(email, password);
|
||||
const result = await loginUser(email, password);
|
||||
|
||||
return reply.send(result);
|
||||
} catch (err) {
|
||||
Logger.error(`Login error: ${errorMessage(err)}`);
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ export const registerUser = async (
|
|||
// Password strength validation is handled by Zod in api.ts
|
||||
|
||||
const db = getDb();
|
||||
const passwordHash = bcrypt.hashSync(password, 12);
|
||||
const passwordHash = await bcrypt.hash(password, 12);
|
||||
const verificationToken = randomBytes(32).toString('hex');
|
||||
const TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1000;
|
||||
const expiresAt = Date.now() + TWENTY_FOUR_HOURS_MS;
|
||||
|
|
@ -92,10 +92,10 @@ export const verifyEmail = (token: string): boolean => {
|
|||
return true;
|
||||
};
|
||||
|
||||
export const loginUser = (
|
||||
export const loginUser = async (
|
||||
email: string,
|
||||
password: string,
|
||||
): { token: string; user: { id: number; email: string } } => {
|
||||
): Promise<{ token: string; user: { id: number; email: string } }> => {
|
||||
const db = getDb();
|
||||
|
||||
const user = db.prepare('SELECT * FROM users WHERE email = ?').get(email) as
|
||||
|
|
@ -108,7 +108,7 @@ export const loginUser = (
|
|||
const dummyHash = '$2a$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW';
|
||||
const hashToCompare = user ? user.password_hash : dummyHash;
|
||||
|
||||
const isMatch = bcrypt.compareSync(password, hashToCompare);
|
||||
const isMatch = await bcrypt.compare(password, hashToCompare);
|
||||
|
||||
if (!user || !isMatch) {
|
||||
throw new Error('Invalid credentials');
|
||||
|
|
|
|||
|
|
@ -80,6 +80,11 @@ export const loadConfigFromEnv = (
|
|||
config.publicUrl = `http://localhost:${config.port}`;
|
||||
}
|
||||
|
||||
// Enforce HTTPS for PUBLIC_URL in production
|
||||
if (process.env.NODE_ENV === 'production' && !config.publicUrl.startsWith('https://')) {
|
||||
throw new Error('PUBLIC_URL must use HTTPS in production');
|
||||
}
|
||||
|
||||
// CORS configuration
|
||||
if (process.env.CORS_ENABLED !== undefined) {
|
||||
config.cors.enabled = process.env.CORS_ENABLED === 'true';
|
||||
|
|
|
|||
|
|
@ -38,6 +38,13 @@ export const initDb = (dataDir: string) => {
|
|||
)
|
||||
`);
|
||||
|
||||
// Create index for verification token lookups
|
||||
db.exec(`
|
||||
CREATE INDEX IF NOT EXISTS idx_users_verification_token
|
||||
ON users(verification_token)
|
||||
WHERE verification_token IS NOT NULL
|
||||
`);
|
||||
|
||||
// Migration: Check if verification_token_expires_at exists
|
||||
const columns = db.pragma('table_info(users)') as { name: string }[];
|
||||
const hasExpiresAt = columns.some(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import * as fs from 'fs';
|
|||
import Fastify, { FastifyInstance } from 'fastify';
|
||||
import cors from '@fastify/cors';
|
||||
import rateLimit from '@fastify/rate-limit';
|
||||
import helmet from '@fastify/helmet';
|
||||
import fastifyStatic from '@fastify/static';
|
||||
import * as path from 'path';
|
||||
import { loadConfigFromEnv, ServerConfig } from './config';
|
||||
|
|
@ -132,6 +133,11 @@ export const createServer = (
|
|||
logger: false, // We use our own logger
|
||||
});
|
||||
|
||||
// Security Headers
|
||||
await fastifyServer.register(helmet, {
|
||||
contentSecurityPolicy: false, // Disable CSP for now to avoid conflicts with WebDAV clients/browsers
|
||||
});
|
||||
|
||||
// Rate Limiting (prevent brute force)
|
||||
// 100 requests per 15 minutes globally is a safe default
|
||||
// We can tighten this for specific routes if needed
|
||||
|
|
@ -191,6 +197,11 @@ export const createServer = (
|
|||
prefix: '/',
|
||||
});
|
||||
|
||||
// Health Check
|
||||
fastifyServer.get('/health', async () => {
|
||||
return { status: 'ok' };
|
||||
});
|
||||
|
||||
// API Routes
|
||||
await fastifyServer.register(apiRoutes, { prefix: '/api' });
|
||||
|
||||
|
|
@ -210,7 +221,8 @@ export const createServer = (
|
|||
const urlPath = req.url.split('?')[0];
|
||||
if (
|
||||
(req.method === 'GET' && staticFiles.includes(urlPath)) ||
|
||||
urlPath === '/verify-email'
|
||||
urlPath === '/verify-email' ||
|
||||
urlPath === '/health'
|
||||
) {
|
||||
done();
|
||||
return;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue