Search code examples
nestjspassport.jsthrottlingnestjs-passportnestjs-fastify

NestJS 9.4.0 crashes while creating Fastify app


I installed Throttler for NestJS 9.4.0 application to secure it but it gives me an error when i use NestFastify anyone know how it should look like correctly? A needs to use Fastify for subsequent security such as CSRF. I've been struggling with solving this problem for a long time, I even decided to ask on ChatGPT and it suggests that everything is fine. I will add that the error only appears after entering the appropriate route.

Works Main Code:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ConfigService } from '@nestjs/config';
import {
  FastifyAdapter,
  NestFastifyApplication,
} from '@nestjs/platform-fastify';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const configService = app.get(ConfigService);
  const port = configService.get<string>('PORT', '');

  // Throttler - Protection
  app.enableCors({
    origin: '*',
    methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
    allowedHeaders: 'Content-Type,Authorization',
    credentials: true,
  });

  await app.listen(port);
}
bootstrap();

Main Code not working:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ConfigService } from '@nestjs/config';
import {
  FastifyAdapter,
  NestFastifyApplication,
} from '@nestjs/platform-fastify';

async function bootstrap() {
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter(),
  );
  const configService = app.get(ConfigService);
  const port = configService.get<string>('PORT', '');

  // Throttler - Protection
  app.enableCors({
    origin: '*',
    methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
    allowedHeaders: 'Content-Type,Authorization',
    credentials: true,
  });

  await app.listen(port);
}
bootstrap();

App Module Code:

@Module({
  imports: [
    ConfigModule.forRoot({
      ignoreEnvFile: !!process.env.CI,
      envFilePath: join(__dirname, '..', '.env'),
      validationSchema: envSchema,
    }),
    TypeOrmModule.forRootAsync({
      useClass: TypeOrmConfigService,
    }),
    // Throttler - Protection
    ThrottlerModule.forRoot({
      ttl: Number(process.env.THROTTLER_TTL),
      limit: Number(process.env.THROTTLER_LIMIT),
      storage: {
        store: 'redis',
        options: {
          url: `redis://${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`,
        },
      },
    }),
    PassportModule.registerAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: async (configService: ConfigService) => ({
        defaultStrategy: configService.get('jwt.defaultStrategy'),
      }),
    }),
    AuthModule,
    UsersModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Error:

[Nest] 26552  - 01.05.2023, 22:48:28   ERROR [ExceptionsHandler] res.setHeader is not a function
TypeError: res.setHeader is not a function
    at FacebookStrategy.strategy.redirect (H:\xampp\htdocs\facebook-app\server\node_modules\passport\lib\middleware\authenticate.js:331:13)
    at stored (H:\xampp\htdocs\facebook-app\server\node_modules\passport-oauth2\lib\strategy.js:285:14)
    at NullStore.store (H:\xampp\htdocs\facebook-app\server\node_modules\passport-oauth2\lib\state\null.js:5:3)
    at FacebookStrategy.OAuth2Strategy.authenticate (H:\xampp\htdocs\facebook-app\server\node_modules\passport-oauth2\lib\strategy.js:297:28)
    at FacebookStrategy.Strategy.authenticate (H:\xampp\htdocs\facebook-app\server\node_modules\passport-facebook\lib\strategy.js:84:41)
    at attempt (H:\xampp\htdocs\facebook-app\server\node_modules\passport\lib\middleware\authenticate.js:369:16)
    at authenticate (H:\xampp\htdocs\facebook-app\server\node_modules\passport\lib\middleware\authenticate.js:370:7)
    at H:\xampp\htdocs\facebook-app\server\node_modules\@nestjs\passport\dist\auth.guard.js:97:3
    at new Promise (<anonymous>)
    at H:\xampp\htdocs\facebook-app\server\node_modules\@nestjs\passport\dist\auth.guard.js:89:83

Solution

  • Passport doesn't always work well with Fastify, especially with the OAuth specific strategies. To get around this, you can decorator the FastifyReply and FastifyRequest objects with methods and properties to make passport work. The following snippet can be used to modify and decorator the objects accordingly:

    const fastifyInstance: FastifyInstance = app.getHttpAdapter().getInstance()
      fastifyInstance
        .addHook('onRequest', async (req, res) => {
          req.socket['encrypted'] = process.env.NODE_ENV === 'production'
        })
        .decorateReply('setHeader', function (name: string, value: unknown) {
          this.header(name, value)
        })
        .decorateReply('end', function () {
          this.send('')
        })
    

    Add this to your main.ts before you call app.listen() and it should work