diff --git a/admin/.env.example b/admin/.env.example index 05a03fd..535b6ea 100644 --- a/admin/.env.example +++ b/admin/.env.example @@ -1,5 +1,6 @@ PORT=8080 HOST=localhost +URL=http://localhost:8080 LOG_LEVEL=info APP_KEY=some_random_key NODE_ENV=development diff --git a/admin/app/validators/common.ts b/admin/app/validators/common.ts index 024c206..642b08a 100644 --- a/admin/app/validators/common.ts +++ b/admin/app/validators/common.ts @@ -68,7 +68,14 @@ export const remoteDownloadValidatorOptional = vine.compile( export const filenameParamValidator = vine.compile( vine.object({ params: vine.object({ - filename: vine.string().trim().minLength(1).maxLength(4096), + filename: vine + .string() + .trim() + .minLength(1) + .maxLength(255) + .regex(/^[^/\\]*$/) // Disallow path separators to prevent traversal + .regex(/^(?!\.\.)/) // Disallow leading double-dots + .regex(/^[^<>:"|?*\x00-\x1f]+$/), // Disallow shell/filesystem special chars }), }) ) diff --git a/admin/config/cors.ts b/admin/config/cors.ts index 4160453..aa96366 100644 --- a/admin/config/cors.ts +++ b/admin/config/cors.ts @@ -1,3 +1,4 @@ +import env from '#start/env' import { defineConfig } from '@adonisjs/cors' /** @@ -8,7 +9,7 @@ import { defineConfig } from '@adonisjs/cors' */ const corsConfig = defineConfig({ enabled: true, - origin: ['*'], + origin: [env.get('URL')], methods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE'], headers: true, exposeHeaders: [], diff --git a/admin/config/session.ts b/admin/config/session.ts index fc49762..46826b6 100644 --- a/admin/config/session.ts +++ b/admin/config/session.ts @@ -1,48 +1,48 @@ -// import env from '#start/env' -// import app from '@adonisjs/core/services/app' -// import { defineConfig, stores } from '@adonisjs/session' +import env from '#start/env' +import app from '@adonisjs/core/services/app' +import { defineConfig, stores } from '@adonisjs/session' -// const sessionConfig = defineConfig({ -// enabled: false, -// cookieName: 'adonis-session', +const sessionConfig = defineConfig({ + enabled: true, + cookieName: 'nomad-session', -// /** -// * When set to true, the session id cookie will be deleted -// * once the user closes the browser. -// */ -// clearWithBrowser: false, + /** + * When set to true, the session id cookie will be deleted + * once the user closes the browser. + */ + clearWithBrowser: false, -// /** -// * Define how long to keep the session data alive without -// * any activity. -// */ -// age: '2h', + /** + * Define how long to keep the session data alive without + * any activity. + */ + age: '2h', -// /** -// * Configuration for session cookie and the -// * cookie store -// */ -// cookie: { -// path: '/', -// httpOnly: true, -// secure: app.inProduction, -// sameSite: 'lax', -// }, + /** + * Configuration for session cookie and the + * cookie store + */ + cookie: { + path: '/', + httpOnly: true, + secure: app.inProduction, + sameSite: 'lax', + }, -// /** -// * The store to use. Make sure to validate the environment -// * variable in order to infer the store name without any -// * errors. -// */ -// store: env.get('SESSION_DRIVER'), + /** + * The store to use. Make sure to validate the environment + * variable in order to infer the store name without any + * errors. + */ + store: env.get('SESSION_DRIVER'), -// /** -// * List of configured stores. Refer documentation to see -// * list of available stores and their config. -// */ -// stores: { -// cookie: stores.cookie(), -// }, -// }) + /** + * List of configured stores. Refer documentation to see + * list of available stores and their config. + */ + stores: { + cookie: stores.cookie(), + }, +}) -// export default sessionConfig +export default sessionConfig diff --git a/admin/config/shield.ts b/admin/config/shield.ts index 5d981fd..1b48b49 100644 --- a/admin/config/shield.ts +++ b/admin/config/shield.ts @@ -1,3 +1,4 @@ +import app from '@adonisjs/core/services/app' import { defineConfig } from '@adonisjs/shield' const shieldConfig = defineConfig({ @@ -6,8 +7,19 @@ const shieldConfig = defineConfig({ * to learn more */ csp: { - enabled: false, - directives: {}, + enabled: true, + directives: { + defaultSrc: ["'self'"], + scriptSrc: ["'self'", "'unsafe-inline'"], // unsafe-inline required for Inertia.js page props + styleSrc: ["'self'", "'unsafe-inline'"], + imgSrc: ["'self'", 'data:', 'blob:'], + connectSrc: ["'self'"], + fontSrc: ["'self'"], + objectSrc: ["'none'"], + frameSrc: ["'none'"], + baseUri: ["'self'"], + formAction: ["'self'"], + }, reportOnly: false, }, @@ -16,8 +28,9 @@ const shieldConfig = defineConfig({ * to learn more */ csrf: { - enabled: false, // TODO: Enable CSRF protection - exceptRoutes: [], + enabled: true, + // Exempt health check and SSE/transmit endpoints from CSRF + exceptRoutes: ['/api/health', '/__transmit/events', '/__transmit/unsubscribe'], enableXsrfCookie: true, methods: ['POST', 'PUT', 'PATCH', 'DELETE'], }, @@ -35,7 +48,7 @@ const shieldConfig = defineConfig({ * Force browser to always use HTTPS */ hsts: { - enabled: false, // TODO: Enable HSTS in production + enabled: app.inProduction, maxAge: '180 days', }, diff --git a/admin/start/env.ts b/admin/start/env.ts index ddf9b5f..e750ca3 100644 --- a/admin/start/env.ts +++ b/admin/start/env.ts @@ -32,7 +32,7 @@ export default await Env.create(new URL('../', import.meta.url), { | Variables for configuring session package |---------------------------------------------------------- */ - //SESSION_DRIVER: Env.schema.enum(['cookie', 'memory'] as const), + SESSION_DRIVER: Env.schema.enum(['cookie', 'memory'] as const), /* |---------------------------------------------------------- diff --git a/admin/start/kernel.ts b/admin/start/kernel.ts index 56e1afa..b3cd86b 100644 --- a/admin/start/kernel.ts +++ b/admin/start/kernel.ts @@ -37,7 +37,7 @@ server.use([ */ router.use([ () => import('@adonisjs/core/bodyparser_middleware'), - // () => import('@adonisjs/session/session_middleware'), + () => import('@adonisjs/session/session_middleware'), () => import('@adonisjs/shield/shield_middleware'), ])