diff --git a/.env b/.env index d80815a..8687165 100644 --- a/.env +++ b/.env @@ -4,7 +4,7 @@ # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB. # See the documentation for all the connection string options: https://pris.ly/d/connection-strings -DATABASE_URL="mysql://root:@localhost:3306/test" +DATABASE_URL="mysql://root:@localhost:3306/test_user" CORS_ALLOW_ORIGINS = ['http://localhost:8080'] CORS_ALLOW_METHOD = "GET,PUT,POST,DELETE,PATCH,OPTIONS" diff --git a/package-lock.json b/package-lock.json index f6f636a..e78d666 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@nestjs/jwt": "^9.0.0", "@nestjs/passport": "^9.0.0", "@nestjs/platform-express": "^9.0.0", - "@prisma/client": "^4.3.0", + "@prisma/client": "^4.3.1", "cors": "^2.8.5", "passport": "^0.6.0", "passport-jwt": "^4.0.0", @@ -37,7 +37,7 @@ "eslint-plugin-prettier": "^4.0.0", "jest": "28.1.2", "prettier": "^2.3.2", - "prisma": "^4.3.0", + "prisma": "^4.3.1", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "28.0.5", @@ -3053,9 +3053,9 @@ } }, "node_modules/ci-info": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.2.tgz", - "integrity": "sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.4.0.tgz", + "integrity": "sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug==", "dev": true }, "node_modules/cjs-module-lexer": { @@ -3469,9 +3469,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.246", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.246.tgz", - "integrity": "sha512-/wFCHUE+Hocqr/LlVGsuKLIw4P2lBWwFIDcNMDpJGzyIysQV4aycpoOitAs32FT94EHKnNqDR/CVZJFbXEufJA==", + "version": "1.4.247", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.247.tgz", + "integrity": "sha512-FLs6R4FQE+1JHM0hh3sfdxnYjKvJpHZyhQDjc2qFq/xFvmmRt/TATNToZhrcGUFzpF2XjeiuozrA8lI0PZmYYw==", "dev": true }, "node_modules/emittery": { @@ -4014,9 +4014,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -10791,9 +10791,9 @@ "dev": true }, "ci-info": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.2.tgz", - "integrity": "sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.4.0.tgz", + "integrity": "sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug==", "dev": true }, "cjs-module-lexer": { @@ -11119,9 +11119,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "electron-to-chromium": { - "version": "1.4.246", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.246.tgz", - "integrity": "sha512-/wFCHUE+Hocqr/LlVGsuKLIw4P2lBWwFIDcNMDpJGzyIysQV4aycpoOitAs32FT94EHKnNqDR/CVZJFbXEufJA==", + "version": "1.4.247", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.247.tgz", + "integrity": "sha512-FLs6R4FQE+1JHM0hh3sfdxnYjKvJpHZyhQDjc2qFq/xFvmmRt/TATNToZhrcGUFzpF2XjeiuozrA8lI0PZmYYw==", "dev": true }, "emittery": { @@ -11535,9 +11535,9 @@ "dev": true }, "fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", diff --git a/package.json b/package.json index 7cbb4e9..534682d 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "@nestjs/jwt": "^9.0.0", "@nestjs/passport": "^9.0.0", "@nestjs/platform-express": "^9.0.0", - "@prisma/client": "^4.3.0", + "@prisma/client": "^4.3.1", "cors": "^2.8.5", "passport": "^0.6.0", "passport-jwt": "^4.0.0", @@ -49,7 +49,7 @@ "eslint-plugin-prettier": "^4.0.0", "jest": "28.1.2", "prettier": "^2.3.2", - "prisma": "^4.3.0", + "prisma": "^4.3.1", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "28.0.5", @@ -74,5 +74,8 @@ ], "coverageDirectory": "../coverage", "testEnvironment": "node" + }, + "prisma": { + "seed": "ts-node prisma/seed.ts" } } diff --git a/prisma/migrations/20220901044610_user_init/migration.sql b/prisma/migrations/20220910015138_user_init/migration.sql similarity index 84% rename from prisma/migrations/20220901044610_user_init/migration.sql rename to prisma/migrations/20220910015138_user_init/migration.sql index 486758e..29cd779 100644 --- a/prisma/migrations/20220901044610_user_init/migration.sql +++ b/prisma/migrations/20220910015138_user_init/migration.sql @@ -4,11 +4,12 @@ CREATE TABLE `User` ( `email` VARCHAR(191) NOT NULL, `password` VARCHAR(191) NOT NULL, `name` VARCHAR(191) NOT NULL, - `role` VARCHAR(191) NOT NULL DEFAULT 'USER', + `role` ENUM('USER', 'ADMIN') NOT NULL DEFAULT 'USER', `is_done` BOOLEAN NULL DEFAULT false, `updatedAt` TIMESTAMP(0) NULL, `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), UNIQUE INDEX `User_email_key`(`email`), + INDEX `email`(`email`), PRIMARY KEY (`id`) ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 59e4330..289aeba 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,6 +1,3 @@ -// This is your Prisma schema file, -// learn more about it in the docs: https://pris.ly/d/prisma-schema - generator client { provider = "prisma-client-js" } @@ -10,14 +7,20 @@ datasource db { url = env("DATABASE_URL") } -//만들려는 모델 model User { - id Int @id @default(autoincrement()) - email String @unique - password String - name String - role String @default("USER") - is_done Boolean? @default(false) + id Int @id @default(autoincrement()) + email String @unique(map: "User_email_key") + password String + name String + role ROLE @default(USER) + is_done Boolean? @default(false) updatedAt DateTime? @db.Timestamp(0) - createdAt DateTime @default(now()) + createdAt DateTime @default(now()) + + @@index([email], name: "email") +} + +enum ROLE { + USER + ADMIN } \ No newline at end of file diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index b7f00ff..9f29a6c 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -1,5 +1,13 @@ /* eslint-disable prettier/prettier */ -import { Body, Controller, Get, Post, Request, UseGuards } from '@nestjs/common' +import { + Body, + Controller, + Get, + Param, + Post, + UseGuards, + Request +} from '@nestjs/common' import { User } from '@prisma/client' import { UserDTO } from 'src/user/dtos/user.dto' import { AuthService } from './auth.service' @@ -13,9 +21,9 @@ export class AuthController { //Login용 @UseGuards(LocalAuthGuard) @Post('login') - login(@Request() req) { + login(@Param('email') email: string, @Param('password') password: string) { //console.log(req) - return this.authService.login(req.user) + return this.authService.login(email, password) } //Profile 여부 확인용 diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index 178e54c..43f0c28 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -6,7 +6,7 @@ import { Module } from '@nestjs/common' import { PassportModule } from '@nestjs/passport' import { AuthService } from './auth.service' import { JwtModule } from '@nestjs/jwt' -import { jwtAcceesTokenTypes } from './guards/jwt.constants' +import { jwtAcceesTokenOptions } from './guards/jwt.constants' import { AuthController } from './auth.controller' import { LocalStrategy } from './guards/local.strategy' import { JwtStrategy } from './guards/jwt.strategy' @@ -17,8 +17,8 @@ import { UsersModule } from '../user/user.module' UsersModule, PassportModule, JwtModule.register({ - secret: jwtAcceesTokenTypes.secret, - signOptions: { expiresIn: jwtAcceesTokenTypes.expiresIn } + secret: jwtAcceesTokenOptions.secret, + signOptions: { expiresIn: jwtAcceesTokenOptions.expiresIn } }) ], controllers: [AuthController], diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 4e391ec..2b149cd 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -4,6 +4,10 @@ import { JwtService } from '@nestjs/jwt' import { User } from '@prisma/client' import { UserDTO } from 'src/user/dtos/user.dto' import { UserService } from '../user/user.service' +import { + jwtAcceesTokenOptions, + jwtRefreshTokenTypes +} from './guards/jwt.constants' @Injectable() export class AuthService { @@ -13,8 +17,8 @@ export class AuthService { ) {} //app.controller.ts에서 @UseGuards(AuthGuard('local'))용 - async validateUser(email: string, password: string): Promise { - const user = await this.userService.fetchOneByEmail(email) + async validateUser(email: string, password: string): Promise | null { + const user = await this.userService.fetchOne({ email: email }) if (user && user.password === password) { const { password, ...result } = user // result는 password 를 제외한 user의 모든 정보를 포함한다. @@ -25,17 +29,37 @@ export class AuthService { return null } - async login(user: any) { + async getTokens(user: User): Promise { //console.log(user) - const payload = { + const access_token_payload = { email: user.email, name: user.name } + const refresh_token_payload = {} // console.log(payload) - return await { access_token: this.jwtService.sign(payload) } + return { + tokens: { + access_token: this.jwtService.sign( + access_token_payload, + jwtAcceesTokenOptions + ), + refresh_token: this.jwtService.sign( + refresh_token_payload, + jwtRefreshTokenTypes + ) + } + } } - async register(data: UserDTO): Promise { - return await this.userService.add(data) + async login(email: string, password: string): Promise | null { + const user = await this.validateUser(email, password) + if (!user) return null + return await this.getTokens(user) + } + + async register(data: UserDTO): Promise | null { + const user = await this.userService.add(data) + if (!user) return null + return await this.getTokens(user) } } diff --git a/src/auth/decorators/role.enum.ts b/src/auth/decorators/role.enum.ts deleted file mode 100644 index bec8f48..0000000 --- a/src/auth/decorators/role.enum.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum Role { - USER = 'USER', - ADMIN = 'ADMIN' -} diff --git a/src/auth/decorators/roles.decorator.ts b/src/auth/decorators/roles.decorator.ts index 6cf4627..3a420a3 100644 --- a/src/auth/decorators/roles.decorator.ts +++ b/src/auth/decorators/roles.decorator.ts @@ -1,5 +1,5 @@ import { SetMetadata } from '@nestjs/common' -import { Role } from './role.enum' +import { ROLE } from '@prisma/client' export const ROLES_KEY = 'roles' -export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles) +export const Roles = (...roles: ROLE[]) => SetMetadata(ROLES_KEY, roles) diff --git a/src/auth/guards/jwt.constants.ts b/src/auth/guards/jwt.constants.ts index 06fdc9f..10bbd3a 100644 --- a/src/auth/guards/jwt.constants.ts +++ b/src/auth/guards/jwt.constants.ts @@ -1,4 +1,4 @@ -export const jwtAcceesTokenTypes = { +export const jwtAcceesTokenOptions = { secret: process.env.JWT_ACCESS_TOKEN_SECRET, expiresIn: process.env.JWT_ACCESS_TOKEN_EXPIREIN, issuer: process.env.JWT_ACCESS_TOKEN_ISSUER diff --git a/src/auth/guards/jwt.strategy.ts b/src/auth/guards/jwt.strategy.ts index 04ac96d..3ae7755 100644 --- a/src/auth/guards/jwt.strategy.ts +++ b/src/auth/guards/jwt.strategy.ts @@ -1,7 +1,7 @@ import { ExtractJwt, Strategy } from 'passport-jwt' import { PassportStrategy } from '@nestjs/passport' import { Injectable } from '@nestjs/common' -import { jwtAcceesTokenTypes } from './jwt.constants' +import { jwtAcceesTokenOptions } from './jwt.constants' @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { @@ -9,7 +9,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, - secretOrKey: jwtAcceesTokenTypes.secret + secretOrKey: jwtAcceesTokenOptions.secret }) } diff --git a/src/auth/guards/roles.guard.ts b/src/auth/guards/roles.guard.ts index 846227a..90eb518 100644 --- a/src/auth/guards/roles.guard.ts +++ b/src/auth/guards/roles.guard.ts @@ -1,8 +1,8 @@ import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common' import { Reflector } from '@nestjs/core' +import { ROLE } from '@prisma/client' import { Observable } from 'rxjs' import { ROLES_KEY } from '../decorators/roles.decorator' -import { Role } from '../decorators/role.enum' //참고: https://shpota.com/2022/07/16/role-based-authorization-with-jwt-using-nestjs.html @Injectable() @@ -12,7 +12,7 @@ export class RolesGuard implements CanActivate { canActivate( context: ExecutionContext ): boolean | Promise | Observable { - const requiredRoles = this.reflector.getAllAndOverride(ROLES_KEY, [ + const requiredRoles = this.reflector.getAllAndOverride(ROLES_KEY, [ context.getHandler(), context.getClass() ]) diff --git a/src/user/dtos/user.dto.ts b/src/user/dtos/user.dto.ts index 8084d24..1ced4b1 100644 --- a/src/user/dtos/user.dto.ts +++ b/src/user/dtos/user.dto.ts @@ -1,8 +1,10 @@ +import { ROLE, User } from '@prisma/client' + export class UserDTO { email: string password: string name: string - role?: string | 'USER' + role?: ROLE | 'USER' is_done?: boolean | false updatedAt?: Date | null createdAt?: Date diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index 0a2a234..8d182f3 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -15,14 +15,14 @@ import { UserDTO } from './dtos/user.dto' import { UserService } from './user.service' import { Roles } from '../auth/decorators/roles.decorator' import { JwtAuthGuard } from '../auth/guards/jwt.auth.guard' -import { Role } from '../auth/decorators/role.enum' import { RolesGuard } from '../auth/guards/roles.guard' +import { ROLE } from '@prisma/client' @Controller('user') export class UserController { constructor(private readonly userService: UserService) {} - @Roles(Role.ADMIN) + @Roles(ROLE.ADMIN) @UseGuards(JwtAuthGuard, RolesGuard) @Get() async fetchAll(@Query() query): Promise { @@ -129,21 +129,21 @@ export class UserController { return result } - @Roles(Role.ADMIN) + @Roles(ROLE.ADMIN) @UseGuards(JwtAuthGuard, RolesGuard) @Get(':id') async fetchOne(@Param('id') id: string): Promise { return await this.userService.fetchOne({ id: Number(id) }) } - @Roles(Role.ADMIN) + @Roles(ROLE.ADMIN) @UseGuards(JwtAuthGuard, RolesGuard) @Post('add') async add(@Body() data: UserDTO): Promise { return await this.userService.add(data) } - @Roles(Role.ADMIN) + @Roles(ROLE.ADMIN) @UseGuards(JwtAuthGuard, RolesGuard) @Put(':id') async update(@Param('id') id: string, @Body() data: UserDTO): Promise { @@ -154,7 +154,7 @@ export class UserController { }) } - @Roles(Role.ADMIN) + @Roles(ROLE.ADMIN) @UseGuards(JwtAuthGuard, RolesGuard) @Delete(':id') async delete(@Param('id') id: string): Promise { diff --git a/src/user/user.service.ts b/src/user/user.service.ts index af7bf7a..c90aff9 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -1,7 +1,7 @@ /* eslint-disable prettier/prettier */ import { Injectable } from '@nestjs/common' import { Prisma, User } from '@prisma/client' -import { PrismaService } from '../prisma.service' +import { PrismaService } from 'src/prisma.service' @Injectable() export class UserService { @@ -12,18 +12,13 @@ export class UserService { }) } - //단일 조회 ByEmail - async fetchOneByEmail(email: string): Promise { - return this.prisma.user.findUnique({ where: { email: email } }) - } - //전체조회 async count(params: { cursor?: Prisma.UserWhereUniqueInput where?: Prisma.UserWhereInput }): Promise { const { cursor, where } = params - return this.prisma.user.count({ + return await this.prisma.user.count({ cursor, where }) @@ -38,7 +33,7 @@ export class UserService { orderBy?: Prisma.UserOrderByWithRelationInput }): Promise { const { skip, take, cursor, where, orderBy } = params - return this.prisma.user.findMany({ + return await this.prisma.user.findMany({ skip, take, cursor, @@ -49,12 +44,12 @@ export class UserService { //단일 조회 async fetchOne(where: Prisma.UserWhereUniqueInput): Promise { - return this.prisma.user.findUnique({ where }) + return await this.prisma.user.findUnique({ where }) } //단일 추가 async add(data: Prisma.UserCreateInput): Promise { - return this.prisma.user.create({ data }) + return await this.prisma.user.create({ data }) } //단일 수정 @@ -63,7 +58,7 @@ export class UserService { data: Prisma.UserUpdateInput }): Promise { const { where, data } = params - return this.prisma.user.update({ + return await this.prisma.user.update({ data, where }) @@ -71,6 +66,6 @@ export class UserService { //단일 삭제 async remove(where: Prisma.UserWhereUniqueInput): Promise { - return this.prisma.user.delete({ where }) + return await this.prisma.user.delete({ where }) } }