Merge branch 'backend' into 'master'
[New] Auth, Log 구현 Backend Auth, Log 구현 See merge request !1
Showing
15 changed files
with
474 additions
and
20 deletions
... | @@ -3,7 +3,8 @@ let cookieParser = require('cookie-parser') | ... | @@ -3,7 +3,8 @@ let cookieParser = require('cookie-parser') |
3 | var cors = require('cors') | 3 | var cors = require('cors') |
4 | var corsConfig = require('./config/cors') | 4 | var corsConfig = require('./config/cors') |
5 | 5 | ||
6 | -let indexRouter = require('./routes/index') | 6 | +const indexRouter = require('./routes/index') |
7 | +const authRouter = require('./routes/authRouter') | ||
7 | 8 | ||
8 | let app = express() | 9 | let app = express() |
9 | app.use(cors(corsConfig)) | 10 | app.use(cors(corsConfig)) |
... | @@ -13,6 +14,7 @@ app.use(express.urlencoded({ extended: false })) | ... | @@ -13,6 +14,7 @@ app.use(express.urlencoded({ extended: false })) |
13 | app.use(cookieParser()) | 14 | app.use(cookieParser()) |
14 | 15 | ||
15 | app.use('/', indexRouter) | 16 | app.use('/', indexRouter) |
17 | +app.use('/', authRouter) | ||
16 | 18 | ||
17 | app.use(function(req, res) { | 19 | app.use(function(req, res) { |
18 | res.status(400) | 20 | res.status(400) | ... | ... |
... | @@ -7,6 +7,8 @@ const database = process.env.MYSQL_DATABASE || 'root' | ... | @@ -7,6 +7,8 @@ const database = process.env.MYSQL_DATABASE || 'root' |
7 | const host = process.env.MYSQL_HOST || '127.0.0.1' | 7 | const host = process.env.MYSQL_HOST || '127.0.0.1' |
8 | const port = process.env.MYSQL_PORT || 3306 | 8 | const port = process.env.MYSQL_PORT || 3306 |
9 | 9 | ||
10 | +const JWT_KEY = process.env.JWT_KEY || null | ||
11 | + | ||
10 | module.exports = { | 12 | module.exports = { |
11 | 'username': username, | 13 | 'username': username, |
12 | 'password': password, | 14 | 'password': password, |
... | @@ -14,5 +16,6 @@ module.exports = { | ... | @@ -14,5 +16,6 @@ module.exports = { |
14 | 'host': host, | 16 | 'host': host, |
15 | 'port': port, | 17 | 'port': port, |
16 | 'dialect': 'mysql', | 18 | 'dialect': 'mysql', |
17 | - 'operatorsAliases': false | 19 | + 'operatorsAliases': false, |
20 | + 'JWT_KEY': JWT_KEY | ||
18 | } | 21 | } | ... | ... |
backend/controllers/authController.js
0 → 100644
1 | +const jwt = require('jsonwebtoken') | ||
2 | +const bcrypt = require('bcrypt') | ||
3 | +const config = require(__dirname + '/../config/config') | ||
4 | + | ||
5 | +const { User } = require('../models'); | ||
6 | +const { sendResponse, sendError } = require('../utils/response') | ||
7 | +const { checkRequiredExist } = require('../utils/validation') | ||
8 | +const { logging } = require('../utils/log') | ||
9 | + | ||
10 | +exports.login = async (req, res) => { | ||
11 | + const required = checkRequiredExist(req.body, ['userId', 'password']) | ||
12 | + if (required) { | ||
13 | + logging('auth', 'error', { code: 400, message: `missingKey:${required}` }, req) | ||
14 | + return sendError(res, 400, `missingKey:${required}`) | ||
15 | + } | ||
16 | + const userId = req.body.userId | ||
17 | + const password = req.body.password | ||
18 | + | ||
19 | + let token = null | ||
20 | + let user = null | ||
21 | + let isSuccess = false | ||
22 | + let tokenInfo = { | ||
23 | + userInfo: { | ||
24 | + id: null, | ||
25 | + }, | ||
26 | + tokenConfig: { | ||
27 | + expiresIn: '24h', | ||
28 | + issuer: 'OSS', | ||
29 | + }, | ||
30 | + } | ||
31 | + | ||
32 | + let match = false | ||
33 | + try { | ||
34 | + user = await User.findOne({ | ||
35 | + where: { | ||
36 | + userId: userId | ||
37 | + } | ||
38 | + }) | ||
39 | + if (user) { | ||
40 | + match = await bcrypt.compare(password, user.password) | ||
41 | + } | ||
42 | + } catch (error) { | ||
43 | + logging('auth', 'error', { code: 500, message: error.message }, req) | ||
44 | + return sendError(res, 500, error.message) | ||
45 | + } | ||
46 | + | ||
47 | + if (match) { | ||
48 | + tokenInfo.userInfo.id = user.id | ||
49 | + isSuccess = true | ||
50 | + } | ||
51 | + else { | ||
52 | + logging('auth', 'error', { code: 401, message: 'Auth Failed' }, req) | ||
53 | + return sendError(res, 401, 'Auth Failed') | ||
54 | + } | ||
55 | + | ||
56 | + if (isSuccess === true) { //성공할 경우 토큰 발행 | ||
57 | + token = jwt.sign (tokenInfo.userInfo, config.JWT_KEY, tokenInfo.tokenConfig) | ||
58 | + logging('auth', 'access', { user: tokenInfo.userInfo.id },req) | ||
59 | + return res.status(200).json({ success: true, access_token: token }) | ||
60 | + } | ||
61 | +} | ||
62 | + | ||
63 | +exports.userInfo = async (req, res) => { | ||
64 | + const id = req.decoded.id | ||
65 | + if (!id) { | ||
66 | + return sendError(res, 401, 'InvalidToken') | ||
67 | + } | ||
68 | + let user = null | ||
69 | + try { | ||
70 | + user = await User.findOne ({ | ||
71 | + where: { | ||
72 | + id | ||
73 | + } | ||
74 | + }) | ||
75 | + } catch (error) { | ||
76 | + return sendError(res, 500, error.message) | ||
77 | + } | ||
78 | + if (user) { | ||
79 | + sendResponse(res, user, 200) | ||
80 | + } else { | ||
81 | + return sendError(res, 404, 'NoUserFound') | ||
82 | + | ||
83 | + } | ||
84 | +} | ||
85 | + | ||
86 | +exports.adminTest = async (req, res) => { | ||
87 | + return sendResponse(res, "Just Test", 200, "Test OK") | ||
88 | +} | ||
89 | + | ||
90 | +exports.isAdmin = async (userId) => { | ||
91 | + const user = await User.findByPk(userId) | ||
92 | + return (user && await user.isAdmin) | ||
93 | +} |
backend/middlewares/auth.js
0 → 100644
1 | +const jwt = require('jsonwebtoken') | ||
2 | +const config = require(__dirname + '/../config/config') | ||
3 | + | ||
4 | +const { sendError } = require('../utils/response') | ||
5 | +const { isAdmin } = require('../controllers/authController') | ||
6 | +const { logging } = require('../utils/log') | ||
7 | + | ||
8 | +exports.memberOnly = (req, res, next) => { | ||
9 | + const token = req.headers.authorization | ||
10 | + try { | ||
11 | + req.decoded = jwt.verify(token, config.JWT_KEY) | ||
12 | + return next() | ||
13 | + } catch (error) { | ||
14 | + if (error.name === 'TokenExpiredError') { | ||
15 | + logging('auth', 'error', { code: 419, message: `TokenExpired` }, req) | ||
16 | + return sendError(res, 419,'TokenExpired') | ||
17 | + | ||
18 | + } else { | ||
19 | + logging('auth', 'error', { code: 401, message: `InvalidToken` }, req) | ||
20 | + return sendError(res, 401, 'InvalidToken') | ||
21 | + } | ||
22 | + } | ||
23 | +} | ||
24 | + | ||
25 | +exports.guestOnly = (req, res, next) => { | ||
26 | + const token = req.headers.authorization | ||
27 | + if (typeof token === undefined) { | ||
28 | + return next() | ||
29 | + } else { | ||
30 | + try { | ||
31 | + const decoded = jwt.verify(token, config.JWT_KEY) | ||
32 | + if (decoded !== null) { | ||
33 | + logging('auth', 'error', { code: 403, message: `GuestOnly` }, req) | ||
34 | + return sendError(res, 403, 'GuestOnly') | ||
35 | + } else { | ||
36 | + return next() | ||
37 | + } | ||
38 | + } catch (error) { | ||
39 | + return next() | ||
40 | + } | ||
41 | + } | ||
42 | +} | ||
43 | + | ||
44 | +exports.adminOnly = async (req, res, next) => { | ||
45 | + const token = req.headers.authorization | ||
46 | + let auth = false | ||
47 | + let userId = null | ||
48 | + | ||
49 | + try{ | ||
50 | + req.decoded = jwt.verify(token, config.JWT_KEY) | ||
51 | + userId = req.decoded.id | ||
52 | + } catch (error) { | ||
53 | + if (error.name === 'TokenExpiredError') { | ||
54 | + logging('auth', 'error', { code: 419, message: `TokenExpired` }, req) | ||
55 | + return sendError(res, 419, 'TokenExpired') | ||
56 | + } else { | ||
57 | + logging('auth', 'error', { code: 401, message: `InvalidToken` }, req) | ||
58 | + return sendError(res, 401, 'InvalidToken') | ||
59 | + } | ||
60 | + } | ||
61 | + | ||
62 | + auth = await isAdmin(userId) | ||
63 | + if (auth) { | ||
64 | + next() | ||
65 | + } else { | ||
66 | + logging('auth', 'error', { code: 403, message: `Unauthoirzed Access` }, req) | ||
67 | + return sendError(res, 403, 'Unauthoirzed Access') | ||
68 | + } | ||
69 | +} |
backend/migrations/20210609133935-user.js
0 → 100644
1 | +'use strict'; | ||
2 | +const bcrypt = require('bcrypt') | ||
3 | +const path = require('path') | ||
4 | +require('dotenv').config({path: path.join(__dirname, "../.env")}) | ||
5 | + | ||
6 | +module.exports = { | ||
7 | + up: async (queryInterface, Sequelize) => { | ||
8 | + return Promise.all([ | ||
9 | + await queryInterface.createTable('Users', { | ||
10 | + id: { | ||
11 | + allowNull: false, | ||
12 | + autoIncrement: true, | ||
13 | + primaryKey: true, | ||
14 | + type: Sequelize.INTEGER | ||
15 | + }, | ||
16 | + userId: { | ||
17 | + type: Sequelize.STRING(191), | ||
18 | + unique:true | ||
19 | + }, | ||
20 | + password: { | ||
21 | + type: Sequelize.STRING(191) | ||
22 | + }, | ||
23 | + name: { | ||
24 | + type: Sequelize.STRING(191) | ||
25 | + }, | ||
26 | + phone: { | ||
27 | + type: Sequelize.STRING(31) | ||
28 | + }, | ||
29 | + email: { | ||
30 | + type: Sequelize.STRING(191) | ||
31 | + }, | ||
32 | + isAdmin: { | ||
33 | + type:Sequelize.BOOLEAN, | ||
34 | + defalutValue: false, | ||
35 | + allowNull: false | ||
36 | + }, | ||
37 | + createdAt: { | ||
38 | + type: Sequelize.DATE, | ||
39 | + allowNull: false | ||
40 | + }, | ||
41 | + updatedAt: { | ||
42 | + type: Sequelize.DATE, | ||
43 | + allowNull: false | ||
44 | + }, | ||
45 | + deletedAt: { | ||
46 | + type: Sequelize.DATE | ||
47 | + } | ||
48 | + }, { | ||
49 | + charset: 'utf8mb4', | ||
50 | + collate: 'utf8mb4_unicode_ci' | ||
51 | + }), | ||
52 | + await queryInterface.bulkInsert('Users', | ||
53 | + [ | ||
54 | + { | ||
55 | + id: 1, | ||
56 | + name: '관리자', | ||
57 | + userId: process.env.INITIAL_ADMIN_ID, | ||
58 | + password: await bcrypt.hash(process.env.INITIAL_ADMIN_PW, 10), | ||
59 | + isAdmin: true, | ||
60 | + createdAt: new Date(), | ||
61 | + updatedAt: new Date() | ||
62 | + } | ||
63 | + ]) | ||
64 | + | ||
65 | + ]) | ||
66 | + | ||
67 | + }, | ||
68 | + down: async (queryInterface, Sequelize) => { | ||
69 | + await queryInterface.dropTable('Users'); | ||
70 | + } | ||
71 | +}; |
backend/migrations/20210609141319-log.js
0 → 100644
1 | +'use strict'; | ||
2 | +module.exports = { | ||
3 | + up: async (queryInterface, Sequelize) => { | ||
4 | + await queryInterface.createTable('Logs', { | ||
5 | + id: { | ||
6 | + allowNull: false, | ||
7 | + autoIncrement: true, | ||
8 | + primaryKey: true, | ||
9 | + type: Sequelize.INTEGER | ||
10 | + }, | ||
11 | + user: { | ||
12 | + type: Sequelize.INTEGER | ||
13 | + }, | ||
14 | + location: { | ||
15 | + type: Sequelize.STRING(191), | ||
16 | + }, | ||
17 | + module: { | ||
18 | + type: Sequelize.STRING(191), | ||
19 | + }, | ||
20 | + actionType: { | ||
21 | + type: Sequelize.STRING(191) | ||
22 | + }, | ||
23 | + data: { | ||
24 | + type: Sequelize.JSON | ||
25 | + }, | ||
26 | + ipAddress:{ | ||
27 | + type: Sequelize.STRING(191) | ||
28 | + }, | ||
29 | + createdAt: { | ||
30 | + allowNull: false, | ||
31 | + type: Sequelize.DATE | ||
32 | + } | ||
33 | + },{ | ||
34 | + charset: 'utf8mb4', | ||
35 | + collate: 'utf8mb4_unicode_ci' | ||
36 | + }) | ||
37 | + }, | ||
38 | + down: async (queryInterface, Sequelize) => { | ||
39 | + await queryInterface.dropTable('Logs'); | ||
40 | + } | ||
41 | +}; |
1 | -'use strict' | 1 | +'use strict'; |
2 | 2 | ||
3 | const fs = require('fs') | 3 | const fs = require('fs') |
4 | const path = require('path') | 4 | const path = require('path') |
5 | const Sequelize = require('sequelize') | 5 | const Sequelize = require('sequelize') |
6 | + | ||
6 | const basename = path.basename(__filename) | 7 | const basename = path.basename(__filename) |
7 | const config = require(__dirname + '/../config/config') | 8 | const config = require(__dirname + '/../config/config') |
8 | -const db = {} | 9 | +let db = {} |
9 | 10 | ||
10 | let sequelize | 11 | let sequelize |
11 | if (config.use_env_variable) { | 12 | if (config.use_env_variable) { |
12 | - sequelize = new Sequelize(process.env[config.use_env_variable], config) | 13 | + sequelize = new Sequelize(process.env[config.use_env_variable], config) |
13 | } else { | 14 | } else { |
14 | - sequelize = new Sequelize(config.database, config.username, config.password, config) | 15 | + sequelize = new Sequelize(config.database, config.username, config.password, config) |
15 | } | 16 | } |
17 | +db.sequelize=sequelize | ||
18 | +db.Sequelize=Sequelize | ||
16 | 19 | ||
17 | fs | 20 | fs |
18 | - .readdirSync(__dirname) | 21 | + .readdirSync(__dirname) |
19 | - .filter(file => { | 22 | + .filter(file => { |
20 | - return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js') | 23 | + return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js') |
21 | - }) | 24 | + }) |
22 | - .forEach(file => { | 25 | + .forEach(file => { |
23 | - const model = sequelize['import'](path.join(__dirname, file)) | 26 | + let model = require(path.join(__dirname, file))(sequelize, Sequelize) |
24 | - db[model.name] = model | 27 | + db[model.name] = model |
25 | - }) | 28 | + }) |
26 | 29 | ||
27 | Object.keys(db).forEach(modelName => { | 30 | Object.keys(db).forEach(modelName => { |
28 | - if (db[modelName].associate) { | 31 | + if (db[modelName].associate) { |
29 | - db[modelName].associate(db) | 32 | + db[modelName].associate(db) |
30 | - } | 33 | + } |
31 | }) | 34 | }) |
32 | 35 | ||
33 | -db.sequelize = sequelize | ||
34 | -db.Sequelize = Sequelize | ||
35 | - | ||
36 | module.exports = db | 36 | module.exports = db | ... | ... |
backend/models/log.js
0 → 100644
1 | +'use strict' | ||
2 | +const { sequelize } = require('.') | ||
3 | +const { Model } = require('sequelize') | ||
4 | +module.exports = (sequelize, DataTypes) => { | ||
5 | + class Log extends Model {} | ||
6 | + | ||
7 | + Log.init({ | ||
8 | + id: { | ||
9 | + allowNull: false, | ||
10 | + autoIncrement: true, | ||
11 | + primaryKey: true, | ||
12 | + type: DataTypes.INTEGER | ||
13 | + }, | ||
14 | + user: { | ||
15 | + type: DataTypes.INTEGER | ||
16 | + }, | ||
17 | + location: { | ||
18 | + type: DataTypes.STRING(191), | ||
19 | + }, | ||
20 | + module: { | ||
21 | + type: DataTypes.STRING(191), | ||
22 | + }, | ||
23 | + actionType: { | ||
24 | + type: DataTypes.STRING(191) | ||
25 | + }, | ||
26 | + data: { | ||
27 | + type: DataTypes.JSON | ||
28 | + }, | ||
29 | + after: { | ||
30 | + type: DataTypes.JSON | ||
31 | + }, | ||
32 | + ipAddress:{ | ||
33 | + type: DataTypes.STRING(191) | ||
34 | + }, | ||
35 | + createdAt: { | ||
36 | + allowNull: false, | ||
37 | + type: DataTypes.DATE | ||
38 | + } | ||
39 | + }, { | ||
40 | + timestamps: true, | ||
41 | + updatedAt:false, | ||
42 | + sequelize, | ||
43 | + modelName: 'Log', | ||
44 | + }); | ||
45 | + return Log; | ||
46 | +}; |
backend/models/user.js
0 → 100644
1 | +'use strict' | ||
2 | +const { sequelize } = require('.') | ||
3 | +const { Model } = require('sequelize') | ||
4 | +module.exports = (sequelize, DataTypes) => { | ||
5 | + class User extends Model { } | ||
6 | + | ||
7 | + User.init({ | ||
8 | + id: { | ||
9 | + type: DataTypes.INTEGER, | ||
10 | + primaryKey: true, | ||
11 | + autoIncrement: true | ||
12 | + }, | ||
13 | + userId: { | ||
14 | + type: DataTypes.STRING(191), | ||
15 | + unique: true | ||
16 | + }, | ||
17 | + password: { | ||
18 | + type: DataTypes.STRING(191), | ||
19 | + }, | ||
20 | + name: { | ||
21 | + type: DataTypes.STRING(191), | ||
22 | + }, | ||
23 | + phone: { | ||
24 | + type: DataTypes.STRING(31), | ||
25 | + }, | ||
26 | + email: { | ||
27 | + type: DataTypes.STRING(191), | ||
28 | + }, | ||
29 | + isAdmin: { | ||
30 | + type: DataTypes.BOOLEAN, | ||
31 | + allowNull: false, | ||
32 | + defaultValue: false, | ||
33 | + } | ||
34 | + }, { | ||
35 | + sequelize, | ||
36 | + paranoid: true, | ||
37 | + modelName: 'User' | ||
38 | + }) | ||
39 | + | ||
40 | + return User | ||
41 | +} |
This diff could not be displayed because it is too large.
... | @@ -6,6 +6,7 @@ | ... | @@ -6,6 +6,7 @@ |
6 | "start": "sequelize db:migrate && node ./bin/www --env-update" | 6 | "start": "sequelize db:migrate && node ./bin/www --env-update" |
7 | }, | 7 | }, |
8 | "dependencies": { | 8 | "dependencies": { |
9 | + "bcrypt": "^5.0.1", | ||
9 | "cookie-parser": "~1.4.4", | 10 | "cookie-parser": "~1.4.4", |
10 | "cors": "^2.8.5", | 11 | "cors": "^2.8.5", |
11 | "debug": "~2.6.9", | 12 | "debug": "~2.6.9", |
... | @@ -17,6 +18,7 @@ | ... | @@ -17,6 +18,7 @@ |
17 | "devDependencies": { | 18 | "devDependencies": { |
18 | "eslint": "^6.8.0", | 19 | "eslint": "^6.8.0", |
19 | "eslint-plugin-node": "^11.1.0", | 20 | "eslint-plugin-node": "^11.1.0", |
21 | + "jsonwebtoken": "^8.5.1", | ||
20 | "sequelize-cli": "^6.2.0" | 22 | "sequelize-cli": "^6.2.0" |
21 | } | 23 | } |
22 | } | 24 | } | ... | ... |
backend/routes/authRouter.js
0 → 100644
1 | +const express = require ('express'); | ||
2 | +const router = express.Router(); | ||
3 | + | ||
4 | +const authController = require ('../controllers/authController') | ||
5 | +const { guestOnly, memberOnly, adminOnly } = require ('../middlewares/auth') | ||
6 | + | ||
7 | +router.post('/login', guestOnly, authController.login) | ||
8 | +router.post('/user/info', memberOnly, authController.userInfo) | ||
9 | +router.post('/admin_test', adminOnly, authController.adminTest) | ||
10 | + | ||
11 | +module.exports = router |
backend/utils/log.js
0 → 100644
1 | +const { Log } = require('../models') | ||
2 | +const url = require('url') | ||
3 | + | ||
4 | +exports.logging = async (module, actionType ,data, req) => { | ||
5 | + | ||
6 | + let logData = { | ||
7 | + module, | ||
8 | + actionType, | ||
9 | + data | ||
10 | + } | ||
11 | + if (req) { | ||
12 | + location = url.format({ | ||
13 | + protocol: req.protocol, | ||
14 | + host: req.get('host'), | ||
15 | + pathname: req.originalUrl | ||
16 | + }) | ||
17 | + if( Object.keys(req).includes('decoded') ) { | ||
18 | + logData.user = req.decoded.id | ||
19 | + } | ||
20 | + logData.location = location | ||
21 | + logData.ipAddress = req.ip | ||
22 | + } | ||
23 | + try { | ||
24 | + await Log.create(logData) | ||
25 | + } catch (error) { | ||
26 | + throw error | ||
27 | + } | ||
28 | +} |
backend/utils/response.js
0 → 100644
1 | +exports.sendError = function (res, code, message) { | ||
2 | + let result = { | ||
3 | + success: false, | ||
4 | + code, | ||
5 | + message, | ||
6 | + data: null | ||
7 | + } | ||
8 | + return res.status(200).json(result) | ||
9 | + | ||
10 | +} | ||
11 | + | ||
12 | +exports.sendResponse = function (res, data, code, message) { | ||
13 | + code = code || 200 | ||
14 | + const result = { | ||
15 | + success: true, | ||
16 | + code, | ||
17 | + message, | ||
18 | + data | ||
19 | + } | ||
20 | + return res.status(code).json(result) | ||
21 | +} |
backend/utils/validation.js
0 → 100644
1 | +exports.checkRequiredExist = (list, required) => { | ||
2 | + try { | ||
3 | + for (let key of required) { | ||
4 | + if (typeof list[key] === 'undefined' || list[key] === null) { | ||
5 | + return key | ||
6 | + } | ||
7 | + } | ||
8 | + return false | ||
9 | + } catch (error) { | ||
10 | + throw error | ||
11 | + } | ||
12 | +} | ||
13 | + | ||
14 | +exports.setValues = (bucket, keys) => { | ||
15 | + let result = { } | ||
16 | + try { | ||
17 | + for (let key of keys) { | ||
18 | + if (typeof bucket[key] !== 'undefined' && bucket[key] !== null) { | ||
19 | + result[key] = bucket[key] | ||
20 | + } | ||
21 | + } | ||
22 | + return result | ||
23 | + } catch (error) { | ||
24 | + throw error | ||
25 | + } | ||
26 | +} |
-
Please register or login to post a comment