송용우

Update User Management API and Sync Mongo Server

1 SERVER_PORT=4000 1 SERVER_PORT=4000
2 -MONGO_URL=mongodb://localhost:27017/jaksimsamil
...\ No newline at end of file ...\ No newline at end of file
2 +MONGO_URL=mongodb://localhost:27017/jaksimsamil
3 +JWT_SECERET=fcbyHhPCxQYLLh98b97hdyAC6tGctHQ7rQDissQx
...\ No newline at end of file ...\ No newline at end of file
......
1 { 1 {
2 - "env": { 2 + "parser": "babel-eslint",
3 - "commonjs": true, 3 + "env": {
4 - "es6": true, 4 + "commonjs": true,
5 - "node": true 5 + "es6": true,
6 - }, 6 + "node": true
7 - "extends": "eslint:recommended", 7 + },
8 - "globals": { 8 + "extends": "eslint:recommended",
9 - "Atomics": "readonly", 9 + "globals": {
10 - "SharedArrayBuffer": "readonly" 10 + "Atomics": "readonly",
11 - }, 11 + "SharedArrayBuffer": "readonly"
12 - "parserOptions": { 12 + },
13 - "ecmaVersion": 11 13 + "parserOptions": {
14 - }, 14 + "ecmaVersion": 11
15 - "rules": { 15 + },
16 - } 16 + "rules": {}
17 } 17 }
......
...@@ -18,17 +18,18 @@ ...@@ -18,17 +18,18 @@
18 18
19 ## API Table 19 ## API Table
20 20
21 -| group | description | method | URL | Detail | Auth | 21 +| group | description | method | URL | Detail | Auth |
22 -| ------- | ------------------------ | ------ | -------------------------- | -------- | --------- | 22 +| -------- | ------------------------ | ------ | -------------------------- | -------- | --------- |
23 -| user | 유저 등록 | POST | api/user | 바로가기 | JWT Token | 23 +| user | 유저 등록 | POST | api/user | 바로가기 | JWT Token |
24 -| user | 유저 삭제 | DELETE | api/user:id | 바로가기 | JWT Token | 24 +| user | 유저 삭제 | DELETE | api/user:id | 바로가기 | JWT Token |
25 -| user | 특정 유저 조회 | GET | api/user:id | 바로가기 | None | 25 +| user | 특정 유저 조회 | GET | api/user:id | 바로가기 | None |
26 -| user | 전체 유저 조회 | GET | api/user | 바로가기 | JWT Token | 26 +| user | 전체 유저 조회 | GET | api/user | 바로가기 | JWT Token |
27 -| friend | 유저 친구 등록 | POST | api/friend | 바로가기 | JWT Token | 27 +| friend | 유저 친구 등록 | POST | api/friend | 바로가기 | JWT Token |
28 -| friend | 유저의 친구 조회 | GET | api/friend:id | 바로가기 | None | 28 +| friend | 유저의 친구 조회 | GET | api/friend:id | 바로가기 | None |
29 -| profile | 유저가 푼 문제 조회 | GET | api/profile/solved:id | 바로가기 | None | 29 +| profile | 유저가 푼 문제 조회 | GET | api/profile/solved:id | 바로가기 | None |
30 -| profile | 유저가 푼 문제 개수 조회 | GET | api/profile/solvednum:id | 바로가기 | None | 30 +| profile | 유저가 푼 문제 개수 조회 | GET | api/profile/solvednum:id | 바로가기 | None |
31 -| profile | 추천 문제 조회 | GET | api/profile/recommendps:id | 바로가기 | None | 31 +| profile | 추천 문제 조회 | GET | api/profile/recommendps:id | 바로가기 | None |
32 -| notify | 슬랙 메시지 전송 요청 | POST | api/notify/slack | 바로가기 | Jwt Token | 32 +| notify | 슬랙 메시지 전송 요청 | POST | api/notify/slack | 바로가기 | Jwt Token |
33 -| auth | 로그인 | POST | api/auth/login | 바로가기 | None | 33 +| auth | 로그인 | POST | api/auth/login | 바로가기 | None |
34 -| auth | 로그아웃 | GET | api/auth/logout | 바로가기 | JWT Token | 34 +| auth | 로그아웃 | GET | api/auth/logout | 바로가기 | JWT Token |
35 +| register | 회원가입 | POST | api/auth/register | 바로가기 | None |
......
...@@ -2,14 +2,17 @@ const express = require("express"); ...@@ -2,14 +2,17 @@ const express = require("express");
2 const morgan = require("morgan"); 2 const morgan = require("morgan");
3 const mongoose = require("mongoose"); 3 const mongoose = require("mongoose");
4 const app = express(); 4 const app = express();
5 +const bodyParser = require("body-parser");
5 const api = require("./src/api"); 6 const api = require("./src/api");
7 +const jwtMiddleware = require("./src/lib/jwtMiddleware");
6 require("dotenv").config(); 8 require("dotenv").config();
7 const { SERVER_PORT, MONGO_URL } = process.env; 9 const { SERVER_PORT, MONGO_URL } = process.env;
8 -app.use( 10 +app.use(morgan("dev"));
9 - morgan("[:date[iso]] :method :status :url :response-time(ms) :user-agent")
10 -);
11 app.use(express.json()); 11 app.use(express.json());
12 app.use(express.urlencoded({ extended: false })); 12 app.use(express.urlencoded({ extended: false }));
13 +app.use(bodyParser());
14 +app.use(jwtMiddleware);
15 +
13 app.use("/api", api); 16 app.use("/api", api);
14 mongoose 17 mongoose
15 .connect(MONGO_URL, { useNewUrlParser: true, useFindAndModify: false }) 18 .connect(MONGO_URL, { useNewUrlParser: true, useFindAndModify: false })
......
...@@ -4,15 +4,20 @@ ...@@ -4,15 +4,20 @@
4 "main": "index.js", 4 "main": "index.js",
5 "license": "MIT", 5 "license": "MIT",
6 "dependencies": { 6 "dependencies": {
7 + "bcrypt": "^4.0.1",
8 + "body-parser": "^1.19.0",
7 "dotenv": "^8.2.0", 9 "dotenv": "^8.2.0",
8 "eslint-config-prettier": "^6.11.0", 10 "eslint-config-prettier": "^6.11.0",
9 "express": "^4.17.1", 11 "express": "^4.17.1",
10 "fs": "^0.0.1-security", 12 "fs": "^0.0.1-security",
13 + "joi": "^14.3.1",
14 + "jsonwebtoken": "^8.5.1",
11 "mongoose": "^5.9.17", 15 "mongoose": "^5.9.17",
12 "morgan": "^1.10.0", 16 "morgan": "^1.10.0",
13 "path": "^0.12.7" 17 "path": "^0.12.7"
14 }, 18 },
15 "devDependencies": { 19 "devDependencies": {
20 + "babel-eslint": "^10.1.0",
16 "eslint": "^7.1.0", 21 "eslint": "^7.1.0",
17 "nodemon": "^2.0.4" 22 "nodemon": "^2.0.4"
18 }, 23 },
......
1 +const Joi = require("joi");
2 +const User = require("../../models/user");
3 +/*
4 +POST /api/auth/register
5 +{
6 + username: 'userid'
7 + password: 'userpassword'
8 +}
9 +*/
10 +exports.register = async (ctx) => {
11 + const schema = Joi.object().keys({
12 + username: Joi.string().alphanum().min(3).max(20).required(),
13 + password: Joi.string().required(),
14 + });
15 + const result = Joi.validate(ctx.request.body, schema);
16 + if (result.error) {
17 + ctx.status = 400;
18 + ctx.body = result.error;
19 + return;
20 + }
21 +
22 + const { username, password } = ctx.request.body;
23 + try {
24 + const isNameExist = await User.findByUsername(username);
25 + if (isNameExist) {
26 + ctx.status = 409;
27 + return;
28 + }
29 + const user = new User({
30 + username,
31 + });
32 + await user.setPassword(password);
33 + await user.save();
34 + ctx.body = user.serialize();
35 +
36 + const token = user.generateToekn();
37 + ctx.cookies.set("acces_token", token, {
38 + //3일동안 유효
39 + maxAge: 1000 * 60 * 60 * 24 * 3,
40 + httpOnly: true,
41 + });
42 + } catch (e) {
43 + ctx.throw(500, e);
44 + }
45 +};
46 +/*
47 +POST /api/auth/login
48 +{
49 + username: 'userid'
50 + password: 'userpassword'
51 +}
52 + */
53 +exports.login = async (ctx) => {
54 + const { username, password } = ctx.request.body;
55 + if (!username || !password) {
56 + ctx.status = 401;
57 + return;
58 + }
59 + try {
60 + const user = await User.findByUsername(username);
61 + if (!user) {
62 + ctx.status = 401;
63 + return;
64 + }
65 + const isPasswordValid = await user.checkPassword(password);
66 + if (!isPasswordValid) {
67 + ctx.status = 401;
68 + return;
69 + }
70 + ctx.body = user.serialize();
71 + const token = user.generateToken();
72 + ctx.cookies.set("acces_token", token, {
73 + //7일동안 유효
74 + maxAge: 1000 * 60 * 60 * 24 * 7,
75 + httpOnly: true,
76 + });
77 + } catch (e) {
78 + ctx.throw(500, e);
79 + }
80 +};
81 +/*
82 +GET api/auth/check
83 +*/
84 +exports.check = async (ctx) => {
85 + const { user } = ctx.state;
86 + if (!user) {
87 + ctx.status = 401;
88 + return;
89 + }
90 + ctx.body = user;
91 +};
92 +/*
93 +POST /api/auth/logout
94 +*/
95 +exports.logout = async (ctx) => {
96 + ctx.cookies.set("access_token");
97 + ctx.status = 204;
98 +};
...@@ -3,5 +3,9 @@ const router = express.Router(); ...@@ -3,5 +3,9 @@ const router = express.Router();
3 3
4 router.post("/login"); 4 router.post("/login");
5 router.get("/logout"); 5 router.get("/logout");
6 +router.post("/register");
7 +
8 +
9 +
6 10
7 module.exports = router; 11 module.exports = router;
......
1 +const jwt = require("jsonwebtoken");
2 +const User = require("../models/user");
3 +const jwtMiddleware = async (ctx, next) => {
4 + const token = ctx.cookies.get("access_token");
5 + if (!token) {
6 + //토큰이 없을 때
7 + return next();
8 + }
9 + try {
10 + const decoded = jwt.verify(token, process.env.JWT_TOKEN);
11 + ctx.state.user = {
12 + _id: decoded._id,
13 + username: decoded.username,
14 + };
15 + //토큰의 남은 유효 기간이 2일 이하라면 재발급
16 + if (decoded.exp - Date.now() / 1000 < 60 * 60 * 24 * 2) {
17 + const user = await User.findById(decoded._id);
18 + const token = user.generateToken();
19 + ctx.cookies.set("access_token", token, {
20 + maxAge: 1000 * 60 * 60 * 24 * 7,
21 + httpOnly: true,
22 + });
23 + }
24 + return next();
25 + } catch (e) {
26 + return next();
27 + }
28 +};
29 +
30 +module.exports = jwtMiddleware;
1 +const mongoose = require("mongoose");
2 +const bcrypt = require("bcrypt");
3 +const jwt = require("jsonwebtoken");
4 +const Schema = mongoose.Schema;
5 +
6 +const UserSchema = new Schema({
7 + username: String,
8 + hashedPassword: String,
9 +});
10 +
11 +UserSchema.methods.setPassword = async function (password) {
12 + const hash = await bcrypt.hash(password, 10);
13 + this.hashedPassword = hash;
14 +};
15 +UserSchema.methodss.checkPassword = async function (password) {
16 + const result = await bcrypt.compare(password, this.hashedPassword);
17 + return result;
18 +};
19 +UserSchema.statics.findByUsername = function (username) {
20 + return this.findOne({ username });
21 +};
22 +UserSchema.methods.serialize = function () {
23 + const data = this.toJSON();
24 + delete data.hashedPassword;
25 + return data;
26 +};
27 +UserSchema.methods.generateToken = function () {
28 + const token = jwt.sign(
29 + {
30 + _id: this.id,
31 + username: this.username,
32 + },
33 + process.env.JWT_SECRET,
34 + {
35 + expiresIn: "7d",
36 + }
37 + );
38 + return token;
39 +};
40 +const User = mongoose.model("User", UserSchema);
41 +module.exports = User;
This diff could not be displayed because it is too large.
This diff is collapsed. Click to expand it.