#Local strategy using passport

1 messages · Page 1 of 1 (latest)

onyx wren
#

Hi everyone
I'm facing an error 500 when try to use the the local strategy that I have using passport-local.
If I remove the things from the constructor the error doesn't happen, but when I add the auth.service to do the validation or even the prisma service to the validation on the guard the error appears again

violet night
#

seems like you are setting up passport wrong

#

hard to tell without the code base

onyx wren
#

ok let me show what I ahve

#
@Module({
  imports: [
    JwtModule.registerAsync({
      useFactory: async (config: ConfigService) => ({
        secret: config.get('auth.secret'),
        signOptions: {
          expiresIn: '2h',
          algorithm: 'HS256',
        },
      }),
      inject: [ConfigService],
    }),
    PassportModule,
    UsersModule,
  ],
  providers: [Bcrypt, AuthService, LocalStrategy, JwtAuthStrategy],
  controllers: [AuthController],
})
export class AuthModule {}
#
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { plainToInstance } from 'class-transformer';
import { Strategy } from 'passport-local';
import { STRATEGY_LOCAL } from '~/modules/auth/constants/strategy.constant';
import { Bcrypt } from '~common/utils/security/bcrypt';
import { PrismaService } from '~infra/database/prisma.service';
import { UserCredentialsValidation } from '~modules/users/dtos/user-credentials-validation.dto';

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy, STRATEGY_LOCAL) {
  constructor(
    private readonly prismaService: PrismaService,
    private readonly bcrypt: Bcrypt,
  ) {
    super({
      usernameField: 'username',
      passwordField: 'password',
      passReqToCallback: true,
    });
  }

  async validate(_request: Request, username: string, password: string) {
    const user = await this.prismaService.user.findUnique({
      where: { username },
    });

    if (!user) {
      return null;
    }

    const match = await this.bcrypt.comparePassword(password, user.password);

    if (!match) {
      return null;
    }

    // use class transformer to remove fields like
    return plainToInstance(UserCredentialsValidation, user, { excludeExtraneousValues: true });
  }
}
#

my strategy

faint crown
#

How are you saying this is the strategy to use?

onyx wren
#
import { Body, Controller, HttpCode, HttpStatus, Post, Req, Res, UseGuards } from '@nestjs/common';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { Request, Response } from 'express';
import { LocalAuthGuard } from '~modules/auth/guards/local-auth.guards';
import { AuthService } from './auth.service';
import { Public } from './decorators/public.decorator';
import { LoginUserDto } from './dto/login-user.dto';

@Public()
@Controller('auth')
@ApiTags('auth')
export class AuthController {
  constructor(private readonly authService: AuthService) {}

  @Post('login')
  @UseGuards(LocalAuthGuard)
  @HttpCode(204)
  @ApiOperation({ summary: 'Login User' })
  @ApiResponse({ status: 204, description: 'User logged with success!' })
  @ApiResponse({
    status: 401,
    description: 'Invalid credentials (username or password).',
  })
  async signIn(@Body() userDto: LoginUserDto, @Res({ passthrough: true }) response: Response) {
    await this.authService.signIn(userDto, response);
  }

  @Post('logout')
  @HttpCode(HttpStatus.NO_CONTENT)
  async signOut(@Req() request: Request, @Res({ passthrough: true }) response: Response) {
    this.authService.signOut(request, response);
  }
}
#

So I put the JWT Auth Guard globally and use the Public() to ignore it as I have seen on the documentation

faint crown
#

And the LocalAuthGuard?

onyx wren
#

Only extends the AuthGuard

import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { STRATEGY_LOCAL } from '../constants/strategy.constant';

@Injectable()
export class LocalAuthGuard extends AuthGuard(STRATEGY_LOCAL) {}

faint crown
#

Okay, so the only other things I could think that might be happening here is that either PrismaService or Bcrypt are set to be request scoped

onyx wren
#

ok already try remove bcrypt

#
import * as bcrypt from 'bcrypt';

export class Bcrypt {
  /**
   * Encodes the given raw password using the bcrypt library
   * @param rawPassword - The password to be encoded
   * @returns A promise that resolves to the encoded password
   */
  async encodePassword(rawPassword: string): Promise<string> {
    const salt = await bcrypt.genSalt();

    return bcrypt.hash(rawPassword, salt);
  }

  /**
   * Compares the given raw password with the encoded password (hash)
   * @param rawPassword - The password to be compared.
   * @param hash - The encoded password to compare with
   * @returns A promise that resolves to true if the passwords match, false otherwise
   */
  async comparePassword(rawPassword: string, hash: string): Promise<boolean> {
    return bcrypt.compare(rawPassword, hash);
  }
}

#

Bcrypt is oly this

#

the prisma Service is globally

#
@Global()
@Module({
  providers: [PrismaService],
  exports: [PrismaService],
})
export class PrismaModule {}
faint crown
#

Hmm, and the actual Prisma service class?

onyx wren
#
import { Inject, Injectable, type OnModuleDestroy, type OnModuleInit, Scope } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { PrismaClient } from '@prisma/client';
import { Request } from 'express';
import { NanoId } from '~common/utils/nanoid';

const extendedPrismaClient = new PrismaClient().$extends({
  query: {
    customer: {
      create: async ({ args, query }) => {
        if (args && typeof args === 'object') {
          args.data = {
            ...args.data,
            code: new NanoId().generate('C'),
          };
        }

        return query(args);
      },
      update: async ({ args, query }) => {
        if (args && typeof args === 'object' && args.data.deletedBy) {
          args.data = {
            ...args.data,
            deletedAt: new Date(),
          };
        }

        return query(args);
      },
    },
  },
});

@Injectable({
  scope: Scope.REQUEST,
})
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  constructor(@Inject(REQUEST) private request: Request) {
    super();
    Object.assign(this, extendedPrismaClient);
  }

  async onModuleInit() {
    await this.$connect();
  }

  async onModuleDestroy() {
    await this.$disconnect();
  }
}
faint crown
#

Request scoped, right there

onyx wren
#

oh thanks

#

I try to get the request to fill automatically the fields createdBy, updatedBy etc

faint crown
onyx wren
#

but didn't work

onyx wren
#

A quick question, how I can type the user inside request.user?

#

I try this

#
declare global {
  namespace Express {
    interface Request {
      user?: {...}
    }
  }
}
#

but didn't work

faint crown
#

What's the problem with accessing it?