Skip to content

Commit

Permalink
feat : google sign in
Browse files Browse the repository at this point in the history
  • Loading branch information
kangjuhyup committed Dec 23, 2024
1 parent 0d4d0b8 commit 5e32f88
Show file tree
Hide file tree
Showing 13 changed files with 134 additions and 68 deletions.
5 changes: 4 additions & 1 deletion packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@aws-sdk/client-s3": "^3.682.0",
"@aws-sdk/s3-request-presigner": "^3.682.0",
"@imgly/background-removal-node": "^1.4.5",
"@nestjs/axios": "^3.1.3",
"@nestjs/common": "^10.0.0",
"@nestjs/config": "^3.3.0",
"@nestjs/core": "^10.0.0",
Expand All @@ -35,6 +36,7 @@
"class-validator": "^0.14.1",
"dotenv": "^16.4.5",
"ioredis": "^5.4.1",
"jsonwebtoken": "^9.0.2",
"mysql2": "^3.11.3",
"node-imap": "^0.9.6",
"passport": "^0.7.0",
Expand All @@ -52,6 +54,7 @@
"@nestjs/testing": "^10.0.0",
"@types/express": "^4.17.17",
"@types/jest": "^29.5.2",
"@types/jsonwebtoken": "^9",
"@types/multer": "^1.4.12",
"@types/node": "^20.3.1",
"@types/node-imap": "^0",
Expand All @@ -62,7 +65,7 @@
"@types/uuid": "^10",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"axios": "^1.7.7",
"axios": "^1.7.9",
"eslint": "^8.42.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const modules = [
const errors = validateSync(validatedConfig);
if (errors.length > 0) {
errors.map((err) => {
console.error(err);
console.error('필수 환경 변수가 없습니다 :', err.constraints);
});
}

Expand Down
25 changes: 23 additions & 2 deletions packages/server/src/domain/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export class AuthController {
@Body() dto: SignRequest,
@Res({ passthrough: true }) res: Response,
) {
const data = await this.auth.signUp(dto.phone, dto.password);
const data = await this.auth.signUp(dto.email, dto.password);
res.cookie('accessToken', data.access, {
httpOnly: true,
});
Expand All @@ -57,7 +57,28 @@ export class AuthController {
@Body() dto: SignRequest,
@Res({ passthrough: true }) res: Response,
) {
const data = await this.auth.signIn(dto.phone, dto.password);
const data = await this.auth.signIn(dto.email, dto.password);
res.cookie('accessToken', data.access, {
httpOnly: true,
});
res.cookie('refreshToken', data.refresh, { httpOnly: true });
return {
result: true,
data,
};
}

@ApiOperation({
summary : '구글 이메일 로그인',
description : '기존 회원이 아닐 경우 회원 가입 처리'
})
@Post('signin/google')
@UseGuards()
async signInWithGoogle(
@Body() body : {code : string},
@Res({ passthrough: true }) res: Response,
) {
const data = await this.auth.signInWithGoogle(body.code)
res.cookie('accessToken', data.access, {
httpOnly: true,
});
Expand Down
14 changes: 10 additions & 4 deletions packages/server/src/domain/auth/auth.facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { AuthService } from './service/auth.service';
import { MailService } from './service/mail.service';
import { SessionService } from './service/session.service';
import { UserService } from '../user/user.service';
import { randomString } from '@app/util/random';
import { GoogleService } from '../google/google.service';

@Injectable()
export class AuthFacade {
Expand All @@ -17,6 +19,7 @@ export class AuthFacade {
private readonly authService: AuthService,
private readonly mailService: MailService,
private readonly sessionService: SessionService,
private readonly googleService: GoogleService,
) {}

async prepareSignUp(phone: string) {
Expand All @@ -38,10 +41,10 @@ export class AuthFacade {
return await this.authService.resign(refreshToken);
}

async signIn(phone: string, pwd: string) {
async signIn(email: string, pwd: string) {
//TODO: 이미 로그인되었을 경우 기존 로그인 세션 제거
const { userId, access, refresh } = await this.authService.signIn(
phone,
email,
pwd,
);
await this.sessionService.setSignInSession(userId, access, refresh);
Expand All @@ -51,11 +54,14 @@ export class AuthFacade {
};
}

async signUpWithGoogle(mail: string) {
const user = await this.userService.getGoogleUser(mail).catch((err) => {
async signInWithGoogle(authCode: string) {
const email = await this.googleService.getEmailFromCode(authCode);
const user = await this.userService.getUser({email}).catch( async (err) => {
if (err instanceof UnauthorizedException) {
return await this.userService.saveUser(email,randomString(10))
}
throw err;
});
return this.signIn(user.email,user.password)
}
}
31 changes: 0 additions & 31 deletions packages/server/src/domain/auth/auth.google.controller.ts

This file was deleted.

3 changes: 1 addition & 2 deletions packages/server/src/domain/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { UserRefreshStrategy } from '@app/jwt/strategy/user.refresh.strategy';
import { AuthFacade } from './auth.facade';
import { MailService } from './service/mail.service';
import { SessionService } from './service/session.service';
import { AuthGoogleController } from './auth.google.controller';
import { GoogleStrategy } from '@app/jwt/strategy/google.strategy';

interface AuthModuleAsyncOptions {
Expand Down Expand Up @@ -42,7 +41,7 @@ export class AuthModule {
inject: options.inject,
}),
],
controllers: [AuthController, AuthGoogleController],
controllers: [AuthController],
providers: [
{
provide: UserAccessStrategy,
Expand Down
14 changes: 4 additions & 10 deletions packages/server/src/domain/auth/dto/sign.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsNotEmpty, IsPhoneNumber, IsString } from 'class-validator';
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';

export class SignRequest {
@ApiProperty({ description: '휴대폰 번호', example: '01012341234' })
@ApiProperty({ description: '이메일 주소', example: '[email protected]' })
@IsNotEmpty()
@IsPhoneNumber('KR')
phone: string;
@IsEmail()
email: string;

@ApiProperty({ description: '패스워드' })
@IsNotEmpty()
@IsString()
password: string;
}

export class GoogleSignRequest extends SignRequest{
@IsNotEmpty()
@IsEmail()
mail : string;
}
8 changes: 4 additions & 4 deletions packages/server/src/domain/auth/service/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ export class AuthService {
private readonly jwtService: JwtService,
) {}

async signIn(phone: string, pwd: string) {
const { userId, password } = await this.userService.getUser({ phone });
async signIn(email: string, pwd: string) {
const { userId, password } = await this.userService.getUser({ email });
if (password !== pwd)
throw new UnauthorizedException('잘못된 비밀번호 입니다.');
const refresh = this._generateRefreshToken(userId);
Expand All @@ -24,8 +24,8 @@ export class AuthService {
};
}

async signUp(phone: string, pwd: string) {
const user = await this.userService.saveUser(phone, pwd);
async signUp(email: string, pwd: string) {
const user = await this.userService.saveUser(email, pwd);
const access = this._generateAccessToken(user.userId);
const refresh = this._generateRefreshToken(user.userId);
await this.userService.updateRefresh(user.userId, refresh);
Expand Down
12 changes: 12 additions & 0 deletions packages/server/src/domain/dto/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,16 @@ export class Enviroments {
@IsNotEmpty()
@IsString()
JWT_REFRESH_EXPIRES: string;

@IsNotEmpty()
@IsString()
GOOGLE_CALLBACK_URL : string;

@IsNotEmpty()
@IsString()
GOOGLE_CLIENT_ID : string;

@IsNotEmpty()
@IsString()
GOOGLE_CLIENT_SECRET : string;
}
49 changes: 49 additions & 0 deletions packages/server/src/domain/google/google.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { HttpService } from "@nestjs/axios";
import { Injectable, UnauthorizedException } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { verify } from 'jsonwebtoken'
import { firstValueFrom } from "rxjs";
@Injectable()
export class GoogleService {

private GOOGLE_OAUTH_URL = 'https://oauth2.googleapis.com/token'
private GOOGLE_CLIENT_ID : string;
private GOOGLE_CLIENT_SECRET : string;
private GOOGLE_CALLBACK_URL : string;

constructor(
private readonly http : HttpService,
private readonly config: ConfigService
) {
this.GOOGLE_CLIENT_ID = config.get<string>('GOOGLE_CLIENT_ID');
this.GOOGLE_CLIENT_SECRET = config.get<string>('GOOGLE_CLIENT_SECRET')
this.GOOGLE_CALLBACK_URL = config.get<string>('GOOGLE_CALLBACK_URL')
}

/**
* 구글 OAuth 로 authCode 를 보내 idToken 획득 후 email 추출
* @param code
* @returns
*/
async getEmailFromCode(code: string) : Promise<string> {
if(!code) throw new UnauthorizedException('AuhorizationCode required')
const { data } = await firstValueFrom(this.http.post<{
id_token : string,
}>(this.GOOGLE_OAUTH_URL,{
code,
client_id : this.GOOGLE_CLIENT_ID,
client_secret : this.GOOGLE_CLIENT_SECRET,
redirect_uri : this.GOOGLE_CALLBACK_URL,
grant_type : 'authorization_code'
}))
const { id_token : idToken } = data;
const decoded = verify(idToken, '', { complete: true }) as {
payload: { email?: string };
};
const email = decoded?.payload?.email;
if (!email) {
throw new Error('Email not found in id_token');
}
return email;
}
}
6 changes: 0 additions & 6 deletions packages/server/src/domain/user/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,6 @@ export class UserService {
return user;
}

async getGoogleUser(email: string): Promise<UserEntity> {
const user = await this.userRepository.selectUserFromEmail({ email });
if (!user) throw new UnauthorizedException('존재하지 않는 회원입니다.');
return user;
}

async validateRefresh(userId: string, refreshToken: string) {
const user = await this.getUser({ userId });
if (user.refreshToken !== refreshToken)
Expand Down
5 changes: 5 additions & 0 deletions packages/server/src/util/random.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/**
* 랜덤한 문자열을 반환한다. ( 기본 길이 10 )
* @param length
* @returns string
*/
export const randomString = (length: number = 10): string => {
const chars =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
Expand Down
Loading

0 comments on commit 5e32f88

Please sign in to comment.