feat: added auth guard and some decorators
This commit is contained in:
+32
-33
@@ -1,58 +1,57 @@
|
||||
import { Body, Controller, Post } from "@nestjs/common";
|
||||
import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
|
||||
import { AuthService } from "./auth.service";
|
||||
import { SendOtpDto } from "./DTO/send-otp.dto";
|
||||
import { LoginCredentialDto } from "./DTO/login-credential.dto";
|
||||
import { VerifyOtpDto } from "./DTO/verify-otp.dto";
|
||||
import { Body, Controller, Get, Post, Req, UseGuards } from '@nestjs/common';
|
||||
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { AuthService } from './auth.service';
|
||||
import { SendOtpDto } from './DTO/send-otp.dto';
|
||||
import { LoginCredentialDto } from './DTO/login-credential.dto';
|
||||
import { VerifyOtpDto } from './DTO/verify-otp.dto';
|
||||
import { AuthGuard } from 'src/common/decorators/auth-guard.decorator';
|
||||
import type { IRequest } from './interfaces/IRequest';
|
||||
import { UserDec } from 'src/common/decorators/user.decorator';
|
||||
|
||||
@ApiTags('Authentication')
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
constructor(
|
||||
private readonly authService: AuthService,
|
||||
) {}
|
||||
constructor(private readonly authService: AuthService) {}
|
||||
|
||||
@Post('send-otp')
|
||||
@ApiOperation({
|
||||
summary: 'Send OTP to a phone number',
|
||||
})
|
||||
@ApiResponse({status: 200, description: 'Sent Otp Successfully!'})
|
||||
@ApiResponse({status: 400, description: 'Bad Request from User!'})
|
||||
async sendOtp(
|
||||
@Body() dto: SendOtpDto
|
||||
) {
|
||||
@ApiResponse({ status: 200, description: 'Sent Otp Successfully!' })
|
||||
@ApiResponse({ status: 400, description: 'Bad Request from User!' })
|
||||
async sendOtp(@Body() dto: SendOtpDto) {
|
||||
return this.authService.sendOTP(dto.phoneNumber);
|
||||
}
|
||||
|
||||
@Post('verify-otp')
|
||||
@ApiOperation({
|
||||
summary: 'Verify OTP.'
|
||||
summary: 'Verify OTP.',
|
||||
})
|
||||
@ApiResponse({status: 200, description: 'Verified Otp Successfully!'})
|
||||
@ApiResponse({status: 400, description: 'Bad Request from the user'})
|
||||
async verifyOtp(
|
||||
@Body() dto: VerifyOtpDto
|
||||
) {
|
||||
@ApiResponse({ status: 200, description: 'Verified Otp Successfully!' })
|
||||
@ApiResponse({ status: 400, description: 'Bad Request from the user' })
|
||||
async verifyOtp(@Body() dto: VerifyOtpDto) {
|
||||
return this.authService.verifyOTP(dto);
|
||||
}
|
||||
|
||||
@Post('check-credential')
|
||||
@ApiOperation({summary: 'Check Login Credentials. If Valid Sends OTP'})
|
||||
@ApiResponse({status: 200, description: 'Valid Credentials!'})
|
||||
@ApiResponse({status: 400, description: 'Credentials Invalid!'})
|
||||
async checkLoginCredential(
|
||||
@Body() dto: LoginCredentialDto
|
||||
) {
|
||||
@ApiOperation({ summary: 'Check Login Credentials. If Valid Sends OTP' })
|
||||
@ApiResponse({ status: 200, description: 'Valid Credentials!' })
|
||||
@ApiResponse({ status: 400, description: 'Credentials Invalid!' })
|
||||
async checkLoginCredential(@Body() dto: LoginCredentialDto) {
|
||||
return this.authService.checkLoginCredentials(dto);
|
||||
}
|
||||
|
||||
@Post('verify-login')
|
||||
@ApiOperation({summary: 'verifies OTP and then login'})
|
||||
@ApiResponse({status: 200, description: 'User Logged in successfully!'})
|
||||
@ApiResponse({status: 400, description: 'Bad Request From The User'})
|
||||
async verifyOtpAndLogin(
|
||||
@Body() verifyOtpDto: VerifyOtpDto
|
||||
) {
|
||||
@ApiOperation({ summary: 'verifies OTP and then login' })
|
||||
@ApiResponse({ status: 200, description: 'User Logged in successfully!' })
|
||||
@ApiResponse({ status: 400, description: 'Bad Request From The User' })
|
||||
async verifyOtpAndLogin(@Body() verifyOtpDto: VerifyOtpDto) {
|
||||
return this.authService.verifyOtpAndLogin(verifyOtpDto);
|
||||
}
|
||||
}
|
||||
|
||||
@AuthGuard()
|
||||
@Get('profile')
|
||||
profile(@Req() req: IRequest, @UserDec('id') userId: string) {
|
||||
return req.user;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,15 +10,19 @@ import { OtpRepository } from './repositories/otp.repository';
|
||||
import { UsersModule } from 'src/users/users.module';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { UserRole } from 'src/users/entities/user-role.entity';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
import { JwtStrategy } from './strategies/jwt.strategy';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([OTP]),
|
||||
PassportModule,
|
||||
JwtModule.register({}),
|
||||
UtilsModule,
|
||||
UsersModule,
|
||||
],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService, OtpRepository],
|
||||
providers: [AuthService, OtpRepository, JwtStrategy],
|
||||
exports: [JwtModule, PassportModule]
|
||||
})
|
||||
export class AuthModule {}
|
||||
|
||||
@@ -133,7 +133,7 @@ export class AuthService {
|
||||
const roles = user.roles.map((r) => r.role);
|
||||
|
||||
const payload = {
|
||||
sub: user.id,
|
||||
id: user.id,
|
||||
phone: user.phone,
|
||||
roles,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { Observable } from 'rxjs';
|
||||
import { SKIP_AUTH_KEY } from 'src/common/decorators/skip-auth.decorator';
|
||||
import { AuthMessages } from 'src/common/enums/messages.enum';
|
||||
import { JWT_STRATEGY_NAME } from 'src/constants';
|
||||
|
||||
@Injectable()
|
||||
export class JwtAuthGuard extends AuthGuard(JWT_STRATEGY_NAME) {
|
||||
constructor(private reflector: Reflector) {
|
||||
super();
|
||||
}
|
||||
|
||||
canActivate(
|
||||
context: ExecutionContext,
|
||||
): boolean | Promise<boolean> | Observable<boolean> {
|
||||
const skipAuth = this.reflector.getAllAndOverride<boolean>(SKIP_AUTH_KEY, [
|
||||
context.getHandler(),
|
||||
context.getClass(),
|
||||
]);
|
||||
|
||||
if (skipAuth) return true;
|
||||
|
||||
return super.canActivate(context);
|
||||
}
|
||||
|
||||
handleRequest(err: unknown, user: any, _info: unknown) {
|
||||
if (err || !user)
|
||||
throw new UnauthorizedException(AuthMessages.TOKEN_EXPIRED_OR_INVALID);
|
||||
return user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { ITokenPayload } from "./IToken-payload";
|
||||
import { IUserIpAndHeaders } from "./IUserIpAndHeader";
|
||||
|
||||
export interface IRequest {
|
||||
user?: ITokenPayload,
|
||||
ip_header?: IUserIpAndHeaders
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { RoleType } from "src/users/enums/user-role.enum";
|
||||
|
||||
export interface ITokenPayload {
|
||||
id: string;
|
||||
phone: string;
|
||||
roles: RoleType[];
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface IUserIpAndHeaders {
|
||||
ip: string;
|
||||
headers: Record<string, string>;
|
||||
userId: string;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { RoleType } from 'src/users/enums/user-role.enum';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { Strategy, ExtractJwt } from 'passport-jwt';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { JWT_STRATEGY_NAME } from 'src/constants';
|
||||
import { ITokenPayload } from '../interfaces/IToken-payload';
|
||||
|
||||
@Injectable()
|
||||
export class JwtStrategy extends PassportStrategy(Strategy, JWT_STRATEGY_NAME) {
|
||||
constructor(configService: ConfigService) {
|
||||
super({
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
ignoreExpiration: false,
|
||||
secretOrKey: configService.getOrThrow<string>('JWT_ACCESS_SECRET'),
|
||||
});
|
||||
}
|
||||
|
||||
async validate(payload: ITokenPayload) {
|
||||
return {
|
||||
id: payload.id,
|
||||
phone: payload.phone,
|
||||
roles: payload.roles
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { applyDecorators, UseGuards } from "@nestjs/common";
|
||||
import { ApiBearerAuth } from "@nestjs/swagger";
|
||||
import { JwtAuthGuard } from "src/auth/guards/jwt-auth.guard";
|
||||
|
||||
export function AuthGuard() {
|
||||
return applyDecorators(
|
||||
UseGuards(JwtAuthGuard),
|
||||
ApiBearerAuth('authorization')
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { SetMetadata } from "@nestjs/common";
|
||||
|
||||
export const SKIP_AUTH_KEY = "skipAuth";
|
||||
|
||||
export const skipAuth = () => SetMetadata(SKIP_AUTH_KEY, true);
|
||||
@@ -0,0 +1,22 @@
|
||||
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
|
||||
import { IRequest } from 'src/auth/interfaces/IRequest';
|
||||
import { ITokenPayload } from 'src/auth/interfaces/IToken-payload';
|
||||
|
||||
export const UserDec = createParamDecorator(
|
||||
(data: keyof ITokenPayload | undefined, ctx: ExecutionContext) => {
|
||||
const req = ctx.switchToHttp().getRequest<IRequest>();
|
||||
const user = req.user;
|
||||
|
||||
return data ? user?.[data] : user;
|
||||
},
|
||||
);
|
||||
|
||||
export const UserIpAndHeaders = createParamDecorator(
|
||||
(_data: unknown, ctx: ExecutionContext) => {
|
||||
const req = ctx.switchToHttp().getRequest<IRequest>();
|
||||
const user = req.user;
|
||||
const ip_header = req.ip_header;
|
||||
|
||||
return { ip: ip_header?.ip, headers: ip_header?.headers, userId: user?.id };
|
||||
},
|
||||
);
|
||||
@@ -14,4 +14,8 @@ export enum OTPMessages {
|
||||
|
||||
export enum UserRoleMessages {
|
||||
USER_ROLE_ALREADY_EXISTS = 'نقش مورد نظر برای کاربر مد نظر وجود دارد!'
|
||||
}
|
||||
|
||||
export enum AuthMessages {
|
||||
TOKEN_EXPIRED_OR_INVALID = 'توکن منسوخ شده یا نامعتبر است!',
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export const JWT_STRATEGY_NAME = "jwt_Strategy";
|
||||
Reference in New Issue
Block a user