diff --git a/README.md b/README.md index a275faa36f153641ecdb8a187872bce88c63aca7..5ed7fa9498a8fd45efa44e4be053c1e3a5388653 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,15 @@ cd .. ``` +## DB 초기값 설정 + +앱에 필요한 기본 디비 테이블 및 초기값 생성 + +```bash +npx ts-node migrations\create.roles.ts +npx ts-node migrations\create.admin.ts +``` + ### 서버 실행 1. 프론트엔드 서버 실행 diff --git a/migrations/create.admin.ts b/migrations/create.admin.ts new file mode 100644 index 0000000000000000000000000000000000000000..1d1321530663cbfe52be9106feaf640d6c581d3b --- /dev/null +++ b/migrations/create.admin.ts @@ -0,0 +1,29 @@ +import { connect } from "mongoose"; +import { mongoUri } from "../src/config"; +import { Role, User } from "../src/models"; +import { userDb } from "../src/db"; + +const roles = [ + ["admin", 1], + ["manager", 10], + ["staff", 100], + ["user", 1000], + ["guest", 10000], +]; + +connect(mongoUri) + .then(async (mongoose) => { + const adminRole = await Role.findOne({ name: "admin" }); + if (!adminRole) { + throw new Error("admin role이 없습니다. 먼저 role 테이블을 만드세요."); + } + await userDb.createUser({ + email: "admin@example.com", + name: "admin", + role: adminRole?._id, + password: "asdfasdf", + }); + console.log("admin 계정이 만들어졌습니다."); + await mongoose.disconnect(); + }) + .catch((error) => console.log("롤 초기 생성 에러", error)); diff --git a/migrations/create.roles.ts b/migrations/create.roles.ts new file mode 100644 index 0000000000000000000000000000000000000000..824d7611fb2be149aed1ec0a6d048191b04fbb95 --- /dev/null +++ b/migrations/create.roles.ts @@ -0,0 +1,28 @@ +import { connect } from "mongoose"; +import { mongoUri } from "../src/config"; +import { Role } from "../src/models"; + +const roles = [ + ["admin", 1], + ["manager", 10], + ["staff", 100], + ["user", 1000], + ["guest", 10000], +]; + +connect(mongoUri) + .then(async (mongoose) => { + const retRoles = roles.map(async ([name, priority]) => { + const result = await Role.create({ name, priority }); + return result; + }); + try { + await Promise.all(retRoles); + console.log("roles created successfully."); + } catch (error) { + console.log("error:", error); + } finally { + await mongoose.disconnect(); + } + }) + .catch((error) => console.log("롤 초기 생성 에러", error)); diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index 5a7bf8ae881b8a64e771d9820d4b2af6871fbbb2..1727db90fa3905843690fdbf5cadc853f3c55ee3 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -4,12 +4,41 @@ import jwt, { JwtPayload } from "jsonwebtoken"; import isLength from "validator/lib/isLength"; import isEmail from "validator/lib/isEmail"; import { asyncWrap } from "../helpers"; -import { userDb } from "../db"; +import { roleDb, userDb } from "../db"; import { jwtCofig, envConfig, cookieConfig } from "../config"; export interface TypedRequestAuth extends Request { auth: T; } + +/** + * 지정된 역할 이상으로 권한이 있는지를 판단하는 미들웨어를 반환합니다. + * @param roleName 역할 문자열 + * @returns 미들웨어 + */ +export const hasRole = (roleName: string) => { + // roleName 이상으로 허락하는 것 + return async (reqExp: Request, res: Response, next: NextFunction) => { + const req = reqExp as TypedRequestAuth<{ userId: string }>; + + if (!req.auth) { + return res.status(401).send("로그인이 필요합니다"); + } + + const { userId } = req.auth; + if (!(await userDb.isValidUserId(userId))) { + return res.status(401).send("유효한 사용자가 아닙니다"); + } + const userRole = await roleDb.findRoleByUserId(userId); + const maxRole = await roleDb.findRoleByName(roleName); + if (maxRole && Number(maxRole.priority) >= Number(userRole.priority)) { + return next(); + } else { + return res.status(401).send("이용 권한이 없습니다"); + } + }; +}; + export const login = asyncWrap(async (req, res) => { const { email, password } = req.body; console.log(`email: ${email}, password: ${password}`); diff --git a/src/controllers/index.ts b/src/controllers/index.ts index 82eed0c70de8f110ee7b35491cc08cdbc66f5ec0..1c27ea6aed19cd31751cdfb8d2acd814d4dd6113 100644 --- a/src/controllers/index.ts +++ b/src/controllers/index.ts @@ -1,3 +1,4 @@ -export * as userCtrl from "./user.controller"; export * as authCtrl from "./auth.controller"; export * as postCtrl from "./post.controller"; +export * as roleCtrl from "./role.controller"; +export * as userCtrl from "./user.controller"; diff --git a/src/controllers/role.controller.ts b/src/controllers/role.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..f672f78f32486b20d4e5ce413e66e627cc7bf03d --- /dev/null +++ b/src/controllers/role.controller.ts @@ -0,0 +1,7 @@ +import { roleDb } from "../db"; +import { asyncWrap } from "../helpers"; + +export const getRoles = asyncWrap(async (req, res, next) => { + const roles = await roleDb.getAllRoles(); + return res.json(roles); +}); diff --git a/src/db/index.ts b/src/db/index.ts index 64daf8dca85fad679735d4938b4fb850900ca5c3..78acdc553d8d38f4a2a8a3a2c89a3cd5ea8500eb 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -1,2 +1,3 @@ -export * as userDb from "./user.db"; +export * as roleDb from "./role.db"; export * as postDb from "./post.db"; +export * as userDb from "./user.db"; diff --git a/src/db/role.db.ts b/src/db/role.db.ts new file mode 100644 index 0000000000000000000000000000000000000000..e7e919a470ae5ddffcb6bae43384a4fffada5200 --- /dev/null +++ b/src/db/role.db.ts @@ -0,0 +1,22 @@ +import { Role, User } from "../models"; + +export const findRoleById = async (roleId: string) => { + const role = await Role.findById(roleId); + return role; +}; + +export const findRoleByName = async (roleName: string) => { + const role = await Role.findOne({ name: roleName }); + return role; +}; + +export const findRoleByUserId = async (userId: string) => { + const user = await User.findById(userId).populate("role"); + const role = user?.get("role"); + return role; +}; + +export const getAllRoles = async () => { + const roles = await Role.find({}); + return roles; +}; diff --git a/src/db/user.db.ts b/src/db/user.db.ts index a81da0d163d7306f1ebbf2fa39da4a48c8ffc3e3..56996632f87d897feede94e73d2a5f0c6c94a5c6 100644 --- a/src/db/user.db.ts +++ b/src/db/user.db.ts @@ -1,17 +1,25 @@ import bcrypt from "bcryptjs"; -import { Files } from "formidable"; import { ObjectId } from "mongoose"; -import { IUser, Post, User } from "../models"; +import { IUser, Role, Post, User } from "../models"; export const createUser = async (user: IUser) => { // 비밀번호 암호화 const hash = await bcrypt.hash(user.password, 10); - const newUser = await User.create({ + // 사용자 역할 추가: 기본값은 "user" + let userRole = null; + if (user.role) { + userRole = await Role.findById(user.role); + } else { + userRole = await Role.findOne({ name: "user" }); + } + const newUser = new User({ email: user.email, password: hash, - name: user.name, + role: userRole, + isNew: true, }); - return newUser; + const retUser = await newUser.save(); + return retUser; }; export const findUserByEmail = async ( @@ -31,6 +39,10 @@ export const findUserByPostId = async (postId: string) => { const post = await Post.findOne({ _id: postId }).populate("user"); return post?.user; }; +export const getProfile = async (userId: string) => { + const profile = await User.findById(userId); + return profile; //이름 수정 +}; export const getUsers = async () => { const users = await User.find({}); @@ -46,9 +58,13 @@ export const isUser = async (email: string) => { } }; -export const getProfile = async (userId: string) => { - const profile = await User.findById(userId); - return profile; //이름 수정 +export const isValidUserId = async (userId: string) => { + const user = await User.findById(userId); + if (user) { + return true; + } else { + return false; + } }; export const postPicture = ( diff --git a/src/models/index.ts b/src/models/index.ts index 44b5b8a54e99b65485de80aa064b39770cb7fdb1..d0554aab37fd8273f348d78c134ba3812db4ee31 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -1,2 +1,3 @@ export { default as User, IUser } from "./user.model"; export { default as Post, PostType } from "./post.model"; +export { default as Role } from "./role.model"; diff --git a/src/models/post.model.ts b/src/models/post.model.ts index 97eba1ef1aecf187dc2887d2cc76c501dee2afc7..061aacf9776ef2b3919aae80a3be3898a72b4ec9 100644 --- a/src/models/post.model.ts +++ b/src/models/post.model.ts @@ -1,4 +1,4 @@ -import { Document, model, Schema, Types } from "mongoose"; +import { model, Schema, Types } from "mongoose"; export interface PostType { title: string; diff --git a/src/models/role.model.ts b/src/models/role.model.ts index 7c5243539b5322ecdc9d21bccf038c231fb4bbb5..9b1724288a8338ef11e81d08b3690fe19924a88c 100644 --- a/src/models/role.model.ts +++ b/src/models/role.model.ts @@ -5,9 +5,12 @@ interface IRole { priority: number; } -const schema = new Schema({ - name: { type: String }, - priority: { type: Number }, -}); +const schema = new Schema( + { + name: { type: String }, + priority: { type: Number }, + }, + { toJSON: { versionKey: false } } +); export default model("Role", schema); diff --git a/src/routes/role.route.ts b/src/routes/role.route.ts new file mode 100644 index 0000000000000000000000000000000000000000..c523025abbddf1328cc4b1c38051ce3bbea0c4c1 --- /dev/null +++ b/src/routes/role.route.ts @@ -0,0 +1,10 @@ +import express from "express"; +import { authCtrl, roleCtrl } from "../controllers"; + +const router = express.Router(); + +router.all("/", authCtrl.requireLogin); + +router.route("/").get(authCtrl.hasRole("admin"), roleCtrl.getRoles); + +export default router; diff --git a/src/routes/user.route.ts b/src/routes/user.route.ts index 246d8c0ffca440309603be9053bf748cf81d938a..af3669b758664865585323416a9bf67dde1215bc 100644 --- a/src/routes/user.route.ts +++ b/src/routes/user.route.ts @@ -6,6 +6,6 @@ const router = express.Router(); router .route("/") .get(authCtrl.requireLogin, userCtrl.getUsers) - .post(authCtrl.requireLogin, userCtrl.createUser); + .post(authCtrl.requireLogin, authCtrl.hasRole("admin"), userCtrl.createUser); export default router;