From cc61bca776eb5935c5ffcabbb3a26161a5ac2df2 Mon Sep 17 00:00:00 2001 From: bangdk Date: Sat, 23 Nov 2024 07:59:18 +0900 Subject: [PATCH] 111 --- .../controllers/app/auth/auth.controller.js | 62 + fems-api/src/services/auth.service.js | 162 +- fems-app/src/components/layout/SideNav.tsx | 2 +- fems-app/src/types/auth.ts | 1 + fems-app/src/types/user.ts | 1 + fems-mqtt/data/mosquitto.db | Bin 156 -> 224 bytes fems-mqtt/log/mosquitto.log | 9232 +++++++++++++++++ 7 files changed, 9450 insertions(+), 10 deletions(-) diff --git a/fems-api/src/controllers/app/auth/auth.controller.js b/fems-api/src/controllers/app/auth/auth.controller.js index c80e313..c920236 100644 --- a/fems-api/src/controllers/app/auth/auth.controller.js +++ b/fems-api/src/controllers/app/auth/auth.controller.js @@ -5,6 +5,7 @@ const authService = require("../../../services/auth.service"); const { body } = require("express-validator"); const validate = require("../../../middleware/validator.middleware"); const authMiddleware = require("../../../middleware/auth.middleware"); +const logger = require("../../../config/logger"); router.post( "/login", @@ -60,4 +61,65 @@ router.post( } ); +// 토큰 검증 엔드포인트 +router.post("/validate", async (req, res, next) => { + try { + const token = req.headers.authorization?.replace("Bearer ", ""); + if (!token) { + return res.status(401).json({ + valid: false, + message: "Token is required", + }); + } + + const result = await authService.validateToken(token); + res.json(result); + } catch (error) { + logger.error("Validate endpoint error:", { + error: error.message, + stack: error.stack, + }); + + if ( + error.name === "JsonWebTokenError" || + error.name === "TokenExpiredError" + ) { + return res.status(401).json({ + valid: false, + message: error.message, + }); + } + + next(error); + } +}); + +// 토큰 갱신 엔드포인트 +router.post("/refresh", async (req, res, next) => { + try { + const token = req.headers.authorization?.replace("Bearer ", ""); + if (!token) { + return res.status(401).json({ + message: "Token is required", + }); + } + + const result = await authService.refreshToken(token); + res.json(result); + } catch (error) { + logger.error("Refresh token endpoint error:", { + error: error.message, + stack: error.stack, + }); + + if (error.name === "JsonWebTokenError") { + return res.status(401).json({ + message: "Invalid token", + }); + } + + next(error); + } +}); + module.exports = router; diff --git a/fems-api/src/services/auth.service.js b/fems-api/src/services/auth.service.js index 5fb046f..c7fa5a7 100644 --- a/fems-api/src/services/auth.service.js +++ b/fems-api/src/services/auth.service.js @@ -68,20 +68,11 @@ class AuthService { const companyData = userData.Company || {}; delete userData.Roles; - // const userInfo = { - // ...userData, - // companyName: companyData.name, - // businessNumber: companyData.businessNumber, - // contractEndDate: companyData.contractEndDate, - // permissions, - // }; const userInfo = { ...userData, - companyId: companyData.id, companyName: companyData.name, businessNumber: companyData.businessNumber, contractEndDate: companyData.contractEndDate, - branchId: userData.Branch?.id, branchName: userData.Branch?.name, permissions, }; @@ -205,12 +196,38 @@ class AuthService { const userInfo = { ...userData, companyName: companyData.name, + branchName: userData.Branch?.name, businessNumber: companyData.businessNumber, contractEndDate: companyData.contractEndDate, permissions, isEdgeLogin: true, // Edge 로그인 여부 표시 }; + // const userInfo = { + // // 기본 사용자 정보 + // id: userData.id, + // username: userData.username, + // name: userData.name, + // email: userData.email, + // phone: userData.phone, + // role: userData.role, + // isActive: userData.isActive, + + // // 회사 정보 + // companyId: companyData.id, + // companyName: companyData.name, + // businessNumber: companyData.businessNumber, + // contractEndDate: companyData.contractEndDate, + + // // 지점 정보 + // branchId: userData.Branch?.id, + // branchName: userData.Branch?.name, + + // // 권한 및 기타 정보 + // permissions, + // isEdgeLogin: true, + // }; + const token = this._generateToken(userInfo); return { @@ -333,6 +350,133 @@ class AuthService { expiresIn: config.jwt.expiresIn, }); } + + async validateToken(token) { + try { + const decoded = jwt.verify(token, config.jwt.secret); + + const user = await User.findOne({ + where: { id: decoded.id }, + include: [ + { + model: Company, + attributes: ["id", "name", "businessNumber", "contractEndDate"], + where: { isActive: true }, + }, + { + model: Branch, + attributes: ["id", "name"], + }, + { + model: Role, + through: { attributes: [] }, + attributes: ["id", "name", "permissions"], + required: false, + }, + ], + attributes: { + exclude: ["password"], + }, + }); + + if (!user || !user.isActive) { + throw new Error("User not found or inactive"); + } + + // 권한 정보 가공 + const permissions = this._processPermissions(user.Roles || []); + + // 사용자 정보 구성 + const userData = user.toJSON(); + const companyData = userData.Company || {}; + delete userData.Roles; + + const userInfo = { + ...userData, + companyId: companyData.id, + companyName: companyData.name, + businessNumber: companyData.businessNumber, + contractEndDate: companyData.contractEndDate, + branchId: userData.Branch?.id, + branchName: userData.Branch?.name, + permissions, + }; + + return { + valid: true, + user: userInfo, + }; + } catch (error) { + logger.error("Token validation error:", error.message); + throw error; + } + } + + async refreshToken(token) { + try { + const decoded = jwt.verify(token, config.jwt.secret, { + ignoreExpiration: true, + }); + + const user = await User.findOne({ + where: { id: decoded.id }, + include: this._getUserIncludes(), + }); + + if (!user || !user.isActive) { + throw new Error("User not found or inactive"); + } + + const userInfo = this._formatUserInfo(user); + const newToken = this._generateToken(userInfo); + + return { + token: newToken, + user: userInfo, + }; + } catch (error) { + logger.error("Token refresh error:", error.message); + throw error; + } + } + + _getUserIncludes() { + return [ + { + model: Company, + attributes: ["id", "name", "businessNumber", "contractEndDate"], + where: { isActive: true }, + }, + { + model: Branch, + attributes: ["id", "name"], + }, + { + model: Role, + through: { attributes: [] }, + attributes: ["id", "name", "permissions"], + required: false, + }, + ]; + } + + _formatUserInfo(user) { + const userData = user.toJSON(); + const companyData = userData.Company || {}; + const permissions = this._processPermissions(userData.Roles || []); + delete userData.Roles; + + return { + ...userData, + companyId: companyData.id, + companyName: companyData.name, + businessNumber: companyData.businessNumber, + contractEndDate: companyData.contractEndDate, + branchId: userData.Branch?.id, + branchName: userData.Branch?.name, + permissions, + }; + } } module.exports = new AuthService(); diff --git a/fems-app/src/components/layout/SideNav.tsx b/fems-app/src/components/layout/SideNav.tsx index d97b913..8f9c1aa 100644 --- a/fems-app/src/components/layout/SideNav.tsx +++ b/fems-app/src/components/layout/SideNav.tsx @@ -333,7 +333,7 @@ export function SideNav() {

