Skip to content

Commit b40f8c9

Browse files
Merge pull request #19 from C-Alexis4414/YL-36-BackSecuring-CSRFToken
feat: YL-36-BackSecuring-CSRFToken
2 parents b910258 + 7884dfb commit b40f8c9

15 files changed

+315
-8
lines changed

package-lock.json

Lines changed: 163 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@
3838
"bcrypt": "^5.1.1",
3939
"class-transformer": "^0.5.1",
4040
"class-validator": "^0.14.1",
41+
"cookie-parser": "^1.4.6",
42+
"csurf": "^1.10.0",
43+
"express-rate-limit": "^7.4.0",
4144
"passport": "^0.7.0",
4245
"passport-jwt": "^4.0.1",
4346
"reflect-metadata": "^0.2.0",

src/app.module.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,21 @@ import { LikedModule } from './liked/liked.module';
55
import { AuthModule } from './authentification/auth.module';
66
import { TagsModule } from './tags/tag.module';
77
import { SubscriptionModule } from './subscription/subcription.module';
8+
import { SecurityModule } from './security/security.module';
89
// import { ConfigModule } from '@nestjs/config';
910

1011
@Module({
1112
imports: [
13+
// ConfigModule.forRoot({
14+
// isGlobal: true,
15+
// }),
1216
UserModule,
1317
CategoryModule,
1418
LikedModule,
1519
TagsModule,
1620
SubscriptionModule,
1721
AuthModule,
22+
SecurityModule,
1823
],
1924
controllers: [],
2025
providers: [],

src/authentification/auth.module.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//TOOLS
22
import { Module } from '@nestjs/common';
3+
// import { ConfigModule, ConfigService } from '@nestjs/config';
34

45
//CONTROLLERS
56
import { AuthController } from './controller/auth.controller';
@@ -19,10 +20,13 @@ import { JwtStrategy } from './jwt.strategy';
1920
controllers: [AuthController],
2021
providers: [AuthService, PrismaService, JwtStrategy, UserService],
2122
imports: [
22-
JwtModule.register({
23+
// ConfigModule,
24+
JwtModule.register({ // JwtModule.registerAsync
25+
// useFactory: async (configService: ConfigService) => ({
2326
global: true,
24-
secret: process.env.JWT_SECRET,
27+
secret: process.env.JWT_SECRET, // secret: configService.get<string>('JWT_SECRET'),
2528
signOptions: { expiresIn: '24h' },
29+
// inject: [ConfigService],
2630
}),],
2731
})
2832
export class AuthModule { }

src/authentification/controller/auth.controller.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// TOOLS
22
import { Body, Controller, Post, Get, Request, UseGuards } from '@nestjs/common';
3-
import { ApiTags } from '@nestjs/swagger';
3+
import { ApiTags, ApiHeader } from '@nestjs/swagger';
44

55
// GUARDS
66
import { JwtAuthGuard } from '../jwt-auth.guard';
@@ -31,6 +31,11 @@ export class AuthController {
3131
return await this.authService.login(authLogin);
3232
}
3333

34+
@ApiHeader({
35+
name: 'X-CSRF-Token',
36+
description: 'CSRF token',
37+
})
38+
3439
@Post('register')
3540
async register(@Body() userData: CreateUserDto) {
3641
return await this.authService.register(userData);

src/authentification/jwt.strategy.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ import { PassportStrategy } from '@nestjs/passport';
33
import { Injectable } from '@nestjs/common';
44
import { AuthPayloadDto } from './dto/auth.dto';
55
import { UserPayloadType } from './type/auth.type';
6+
// import { ConfigService } from '@nestjs/config';
67

78
@Injectable()
89
export class JwtStrategy extends PassportStrategy(Strategy) {
9-
constructor() {
10+
constructor() { // constructor(private configService: ConfigService)
1011
super({
1112
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
1213
ignoreExpiration: false,
13-
secretOrKey: process.env.JWT_SECRET,
14+
secretOrKey: process.env.JWT_SECRET, // secretOrKey: configService.get<string>('JWT_SECRET'),
1415
});
1516
}
1617

src/csrf-exception.filter.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { ExceptionFilter, Catch, ArgumentsHost, ForbiddenException } from '@nestjs/common';
2+
3+
@Catch(ForbiddenException)
4+
export class CsrfExceptionFilter implements ExceptionFilter {
5+
catch(exception: ForbiddenException, host: ArgumentsHost) {
6+
const ctx = host.switchToHttp();
7+
const response = ctx.getResponse();
8+
// Return a custom message for csrf errors
9+
response.status(403).json({
10+
message: 'ERROR 403 - CSRF token is invalid, please pass it in the header of the request'
11+
});
12+
}
13+
}

src/csrf.interceptor.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
2+
import { Observable } from 'rxjs';
3+
import { tap } from 'rxjs/operators';
4+
5+
@Injectable()
6+
export class CsrfInterceptor implements NestInterceptor {
7+
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
8+
const ctx = context.switchToHttp();
9+
const request = ctx.getRequest();
10+
11+
// Added a CSRF header for each query
12+
request.headers['X-CSRF-TOKEN'] = process.env.CSRF_TOKEN;
13+
14+
return next.handle().pipe(tap(() => {}));
15+
}
16+
}
17+

src/main.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { NestFactory } from '@nestjs/core';
22
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
33
import { AppModule } from './app.module';
4+
import { CsrfExceptionFilter } from './csrf-exception.filter';
45

56
async function bootstrap() {
67
const app = await NestFactory.create(AppModule);
@@ -10,6 +11,26 @@ async function bootstrap() {
1011
.setDescription('testing API address')
1112
.setVersion('1.0')
1213
.addTag('links')
14+
.addBearerAuth(
15+
{
16+
type: 'http',
17+
scheme: 'bearer',
18+
bearerFormat: 'JWT',
19+
},
20+
'authorization'
21+
)
22+
.addCookieAuth('csrf-token', {
23+
type: 'apiKey',
24+
in: 'cookie',
25+
name: 'X-CSRF-TOKEN',
26+
description: 'CSRF token for protection',
27+
})
28+
.addApiKey({
29+
type: 'apiKey',
30+
name: 'X-CSRF-TOKEN',
31+
in: 'header',
32+
description: 'CSRF protection token',
33+
}, 'csrf-token')
1334
.build();
1435
const document = SwaggerModule.createDocument(app, config);
1536
SwaggerModule.setup('api', app, document);
@@ -19,7 +40,15 @@ async function bootstrap() {
1940
// scheme: 'basic',
2041
// });
2142

22-
app.enableCors();
43+
app.enableCors({
44+
origin: 'http://localhost:3000',
45+
credentials: true,
46+
});
47+
48+
// Save global filter for csrf errors
49+
app.useGlobalFilters(new CsrfExceptionFilter());
50+
2351
await app.listen(3000);
52+
2453
}
2554
bootstrap()

0 commit comments

Comments
 (0)