Skip to content

Commit

Permalink
feat: add user routes, enable auth globally
Browse files Browse the repository at this point in the history
  • Loading branch information
crazyoptimist committed May 15, 2024
1 parent 113658a commit 2e3d439
Show file tree
Hide file tree
Showing 10 changed files with 170 additions and 31 deletions.
16 changes: 9 additions & 7 deletions src/modules/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,41 +11,43 @@ import { AuthService } from './auth.service';
import { SigninDto } from './dto/signin.dto';
import { SignupDto } from './dto/signup.dto';
import { JwtAuthGuard } from './passport/jwt.guard';
import { UsersService } from '@modules/user/user.service';
import { UserService } from '@modules/user/user.service';
import { IRequest } from '@modules/user/user.interface';
import { NoAuth } from '@modules/common/decorator/no-auth.decorator';

@Controller('api/auth')
@ApiTags('authentication')
export class AuthController {
constructor(
private readonly authService: AuthService,
private readonly userService: UsersService,
private readonly userService: UserService,
) {}

@Post('login')
@NoAuth()
@ApiResponse({ status: 201, description: 'Successful Login' })
@ApiResponse({ status: 400, description: 'Bad Request' })
@ApiResponse({ status: 401, description: 'Unauthorized' })
async login(@Body() signinDto: SigninDto): Promise<any> {
const user = await this.authService.validateUser(signinDto);
return await this.authService.createToken(user);
return this.authService.createToken(user);
}

@Post('signup')
@NoAuth()
@ApiResponse({ status: 201, description: 'Successful Registration' })
@ApiResponse({ status: 400, description: 'Bad Request' })
@ApiResponse({ status: 401, description: 'Unauthorized' })
async signup(@Body() signupDto: SignupDto): Promise<any> {
const user = await this.userService.create(signupDto);
return await this.authService.createToken(user);
return this.authService.createToken(user);
}

@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get('me')
@ApiBearerAuth()
@ApiResponse({ status: 200, description: 'Successful Response' })
@ApiResponse({ status: 401, description: 'Unauthorized' })
async getLoggedInUser(@Request() request: IRequest): Promise<any> {
return request.user;
return this.userService.findOne(request.user?.id);
}
}
4 changes: 2 additions & 2 deletions src/modules/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';

import { Hash } from '@app/utils/hash.util';
import { UsersService } from '@modules/user/user.service';
import { UserService } from '@modules/user/user.service';
import { IUser } from '@modules/user/user.interface';
import { SigninDto } from './dto/signin.dto';
import { JwtPayload } from './passport/jwt.strategy';
Expand All @@ -13,7 +13,7 @@ export class AuthService {
constructor(
private readonly jwtService: JwtService,
private readonly configService: ConfigService,
private readonly userService: UsersService,
private readonly userService: UserService,
) {}

async validateUser(signinDto: SigninDto): Promise<IUser> {
Expand Down
17 changes: 15 additions & 2 deletions src/modules/auth/passport/jwt.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,26 @@ import {
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { IS_NO_AUTH_KEY } from '@app/modules/common/decorator/no-auth.decorator';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(private reflector: Reflector) {
super();
}

canActivate(context: ExecutionContext) {
// Add your custom authentication logic here
// for example, call super.logIn(request) to establish a session.
const isNoAuth = this.reflector.getAllAndOverride<boolean>(IS_NO_AUTH_KEY, [
context.getHandler(),
context.getClass(),
]);

if (isNoAuth) {
return true;
}

return super.canActivate(context);
}

Expand Down
4 changes: 3 additions & 1 deletion src/modules/auth/passport/jwt.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
});
}

// This function should return a user object,
// which will then be injected into the request object by Nest.
async validate({ sub }: JwtPayload) {
return { userId: sub };
return { id: sub };
}
}
4 changes: 4 additions & 0 deletions src/modules/common/decorator/no-auth.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { SetMetadata } from '@nestjs/common';

export const IS_NO_AUTH_KEY = 'isNoAuth';
export const NoAuth = () => SetMetadata(IS_NO_AUTH_KEY, true);
11 changes: 10 additions & 1 deletion src/modules/main/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule, TypeOrmModuleAsyncOptions } from '@nestjs/typeorm';
import { APP_GUARD } from '@nestjs/core';

import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { AuthModule } from '@modules/auth/auth.module';
import { JwtAuthGuard } from '@modules/auth/passport/jwt.guard';
import { CommonModule } from '@modules/common/common.module';

