Skip to content

Commit

Permalink
Feature/#230 - 테스터 유저 기능 구현 (#238)
Browse files Browse the repository at this point in the history
  • Loading branch information
xjfcnfw3 authored Nov 25, 2024
2 parents 7cc8591 + 22d60c6 commit cb22ad5
Show file tree
Hide file tree
Showing 9 changed files with 175 additions and 16 deletions.
2 changes: 2 additions & 0 deletions packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"nest-winston": "^1.9.7",
"passport": "^0.7.0",
"passport-google-oauth20": "^2.0.0",
"passport-local": "^1.0.0",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1",
"socket.io": "^4.8.1",
Expand All @@ -58,6 +59,7 @@
"@types/jest": "^29.5.2",
"@types/node": "^20.3.1",
"@types/passport-google-oauth20": "^2.0.16",
"@types/passport-local": "^1.0.38",
"@types/supertest": "^6.0.0",
"@types/unzipper": "^0.10.10",
"@types/ws": "^8.5.13",
Expand Down
16 changes: 13 additions & 3 deletions packages/backend/src/auth/auth.module.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { GoogleAuthController } from '@/auth/google/googleAuth.controller';
import { GoogleAuthService } from '@/auth/google/googleAuth.service';
import { GoogleStrategy } from '@/auth/google/strategy/google.strategy';
import { SessionSerializer } from '@/auth/session/session.serializer';
import { TesterStrategy } from '@/auth/tester/strategy/tester.strategy';
import { TesterAuthController } from '@/auth/tester/testerAuth.controller';
import { TesterAuthService } from '@/auth/tester/testerAuth.service';
import { UserModule } from '@/user/user.module';

@Module({
imports: [UserModule],
controllers: [GoogleAuthController],
providers: [GoogleStrategy, GoogleAuthService, SessionSerializer],
imports: [UserModule, PassportModule.register({ session: true })],
controllers: [GoogleAuthController, TesterAuthController],
providers: [
GoogleStrategy,
GoogleAuthService,
SessionSerializer,
TesterAuthService,
TesterStrategy,
],
})
export class AuthModule {}
5 changes: 2 additions & 3 deletions packages/backend/src/auth/google/strategy/google.strategy.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { Inject, Injectable } from '@nestjs/common';
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Profile, Strategy, VerifyCallback } from 'passport-google-oauth20';
import { GoogleAuthService } from '@/auth/google/googleAuth.service';
import { OauthType } from '@/user/domain/ouathType';
import { Logger } from 'winston';