- {user?.companyName} + {user?.companyName} - {user?.branchName}

사업자번호: {formatBusinessNumber(user?.businessNumber || "")} diff --git a/fems-app/src/types/auth.ts b/fems-app/src/types/auth.ts index 2e6088c..a6e1cfa 100644 --- a/fems-app/src/types/auth.ts +++ b/fems-app/src/types/auth.ts @@ -20,6 +20,7 @@ export interface User { businessNumber: string; contractEndDate: string; branchId?: string; + branchName: string; permissions: Permissions; // 권한 정보 추가 } diff --git a/fems-app/src/types/user.ts b/fems-app/src/types/user.ts index c7d8712..2835861 100644 --- a/fems-app/src/types/user.ts +++ b/fems-app/src/types/user.ts @@ -13,6 +13,7 @@ export interface User { businessNumber: string; contractEndDate: string; branchId?: string; + branchName: string; departmentId?: string; roleId?: string; Company?: { diff --git a/fems-mqtt/data/mosquitto.db b/fems-mqtt/data/mosquitto.db index 4ed8032404fad223ad93a09e423deaa30cd08924..e299078e84e5d14d31c7cc305ba43697324ce09c 100644 GIT binary patch delta 144 zcmbQk_<(VOyk0>i3j-K%Kqw%|1jL~e#G&GFVFos~iiy4wOvaWI(=`G?ifpFcU_?km uSTF%;kU1*Bk-_>&MfusOMf%A(nW=dt#rnC46`8rExkf