feat: add some security

This commit is contained in:
2026-07-11 17:34:18 +03:30
parent f80cf95f65
commit f399ec4a04
10 changed files with 69 additions and 9 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"local": {
"baseUrl": "http://localhost:3000"
"baseUrl": "http://localhost:3000/v1"
},
"dev": {
"baseUrl": "http://localhost:3000"
+7
View File
@@ -2,4 +2,11 @@ import { registerAs } from '@nestjs/config';
export default registerAs('app', () => ({
environment: process.env.NODE_ENV || 'production',
corsOrigins: process.env.CORS_ORIGINS?.split(',').map((origin) =>
origin.trim(),
),
corsSubdomain: process.env.CORS_SUBDOMAIN,
swaggerServers: process.env.SWAGGER_SERVERS?.split(',').map((origin) =>
origin.trim(),
),
}));
+3
View File
@@ -18,4 +18,7 @@ export default Joi.object({
JWT_AUDIENCE: Joi.string().required(),
REDIS_HOST: Joi.string().required(),
REDIS_PORT: Joi.number().port().default(6379),
CORS_ORIGINS: Joi.string().required(),
CORS_SUBDOMAIN: Joi.string().required(),
SWAGGER_SERVERS: Joi.string().required(),
});
+44 -8
View File
@@ -4,10 +4,13 @@ import { I18nValidationExceptionFilter, I18nValidationPipe } from 'nestjs-i18n';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { ResponseInterceptor } from './common/interceptors/response.interceptor';
import helmet from 'helmet';
import { ConfigService } from '@nestjs/config';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('v1');
app.useGlobalPipes(
new I18nValidationPipe({
whitelist: true,
@@ -32,24 +35,57 @@ async function bootstrap() {
app.useGlobalInterceptors(new ResponseInterceptor());
const configService = app.get(ConfigService);
const origins = configService.get<string[]>('app.corsOrigins');
const subdomain = configService.get<string>('app.corsSubdomain');
console.log(subdomain, origins);
app.enableCors({
origin: ['http://localhost:5173'],
origin: (origin: string, callback: any) => {
if (!origin) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call
return callback(null, true);
}
if (origins?.includes(origin)) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call
return callback(null, true);
}
try {
const hostname = new URL(origin).hostname;
if (hostname === subdomain || hostname.endsWith(`.${subdomain}`)) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call
return callback(null, true);
}
} catch {
/* empty */
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
callback(new Error('Not allowed by CORS'));
},
credentials: true,
});
app.use(helmet());
const config = new DocumentBuilder()
.setTitle('Borna API')
.setDescription('Borna API Documentation')
.setTitle('Heala API')
.setDescription('Heala API Documentation')
.setVersion('1.0')
.setLicense('MIT', 'https://opensource.org/license/mit')
.addServer('http://localhost:3000')
.build();
.setLicense('MIT', 'https://opensource.org/license/mit');
const document = SwaggerModule.createDocument(app, config);
const swaggerServers = configService.get<string[]>('app.swaggerServers');
SwaggerModule.setup('api/v1', app, document);
swaggerServers?.forEach((server) => config.addServer(server));
const document = SwaggerModule.createDocument(app, config.build());
SwaggerModule.setup('v1/docs', app, document);
await app.listen(process.env.PORT ?? 3000);
}
@@ -2,8 +2,10 @@ import { Body, Controller, HttpCode, HttpStatus, Post } from '@nestjs/common';
import { AdminAuthService } from './providers/admin-auth.service';
import { AdminLoginDTO } from './dtos/admin-login.dto';
import { Public } from '@/modules/auth/decorators/public.decorator';
import { Throttle } from '@nestjs/throttler';
@Controller('admin/auth')
@Throttle({ login: {} })
export class AdminAuthController {
constructor(
/**
@@ -17,9 +17,11 @@ import {
Post,
Query,
} from '@nestjs/common';
import { Throttle } from '@nestjs/throttler';
@Controller('admin/choices')
@Roles(Role.SUPERUSER, Role.ADMIN)
@Throttle({ admin: {} })
export class AdminChoicesController {
constructor(
/**
@@ -21,9 +21,11 @@ import {
Post,
Query,
} from '@nestjs/common';
import { Throttle } from '@nestjs/throttler';
@Controller('admin/questions')
@Roles(Role.SUPERUSER, Role.ADMIN)
@Throttle({ admin: {} })
export class AdminQuestionsController {
constructor(
/**
@@ -15,9 +15,11 @@ import {
Post,
Query,
} from '@nestjs/common';
import { Throttle } from '@nestjs/throttler';
@Controller('admin/scoring')
@Roles(Role.SUPERUSER, Role.ADMIN)
@Throttle({ admin: {} })
export class AdminScoringController {
constructor(
/**
@@ -17,9 +17,11 @@ import {
Post,
Query,
} from '@nestjs/common';
import { Throttle } from '@nestjs/throttler';
@Controller('admin/tests')
@Roles(Role.SUPERUSER, Role.ADMIN)
@Throttle({ admin: {} })
export class AdminTestsController {
constructor(
/**
+4
View File
@@ -4,6 +4,7 @@ import { AuthService } from './providers/auth.service';
import { VerifyOtpDTO } from './dtos/verify-otp.dto';
import { Public } from './decorators/public.decorator';
import { RefreshTokenDTO } from './dtos/refresh-token.dto';
import { Throttle } from '@nestjs/throttler';
@Controller('auth')
export class AuthController {
@@ -17,6 +18,7 @@ export class AuthController {
@Public()
@Post('login')
@HttpCode(HttpStatus.OK)
@Throttle({ login: {} })
public async logIn(@Body() loginDto: LoginDTO) {
return await this.authService.logIn(loginDto);
}
@@ -24,6 +26,7 @@ export class AuthController {
@Public()
@Post('verify-otp')
@HttpCode(HttpStatus.OK)
@Throttle({ otp: {} })
public async verifyOTP(@Body() verifyOtpDto: VerifyOtpDTO) {
return await this.authService.verifyOTP(verifyOtpDto);
}
@@ -31,6 +34,7 @@ export class AuthController {
@Public()
@Post('refresh')
@HttpCode(HttpStatus.OK)
@Throttle({ login: {} })
public async refreshToken(@Body() refreshTokenDto: RefreshTokenDTO) {
return await this.authService.refreshToken(refreshTokenDto);
}