Skip to content

Commit

Permalink
Merge pull request #61 from boostcampwm2023/feat/20-auth-login-api
Browse files Browse the repository at this point in the history
[Feat] JWT를 활용한 인증 및 로그인 API 구현
  • Loading branch information
mingxoxo authored Nov 20, 2023
2 parents ef915d6 + 1355bc7 commit e917b8d
Show file tree
Hide file tree
Showing 17 changed files with 376 additions and 37 deletions.
227 changes: 206 additions & 21 deletions BE/package-lock.json

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions BE/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,20 @@
"@nestjs/common": "^10.0.0",
"@nestjs/config": "^3.1.1",
"@nestjs/core": "^10.0.0",
"@nestjs/jwt": "^10.2.0",
"@nestjs/passport": "^10.0.2",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/typeorm": "^10.0.0",
"@types/dotenv": "^8.2.0",
"@types/passport-jwt": "^3.0.13",
"bcryptjs": "^2.4.3",
"class-transformer": "^0.5.1",
"aws-sdk": "^2.1499.0",
"class-validator": "^0.14.0",
"dotenv": "^16.3.1",
"mysql2": "^3.6.3",
"passport": "^0.6.0",
"passport-jwt": "^4.0.1",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1",
"typeorm": "^0.3.17"
Expand Down
2 changes: 2 additions & 0 deletions BE/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import { TypeOrmModule } from "@nestjs/typeorm";
import { typeORMConfig } from "./configs/typeorm.config";
import { UsersModule } from "./users/users.module";
import { DiariesModule } from "./diaries/diaries.module";
import { AuthModule } from "./auth/auth.module";
import { IntroduceModule } from "./introduce/introduce.module";

@Module({
imports: [
TypeOrmModule.forRoot(typeORMConfig),
UsersModule,
DiariesModule,
AuthModule,
IntroduceModule,
],
})
Expand Down
16 changes: 16 additions & 0 deletions BE/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Body, Controller, Post, ValidationPipe } from "@nestjs/common";
import { AuthService } from "./auth.service";
import { AuthCredentialsDto } from "./dto/auth-credential.dto";
import { AccessTokenDto } from "./dto/auth-access-token.dto";

@Controller("auth")
export class AuthController {
constructor(private authService: AuthService) {}

@Post("/signin")
signIn(
@Body(ValidationPipe) authCredentialsDto: AuthCredentialsDto,
): Promise<AccessTokenDto> {
return this.authService.signIn(authCredentialsDto);
}
}
25 changes: 25 additions & 0 deletions BE/src/auth/auth.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import "dotenv/config";
import { Module } from "@nestjs/common";
import { AuthController } from "./auth.controller";
import { AuthService } from "./auth.service";
import { JwtModule } from "@nestjs/jwt";
import { PassportModule } from "@nestjs/passport";
import { JwtStrategy } from "./jwt.strategy";
import { UsersModule } from "src/users/users.module";

@Module({
imports: [
PassportModule.register({ defaultStrategy: "jwt" }),
JwtModule.register({
secret: process.env.JWT_SECRET,
signOptions: {
expiresIn: process.env.JWT_ACCESS_TOKEN_TIME,
},
}),
UsersModule,
],
controllers: [AuthController],
providers: [AuthService, JwtStrategy],
exports: [PassportModule],
})
export class AuthModule {}
30 changes: 30 additions & 0 deletions BE/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Injectable, UnauthorizedException } from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";
import { UsersRepository } from "src/users/users.repository";
import { AuthCredentialsDto } from "./dto/auth-credential.dto";
import * as bcrypt from "bcryptjs";
import { AccessTokenDto } from "./dto/auth-access-token.dto";

@Injectable()
export class AuthService {
constructor(
private usersRepository: UsersRepository,
private jwtService: JwtService,
) {}

async signIn(
authCredentialsDto: AuthCredentialsDto,
): Promise<AccessTokenDto> {
const { userId, password } = authCredentialsDto;
const user = await this.usersRepository.getUserByUserId(userId);

if (user && (await bcrypt.compare(password, user.password))) {
const payload = { userId };
const accessToken = await this.jwtService.sign(payload);

return new AccessTokenDto(accessToken);
} else {
throw new UnauthorizedException("login failed");
}
}
}
7 changes: 7 additions & 0 deletions BE/src/auth/dto/auth-access-token.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export class AccessTokenDto {
accessToken: string;

constructor(accessToken: string) {
this.accessToken = accessToken;
}
}
11 changes: 11 additions & 0 deletions BE/src/auth/dto/auth-credential.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { IsString, MaxLength } from "class-validator";

export class AuthCredentialsDto {
@IsString()
@MaxLength(20)
userId: string;

@IsString()
@MaxLength(20)
password: string;
}
25 changes: 25 additions & 0 deletions BE/src/auth/jwt.strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Injectable, UnauthorizedException } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { ExtractJwt, Strategy } from "passport-jwt";
import { User } from "src/users/users.entity";
import { UsersRepository } from "src/users/users.repository";

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private userRepository: UsersRepository) {
super({
secretOrKey: process.env.JWT_SECRET,
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
});
}

