Skip to content

Commit

Permalink
merge main into yubin
Browse files Browse the repository at this point in the history
  • Loading branch information
yubinquitous committed May 6, 2024
2 parents 4b244da + c000c38 commit 4bab4ef
Show file tree
Hide file tree
Showing 80 changed files with 4,712 additions and 2,325 deletions.
4,941 changes: 3,437 additions & 1,504 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
"test:e2e": "NODE_ENV=test jest --watch --no-cache --config ./test/jest-e2e.json"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.554.0",
"@aws-sdk/s3-request-presigner": "^3.554.0",
"@liaoliaots/nestjs-redis": "^9.0.5",
"@nestjs/cache-manager": "^2.1.1",
"@nestjs/common": "^9.4.3",
Expand Down
13 changes: 10 additions & 3 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import typeOrmConfig from './config/typeorm.config';
import { GameModule } from './game/game.module';
import { UsersModule } from './users/users.module';
import { ScheduleModule } from '@nestjs/schedule';
import { UserRepositoryModule } from './user-repository/user-repository.module';
import { FriendsModule } from './friends/friends.module';
import userConfig from './config/user.config';
import googleConfig from './config/google.config';
import s3Config from './config/s3.config';

@Module({
imports: [
Expand All @@ -28,11 +32,12 @@ import userConfig from './config/user.config';
load: [
redisConfig,
ftConfig,
googleConfig,
pgadminConfig,
jwtConfig,
typeOrmConfig,
redisConfig,
userConfig,
s3Config,
],
}),
TypeOrmModule.forRootAsync({
Expand All @@ -42,15 +47,17 @@ import userConfig from './config/user.config';
}),
RedisModule.forRootAsync({
inject: [redisConfig.KEY],
imports: [ConfigModule.forFeature(redisConfig)],
// imports: [ConfigModule.forFeature(redisConfig)],
useFactory: (redisConfigure: ConfigType<typeof redisConfig>) =>
redisConfigure,
}),
ScheduleModule.forRoot(),
UserRepositoryModule,
AuthModule,
UsersModule,
FriendsModule,
ChannelsModule,
GameModule,
UsersModule,
RedisModule,
SwaggerModule,
],
Expand Down
79 changes: 60 additions & 19 deletions src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,25 @@
import {
Body,
Controller,
Logger,
Patch,
Post,
Res,
UseGuards,
} from '@nestjs/common';
import { Body, Controller, Patch, Post, Res, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
import { Response } from 'express';
import { User } from 'src/users/entities/user.entity';
import { SignupRequestDto } from '../users/dto/signup-request.dto';
import { UsersService } from '../users/users.service';
import { User } from 'src/user-repository/entities/user.entity';
import { SignupRequestDto } from './dto/signup-request.dto';
import { AuthService } from './auth.service';
import { SigninMfaRequestDto } from './dto/signin-mfa-request.dto';
import { UserSigninResponseDto } from './dto/user-signin-response.dto';
import { FtAuthService } from './ft-auth.service';
import { GetUser } from './get-user.decorator';
import { UsersRepository } from '../user-repository/users.repository';
import { GoogleAuthService } from './google-auth.service';

@Controller('auth')
@ApiTags('auth')
export class AuthController {
constructor(
private readonly authService: AuthService,
private readonly ftAuthService: FtAuthService,
private readonly usersService: UsersService,
private readonly googleAuthService: GoogleAuthService,
private readonly usersRepository: UsersRepository,
) {}

@Post('/signin')
Expand Down Expand Up @@ -53,14 +47,48 @@ export class AuthController {
// token을 쿠키에 저장한다.
res.cookie('accessToken', jwtAccessToken, {
// httpOnly: true, // 자동로그인을 위해 httpOnly를 false로 설정
secure: true,
sameSite: 'none',
// secure: true,
// sameSite: 'none',
expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 1),
});
}

const userSigninResponseDto: UserSigninResponseDto = {
userId: user.id,
nickname: user.nickname,
isFirstLogin: user.nickname === null,
isMfaEnabled: user.isMfaEnabled,
mfaUrl,
};

return res.send(userSigninResponseDto);
}

@Post('/signin-google')
async signinGoogle(@Body('code') code: any, @Res() res: Response) {
console.log('google code: ', code);

const accessToken = await this.googleAuthService.getAccessToken(code);
const userData = await this.googleAuthService.getUserData(accessToken);
const { user, mfaUrl } = await this.authService.findOrCreateUser(
userData,
);

const jwtAccessToken = await this.authService.generateJwtToken(user);

if (user.isMfaEnabled == false) {
// token을 쿠키에 저장한다.
res.cookie('accessToken', jwtAccessToken, {
// httpOnly: true, // 자동로그인을 위해 httpOnly를 false로 설정
// secure: true,
// sameSite: 'none',
expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 1),
});
}

const userSigninResponseDto: UserSigninResponseDto = {
userId: user.id,
nickname: user.nickname,
isFirstLogin: user.nickname === null,
isMfaEnabled: user.isMfaEnabled,
mfaUrl,
Expand All @@ -83,7 +111,13 @@ export class AuthController {
const nickname = signupRequestDto.nickname;
const avatar = signupRequestDto.avatar;

await this.usersService.signup(user.id, nickname, avatar);
const preSignedUrl = await this.authService.signup(
user.id,
nickname,
avatar,
);

return { preSignedUrl: preSignedUrl };
}

// TODO: 테스트용 코드. 추후 삭제
Expand All @@ -100,7 +134,9 @@ export class AuthController {
@ApiResponse({ status: 201, description: '토큰 발급 성공' })
async testSignIn(@Body('nickname') nickname: string, @Res() res: Response) {
// 이미 존재하는 유저인지 확인한다.
const existUser = await this.usersService.findUserByNickname(nickname);
const existUser = await this.usersRepository.findUserByNickname(
nickname,
);
// 이미 존재하는 유저라면 토큰을 발급한다.
if (existUser) {
const jwtAccessToken = await this.authService.generateJwtToken(
Expand All @@ -123,6 +159,7 @@ export class AuthController {

const userSigninResponseDto: UserSigninResponseDto = {
userId: existUser.id,
nickname: existUser.nickname,
isFirstLogin: false,
isMfaEnabled: false,
mfaUrl: undefined,
Expand All @@ -131,7 +168,10 @@ export class AuthController {
return res.send(userSigninResponseDto);
}

const user = await this.usersService.createUser(nickname, 'test@test');
const user = await this.usersRepository.createUser(
nickname,
'test@test',
);

const jwtAccessToken = await this.authService.generateJwtToken(user);

Expand All @@ -152,6 +192,7 @@ export class AuthController {

const userSigninResponseDto: UserSigninResponseDto = {
userId: user.id,
nickname: user.nickname,
isFirstLogin: false,
isMfaEnabled: false,
mfaUrl: undefined,
Expand All @@ -168,7 +209,7 @@ export class AuthController {
})
@UseGuards(AuthGuard('access'))
async signout(@GetUser() user: User, @Res() res: Response) {
await this.usersService.signout(user.id);
await this.authService.signout(user.id);

res.clearCookie('accessToken');
res.clearCookie('refreshToken');
Expand Down
8 changes: 5 additions & 3 deletions src/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { AuthService } from './auth.service';
import { FtAuthService } from './ft-auth.service';
import { JwtAccessStrategy } from './jwt-access.strategy';
import { JwtRefreshStrategy } from './jwt-refresh.strategy';
import { UsersModule } from '../users/users.module';
import { UserRepositoryModule } from '../user-repository/user-repository.module';
import { GoogleAuthService } from './google-auth.service';

@Module({
imports: [
Expand All @@ -18,15 +19,16 @@ import { UsersModule } from '../users/users.module';
useFactory: (jwtConfigure: ConfigType<typeof jwtConfig>) =>
jwtConfigure,
}),
UsersModule,
UserRepositoryModule,
],
controllers: [AuthController],
providers: [
AuthService,
FtAuthService,
GoogleAuthService,
JwtAccessStrategy,
JwtRefreshStrategy,
],
exports: [AuthService, JwtAccessStrategy, JwtRefreshStrategy],
exports: [JwtAccessStrategy, JwtRefreshStrategy],
})
export class AuthModule {}
73 changes: 69 additions & 4 deletions src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ import { Socket } from 'socket.io';
import * as speakeasy from 'speakeasy';
import jwtConfig from 'src/config/jwt.config';
import userConfig from 'src/config/user.config';
import { User } from 'src/users/entities/user.entity';
import { UsersRepository } from '../users/users.repository';
import { FtUserParamDto } from './dto/ft-user-param.dto';
import { User } from 'src/user-repository/entities/user.entity';
import { UsersRepository } from '../user-repository/users.repository';
import { OauthUserinfoParamDto } from './dto/oauth-userinfo-param.dto';
import { SigninMfaRequestDto } from './dto/signin-mfa-request.dto';
import { UserFindReturnDto } from './dto/user-find-return.dto';
import { UserStatus } from '../common/enum';
import { DBUpdateFailureException } from '../common/exception/custom-exception';

@Injectable()
export class AuthService {
Expand All @@ -32,6 +34,69 @@ export class AuthService {

private readonly logger = new Logger(AuthService.name);

async signup(userId: number, nickname: string, hasAvatar: boolean) {
const user = await this.validateUserExist(userId);

await this.validateUserAlreadySignUp(user);

await this.validateNickname(nickname);

let ret;
let updateUserDataDto;
if (hasAvatar) {
const { avatar, preSignedUrl } =
await this.usersRepository.getAvatarAndPresignedUrl(nickname);
updateUserDataDto = {
nickname: nickname,
avatar: avatar,
};
ret = preSignedUrl;
} else {
updateUserDataDto = {
nickname: nickname,
};
ret = null;
}

const updateRes = await this.usersRepository.update(userId, {
...updateUserDataDto,
});
if (updateRes.affected !== 1) {
throw DBUpdateFailureException(
`유저 ${userId}의 db 업데이트가 실패했습니다`,
);
}

return ret;
}

async validateUserExist(userId: number) {
const user = await this.usersRepository.findOne({
where: {
id: userId,
},
});

if (!user) {
throw new BadRequestException(
`User with id ${userId} doesn't exist`,
);
}
return user;
}

async validateUserAlreadySignUp(user: User) {
if (user.nickname)
throw new BadRequestException(`${user.id} is already signed up`);
}

async signout(userId: number) {
await this.usersRepository.update(userId, {
refreshToken: null,
status: UserStatus.OFFLINE,
});
}

private async createMfaSecret() {
const secret = speakeasy.generateSecret({
name: this.userConfigure.MFA_SECRET,
Expand All @@ -40,7 +105,7 @@ export class AuthService {
}

async findOrCreateUser(
userData: FtUserParamDto,
userData: OauthUserinfoParamDto,
): Promise<UserFindReturnDto> {
const user = await this.usersRepository.findOne({
where: { email: userData.email },
Expand Down
4 changes: 0 additions & 4 deletions src/auth/dto/ft-user-param.dto.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type FtOauthResponseDto = {
export type OauthResponseDto = {
grant_type: string;
client_id: string;
client_secret: string;
Expand Down
4 changes: 4 additions & 0 deletions src/auth/dto/oauth-userinfo-param.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type OauthUserinfoParamDto = {
email: string;
oauthId: number;
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import { ApiProperty } from '@nestjs/swagger';
export class SignupRequestDto {
@ApiProperty({ description: '아바타' })
@IsNotEmpty()
@IsString()
avatar: string;
avatar: boolean;
@ApiProperty({ description: '닉네임' })
@IsNotEmpty()
@IsString()
Expand Down
2 changes: 1 addition & 1 deletion src/auth/dto/user-find-return.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { User } from 'src/users/entities/user.entity';
import { User } from 'src/user-repository/entities/user.entity';

export type UserFindReturnDto = {
user: User;
Expand Down
1 change: 1 addition & 0 deletions src/auth/dto/user-signin-response.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export type UserSigninResponseDto = {
userId: number;
nickname: string | null;
isFirstLogin: boolean;
isMfaEnabled: boolean;
mfaUrl?: string;
Expand Down
Loading

0 comments on commit 4bab4ef

Please sign in to comment.