diff --git a/backend/.env.example b/backend/.env.example index 9e03ac1..2130a9c 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -17,6 +17,10 @@ USE_REDIS_CACHE=true PORT=3001 APP_NAME=STATION BACKEND +# CORS Configuration +# Production: https://station.drdnt.org +ALLOWED_ORIGIN=http://localhost:5173 + # UEX Sync Configuration UEX_SYNC_ENABLED=true UEX_CATEGORIES_SYNC_ENABLED=true diff --git a/backend/package.json b/backend/package.json index 76649e9..3ece5ea 100644 --- a/backend/package.json +++ b/backend/package.json @@ -50,8 +50,11 @@ "cache-manager-redis-yet": "^5.1.5", "class-transformer": "^0.5.1", "class-validator": "^0.14.2", + "cookie-parser": "^1.4.7", "dotenv": "^16.4.5", "figlet": "^1.8.0", + "helmet": "^8.0.0", + "joi": "^17.13.3", "passport": "^0.7.0", "passport-http-bearer": "^1.0.1", "passport-jwt": "^4.0.1", @@ -68,6 +71,7 @@ "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.3.9", "@types/express": "^4.17.21", + "@types/cookie-parser": "^1.4.8", "@types/figlet": "^1.7.0", "@types/jest": "^29.5.12", "@types/node": "^20.12.12", diff --git a/backend/src/main.ts b/backend/src/main.ts index 04c2f9d..b4e5012 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -2,9 +2,11 @@ import 'reflect-metadata'; import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { Logger, ValidationPipe } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; import * as figlet from 'figlet'; import * as dotenv from 'dotenv'; +import helmet from 'helmet'; import { HttpExceptionFilter } from './common/filters/http-exception.filter'; dotenv.config(); @@ -13,14 +15,22 @@ async function bootstrap() { const app = await NestFactory.create(AppModule); // Application configuration - const port = process.env.PORT || 3001; - const appName = process.env.APP_NAME || 'STATION BACKEND'; + const configService = app.get(ConfigService); + const port = configService.get('PORT') || 3001; + const appName = configService.get('APP_NAME') || 'STATION BACKEND'; // ASCII Art for Application Name console.log(figlet.textSync(appName, { horizontalLayout: 'full' })); - // Enable CORS (if needed for APIs) - app.enableCors(); + // Security headers — must be registered before other middleware + app.use(helmet()); + + // CORS — restrict to known origin + app.enableCors({ + origin: configService.get('ALLOWED_ORIGIN'), + credentials: true, + methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], + }); // Global Validation Pipe app.useGlobalPipes( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f6c590f..6077601 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -77,12 +77,21 @@ importers: class-validator: specifier: ^0.14.2 version: 0.14.2 + cookie-parser: + specifier: ^1.4.7 + version: 1.4.7 dotenv: specifier: ^16.4.5 version: 16.6.1 figlet: specifier: ^1.8.0 version: 1.9.4 + helmet: + specifier: ^8.0.0 + version: 8.1.0 + joi: + specifier: ^17.13.3 + version: 17.13.3 passport: specifier: ^0.7.0 version: 0.7.0 @@ -123,6 +132,9 @@ importers: '@nestjs/testing': specifier: ^10.3.9 version: 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20)(@nestjs/platform-express@10.4.20) + '@types/cookie-parser': + specifier: ^1.4.8 + version: 1.4.10(@types/express@4.17.25) '@types/express': specifier: ^4.17.21 version: 4.17.25 @@ -713,6 +725,12 @@ packages: resolution: {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@hapi/hoek@9.3.0': + resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} + + '@hapi/topo@5.1.0': + resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} + '@humanwhocodes/config-array@0.13.0': resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} engines: {node: '>=10.10.0'} @@ -1285,6 +1303,15 @@ packages: cpu: [x64] os: [win32] + '@sideway/address@4.1.5': + resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} + + '@sideway/formula@3.0.1': + resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} + + '@sideway/pinpoint@2.0.0': + resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} + '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} @@ -1367,6 +1394,11 @@ packages: '@types/content-disposition@0.5.9': resolution: {integrity: sha512-8uYXI3Gw35MhiVYhG3s295oihrxRyytcRHjSjqnqZVDDy/xcGBRny7+Xj1Wgfhv5QzRtN2hB2dVRBUX9XW3UcQ==} + '@types/cookie-parser@1.4.10': + resolution: {integrity: sha512-B4xqkqfZ8Wek+rCOeRxsjMS9OgvzebEzzLYw7NHYuvzb7IdxOkI0ZHGgeEBX4PUM7QGVvNSK60T3OvWj3YfBRg==} + peerDependencies: + '@types/express': '*' + '@types/cookiejar@2.1.5': resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==} @@ -2061,6 +2093,10 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-parser@1.4.7: + resolution: {integrity: sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==} + engines: {node: '>= 0.8.0'} + cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} @@ -2068,6 +2104,10 @@ packages: resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} engines: {node: '>= 0.6'} + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + cookiejar@2.1.4: resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} @@ -2673,6 +2713,10 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + helmet@8.1.0: + resolution: {integrity: sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==} + engines: {node: '>=18.0.0'} + hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} @@ -3031,6 +3075,9 @@ packages: node-notifier: optional: true + joi@17.13.3: + resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -4969,6 +5016,12 @@ snapshots: '@eslint/js@9.39.1': {} + '@hapi/hoek@9.3.0': {} + + '@hapi/topo@5.1.0': + dependencies: + '@hapi/hoek': 9.3.0 + '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 @@ -5604,6 +5657,14 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.53.2': optional: true + '@sideway/address@4.1.5': + dependencies: + '@hapi/hoek': 9.3.0 + + '@sideway/formula@3.0.1': {} + + '@sideway/pinpoint@2.0.0': {} + '@sinclair/typebox@0.27.8': {} '@sinonjs/commons@3.0.1': @@ -5710,6 +5771,10 @@ snapshots: '@types/content-disposition@0.5.9': {} + '@types/cookie-parser@1.4.10(@types/express@4.17.25)': + dependencies: + '@types/express': 4.17.25 + '@types/cookiejar@2.1.5': {} '@types/cookies@0.9.2': @@ -6541,10 +6606,17 @@ snapshots: convert-source-map@2.0.0: {} + cookie-parser@1.4.7: + dependencies: + cookie: 0.7.2 + cookie-signature: 1.0.6 + cookie-signature@1.0.6: {} cookie@0.7.1: {} + cookie@0.7.2: {} + cookiejar@2.1.4: {} core-util-is@1.0.3: {} @@ -7242,6 +7314,8 @@ snapshots: dependencies: function-bind: 1.1.2 + helmet@8.1.0: {} + hoist-non-react-statics@3.3.2: dependencies: react-is: 16.13.1 @@ -7806,6 +7880,14 @@ snapshots: - supports-color - ts-node + joi@17.13.3: + dependencies: + '@hapi/hoek': 9.3.0 + '@hapi/topo': 5.1.0 + '@sideway/address': 4.1.5 + '@sideway/formula': 3.0.1 + '@sideway/pinpoint': 2.0.0 + js-tokens@4.0.0: {} js-yaml@3.14.2: