feat: Basic setup with auth

This commit is contained in:
sauravdhakal12
2026-02-21 17:21:48 +05:45
parent f6bce78aee
commit f4c9174752
24 changed files with 418 additions and 18 deletions

View File

@@ -1,4 +1,55 @@
import { Controller } from '@nestjs/common';
import {
Body,
Controller,
HttpCode,
HttpStatus,
Post,
Res,
} from '@nestjs/common';
import { AuthService } from './auth.service';
import { ApiOperation } from '@nestjs/swagger';
import {
LoginUserRequestDTO,
LoginUserResponseDTO,
RegisterUserRequestDTO,
} from './dto';
import { Response } from 'express';
import { DataResponse } from 'common/http';
@Controller('auth')
export class AuthController {}
export class AuthController {
constructor(private readonly authService: AuthService) {}
@ApiOperation({ summary: 'User login' })
@HttpCode(HttpStatus.OK)
@Post('/login')
async login(
@Body() body: LoginUserRequestDTO,
@Res({ passthrough: true }) response: Response,
): Promise<DataResponse<LoginUserResponseDTO>> {
const { accessToken, refreshToken, user } =
await this.authService.login(body);
response.cookie('accessToken', accessToken);
return new DataResponse(
new LoginUserResponseDTO(user, accessToken, refreshToken),
'Login successfull',
);
}
@ApiOperation({ summary: 'User register' })
@HttpCode(HttpStatus.CREATED)
@Post('/register')
async register(@Body() body: RegisterUserRequestDTO): Promise<string> {
await this.authService.register(body);
return 'Registered successfully. Login to continue.';
}
logout() {}
forgotPassword() {}
regenTokens() {}
}

View File

@@ -1,17 +1,23 @@
import { Module } from '@nestjs/common';
import { Global, Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { APP_GUARD } from '@nestjs/core';
import { APP_GUARD, Reflector } from '@nestjs/core';
import { AuthGuard } from './guards/auth.guard';
import { UserModule } from 'src/user/user.module';
import { JwtModule } from '@nestjs/jwt';
import { RequestContextModule } from 'core/als/request-context.module';
@Global()
@Module({
providers: [
AuthService,
{
provide: APP_GUARD,
useClass: AuthGuard,
useFactory: () => AuthGuard,
inject: [Reflector],
},
],
controllers: [AuthController],
imports: [UserModule, JwtModule, RequestContextModule],
})
export class AuthModule {}

View File

@@ -1,13 +1,74 @@
import { Injectable } from '@nestjs/common';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { Public } from './decorators';
import { LoginUserRequestDTO, RegisterUserRequestDTO } from './dto';
import * as bcrypt from 'bcrypt';
import { UserService } from 'src/user/user.service';
import { TokenInputType } from './types';
import { JwtService } from '@nestjs/jwt';
@Injectable()
@Public()
export class AuthService {
async login() {}
constructor(
private readonly userService: UserService,
private readonly jwtService: JwtService,
) {}
async signup() {}
async register(dto: RegisterUserRequestDTO) {
const hashedPassword = await bcrypt.hash(dto.password, 10);
await this.userService.createUserWithPassword({
...dto,
password: hashedPassword,
});
@Public(false)
async logout() {}
return true;
}
async login(dto: LoginUserRequestDTO) {
const user = await this.userService.findUserForAuth(dto.email);
if (!user) throw new UnauthorizedException('Invalid credentials.');
const passwordMatch = await bcrypt.compare(dto.password, user.password);
if (!passwordMatch) throw new UnauthorizedException('Invalid credentials.');
const token = {
userId: user.id,
email: user.email,
role: user.role,
};
// TODO: Store more info: orgId, orgRole, etc
const { accessToken, refreshToken } = await this.genSignedTokens(token);
const hashedRefreshToken = await bcrypt.hash(refreshToken, 10);
await this.userService.updateRefreshToken(user.id, hashedRefreshToken);
return {
accessToken,
refreshToken,
user,
};
}
logout() {}
resetPassword() {}
// TODO: Use nest jwt
private async genSignedTokens(token: TokenInputType) {
const accessToken = await this.jwtService.signAsync(token, {
secret: 'demo',
});
const refreshToken = await this.jwtService.signAsync(
{
userId: token.userId,
},
{
secret: 'demo',
},
);
return { accessToken, refreshToken };
}
}

View File

@@ -1 +1,3 @@
export * from './register-user.dto';
export * from './login-user.dto';
export * from './login-response.dto';

View File

@@ -0,0 +1,14 @@
import { User } from 'prisma/generated/prisma/client';
import { UserDTO } from 'src/user/dtos';
export class LoginUserResponseDTO {
readonly accessToken: string;
readonly refreshToken: string;
readonly user: UserDTO;
constructor(user: User, accessToken: string, refreshToken: string) {
this.user = new UserDTO(user);
this.accessToken = accessToken;
this.refreshToken = refreshToken;
}
}

View File

@@ -0,0 +1,24 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsNotEmpty, IsString, MinLength } from 'class-validator';
export class LoginUserRequestDTO {
@ApiProperty({
description: "User's email",
example: 'user@example.com',
type: 'string',
})
@IsEmail()
@IsNotEmpty()
email: string;
@ApiProperty({
description: "User's password",
example: '123456',
type: 'string',
minLength: 6,
})
@IsString()
@IsNotEmpty()
@MinLength(6)
password: string;
}

View File

@@ -12,9 +12,9 @@ import { PUBLIC_KEY } from 'common/keys';
export class AuthGuard implements CanActivate {
constructor(
private readonly reflector: Reflector,
private readonly jwtService: JwtService,
private readonly requestContext: RequestContextService,
private readonly reflector: Reflector,
) {}
async canActivate(context: ExecutionContext) {

View File

@@ -1,2 +1,3 @@
export * from './jwt';
export * from './role';
export * from './token';

View File

@@ -1,11 +1,12 @@
import { UserRoleType } from './role';
import { ORG_ROLE, USER_ROLE } from 'prisma/generated/prisma/enums';
export interface JwtPayload {
iat?: number;
exp?: number;
orgId?: string;
orgRole?: ORG_ROLE;
userId: string;
email: string;
role: UserRoleType;
permission?: string[];
role: USER_ROLE;
permissions?: string[];
}

11
src/auth/types/token.ts Normal file
View File

@@ -0,0 +1,11 @@
export interface TokenInputType {
userId: string;
email: string;
role: string;
}
export interface AccessTokenPayloadType extends TokenInputType {}
export interface RefreshTokenPayloadType {
userId: string;
}