export interface OauthUserInfo {
type: OauthType;
Expand All @@ -15,7 +14,7 @@ export interface OauthUserInfo {

@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy) {
constructor(private readonly googleAuthService: GoogleAuthService, @Inject('winston') private readonly logger: Logger) {
constructor(private readonly googleAuthService: GoogleAuthService) {
super({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
Expand Down
16 changes: 16 additions & 0 deletions packages/backend/src/auth/tester/guard/tester.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class TestAuthGuard extends AuthGuard('local') {
constructor() {
super();
}

async canActivate(context: ExecutionContext) {
const isActivate = (await super.canActivate(context)) as boolean;
const request = context.switchToHttp().getRequest();
await super.logIn(request);
return isActivate;
}
}
16 changes: 16 additions & 0 deletions packages/backend/src/auth/tester/strategy/tester.strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-local';
import { TesterAuthService } from '@/auth/tester/testerAuth.service';

@Injectable()
export class TesterStrategy extends PassportStrategy(Strategy) {
constructor(private readonly testerAuthService: TesterAuthService) {
super();
}

async validate(username: string, password: string, done: CallableFunction) {
const user = await this.testerAuthService.attemptAuthentication();
done(null, user);
}
}
51 changes: 51 additions & 0 deletions packages/backend/src/auth/tester/testerAuth.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Controller, Get, Req, Res, UseGuards } from '@nestjs/common';
import {
ApiOkResponse,
ApiOperation,
ApiQuery,
ApiTags,
} from '@nestjs/swagger';
import { Request, Response } from 'express';
import { TestAuthGuard } from '@/auth/tester/guard/tester.guard';

@ApiTags('Auth')
@Controller('auth/tester')
export class TesterAuthController {
constructor() {}

@ApiOperation({
summary: '테스터 로그인 api',
description: '테스터로 로그인합니다.',
})
@ApiQuery({
name: 'username',
required: true,
description: '테스터 아이디(값만 넣으면 됨)',
})
@ApiQuery({
name: 'password',
required: true,
description: '테스터 비밀번호(값만 넣으면 됨)',
})
@Get('/login')
@UseGuards(TestAuthGuard)
async handleLogin(@Res() response: Response) {
response.redirect('/');
}

@ApiOperation({
summary: '로그인 상태 확인',
description: '로그인 상태를 확인합니다.',
})
@ApiOkResponse({
description: '로그인된 상태',
example: { message: 'Authenticated' },
})
@Get('/status')
async user(@Req() request: Request) {
if (request.user) {
return { message: 'Authenticated' };
}
return { message: 'Not Authenticated' };
}
}
11 changes: 11 additions & 0 deletions packages/backend/src/auth/tester/testerAuth.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Injectable } from '@nestjs/common';
import { UserService } from '@/user/user.service';

@Injectable()
export class TesterAuthService {
constructor(private readonly userService: UserService) {}

async attemptAuthentication() {
return await this.userService.registerTester();
}
}
50 changes: 40 additions & 10 deletions packages/backend/src/user/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
import { DataSource, EntityManager } from 'typeorm';
import { OauthType } from './domain/ouathType';
import { User } from './domain/user.entity';
import { status, subject } from '@/user/constants/randomNickname';

type RegisterRequest = Required<
Pick<User, 'email' | 'nickname' | 'type' | 'oauthId'>
Expand All @@ -27,22 +28,25 @@ export class UserService {
});
}

async registerTester() {
return await this.dataSource.transaction(async (manager) => {
return await manager.save(User, {
nickname: this.generateRandomNickname(),
email: 'tester@nav',
type: OauthType.LOCAL,
oauthId: String(
(await this.getMaxOauthId(OauthType.LOCAL, manager)) + 1,
),
});
});
}

async findUserByOauthIdAndType(oauthId: string, type: OauthType) {
return await this.dataSource.manager.findOne(User, {
where: { oauthId, type },
});
}

private async validateUserExists(
type: OauthType,
oauthId: string,
manager: EntityManager,
) {
if (await manager.exists(User, { where: { oauthId, type } })) {
throw new BadRequestException('user already exists');
}
}

async updateUserTheme(userId: number, isLight?: boolean): Promise<User> {
return await this.dataSource.transaction(async (manager) => {
if (isLight === undefined) {
Expand Down Expand Up @@ -72,4 +76,30 @@ export class UserService {

return user.isLight;
}

private generateRandomNickname() {
const statusName = status[Math.floor(Math.random() * status.length)];
const subjectName = subject[Math.floor(Math.random() * subject.length)];
return `${statusName}${subjectName}`;
}

private async getMaxOauthId(oauthType: OauthType, manager: EntityManager) {
const result = await manager
.createQueryBuilder(User, 'user')
.select('MAX(user.oauthId)', 'max')
.where('user.type = :oauthType', { oauthType })
.getRawOne();

return result ? Number(result.max) : 1;
}

private async validateUserExists(
type: OauthType,
oauthId: string,
manager: EntityManager,
) {
if (await manager.exists(User, { where: { oauthId, type } })) {
throw new BadRequestException('user already exists');
}
}
}
24 changes: 24 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2156,6 +2156,15 @@
"@types/passport" "*"
"@types/passport-oauth2" "*"

"@types/passport-local@^1.0.38":
version "1.0.38"
resolved "https://registry.yarnpkg.com/@types/passport-local/-/passport-local-1.0.38.tgz#8073758188645dde3515808999b1c218a6fe7141"
integrity sha512-nsrW4A963lYE7lNTv9cr5WmiUD1ibYJvWrpE13oxApFsRt77b0RdtZvKbCdNIY4v/QZ6TRQWaDDEwV1kCTmcXg==
dependencies:
"@types/express" "*"
"@types/passport" "*"
"@types/passport-strategy" "*"

"@types/passport-oauth2@*":
version "1.4.17"
resolved "https://registry.yarnpkg.com/@types/passport-oauth2/-/passport-oauth2-1.4.17.tgz#d5d54339d44f6883d03e69dc0cc0e2114067abb4"
Expand All @@ -2165,6 +2174,14 @@
"@types/oauth" "*"
"@types/passport" "*"

"@types/passport-strategy@*":
version "0.2.38"
resolved "https://registry.yarnpkg.com/@types/passport-strategy/-/passport-strategy-0.2.38.tgz#482abba0b165cd4553ec8b748f30b022bd6c04d3"
integrity sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==
dependencies:
"@types/express" "*"
"@types/passport" "*"

"@types/passport@*":
version "1.0.17"
resolved "https://registry.yarnpkg.com/@types/passport/-/passport-1.0.17.tgz#718a8d1f7000ebcf6bbc0853da1bc8c4bc7ea5e6"
Expand Down Expand Up @@ -7046,6 +7063,13 @@ passport-google-oauth20@^2.0.0:
dependencies:
passport-oauth2 "1.x.x"

passport-local@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/passport-local/-/passport-local-1.0.0.tgz#1fe63268c92e75606626437e3b906662c15ba6ee"
integrity sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==
dependencies:
passport-strategy "1.x.x"

[email protected]:
version "1.8.0"
resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.8.0.tgz#55725771d160f09bbb191828d5e3d559eee079c8"
Expand Down

0 comments on commit cb22ad5

Please sign in to comment.