@thinkingcat/auth-utils 1.0.50 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,1642 +1,34 @@
1
- import { jwtVerify, EncryptJWT } from 'jose';
2
- // ============================================================================
3
- // UTILITY FUNCTIONS
4
- // ============================================================================
5
- /**
6
- * 조건부 로깅 유틸리티 (환경 변수 AUTH_UTILS_DEBUG=true 시에만 로그 출력)
7
- */
8
- function debugLog(context, ...args) {
9
- if (process.env.AUTH_UTILS_DEBUG === 'true' || process.env.NODE_ENV === 'development') {
10
- console.log(`[${context}]`, ...args);
11
- }
12
- }
13
- function debugError(context, ...args) {
14
- if (process.env.AUTH_UTILS_DEBUG === 'true' || process.env.NODE_ENV === 'development') {
15
- console.error(`[${context}]`, ...args);
16
- }
17
- }
18
- /**
19
- * Edge Runtime 호환을 위해 Web Crypto API 사용
20
- */
21
- async function createHashSHA256(data) {
22
- const encoder = new TextEncoder();
23
- const dataBuffer = encoder.encode(data);
24
- const hashBuffer = await crypto.subtle.digest('SHA-256', dataBuffer);
25
- const hashArray = Array.from(new Uint8Array(hashBuffer));
26
- return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
27
- }
28
- /**
29
- * Edge Runtime 호환을 위해 next/server를 동적 import
30
- */
31
- async function getNextServer() {
32
- return await import('next/server');
33
- }
34
- /**
35
- * NextAuth 세션 토큰 쿠키를 삭제하는 헬퍼 함수
36
- */
37
- function deleteNextAuthSessionCookie(response, isProduction) {
38
- const cookieName = isProduction
39
- ? '__Secure-next-auth.session-token'
40
- : 'next-auth.session-token';
41
- response.cookies.delete(cookieName);
42
- }
43
- /**
44
- * 모든 인증 관련 쿠키를 삭제하는 헬퍼 함수
45
- */
46
- function clearAllAuthCookies(response, cookiePrefix, isProduction) {
47
- clearAuthCookies(response, cookiePrefix);
48
- deleteNextAuthSessionCookie(response, isProduction);
49
- }
50
- // ============================================================================
51
- // JWT CORE FUNCTIONS
52
- // ============================================================================
53
- /**
54
- * 토큰 검증 및 디코딩
55
- * @param accessToken JWT access token
56
- * @param secret JWT 서명에 사용할 secret key
57
- * @returns 검증된 payload 또는 null
58
- */
59
- export async function verifyToken(accessToken, secret) {
60
- try {
61
- const secretBytes = new TextEncoder().encode(secret);
62
- const { payload } = await jwtVerify(accessToken, secretBytes);
63
- if (payload && typeof payload === 'object' && payload.email) {
64
- return { payload: payload };
65
- }
66
- return null;
67
- }
68
- catch {
69
- return null;
70
- }
71
- }
72
- /**
73
- * payload에서 역할 추출 (서비스별)
74
- * @param payload JWT payload
75
- * @param serviceId 서비스 ID (필수)
76
- * @param defaultRole 기본 역할 (기본값: 'ADMIN')
77
- * @returns 추출된 역할
78
- */
79
- export function extractRoleFromPayload(payload, serviceId, defaultRole = 'ADMIN') {
80
- const services = payload.services || [];
81
- const service = services.find((s) => s.serviceId === serviceId);
82
- return service?.role || payload.role || defaultRole;
83
- }
84
- /**
85
- * payload에서 NextAuth JWT 객체 생성
86
- * @param payload JWT payload
87
- * @param serviceId 서비스 ID (필수)
88
- * @returns NextAuth JWT 객체
89
- */
90
- export function createNextAuthJWT(payload, serviceId) {
91
- const services = payload.services || [];
92
- const service = services.find((s) => s.serviceId === serviceId);
93
- const effectiveRole = service?.role || payload.role || 'ADMIN';
94
- // name 필드 결정: payload.name이 있으면 사용, 없으면 decryptedEmail 또는 maskedEmail 사용
95
- // email은 암호화되어 있을 수 있으므로 직접 사용하지 않음
96
- const displayName = payload.name
97
- || payload.decryptedEmail
98
- || payload.maskedEmail
99
- || 'User';
100
- const jwt = {
101
- id: (payload.id || payload.sub),
102
- email: payload.email,
103
- name: displayName,
104
- role: effectiveRole, // Role enum 타입 (string으로 캐스팅)
105
- services: payload.services,
106
- phoneVerified: payload.phoneVerified ?? false,
107
- emailVerified: payload.emailVerified ?? false,
108
- smsVerified: payload.smsVerified ?? false,
109
- iat: Math.floor(Date.now() / 1000),
110
- exp: Math.floor(Date.now() / 1000) + (30 * 24 * 60 * 60), // 30일
111
- };
112
- // 선택적 필드들
113
- if (payload.phone)
114
- jwt.phone = payload.phone;
115
- if (payload.isPasswordReset)
116
- jwt.isPasswordReset = payload.isPasswordReset;
117
- if (payload.decryptedEmail)
118
- jwt.decryptedEmail = payload.decryptedEmail;
119
- if (payload.decryptedPhone)
120
- jwt.decryptedPhone = payload.decryptedPhone;
121
- if (payload.emailHash)
122
- jwt.emailHash = payload.emailHash;
123
- if (payload.maskedEmail)
124
- jwt.maskedEmail = payload.maskedEmail;
125
- if (payload.phoneHash)
126
- jwt.phoneHash = payload.phoneHash;
127
- if (payload.maskedPhone)
128
- jwt.maskedPhone = payload.maskedPhone;
129
- if (payload.refreshToken)
130
- jwt.refreshToken = payload.refreshToken;
131
- if (payload.accessToken)
132
- jwt.accessToken = payload.accessToken;
133
- if (payload.accessTokenExpires)
134
- jwt.accessTokenExpires = payload.accessTokenExpires;
135
- if (payload.serviceId)
136
- jwt.serviceId = payload.serviceId;
137
- return jwt;
138
- }
139
- /**
140
- * NextAuth JWT를 인코딩된 세션 토큰으로 변환
141
- * @param jwt NextAuth JWT 객체
142
- * @param secret JWT 서명에 사용할 secret key
143
- * @param maxAge 토큰 유효 기간 (초, 기본값: 30일)
144
- * @returns 인코딩된 세션 토큰
145
- */
146
- export async function encodeNextAuthToken(jwt, secret, maxAge = 30 * 24 * 60 * 60) {
147
- // NextAuth의 encode() 함수를 우선적으로 사용 (가장 호환성 좋음)
148
- try {
149
- const { encode } = await import('next-auth/jwt');
150
- debugLog('encodeNextAuthToken', 'Using next-auth/jwt encode');
151
- const encoded = await encode({
152
- token: jwt,
153
- secret: secret,
154
- maxAge: maxAge,
155
- });
156
- debugLog('encodeNextAuthToken', 'NextAuth encode successful, length:', encoded.length);
157
- return encoded;
158
- }
159
- catch (error) {
160
- // Edge Runtime에서 encode가 작동하지 않을 수 있으므로
161
- // jose의 EncryptJWT를 fallback으로 사용
162
- debugLog('encodeNextAuthToken', 'NextAuth encode failed, using jose EncryptJWT fallback', error);
163
- // NextAuth는 secret을 SHA-256 해시하여 32바이트 키로 사용
164
- const secretHash = await createHashSHA256(secret);
165
- // SHA-256 해시는 64자 hex 문자열이므로, 32바이트로 변환
166
- const keyBytes = new Uint8Array(32);
167
- for (let i = 0; i < 32; i++) {
168
- keyBytes[i] = parseInt(secretHash.slice(i * 2, i * 2 + 2), 16);
169
- }
170
- const now = Math.floor(Date.now() / 1000);
171
- // EncryptJWT를 사용하여 JWE 토큰 생성
172
- // NextAuth는 'dir' 키 관리와 'A256GCM' 암호화를 사용
173
- try {
174
- const token = await new EncryptJWT(jwt)
175
- .setProtectedHeader({
176
- alg: 'dir', // Direct key agreement
177
- enc: 'A256GCM' // AES-256-GCM encryption
178
- })
179
- .setIssuedAt(now)
180
- .setExpirationTime(now + maxAge)
181
- .setJti(crypto.randomUUID())
182
- .encrypt(keyBytes);
183
- debugLog('encodeNextAuthToken', 'EncryptJWT fallback successful, length:', token.length);
184
- return token;
185
- }
186
- catch (encryptError) {
187
- debugError('encodeNextAuthToken', 'EncryptJWT also failed:', encryptError);
188
- throw new Error(`Failed to encode NextAuth token: ${error instanceof Error ? error.message : String(error)}`);
189
- }
190
- }
191
- }
192
- export function setCustomTokens(response, accessToken, optionsOrRefreshToken, options) {
193
- // 옵션 파라미터 처리: refreshToken과 options를 분리
194
- let refreshTokenValue;
195
- let cookiePrefix;
196
- let isProduction;
197
- let cookieDomain;
198
- if (typeof optionsOrRefreshToken === 'string') {
199
- // 기존 방식: refreshToken이 문자열로 전달된 경우
200
- refreshTokenValue = optionsOrRefreshToken;
201
- const { cookiePrefix: prefix, isProduction: prod = false, cookieDomain: domain, } = options || {};
202
- if (!prefix) {
203
- throw new Error('cookiePrefix is required');
204
- }
205
- cookiePrefix = prefix;
206
- isProduction = prod;
207
- cookieDomain = domain;
208
- }
209
- else {
210
- // 새로운 방식: options 객체로 전달된 경우
211
- const opts = optionsOrRefreshToken || {};
212
- refreshTokenValue = opts.refreshToken;
213
- if (!opts.cookiePrefix) {
214
- throw new Error('cookiePrefix is required');
215
- }
216
- cookiePrefix = opts.cookiePrefix;
217
- isProduction = opts.isProduction || false;
218
- cookieDomain = opts.cookieDomain;
219
- }
220
- // 쿠키 옵션 생성
221
- const cookieOptions = {
222
- httpOnly: true,
223
- secure: isProduction,
224
- sameSite: isProduction ? 'none' : 'lax',
225
- path: '/',
226
- maxAge: 15 * 60, // access token: 15분
227
- };
228
- if (cookieDomain) {
229
- cookieOptions.domain = cookieDomain;
230
- }
231
- // access_token 설정
232
- const accessTokenName = `${cookiePrefix}_access_token`;
233
- response.cookies.delete(accessTokenName);
234
- response.cookies.set(accessTokenName, accessToken, cookieOptions);
235
- debugLog('setCustomTokens', `Set ${accessTokenName} cookie:`, {
236
- hasValue: !!accessToken,
237
- maxAge: cookieOptions.maxAge,
238
- domain: cookieOptions.domain,
239
- secure: cookieOptions.secure,
240
- sameSite: cookieOptions.sameSite,
241
- });
242
- // refresh_token 설정 (있는 경우)
243
- if (refreshTokenValue) {
244
- const refreshTokenName = `${cookiePrefix}_refresh_token`;
245
- const refreshCookieOptions = {
246
- ...cookieOptions,
247
- maxAge: 30 * 24 * 60 * 60, // refresh token: 30일
248
- };
249
- response.cookies.set(refreshTokenName, refreshTokenValue, refreshCookieOptions);
250
- debugLog('setCustomTokens', `Set ${refreshTokenName} cookie:`, {
251
- hasValue: !!refreshTokenValue,
252
- maxAge: refreshCookieOptions.maxAge,
253
- domain: refreshCookieOptions.domain,
254
- });
255
- }
256
- }
257
- /**
258
- * NextAuth 세션 토큰만 설정
259
- * @param response NextResponse 객체
260
- * @param sessionToken NextAuth session token
261
- * @param options 쿠키 설정 옵션
262
- * @param options.isProduction 프로덕션 환경 여부 (기본값: false)
263
- * @param options.cookieDomain 쿠키 도메인 (선택)
264
- */
265
- export function setNextAuthToken(response, sessionToken, options = {}) {
266
- const { isProduction = false, cookieDomain, } = options;
267
- // createNextAuthCookies와 동일한 로직 사용
268
- const cookies = createNextAuthCookies({ isProduction, cookieDomain });
269
- const cookieName = cookies.sessionToken.name;
270
- response.cookies.delete(cookieName);
271
- response.cookies.set(cookieName, sessionToken, {
272
- ...cookies.sessionToken.options,
273
- httpOnly: true,
274
- maxAge: 30 * 24 * 60 * 60, // 30일
275
- });
276
- }
277
- /**
278
- * 리다이렉트용 HTML 생성
279
- * @param redirectPath 리다이렉트할 경로
280
- * @param text 표시할 텍스트 (필수)
281
- * @returns HTML 문자열
282
- */
283
- export function createRedirectHTML(redirectPath, text) {
284
- return `
285
- <!DOCTYPE html>
286
- <html>
287
- <head>
288
- <meta charset="utf-8">
289
- <title>Redirecting...</title>
290
- <style>
291
- * { margin: 0; padding: 0; box-sizing: border-box; }
292
- html, body {
293
- width: 100%;
294
- height: 100%;
295
- overflow: hidden;
296
- }
297
- body {
298
- display: flex;
299
- align-items: flex-start;
300
- justify-content: flex-start;
301
- padding-left: 10%;
302
- padding-top: 20%;
303
- background: #fff;
304
- }
305
- .typing-text {
306
- font-family: 'Courier New', monospace;
307
- font-size: 2.5rem;
308
- font-weight: bold;
309
- color: #667eea;
310
- letter-spacing: 0.1em;
311
- }
312
- .cursor {
313
- display: inline-block;
314
- width: 3px;
315
- height: 2.5rem;
316
- background-color: #667eea;
317
- margin-left: 2px;
318
- animation: blink 0.7s infinite;
319
- }
320
- @keyframes blink {
321
- 0%, 50% { opacity: 1; }
322
- 51%, 100% { opacity: 0; }
323
- }
324
- </style>
325
- </head>
326
- <body>
327
- <div class="typing-text">
328
- <span id="text"></span><span class="cursor"></span>
329
- </div>
330
- <script>
331
- const text = '${text}';
332
- let index = 0;
333
- const speed = 100;
334
-
335
- function type() {
336
- if (index < text.length) {
337
- document.getElementById('text').textContent += text.charAt(index);
338
- index++;
339
- setTimeout(type, speed);
340
- } else {
341
- setTimeout(() => window.location.href = '${redirectPath}', 200);
342
- }
343
- }
344
-
345
- type();
346
- </script>
347
- </body>
348
- </html>
349
- `;
350
- }
351
- /**
352
- * access token과 refresh token을 사용하여 완전한 인증 세션 생성
353
- * @param accessToken access token
354
- * @param secret JWT 서명에 사용할 secret key
355
- * @param options 추가 옵션
356
- * @param options.req NextRequest 객체 (필수 - URL origin을 위해 필요)
357
- * @param options.refreshToken refresh token (선택)
358
- * @param options.redirectPath 리다이렉트할 경로 (HTTP 302 리다이렉트 사용)
359
- * @param options.text 응답 메시지 텍스트 (선택사항)
360
- * @param options.cookiePrefix 쿠키 이름 접두사 (필수)
361
- * @param options.isProduction 프로덕션 환경 여부 (기본값: false)
362
- * @param options.cookieDomain 쿠키 도메인 (선택)
363
- * @param options.serviceId 서비스 ID (필수)
364
- * @param options.licenseKey 라이센스 키 (필수)
365
- * @returns NextResponse 객체 (리다이렉트 또는 JSON 응답)
366
- */
367
- export async function createAuthResponse(accessToken, secret, options) {
368
- await checkLicenseKey(options.licenseKey);
369
- const { req, refreshToken, redirectPath, text, cookiePrefix, isProduction = false, cookieDomain, serviceId, } = options;
370
- // 1. 토큰 검증
371
- const tokenResult = await verifyToken(accessToken, secret);
372
- if (!tokenResult) {
373
- throw new Error('Invalid token');
374
- }
375
- const { payload } = tokenResult;
376
- // 2. NextAuth JWT 생성
377
- const jwt = createNextAuthJWT(payload, serviceId);
378
- // refreshToken 추가
379
- if (refreshToken) {
380
- jwt.refreshToken = refreshToken;
381
- }
382
- // accessTokenExpires 추가 (15분)
383
- jwt.accessTokenExpires = Date.now() + (15 * 60 * 1000);
384
- debugLog('createAuthResponse', 'JWT prepared (NextAuth will create session from custom tokens):', {
385
- hasId: !!jwt.id,
386
- hasEmail: !!jwt.email,
387
- hasRole: !!jwt.role,
388
- hasRefreshToken: !!jwt.refreshToken,
389
- });
390
- // 3. Response 생성 (HTTP 302 리다이렉트 사용)
391
- const { NextResponse: NextResponseClass } = await getNextServer();
392
- // redirectPath가 있으면 302 리다이렉트, 없으면 200 OK
393
- const response = redirectPath
394
- ? NextResponseClass.redirect(new URL(redirectPath, req.url), { status: 302 })
395
- : NextResponseClass.json({ success: true, message: text || 'Authentication successful' }, { status: 200 });
396
- // 4. NextAuth 세션 쿠키 생성
397
- // 주의: NextAuth는 세션 쿠키를 자동으로 관리하므로, 직접 설정하는 것이 문제를 일으킬 수 있습니다.
398
- // 하지만 미들웨어에서는 NextAuth의 정상 플로우를 사용할 수 없으므로,
399
- // NextAuth의 encode()와 decode()가 호환되도록 주의해야 합니다.
400
- const nextAuthCookieName = isProduction
401
- ? '__Secure-next-auth.session-token'
402
- : 'next-auth.session-token';
403
- // NextAuth 세션 쿠키 설정을 건너뛰고 커스텀 토큰만 사용
404
- // NextAuth는 JWT 콜백을 통해 자동으로 세션 쿠키를 생성하므로,
405
- // 미들웨어에서 직접 설정하면 NextAuth가 디코딩할 수 없는 경우가 있습니다.
406
- // 따라서 커스텀 토큰만 설정하고, NextAuth 세션은 JWT 콜백에서 처리하도록 합니다.
407
- debugLog('createAuthResponse', 'Skipping NextAuth session cookie - will be handled by NextAuth JWT callback', {
408
- name: nextAuthCookieName,
409
- hasJWT: !!jwt,
410
- jwtId: jwt?.id,
411
- });
412
- // 5. 커스텀 토큰 쿠키 설정
413
- if (refreshToken) {
414
- setCustomTokens(response, accessToken, refreshToken, {
415
- cookiePrefix,
416
- isProduction,
417
- cookieDomain,
418
- });
419
- }
420
- else {
421
- setCustomTokens(response, accessToken, {
422
- cookiePrefix,
423
- isProduction,
424
- cookieDomain,
425
- });
426
- }
427
- debugLog('createAuthResponse', 'Custom tokens set successfully');
428
- console.log('[createAuthResponse] All cookies set:', {
429
- nextAuthCookie: nextAuthCookieName,
430
- accessTokenCookie: `${cookiePrefix}_access_token`,
431
- refreshTokenCookie: refreshToken ? `${cookiePrefix}_refresh_token` : 'none',
432
- });
433
- return response;
434
- }
435
- // ============================================================================
436
- // SSO INTEGRATION FUNCTIONS
437
- // ============================================================================
438
- /**
439
- * 서비스 구독 유효성 확인 함수
440
- * @param services 서비스 정보 배열
441
- * @param serviceId 확인할 서비스 ID
442
- * @param ssoBaseURL SSO 서버 기본 URL (필수)
443
- * @returns 구독 유효성 결과
444
- */
445
- export function validateServiceSubscription(services, serviceId, ssoBaseURL) {
446
- const filteredServices = services.filter(service => service.serviceId === serviceId);
447
- if (filteredServices.length === 0) {
448
- return {
449
- isValid: false,
450
- redirectUrl: `${ssoBaseURL}/services/${serviceId}?type=subscription_required`
451
- };
452
- }
453
- const service = filteredServices[0];
454
- if (service.status !== "ACTIVE") {
455
- return {
456
- isValid: false,
457
- redirectUrl: `${ssoBaseURL}/services/${service.serviceId}?type=subscription_required`,
458
- service
459
- };
460
- }
461
- return {
462
- isValid: true,
463
- service
464
- };
465
- }
466
- /**
467
- * SSO 서버에서 refresh token을 사용하여 새로운 access token을 발급받는 함수
468
- * @param refreshToken refresh token
469
- * @param options 옵션
470
- * @param options.ssoBaseURL SSO 서버 기본 URL (필수)
471
- * @param options.authServiceKey 인증 서비스 키 (기본값: 환경 변수)
472
- * @returns SSO refresh token 응답
473
- */
474
- export async function refreshSSOToken(refreshToken, options) {
475
- const { ssoBaseURL, authServiceKey } = options;
476
- if (!authServiceKey) {
477
- throw new Error('AUTH_SERVICE_SECRET_KEY not configured');
478
- }
479
- const refreshResponse = await fetch(`${ssoBaseURL}/api/sso/refresh`, {
480
- method: 'POST',
481
- headers: {
482
- 'Content-Type': 'application/json',
483
- 'x-auth-service-key': authServiceKey,
484
- },
485
- body: JSON.stringify({ refreshToken }),
486
- });
487
- return await refreshResponse.json();
488
- }
489
- /**
490
- * SSO 서버에서 사용자의 refresh token을 가져오는 함수
491
- * @param userId 사용자 ID
492
- * @param accessToken access token
493
- * @param options 옵션
494
- * @param options.ssoBaseURL SSO 서버 기본 URL (필수)
495
- * @param options.authServiceKey 인증 서비스 키 (기본값: 환경 변수)
496
- * @returns refresh token 또는 null
497
- */
498
- export async function getRefreshTokenFromSSO(userId, accessToken, options) {
499
- const { ssoBaseURL, authServiceKey } = options;
500
- if (!authServiceKey) {
501
- return null;
502
- }
503
- try {
504
- const refreshResponse = await fetch(`${ssoBaseURL}/api/sso/get-refresh-token`, {
505
- method: 'POST',
506
- headers: {
507
- 'Content-Type': 'application/json',
508
- 'x-auth-service-key': authServiceKey,
509
- },
510
- body: JSON.stringify({
511
- userId,
512
- accessToken
513
- }),
514
- });
515
- const refreshResult = await refreshResponse.json();
516
- if (refreshResponse.ok && refreshResult.success && refreshResult.refreshToken) {
517
- return refreshResult.refreshToken;
518
- }
519
- }
520
- catch {
521
- // Refresh token 실패해도 access token으로 일단 로그인 가능
522
- }
523
- return null;
524
- }
525
- // ============================================================================
526
- // TOKEN REFRESH & VERIFICATION FUNCTIONS
527
- // ============================================================================
528
- export async function verifyAndRefreshToken(req, secret, options) {
529
- const { cookiePrefix, serviceId, isProduction, cookieDomain, text, ssoBaseURL, authServiceKey, forceRefresh = false, } = options;
530
- // 1. access_token 쿠키 확인
531
- // forceRefresh가 true이면 access token이 있어도 refresh를 시도
532
- const accessTokenName = `${cookiePrefix}_access_token`;
533
- const accessToken = req.cookies.get(accessTokenName)?.value;
534
- if (accessToken && !forceRefresh) {
535
- try {
536
- const secretBytes = new TextEncoder().encode(secret);
537
- const { payload } = await jwtVerify(accessToken, secretBytes);
538
- if (payload && typeof payload === 'object' && payload.email) {
539
- return { isValid: true, payload: payload };
540
- }
541
- }
542
- catch {
543
- // 토큰 검증 실패
544
- }
545
- }
546
- // 리프레시 토큰으로 갱신 시도
547
- const refreshTokenName = `${cookiePrefix}_refresh_token`;
548
- const refreshToken = req.cookies.get(refreshTokenName)?.value;
549
- debugLog('verifyAndRefreshToken', 'Checking refresh:', {
550
- hasRefreshToken: !!refreshToken,
551
- forceRefresh,
552
- });
553
- if (refreshToken) {
554
- try {
555
- if (!ssoBaseURL || !authServiceKey) {
556
- debugLog('verifyAndRefreshToken', 'SSO config missing');
557
- return { isValid: false, error: 'SSO_CONFIG_MISSING' };
558
- }
559
- debugLog('verifyAndRefreshToken', 'Attempting token refresh...');
560
- const refreshResult = await refreshSSOToken(refreshToken, {
561
- ssoBaseURL,
562
- authServiceKey,
563
- });
564
- debugLog('verifyAndRefreshToken', 'Refresh result:', {
565
- success: refreshResult.success,
566
- hasAccessToken: !!refreshResult.accessToken,
567
- });
568
- if (refreshResult.success && refreshResult.accessToken) {
569
- const newRefreshToken = refreshResult.refreshToken || refreshToken;
570
- try {
571
- let payload;
572
- try {
573
- const secretBytes = new TextEncoder().encode(secret);
574
- const { payload: tokenPayload } = await jwtVerify(refreshResult.accessToken, secretBytes);
575
- if (tokenPayload && typeof tokenPayload === 'object' && tokenPayload.email) {
576
- payload = tokenPayload;
577
- }
578
- }
579
- catch {
580
- // 토큰 검증 실패
581
- }
582
- debugLog('verifyAndRefreshToken', 'Updating cookies including NextAuth session...');
583
- // NextResponse.next()를 생성
584
- const { NextResponse: NextResponseClass } = await getNextServer();
585
- const response = NextResponseClass.next();
586
- // NextAuth JWT 생성
587
- const jwt = createNextAuthJWT(payload, serviceId);
588
- if (newRefreshToken) {
589
- jwt.refreshToken = newRefreshToken;
590
- }
591
- jwt.accessTokenExpires = Date.now() + (15 * 60 * 1000);
592
- // NextAuth 세션 쿠키 생성
593
- // 미들웨어에서는 NextAuth JWT callback이 실행되지 않으므로,
594
- // refresh 후 NextAuth 세션 쿠키를 직접 설정해야 합니다.
595
- try {
596
- const encodedSessionToken = await encodeNextAuthToken(jwt, secret, 30 * 24 * 60 * 60);
597
- setNextAuthToken(response, encodedSessionToken, {
598
- isProduction,
599
- cookieDomain,
600
- });
601
- debugLog('verifyAndRefreshToken', 'NextAuth session cookie set successfully', {
602
- hasJWT: !!jwt,
603
- jwtId: jwt?.id,
604
- });
605
- }
606
- catch (error) {
607
- debugError('verifyAndRefreshToken', 'Failed to set NextAuth session cookie:', error);
608
- // NextAuth 세션 쿠키 설정 실패해도 커스텀 토큰은 설정하므로 계속 진행
609
- }
610
- // 커스텀 토큰 쿠키 설정
611
- if (newRefreshToken) {
612
- setCustomTokens(response, refreshResult.accessToken, newRefreshToken, {
613
- cookiePrefix,
614
- isProduction,
615
- cookieDomain,
616
- });
617
- }
618
- else {
619
- setCustomTokens(response, refreshResult.accessToken, {
620
- cookiePrefix,
621
- isProduction,
622
- cookieDomain,
623
- });
624
- }
625
- debugLog('verifyAndRefreshToken', 'All cookies updated');
626
- return { isValid: true, response, payload };
627
- }
628
- catch (error) {
629
- debugError('verifyAndRefreshToken', 'Failed to create auth response:', error);
630
- return { isValid: false, error: 'SESSION_CREATION_FAILED' };
631
- }
632
- }
633
- else {
634
- debugLog('verifyAndRefreshToken', 'Refresh failed, clearing all cookies');
635
- const { NextResponse: NextResponseClass } = await getNextServer();
636
- const response = NextResponseClass.next();
637
- clearAllAuthCookies(response, options.cookiePrefix, options.isProduction);
638
- return { isValid: false, response, error: 'REFRESH_FAILED' };
639
- }
640
- }
641
- catch (error) {
642
- debugError('verifyAndRefreshToken', 'Token refresh error:', error);
643
- const { NextResponse: NextResponseClass } = await getNextServer();
644
- const response = NextResponseClass.next();
645
- clearAllAuthCookies(response, options.cookiePrefix, options.isProduction);
646
- return { isValid: false, response, error: 'REFRESH_ERROR' };
647
- }
648
- }
649
- return { isValid: false, error: 'NO_TOKEN' };
650
- }
651
- // ============================================================================
652
- // HELPER FUNCTIONS
653
- // ============================================================================
654
- /**
655
- * 에러 페이지로 리다이렉트하는 헬퍼 함수
656
- * @param req NextRequest 객체
657
- * @param code 에러 코드
658
- * @param message 에러 메시지
659
- * @param errorPath 에러 페이지 경로 (기본값: '/error')
660
- * @returns NextResponse 리다이렉트 응답
661
- */
662
- export async function redirectToError(req, code, message, errorPath = '/error') {
663
- const url = new URL(errorPath, req.url);
664
- url.searchParams.set('code', code);
665
- url.searchParams.set('message', message);
666
- const { NextResponse: NextResponseClass } = await getNextServer();
667
- return NextResponseClass.redirect(url);
668
- }
669
- /**
670
- * 인증 쿠키를 삭제하는 헬퍼 함수
671
- * @param response NextResponse 객체
672
- * @param cookiePrefix 쿠키 이름 접두사 (필수)
673
- * @returns NextResponse 객체
674
- */
675
- export function clearAuthCookies(response, cookiePrefix) {
676
- response.cookies.delete(`${cookiePrefix}_access_token`);
677
- response.cookies.delete(`${cookiePrefix}_refresh_token`);
678
- return response;
679
- }
680
- /**
681
- * JWT에서 서비스별 역할을 추출하는 헬퍼 함수
682
- * @param token NextAuth JWT 객체 또는 null
683
- * @param serviceId 서비스 ID (필수)
684
- * @returns 추출된 역할 또는 undefined
685
- */
686
- export function getEffectiveRole(token, serviceId) {
687
- if (!token)
688
- return undefined;
689
- // token이 이미 JWT 객체인 경우 role을 직접 사용
690
- if (token.role) {
691
- return token.role;
692
- }
693
- // services에서 추출
694
- const services = token.services || [];
695
- const service = services.find((s) => s.serviceId === serviceId);
696
- return service?.role;
697
- }
698
- /**
699
- * 구독이 필요한 경로인지 확인하는 헬퍼 함수
700
- * @param pathname 경로명
701
- * @param role 사용자 역할
702
- * @param subscriptionRequiredPaths 구독이 필요한 경로 배열
703
- * @param systemAdminRole 시스템 관리자 역할 (기본값: 'SYSTEM_ADMIN')
704
- * @returns 구독이 필요한지 여부
705
- */
706
- export function requiresSubscription(pathname, role, subscriptionRequiredPaths, systemAdminRole = 'SYSTEM_ADMIN') {
707
- // 시스템 관리자는 구독 확인 제외
708
- if (role === systemAdminRole) {
709
- return false;
710
- }
711
- // 구독이 필요한 경로인지 확인
712
- return subscriptionRequiredPaths.some(path => pathname.startsWith(path));
713
- }
714
- // ============================================================================
715
- // NEXTAUTH CONFIGURATION FUNCTIONS
716
- // ============================================================================
717
- // 유효한 라이센스 키 해시 목록
718
- const VALID_LICENSE_KEY_HASHES = new Set([
719
- '73bce4f3b64804c255cdab450d759a8b53038f9edb59ae42d9988b08dfd007e2',
720
- ]);
721
- /**
722
- * NextAuth 쿠키 설정 생성
723
- * @param options 옵션
724
- * @param options.isProduction 프로덕션 환경 여부
725
- * @param options.cookieDomain 쿠키 도메인 (선택)
726
- * @returns NextAuth 쿠키 설정 객체
727
- */
728
- export function createNextAuthCookies(options) {
729
- const { isProduction = false, cookieDomain } = options;
730
- const isSecure = isProduction;
731
- // cookieDomain이 설정되어 있으면 같은 도메인/서브도메인 간 쿠키 공유를 위해 'lax' 사용
732
- // cookieDomain이 없고 프로덕션 환경이면 크로스 도메인을 위해 'none' 사용
733
- // 개발 환경이면 항상 'lax' 사용
734
- const sameSiteValue = cookieDomain ? 'lax' : (isSecure ? 'none' : 'lax');
735
- return {
736
- sessionToken: {
737
- name: isSecure ? `__Secure-next-auth.session-token` : `next-auth.session-token`,
738
- options: {
739
- httpOnly: true,
740
- sameSite: sameSiteValue,
741
- path: '/',
742
- secure: isSecure,
743
- ...(cookieDomain && { domain: cookieDomain }),
744
- },
745
- },
746
- callbackUrl: {
747
- name: isSecure ? `__Secure-next-auth.callback-url` : `next-auth.callback-url`,
748
- options: {
749
- sameSite: sameSiteValue,
750
- path: '/',
751
- secure: isSecure,
752
- ...(cookieDomain && { domain: cookieDomain }),
753
- },
754
- },
755
- csrfToken: {
756
- name: isSecure ? `__Secure-next-auth.csrf-token` : `next-auth.csrf-token`,
757
- options: {
758
- httpOnly: true,
759
- sameSite: sameSiteValue,
760
- path: '/',
761
- secure: isSecure,
762
- ...(cookieDomain && { domain: cookieDomain }),
763
- },
764
- },
765
- };
766
- }
767
- /**
768
- * NextAuth 기본 설정 생성
769
- * @param options 옵션
770
- * @param options.secret NextAuth secret
771
- * @param options.isProduction 프로덕션 환경 여부
772
- * @param options.cookieDomain 쿠키 도메인 (선택)
773
- * @param options.signInPath 로그인 페이지 경로 (기본값: '/login')
774
- * @param options.errorPath 에러 페이지 경로 (기본값: '/login')
775
- * @param options.nextAuthUrl NextAuth URL (선택)
776
- * @param options.sessionMaxAge 세션 최대 유지 시간 (초, 기본값: 30일)
777
- * @param options.jwtMaxAge JWT 최대 유지 시간 (초, 기본값: 30일)
778
- * @returns NextAuth 기본 설정 객체
779
- */
780
- export function createNextAuthBaseConfig(options) {
781
- const { secret, isProduction = false, cookieDomain, signInPath = '/login', errorPath = '/login', nextAuthUrl, sessionMaxAge = 30 * 24 * 60 * 60, // 30일
782
- jwtMaxAge = 30 * 24 * 60 * 60, // 30일
783
- } = options;
784
- return {
785
- session: {
786
- strategy: 'jwt',
787
- maxAge: sessionMaxAge,
788
- },
789
- jwt: {
790
- maxAge: jwtMaxAge,
791
- },
792
- providers: [],
793
- ...(nextAuthUrl && { url: nextAuthUrl }),
794
- pages: {
795
- signIn: signInPath,
796
- error: errorPath,
797
- },
798
- cookies: createNextAuthCookies({ isProduction, cookieDomain }),
799
- secret,
800
- };
801
- }
802
- /**
803
- * JWT 콜백에서 초기 로그인 시 토큰 생성 헬퍼
804
- * @param token 기존 토큰
805
- * @param user 사용자 정보
806
- * @param account 계정 정보
807
- * @returns 업데이트된 JWT 토큰
808
- */
809
- export function createInitialJWTToken(token, user, account) {
810
- return {
811
- ...token,
812
- id: user.id,
813
- email: user.email ?? undefined,
814
- emailHash: user.emailHash ?? undefined,
815
- maskedEmail: user.maskedEmail ?? undefined,
816
- phoneHash: user.phoneHash ?? undefined,
817
- maskedPhone: user.maskedPhone ?? undefined,
818
- role: user.role,
819
- phone: user.phone ?? undefined,
820
- decryptedEmail: user.decryptedEmail ?? undefined,
821
- decryptedPhone: user.decryptedPhone ?? undefined,
822
- refreshToken: user.refreshToken ?? undefined,
823
- accessTokenExpires: Date.now() + (15 * 60 * 1000), // 15분
824
- serviceId: account?.serviceId ?? undefined,
825
- };
826
- }
827
- /**
828
- * Session 콜백에서 빈 세션 반환 헬퍼
829
- * @param session 기존 세션
830
- * @returns 빈 세션 객체
831
- */
832
- export function createEmptySession(session) {
833
- return {
834
- ...session,
835
- user: {
836
- ...session.user,
837
- id: '',
838
- email: null,
839
- role: 'GUEST',
840
- },
841
- expires: new Date().toISOString(),
842
- };
843
- }
844
- /**
845
- * Session 콜백에서 토큰 정보를 세션에 매핑하는 헬퍼
846
- * @param session 기존 세션
847
- * @param token JWT 토큰
848
- * @returns 업데이트된 세션
849
- */
850
- export function mapTokenToSession(session, token) {
851
- if (!session.user) {
852
- return session;
853
- }
854
- const user = session.user;
855
- user.id = token.id;
856
- user.email = token.email;
857
- user.name = token.name;
858
- user.role = token.role;
859
- user.smsVerified = token.smsVerified;
860
- user.emailVerified = token.emailVerified;
861
- user.phone = token.phone;
862
- user.phoneVerified = token.phoneVerified;
863
- user.isPasswordReset = token.isPasswordReset || false;
864
- user.decryptedEmail = token.decryptedEmail;
865
- user.decryptedPhone = token.decryptedPhone;
866
- return session;
867
- }
868
- /**
869
- * JWT 콜백을 위한 통합 헬퍼 함수
870
- * 초기 로그인, 토큰 갱신, 커스텀 토큰 읽기를 모두 처리
871
- * @param token 기존 JWT 토큰
872
- * @param user 사용자 정보 (초기 로그인 시)
873
- * @param account 계정 정보 (초기 로그인 시)
874
- * @param options 옵션
875
- * @param options.secret NextAuth secret (커스텀 토큰 읽기용)
876
- * @param options.licenseKey 라이센스 키 (커스텀 토큰 읽기용)
877
- * @param options.serviceId 서비스 ID (커스텀 토큰 읽기용)
878
- * @param options.cookieName 커스텀 토큰 쿠키 이름 (기본값: '{serviceId}_access_token')
879
- * @param options.debug 디버깅 로그 출력 여부 (기본값: false)
880
- * @returns 업데이트된 JWT 토큰
881
- */
882
- export async function handleJWTCallback(token, user, account, options) {
883
- const { secret, licenseKey, serviceId, cookieName, debug = false, ssoBaseURL, authServiceKey, } = options || {};
884
- // 디버깅 로그
885
- if (debug) {
886
- debugLog('handleJWTCallback', 'Token received:', {
887
- hasId: !!token.id,
888
- hasEmail: !!token.email,
889
- hasRole: !!token.role,
890
- hasExpires: !!token.accessTokenExpires,
891
- });
892
- }
893
- // 1. 초기 로그인 시 (providers를 통한 로그인)
894
- if (account && user) {
895
- debugLog('handleJWTCallback', 'Initial login, creating token from user data');
896
- return createInitialJWTToken(token, user, account);
897
- }
898
- // 2. 커스텀 토큰 쿠키 우선 체크 (middleware에서 refresh한 토큰이 있을 수 있음)
899
- if (secret && licenseKey && serviceId) {
900
- const cookieNameToUse = cookieName || `${serviceId}_access_token`;
901
- debugLog('handleJWTCallback', 'Checking custom token cookie first:', cookieNameToUse);
902
- const customJwt = await getJWTFromCustomTokenCookie(cookieNameToUse, secret, serviceId, licenseKey);
903
- if (customJwt) {
904
- debugLog('handleJWTCallback', 'Found valid custom token cookie, using it');
905
- // refreshToken이 있으면 유지
906
- if (token.refreshToken) {
907
- customJwt.refreshToken = token.refreshToken;
908
- }
909
- return customJwt;
910
- }
911
- debugLog('handleJWTCallback', 'No valid custom token cookie found');
912
- }
913
- // 3. 토큰 유효성 체크
914
- const now = Date.now();
915
- const expires = token.accessTokenExpires;
916
- const hasValidToken = token.id && expires && expires > now;
917
- const refreshToken = token.refreshToken;
918
- debugLog('handleJWTCallback', 'Token status:', {
919
- hasId: !!token.id,
920
- hasExpires: !!expires,
921
- expiresIn: expires ? Math.round((expires - now) / 1000) + 's' : 'N/A',
922
- hasValidToken,
923
- hasRefreshToken: !!refreshToken,
924
- });
925
- // 3-1. nextauth token이 있고 만료되지 않았으면 그대로 사용
926
- if (hasValidToken) {
927
- debugLog('handleJWTCallback', 'Token is still valid, using existing token');
928
- return token;
929
- }
930
- // 3-2. nextauth token이 없거나 만료됨 → refresh token으로 갱신 시도
931
- // (refreshToken이 있고 SSO 설정이 있을 때만)
932
- if (refreshToken && ssoBaseURL && authServiceKey && secret) {
933
- debugLog('handleJWTCallback', 'Token invalid or expired, attempting SSO refresh');
934
- try {
935
- debugLog('handleJWTCallback', 'Calling SSO refresh endpoint:', `${ssoBaseURL}/api/sso/refresh`);
936
- const response = await fetch(`${ssoBaseURL}/api/sso/refresh`, {
937
- method: 'POST',
938
- headers: {
939
- 'Content-Type': 'application/json',
940
- 'x-auth-service-key': authServiceKey,
941
- },
942
- body: JSON.stringify({ refreshToken }),
943
- });
944
- debugLog('handleJWTCallback', 'SSO refresh response status:', response.status);
945
- if (response.ok) {
946
- const result = await response.json();
947
- if (result.success && result.accessToken) {
948
- debugLog('handleJWTCallback', 'Successfully refreshed token from SSO');
949
- // 새 액세스 토큰 검증 및 페이로드 추출
950
- const tokenResult = await verifyToken(result.accessToken, secret);
951
- if (tokenResult) {
952
- const newJWT = createNextAuthJWT(tokenResult.payload, serviceId || '');
953
- return {
954
- ...newJWT,
955
- refreshToken, // 기존 refresh token 유지
956
- accessTokenExpires: Date.now() + (15 * 60 * 1000), // 15분
957
- };
958
- }
959
- }
960
- }
961
- debugLog('handleJWTCallback', 'Failed to refresh token from SSO');
962
- }
963
- catch (error) {
964
- console.error('[handleJWTCallback] Error refreshing token:', error);
965
- }
966
- }
967
- else {
968
- debugLog('handleJWTCallback', 'Cannot refresh - missing requirements:', {
969
- hasRefreshToken: !!refreshToken,
970
- hasSSO: !!ssoBaseURL,
971
- hasAuthKey: !!authServiceKey,
972
- hasSecret: !!secret,
973
- });
974
- }
975
- // 4. refresh 실패 시 - 기존 토큰이 있으면 반환
976
- if (token.id) {
977
- debugLog('handleJWTCallback', 'Refresh failed, returning existing token (possibly expired)');
978
- return token;
979
- }
980
- // 5. 모든 시도 실패 - 빈 토큰 반환
981
- debugLog('handleJWTCallback', 'All attempts failed, returning empty token');
982
- return token;
983
- }
984
- /**
985
- * 쿠키에서 커스텀 토큰을 읽어서 NextAuth JWT로 변환하는 헬퍼 함수
986
- * NextAuth JWT 콜백에서 사용
987
- * @param cookieName 쿠키 이름 (예: 'checkon_access_token')
988
- * @param secret JWT 서명에 사용할 secret key
989
- * @param serviceId 서비스 ID (필수)
990
- * @param licenseKey 라이센스 키 (필수)
991
- * @returns NextAuth JWT 객체 또는 null
992
- */
993
- export async function getJWTFromCustomTokenCookie(cookieName, secret, serviceId, licenseKey) {
994
- debugLog('getJWTFromCustomTokenCookie', `Reading cookie: ${cookieName}`);
995
- try {
996
- const { cookies } = await import('next/headers');
997
- const cookieStore = await cookies();
998
- const accessToken = cookieStore.get(cookieName)?.value;
999
- if (!accessToken) {
1000
- debugLog('getJWTFromCustomTokenCookie', 'No access token found in cookies');
1001
- return null;
1002
- }
1003
- await checkLicenseKey(licenseKey);
1004
- const tokenResult = await verifyToken(accessToken, secret);
1005
- if (!tokenResult) {
1006
- debugLog('getJWTFromCustomTokenCookie', 'Token verification failed');
1007
- return null;
1008
- }
1009
- const { payload } = tokenResult;
1010
- const jwt = createNextAuthJWT(payload, serviceId);
1011
- // accessTokenExpires 추가 (15분)
1012
- jwt.accessTokenExpires = Date.now() + (15 * 60 * 1000);
1013
- // refreshToken 읽기 (쿠키에서)
1014
- const refreshTokenCookieName = cookieName.replace('_access_token', '_refresh_token');
1015
- const refreshToken = cookieStore.get(refreshTokenCookieName)?.value;
1016
- if (refreshToken) {
1017
- jwt.refreshToken = refreshToken;
1018
- }
1019
- debugLog('getJWTFromCustomTokenCookie', 'JWT created successfully from custom token', {
1020
- hasAccessTokenExpires: !!jwt.accessTokenExpires,
1021
- hasRefreshToken: !!jwt.refreshToken,
1022
- });
1023
- return jwt;
1024
- }
1025
- catch (error) {
1026
- debugError('getJWTFromCustomTokenCookie', `Failed to read ${cookieName}:`, error);
1027
- return null;
1028
- }
1029
- }
1030
- // ============================================================================
1031
- // LICENSE & AUTHORIZATION FUNCTIONS
1032
- // ============================================================================
1033
- export async function checkLicenseKey(licenseKey) {
1034
- if (!licenseKey || licenseKey.length < 10) {
1035
- throw new Error('License key is required');
1036
- }
1037
- const keyHash = await createHashSHA256(licenseKey);
1038
- if (!VALID_LICENSE_KEY_HASHES.has(keyHash)) {
1039
- throw new Error('Invalid license key');
1040
- }
1041
- }
1042
- export function checkRoleAccess(pathname, role, roleConfig) {
1043
- // 각 역할 설정을 확인
1044
- for (const [configRole, config] of Object.entries(roleConfig)) {
1045
- const isPathMatch = config.paths.some(path => pathname.startsWith(path));
1046
- if (isPathMatch) {
1047
- // 역할이 정확히 일치하거나 allowedRoles에 포함되어 있으면 허용
1048
- if (role === configRole || config.allowedRoles?.includes(role)) {
1049
- return { allowed: true };
1050
- }
1051
- // 접근 거부
1052
- return { allowed: false, message: config.message };
1053
- }
1054
- }
1055
- // 매칭되는 경로가 없으면 허용
1056
- return { allowed: true };
1057
- }
1058
- // ============================================================================
1059
- // REDIRECT FUNCTIONS
1060
- // ============================================================================
1061
- /**
1062
- * SSO 로그인 페이지로 리다이렉트하는 헬퍼 함수
1063
- * @param req NextRequest 객체
1064
- * @param serviceId 서비스 ID
1065
- * @param ssoBaseURL SSO 서버 기본 URL (필수)
1066
- * @returns NextResponse 리다이렉트 응답
1067
- */
1068
- export async function redirectToSSOLogin(req, serviceId, ssoBaseURL) {
1069
- const { NextResponse: NextResponseClass } = await getNextServer();
1070
- return NextResponseClass.redirect(new URL(`${ssoBaseURL}/auth/login?serviceId=${serviceId}`, req.url));
1071
- }
1072
- /**
1073
- * 역할별 대시보드 경로로 리다이렉트하는 헬퍼 함수
1074
- * @param req NextRequest 객체
1075
- * @param role 사용자 역할
1076
- * @param rolePaths 역할별 경로 설정 객체
1077
- * @param defaultPath 기본 경로 (기본값: '/admin')
1078
- * @returns NextResponse 리다이렉트 응답
1079
- */
1080
- export async function redirectToRoleDashboard(req, role, rolePaths, defaultPath = '/admin') {
1081
- const redirectPath = rolePaths[role] || defaultPath;
1082
- const { NextResponse: NextResponseClass } = await getNextServer();
1083
- return NextResponseClass.redirect(new URL(redirectPath, req.url));
1084
- }
1085
- // ============================================================================
1086
- // TOKEN VALIDATION UTILITIES
1087
- // ============================================================================
1088
- /**
1089
- * 토큰이 만료되었는지 확인하는 함수
1090
- * @param token NextAuth JWT 객체
1091
- * @returns 만료 여부
1092
- */
1093
- export function isTokenExpired(token) {
1094
- if (!token)
1095
- return true;
1096
- if (token.exp && typeof token.exp === 'number' && token.exp < Math.floor(Date.now() / 1000)) {
1097
- return true;
1098
- }
1099
- return false;
1100
- }
1101
- /**
1102
- * 토큰이 유효한지 확인하는 함수 (만료 및 필수 필드 체크)
1103
- * @param token NextAuth JWT 객체
1104
- * @returns 유효성 여부
1105
- */
1106
- export function isValidToken(token) {
1107
- if (!token)
1108
- return false;
1109
- if (isTokenExpired(token))
1110
- return false;
1111
- if (!token.email || !token.id)
1112
- return false;
1113
- return true;
1114
- }
1115
- /**
1116
- * 특정 역할을 가지고 있는지 확인하는 함수
1117
- * @param token NextAuth JWT 객체
1118
- * @param role 확인할 역할
1119
- * @param serviceId 서비스 ID (필수)
1120
- * @returns 역할 보유 여부
1121
- */
1122
- export function hasRole(token, role, serviceId) {
1123
- if (!token)
1124
- return false;
1125
- const effectiveRole = getEffectiveRole(token, serviceId);
1126
- return effectiveRole === role;
1127
- }
1128
- /**
1129
- * 여러 역할 중 하나라도 가지고 있는지 확인하는 함수
1130
- * @param token NextAuth JWT 객체
1131
- * @param roles 확인할 역할 배열
1132
- * @param serviceId 서비스 ID (필수)
1133
- * @returns 역할 보유 여부
1134
- */
1135
- export function hasAnyRole(token, roles, serviceId) {
1136
- if (!token)
1137
- return false;
1138
- const effectiveRole = getEffectiveRole(token, serviceId);
1139
- return roles.includes(effectiveRole || '');
1140
- }
1141
- // ============================================================================
1142
- // PATH UTILITIES
1143
- // ============================================================================
1144
- /**
1145
- * 공개 경로인지 확인하는 함수
1146
- * @param pathname 경로명
1147
- * @param publicPaths 공개 경로 배열
1148
- * @returns 공개 경로 여부
1149
- */
1150
- export function isPublicPath(pathname, publicPaths) {
1151
- return publicPaths.some(path => pathname === path || pathname.startsWith(path));
1152
- }
1153
- /**
1154
- * API 경로인지 확인하는 함수
1155
- * @param pathname 경로명
1156
- * @returns API 경로 여부
1157
- */
1158
- export function isApiPath(pathname) {
1159
- return pathname.startsWith('/api/');
1160
- }
1161
- /**
1162
- * 보호된 API 경로인지 확인하는 함수
1163
- * @param pathname 경로명
1164
- * @param exemptPaths 제외할 경로 배열
1165
- * @returns 보호된 API 경로 여부
1166
- */
1167
- export function isProtectedApiPath(pathname, exemptPaths = []) {
1168
- if (!isApiPath(pathname))
1169
- return false;
1170
- return !exemptPaths.some(path => pathname.startsWith(path));
1171
- }
1172
- // ============================================================================
1173
- // AUTHENTICATION CHECK FUNCTIONS
1174
- // ============================================================================
1175
- /**
1176
- * NextAuth 토큰과 자체 토큰을 모두 확인하는 통합 인증 체크 함수
1177
- * @param req NextRequest 객체
1178
- * @param secret JWT 서명에 사용할 secret key
1179
- * @param options 옵션
1180
- * @returns 인증 결과
1181
- */
1182
- export async function checkAuthentication(req, secret, options) {
1183
- const { cookiePrefix, serviceId, getNextAuthToken, } = options;
1184
- let nextAuthToken = null;
1185
- if (getNextAuthToken) {
1186
- nextAuthToken = await getNextAuthToken(req);
1187
- }
1188
- if (nextAuthToken && isValidToken(nextAuthToken)) {
1189
- return {
1190
- isAuthenticated: true,
1191
- token: nextAuthToken,
1192
- };
1193
- }
1194
- const authCheck = await verifyAndRefreshToken(req, secret, options);
1195
- if (authCheck.response) {
1196
- return {
1197
- isAuthenticated: true,
1198
- response: authCheck.response,
1199
- payload: authCheck.payload,
1200
- };
1201
- }
1202
- if (authCheck.isValid && authCheck.payload) {
1203
- return {
1204
- isAuthenticated: true,
1205
- payload: authCheck.payload,
1206
- };
1207
- }
1208
- return {
1209
- isAuthenticated: false,
1210
- error: authCheck.error || 'NO_TOKEN',
1211
- };
1212
- }
1213
- /**
1214
- * NextAuth 토큰과 자체 토큰을 모두 확인하는 미들웨어용 함수
1215
- * NextAuth 토큰이 있으면 바로 통과, 없으면 자체 토큰을 확인
1216
- * @param req NextRequest 객체
1217
- * @param nextAuthToken NextAuth JWT 토큰 (null 가능)
1218
- * @param secret JWT 서명에 사용할 secret key
1219
- * @param options 옵션
1220
- * @returns 인증 결과
1221
- */
1222
- export async function verifyAndRefreshTokenWithNextAuth(req, nextAuthToken, secret, options) {
1223
- const { cookiePrefix, isProduction } = options;
1224
- // NextAuth 세션 토큰 쿠키 확인
1225
- const nextAuthSessionTokenCookieName = isProduction
1226
- ? '__Secure-next-auth.session-token'
1227
- : 'next-auth.session-token';
1228
- const nextAuthCookieValue = req.cookies.get(nextAuthSessionTokenCookieName)?.value;
1229
- const hasNextAuthSessionTokenCookie = !!nextAuthCookieValue;
1230
- debugLog('verifyAndRefreshTokenWithNextAuth', 'NextAuth cookie check:', {
1231
- cookieName: nextAuthSessionTokenCookieName,
1232
- hasCookie: hasNextAuthSessionTokenCookie,
1233
- cookieLength: nextAuthCookieValue?.length || 0,
1234
- cookiePrefix: nextAuthCookieValue?.substring(0, 30) + '...' || 'none',
1235
- });
1236
- // NextAuth 토큰 확인
1237
- const hasValidNextAuthToken = nextAuthToken && isValidToken(nextAuthToken);
1238
- // Access token 확인
1239
- const accessTokenName = `${cookiePrefix}_access_token`;
1240
- const accessToken = req.cookies.get(accessTokenName)?.value;
1241
- let hasValidAccessToken = false;
1242
- if (accessToken) {
1243
- try {
1244
- const secretBytes = new TextEncoder().encode(secret);
1245
- const { payload } = await jwtVerify(accessToken, secretBytes);
1246
- if (payload && typeof payload === 'object' && payload.email) {
1247
- hasValidAccessToken = true;
1248
- }
1249
- }
1250
- catch {
1251
- // 토큰 검증 실패
1252
- }
1253
- }
1254
- // Refresh token 확인
1255
- const refreshTokenName = `${cookiePrefix}_refresh_token`;
1256
- const refreshToken = req.cookies.get(refreshTokenName)?.value;
1257
- debugLog('verifyAndRefreshTokenWithNextAuth', 'Token status:', {
1258
- hasNextAuthCookie: hasNextAuthSessionTokenCookie,
1259
- hasValidNextAuth: hasValidNextAuthToken,
1260
- hasValidAccess: hasValidAccessToken,
1261
- hasRefresh: !!refreshToken,
1262
- });
1263
- // NextAuth cookie와 access token이 모두 유효하면 통과
1264
- if (hasValidNextAuthToken && hasValidAccessToken) {
1265
- debugLog('verifyAndRefreshTokenWithNextAuth', 'Both NextAuth and access tokens are valid');
1266
- // payload 추출
1267
- let payload;
1268
- if (accessToken) {
1269
- try {
1270
- const secretBytes = new TextEncoder().encode(secret);
1271
- const result = await jwtVerify(accessToken, secretBytes);
1272
- payload = result.payload;
1273
- }
1274
- catch {
1275
- // 이미 검증됐으므로 실패하지 않을 것
1276
- }
1277
- }
1278
- return {
1279
- isValid: true,
1280
- token: nextAuthToken,
1281
- payload
1282
- };
1283
- }
1284
- // NextAuth cookie가 없거나 access token이 없으면 refresh 시도
1285
- if (refreshToken && (!hasValidNextAuthToken || !hasValidAccessToken)) {
1286
- debugLog('verifyAndRefreshTokenWithNextAuth', 'Missing NextAuth or access token, attempting refresh');
1287
- const authCheck = await verifyAndRefreshToken(req, secret, {
1288
- ...options,
1289
- forceRefresh: true,
1290
- });
1291
- // refresh 후 NextAuth token 재조회 (새로 생성된 cookie에서)
1292
- let refreshedToken = null;
1293
- if (authCheck.isValid && authCheck.payload) {
1294
- // payload에서 JWT 생성
1295
- refreshedToken = createNextAuthJWT(authCheck.payload, options.serviceId);
1296
- }
1297
- return {
1298
- ...authCheck,
1299
- token: refreshedToken || undefined
1300
- };
1301
- }
1302
- // 하나라도 유효하면 일단 통과 (refresh token이 없는 경우)
1303
- if (hasValidNextAuthToken || hasValidAccessToken) {
1304
- debugLog('verifyAndRefreshTokenWithNextAuth', 'At least one token is valid (no refresh token)');
1305
- // payload 추출
1306
- let payload;
1307
- if (accessToken && hasValidAccessToken) {
1308
- try {
1309
- const secretBytes = new TextEncoder().encode(secret);
1310
- const result = await jwtVerify(accessToken, secretBytes);
1311
- payload = result.payload;
1312
- }
1313
- catch {
1314
- // 무시
1315
- }
1316
- }
1317
- return {
1318
- isValid: true,
1319
- token: nextAuthToken || (payload ? createNextAuthJWT(payload, options.serviceId) : undefined),
1320
- payload
1321
- };
1322
- }
1323
- debugLog('verifyAndRefreshTokenWithNextAuth', 'No tokens available');
1324
- return { isValid: false, error: 'NO_TOKEN' };
1325
- }
1326
- // ============================================================================
1327
- // MIDDLEWARE CONFIGURATION & HANDLER
1328
- // ============================================================================
1329
- /**
1330
- * 기본 미들웨어 설정을 생성하는 함수
1331
- * @param config 커스텀 설정 (필수: serviceId 포함)
1332
- * @param defaults 기본 설정값 (선택사항, 제공하지 않으면 최소 기본값 사용)
1333
- * @returns 미들웨어 설정 객체
1334
- */
1335
- export function createMiddlewareConfig(config, defaults) {
1336
- // 기본값 설정
1337
- const defaultPublicPaths = defaults?.publicPaths || [
1338
- '/robots.txt',
1339
- '/sitemap.xml',
1340
- '/ads.txt',
1341
- '/images',
1342
- '/login',
1343
- '/register',
1344
- '/forgot-password',
1345
- '/reset-password',
1346
- '/about',
1347
- '/pricing',
1348
- '/support',
1349
- '/terms',
1350
- '/privacy',
1351
- '/refund',
1352
- '/error',
1353
- ];
1354
- const defaultSubscriptionRequiredPaths = defaults?.subscriptionRequiredPaths || [
1355
- '/admin',
1356
- '/teacher',
1357
- '/student',
1358
- '/personal',
1359
- ];
1360
- const defaultSubscriptionExemptApiPaths = defaults?.subscriptionExemptApiPaths || [
1361
- '/api/auth',
1362
- '/api/sso',
1363
- '/api/admin/subscription',
1364
- '/api/admin/payment-methods',
1365
- ];
1366
- const defaultAuthApiPaths = defaults?.authApiPaths || [
1367
- '/api/auth/send-verification',
1368
- '/api/auth/verify-code',
1369
- '/api/auth/verify-user',
1370
- '/api/auth/select-academy',
1371
- ];
1372
- const defaultRolePaths = defaults?.rolePaths || {
1373
- SYSTEM_ADMIN: '/system',
1374
- ADMIN: '/admin',
1375
- TEACHER: '/teacher',
1376
- STUDENT: '/student',
1377
- };
1378
- const defaultSystemAdminRole = defaults?.systemAdminRole || 'SYSTEM_ADMIN';
1379
- const defaultErrorPath = defaults?.errorPath || '/error';
1380
- // 커스텀 설정으로 병합
1381
- return {
1382
- publicPaths: config.publicPaths || defaultPublicPaths,
1383
- subscriptionRequiredPaths: config.subscriptionRequiredPaths || defaultSubscriptionRequiredPaths,
1384
- subscriptionExemptApiPaths: config.subscriptionExemptApiPaths || defaultSubscriptionExemptApiPaths,
1385
- authApiPaths: config.authApiPaths || defaultAuthApiPaths,
1386
- rolePaths: config.rolePaths || defaultRolePaths,
1387
- roleAccessConfig: config.roleAccessConfig || {},
1388
- systemAdminRole: config.systemAdminRole || defaultSystemAdminRole,
1389
- errorPath: config.errorPath || defaultErrorPath,
1390
- serviceId: config.serviceId,
1391
- };
1392
- }
1393
- /**
1394
- * 통합 미들웨어 핸들러 함수
1395
- * 모든 인증, 권한, 구독 체크를 포함한 완전한 미들웨어 로직
1396
- * @param req NextRequest 객체
1397
- * @param config 미들웨어 설정
1398
- * @param options 미들웨어 실행 옵션 (secret 필수)
1399
- * @returns NextResponse 또는 null (다음 미들웨어로 진행)
1400
- */
1401
- export async function handleMiddleware(req, config, options) {
1402
- // Edge Runtime 호환을 위해 next/server를 한 번만 import
1403
- const { NextResponse: NextResponseClass } = await getNextServer();
1404
- try {
1405
- const pathname = req.nextUrl.pathname;
1406
- const { secret, isProduction, cookieDomain, getNextAuthToken, licenseKey } = options;
1407
- debugLog('handleMiddleware', `Processing: ${pathname}`);
1408
- await checkLicenseKey(licenseKey);
1409
- if (!config.serviceId) {
1410
- throw new Error('serviceId is required in middleware config');
1411
- }
1412
- const serviceId = config.serviceId;
1413
- const cookiePrefix = serviceId;
1414
- let token = null;
1415
- if (getNextAuthToken) {
1416
- token = await getNextAuthToken(req);
1417
- debugLog('handleMiddleware', 'Custom getNextAuthToken result:', { hasToken: !!token });
1418
- }
1419
- else {
1420
- try {
1421
- const { getToken } = await import('next-auth/jwt');
1422
- token = await getToken({ req, secret });
1423
- debugLog('handleMiddleware', 'getToken result:', { hasToken: !!token });
1424
- }
1425
- catch (error) {
1426
- debugLog('handleMiddleware', 'getToken failed:', error);
1427
- // NextAuth가 없으면 null 유지
1428
- }
1429
- }
1430
- debugLog('handleMiddleware', 'Token status:', {
1431
- hasToken: !!token,
1432
- hasId: !!token?.id,
1433
- });
1434
- const effectiveRole = getEffectiveRole(token, serviceId);
1435
- debugLog('handleMiddleware', `Effective role: ${effectiveRole || 'none'}`);
1436
- // 1. API 요청 처리
1437
- if (pathname.startsWith('/api/')) {
1438
- if (config.authApiPaths.includes(pathname)) {
1439
- return NextResponseClass.next();
1440
- }
1441
- if (config.subscriptionExemptApiPaths.some((path) => pathname.startsWith(path))) {
1442
- return NextResponseClass.next();
1443
- }
1444
- const authCheck = await verifyAndRefreshTokenWithNextAuth(req, token, secret, {
1445
- cookiePrefix,
1446
- serviceId,
1447
- isProduction,
1448
- cookieDomain,
1449
- text: serviceId,
1450
- ssoBaseURL: options.ssoBaseURL,
1451
- authServiceKey: options.authServiceKey,
1452
- licenseKey: options.licenseKey,
1453
- });
1454
- if (authCheck.response) {
1455
- return authCheck.response;
1456
- }
1457
- if (!authCheck.isValid) {
1458
- const response = await redirectToError(req, 'UNAUTHORIZED', '인증이 필요합니다.', config.errorPath);
1459
- return clearAuthCookies(response, cookiePrefix);
1460
- }
1461
- return NextResponseClass.next();
1462
- }
1463
- // 2. 루트 경로 처리 - SSO 토큰 처리 (인증 체크보다 먼저!)
1464
- if (pathname === '/') {
1465
- const tokenParam = req.nextUrl.searchParams.get('token');
1466
- console.log('[handleMiddleware] Root path check:', {
1467
- pathname,
1468
- hasTokenParam: !!tokenParam,
1469
- tokenLength: tokenParam?.length || 0,
1470
- });
1471
- if (tokenParam) {
1472
- debugLog('handleMiddleware', 'Processing SSO token from query parameter');
1473
- console.log('[handleMiddleware] Processing SSO token from query parameter');
1474
- try {
1475
- // 1. 토큰 검증
1476
- const tokenResult = await verifyToken(tokenParam, secret);
1477
- if (!tokenResult) {
1478
- throw new Error('Invalid token');
1479
- }
1480
- const { payload } = tokenResult;
1481
- // 2. 역할 추출
1482
- const defaultRole = Object.keys(config.rolePaths)[0] || 'ADMIN';
1483
- const tokenRole = extractRoleFromPayload(payload, serviceId, defaultRole);
1484
- debugLog('handleMiddleware', `Extracted role: ${tokenRole}`);
1485
- // 3. Refresh token 가져오기 (서버 간 통신)
1486
- const userId = payload.id || payload.sub || payload.userId || '';
1487
- const ssoBaseURL = options.ssoBaseURL;
1488
- const authServiceKey = options.authServiceKey;
1489
- debugLog('handleMiddleware', 'Getting refresh token from SSO:', {
1490
- userId,
1491
- hasSSO: !!ssoBaseURL,
1492
- hasAuthKey: !!authServiceKey,
1493
- });
1494
- let refreshToken = '';
1495
- if (authServiceKey && userId) {
1496
- try {
1497
- const refreshTokenResult = await getRefreshTokenFromSSO(userId, tokenParam, { ssoBaseURL, authServiceKey });
1498
- refreshToken = refreshTokenResult || '';
1499
- debugLog('handleMiddleware', 'Refresh token result:', {
1500
- hasRefreshToken: !!refreshToken,
1501
- length: refreshToken.length,
1502
- });
1503
- }
1504
- catch (error) {
1505
- debugError('handleMiddleware', 'Failed to get refresh token:', error);
1506
- // refresh token이 없어도 access token으로는 로그인 가능
1507
- }
1508
- }
1509
- else {
1510
- debugLog('handleMiddleware', 'Skipping refresh token fetch:', {
1511
- hasUserId: !!userId,
1512
- hasAuthKey: !!authServiceKey,
1513
- });
1514
- }
1515
- // 4. 자체 토큰 생성 및 쿠키 설정
1516
- const redirectPath = config.rolePaths[tokenRole] || config.rolePaths[defaultRole] || '/admin';
1517
- debugLog('handleMiddleware', `Creating auth response, redirect to: ${redirectPath}`);
1518
- console.log('[handleMiddleware] Creating auth response:', {
1519
- redirectPath,
1520
- hasRefreshToken: !!refreshToken,
1521
- cookiePrefix,
1522
- isProduction,
1523
- cookieDomain,
1524
- });
1525
- const response = await createAuthResponse(tokenParam, secret, {
1526
- req,
1527
- refreshToken: refreshToken || undefined,
1528
- redirectPath,
1529
- text: serviceId,
1530
- cookiePrefix,
1531
- isProduction,
1532
- cookieDomain,
1533
- serviceId,
1534
- licenseKey: options.licenseKey,
1535
- });
1536
- console.log('[handleMiddleware] Auth response created, cookies set:', {
1537
- hasResponse: !!response,
1538
- cookieNames: ['renton_access_token', 'renton_refresh_token', isProduction ? '__Secure-next-auth.session-token' : 'next-auth.session-token'],
1539
- });
1540
- return response;
1541
- }
1542
- catch (error) {
1543
- debugError('handleMiddleware', 'Error processing token:', error);
1544
- const ssoBaseURL = options.ssoBaseURL;
1545
- return await redirectToSSOLogin(req, serviceId, ssoBaseURL);
1546
- }
1547
- }
1548
- // 토큰이 없고 이미 인증된 경우 역할별 대시보드로 리다이렉트
1549
- if (token && effectiveRole) {
1550
- return await redirectToRoleDashboard(req, effectiveRole, config.rolePaths);
1551
- }
1552
- // 인증되지 않은 경우 SSO 로그인 페이지로 리다이렉트
1553
- const ssoBaseURL = options.ssoBaseURL;
1554
- return await redirectToSSOLogin(req, serviceId, ssoBaseURL);
1555
- }
1556
- // 3. 공개 경로 처리
1557
- if (config.publicPaths.some((path) => pathname === path || pathname.startsWith(path))) {
1558
- if (pathname === '/error' || pathname === '/verification') {
1559
- return NextResponseClass.next();
1560
- }
1561
- if (token && effectiveRole) {
1562
- return await redirectToRoleDashboard(req, effectiveRole, config.rolePaths);
1563
- }
1564
- return NextResponseClass.next();
1565
- }
1566
- // 4. 인증 체크
1567
- const authCheck = await verifyAndRefreshTokenWithNextAuth(req, token, secret, {
1568
- cookiePrefix,
1569
- serviceId,
1570
- isProduction,
1571
- cookieDomain,
1572
- text: serviceId,
1573
- ssoBaseURL: options.ssoBaseURL,
1574
- authServiceKey: options.authServiceKey,
1575
- licenseKey: options.licenseKey,
1576
- });
1577
- if (authCheck.response) {
1578
- return authCheck.response;
1579
- }
1580
- if (!authCheck.isValid) {
1581
- const ssoBaseURL = options.ssoBaseURL;
1582
- return await redirectToSSOLogin(req, serviceId, ssoBaseURL);
1583
- }
1584
- // 5. 토큰 확인 - authCheck 결과 재사용 (중복 검증 제거)
1585
- let finalToken = authCheck.token || token;
1586
- // authCheck에서 토큰을 반환하지 않았지만 유효한 경우 (드문 케이스)
1587
- if (!finalToken && authCheck.isValid) {
1588
- debugLog('handleMiddleware', 'authCheck valid but no token, trying to get NextAuth token');
1589
- if (getNextAuthToken) {
1590
- finalToken = await getNextAuthToken(req);
1591
- }
1592
- else {
1593
- try {
1594
- const { getToken } = await import('next-auth/jwt');
1595
- finalToken = await getToken({ req, secret });
1596
- }
1597
- catch {
1598
- // NextAuth가 없으면 null 유지
1599
- }
1600
- }
1601
- }
1602
- if (!finalToken) {
1603
- const ssoBaseURL = options.ssoBaseURL;
1604
- return await redirectToSSOLogin(req, serviceId, ssoBaseURL);
1605
- }
1606
- // 6. 토큰 에러 체크
1607
- if (finalToken.error === "RefreshAccessTokenError") {
1608
- const ssoBaseURL = options.ssoBaseURL;
1609
- return await redirectToSSOLogin(req, serviceId, ssoBaseURL);
1610
- }
1611
- // 7. 토큰 유효성 체크
1612
- if (!finalToken.role || !finalToken.email) {
1613
- const ssoBaseURL = options.ssoBaseURL;
1614
- return await redirectToSSOLogin(req, serviceId, ssoBaseURL);
1615
- }
1616
- // 8. 역할 기반 접근 제어
1617
- const finalEffectiveRole = finalToken.role || getEffectiveRole(finalToken, serviceId) || effectiveRole || '';
1618
- if (config.roleAccessConfig && Object.keys(config.roleAccessConfig).length > 0 && finalEffectiveRole) {
1619
- const roleCheck = checkRoleAccess(pathname, finalEffectiveRole, config.roleAccessConfig);
1620
- if (!roleCheck.allowed) {
1621
- return await redirectToError(req, 'ACCESS_DENIED', roleCheck.message || '접근 권한이 없습니다.', config.errorPath);
1622
- }
1623
- }
1624
- // 9. 구독 상태 확인 (시스템 관리자 제외)
1625
- if (finalEffectiveRole && requiresSubscription(pathname, finalEffectiveRole, config.subscriptionRequiredPaths, config.systemAdminRole)) {
1626
- const services = finalToken.services || [];
1627
- const ssoBaseURL = options.ssoBaseURL;
1628
- if (!ssoBaseURL) {
1629
- throw new Error('ssoBaseURL is required in middleware options');
1630
- }
1631
- const subscriptionCheck = validateServiceSubscription(services, serviceId, ssoBaseURL);
1632
- if (!subscriptionCheck.isValid) {
1633
- return NextResponseClass.redirect(subscriptionCheck.redirectUrl);
1634
- }
1635
- }
1636
- return null;
1637
- }
1638
- catch (error) {
1639
- debugError('handleMiddleware', 'Middleware error:', error);
1640
- return await redirectToError(req, 'INTERNAL_ERROR', '서버 오류가 발생했습니다.', config.errorPath);
1641
- }
1642
- }
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./types/index"), exports);
18
+ __exportStar(require("./utils/logger"), exports);
19
+ __exportStar(require("./utils/crypto"), exports);
20
+ __exportStar(require("./utils/server"), exports);
21
+ __exportStar(require("./utils/redirect"), exports);
22
+ __exportStar(require("./utils/path"), exports);
23
+ __exportStar(require("./utils/license"), exports);
24
+ __exportStar(require("./core/jwt"), exports);
25
+ __exportStar(require("./core/cookies"), exports);
26
+ __exportStar(require("./core/auth-response"), exports);
27
+ __exportStar(require("./core/roles"), exports);
28
+ __exportStar(require("./core/verify"), exports);
29
+ __exportStar(require("./sso/api"), exports);
30
+ __exportStar(require("./nextauth/config"), exports);
31
+ __exportStar(require("./nextauth/callbacks"), exports);
32
+ __exportStar(require("./nextauth/session"), exports);
33
+ __exportStar(require("./middleware/config"), exports);
34
+ __exportStar(require("./middleware/handler"), exports);