summaryrefslogtreecommitdiff
path: root/middleware.ts
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-06-27 01:16:20 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-06-27 01:16:20 +0000
commite9897d416b3e7327bbd4d4aef887eee37751ae82 (patch)
treebd20ce6eadf9b21755bd7425492d2d31c7700a0e /middleware.ts
parent3bf1952c1dad9d479bb8b22031b06a7434d37c37 (diff)
(대표님) 20250627 오전 10시 작업사항
Diffstat (limited to 'middleware.ts')
-rw-r--r--middleware.ts151
1 files changed, 111 insertions, 40 deletions
diff --git a/middleware.ts b/middleware.ts
index ff483818..ed471109 100644
--- a/middleware.ts
+++ b/middleware.ts
@@ -17,7 +17,8 @@ const publicPaths = [
'/partners/repository',
'/partners/signup',
'/api/auth',
- '/spreadTest'
+ '/spreadTest',
+ '/auth/reset-password',
];
// 경로가 공개 경로인지 확인하는 함수
@@ -61,6 +62,50 @@ function getDomainRedirectPath(path: string, domain: string, lng: string) {
return null;
}
+// 세션 타임아웃 체크 함수
+function checkSessionTimeout(token: any): { isExpired: boolean; isExpiringSoon: boolean } {
+ if (!token?.sessionExpiredAt) {
+ return { isExpired: false, isExpiringSoon: false };
+ }
+
+ const now = Date.now();
+ const expiresAt = token.sessionExpiredAt;
+ const timeUntilExpiry = expiresAt - now;
+ const warningThreshold = 10 * 60 * 1000; // 10분
+
+ return {
+ isExpired: timeUntilExpiry <= 0,
+ isExpiringSoon: timeUntilExpiry <= warningThreshold && timeUntilExpiry > 0
+ };
+}
+
+// 로그인 페이지 URL 생성 함수 (세션 만료 정보 포함)
+function createLoginUrl(pathname: string, detectedLng: string, origin: string, request: NextRequest, reason?: string) {
+ let loginPath;
+
+ // 경로에 따라 적절한 로그인 페이지 선택
+ if (pathname.includes('/partners') || pathname.startsWith(`/${detectedLng}/vendor`)) {
+ loginPath = `/${detectedLng}/partners`;
+ } else {
+ loginPath = `/${detectedLng}/evcp`;
+ }
+
+ const redirectUrl = new URL(loginPath, origin);
+
+ // 로그인 후 원래 페이지로 리다이렉트하기 위해 callbackUrl 추가
+ redirectUrl.searchParams.set('callbackUrl', request.nextUrl.pathname + request.nextUrl.search);
+
+ // 세션 만료 관련 정보 추가
+ if (reason) {
+ redirectUrl.searchParams.set('reason', reason);
+ if (reason === 'expired') {
+ redirectUrl.searchParams.set('message', '세션이 만료되었습니다. 다시 로그인해주세요.');
+ }
+ }
+
+ return redirectUrl;
+}
+
export async function middleware(request: NextRequest) {
/**
* 1. 쿠키에서 언어 가져오기
@@ -110,7 +155,22 @@ export async function middleware(request: NextRequest) {
const token = await getToken({ req: request });
/**
- * 6. 인증된 사용자의 도메인-URL 일치 확인 및 리다이렉션
+ * 6. 세션 타임아웃 체크 (인증된 사용자에 대해서만)
+ */
+ if (token && !isPublicPath(pathname, detectedLng)) {
+ const { isExpired, isExpiringSoon } = checkSessionTimeout(token);
+
+ if (isExpired) {
+ console.log(`Session expired in middleware for user ${token.email}`);
+ const loginUrl = createLoginUrl(pathname, detectedLng, origin, request, 'expired');
+ return NextResponse.redirect(loginUrl);
+ }
+
+ // 세션 만료 경고를 위한 응답 헤더 설정은 나중에 적용
+ }
+
+ /**
+ * 7. 인증된 사용자의 도메인-URL 일치 확인 및 리다이렉션
*/
if (token && token.domain && !isPublicPath(pathname, detectedLng)) {
// 사용자의 domain과 URL 경로가 일치하는지 확인
@@ -125,57 +185,68 @@ export async function middleware(request: NextRequest) {
}
/**
- * 6.5 이미 로그인한 사용자가 로그인 페이지에 접근할 경우 대시보드로 리다이렉트
- */
-if (token) {
- // 로그인 페이지 경로 확인 (정확한 /ko/evcp 또는 /en/partners 등)
- const isEvcpLoginPage = pathname === `/${detectedLng}/evcp`;
- const isPartnersLoginPage = pathname === `/${detectedLng}/partners`;
-
- if (isEvcpLoginPage) {
- // EVCP 로그인 페이지에 접근한 경우 report 페이지로 리다이렉트
- const redirectUrl = new URL(`/${detectedLng}/evcp/report`, origin);
- redirectUrl.search = searchParams.toString();
- return NextResponse.redirect(redirectUrl);
- } else if (isPartnersLoginPage) {
- // Partners 로그인 페이지에 접근한 경우 dashboard 페이지로 리다이렉트
- const redirectUrl = new URL(`/${detectedLng}/partners/dashboard`, origin);
- redirectUrl.search = searchParams.toString();
- return NextResponse.redirect(redirectUrl);
+ * 8. 이미 로그인한 사용자가 로그인 페이지에 접근할 경우 대시보드로 리다이렉트
+ */
+ if (token) {
+ // 세션이 만료되지 않은 경우에만 대시보드로 리다이렉트
+ const { isExpired } = checkSessionTimeout(token);
+
+ if (!isExpired) {
+ // 로그인 페이지 경로 확인 (정확한 /ko/evcp 또는 /en/partners 등)
+ const isEvcpLoginPage = pathname === `/${detectedLng}/evcp`;
+ const isPartnersLoginPage = pathname === `/${detectedLng}/partners`;
+
+ if (isEvcpLoginPage) {
+ // EVCP 로그인 페이지에 접근한 경우 report 페이지로 리다이렉트
+ const redirectUrl = new URL(`/${detectedLng}/evcp/report`, origin);
+ redirectUrl.search = searchParams.toString();
+ return NextResponse.redirect(redirectUrl);
+ } else if (isPartnersLoginPage) {
+ // Partners 로그인 페이지에 접근한 경우 dashboard 페이지로 리다이렉트
+ const redirectUrl = new URL(`/${detectedLng}/partners/dashboard`, origin);
+ redirectUrl.search = searchParams.toString();
+ return NextResponse.redirect(redirectUrl);
+ }
+ }
}
-}
-
/**
- * 7. 인증 확인: 공개 경로가 아닌 경우 로그인 체크 및 리다이렉트
+ * 9. 인증 확인: 공개 경로가 아닌 경우 로그인 체크 및 리다이렉트
*/
if (!isPublicPath(pathname, detectedLng)) {
if (!token) {
- // 어떤 로그인 페이지로 리다이렉트할지 결정
- let loginPath;
-
- // 경로에 따라 적절한 로그인 페이지 선택
- if (pathname.includes('/partners') || pathname.startsWith(`/${detectedLng}/vendor`)) {
- loginPath = `/${detectedLng}/partners`;
- } else {
- loginPath = `/${detectedLng}/evcp`;
- }
-
- // 로그인 후 원래 페이지로 리다이렉트하기 위해 callbackUrl 추가
- const redirectUrl = new URL(loginPath, origin);
- redirectUrl.searchParams.set('callbackUrl', request.nextUrl.pathname + request.nextUrl.search);
-
- return NextResponse.redirect(redirectUrl);
+ const loginUrl = createLoginUrl(pathname, detectedLng, origin, request);
+ return NextResponse.redirect(loginUrl);
+ }
+
+ // 토큰은 있지만 세션이 만료된 경우 (이미 위에서 처리되었지만 추가 안전장치)
+ const { isExpired } = checkSessionTimeout(token);
+ if (isExpired) {
+ const loginUrl = createLoginUrl(pathname, detectedLng, origin, request, 'expired');
+ return NextResponse.redirect(loginUrl);
}
}
/**
- * 8. 위 조건에 걸리지 않았다면 그대로 Next.js로 넘긴다.
+ * 10. 위 조건에 걸리지 않았다면 그대로 Next.js로 넘긴다.
*/
const response = NextResponse.next();
/**
- * 9. 쿠키에 저장된 언어와 현재 lng가 다르면 업데이트
+ * 11. 세션 만료 경고를 위한 헤더 추가
+ */
+ if (token && !isPublicPath(pathname, detectedLng)) {
+ const { isExpiringSoon } = checkSessionTimeout(token);
+
+ if (isExpiringSoon && token.sessionExpiredAt) {
+ response.headers.set('X-Session-Warning', 'true');
+ response.headers.set('X-Session-Expires-At', token.sessionExpiredAt.toString());
+ response.headers.set('X-Session-Time-Left', (token.sessionExpiredAt - Date.now()).toString());
+ }
+ }
+
+ /**
+ * 12. 쿠키에 저장된 언어와 현재 lng가 다르면 업데이트
*/
const currentCookie = request.cookies.get(cookieName)?.value;
if (detectedLng && detectedLng !== currentCookie) {
@@ -186,7 +257,7 @@ if (token) {
}
/**
- * 10. 매칭할 경로 설정
+ * 13. 매칭할 경로 설정
*/
export const config = {
matcher: [