diff --git a/server/api-server/package.json b/server/api-server/package.json index 8f18dc8..853e677 100644 --- a/server/api-server/package.json +++ b/server/api-server/package.json @@ -22,6 +22,7 @@ }, "dependencies": { "@nestjs/axios": "^3.0.1", + "@nestjs/cache-manager": "^2.1.1", "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.1.1", "@nestjs/core": "^10.0.0", @@ -58,6 +59,7 @@ "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@types/bcrypt": "^5.0.2", + "@types/cookie-parser": "^1.4.6", "@types/express": "^4.17.21", "@types/express-session": "^1.17.10", "@types/ioredis": "^5.0.0", diff --git a/server/api-server/src/auth/auth.controller.ts b/server/api-server/src/auth/auth.controller.ts index 133faeb..caa24c9 100644 --- a/server/api-server/src/auth/auth.controller.ts +++ b/server/api-server/src/auth/auth.controller.ts @@ -7,6 +7,7 @@ import { Session, Post, Body, + HttpException, } from '@nestjs/common'; import { NaverAuthGuard } from './guard/naver-auth.guard'; import { AuthService } from './auth.service'; @@ -36,9 +37,17 @@ export class AuthController { @Req() req, @Res() res: Response, ): Promise { - session.userId = req.user.userId; - - const sessionId = session.id; - res.json({ sessionId }); + session.userId = req.user.userId; + res.redirect(process.env.CLIENT_ORIGIN); + res.send(``); + res.end(); + } + + @Get('sessionId') + async getSessionId(@Session() session: Record) { + if (!session.id) { + throw new HttpException('Unauthorized', 401); + } + return { session: session.id }; } } diff --git a/server/api-server/src/auth/auth.service.ts b/server/api-server/src/auth/auth.service.ts index f1feaf4..20556c0 100644 --- a/server/api-server/src/auth/auth.service.ts +++ b/server/api-server/src/auth/auth.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common'; import { UsersService } from '../users/users.service'; import { CreateUserDto } from '../users/dto/create-user.dto'; import { v4 as uuid } from 'uuid'; + @Injectable() export class AuthService { constructor(private usersService: UsersService) {} diff --git a/server/api-server/src/auth/guard/logged-in.guard.ts b/server/api-server/src/auth/guard/logged-in.guard.ts index d4105ce..3ce358e 100644 --- a/server/api-server/src/auth/guard/logged-in.guard.ts +++ b/server/api-server/src/auth/guard/logged-in.guard.ts @@ -1,25 +1,31 @@ -import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; +import { + CanActivate, + ExecutionContext, + Injectable, + Logger, +} from '@nestjs/common'; import { Request } from 'express'; -import { Observable } from 'rxjs'; + +interface LoggedInSession { + cookie: any; + userId: string | undefined; +} @Injectable() export class LoggedInGuard implements CanActivate { - canActivate( - context: ExecutionContext, - ): boolean | Promise | Observable { + private readonly logger = new Logger(LoggedInGuard.name); + + async canActivate(context: ExecutionContext): Promise { const request = context.switchToHttp().getRequest() as Request; - const sid = this.getSidFromCookie(request.headers.cookie); - // TODO token이 유효한지 확인하는 로직이 필요함 - return sid !== null; - } + const session = request.session as unknown as LoggedInSession; + const isAuthenticated = session?.userId ? true : false; - getSidFromCookie(cookieString) { - const cookies = cookieString.split('; ').reduce((acc, cookie) => { - const [key, value] = cookie.split('='); - acc[key] = value; - return acc; - }, {}); + if (isAuthenticated) { + this.logger.debug(`Authenticated: ${session?.userId}`); + } else { + this.logger.debug(`Unauthenticated`); + } - return cookies['connect.sid']; + return isAuthenticated; } } diff --git a/server/api-server/src/common/redis.session.ts b/server/api-server/src/common/redis.session.ts index 26328c5..f2aab69 100644 --- a/server/api-server/src/common/redis.session.ts +++ b/server/api-server/src/common/redis.session.ts @@ -2,11 +2,11 @@ import * as session from 'express-session'; import RedisStore from 'connect-redis'; import { createClient } from 'redis'; import { Logger } from '@nestjs/common'; +import * as process from 'process'; export function getSession() { const logger = new Logger(getSession.name); - - const url = process.env.REDIS_URL || 'redis://localhost:6379'; + const url = process.env.REDIS_URL; const redisClient = createClient({ url, }); @@ -21,9 +21,10 @@ export function getSession() { saveUninitialized: false, resave: false, store: redisStore, + proxy: true, cookie: { - httpOnly: false, - secure: false, + httpOnly: true, + secure: process.env.NODE_ENV === 'production', maxAge: 1000 * 60 * 60 * 24 * 7, }, }); diff --git a/server/api-server/src/main.ts b/server/api-server/src/main.ts index 1b8eabe..5dca596 100644 --- a/server/api-server/src/main.ts +++ b/server/api-server/src/main.ts @@ -3,10 +3,16 @@ import { AppModule } from './app.module'; import { getSession } from './common/redis.session'; import { RedisIoAdapter } from './common/redis.adapter'; import * as passport from 'passport'; +import * as process from 'process'; async function bootstrap() { const app = await NestFactory.create(AppModule); + app.enableCors({ + origin: process.env.CLIENT_ORIGIN, + methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS', + credentials: true, + }); const session = getSession(); app.use(session); app.use(passport.initialize()); diff --git a/server/api-server/src/streams/streams.controller.ts b/server/api-server/src/streams/streams.controller.ts index 1946fbd..0cdf9a5 100644 --- a/server/api-server/src/streams/streams.controller.ts +++ b/server/api-server/src/streams/streams.controller.ts @@ -5,7 +5,6 @@ import { UpdateStreamDto } from './dto/update-stream.dto'; @Controller('streams') export class StreamsController { constructor(private readonly streamsService: StreamsService) {} - @Get() findAll(@Query('page') page = '1', @Query('size') size = '5') { return this.streamsService.findAll(+page, +size); diff --git a/server/api-server/src/users/users.controller.spec.ts b/server/api-server/src/users/users.controller.spec.ts index 76db712..8c6281f 100644 --- a/server/api-server/src/users/users.controller.spec.ts +++ b/server/api-server/src/users/users.controller.spec.ts @@ -15,7 +15,7 @@ describe('UsersController', () => { beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ imports: [ - ConfigModule.forRoot({isGlobal: true}), + ConfigModule.forRoot({ isGlobal: true }), TypeOrmModule.forRoot({ type: 'mysql', host: process.env.DB_HOST, @@ -32,7 +32,7 @@ describe('UsersController', () => { ], controllers: [UsersController], providers: [UsersService], - exports: [UsersService, TypeOrmModule] + exports: [UsersService, TypeOrmModule], }).compile(); controller = module.get(UsersController); @@ -57,16 +57,16 @@ describe('UsersController', () => { // Arrange const expectedResult = [ { - "id": "3cdce141-d9de-4f7b-b95c-e42acb602167", - "userId": "ddd", - "oauthId": "iNGxLq4KTWs7A6YCAMkdj76nNbWoW2Q1CO9kPbxbOqg", - "oauthType": "naver", - "nickname": "jmh", - "createdAt": "2023-11-27T14:37:35.955Z", - "updatedAt": "2023-11-27T14:45:14.795Z", - "deletedAt": null, - "stream": null // Add the stream property here - } + id: '3cdce141-d9de-4f7b-b95c-e42acb602167', + userId: 'ddd', + oauthId: 'iNGxLq4KTWs7A6YCAMkdj76nNbWoW2Q1CO9kPbxbOqg', + oauthType: 'naver', + nickname: 'jmh', + createdAt: '2023-11-27T14:37:35.955Z', + updatedAt: '2023-11-27T14:45:14.795Z', + deletedAt: null, + stream: null, // Add the stream property here + }, ]; jest.spyOn(service, 'findAll').mockResolvedValue(expectedResult); diff --git a/server/api-server/src/users/users.controller.ts b/server/api-server/src/users/users.controller.ts index bea4d03..acbe5b7 100644 --- a/server/api-server/src/users/users.controller.ts +++ b/server/api-server/src/users/users.controller.ts @@ -7,10 +7,15 @@ import { Param, Delete, Req, + UseGuards, + Session, + HttpException, + HttpStatus, } from '@nestjs/common'; import { UsersService } from './users.service'; import { CreateUserDto } from './dto/create-user.dto'; import { UpdateUserDto } from './dto/update-user.dto'; +import { LoggedInGuard } from '../auth/guard/logged-in.guard'; @Controller('users') export class UsersController { @@ -21,6 +26,18 @@ export class UsersController { return this.usersService.create(createUserDto); } + @UseGuards(LoggedInGuard) + @Get('me') + async getUserBySessionId(@Session() session: Record) { + if (!session.userId) { + throw new HttpException('Unauthorized', HttpStatus.UNAUTHORIZED); + } + + const user = await this.usersService.findOne(session.userId); + + return { userId: user.userId, nickname: user.nickname }; + } + @Get() findAll() { return this.usersService.findAll(); @@ -30,6 +47,7 @@ export class UsersController { findOne(@Param('id') id: string) { return this.usersService.findOne(id); } + @Patch() update(@Req() req, @Body() updateUserDto: UpdateUserDto) { const id = req.session.userId; diff --git a/server/api-server/src/users/users.module.ts b/server/api-server/src/users/users.module.ts index 71413a5..5425033 100644 --- a/server/api-server/src/users/users.module.ts +++ b/server/api-server/src/users/users.module.ts @@ -3,15 +3,10 @@ import { UsersService } from './users.service'; import { UsersController } from './users.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './entities/user.entity'; -import { AuthModule } from '../auth/auth.module'; import { StreamsModule } from '../streams/streams.module'; @Module({ - imports: [ - TypeOrmModule.forFeature([User]), - forwardRef(() => AuthModule), - forwardRef(() => StreamsModule), - ], + imports: [TypeOrmModule.forFeature([User]), forwardRef(() => StreamsModule)], controllers: [UsersController], providers: [UsersService], exports: [UsersService, TypeOrmModule], diff --git a/server/api-server/src/users/users.service.ts b/server/api-server/src/users/users.service.ts index ecaa104..7ab16c4 100644 --- a/server/api-server/src/users/users.service.ts +++ b/server/api-server/src/users/users.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { CreateUserDto } from './dto/create-user.dto'; import { UpdateUserDto } from './dto/update-user.dto'; import { InjectRepository } from '@nestjs/typeorm'; @@ -24,8 +24,8 @@ export class UsersService { return newUser; } - findAll(): Promise { - return this.userRepo.find(); + async findAll() { + return await this.userRepo.find(); } async findByOAuthId(oauthId: string) { @@ -35,15 +35,19 @@ export class UsersService { } async findOne(id: string) { - return await this.userRepo.findOne({ + const user = await this.userRepo.findOne({ where: { id }, }); + if (!user || !id) { + throw new HttpException('User not found', HttpStatus.NOT_FOUND); + } + return user; } async update(id: string, updateUserDto: UpdateUserDto) { const user = await this.findOne(id); if (!user) { - throw new Error(`User with id: ${id} not found`); + throw new HttpException('User not found', HttpStatus.NOT_FOUND); } for (const key in updateUserDto) { @@ -57,7 +61,12 @@ export class UsersService { return updatedUser; } - remove(id: string) { - return `This action removes a #${id} user`; + async remove(id: string) { + const result = await this.userRepo.softDelete(id); + if (!result.affected) + throw new HttpException('User not found', HttpStatus.NOT_FOUND); + else { + return result; + } } } diff --git a/server/api-server/yarn.lock b/server/api-server/yarn.lock index d9111fa..a4c32fb 100644 --- a/server/api-server/yarn.lock +++ b/server/api-server/yarn.lock @@ -705,6 +705,11 @@ resolved "https://registry.npmjs.org/@nestjs/axios/-/axios-3.0.1.tgz" integrity sha512-VlOZhAGDmOoFdsmewn8AyClAdGpKXQQaY1+3PGB+g6ceurGIdTxZgRX3VXc1T6Zs60PedWjg3A82TDOB05mrzQ== +"@nestjs/cache-manager@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@nestjs/cache-manager/-/cache-manager-2.1.1.tgz#abc042c6ac83400c64fd293b48ddb2dec99f6096" + integrity sha512-oYfRys4Ng0zp2HTUPNjH7gizf4vvG3PQZZ+3yGemb3xrF+p3JxDSK0cDq9NTjHzD5UmhjiyAftB9GkuL+t3r9g== + "@nestjs/cli@^10.0.0": version "10.2.1" resolved "https://registry.npmjs.org/@nestjs/cli/-/cli-10.2.1.tgz" @@ -1030,6 +1035,13 @@ dependencies: "@types/node" "*" +"@types/cookie-parser@^1.4.6": + version "1.4.6" + resolved "https://registry.yarnpkg.com/@types/cookie-parser/-/cookie-parser-1.4.6.tgz#002643c514cccf883a65cbe044dbdc38c0b92ade" + integrity sha512-KoooCrD56qlLskXPLGUiJxOMnv5l/8m7cQD2OxJ73NPMhuSz9PmvwRD6EpjDyKBVrdJDdQ4bQK7JFNHnNmax0w== + dependencies: + "@types/express" "*" + "@types/cookie@^0.4.1": version "0.4.1" resolved "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz"