Fix MEDIUM security issues, add logging and request timeout

- Password minimum increased from 8 to 12 characters
- CORS configurable via CORS_ORIGINS env var (comma-separated whitelist)
- Pagination empty string q param handled via preprocess
- Request timeout set to 30 seconds
- Log file output via LOG_FILE env var (stdout + file in production)
- Pino-pretty in development, JSON to stdout + file in production
This commit is contained in:
Ryan Moon
2026-03-28 16:14:05 -05:00
parent 693121ec14
commit e44d461de1
4 changed files with 27 additions and 6 deletions

View File

@@ -19,9 +19,21 @@ export async function buildApp() {
const app = Fastify({
logger: {
level: process.env.LOG_LEVEL ?? 'info',
...(process.env.NODE_ENV === 'development' ? { transport: { target: 'pino-pretty' } } : {}),
...(process.env.NODE_ENV === 'development'
? { transport: { target: 'pino-pretty' } }
: process.env.LOG_FILE
? {
transport: {
targets: [
{ target: 'pino/file', options: { destination: 1 } }, // stdout
{ target: 'pino/file', options: { destination: process.env.LOG_FILE, mkdir: true } },
],
},
}
: {}),
},
genReqId: () => crypto.randomUUID(),
requestTimeout: 30000, // 30 seconds
})
// Plugins

View File

@@ -2,7 +2,16 @@ import fp from 'fastify-plugin'
import cors from '@fastify/cors'
export const corsPlugin = fp(async (app) => {
await app.register(cors, {
origin: process.env.NODE_ENV === 'development' ? true : false,
})
const corsOrigins = process.env.CORS_ORIGINS
let origin: boolean | string[]
if (process.env.NODE_ENV === 'development') {
origin = true // allow all in dev
} else if (corsOrigins) {
origin = corsOrigins.split(',').map((o) => o.trim())
} else {
origin = false
}
await app.register(cors, { origin })
})

View File

@@ -5,7 +5,7 @@ export type UserRole = z.infer<typeof UserRole>
export const RegisterSchema = z.object({
email: z.string().email(),
password: z.string().min(8).max(128),
password: z.string().min(12).max(128),
firstName: z.string().min(1).max(100),
lastName: z.string().min(1).max(100),
role: UserRole.default('staff'),

View File

@@ -5,7 +5,7 @@ export const PaginationSchema = z.object({
limit: z.coerce.number().int().min(1).max(100).default(25),
sort: z.string().max(50).optional(),
order: z.enum(['asc', 'desc']).default('asc'),
q: z.string().max(255).optional(),
q: z.preprocess((v) => (v === '' ? undefined : v), z.string().max(255).optional()),
})
export type PaginationInput = z.infer<typeof PaginationSchema>