auto commit
This commit is contained in:
parent
35e2a31442
commit
f03a9209ea
@ -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;
|
module.exports = router;
|
||||||
|
@ -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) {
|
_processPermissions(roles) {
|
||||||
// 기본 권한 설정 (모든 사용자가 가져야 할 기본 권한)
|
// 기본 권한 설정 (모든 사용자가 가져야 할 기본 권한)
|
||||||
const permissions = {
|
const permissions = {
|
||||||
@ -101,7 +206,13 @@ class AuthService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _logAuthAttempt(userId, action, ipAddress, userAgent) {
|
async _logAuthAttempt(
|
||||||
|
userId,
|
||||||
|
action,
|
||||||
|
ipAddress,
|
||||||
|
userAgent,
|
||||||
|
businessNumber = null
|
||||||
|
) {
|
||||||
if (userId) {
|
if (userId) {
|
||||||
// userId가 있을 때만 로그 생성
|
// userId가 있을 때만 로그 생성
|
||||||
await AuthLog.create({
|
await AuthLog.create({
|
||||||
@ -109,6 +220,7 @@ class AuthService {
|
|||||||
action,
|
action,
|
||||||
ipAddress,
|
ipAddress,
|
||||||
userAgent,
|
userAgent,
|
||||||
|
metadata: businessNumber ? { businessNumber } : null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ const AdminLayout = ({ children }: { children: React.ReactNode }) => {
|
|||||||
return (
|
return (
|
||||||
<AdminGuard>
|
<AdminGuard>
|
||||||
<div className="h-screen flex">
|
<div className="h-screen flex">
|
||||||
|
{/* 왼쪽 사이드바 */}
|
||||||
<aside className="w-64 h-screen flex-shrink-0 bg-gray-800">
|
<aside className="w-64 h-screen flex-shrink-0 bg-gray-800">
|
||||||
<SideNav />
|
<SideNav />
|
||||||
</aside>
|
</aside>
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
// src/middleware.ts
|
// src/middleware.ts
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import type { NextRequest } 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) {
|
export function middleware(request: NextRequest) {
|
||||||
const token = request.cookies.get("token")?.value;
|
const token = request.cookies.get("token")?.value;
|
||||||
@ -37,29 +38,29 @@ export function middleware(request: NextRequest) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 사용자 관리 페이지
|
// // 사용자 관리 페이지
|
||||||
if (request.nextUrl.pathname.startsWith("/users")) {
|
// if (request.nextUrl.pathname.startsWith("/users")) {
|
||||||
const hasUserManageAccess = hasAnyPermission(tokenData, [
|
// const hasUserManageAccess = hasAnyPermission(tokenData, [
|
||||||
"users:manage",
|
// "users:manage",
|
||||||
"users:view",
|
// "users:view",
|
||||||
]);
|
// ]);
|
||||||
|
|
||||||
if (!hasUserManageAccess) {
|
// if (!hasUserManageAccess) {
|
||||||
return NextResponse.redirect(new URL("/dashboard/overview", request.url));
|
// return NextResponse.redirect(new URL("/dashboard/overview", request.url));
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 부서 관리 페이지
|
// // 부서 관리 페이지
|
||||||
if (request.nextUrl.pathname.startsWith("/departments")) {
|
// if (request.nextUrl.pathname.startsWith("/departments")) {
|
||||||
const hasDepartmentAccess = hasAnyPermission(tokenData, [
|
// const hasDepartmentAccess = hasAnyPermission(tokenData, [
|
||||||
"departments:manage",
|
// "departments:manage",
|
||||||
"departments:view",
|
// "departments:view",
|
||||||
]);
|
// ]);
|
||||||
|
|
||||||
if (!hasDepartmentAccess) {
|
// if (!hasDepartmentAccess) {
|
||||||
return NextResponse.redirect(new URL("/dashboard/overview", request.url));
|
// return NextResponse.redirect(new URL("/dashboard/overview", request.url));
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 응답 헤더에 사용자 권한 정보 추가 (옵션)
|
// 응답 헤더에 사용자 권한 정보 추가 (옵션)
|
||||||
const response = NextResponse.next();
|
const response = NextResponse.next();
|
||||||
|
Loading…
Reference in New Issue
Block a user