Skip to content

Commit a3049af

Browse files
Merge pull request #220 from boostcampwm-2024/feature-be-#189
로그인, 회원가입 구현
2 parents c6ca765 + 14142c5 commit a3049af

File tree

18 files changed

+415
-47
lines changed

18 files changed

+415
-47
lines changed

apps/backend/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"@nestjs/config": "^3.3.0",
2626
"@nestjs/core": "^10.0.0",
2727
"@nestjs/mapped-types": "*",
28+
"@nestjs/passport": "^10.0.3",
2829
"@nestjs/platform-express": "^10.0.0",
2930
"@nestjs/platform-socket.io": "^10.4.8",
3031
"@nestjs/platform-ws": "^10.4.7",
@@ -38,6 +39,9 @@
3839
"lib0": "^0.2.98",
3940
"node-ts-cache": "^4.4.0",
4041
"node-ts-cache-storage-memory": "^4.4.0",
42+
"passport": "^0.7.0",
43+
"passport-kakao": "^1.0.1",
44+
"passport-naver": "^1.0.6",
4145
"path": "^0.12.7",
4246
"reflect-metadata": "^0.1.13",
4347
"rxjs": "^7.8.1",

apps/backend/src/app.module.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@ import { TypeOrmModule } from '@nestjs/typeorm';
99
import { Page } from './page/page.entity';
1010
import { Edge } from './edge/edge.entity';
1111
import { Node } from './node/node.entity';
12+
import { User } from './user/user.entity';
1213
import { YjsModule } from './yjs/yjs.module';
1314
import * as path from 'path';
1415
import { ServeStaticModule } from '@nestjs/serve-static';
1516
import { UploadModule } from './upload/upload.module';
17+
import { AuthModule } from './auth/auth.module';
18+
import { UserModule } from './user/user.module';
1619

1720
@Module({
1821
imports: [
@@ -29,7 +32,7 @@ import { UploadModule } from './upload/upload.module';
2932
useFactory: (configService: ConfigService) => ({
3033
type: 'sqlite',
3134
database: configService.get('DB_NAME'),
32-
entities: [Node, Page, Edge],
35+
entities: [Node, Page, Edge, User],
3336
logging: true,
3437
synchronize: true,
3538
}),
@@ -39,6 +42,8 @@ import { UploadModule } from './upload/upload.module';
3942
EdgeModule,
4043
YjsModule,
4144
UploadModule,
45+
AuthModule,
46+
UserModule,
4247
],
4348
controllers: [AppController],
4449
providers: [AppService],
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Test, TestingModule } from '@nestjs/testing';
2+
import { AuthController } from './auth.controller';
3+
import { AuthService } from './auth.service';
4+
5+
describe('AuthController', () => {
6+
let controller: AuthController;
7+
8+
beforeEach(async () => {
9+
const module: TestingModule = await Test.createTestingModule({
10+
controllers: [AuthController],
11+
providers: [
12+
{
13+
provide: AuthService,
14+
useValue: {},
15+
},
16+
],
17+
}).compile();
18+
19+
controller = module.get<AuthController>(AuthController);
20+
});
21+
22+
it('should be defined', () => {
23+
expect(controller).toBeDefined();
24+
});
25+
});
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { Controller, Get, UseGuards, Req } from '@nestjs/common';
2+
import { AuthGuard } from '@nestjs/passport';
3+
import { AuthService } from './auth.service';
4+
5+
@Controller('auth')
6+
export class AuthController {
7+
constructor(private readonly authService: AuthService) {}
8+
9+
@Get('naver')
10+
@UseGuards(AuthGuard('naver'))
11+
async naverLogin() {
12+
// 네이버 로그인 페이지로 리디렉션
13+
// Passport가 리디렉션 처리
14+
}
15+
16+
@Get('naver/callback')
17+
@UseGuards(AuthGuard('naver'))
18+
async naverCallback(@Req() req) {
19+
// 네이버 인증 후 사용자 정보 반환
20+
return {
21+
message: '네이버 로그인 성공',
22+
user: req.user,
23+
};
24+
}
25+
26+
@Get('kakao')
27+
@UseGuards(AuthGuard('kakao'))
28+
async kakaoLogin() {
29+
// 카카오 로그인 페이지로 리디렉션
30+
// Passport가 리디렉션 처리
31+
}
32+
33+
@Get('kakao/callback')
34+
@UseGuards(AuthGuard('kakao'))
35+
async kakaoCallback(@Req() req) {
36+
// 카카오 인증 후 사용자 정보 반환
37+
return {
38+
message: '카카오 로그인 성공',
39+
user: req.user,
40+
};
41+
}
42+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Module } from '@nestjs/common';
2+
import { UserRepository } from '../user/user.repository';
3+
import { UserModule } from 'src/user/user.module';
4+
import { AuthService } from './auth.service';
5+
import { AuthController } from './auth.controller';
6+
import { NaverStrategy } from './strategies/naver.strategy';
7+
import { KakaoStrategy } from './strategies/kakao.strategy';
8+
@Module({
9+
imports: [UserModule],
10+
controllers: [AuthController],
11+
providers: [AuthService, NaverStrategy, KakaoStrategy, UserRepository],
12+
})
13+
export class AuthModule {}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Test, TestingModule } from '@nestjs/testing';
2+
import { AuthService } from './auth.service';
3+
import { UserRepository } from '../user/user.repository';
4+
5+
describe('AuthService', () => {
6+
let service: AuthService;
7+
8+
beforeEach(async () => {
9+
const module: TestingModule = await Test.createTestingModule({
10+
providers: [
11+
AuthService,
12+
{
13+
provide: UserRepository,
14+
useValue: {},
15+
},
16+
],
17+
}).compile();
18+
19+
service = module.get<AuthService>(AuthService);
20+
});
21+
22+
it('should be defined', () => {
23+
expect(service).toBeDefined();
24+
});
25+
});
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Injectable } from '@nestjs/common';
2+
import { UserRepository } from '../user/user.repository';
3+
import { User } from '../user/user.entity';
4+
import { CreateUserDto } from './dto/createUser.dto';
5+
6+
@Injectable()
7+
export class AuthService {
8+
constructor(private readonly userRepository: UserRepository) {}
9+
10+
async findUser(dto: CreateUserDto): Promise<User | null> {
11+
const { providerId, provider } = dto;
12+
13+
const user = await this.userRepository.findOne({
14+
where: { providerId, provider },
15+
});
16+
17+
return user;
18+
}
19+
20+
async createUser(dto: CreateUserDto): Promise<User> {
21+
const user = this.userRepository.create(dto);
22+
return this.userRepository.save(user);
23+
}
24+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { ApiProperty } from '@nestjs/swagger';
2+
import { IsString, IsEmail, IsIn } from 'class-validator';
3+
4+
export class CreateUserDto {
5+
@IsString()
6+
@ApiProperty({
7+
example: 'abc1234',
8+
description: '사용자의 카카오/네이버 아이디',
9+
})
10+
providerId: string;
11+
12+
@IsString()
13+
@IsIn(['naver', 'kakao'], {
14+
message: 'provider는 naver 또는 kakao 중 하나여야 합니다.',
15+
})
16+
@ApiProperty({
17+
example: 'naver',
18+
description: '연동되는 서비스: 네이버/카카오',
19+
})
20+
provider: string;
21+
22+
@IsEmail()
23+
@ApiProperty({
24+
example: '[email protected]',
25+
description: '사용자의 이메일 주소(카카오, 네이버 외에도) 가능',
26+
})
27+
email: string;
28+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// src/auth/strategies/kakao.strategy.ts
2+
import { Injectable } from '@nestjs/common';
3+
import { PassportStrategy } from '@nestjs/passport';
4+
import { Profile, Strategy } from 'passport-kakao';
5+
import { AuthService } from '../auth.service';
6+
import { CreateUserDto } from '../dto/createUser.dto';
7+
8+
@Injectable()
9+
export class KakaoStrategy extends PassportStrategy(Strategy, 'kakao') {
10+
constructor(private authService: AuthService) {
11+
super({
12+
clientID: process.env.KAKAO_CLIENT_ID,
13+
clientSecret: process.env.KAKAO_CLIENT_SECRET,
14+
callbackURL: process.env.KAKAO_CALLBACK_URL,
15+
});
16+
}
17+
18+
async validate(accessToken: string, refreshToken: string, profile: Profile) {
19+
// 카카오 인증 이후 사용자 정보 처리
20+
const createUserDto: CreateUserDto = {
21+
providerId: profile.id,
22+
provider: 'kakao',
23+
email: profile._json.kakao_account.email,
24+
};
25+
let user = await this.authService.findUser(createUserDto);
26+
if (!user) {
27+
user = await this.authService.createUser(createUserDto);
28+
}
29+
return user; // req.user로 반환
30+
}
31+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// src/auth/strategies/naver.strategy.ts
2+
import { Injectable } from '@nestjs/common';
3+
import { PassportStrategy } from '@nestjs/passport';
4+
import { Profile, Strategy } from 'passport-naver';
5+
import { AuthService } from '../auth.service';
6+
import { CreateUserDto } from '../dto/createUser.dto';
7+
8+
@Injectable()
9+
export class NaverStrategy extends PassportStrategy(Strategy, 'naver') {
10+
constructor(private authService: AuthService) {
11+
super({
12+
clientID: process.env.NAVER_CLIENT_ID, // 환경 변수로 관리
13+
clientSecret: process.env.NAVER_CLIENT_SECRET,
14+
callbackURL: process.env.NAVER_CALLBACK_URL,
15+
});
16+
}
17+
18+
async validate(accessToken: string, refreshToken: string, profile: Profile) {
19+
// 네이버 인증 이후 사용자 정보 처리
20+
const createUserDto: CreateUserDto = {
21+
providerId: profile.id,
22+
provider: 'naver',
23+
email: profile._json.email,
24+
};
25+
let user = await this.authService.findUser(createUserDto);
26+
if (!user) {
27+
user = await this.authService.createUser(createUserDto);
28+
}
29+
return user; // req.user로 반환
30+
}
31+
}

0 commit comments

Comments
 (0)