diff --git a/fems-api/src/controllers/app/auth/auth.controller.js b/fems-api/src/controllers/app/auth/auth.controller.js index bbfdcad..1606731 100644 --- a/fems-api/src/controllers/app/auth/auth.controller.js +++ b/fems-api/src/controllers/app/auth/auth.controller.js @@ -34,4 +34,33 @@ router.post("/logout", authMiddleware, async (req, res, next) => { } }); +router.post( + "/edge-login", + [ + body("username").notEmpty().withMessage("Username is required"), + body("password").notEmpty().withMessage("Password is required"), + body("businessNumber") + .notEmpty() + .withMessage("Business number is required") + .matches(/^[0-9-]{10,20}$/) + .withMessage("Invalid business number format"), + validate, + ], + async (req, res, next) => { + try { + const { username, password, businessNumber } = req.body; + const result = await authService.edgeLogin( + username, + password, + businessNumber, + req.ip, + req.headers["user-agent"] + ); + res.json(result); + } catch (error) { + next(error); + } + } +); + module.exports = router; diff --git a/fems-api/src/services/auth.service.js b/fems-api/src/services/auth.service.js index f8a779b..47e9ab5 100644 --- a/fems-api/src/services/auth.service.js +++ b/fems-api/src/services/auth.service.js @@ -79,6 +79,111 @@ class AuthService { }; } + async edgeLogin(username, password, businessNumber, ipAddress, userAgent) { + // 1. 먼저 회사 검증 + const company = await Company.findOne({ + where: { + businessNumber, + isActive: true, + }, + }); + + if (!company) { + throw new Error("Invalid business number or company not found"); + } + + // 2. 사용자 검증 + const userWithPassword = await User.findOne({ + where: { + username, + companyId: company.id, + isActive: true, + }, + attributes: ["id", "password", "isActive"], + }); + + if (!userWithPassword) { + await this._logAuthAttempt( + null, + "edge_failed_login", + ipAddress, + userAgent, + businessNumber + ); + throw new Error("Invalid credentials"); + } + + // 3. 비밀번호 검증 + const isValidPassword = await userWithPassword.validatePassword(password); + if (!isValidPassword) { + await this._logAuthAttempt( + userWithPassword.id, + "edge_failed_login", + ipAddress, + userAgent, + businessNumber + ); + throw new Error("Invalid credentials"); + } + + // 4. 사용자 정보 조회 (상세 정보) + const user = await User.findOne({ + where: { id: userWithPassword.id }, + include: [ + { + model: Company, + attributes: ["id", "name", "businessNumber"], + where: { isActive: true }, // 활성화된 회사만 + }, + { + model: Branch, + attributes: ["id", "name"], + }, + { + model: Role, + through: { attributes: [] }, + attributes: ["id", "name", "permissions"], + required: false, + }, + ], + attributes: { + exclude: ["password"], + }, + }); + + // 5. 마지막 로그인 시간 업데이트 + await User.update({ lastLoginAt: new Date() }, { where: { id: user.id } }); + + // 6. 로그인 기록 + await this._logAuthAttempt( + user.id, + "edge_login", + ipAddress, + userAgent, + businessNumber + ); + + // 7. 권한 정보 처리 + const permissions = this._processPermissions(user.Roles || []); + + // 8. 응답 데이터 구성 + const userData = user.toJSON(); + delete userData.Roles; + + const userInfo = { + ...userData, + permissions, + isEdgeLogin: true, // Edge 로그인 여부 표시 + }; + + const token = this._generateToken(userInfo); + + return { + token, + user: userInfo, + }; + } + _processPermissions(roles) { // 기본 권한 설정 (모든 사용자가 가져야 할 기본 권한) const permissions = { @@ -101,7 +206,13 @@ class AuthService { return true; } - async _logAuthAttempt(userId, action, ipAddress, userAgent) { + async _logAuthAttempt( + userId, + action, + ipAddress, + userAgent, + businessNumber = null + ) { if (userId) { // userId가 있을 때만 로그 생성 await AuthLog.create({ @@ -109,6 +220,7 @@ class AuthService { action, ipAddress, userAgent, + metadata: businessNumber ? { businessNumber } : null, }); } } diff --git a/fems-app/src/app/(admin)/layout.tsx b/fems-app/src/app/(admin)/layout.tsx index a9ac5fd..98c56b3 100644 --- a/fems-app/src/app/(admin)/layout.tsx +++ b/fems-app/src/app/(admin)/layout.tsx @@ -8,6 +8,7 @@ const AdminLayout = ({ children }: { children: React.ReactNode }) => { return (
+ {/* 왼쪽 사이드바 */} diff --git a/fems-app/src/middleware.tsx b/fems-app/src/middleware.tsx index 32dd468..ef0dfa2 100644 --- a/fems-app/src/middleware.tsx +++ b/fems-app/src/middleware.tsx @@ -1,7 +1,8 @@ // src/middleware.ts import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; -import { decodeToken, hasPermission, hasAnyPermission } from "@/lib/jwt"; +// import { decodeToken, hasPermission, hasAnyPermission } from "@/lib/jwt"; +import { decodeToken, hasPermission } from "@/lib/jwt"; export function middleware(request: NextRequest) { const token = request.cookies.get("token")?.value; @@ -37,29 +38,29 @@ export function middleware(request: NextRequest) { } } - // 사용자 관리 페이지 - if (request.nextUrl.pathname.startsWith("/users")) { - const hasUserManageAccess = hasAnyPermission(tokenData, [ - "users:manage", - "users:view", - ]); + // // 사용자 관리 페이지 + // if (request.nextUrl.pathname.startsWith("/users")) { + // const hasUserManageAccess = hasAnyPermission(tokenData, [ + // "users:manage", + // "users:view", + // ]); - if (!hasUserManageAccess) { - return NextResponse.redirect(new URL("/dashboard/overview", request.url)); - } - } + // if (!hasUserManageAccess) { + // return NextResponse.redirect(new URL("/dashboard/overview", request.url)); + // } + // } - // 부서 관리 페이지 - if (request.nextUrl.pathname.startsWith("/departments")) { - const hasDepartmentAccess = hasAnyPermission(tokenData, [ - "departments:manage", - "departments:view", - ]); + // // 부서 관리 페이지 + // if (request.nextUrl.pathname.startsWith("/departments")) { + // const hasDepartmentAccess = hasAnyPermission(tokenData, [ + // "departments:manage", + // "departments:view", + // ]); - if (!hasDepartmentAccess) { - return NextResponse.redirect(new URL("/dashboard/overview", request.url)); - } - } + // if (!hasDepartmentAccess) { + // return NextResponse.redirect(new URL("/dashboard/overview", request.url)); + // } + // } // 응답 헤더에 사용자 권한 정보 추가 (옵션) const response = NextResponse.next();