Skip to content

Commit

Permalink
Revert "✨ [Feature] Google OAuth login (#604)"
Browse files Browse the repository at this point in the history
This reverts commit 59ae80e.
  • Loading branch information
nyeoni authored Aug 22, 2023
1 parent 0ce5be6 commit c4484d8
Show file tree
Hide file tree
Showing 24 changed files with 99 additions and 220 deletions.
7 changes: 1 addition & 6 deletions backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,7 @@ FORTYTWO_APP_ID=
# 42인트라넷에서 발급받은 API SECRET KEY
FORTYTWO_APP_SECRET=
# 42인트라넷에서 입력한 callback url
FORTYTWO_CALLBACK_URL=http://localhost:3000/api/v1/auth/callback/ft

# Google OAuth
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_CALLBACK_URL=http://localhost:3000/api/v1/auth/callback/google
CALLBACK_URL=http://localhost:3000/api/v1/auth/42login/callback

# JWT token secret 값
USER_JWT_SECRETKEY=
Expand Down
4 changes: 1 addition & 3 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"@nestjs/swagger": "^6.3.0",
"@nestjs/typeorm": "^9.0.1",
"@nestjs/websockets": "^9.4.0",
"@types/passport-jwt": "^3.0.8",
"bcrypt": "^5.1.0",
"cache-manager": "^5.2.1",
"class-transformer": "^0.5.1",
Expand All @@ -50,7 +51,6 @@
"nodemailer": "^6.9.1",
"passport": "^0.6.0",
"passport-42": "^1.2.6",
"passport-google-oauth20": "^2.0.0",
"passport-jwt": "^4.0.1",
"pg": "^8.10.0",
"postgresql": "^0.0.1",
Expand All @@ -74,8 +74,6 @@
"@types/multer": "^1.4.7",
"@types/node": "18.11.18",
"@types/passport": "^1.0.12",
"@types/passport-google-oauth20": "^2.0.11",
"@types/passport-jwt": "^3.0.9",
"@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
Expand Down
64 changes: 31 additions & 33 deletions backend/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { COOKIE_OPTIONS } from '../common/constant';
import { ExtractUserId } from '../common/decorator/extract-user-id.decorator';
import { ErrorResponseDto } from '../common/dto/error-response.dto';
import { SuccessResponseDto } from '../common/dto/success-response.dto';
import { AppConfigService } from '../config/app/configuration.service';

import { AuthService } from './auth.service';
import { ExtractUser } from './decorator/extract-user.decorator';
Expand All @@ -22,75 +23,68 @@ import { CodeVerificationRequestDto } from './dto/request/code-verification-requ
import { TwoFactorAuthRequestDto } from './dto/request/two-factor-auth-request.dto';
import { TwoFactorAuthResponseDto } from './dto/response/two-factor-auth-response.dto';
import { FtGuard } from './guard/ft.guard';
import { GoogleGuard } from './guard/google.guard';
import { TwoFaGuard } from './guard/two-fa.guard';
import { UserGuard } from './guard/user.guard';
import { LoginInfo } from './type/login-info';
import { SocialResponseOptions } from './type/social-response-options';

@ApiTags('auth')
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
constructor(private readonly authService: AuthService, private readonly appConfigService: AppConfigService) {}

/**
* @summary 로그인
* @description GET /auth/login/ft
* @description GET /auth/login
*/
@ApiOperation({ summary: '42 로그인' })
@SkipUserGuard()
@UseGuards(FtGuard)
@Get('login/ft')
@UseGuards(FtGuard) // strategy.constructor
@Get('42login')
login(): void {
return;
}