async validate(payload) {
const { userId } = payload;
const user: User = await this.userRepository.getUserByUserId(userId);

if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
3 changes: 3 additions & 0 deletions BE/src/diaries/diaries.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Param,
Post,
Put,
UseGuards,
} from "@nestjs/common";
import { DiariesService } from "./diaries.service";
import {
Expand All @@ -15,8 +16,10 @@ import {
UpdateDiaryDto,
} from "./diaries.dto";
import { Diary } from "./diaries.entity";
import { AuthGuard } from "@nestjs/passport";

@Controller("diaries")
@UseGuards(AuthGuard())
export class DiariesController {
constructor(private diariesService: DiariesService) {}

Expand Down
3 changes: 2 additions & 1 deletion BE/src/diaries/diaries.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { DiariesController } from "./diaries.controller";
import { DiariesService } from "./diaries.service";
import { DiariesRepository } from "./diaries.repository";
import { Diary } from "./diaries.entity";
import { AuthModule } from "src/auth/auth.module";

@Module({
imports: [TypeOrmModule.forFeature([Diary])],
imports: [TypeOrmModule.forFeature([Diary]), AuthModule],
controllers: [DiariesController],
providers: [DiariesService, DiariesRepository],
})
Expand Down
6 changes: 4 additions & 2 deletions BE/src/users/users.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Body, Controller, Post } from "@nestjs/common";
import { Body, Controller, Post, ValidationPipe } from "@nestjs/common";
import { UsersService } from "./users.service";
import { CreateUserDto } from "./users.dto";

Expand All @@ -7,7 +7,9 @@ export class UsersController {
constructor(private usersService: UsersService) {}

@Post()
async registerUser(@Body() createUserDto: CreateUserDto): Promise<void> {
async registerUser(
@Body(ValidationPipe) createUserDto: CreateUserDto,
): Promise<void> {
await this.usersService.registerUser(createUserDto);
return;
}
Expand Down
5 changes: 4 additions & 1 deletion BE/src/users/users.dto.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { IsString, IsNumber, IsEnum } from "class-validator";
import { IsString, IsNumber, IsEnum, MaxLength } from "class-validator";
import { premiumStatus } from "src/utils/enum";

export class CreateUserDto {
@IsString()
@MaxLength(20)
userId: string;

@IsString()
@MaxLength(20)
password: string;

@IsString()
@MaxLength(20)
nickname: string;
}

Expand Down
2 changes: 1 addition & 1 deletion BE/src/users/users.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export class User extends BaseEntity {
@Column({ length: 20, unique: true })
userId: string;

@Column({ length: 20 })
@Column({ length: 60 })
password: string;

@Column({ length: 20 })
Expand Down
1 change: 1 addition & 0 deletions BE/src/users/users.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ import { UsersRepository } from "./users.repository";
imports: [TypeOrmModule.forFeature([User])],
controllers: [UsersController],
providers: [UsersService, UsersRepository],
exports: [UsersRepository],
})
export class UsersModule {}
40 changes: 31 additions & 9 deletions BE/src/users/users.repository.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,38 @@
import {
ConflictException,
InternalServerErrorException,
NotFoundException,
} from "@nestjs/common";
import { CreateUserDto } from "./users.dto";
import { User } from "./users.entity";
import * as bcrypt from "bcryptjs";

export class UsersRepository {
async createUser(
createUserDto: CreateUserDto,
encodedPassword: string,
): Promise<User> {
const { userId, nickname } = createUserDto;
const password = encodedPassword;
const newUser = User.create({ userId, password, nickname });
await newUser.save();
async createUser(createUserDto: CreateUserDto): Promise<User> {
const { userId, password, nickname } = createUserDto;

return newUser;
const salt = await bcrypt.genSalt();
const hashedPassword = await bcrypt.hash(password, salt);
const user = User.create({ userId, password: hashedPassword, nickname });

try {
await user.save();
} catch (error) {
if (error.code === "ER_DUP_ENTRY") {
throw new ConflictException("Existing userId");
} else {
throw new InternalServerErrorException();
}
}

return user;
}

async getUserByUserId(userId: string): Promise<User> {
const found = await User.findOne({ where: { userId } });
if (!found) {
throw new NotFoundException(`Can't find User with UserId: [${userId}]`);
}
return found;
}
}
3 changes: 1 addition & 2 deletions BE/src/users/users.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export class UsersService {
constructor(private usersRepository: UsersRepository) {}

async registerUser(createUserDto: CreateUserDto): Promise<User> {
const encodedPassword = btoa(createUserDto.password);
return this.usersRepository.createUser(createUserDto, encodedPassword);
return this.usersRepository.createUser(createUserDto);
}
}

0 comments on commit e917b8d

Please sign in to comment.