// TypeORM Entities
Expand Down Expand Up @@ -37,6 +39,13 @@ import { User } from '@modules/user/user.entity';
AuthModule,
],
controllers: [AppController],
providers: [AppService],
providers: [
AppService,
{
// Enables JWT authentication globally
provide: APP_GUARD,
useClass: JwtAuthGuard,
},
],
})
export class AppModule {}
69 changes: 69 additions & 0 deletions src/modules/user/user.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import {
Controller,
Body,
Param,
Post,
Get,
Patch,
Delete,
} from '@nestjs/common';
import { ApiResponse, ApiTags, ApiBearerAuth } from '@nestjs/swagger';
import { UserService } from './user.service';
import { SignupDto } from '../auth/dto/signup.dto';
import { IUser } from './user.interface';
import { UpdateUserDto } from './user.dto';

@Controller('api/users')
@ApiTags('users')
export class UserController {
constructor(private readonly userService: UserService) {}

@Post()
@ApiBearerAuth()
@ApiResponse({ status: 201, description: 'New User Created' })
@ApiResponse({ status: 400, description: 'Bad Request' })
@ApiResponse({ status: 401, description: 'Unauthorized' })
async create(@Body() signupDto: SignupDto): Promise<IUser> {
return this.userService.create(signupDto);
}

@Get()
@ApiBearerAuth()
@ApiResponse({ status: 200, description: 'All Users' })
@ApiResponse({ status: 401, description: 'Unauthorized' })
async findAll(): Promise<Array<IUser>> {
return this.userService.findAll();
}

@Get(':id')
@ApiBearerAuth()
@ApiResponse({ status: 200, description: 'User For Given ID' })
@ApiResponse({ status: 401, description: 'Unauthorized' })
@ApiResponse({ status: 404, description: 'Not Found' })
async findOne(@Param('id') id: string): Promise<IUser> {
return this.userService.findOne(+id);
}

@Patch(':id')
@ApiBearerAuth()
@ApiResponse({ status: 200, description: 'Successful Update' })
@ApiResponse({ status: 400, description: 'Bad Request' })
@ApiResponse({ status: 401, description: 'Unauthorized' })
@ApiResponse({ status: 403, description: 'Forbidden Resource' })
async update(
@Param('id') id: string,
@Body() updateUserDto: UpdateUserDto,
): Promise<any> {
return this.userService.update(+id, updateUserDto);
}

@Delete(':id')
@ApiBearerAuth()
@ApiResponse({ status: 200, description: 'Successful Delete' })
@ApiResponse({ status: 400, description: 'Bad Request' })
@ApiResponse({ status: 401, description: 'Unauthorized' })
@ApiResponse({ status: 403, description: 'Forbidden Resource' })
async delete(@Param('id') id: number): Promise<any> {
return this.userService.delete(+id);
}
}
12 changes: 12 additions & 0 deletions src/modules/user/user.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ApiPropertyOptional } from '@nestjs/swagger';
import { Allow } from 'class-validator';

export class UpdateUserDto {
@ApiPropertyOptional()
@Allow()
firstName: string;

@ApiPropertyOptional()
@Allow()
lastName: string;
}
8 changes: 5 additions & 3 deletions src/modules/user/user.module.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';
import { UsersService } from './user.service';
import { UserController } from './user.controller';
import { UserService } from './user.service';

@Module({
imports: [TypeOrmModule.forFeature([User])],
exports: [UsersService],
providers: [UsersService],
controllers: [UserController],
providers: [UserService],
exports: [UserService],
})
export class UserModule {}
56 changes: 41 additions & 15 deletions src/modules/user/user.service.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,53 @@
import { ConflictException, Injectable } from '@nestjs/common';
import {
ConflictException,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';

import { User } from './user.entity';
import { SignupDto } from '@modules/auth/dto/signup.dto';
import { UpdateUserDto } from './user.dto';

@Injectable()
export class UsersService {
export class UserService {
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
) {}

async create(signupDto: SignupDto) {
const user = await this.findByEmail(signupDto.email);

if (user) {
throw new ConflictException(
'A user with the provided email already exists.',
);
}

return await this.userRepository.save(
this.userRepository.create(signupDto),
);
}

async findAll() {
return await this.userRepository.find();
}

async findOne(id: number) {
return this.userRepository.findOne({
// This is a known issue in TypeORM
if (!Number(id)) {
throw new NotFoundException();
}

const user = await this.userRepository.findOne({
where: { id },
});
if (!user) {
throw new NotFoundException();
}

return user;
}

async findByEmail(email: string) {
Expand All @@ -26,17 +58,11 @@ export class UsersService {
.getOne();
}

async create(signupDto: SignupDto) {
const user = await this.findByEmail(signupDto.email);

if (user) {
throw new ConflictException(
'A user with the provided email already exists.',
);
}
async update(id: number, dto: UpdateUserDto) {
return await this.userRepository.update(id, dto);
}

return await this.userRepository.save(
this.userRepository.create(signupDto),
);
async delete(id: number) {
return await this.userRepository.delete(id);
}
}

0 comments on commit 2e3d439

Please sign in to comment.