diff --git a/backend/app.js b/backend/app.js
index cda30ae0..ee549fe3 100644
--- a/backend/app.js
+++ b/backend/app.js
@@ -7,6 +7,8 @@ import mongoose from 'mongoose'
import controller from './controller'
import statusCode from './util/statusCode'
import resMessage from './util/resMessage'
+import passport from 'passport'
+import passportConfig from './config/passport'
import './chatServer'
import cors from 'cors'
@@ -26,8 +28,11 @@ app.use(logger('dev'))
app.use(cors({ origin: true, credentials: true }))
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
-app.use(cookieParser())
app.use(express.static(path.join(__dirname, '../dist')))
+app.use(cookieParser(process.env.COOKIE_SECRET))
+app.use(passport.initialize())
+app.use(cors({ origin: true, credentials: true }))
+passportConfig()
app.use('/api', controller)
app.use('/docs', express.static(path.join(__dirname, './docs')))
diff --git a/backend/config/passport.js b/backend/config/passport.js
new file mode 100644
index 00000000..0e4fd4d9
--- /dev/null
+++ b/backend/config/passport.js
@@ -0,0 +1,87 @@
+const passport = require('passport')
+const GitHubStrategy = require('passport-github').Strategy
+const JWTStrategy = require('passport-jwt').Strategy
+const { User } = require('../model/User')
+
+require('dotenv').config()
+
+const githubStrategyOption = {
+ clientID: process.env.GITHUB_CLIENT_ID,
+ clientSecret: process.env.GITHUB_CLIENT_SECRET,
+ callbackURL: process.env.GITHUB_CALLBACK_URL,
+}
+
+async function gitStrategyLogin(profiles) {
+ try {
+ let user = await User.findOne({ OAuthId: profiles.id })
+ if (user === null) {
+ const data = await User.create({
+ OAuthId: profiles.id,
+ fullName: profiles.username,
+ isDeleted: false,
+ })
+ return {
+ success: true,
+ id: data._id,
+ }
+ }
+ return {
+ success: true,
+ id: user._id,
+ }
+ } catch (err) {
+ return { success: false }
+ }
+}
+
+async function githubVerify(accessToken, refreshToken, profile, done) {
+ try {
+ const result = await gitStrategyLogin(profile)
+ const user = { id: result.id }
+
+ if (result.success) {
+ return done(null, user)
+ }
+ return done(null, false, { message: '깃허브 로그인에 실패했습니다.' })
+ } catch (err) {
+ return done(null, false, { message: 'GitHub verify err 발생' })
+ }
+}
+
+const cookieExtractor = req => {
+ if (req.signedCookies) return req.signedCookies.token
+ if (req.cookies) return req.cookies
+}
+
+const isExist = async userId => {
+ try {
+ let user = await User.findOne({ _id: userId })
+ return {
+ success: true,
+ id: user._id,
+ }
+ } catch (err) {
+ return { success: false }
+ }
+}
+
+const jwtStrategyOption = {
+ jwtFromRequest: cookieExtractor,
+ secretOrKey: process.env.JWT_SECRET,
+}
+async function jwtVerify(payload, done) {
+ try {
+ const result = await isExist(payload.id)
+ if (!result.success) {
+ return done(null, false, { message: 'JWT 토큰 인증에 실패했습니다.' })
+ }
+ return done(null, result)
+ } catch (err) {
+ return done(null, false, { message: 'JWT verify err 발생' })
+ }
+}
+
+module.exports = () => {
+ passport.use(new GitHubStrategy(githubStrategyOption, githubVerify))
+ passport.use(new JWTStrategy(jwtStrategyOption, jwtVerify))
+}
diff --git a/backend/controller/index.js b/backend/controller/index.js
index 1578f4fc..1e0ce416 100644
--- a/backend/controller/index.js
+++ b/backend/controller/index.js
@@ -1,8 +1,11 @@
import express from 'express'
+import userController from './user'
import channelController from './channel'
const router = express.Router()
router.use('/channel', channelController)
+router.use('/user', userController)
+
module.exports = router
diff --git a/backend/controller/user/index.js b/backend/controller/user/index.js
new file mode 100644
index 00000000..8fc33cde
--- /dev/null
+++ b/backend/controller/user/index.js
@@ -0,0 +1,9 @@
+import express from 'express'
+const router = express.Router()
+const controller = require('./userController')
+
+router.get('/sign-in/github', controller.githubLogin)
+router.get('/sign-in/github/callback', controller.githubCallback)
+router.get('/auth', controller.authCheck)
+
+module.exports = router
diff --git a/backend/controller/user/userController.js b/backend/controller/user/userController.js
new file mode 100644
index 00000000..b3e524ec
--- /dev/null
+++ b/backend/controller/user/userController.js
@@ -0,0 +1,40 @@
+const passport = require('passport')
+const jwt = require('jsonwebtoken')
+
+exports.githubLogin = passport.authenticate('github')
+
+exports.githubCallback = async (req, res, next) => {
+ const frontHost = process.env.FRONTEND_HOST
+ passport.authenticate('github', (err, id) => {
+ if (err || !id) {
+ return res.status(200).redirect(frontHost)
+ }
+ req.login(id, { session: false }, err => {
+ if (err) {
+ res.send(err)
+ }
+
+ const token = jwt.sign(id, process.env.JWT_SECRET, { expiresIn: '1H' })
+ res.cookie('token', token, {
+ maxAge: 1000 * 60 * 60,
+ httpOnly: true,
+ signed: true,
+ })
+ return res.status(200).redirect(frontHost)
+ })
+ })(req, res)
+}
+
+exports.authCheck = (req, res) => {
+ let token = req.signedCookies.token
+ if (token) {
+ try {
+ let decoded = jwt.verify(token, process.env.JWT_SECRET)
+ return res.json({ verify: true })
+ } catch (err) {
+ return res.json({ verify: false })
+ }
+ } else {
+ return res.json({ verify: false, message: 'token does not exist' })
+ }
+}
diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml
index 441d1e98..0912c14c 100644
--- a/backend/docs/swagger.yaml
+++ b/backend/docs/swagger.yaml
@@ -343,6 +343,48 @@ paths:
description: bad request
default:
description: Default error sample response
+ /api/user/sign-in/github:
+ summary: workspace user login
+ description: selectworkspace user login
+ post:
+ summary: workspace user login
+ description: workspace user login
+ operationId: ''
+ parameters:
+ - name: oauthId
+ in: query
+ description: workspace user id
+ required: true
+ explode: true
+ schema:
+ type: integer
+ responses:
+ '200':
+ description: signin success
+ '400':
+ description: bad input parameter
+ /api/user/sign-out:
+ summary: workspace user signout
+ description: selectworkspace user signout
+ delete:
+ summary: workspace user signout
+ description: workspace user signout
+ operationId: ''
+ parameters:
+ - name: workspaceUserInfoId
+ in: query
+ description: workspace user id
+ required: true
+ explode: true
+ schema:
+ type: integer
+ responses:
+ '200':
+ description: search results matching criteria
+ '400':
+ description: bad input parameter
+
+
components:
schemas:
InventoryItem:
diff --git a/backend/middleware/auth.js b/backend/middleware/auth.js
new file mode 100644
index 00000000..0dbf6edd
--- /dev/null
+++ b/backend/middleware/auth.js
@@ -0,0 +1,12 @@
+const passport = require('passport')
+require('dotenv').config()
+
+exports.Auth = (req, res, next) => {
+ passport.authenticate('jwt', { session: false }, (err, user) => {
+ if (err || !user || !user.success) {
+ next({ status: 403, message: 'auth error' })
+ }
+ req.user = user
+ next()
+ })(req, res, next)
+}
diff --git a/backend/package-lock.json b/backend/package-lock.json
index 91f92655..4a0e0752 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -1357,6 +1357,10 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
+ "base64url": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz",
+ "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A=="
"base64id": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
@@ -1534,6 +1538,11 @@
"resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz",
"integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg=="
},
+ "buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
+ },
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
@@ -1924,6 +1933,14 @@
"resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
"integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI="
},
+ "ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+ "requires": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -3076,6 +3093,49 @@
"minimist": "^1.2.5"
}
},
+ "jsonwebtoken": {
+ "version": "8.5.1",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
+ "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
+ "requires": {
+ "jws": "^3.2.2",
+ "lodash.includes": "^4.3.0",
+ "lodash.isboolean": "^3.0.3",
+ "lodash.isinteger": "^4.0.4",
+ "lodash.isnumber": "^3.0.3",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.isstring": "^4.0.1",
+ "lodash.once": "^4.0.0",
+ "ms": "^2.1.1",
+ "semver": "^5.6.0"
+ },
+ "dependencies": {
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ }
+ }
+ },
+ "jwa": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
+ "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+ "requires": {
+ "buffer-equal-constant-time": "1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "jws": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+ "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+ "requires": {
+ "jwa": "^1.4.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
"kareem": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz",
@@ -3150,6 +3210,41 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
},
+ "lodash.includes": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+ "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8="
+ },
+ "lodash.isboolean": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+ "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY="
+ },
+ "lodash.isinteger": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+ "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M="
+ },
+ "lodash.isnumber": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+ "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w="
+ },
+ "lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs="
+ },
+ "lodash.isstring": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+ "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE="
+ },
+ "lodash.once": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+ "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w="
+ },
"lowercase-keys": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
@@ -3430,6 +3525,11 @@
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz",
"integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ=="
},
+ "oauth": {
+ "version": "0.9.15",
+ "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz",
+ "integrity": "sha1-vR/vr2hslrdUda7VGWQS/2DPucE="
+ },
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -3589,6 +3689,49 @@
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
},
+ "passport": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/passport/-/passport-0.4.1.tgz",
+ "integrity": "sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg==",
+ "requires": {
+ "passport-strategy": "1.x.x",
+ "pause": "0.0.1"
+ }
+ },
+ "passport-github": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/passport-github/-/passport-github-1.1.0.tgz",
+ "integrity": "sha1-jOHj/NYa11eOsd9ZWDnkrqEjVdQ=",
+ "requires": {
+ "passport-oauth2": "1.x.x"
+ }
+ },
+ "passport-jwt": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.0.tgz",
+ "integrity": "sha512-BwC0n2GP/1hMVjR4QpnvqA61TxenUMlmfNjYNgK0ZAs0HK4SOQkHcSv4L328blNTLtHq7DbmvyNJiH+bn6C5Mg==",
+ "requires": {
+ "jsonwebtoken": "^8.2.0",
+ "passport-strategy": "^1.0.0"
+ }
+ },
+ "passport-oauth2": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.5.0.tgz",
+ "integrity": "sha512-kqBt6vR/5VlCK8iCx1/KpY42kQ+NEHZwsSyt4Y6STiNjU+wWICG1i8ucc1FapXDGO15C5O5VZz7+7vRzrDPXXQ==",
+ "requires": {
+ "base64url": "3.x.x",
+ "oauth": "0.9.x",
+ "passport-strategy": "1.x.x",
+ "uid2": "0.0.x",
+ "utils-merge": "1.x.x"
+ }
+ },
+ "passport-strategy": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
+ "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ="
+ },
"path-exists": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
@@ -3633,6 +3776,11 @@
}
}
},
+ "pause": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
+ "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10="
+ },
"picomatch": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
@@ -4506,6 +4654,11 @@
"is-typedarray": "^1.0.0"
}
},
+ "uid2": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz",
+ "integrity": "sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I="
+ },
"undefsafe": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz",
diff --git a/backend/package.json b/backend/package.json
index 7644ffd5..f17b0f58 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -19,15 +19,19 @@
"homepage": "https://github.com/boostcamp-2020/Project12-C-Slack-Web",
"dependencies": {
"concurrently": "^5.3.0",
- "cookie-parser": "~1.4.4",
+ "cookie-parser": "^1.4.5",
"core-js": "^3.6.5",
"cors": "^2.8.5",
"debug": "~2.6.9",
"dotenv": "^8.2.0",
"express": "~4.16.1",
+ "jsonwebtoken": "^8.5.1",
"mongoose": "^5.10.15",
"morgan": "~1.9.1",
"nodemon": "^2.0.4",
+ "passport": "^0.4.1",
+ "passport-github": "^1.1.0",
+ "passport-jwt": "^4.0.0"
"socket.io": "^3.0.3"
},
"devDependencies": {
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 6655d449..a0565901 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -5489,6 +5489,14 @@
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.1.1.tgz",
"integrity": "sha512-5Kgy8Cz6LPC9DJcNb3yjAXTu3XihQgEdnIg50c//zOC/MyLP0Clg+Y8Sh9ZjjnvBrDZU4DgXS9C3T9r4/scGZQ=="
},
+ "axios": {
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.0.tgz",
+ "integrity": "sha512-fmkJBknJKoZwem3/IKSSLpkdNXZeBu5Q7GA/aRsr2btgrptmSCxi2oFjZHqGdK9DoTil9PIHlPIZw2EcRJXRvw==",
+ "requires": {
+ "follow-redirects": "^1.10.0"
+ }
+ },
"axobject-query": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index beb00066..6fa08c4e 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -7,6 +7,7 @@
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
+ "axios": "^0.21.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-router-dom": "^5.2.0",
diff --git a/frontend/src/hooks/Auth.js b/frontend/src/hooks/Auth.js
new file mode 100644
index 00000000..b9b3dd73
--- /dev/null
+++ b/frontend/src/hooks/Auth.js
@@ -0,0 +1,31 @@
+import React, { useState, useEffect } from 'react'
+import request from '../util/request'
+
+export default function Auth(Component, loginRequired) {
+ function Authentication(props) {
+ const [loading, setloading] = useState(true)
+ useEffect(() => {
+ ;(async () => {
+ try {
+ const data = await request.GET('/api/user/auth')
+ if (!data.verify) {
+ // 로그인이 되어 있지 않을때
+ if (loginRequired) {
+ props.history.push('/login')
+ }
+ } else {
+ if (!loginRequired) {
+ // 로그인 유저가 접근하면 안되는 페이지
+ props.history.push('/')
+ }
+ }
+ setloading(false)
+ } catch (err) {
+ console.error(err)
+ }
+ })()
+ }, [])
+ return !loading &&