diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-04-08 03:08:19 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-04-08 03:08:19 +0000 |
| commit | 9ceed79cf32c896f8a998399bf1b296506b2cd4a (patch) | |
| tree | f84750fa6cac954d5e31221fc47a54c655fc06a9 /middleware.ts | |
| parent | 230ce796836c25df26c130dbcd616ef97d12b2ec (diff) | |
로그인 및 미들웨어 처리. 구조 변경
Diffstat (limited to 'middleware.ts')
| -rw-r--r-- | middleware.ts | 118 |
1 files changed, 102 insertions, 16 deletions
diff --git a/middleware.ts b/middleware.ts index 4237bfb4..84ee2313 100644 --- a/middleware.ts +++ b/middleware.ts @@ -4,12 +4,61 @@ export const runtime = 'nodejs'; import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; import acceptLanguage from 'accept-language'; +import { getToken } from 'next-auth/jwt'; import { fallbackLng, languages, cookieName } from '@/i18n/settings'; acceptLanguage.languages(languages); -export function middleware(request: NextRequest) { +// 로그인이 필요 없는 공개 경로 +const publicPaths = [ + '/evcp', + '/partners', + '/api/auth', +]; + +// 경로가 공개 경로인지 확인하는 함수 +function isPublicPath(path: string, lng: string) { + // 1. 정확한 로그인 페이지 매칭 (/ko/evcp, /en/partners 등) + if (publicPaths.some(publicPath => path === `/${lng}${publicPath}`)) { + return true; + } + + // 2. auth API는 별도 처리 + if (path.startsWith('/api/auth')) { + return true; + } + + return false; +} + +// 도메인-URL 일치 여부 확인 및 올바른 리다이렉트 경로 반환 +function getDomainRedirectPath(path: string, domain: string, lng: string) { + // 도메인이 없는 경우 리다이렉트 없음 + if (!domain) return null; + + // URL에 partners가 있는지 확인 + const hasPartnersInPath = path.includes('/partners'); + // URL에 evcp가 있는지 확인 + const hasEvcpInPath = path.includes('/evcp'); + + // 1. 도메인이 'partners'인데 URL에 '/evcp/'가 있으면 + if (domain === 'partners' && hasEvcpInPath) { + // URL에서 '/evcp/'를 '/partners/'로 교체 + return path.replace('/evcp/', '/partners/'); + } + + // 2. 도메인이 'evcp'인데 URL에 '/partners/'가 있으면 + if (domain === 'evcp' && hasPartnersInPath) { + // URL에서 '/partners/'를 '/evcp/'로 교체 + return path.replace('/partners/', '/evcp/'); + } + + // 불일치가 없으면 null 반환 (리다이렉트 필요 없음) + return null; +} + +export async function middleware(request: NextRequest) { /** * 1. 쿠키에서 언어 가져오기 */ @@ -26,22 +75,16 @@ export function middleware(request: NextRequest) { const { pathname, searchParams, origin } = request.nextUrl; /** - * 3. "/"" 경로로 들어온 경우 -> "/{lng}"로 리다이렉트 - * (예) / -> /en, / -> /ko ... + * 3. "/" 경로로 들어온 경우 -> "/{lng}"로 리다이렉트 */ if (pathname === '/') { const redirectUrl = new URL(`/${lng}`, origin); - // 쿼리 파라미터가 있을 경우도 붙여주기 redirectUrl.search = searchParams.toString(); return NextResponse.redirect(redirectUrl); } /** * 4. 현재 pathname이 언어 경로를 포함하고 있는지 확인 - * - /en - * - /en/... (다른 하위 경로들) - * - /ko - * - /ko/... */ const hasValidLngInPath = languages.some( (language) => pathname === `/${language}` || pathname.startsWith(`/${language}/`), @@ -49,7 +92,6 @@ export function middleware(request: NextRequest) { /** * 5. 언어 경로가 누락된 경우 -> "/{lng}" + 기존 pathname 으로 리다이렉트 - * 예) /dashboard -> /en/dashboard */ if (!hasValidLngInPath) { const redirectUrl = new URL(`/${lng}${pathname}`, origin); @@ -57,28 +99,72 @@ export function middleware(request: NextRequest) { return NextResponse.redirect(redirectUrl); } + // 언어 코드 추출 + const pathnameParts = pathname.split('/'); + const detectedLng = pathnameParts[1]; // 예: /ko/partners -> ko + + // 토큰 가져오기 (인증 상태 확인 및 도메인 검증에 사용) + const token = await getToken({ req: request }); + + /** + * 6. 인증된 사용자의 도메인-URL 일치 확인 및 리다이렉션 + */ + if (token && token.domain) { + // 사용자의 domain과 URL 경로가 일치하는지 확인 + const redirectPath = getDomainRedirectPath(pathname, token.domain as string, detectedLng); + + // 도메인과 URL이 일치하지 않으면 리다이렉트 + if (redirectPath) { + const redirectUrl = new URL(redirectPath, origin); + redirectUrl.search = searchParams.toString(); + return NextResponse.redirect(redirectUrl); + } + } + + /** + * 7. 인증 확인: 공개 경로가 아닌 경우 로그인 체크 및 리다이렉트 + */ + 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.url); + + return NextResponse.redirect(redirectUrl); + } + } + /** - * 6. 위 조건에 걸리지 않았다면 그대로 Next.js로 넘긴다. + * 8. 위 조건에 걸리지 않았다면 그대로 Next.js로 넘긴다. */ const response = NextResponse.next(); /** - * 7. 쿠키에 저장된 언어와 현재 lng가 다르면 업데이트 + * 9. 쿠키에 저장된 언어와 현재 lng가 다르면 업데이트 */ const currentCookie = request.cookies.get(cookieName)?.value; - if (lng && lng !== currentCookie) { - response.cookies.set(cookieName, lng, { path: '/' }); + if (detectedLng && detectedLng !== currentCookie) { + response.cookies.set(cookieName, detectedLng, { path: '/' }); } return response; } /** - * 8. (선택) 매칭할 경로 설정 - * - _next, 정적파일(.*), API 등의 경로는 제외시키는 예시 + * 10. 매칭할 경로 설정 */ export const config = { matcher: [ - '/((?!_next|.*\\..*|api).*)', + '/((?!_next|.*\\..*|api/(?!auth)).*)', ], };
\ No newline at end of file |
