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:
@@ -19,9 +19,21 @@ export async function buildApp() {
|
|||||||
const app = Fastify({
|
const app = Fastify({
|
||||||
logger: {
|
logger: {
|
||||||
level: process.env.LOG_LEVEL ?? 'info',
|
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(),
|
genReqId: () => crypto.randomUUID(),
|
||||||
|
requestTimeout: 30000, // 30 seconds
|
||||||
})
|
})
|
||||||
|
|
||||||
// Plugins
|
// Plugins
|
||||||
|
|||||||
@@ -2,7 +2,16 @@ import fp from 'fastify-plugin'
|
|||||||
import cors from '@fastify/cors'
|
import cors from '@fastify/cors'
|
||||||
|
|
||||||
export const corsPlugin = fp(async (app) => {
|
export const corsPlugin = fp(async (app) => {
|
||||||
await app.register(cors, {
|
const corsOrigins = process.env.CORS_ORIGINS
|
||||||
origin: process.env.NODE_ENV === 'development' ? true : false,
|
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 })
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export type UserRole = z.infer<typeof UserRole>
|
|||||||
|
|
||||||
export const RegisterSchema = z.object({
|
export const RegisterSchema = z.object({
|
||||||
email: z.string().email(),
|
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),
|
firstName: z.string().min(1).max(100),
|
||||||
lastName: z.string().min(1).max(100),
|
lastName: z.string().min(1).max(100),
|
||||||
role: UserRole.default('staff'),
|
role: UserRole.default('staff'),
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export const PaginationSchema = z.object({
|
|||||||
limit: z.coerce.number().int().min(1).max(100).default(25),
|
limit: z.coerce.number().int().min(1).max(100).default(25),
|
||||||
sort: z.string().max(50).optional(),
|
sort: z.string().max(50).optional(),
|
||||||
order: z.enum(['asc', 'desc']).default('asc'),
|
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>
|
export type PaginationInput = z.infer<typeof PaginationSchema>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user