nestjs_auth ...

This commit is contained in:
최준흠 2022-09-10 12:02:23 +09:00
parent 64cd01b669
commit a1be293719
16 changed files with 110 additions and 78 deletions

2
.env
View File

@ -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"

40
package-lock.json generated
View File

@ -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",

View File

@ -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"
}
}

View File

@ -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;

View File

@ -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
}

View File

@ -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 여부 확인용

View File

@ -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],

View File

@ -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<any> {
const user = await this.userService.fetchOneByEmail(email)
async validateUser(email: string, password: string): Promise<any> | 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<any> {
//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<User> {
return await this.userService.add(data)
async login(email: string, password: string): Promise<any> | null {
const user = await this.validateUser(email, password)
if (!user) return null
return await this.getTokens(user)
}
async register(data: UserDTO): Promise<any> | null {
const user = await this.userService.add(data)
if (!user) return null
return await this.getTokens(user)
}
}

View File

@ -1,4 +0,0 @@
export enum Role {
USER = 'USER',
ADMIN = 'ADMIN'
}

View File

@ -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)

View File

@ -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

View File

@ -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
})
}

View File

@ -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<boolean> | Observable<boolean> {
const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
const requiredRoles = this.reflector.getAllAndOverride<ROLE[]>(ROLES_KEY, [
context.getHandler(),
context.getClass()
])

View File

@ -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

View File

@ -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<any> {
@ -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<User | undefined> {
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<User> {
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<User> {
@ -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<User | undefined> {

View File

@ -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<User | undefined> {
return this.prisma.user.findUnique({ where: { email: email } })
}
//전체조회
async count(params: {
cursor?: Prisma.UserWhereUniqueInput
where?: Prisma.UserWhereInput
}): Promise<number> {
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<User[]> {
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<User | null> {
return this.prisma.user.findUnique({ where })
return await this.prisma.user.findUnique({ where })
}
//단일 추가
async add(data: Prisma.UserCreateInput): Promise<User> {
return this.prisma.user.create({ data })
return await this.prisma.user.create({ data })
}
//단일 수정
@ -63,7 +58,7 @@ export class UserService {
data: Prisma.UserUpdateInput
}): Promise<User | null> {
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<User | null> {
return this.prisma.user.delete({ where })
return await this.prisma.user.delete({ where })
}
}