Skip to content

Commit

Permalink
Revert "backend/DANG-1268: OAuth 리팩토링"
Browse files Browse the repository at this point in the history
  • Loading branch information
do0ori authored Dec 16, 2024
1 parent ec80861 commit b4b5d8d
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 111 deletions.
109 changes: 57 additions & 52 deletions backend/server/src/auth/auth.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { HttpService } from '@nestjs/axios';
import { NotFoundException, UnauthorizedException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import { Test, TestingModule } from '@nestjs/testing';

import { AuthService } from './auth.service';
import { OauthService } from './oauth/oauth.service.interface';
import { GoogleService } from './oauth/google.service';
import { KakaoService } from './oauth/kakao.service';
import { NaverService } from './oauth/naver.service';
import { AccessTokenPayload, RefreshTokenPayload, TokenService } from './token/token.service';
import { OauthAuthorizeData } from './types/oauth-authorize-data.type';
import { OauthData } from './types/oauth-data.type';
Expand All @@ -13,88 +16,88 @@ import { OAUTH_PROVIDERS } from './types/oauth-provider.type';
import { WinstonLoggerService } from '../common/logger/winstonLogger.service';
import { DogsService } from '../dogs/dogs.service';
import { mockUser } from '../fixtures/users.fixture';
import { S3Service } from '../s3/s3.service';
import { Users } from '../users/users.entity';
import { UsersRepository } from '../users/users.repository';
import { UsersService } from '../users/users.service';

describe('AuthService', () => {
let service: AuthService;
let usersService: UsersService;
let tokenService: TokenService;
let mockOauthServices: Map<string, OauthService>;
let googleService: GoogleService;
let kakaoService: KakaoService;
let naverService: NaverService;

beforeEach(async () => {
mockOauthServices = new Map<string, OauthService>();

const mockTokenResponse = {
access_token: mockUser.oauthAccessToken,
expires_in: 3600,
refresh_token: mockUser.oauthRefreshToken,
refresh_token_expires_in: 3600,
scope: 'scope',
token_type: 'bearer',
};

const mockUserInfo = {
oauthId: mockUser.oauthId,
oauthNickname: 'test',
email: 'test@mail.com',
profileImageUrl: 'test.jpg',
};

OAUTH_PROVIDERS.forEach((provider) => {
const mockOauthService = {
requestToken: jest.fn().mockResolvedValue(mockTokenResponse),
requestUserInfo: jest.fn().mockResolvedValue(mockUserInfo),
requestTokenExpiration: jest.fn().mockResolvedValue(undefined),
requestTokenRefresh: jest.fn().mockResolvedValue(mockTokenResponse),
requestUnlink: provider === 'kakao' ? jest.fn().mockResolvedValue(undefined) : undefined,
};
mockOauthServices.set(provider, mockOauthService as OauthService);
});

const module: TestingModule = await Test.createTestingModule({
providers: [
AuthService,
TokenService,
{
provide: UsersService,
useValue: {
updateAndFindOne: jest.fn(),
createIfNotExists: jest.fn(),
findOne: jest.fn(),
delete: jest.fn(),
update: jest.fn(),
},
provide: S3Service,
useValue: { deleteObjectFolder: jest.fn() },
},
{
provide: DogsService,
useValue: {
deleteOwnDogs: jest.fn(),
},
provide: UsersService,
useValue: { updateAndFindOne: jest.fn(), createIfNotExists: jest.fn(), findOne: jest.fn() },
},
{
provide: 'OAUTH_SERVICES',
useValue: mockOauthServices,
provide: DogsService,
useValue: { deleteDogFromUser: jest.fn() },
},
ConfigService,
{ provide: UsersRepository, useValue: {} },
TokenService,
JwtService,
{ provide: HttpService, useValue: {} },
GoogleService,
KakaoService,
NaverService,
ConfigService,
WinstonLoggerService,
],
}).compile();

service = module.get<AuthService>(AuthService);
usersService = module.get<UsersService>(UsersService);
tokenService = module.get<TokenService>(TokenService);
googleService = module.get<GoogleService>(GoogleService);
kakaoService = module.get<KakaoService>(KakaoService);
naverService = module.get<NaverService>(NaverService);

const oauthServiceList = [googleService, kakaoService, naverService];

const mockTokenResponse = {
access_token: mockUser.oauthAccessToken,
expires_in: 3600,
refresh_token: mockUser.oauthRefreshToken,
refresh_token_expires_in: 3600,
scope: 'scope',
token_type: 'bearer',
};

for (const oauthService of oauthServiceList) {
jest.spyOn(oauthService, 'requestToken').mockResolvedValue(mockTokenResponse);
jest.spyOn(oauthService, 'requestUserInfo').mockResolvedValue({
oauthId: mockUser.oauthId,
oauthNickname: 'test',
email: 'test@mail.com',
profileImageUrl: 'test.jpg',
});
jest.spyOn(oauthService, 'requestTokenExpiration').mockResolvedValue();
jest.spyOn(oauthService, 'requestTokenRefresh').mockResolvedValue(mockTokenResponse);
}

jest.spyOn(tokenService, 'signRefreshToken').mockResolvedValue(mockUser.refreshToken);
jest.spyOn(tokenService, 'signAccessToken').mockResolvedValue(mockUser.refreshToken);
jest.spyOn(kakaoService, 'requestUnlink').mockResolvedValue();
jest.spyOn(tokenService, 'signRefreshToken').mockResolvedValue(Promise.resolve(mockUser.refreshToken));
jest.spyOn(tokenService, 'signAccessToken').mockResolvedValue(Promise.resolve(mockUser.refreshToken));
});

const authorizeCode = 'authorizeCode';

describe('login', () => {
context('사용자가 존재하면', () => {
for (const provider of OAUTH_PROVIDERS) {
for (let i = 0; i < 3; i++) {
const provider = OAUTH_PROVIDERS[i];
it(`${provider} 로그인 후 access token과 refresh token을 반환해야 한다.`, async () => {
jest.spyOn(usersService, 'updateAndFindOne').mockResolvedValue({ id: 1 } as Users);

Expand All @@ -109,7 +112,8 @@ describe('AuthService', () => {
});

context('사용자가 존재하지 않으면', () => {
for (const provider of OAUTH_PROVIDERS) {
for (let i = 0; i < 3; i++) {
const provider = OAUTH_PROVIDERS[i];
it(`${provider} 로그인 후 oauth data를 반환해야 한다.`, async () => {
jest.spyOn(usersService, 'updateAndFindOne').mockRejectedValue(new NotFoundException());

Expand All @@ -127,7 +131,8 @@ describe('AuthService', () => {

describe('signup', () => {
context('사용자가 존재하지 않으면', () => {
for (const provider of OAUTH_PROVIDERS) {
for (let i = 0; i < 3; i++) {
const provider = OAUTH_PROVIDERS[i];
it(`${provider} 회원가입 후 access token과 refresh token을 반환해야 한다.`, async () => {
jest.spyOn(usersService, 'createIfNotExists').mockResolvedValue({ id: 1 } as Users);

Expand Down
47 changes: 17 additions & 30 deletions backend/server/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { Inject, Injectable, NotFoundException, UnauthorizedException } from '@nestjs/common';
import { Injectable, NotFoundException, UnauthorizedException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Transactional } from 'typeorm-transactional';

import { GoogleService } from './oauth/google.service';
import { KakaoService } from './oauth/kakao.service';
import { OauthService } from './oauth/oauth.service.interface';
import { NaverService } from './oauth/naver.service';
import { AccessTokenPayload, RefreshTokenPayload, TokenService } from './token/token.service';
import { AuthData } from './types/auth-data.type';
import { OauthAuthorizeData } from './types/oauth-authorize-data.type';
import { OauthData } from './types/oauth-data.type';

import { WinstonLoggerService } from '../common/logger/winstonLogger.service';
import { DogsService } from '../dogs/dogs.service';
import { S3Service } from '../s3/s3.service';
import { Users } from '../users/users.entity';
import { UsersService } from '../users/users.service';

Expand All @@ -20,30 +22,23 @@ export class AuthService {
private readonly tokenService: TokenService,
private readonly usersService: UsersService,
private readonly dogsService: DogsService,
@Inject('OAUTH_SERVICES')
private readonly oauthServices: Map<string, OauthService>,
private readonly googleService: GoogleService,
private readonly kakaoService: KakaoService,
private readonly naverService: NaverService,
private readonly configService: ConfigService,
private readonly s3Service: S3Service,
private readonly logger: WinstonLoggerService,
) {}

private readonly REDIRECT_URI = this.configService.get<string>('CORS_ORIGIN') + '/callback';
private readonly S3_PROFILE_IMAGE_PATH = 'default/profile.png';

private getOauthService(provider: string): OauthService {
const oauthService = this.oauthServices.get(provider);
if (!oauthService) throw new Error(`Unknown provider: ${provider}`);
return oauthService;
}

async login({ authorizeCode, provider }: OauthAuthorizeData): Promise<AuthData | OauthData | undefined> {
const oauthService = this.getOauthService(provider);
const { access_token: oauthAccessToken, refresh_token: oauthRefreshToken } = await this[
`${provider}Service`
].requestToken(authorizeCode, this.REDIRECT_URI);

const { access_token: oauthAccessToken, refresh_token: oauthRefreshToken } = await oauthService.requestToken(
authorizeCode,
this.REDIRECT_URI,
);

const { oauthId } = await oauthService.requestUserInfo(oauthAccessToken);
const { oauthId } = await this[`${provider}Service`].requestUserInfo(oauthAccessToken);

const refreshToken = await this.tokenService.signRefreshToken(oauthId, provider);
this.logger.debug('login - signRefreshToken', { refreshToken });
Expand All @@ -68,9 +63,7 @@ export class AuthService {
}

async signup({ oauthAccessToken, oauthRefreshToken, provider }: OauthData): Promise<AuthData> {
const oauthService = this.getOauthService(provider);

const { oauthId, oauthNickname, email } = await oauthService.requestUserInfo(oauthAccessToken);
const { oauthId, oauthNickname, email } = await this[`${provider}Service`].requestUserInfo(oauthAccessToken);
const profileImageUrl = this.S3_PROFILE_IMAGE_PATH;

const refreshToken = await this.tokenService.signRefreshToken(oauthId, provider);
Expand All @@ -93,19 +86,15 @@ export class AuthService {
}

async logout({ userId, provider }: AccessTokenPayload): Promise<void> {
const oauthService = this.getOauthService(provider);

const { oauthAccessToken } = await this.usersService.findOne({ where: { id: userId } });
this.logger.debug('logout - oauthAccessToken', { oauthAccessToken });

if (provider === 'kakao') {
await (oauthService as KakaoService).requestTokenExpiration(oauthAccessToken);
await this.kakaoService.requestTokenExpiration(oauthAccessToken);
}
}

async reissueTokens({ oauthId, provider }: RefreshTokenPayload): Promise<AuthData> {
const oauthService = this.getOauthService(provider);

const { id: userId, oauthRefreshToken } = await this.usersService.findOne({
where: { oauthId },
select: ['id', 'oauthRefreshToken'],
Expand All @@ -116,7 +105,7 @@ export class AuthService {
newAccessToken,
newRefreshToken,
] = await Promise.all([
oauthService.requestTokenRefresh(oauthRefreshToken),
this[`${provider}Service`].requestTokenRefresh(oauthRefreshToken),
this.tokenService.signAccessToken(userId, provider),
this.tokenService.signRefreshToken(oauthId, provider),
]);
Expand All @@ -133,14 +122,12 @@ export class AuthService {
}

async deactivate({ userId, provider }: AccessTokenPayload): Promise<void> {
const oauthService = this.getOauthService(provider);

const { oauthAccessToken } = await this.usersService.findOne({ where: { id: userId } });

if (provider === 'kakao') {
await (oauthService as KakaoService).requestUnlink(oauthAccessToken);
await this.kakaoService.requestUnlink(oauthAccessToken);
} else {
await oauthService.requestTokenExpiration(oauthAccessToken);
await this[`${provider}Service`].requestTokenExpiration(oauthAccessToken);
}

await this.deleteUserData(userId);
Expand Down
29 changes: 3 additions & 26 deletions backend/server/src/auth/oauth/oauth.module.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,13 @@
import { HttpModule } from '@nestjs/axios';
import { Module, Type } from '@nestjs/common';
import { Module } from '@nestjs/common';

import { GoogleService } from './google.service';
import { KakaoService } from './kakao.service';
import { NaverService } from './naver.service';
import { OauthService } from './oauth.service.interface';

export const OAUTH_REGISTRY = new Map<string, Type<OauthService>>([
['google', GoogleService],
['kakao', KakaoService],
['naver', NaverService],
]);

@Module({
imports: [HttpModule],
providers: [
{
provide: 'OAUTH_SERVICES',
useFactory: () => {
const oauthServices = new Map<string, OauthService>();

for (const [provider, ServiceClass] of OAUTH_REGISTRY) {
oauthServices.set(provider, new ServiceClass());
}

return oauthServices;
},
},
GoogleService,
KakaoService,
NaverService,
],
exports: ['OAUTH_SERVICES'],
providers: [GoogleService, KakaoService, NaverService],
exports: [GoogleService, KakaoService, NaverService],
})
export class OauthModule {}
4 changes: 1 addition & 3 deletions backend/server/src/auth/types/oauth-provider.type.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { OAUTH_REGISTRY } from '../oauth/oauth.module';

export const OAUTH_PROVIDERS = [...OAUTH_REGISTRY.keys()] as const;
export const OAUTH_PROVIDERS = ['google', 'kakao', 'naver'] as const;

export type OauthProvider = (typeof OAUTH_PROVIDERS)[number];

0 comments on commit b4b5d8d

Please sign in to comment.