@@ -13,6 +13,19 @@ import { ApplicationModule } from './modules/application/application.module';
|
|||||||
imports: [
|
imports: [
|
||||||
ConfigModule.forRoot({
|
ConfigModule.forRoot({
|
||||||
isGlobal: true,
|
isGlobal: true,
|
||||||
|
// Load .env files depending on NODE_ENV. Default to development .env
|
||||||
|
envFilePath: process.env.NODE_ENV === 'production' ? '.env.production' : '.env',
|
||||||
|
// Basic validation: ensure expected frontend URLs are present
|
||||||
|
validate: (env: Record<string, any>) => {
|
||||||
|
const errors: string[] = [];
|
||||||
|
if (!env.FRONTEND_URL) errors.push('FRONTEND_URL is not set');
|
||||||
|
if (!env.PRODUCTION_FRONTEND_URL) {
|
||||||
|
// production frontend URL is recommended but not mandatory for local development
|
||||||
|
if (process.env.NODE_ENV === 'production') errors.push('PRODUCTION_FRONTEND_URL is not set');
|
||||||
|
}
|
||||||
|
if (errors.length > 0) throw new Error('Environment validation error: ' + errors.join('; '));
|
||||||
|
return env;
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
IdentityModule,
|
IdentityModule,
|
||||||
|
|||||||
+19
-1
@@ -20,7 +20,25 @@ async function bootstrap() {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
app.enableCors();
|
// CORS configuration: only allow configured frontend origins and enable credentials
|
||||||
|
const allowedOrigins: string[] = [];
|
||||||
|
const frontend = config.get<string>('FRONTEND_URL');
|
||||||
|
const prodFrontend = config.get<string>('PRODUCTION_FRONTEND_URL');
|
||||||
|
if (frontend) allowedOrigins.push(frontend);
|
||||||
|
if (prodFrontend) allowedOrigins.push(prodFrontend);
|
||||||
|
|
||||||
|
app.enableCors({
|
||||||
|
origin: allowedOrigins,
|
||||||
|
credentials: true,
|
||||||
|
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
||||||
|
allowedHeaders: [
|
||||||
|
'Content-Type',
|
||||||
|
'Authorization',
|
||||||
|
'Accept',
|
||||||
|
'Origin',
|
||||||
|
'X-Requested-With',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
const swaggerEnabled = config.get<string>('SWAGGER_ENABLED') === 'true';
|
const swaggerEnabled = config.get<string>('SWAGGER_ENABLED') === 'true';
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ import { Controller, Get, Post, Query, Res, Req, Body, HttpCode, HttpStatus } fr
|
|||||||
import { ApiTags, ApiOperation } from '@nestjs/swagger';
|
import { ApiTags, ApiOperation } from '@nestjs/swagger';
|
||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
import { Response, Request } from 'express';
|
import { Response, Request } from 'express';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
@ApiTags('Auth')
|
@ApiTags('Auth')
|
||||||
@Controller('auth')
|
@Controller('auth')
|
||||||
export class AuthController {
|
export class AuthController {
|
||||||
constructor(private readonly authService: AuthService) {}
|
constructor(private readonly authService: AuthService, private readonly config: ConfigService) {}
|
||||||
|
|
||||||
@Get('login')
|
@Get('login')
|
||||||
@ApiOperation({ summary: 'Start Authorization Code + PKCE login (redirect to Identity Provider)' })
|
@ApiOperation({ summary: 'Start Authorization Code + PKCE login (redirect to Identity Provider)' })
|
||||||
@@ -21,13 +22,17 @@ export class AuthController {
|
|||||||
async callback(@Query('code') code: string, @Query('state') state: string, @Res() res: Response) {
|
async callback(@Query('code') code: string, @Query('state') state: string, @Res() res: Response) {
|
||||||
const result = await this.authService.handleCallback(code, state);
|
const result = await this.authService.handleCallback(code, state);
|
||||||
|
|
||||||
// set cookies
|
// set cookies with environment-aware options
|
||||||
|
const isProd = this.config.get<string>('NODE_ENV') === 'production' || process.env.NODE_ENV === 'production';
|
||||||
|
const cookieDomain = this.config.get<string>('RAYLAB_COOKIE_DOMAIN') || (isProd ? '.raylab.site' : undefined);
|
||||||
|
|
||||||
const cookieOptions: any = {
|
const cookieOptions: any = {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
secure: process.env.NODE_ENV === 'production',
|
secure: isProd, // secure in production
|
||||||
sameSite: 'lax',
|
sameSite: isProd ? 'none' : 'lax', // cross-site in production
|
||||||
path: '/',
|
path: '/',
|
||||||
};
|
};
|
||||||
|
if (cookieDomain) cookieOptions.domain = cookieDomain;
|
||||||
|
|
||||||
// access token cookie (internal JWT)
|
// access token cookie (internal JWT)
|
||||||
res.cookie('raylab_jwt', result.accessToken, { ...cookieOptions, maxAge: result.expiresIn * 1000 });
|
res.cookie('raylab_jwt', result.accessToken, { ...cookieOptions, maxAge: result.expiresIn * 1000 });
|
||||||
|
|||||||
Reference in New Issue
Block a user