/**
* @summary 로그인 callback
* @description GET /auth/callback/ft
* @description GET /auth/login/callback
*/
@ApiOperation({ summary: '42 로그인 callback' })
@SkipUserGuard()
@UseGuards(FtGuard) // strategy.validate() -> return 값 기반으로 request 객체 담아줌
@Get('callback/ft')
@Get('42login/callback')
async callbackLogin(@ExtractUser() user: LoginInfo, @Res() res: Response): Promise<void> {
const responseOptions: SocialResponseOptions = await this.authService.socialAuth(user);
// 또는 @ReqUser('email') email: string console.log('42 Login Callback!');
const clientUrl = this.appConfigService.clientUrl;

if (responseOptions.cookieKey !== undefined) {
res.cookie(responseOptions.cookieKey, responseOptions.token, COOKIE_OPTIONS);
if (user.id === null) {
// UNREGSIETERED -> JOIN (sign up)
const token = await this.authService.signUp(user);
res.cookie('jwt-for-unregistered', token, COOKIE_OPTIONS).redirect(`${clientUrl}/auth/register`);
} else {
// REGISTERED -> LOGIN (sign in)
const { twoFa } = await this.authService.getTwoFactorAuth(user.id);
if (twoFa !== null) {
const token = await this.authService.sendAuthCode(user.id, twoFa);
res.cookie('jwt-for-2fa', token, COOKIE_OPTIONS).redirect(`${clientUrl}/auth/2fa`);
} else {
const token = await this.authService.signIn(user.id);
res.redirect(`${clientUrl}/auth?token=${token}`);
}
}
res.redirect(responseOptions.redirectUrl);
}

@ApiOperation({ summary: 'google 로그인' })
@SkipUserGuard()
@UseGuards(GoogleGuard)
@Get('login/google')
async googleLogin(): Promise<void> {
return;
}

@ApiOperation({ summary: 'google 로그인 callback' })
@SkipUserGuard()
@UseGuards(GoogleGuard)
@Get('callback/google')
async googleCallbackLogin(@ExtractUser() user: LoginInfo, @Res() res: Response): Promise<void> {
return this.callbackLogin(user, res);
}

/**
* @summary 로그인 2단계 인증
* @description POST /auth/login/2fa
*
* 로그인 시 2fa 설정 되어있는 경우 맞는 인증 코드인지 확인한다.
* @description POST /auth/42login/2fa
*/
@ApiOperation({ summary: '로그인 2단계 인증' })
@ApiOperation({ summary: '42 로그인 2단계 인증' })
@ApiHeaders([{ name: 'x-my-id', description: '내 아이디 (임시값)' }])
@ApiForbiddenResponse({ type: ErrorResponseDto, description: '유효하지 않은 인증 코드' })
@ApiBadRequestResponse({ type: ErrorResponseDto, description: '잘못된 인증 코드' })
@SkipUserGuard()
@UseGuards(TwoFaGuard)
@HttpCode(HttpStatus.OK)
@Post('login/2fa')
@Post('42login/2fa')
async twoFactorAuthLogin(
@ExtractUserId() myId: number,
@Body() { code }: CodeVerificationRequestDto,
Expand All @@ -107,6 +101,7 @@ export class AuthController {
@ApiOperation({ summary: '2단계 인증 이메일 가져오기' })
@ApiNotFoundResponse({ type: ErrorResponseDto, description: '유저 없음' })
@ApiHeaders([{ name: 'x-my-id', description: '내 아이디 (임시값)' }])
@UseGuards(UserGuard)
@Get('2fa')
getTwoFactorAuth(@ExtractUserId() myId: number): Promise<TwoFactorAuthResponseDto> {
return this.authService.getTwoFactorAuth(myId);
Expand All @@ -121,6 +116,7 @@ export class AuthController {
@ApiConflictResponse({ type: ErrorResponseDto, description: '중복된 이메일 혹은 이미 인증 완료한 유저' })
@ApiHeaders([{ name: 'x-my-id', description: '내 아이디 (임시값)' }])
@HttpCode(HttpStatus.OK)
@UseGuards(UserGuard)
@Post('2fa')
async twoFactorAuth(
@ExtractUserId() myId: number,
Expand All @@ -142,6 +138,7 @@ export class AuthController {
@ApiBadRequestResponse({ type: ErrorResponseDto, description: '잘못된 2단계 인증 코드' })
@ApiHeaders([{ name: 'x-my-id', description: '내 아이디 (임시값)' }])
@HttpCode(HttpStatus.OK)
@UseGuards(UserGuard)
@Post('2fa/verify')
async updateTwoFactorAuth(
@ExtractUserId() myId: number,
Expand All @@ -159,6 +156,7 @@ export class AuthController {
@ApiOperation({ summary: '2단계 인증 삭제하기' })
@ApiConflictResponse({ type: ErrorResponseDto, description: '2단계 인증하지 않은 유저' })
@ApiHeaders([{ name: 'x-my-id', description: '내 아이디 (임시값)' }])
@UseGuards(UserGuard)
@Delete('2fa')
deleteTwoFactorAuth(@ExtractUserId() myId: number): Promise<SuccessResponseDto> {
return this.authService.deleteTwoFactorAuth(myId);
Expand Down
5 changes: 1 addition & 4 deletions backend/src/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { MailerModule } from '@nestjs-modules/mailer';
import { TWO_FA_EXPIRES_IN, TWO_FA_MAX } from '../common/constant';
import { AppConfigModule } from '../config/app/configuration.module';
import { FtAuthConfigModule } from '../config/auth/ft/configuration.module';
import { GoogleAuthConfigModule } from '../config/auth/google/configuration.module';
import { JwtConfigModule } from '../config/auth/jwt/configuration.module';
import { MailerConfigModule } from '../config/auth/mailer/configuration.module';
import { MailerConfigService } from '../config/auth/mailer/configuration.service';
Expand All @@ -17,15 +16,13 @@ import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { UserGuard } from './guard/user.guard';
import { FtStrategy } from './strategy/ft.strategy';
import { GoogleStrategy } from './strategy/google.strategy';
import { UserStrategy } from './strategy/user.strategy';

@Module({
imports: [
TypeOrmModule.forFeature([Auth]),
JwtModule.register({}),
FtAuthConfigModule,
GoogleAuthConfigModule,
JwtConfigModule,
MailerConfigModule,
AppConfigModule,
Expand All @@ -40,7 +37,7 @@ import { UserStrategy } from './strategy/user.strategy';
inject: [MailerConfigService],
}),
],
providers: [AuthService, FtStrategy, UserStrategy, GoogleStrategy, UserGuard],
providers: [AuthService, FtStrategy, UserStrategy, UserGuard],
controllers: [AuthController],
exports: [AuthService],
})
Expand Down
31 changes: 5 additions & 26 deletions backend/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,11 @@ import { Repository } from 'typeorm';

import { AUTH_JWT_EXPIRES_IN, TWO_FA_EXPIRES_IN, TWO_FA_JWT_EXPIRES_IN, USER_JWT_EXPIRES_IN } from '../common/constant';
import { SuccessResponseDto } from '../common/dto/success-response.dto';
import { AppConfigService } from '../config/app/configuration.service';
import { JwtConfigService } from '../config/auth/jwt/configuration.service';
import { Auth } from '../entity/auth.entity';

import { TwoFactorAuthResponseDto } from './dto/response/two-factor-auth-response.dto';
import { LoginInfo } from './type/login-info';
import { SocialResponseOptions } from './type/social-response-options';
import { TwoFactorAuth } from './type/two-factor-auth';

@Injectable()
Expand All @@ -24,7 +22,6 @@ export class AuthService {
private readonly authRepository: Repository<Auth>,
private readonly jwtService: JwtService,
private readonly jwtConfigService: JwtConfigService,
private readonly appConfigService: AppConfigService,
@Inject(CACHE_MANAGER) private cacheManager: Cache,
private readonly mailerService: MailerService,
) {}
Expand All @@ -45,7 +42,11 @@ export class AuthService {
return this.jwtService.sign(payload, signOptions);
}

signIn(userId: number): string {
async signIn(userId: number): Promise<string> {
// CHECK 필요 없을 거 같음 (무조건 user table에 있는 경우에 signIn이 실행됨)
// if ((await this.userRepository.findOneBy({ id: userId })) === null) {
// throw new NotFoundException('[Login Error] 존재하지 않는 유저입니다.');
// }
const payload = { userId };
const signOptions = {
secret: this.jwtConfigService.userSecretKey,
Expand All @@ -54,28 +55,6 @@ export class AuthService {
return this.jwtService.sign(payload, signOptions);
}

async socialAuth(user: LoginInfo): Promise<SocialResponseOptions> {
const auth = await this.authRepository.findOneBy({ email: user.email });
let token = '';
const clientUrl = this.appConfigService.clientUrl;

if (auth === null) {
// unregistered user
token = await this.signUp(user);
return { cookieKey: 'jwt-for-unregistered', token, redirectUrl: `${clientUrl}/auth/register` };
} else {
const userId = auth.id;
const { twoFa } = await this.getTwoFactorAuth(userId);
if (twoFa === null) {
token = this.signIn(userId);
return { token, redirectUrl: `${clientUrl}/auth?token=${token}` };
} else {
token = await this.sendAuthCode(userId, twoFa);
}
return { cookieKey: 'jwt-for-2fa', token, redirectUrl: `${clientUrl}/auth/2fa` };
}
}

async twoFactorAuthSignIn(myId: number, code: string): Promise<string> {
const value: TwoFactorAuth | undefined = await this.cacheManager.get(`${myId}`);
if (value === undefined) {
Expand Down
25 changes: 23 additions & 2 deletions backend/src/auth/guard/ft.guard.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
import { Injectable } from '@nestjs/common';
import { Injectable, CanActivate, ExecutionContext, InternalServerErrorException, Logger } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { Observable } from 'rxjs';

@Injectable()
export class FtGuard extends AuthGuard('ft') {}
export class FtGuard extends AuthGuard('ft') implements CanActivate {
constructor() {
super();
}
logger: Logger = new Logger('FtGuard');

canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
const maxRetries = 5;

for (let retry = 0; retry < maxRetries; ++retry) {
try {
this.logger.log('FtGuard canActivate() try:', retry);
return super.canActivate(context);
} catch (err) {
// do nothing
this.logger.error('FtGuard canActivate() error:', err);
}
}
throw new InternalServerErrorException('Max retries exceeded. Unable to activate guard.');
}
}
5 changes: 0 additions & 5 deletions backend/src/auth/guard/google.guard.ts

This file was deleted.

4 changes: 2 additions & 2 deletions backend/src/auth/guard/user.guard.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable, ExecutionContext, BadRequestException } from '@nestjs/common';
import { Injectable, CanActivate, ExecutionContext, BadRequestException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { Request } from 'express';
Expand All @@ -7,7 +7,7 @@ import { Observable } from 'rxjs';
import { AppConfigService } from '../../config/app/configuration.service';

@Injectable()
export class UserGuard extends AuthGuard('user') {
export class UserGuard extends AuthGuard('user') implements CanActivate {
constructor(private reflector: Reflector, private readonly appConfigService: AppConfigService) {
super();
}
Expand Down
25 changes: 19 additions & 6 deletions backend/src/auth/strategy/ft.strategy.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { InjectRepository } from '@nestjs/typeorm';
import { Strategy } from 'passport-42';
import { Repository } from 'typeorm';

import { FtAuthConfigService } from '../../config/auth/ft/configuration.service';
import { Auth, AuthStatus } from '../../entity/auth.entity';
import { LoginInfo } from '../type/login-info';

@Injectable()
export class FtStrategy extends PassportStrategy(Strategy, 'ft') {
constructor(private readonly ftAuthConfigService: FtAuthConfigService) {
constructor(
private readonly ftAuthConfigService: FtAuthConfigService,
@InjectRepository(Auth)
private readonly authRepository: Repository<Auth>,
) {
super({
clientID: ftAuthConfigService.id,
clientSecret: ftAuthConfigService.secret,
Expand All @@ -27,10 +34,16 @@ export class FtStrategy extends PassportStrategy(Strategy, 'ft') {
* @param cb validate()에서 return한 값이 request 객체에 담김
* @returns validate()에서 return한 값
*/
async validate(accessToken: string, refreshToken: string, profile: LoginInfo): Promise<LoginInfo> {
if (profile.email === undefined || profile.email === null) {
throw new UnauthorizedException('42 email is empty');
async validate(accessToken: string, refreshToken: string, profile: LoginInfo) {
const auth = await this.authRepository.findOneBy({ email: profile.email });

if (auth === null || auth.status === AuthStatus.UNREGISTERD) {
profile.id = null;
} else {
// auth.status === REGISTERD 이고, user table에 존재하는 경우
profile.id = auth.id;
}
return { provider: '42', email: profile.email, id: null };

return profile;
}
}
26 changes: 0 additions & 26 deletions backend/src/auth/strategy/google.strategy.ts

This file was deleted.

Loading

0 comments on commit c4484d8

Please sign in to comment.