feat: Basic setup with auth
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { AppService } from './app.service';
|
||||
import { Public } from './auth/decorators';
|
||||
|
||||
@Controller()
|
||||
@Controller('')
|
||||
@Public(true)
|
||||
export class AppController {
|
||||
constructor(private readonly appService: AppService) {}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
|
||||
import { Logger, MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { UserModule } from './user/user.module';
|
||||
@@ -7,6 +7,10 @@ import { RequestContextMiddleware } from 'core/als/request-context.middleware';
|
||||
import { RequestContextModule } from 'core/als/request-context.module';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { PrismaModule } from './prisma/prisma.module';
|
||||
import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core';
|
||||
import { ResponseInterceptor } from 'common/interceptors/response.interceptor';
|
||||
import { ExceptionsHandler } from '@nestjs/core/exceptions/exceptions-handler';
|
||||
import { HttpExceptionFilter } from 'common/exceptions/exception-filter';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -19,7 +23,18 @@ import { PrismaModule } from './prisma/prisma.module';
|
||||
PrismaModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
providers: [
|
||||
AppService,
|
||||
Logger,
|
||||
{
|
||||
provide: APP_INTERCEPTOR,
|
||||
useClass: ResponseInterceptor,
|
||||
},
|
||||
{
|
||||
provide: APP_FILTER,
|
||||
useClass: HttpExceptionFilter,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class AppModule implements NestModule {
|
||||
configure(consumer: MiddlewareConsumer) {
|
||||
|
||||
@@ -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() {}
|
||||
}
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
export * from './register-user.dto';
|
||||
export * from './login-user.dto';
|
||||
export * from './login-response.dto';
|
||||
|
||||
14
src/auth/dto/login-response.dto.ts
Normal file
14
src/auth/dto/login-response.dto.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
24
src/auth/dto/login-user.dto.ts
Normal file
24
src/auth/dto/login-user.dto.ts
Normal 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;
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './jwt';
|
||||
export * from './role';
|
||||
export * from './token';
|
||||
|
||||
@@ -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
11
src/auth/types/token.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export interface TokenInputType {
|
||||
userId: string;
|
||||
email: string;
|
||||
role: string;
|
||||
}
|
||||
|
||||
export interface AccessTokenPayloadType extends TokenInputType {}
|
||||
|
||||
export interface RefreshTokenPayloadType {
|
||||
userId: string;
|
||||
}
|
||||
37
src/main.ts
37
src/main.ts
@@ -1,10 +1,47 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
||||
const swaggerConfig = new DocumentBuilder()
|
||||
.setTitle('Kaa Khane')
|
||||
.setDescription(`API Documentation for Kaa Khane`)
|
||||
.setVersion('0.0.1')
|
||||
.addGlobalResponse(
|
||||
{
|
||||
status: 500,
|
||||
description: 'Internal Server Error',
|
||||
},
|
||||
{
|
||||
status: 401,
|
||||
description: 'Unauthorized',
|
||||
},
|
||||
{
|
||||
status: 403,
|
||||
description: 'Forbidden',
|
||||
},
|
||||
)
|
||||
.addBearerAuth(
|
||||
{
|
||||
type: 'http',
|
||||
scheme: 'bearer',
|
||||
bearerFormat: 'JWT',
|
||||
},
|
||||
'access-token',
|
||||
)
|
||||
.build();
|
||||
|
||||
const documentFactory = () =>
|
||||
SwaggerModule.createDocument(app, swaggerConfig);
|
||||
SwaggerModule.setup('/docs', app, documentFactory, {
|
||||
swaggerOptions: {
|
||||
persistAuthorization: true,
|
||||
},
|
||||
});
|
||||
|
||||
const config = app.get(ConfigService);
|
||||
const port = config.get<number>('PORT') ?? 3000;
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ export class PrismaService
|
||||
const connectionPool = new Pool({
|
||||
connectionString,
|
||||
});
|
||||
console.log(connectionString);
|
||||
|
||||
const adapter = new PrismaPg(connectionPool);
|
||||
|
||||
|
||||
@@ -5,5 +5,6 @@ import { PrismaModule } from 'src/prisma/prisma.module';
|
||||
@Module({
|
||||
providers: [UserService],
|
||||
imports: [PrismaModule],
|
||||
exports: [UserService],
|
||||
})
|
||||
export class UserModule {}
|
||||
|
||||
Reference in New Issue
Block a user