diff --git a/app.ts b/app.ts index a43bae7..612690f 100644 --- a/app.ts +++ b/app.ts @@ -14,7 +14,7 @@ class App { private router(): void { this.application.get('/', (req: express.Request, res: express.Response) => { - res.send('hello! world!'); + res.send("hello! It's Trollo Server!"); }); } } @@ -31,7 +31,7 @@ app.use(express.urlencoded({ extended: true })); app.use(cookieParser()); app.use(express.json()); app.use(cors(corsOption)); -app.use('/', devRouter); +app.use('/', devRouter); // 테스트용으로 바로 넘어감, 배포 전 삭제해야함! app.listen(4000, () => { console.log('Server listening on port 4000'); }); diff --git a/controller/emailauth.ts b/controller/emailauth.ts index 0008c97..141a6c5 100644 --- a/controller/emailauth.ts +++ b/controller/emailauth.ts @@ -1,4 +1,4 @@ -import express from 'express'; +import { Request, Response } from 'express'; const jwt = require('jsonwebtoken'); import * as dotenv from 'dotenv'; dotenv.config(); @@ -8,7 +8,7 @@ import { refreshTokenGenerator } from '../Auth/GenerateRefreshToken'; // const Users = require('../src/db/models/user'); const emailAuthController = { - authorizationCode: async (req: express.Request, res: express.Response) => { + authorizationCode: async (req: Request, res: Response) => { //오소리코드 확인 // console.log(req.query); @@ -41,14 +41,22 @@ const emailAuthController = { // secure: true, // sameOrigin: 'none', }); - res.status(200).send({ message: 'ok', data: { accessToken: accessToken } }); + // access token과 loginType을 응답으로 보내줌 + res.status(200).json({ + accessToken, + LoginType: 'email', + }); } else { //expired - res.status(404).send({ message: 'authorizationCode Expired!' }); + res.status(403).json({ + message: 'authorizationCode Expired!', + }); } } } catch (err) { - res.status(500).send({ message: 'authorizationCode Error!' }); + res.status(401).json({ + message: 'authorizationCode Error!', + }); } }, ); diff --git a/controller/loginOAuth.ts b/controller/loginOAuth.ts index 2744f92..11c8c08 100644 --- a/controller/loginOAuth.ts +++ b/controller/loginOAuth.ts @@ -1,6 +1,5 @@ import { Request, Response } from 'express'; import axios from 'axios'; -//const jwt = require('jsonwebtoken'); import * as dotenv from 'dotenv'; dotenv.config(); import { Users } from '../src/db/models/user'; @@ -16,11 +15,12 @@ const oauthController = { client_id: process.env.GOOGLE_CLIENT_ID, client_secret: process.env.GOOGLE_CLIENT_SECRET, code: req.body.authorizationCode, - redirect_uri: process.env.CLIENT_URL, // 클라이언트 리디렉션 uri - 나중에 수정해야함 + redirect_uri: process.env.CLIENT_URL, grant_type: 'authorization_code', }) .then(async result => { let accessToken = result.data.access_token; + let refreshToken = result.data.refresh_token; // accessToken을 통해 로그인한 유저 정보 가져오기 const resInfo = await axios .get(googleInfoURL, { @@ -38,19 +38,26 @@ const oauthController = { email: resInfo, }, }); - if (userInfo == null && userInfo !== undefined) { + if (userInfo == null && resInfo !== undefined) { await Users.create({ email: resInfo, }); } + // cookie에 refresh token 저장 + res.cookie('refreshToken', refreshToken, { + maxAge: 1000 * 60 * 60 * 24 * 7, + httpOnly: true, + }); + // access token과 loginType을 응답으로 보내줌 res.status(200).json({ accessToken, + LoginType: 'google', }); }) .catch(err => { console.log(err.message); - res.status(400).json({ - message: 'login error', + res.status(401).json({ + message: 'authorizationCode Error!', }); }); }, @@ -79,7 +86,7 @@ const oauthController = { const resInfo = await axios .get(githubInfoURL, { headers: { - authorization: `Bearer ${accessToken}`, //`token ${accessToken}`, + authorization: `Bearer ${accessToken}`, }, }) .then(result => { @@ -95,19 +102,21 @@ const oauthController = { email: `${resInfo}@github.com`, }, }); - if (userInfo == null && userInfo !== undefined) { + if (userInfo == null && resInfo !== undefined) { await Users.create({ email: `${resInfo}@github.com`, }); } + // access token과 loginType을 응답으로 보내줌 res.status(200).json({ accessToken, + LoginType: 'github', }); }) .catch(err => { console.log(err.message); - res.status(400).json({ - message: 'error', + res.status(401).json({ + message: 'authorizationCode Error!', }); }); }, diff --git a/middleware/authChecker.ts b/middleware/authChecker.ts index cbb3446..0f26fb3 100644 --- a/middleware/authChecker.ts +++ b/middleware/authChecker.ts @@ -1,51 +1,78 @@ import jwt, { VerifyErrors } from 'jsonwebtoken'; -// import jwtObj from '../config/jwt'; -import * as dotenv from 'dotenv'; +import axios from 'axios'; import { Request, Response, NextFunction } from 'express'; import { accessTokenGenerator } from '../Auth/GenerateAccessToken'; +import * as dotenv from 'dotenv'; dotenv.config(); -export const authChecker = (req: Request, res: Response, next: NextFunction) => { +export const authChecker = async (req: Request, res: Response, next: NextFunction) => { if (req.headers.authorization) { - const token = req.headers.authorization.split('Bearer ')[1]; - - jwt.verify(token, process.env.ACCESS_SECRET as string, err => { - if (err) { - // 기간만료 ? 맞다. - // 그럼이제 리프레시토큰을 이용해서 액세스토큰 재발급 - // 그럼 두가지 분기처리를 해야한다, 리프레시토큰이없거나,만료되었거나해서 리다이렉트 로그인페이지 - // 다시 액세스토큰을 내려주거나 - // res.status(401).json({ error: 'expired!' }); - const refresh = req.cookies.refreshToken; - if (refresh) { - //리프레시토큰 존재 - jwt.verify( - refresh, - process.env.REFRESH_SECRET as string, - async (err: VerifyErrors | null, decoded: any | undefined) => { - if (err) { - //리프레시토큰 정상적이지않음, - res.redirect(`${process.env.CLIENT_URL}/Login`); - } else { - // 액세스토큰 새로 발급 - const id = decoded.userId; - const email = decoded.email; - const newAccessToken = await accessTokenGenerator(id, email); - res.send({ message: 'newAccessToken', data: { accessToken: newAccessToken } }); - } - }, - ); - } else { - //리프레시 없음 - res.redirect(`${process.env.CLIENT_URL}/Login`); + const accessToken = req.headers.authorization.split('Bearer ')[1]; + const LoginType = req.headers.LoginType; + if (LoginType === 'email') { + // 로그인 방식 - email + jwt.verify(accessToken, process.env.ACCESS_SECRET as string, err => { + if (err) { + // 기간만료 ? 맞다. + // 그럼이제 리프레시토큰을 이용해서 액세스토큰 재발급 + // 그럼 두가지 분기처리를 해야한다, 리프레시토큰이없거나,만료되었거나해서 리다이렉트 로그인페이지 + // 다시 액세스토큰을 내려주거나 + // res.status(401).json({ error: 'expired!' }); + const refreshToken = req.cookies.refreshToken; + if (refreshToken) { + // refresh token 존재 + jwt.verify( + refreshToken, + process.env.REFRESH_SECRET as string, + async (err: VerifyErrors | null, decoded: any | undefined) => { + if (err) { + // refresh token 정상적이지않음 + res.redirect(`${process.env.CLIENT_URL}/Login`); + } else { + // 새로운 access token을 발급받음 + const id = decoded.userId; + const email = decoded.email; + const newAccessToken = await accessTokenGenerator(id, email); + req.newAccessToken = newAccessToken; + } + }, + ); + } else { + // refresh token 없음 + res.redirect(`${process.env.CLIENT_URL}/login`); + } } - } else { - // 액세스토큰 이상없음 다음꺼로 넘어감 - next(); - } - }); + }); + } else if (LoginType === 'google') { + // 로그인 방식 - google + // refresh token을 이용하여 새로운 access token을 발급받음 + const googleLoginURL = 'https://accounts.google.com/o/oauth2/token'; + await axios + .post(googleLoginURL, { + client_id: process.env.GOOGLE_CLIENT_ID, + client_secret: process.env.GOOGLE_CLIENT_SECRET, + grant_type: 'refresh_token', + refresh_token: req.cookies.refreshToken, + }) + .then(async result => { + let accessToken = result.data.access_token; + req.newAccessToken = accessToken; + }) + .catch(err => { + // 에러 발생 -> 인증 불가 -> 다시 로그인해야함 + console.log(err.message); + res.redirect(`${process.env.CLIENT_URL}/login`); + }); + } else if (LoginType === 'github') { + // 로그인 방식 - github + // refresh token이 없음, 로그아웃 하기 전까지 access token 계속 사용 가능 + req.newAccessToken = accessToken; + } + // 실제 요청으로 넘어감 + // 나중에 응답 보낼때 accessToken에 req.newAccessToken을 넣어주면 됨 + next(); } else { - // 액세스 토큰 없을때 - res.redirect(`${process.env.CLIENT_URL}/Login`); + // access token이 없을 때 -> 로그인 페이지로 돌아감 + res.redirect(`${process.env.CLIENT_URL}/login`); } }; diff --git a/package-lock.json b/package-lock.json index 6c73160..440d9bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -445,7 +445,8 @@ "arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true }, "argparse": { "version": "1.0.10", @@ -627,7 +628,8 @@ "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true }, "bytes": { "version": "3.1.0", @@ -927,7 +929,8 @@ "create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true }, "cross-spawn": { "version": "7.0.3", @@ -1009,7 +1012,8 @@ "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true }, "dir-glob": { "version": "3.0.1", @@ -2273,7 +2277,8 @@ "make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true }, "media-typer": { "version": "0.3.0", @@ -3554,12 +3559,14 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true }, "source-map-support": { "version": "0.5.19", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -3758,6 +3765,7 @@ "version": "9.1.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", + "dev": true, "requires": { "arg": "^4.1.0", "create-require": "^1.1.0", @@ -3821,7 +3829,8 @@ "typescript": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", - "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==" + "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==", + "dev": true }, "umzug": { "version": "2.3.0", @@ -4185,7 +4194,8 @@ "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true } } } diff --git a/package.json b/package.json index ac9d796..8af780f 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "start": "nodemon --exec ts-node app.ts", + "start": "nodemon --exec ts-node -T app.ts", "create_db": "ts-node ./src/db/migrations/migration-all-table.ts" }, "repository": { @@ -25,7 +25,9 @@ "eslint": "^7.25.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^3.4.0", - "prettier": "^2.2.1" + "prettier": "^2.2.1", + "ts-node": "^9.1.1", + "typescript": "^4.2.4" }, "dependencies": { "@types/dotenv": "^8.2.0", @@ -50,8 +52,6 @@ "sequelize-cli": "^6.2.0", "sequelize-cli-typescript": "^3.2.0-c", "sequelize-typescript": "^2.1.0", - "ts-node": "^9.1.1", - "typescript": "^4.2.4", "util": "^0.10.4" } } diff --git a/routes/board.ts b/routes/board.ts index 728f98d..313e14b 100644 --- a/routes/board.ts +++ b/routes/board.ts @@ -1,17 +1,21 @@ import app from '../app'; +import { authChecker } from './../middleware/authChecker'; import { boardController } from '../controller/board'; +// 실제 요청 처리하기 전 access token 확인 +app.use('/board', authChecker); + // 게시판 글 목록 데이터 보내주기 app.get('/board', boardController.boardAll); // 게시글 상세 내용 + 댓글 데이터 보내주기 -app.get('/board/:id', boardController.boardOne); +app.get('/board/:board_id', boardController.boardOne); // 게시글 삭제하기 -app.delete('/board/:id', boardController.boardDelete); +app.delete('/board/:board_id', boardController.boardDelete); // 댓글 추가하기 -app.post('/board/comment', boardController.commentAdd); +app.post('/board/:board_id/comment', boardController.commentAdd); // 댓글 삭제하기 -app.delete('/board/:id/:commentId', boardController.commentDelete); +app.delete('/board/:board_id/:comment_id', boardController.commentDelete); diff --git a/routes/workspace.ts b/routes/workspace.ts index 3f67956..9be4c03 100644 --- a/routes/workspace.ts +++ b/routes/workspace.ts @@ -1,6 +1,10 @@ import app from '../app'; +import { authChecker } from './../middleware/authChecker'; import { workspaceController } from '../controller/workspace'; +// 실제 요청 처리하기 전 access token 확인 +app.use('/workspace', authChecker); + //workspace(칸반보드) 데이터 보내주기 app.get('/workspace', workspaceController.get); diff --git a/src/customType/express.d.ts b/src/customType/express.d.ts new file mode 100644 index 0000000..5f8cd6c --- /dev/null +++ b/src/customType/express.d.ts @@ -0,0 +1,7 @@ +export declare global { + namespace Express { + interface Request { + newAccessToken?: string; + } + } +} diff --git a/tsconfig.json b/tsconfig.json index 04c581a..484ab5f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -43,11 +43,11 @@ // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ /* Module Resolution Options */ - // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ - // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + "baseUrl": ".", /* Base directory to resolve non-absolute module names. */ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ - // "typeRoots": [], /* List of folders to include type definitions from. */ + "typeRoots": ["./node_modules/@types", "./src/customType"], /* List of folders to include type definitions from. */ // "types": [], /* Type declaration files to be included in compilation. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */