@thinkingcat/auth-utils 1.0.7 → 1.0.9

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,4 +1,37 @@
1
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.verifyToken = verifyToken;
4
37
  exports.extractRoleFromPayload = extractRoleFromPayload;
@@ -16,6 +49,7 @@ exports.redirectToError = redirectToError;
16
49
  exports.clearAuthCookies = clearAuthCookies;
17
50
  exports.getEffectiveRole = getEffectiveRole;
18
51
  exports.requiresSubscription = requiresSubscription;
52
+ exports.checkLicenseKey = checkLicenseKey;
19
53
  exports.checkRoleAccess = checkRoleAccess;
20
54
  exports.redirectToSSOLogin = redirectToSSOLogin;
21
55
  exports.redirectToRoleDashboard = redirectToRoleDashboard;
@@ -33,6 +67,7 @@ exports.handleMiddleware = handleMiddleware;
33
67
  const jwt_1 = require("next-auth/jwt");
34
68
  const jose_1 = require("jose");
35
69
  const server_1 = require("next/server");
70
+ const crypto_1 = require("crypto");
36
71
  /**
37
72
  * 토큰 검증 및 디코딩
38
73
  * @param accessToken JWT access token
@@ -55,11 +90,11 @@ async function verifyToken(accessToken, secret) {
55
90
  /**
56
91
  * payload에서 역할 추출 (서비스별)
57
92
  * @param payload JWT payload
58
- * @param serviceId 서비스 ID (기본값: 'checkon')
93
+ * @param serviceId 서비스 ID (필수)
59
94
  * @param defaultRole 기본 역할 (기본값: 'ADMIN')
60
95
  * @returns 추출된 역할
61
96
  */
62
- function extractRoleFromPayload(payload, serviceId = 'checkon', defaultRole = 'ADMIN') {
97
+ function extractRoleFromPayload(payload, serviceId, defaultRole = 'ADMIN') {
63
98
  const services = payload.services || [];
64
99
  const service = services.find((s) => s.serviceId === serviceId);
65
100
  return service?.role || payload.role || defaultRole;
@@ -67,18 +102,19 @@ function extractRoleFromPayload(payload, serviceId = 'checkon', defaultRole = 'A
67
102
  /**
68
103
  * payload에서 NextAuth JWT 객체 생성
69
104
  * @param payload JWT payload
105
+ * @param serviceId 서비스 ID (필수)
70
106
  * @param includeAcademies academies 정보 포함 여부
71
107
  * @returns NextAuth JWT 객체
72
108
  */
73
- function createNextAuthJWT(payload, includeAcademies = false) {
109
+ function createNextAuthJWT(payload, serviceId, includeAcademies = false) {
74
110
  const services = payload.services || [];
75
- const checkonService = services.find((s) => s.serviceId === 'checkon');
76
- const effectiveRole = checkonService?.role || payload.role || 'ADMIN';
111
+ const service = services.find((s) => s.serviceId === serviceId);
112
+ const effectiveRole = service?.role || payload.role || 'ADMIN';
77
113
  const jwt = {
78
114
  id: (payload.id || payload.sub),
79
115
  email: payload.email,
80
116
  name: payload.name,
81
- role: effectiveRole, // Role enum 타입
117
+ role: effectiveRole, // Role enum 타입 (string으로 캐스팅)
82
118
  services: payload.services,
83
119
  academies: includeAcademies
84
120
  ? payload.academies || []
@@ -142,7 +178,10 @@ function setCustomTokens(response, accessToken, optionsOrRefreshToken, options)
142
178
  if (typeof optionsOrRefreshToken === 'string') {
143
179
  // 기존 방식: refreshToken이 문자열로 전달된 경우
144
180
  refreshTokenValue = optionsOrRefreshToken;
145
- const { cookiePrefix: prefix = 'checkon', isProduction: prod = false, } = options || {};
181
+ const { cookiePrefix: prefix, isProduction: prod = false, } = options || {};
182
+ if (!prefix) {
183
+ throw new Error('cookiePrefix is required');
184
+ }
146
185
  cookiePrefix = prefix;
147
186
  isProduction = prod;
148
187
  }
@@ -150,7 +189,10 @@ function setCustomTokens(response, accessToken, optionsOrRefreshToken, options)
150
189
  // 새로운 방식: options 객체로 전달된 경우
151
190
  const opts = optionsOrRefreshToken || {};
152
191
  refreshTokenValue = opts.refreshToken;
153
- cookiePrefix = opts.cookiePrefix || 'checkon';
192
+ if (!opts.cookiePrefix) {
193
+ throw new Error('cookiePrefix is required');
194
+ }
195
+ cookiePrefix = opts.cookiePrefix;
154
196
  isProduction = opts.isProduction || false;
155
197
  }
156
198
  // access_token 설정
@@ -204,10 +246,10 @@ function setNextAuthToken(response, sessionToken, options = {}) {
204
246
  /**
205
247
  * 리다이렉트용 HTML 생성
206
248
  * @param redirectPath 리다이렉트할 경로
207
- * @param text 표시할 텍스트 (기본값: 'checkon')
249
+ * @param text 표시할 텍스트 (필수)
208
250
  * @returns HTML 문자열
209
251
  */
210
- function createRedirectHTML(redirectPath, text = 'checkon') {
252
+ function createRedirectHTML(redirectPath, text) {
211
253
  return `
212
254
  <!DOCTYPE html>
213
255
  <html>
@@ -282,14 +324,15 @@ function createRedirectHTML(redirectPath, text = 'checkon') {
282
324
  * @param options 추가 옵션
283
325
  * @param options.refreshToken refresh token (선택)
284
326
  * @param options.redirectPath 리다이렉트할 경로 (기본값: 페이지 리로드)
285
- * @param options.text 리다이렉트 HTML에 표시할 텍스트 (기본값: 'checkon')
286
- * @param options.cookiePrefix 쿠키 이름 접두사 (기본값: 'checkon')
327
+ * @param options.text 리다이렉트 HTML에 표시할 텍스트 (선택사항)
328
+ * @param options.cookiePrefix 쿠키 이름 접두사 (필수)
287
329
  * @param options.isProduction 프로덕션 환경 여부 (기본값: false)
288
330
  * @param options.cookieDomain 쿠키 도메인 (선택)
289
331
  * @returns NextResponse 객체
290
332
  */
291
- async function createAuthResponse(accessToken, secret, options = {}) {
292
- const { refreshToken, redirectPath, text = 'checkon', cookiePrefix = 'checkon', isProduction = false, cookieDomain, } = options;
333
+ async function createAuthResponse(accessToken, secret, options) {
334
+ checkLicenseKey(options.licenseKey);
335
+ const { refreshToken, redirectPath, text, cookiePrefix, isProduction = false, cookieDomain, serviceId, } = options;
293
336
  // 1. 토큰 검증
294
337
  const tokenResult = await verifyToken(accessToken, secret);
295
338
  if (!tokenResult) {
@@ -297,14 +340,15 @@ async function createAuthResponse(accessToken, secret, options = {}) {
297
340
  }
298
341
  const { payload } = tokenResult;
299
342
  // 2. 역할 추출
300
- const role = extractRoleFromPayload(payload);
343
+ const role = extractRoleFromPayload(payload, serviceId);
301
344
  // 3. NextAuth JWT 생성 및 인코딩
302
- const jwt = createNextAuthJWT(payload);
345
+ const jwt = createNextAuthJWT(payload, serviceId);
303
346
  const sessionToken = await encodeNextAuthToken(jwt, secret);
304
347
  // 4. HTML 생성
348
+ const displayText = text || serviceId;
305
349
  const html = redirectPath
306
- ? createRedirectHTML(redirectPath, text)
307
- : createRedirectHTML('', text).replace("window.location.href = ''", "window.location.reload()");
350
+ ? createRedirectHTML(redirectPath, displayText)
351
+ : createRedirectHTML('', displayText).replace("window.location.href = ''", "window.location.reload()");
308
352
  // 5. Response 생성
309
353
  const response = new server_1.NextResponse(html, {
310
354
  status: 200,
@@ -339,21 +383,22 @@ async function createAuthResponse(accessToken, secret, options = {}) {
339
383
  * 서비스 구독 유효성 확인 함수
340
384
  * @param services 서비스 정보 배열
341
385
  * @param serviceId 확인할 서비스 ID
386
+ * @param ssoBaseURL SSO 서버 기본 URL (필수)
342
387
  * @returns 구독 유효성 결과
343
388
  */
344
- function validateServiceSubscription(services, serviceId) {
389
+ function validateServiceSubscription(services, serviceId, ssoBaseURL) {
345
390
  const filteredServices = services.filter(service => service.serviceId === serviceId);
346
391
  if (filteredServices.length === 0) {
347
392
  return {
348
393
  isValid: false,
349
- redirectUrl: `${process.env.SSO_AUTH_SERVER_URL || 'http://localhost:3000'}/services/${serviceId}?type=subscription_required`
394
+ redirectUrl: `${ssoBaseURL}/services/${serviceId}?type=subscription_required`
350
395
  };
351
396
  }
352
397
  const service = filteredServices[0];
353
398
  if (service.status !== "ACTIVE") {
354
399
  return {
355
400
  isValid: false,
356
- redirectUrl: `${process.env.SSO_AUTH_SERVER_URL || 'http://localhost:3000'}/services/${service.serviceId}?type=subscription_required`,
401
+ redirectUrl: `${ssoBaseURL}/services/${service.serviceId}?type=subscription_required`,
357
402
  service
358
403
  };
359
404
  }
@@ -366,13 +411,12 @@ function validateServiceSubscription(services, serviceId) {
366
411
  * SSO 서버에서 refresh token을 사용하여 새로운 access token을 발급받는 함수
367
412
  * @param refreshToken refresh token
368
413
  * @param options 옵션
369
- * @param options.ssoBaseURL SSO 서버 기본 URL (기본값: 환경 변수 또는 기본값)
414
+ * @param options.ssoBaseURL SSO 서버 기본 URL (필수)
370
415
  * @param options.authServiceKey 인증 서비스 키 (기본값: 환경 변수)
371
416
  * @returns SSO refresh token 응답
372
417
  */
373
418
  async function refreshSSOToken(refreshToken, options) {
374
- const ssoBaseURL = options?.ssoBaseURL || process.env.NEXT_PUBLIC_SSO_BASE_URL || 'https://sso.thinkingcatworks.com';
375
- const authServiceKey = options?.authServiceKey || process.env.AUTH_SERVICE_SECRET_KEY;
419
+ const { ssoBaseURL, authServiceKey } = options;
376
420
  if (!authServiceKey) {
377
421
  throw new Error('AUTH_SERVICE_SECRET_KEY not configured');
378
422
  }
@@ -391,13 +435,12 @@ async function refreshSSOToken(refreshToken, options) {
391
435
  * @param userId 사용자 ID
392
436
  * @param accessToken access token
393
437
  * @param options 옵션
394
- * @param options.ssoBaseURL SSO 서버 기본 URL (기본값: 환경 변수 또는 기본값)
438
+ * @param options.ssoBaseURL SSO 서버 기본 URL (필수)
395
439
  * @param options.authServiceKey 인증 서비스 키 (기본값: 환경 변수)
396
440
  * @returns refresh token 또는 null
397
441
  */
398
442
  async function getRefreshTokenFromSSO(userId, accessToken, options) {
399
- const ssoBaseURL = options?.ssoBaseURL || process.env.NEXT_PUBLIC_SSO_BASE_URL || 'https://sso.thinkingcatworks.com';
400
- const authServiceKey = options?.authServiceKey || process.env.AUTH_SERVICE_SECRET_KEY;
443
+ const { ssoBaseURL, authServiceKey } = options;
401
444
  if (!authServiceKey) {
402
445
  return null;
403
446
  }
@@ -423,56 +466,59 @@ async function getRefreshTokenFromSSO(userId, accessToken, options) {
423
466
  }
424
467
  return null;
425
468
  }
426
- /**
427
- * 토큰 검증 및 리프레시 처리 공통 함수
428
- *
429
- * @param req - NextRequest 객체
430
- * @param secret - JWT 서명에 사용할 secret key
431
- * @param options - 옵션
432
- * @param options.cookiePrefix - 쿠키 이름 접두사 (기본값: 'checkon')
433
- * @param options.isProduction - 프로덕션 환경 여부 (기본값: false)
434
- * @param options.cookieDomain - 쿠키 도메인 (선택)
435
- * @param options.text - 리다이렉트 HTML에 표시할 텍스트 (기본값: 'checkon')
436
- * @param options.ssoBaseURL - SSO 서버 기본 URL (기본값: 환경 변수 또는 기본값)
437
- * @param options.authServiceKey - 인증 서비스 키 (기본값: 환경 변수)
438
- * @returns 검증 결과 및 필요시 리프레시된 응답
439
- */
440
469
  async function verifyAndRefreshToken(req, secret, options) {
441
- const { cookiePrefix = 'checkon', isProduction = false, cookieDomain, text = 'checkon', } = options || {};
470
+ const { cookiePrefix, serviceId, isProduction, cookieDomain, text, ssoBaseURL, authServiceKey, } = options;
442
471
  // 1. access_token 쿠키 확인
443
472
  const accessTokenName = `${cookiePrefix}_access_token`;
444
473
  const accessToken = req.cookies.get(accessTokenName)?.value;
445
474
  if (accessToken) {
446
- const tokenResult = await verifyToken(accessToken, secret);
447
- if (tokenResult) {
448
- return { isValid: true, payload: tokenResult.payload };
475
+ try {
476
+ const secretBytes = new TextEncoder().encode(secret);
477
+ const { payload } = await (0, jose_1.jwtVerify)(accessToken, secretBytes);
478
+ if (payload && typeof payload === 'object' && payload.email) {
479
+ return { isValid: true, payload: payload };
480
+ }
481
+ }
482
+ catch {
483
+ // 토큰 검증 실패
449
484
  }
450
- // 토큰이 만료되었거나 유효하지 않음 (리프레시 시도)
451
485
  }
452
- // 2. 리프레시 토큰으로 갱신 시도
486
+ // 리프레시 토큰으로 갱신 시도
453
487
  const refreshTokenName = `${cookiePrefix}_refresh_token`;
454
488
  const refreshToken = req.cookies.get(refreshTokenName)?.value;
455
489
  if (refreshToken) {
456
490
  try {
491
+ if (!ssoBaseURL || !authServiceKey) {
492
+ return { isValid: false, error: 'SSO_CONFIG_MISSING' };
493
+ }
457
494
  const refreshResult = await refreshSSOToken(refreshToken, {
458
- ssoBaseURL: options?.ssoBaseURL,
459
- authServiceKey: options?.authServiceKey,
495
+ ssoBaseURL,
496
+ authServiceKey,
460
497
  });
461
498
  if (refreshResult.success && refreshResult.accessToken) {
462
- // 토큰 갱신 성공 - createAuthResponse 사용
463
499
  const newRefreshToken = refreshResult.refreshToken || refreshToken;
464
500
  try {
501
+ let payload;
502
+ try {
503
+ const secretBytes = new TextEncoder().encode(secret);
504
+ const { payload: tokenPayload } = await (0, jose_1.jwtVerify)(refreshResult.accessToken, secretBytes);
505
+ if (tokenPayload && typeof tokenPayload === 'object' && tokenPayload.email) {
506
+ payload = tokenPayload;
507
+ }
508
+ }
509
+ catch {
510
+ // 토큰 검증 실패
511
+ }
465
512
  const response = await createAuthResponse(refreshResult.accessToken, secret, {
466
513
  refreshToken: newRefreshToken,
467
- redirectPath: '', // 리다이렉트 없이 현재 페이지 유지
468
- text,
514
+ redirectPath: '',
515
+ text: text || serviceId,
469
516
  cookiePrefix,
470
517
  isProduction,
471
518
  cookieDomain,
519
+ serviceId,
520
+ licenseKey: options.licenseKey,
472
521
  });
473
- // payload 추출을 위해 토큰 검증
474
- const tokenResult = await verifyToken(refreshResult.accessToken, secret);
475
- const payload = tokenResult?.payload;
476
522
  return { isValid: true, response, payload };
477
523
  }
478
524
  catch (error) {
@@ -508,10 +554,10 @@ function redirectToError(req, code, message, errorPath = '/error') {
508
554
  /**
509
555
  * 인증 쿠키를 삭제하는 헬퍼 함수
510
556
  * @param response NextResponse 객체
511
- * @param cookiePrefix 쿠키 이름 접두사 (기본값: 'checkon')
557
+ * @param cookiePrefix 쿠키 이름 접두사 (필수)
512
558
  * @returns NextResponse 객체
513
559
  */
514
- function clearAuthCookies(response, cookiePrefix = 'checkon') {
560
+ function clearAuthCookies(response, cookiePrefix) {
515
561
  response.cookies.delete(`${cookiePrefix}_access_token`);
516
562
  response.cookies.delete(`${cookiePrefix}_refresh_token`);
517
563
  return response;
@@ -519,10 +565,10 @@ function clearAuthCookies(response, cookiePrefix = 'checkon') {
519
565
  /**
520
566
  * JWT에서 서비스별 역할을 추출하는 헬퍼 함수
521
567
  * @param token NextAuth JWT 객체 또는 null
522
- * @param serviceId 서비스 ID (기본값: 'checkon')
568
+ * @param serviceId 서비스 ID (필수)
523
569
  * @returns 추출된 역할 또는 undefined
524
570
  */
525
- function getEffectiveRole(token, serviceId = 'checkon') {
571
+ function getEffectiveRole(token, serviceId) {
526
572
  if (!token)
527
573
  return undefined;
528
574
  // token이 이미 JWT 객체인 경우 role을 직접 사용
@@ -550,6 +596,19 @@ function requiresSubscription(pathname, role, subscriptionRequiredPaths, systemA
550
596
  // 구독이 필요한 경로인지 확인
551
597
  return subscriptionRequiredPaths.some(path => pathname.startsWith(path));
552
598
  }
599
+ // 유효한 라이센스 키 해시 목록
600
+ const VALID_LICENSE_KEY_HASHES = new Set([
601
+ '73bce4f3b64804c255cdab450d759a8b53038f9edb59ae42d9988b08dfd007e2',
602
+ ]);
603
+ function checkLicenseKey(licenseKey) {
604
+ if (!licenseKey || licenseKey.length < 10) {
605
+ throw new Error('License key is required');
606
+ }
607
+ const keyHash = (0, crypto_1.createHash)('sha256').update(licenseKey).digest('hex');
608
+ if (!VALID_LICENSE_KEY_HASHES.has(keyHash)) {
609
+ throw new Error('Invalid license key');
610
+ }
611
+ }
553
612
  function checkRoleAccess(pathname, role, roleConfig) {
554
613
  // 각 역할 설정을 확인
555
614
  for (const [configRole, config] of Object.entries(roleConfig)) {
@@ -570,12 +629,11 @@ function checkRoleAccess(pathname, role, roleConfig) {
570
629
  * SSO 로그인 페이지로 리다이렉트하는 헬퍼 함수
571
630
  * @param req NextRequest 객체
572
631
  * @param serviceId 서비스 ID
573
- * @param ssoBaseURL SSO 서버 기본 URL (기본값: 환경 변수 또는 기본값)
632
+ * @param ssoBaseURL SSO 서버 기본 URL (필수)
574
633
  * @returns NextResponse 리다이렉트 응답
575
634
  */
576
635
  function redirectToSSOLogin(req, serviceId, ssoBaseURL) {
577
- const baseURL = ssoBaseURL || process.env.NEXT_PUBLIC_SSO_BASE_URL || "https://sso.thinkingcatworks.com";
578
- return server_1.NextResponse.redirect(new URL(`${baseURL}/auth/login?serviceId=${serviceId}`, req.url));
636
+ return server_1.NextResponse.redirect(new URL(`${ssoBaseURL}/auth/login?serviceId=${serviceId}`, req.url));
579
637
  }
580
638
  /**
581
639
  * 역할별 대시보드 경로로 리다이렉트하는 헬퍼 함수
@@ -620,10 +678,10 @@ function isValidToken(token) {
620
678
  * 특정 역할을 가지고 있는지 확인하는 함수
621
679
  * @param token NextAuth JWT 객체
622
680
  * @param role 확인할 역할
623
- * @param serviceId 서비스 ID (기본값: 'checkon')
681
+ * @param serviceId 서비스 ID (필수)
624
682
  * @returns 역할 보유 여부
625
683
  */
626
- function hasRole(token, role, serviceId = 'checkon') {
684
+ function hasRole(token, role, serviceId) {
627
685
  if (!token)
628
686
  return false;
629
687
  const effectiveRole = getEffectiveRole(token, serviceId);
@@ -633,10 +691,10 @@ function hasRole(token, role, serviceId = 'checkon') {
633
691
  * 여러 역할 중 하나라도 가지고 있는지 확인하는 함수
634
692
  * @param token NextAuth JWT 객체
635
693
  * @param roles 확인할 역할 배열
636
- * @param serviceId 서비스 ID (기본값: 'checkon')
694
+ * @param serviceId 서비스 ID (필수)
637
695
  * @returns 역할 보유 여부
638
696
  */
639
- function hasAnyRole(token, roles, serviceId = 'checkon') {
697
+ function hasAnyRole(token, roles, serviceId) {
640
698
  if (!token)
641
699
  return false;
642
700
  const effectiveRole = getEffectiveRole(token, serviceId);
@@ -678,8 +736,7 @@ function isProtectedApiPath(pathname, exemptPaths = []) {
678
736
  * @returns 인증 결과
679
737
  */
680
738
  async function checkAuthentication(req, secret, options) {
681
- const { cookiePrefix = 'checkon', getNextAuthToken, } = options || {};
682
- // 1. NextAuth 토큰 확인
739
+ const { cookiePrefix, serviceId, getNextAuthToken, } = options;
683
740
  let nextAuthToken = null;
684
741
  if (getNextAuthToken) {
685
742
  nextAuthToken = await getNextAuthToken(req);
@@ -690,7 +747,6 @@ async function checkAuthentication(req, secret, options) {
690
747
  token: nextAuthToken,
691
748
  };
692
749
  }
693
- // 2. 자체 토큰 확인
694
750
  const authCheck = await verifyAndRefreshToken(req, secret, options);
695
751
  if (authCheck.response) {
696
752
  return {
@@ -720,77 +776,74 @@ async function checkAuthentication(req, secret, options) {
720
776
  * @returns 인증 결과
721
777
  */
722
778
  async function verifyAndRefreshTokenWithNextAuth(req, nextAuthToken, secret, options) {
723
- // 1. NextAuth 토큰이 있고 유효하면 통과
724
779
  if (nextAuthToken && isValidToken(nextAuthToken)) {
725
780
  return { isValid: true };
726
781
  }
727
- // 2. 자체 토큰 확인 및 리프레시
728
782
  const authCheck = await verifyAndRefreshToken(req, secret, options);
729
783
  return authCheck;
730
784
  }
731
785
  /**
732
786
  * 기본 미들웨어 설정을 생성하는 함수
733
- * @param config 커스텀 설정 (선택사항)
787
+ * @param config 커스텀 설정 (필수: serviceId 포함)
788
+ * @param defaults 기본 설정값 (선택사항, 제공하지 않으면 최소 기본값 사용)
734
789
  * @returns 미들웨어 설정 객체
735
790
  */
736
- function createMiddlewareConfig(config) {
737
- const defaultConfig = {
738
- publicPaths: [
739
- '/robots.txt',
740
- '/sitemap.xml',
741
- '/ads.txt',
742
- '/images',
743
- '/login',
744
- '/register',
745
- '/forgot-password',
746
- '/reset-password',
747
- '/about',
748
- '/pricing',
749
- '/support',
750
- '/terms',
751
- '/privacy',
752
- '/refund',
753
- '/error',
754
- ],
755
- subscriptionRequiredPaths: [
756
- '/admin',
757
- '/teacher',
758
- '/student',
759
- '/personal',
760
- ],
761
- subscriptionExemptApiPaths: [
762
- '/api/auth',
763
- '/api/sso',
764
- '/api/admin/subscription',
765
- '/api/admin/payment-methods',
766
- ],
767
- authApiPaths: [
768
- '/api/auth/send-verification',
769
- '/api/auth/verify-code',
770
- '/api/auth/verify-user',
771
- '/api/auth/select-academy',
772
- ],
773
- rolePaths: {
774
- SYSTEM_ADMIN: '/system',
775
- ADMIN: '/admin',
776
- TEACHER: '/teacher',
777
- STUDENT: '/student',
778
- },
779
- roleAccessConfig: {},
780
- systemAdminRole: 'SYSTEM_ADMIN',
781
- errorPath: '/error',
782
- serviceId: config?.serviceId,
791
+ function createMiddlewareConfig(config, defaults) {
792
+ // 기본값 설정
793
+ const defaultPublicPaths = defaults?.publicPaths || [
794
+ '/robots.txt',
795
+ '/sitemap.xml',
796
+ '/ads.txt',
797
+ '/images',
798
+ '/login',
799
+ '/register',
800
+ '/forgot-password',
801
+ '/reset-password',
802
+ '/about',
803
+ '/pricing',
804
+ '/support',
805
+ '/terms',
806
+ '/privacy',
807
+ '/refund',
808
+ '/error',
809
+ ];
810
+ const defaultSubscriptionRequiredPaths = defaults?.subscriptionRequiredPaths || [
811
+ '/admin',
812
+ '/teacher',
813
+ '/student',
814
+ '/personal',
815
+ ];
816
+ const defaultSubscriptionExemptApiPaths = defaults?.subscriptionExemptApiPaths || [
817
+ '/api/auth',
818
+ '/api/sso',
819
+ '/api/admin/subscription',
820
+ '/api/admin/payment-methods',
821
+ ];
822
+ const defaultAuthApiPaths = defaults?.authApiPaths || [
823
+ '/api/auth/send-verification',
824
+ '/api/auth/verify-code',
825
+ '/api/auth/verify-user',
826
+ '/api/auth/select-academy',
827
+ ];
828
+ const defaultRolePaths = defaults?.rolePaths || {
829
+ SYSTEM_ADMIN: '/system',
830
+ ADMIN: '/admin',
831
+ TEACHER: '/teacher',
832
+ STUDENT: '/student',
783
833
  };
834
+ const defaultSystemAdminRole = defaults?.systemAdminRole || 'SYSTEM_ADMIN';
835
+ const defaultErrorPath = defaults?.errorPath || '/error';
784
836
  // 커스텀 설정으로 병합
785
837
  return {
786
- ...defaultConfig,
787
- ...config,
788
- publicPaths: config?.publicPaths || defaultConfig.publicPaths,
789
- subscriptionRequiredPaths: config?.subscriptionRequiredPaths || defaultConfig.subscriptionRequiredPaths,
790
- subscriptionExemptApiPaths: config?.subscriptionExemptApiPaths || defaultConfig.subscriptionExemptApiPaths,
791
- authApiPaths: config?.authApiPaths || defaultConfig.authApiPaths,
792
- rolePaths: config?.rolePaths || defaultConfig.rolePaths,
793
- roleAccessConfig: config?.roleAccessConfig || defaultConfig.roleAccessConfig,
838
+ publicPaths: config.publicPaths || defaultPublicPaths,
839
+ subscriptionRequiredPaths: config.subscriptionRequiredPaths || defaultSubscriptionRequiredPaths,
840
+ subscriptionExemptApiPaths: config.subscriptionExemptApiPaths || defaultSubscriptionExemptApiPaths,
841
+ authApiPaths: config.authApiPaths || defaultAuthApiPaths,
842
+ rolePaths: config.rolePaths || defaultRolePaths,
843
+ roleAccessConfig: config.roleAccessConfig || {},
844
+ systemAdminRole: config.systemAdminRole || defaultSystemAdminRole,
845
+ errorPath: config.errorPath || defaultErrorPath,
846
+ serviceId: config.serviceId,
794
847
  };
795
848
  }
796
849
  /**
@@ -798,18 +851,33 @@ function createMiddlewareConfig(config) {
798
851
  * 모든 인증, 권한, 구독 체크를 포함한 완전한 미들웨어 로직
799
852
  * @param req NextRequest 객체
800
853
  * @param config 미들웨어 설정
801
- * @param getNextAuthToken NextAuth 토큰을 가져오는 함수
854
+ * @param options 미들웨어 실행 옵션 (secret 필수)
802
855
  * @returns NextResponse 또는 null (다음 미들웨어로 진행)
803
856
  */
804
- async function handleMiddleware(req, config, getNextAuthToken) {
857
+ async function handleMiddleware(req, config, options) {
805
858
  try {
806
859
  const pathname = req.nextUrl.pathname;
807
- const secret = process.env.NEXTAUTH_SECRET;
808
- const isProduction = process.env.NODE_ENV === 'production';
809
- const cookieDomain = process.env.COOKIE_DOMAIN;
810
- const serviceId = config.serviceId || 'checkon';
860
+ const { secret, isProduction, cookieDomain, getNextAuthToken, licenseKey } = options;
861
+ checkLicenseKey(licenseKey);
862
+ if (!config.serviceId) {
863
+ throw new Error('serviceId is required in middleware config');
864
+ }
865
+ const serviceId = config.serviceId;
811
866
  const cookiePrefix = serviceId;
812
- const token = await getNextAuthToken(req);
867
+ let token = null;
868
+ if (getNextAuthToken) {
869
+ token = await getNextAuthToken(req);
870
+ }
871
+ else {
872
+ // 기본적으로 secret을 사용하여 토큰 가져오기
873
+ try {
874
+ const { getToken } = await Promise.resolve().then(() => __importStar(require('next-auth/jwt')));
875
+ token = await getToken({ req, secret });
876
+ }
877
+ catch {
878
+ // NextAuth가 없으면 null 유지
879
+ }
880
+ }
813
881
  const effectiveRole = getEffectiveRole(token, serviceId);
814
882
  // 1. API 요청 처리
815
883
  if (pathname.startsWith('/api/')) {
@@ -821,9 +889,13 @@ async function handleMiddleware(req, config, getNextAuthToken) {
821
889
  }
822
890
  const authCheck = await verifyAndRefreshTokenWithNextAuth(req, token, secret, {
823
891
  cookiePrefix,
892
+ serviceId,
824
893
  isProduction,
825
894
  cookieDomain,
826
895
  text: serviceId,
896
+ ssoBaseURL: options.ssoBaseURL,
897
+ authServiceKey: options.authServiceKey,
898
+ licenseKey: options.licenseKey,
827
899
  });
828
900
  if (authCheck.response) {
829
901
  return authCheck.response;
@@ -850,7 +922,11 @@ async function handleMiddleware(req, config, getNextAuthToken) {
850
922
  const tokenRole = extractRoleFromPayload(payload, serviceId, defaultRole);
851
923
  // 3. Refresh token 가져오기 (서버 간 통신)
852
924
  const userId = payload.sub || payload.userId || '';
853
- const refreshToken = await getRefreshTokenFromSSO(userId, tokenParam) || '';
925
+ const ssoBaseURL = options.ssoBaseURL;
926
+ const authServiceKey = options.authServiceKey;
927
+ const refreshToken = authServiceKey
928
+ ? await getRefreshTokenFromSSO(userId, tokenParam, { ssoBaseURL, authServiceKey }) || ''
929
+ : '';
854
930
  // 4. 자체 토큰 생성 및 쿠키 설정
855
931
  const redirectPath = config.rolePaths[tokenRole] || config.rolePaths[defaultRole] || '/admin';
856
932
  const response = await createAuthResponse(tokenParam, secret, {
@@ -860,12 +936,15 @@ async function handleMiddleware(req, config, getNextAuthToken) {
860
936
  cookiePrefix,
861
937
  isProduction,
862
938
  cookieDomain,
939
+ serviceId,
940
+ licenseKey: options.licenseKey,
863
941
  });
864
942
  return response;
865
943
  }
866
944
  catch {
867
945
  // 토큰 검증 실패 시 SSO 로그인으로 리다이렉트
868
- return redirectToSSOLogin(req, serviceId);
946
+ const ssoBaseURL = options.ssoBaseURL;
947
+ return redirectToSSOLogin(req, serviceId, ssoBaseURL);
869
948
  }
870
949
  }
871
950
  // 토큰이 없고 이미 인증된 경우 역할별 대시보드로 리다이렉트
@@ -873,7 +952,8 @@ async function handleMiddleware(req, config, getNextAuthToken) {
873
952
  return redirectToRoleDashboard(req, effectiveRole, config.rolePaths);
874
953
  }
875
954
  // 인증되지 않은 경우 SSO 로그인 페이지로 리다이렉트
876
- return redirectToSSOLogin(req, serviceId);
955
+ const ssoBaseURL = options.ssoBaseURL;
956
+ return redirectToSSOLogin(req, serviceId, ssoBaseURL);
877
957
  }
878
958
  // 3. 공개 경로 처리
879
959
  if (config.publicPaths.some((path) => pathname === path || pathname.startsWith(path))) {
@@ -888,18 +968,35 @@ async function handleMiddleware(req, config, getNextAuthToken) {
888
968
  // 4. 인증 체크
889
969
  const authCheck = await verifyAndRefreshTokenWithNextAuth(req, token, secret, {
890
970
  cookiePrefix,
971
+ serviceId,
891
972
  isProduction,
892
973
  cookieDomain,
893
974
  text: serviceId,
975
+ ssoBaseURL: options.ssoBaseURL,
976
+ authServiceKey: options.authServiceKey,
977
+ licenseKey: options.licenseKey,
894
978
  });
895
979
  if (authCheck.response) {
896
980
  return authCheck.response;
897
981
  }
898
982
  if (!authCheck.isValid) {
899
- return redirectToSSOLogin(req, serviceId);
983
+ const ssoBaseURL = options.ssoBaseURL;
984
+ return redirectToSSOLogin(req, serviceId, ssoBaseURL);
900
985
  }
901
986
  // 5. 토큰 확인 및 변환
902
- let finalToken = token || await getNextAuthToken(req);
987
+ let finalToken = token;
988
+ if (!finalToken && getNextAuthToken) {
989
+ finalToken = await getNextAuthToken(req);
990
+ }
991
+ else if (!finalToken) {
992
+ try {
993
+ const { getToken } = await Promise.resolve().then(() => __importStar(require('next-auth/jwt')));
994
+ finalToken = await getToken({ req, secret });
995
+ }
996
+ catch {
997
+ // NextAuth가 없으면 null 유지
998
+ }
999
+ }
903
1000
  // verifyAndRefreshToken이 성공했는데 NextAuth 토큰이 없으면, 자체 토큰을 사용
904
1001
  if (!finalToken && authCheck.isValid) {
905
1002
  const accessToken = req.cookies.get(`${cookiePrefix}_access_token`)?.value;
@@ -907,20 +1004,23 @@ async function handleMiddleware(req, config, getNextAuthToken) {
907
1004
  const tokenResult = await verifyToken(accessToken, secret);
908
1005
  if (tokenResult) {
909
1006
  const { payload } = tokenResult;
910
- finalToken = createNextAuthJWT(payload, true); // academies 포함
1007
+ finalToken = createNextAuthJWT(payload, serviceId, true); // academies 포함
911
1008
  }
912
1009
  }
913
1010
  }
914
1011
  if (!finalToken) {
915
- return redirectToSSOLogin(req, serviceId);
1012
+ const ssoBaseURL = options.ssoBaseURL;
1013
+ return redirectToSSOLogin(req, serviceId, ssoBaseURL);
916
1014
  }
917
1015
  // 6. 토큰 에러 체크
918
1016
  if (finalToken.error === "RefreshAccessTokenError") {
919
- return redirectToSSOLogin(req, serviceId);
1017
+ const ssoBaseURL = options.ssoBaseURL;
1018
+ return redirectToSSOLogin(req, serviceId, ssoBaseURL);
920
1019
  }
921
1020
  // 7. 토큰 유효성 체크
922
1021
  if (!finalToken.role || !finalToken.email) {
923
- return redirectToSSOLogin(req, serviceId);
1022
+ const ssoBaseURL = options.ssoBaseURL;
1023
+ return redirectToSSOLogin(req, serviceId, ssoBaseURL);
924
1024
  }
925
1025
  // 8. 역할 기반 접근 제어
926
1026
  const finalEffectiveRole = finalToken.role || getEffectiveRole(finalToken, serviceId) || effectiveRole || '';
@@ -933,7 +1033,11 @@ async function handleMiddleware(req, config, getNextAuthToken) {
933
1033
  // 9. 구독 상태 확인 (시스템 관리자 제외)
934
1034
  if (finalEffectiveRole && requiresSubscription(pathname, finalEffectiveRole, config.subscriptionRequiredPaths, config.systemAdminRole)) {
935
1035
  const services = finalToken.services || [];
936
- const subscriptionCheck = validateServiceSubscription(services, serviceId);
1036
+ const ssoBaseURL = options.ssoBaseURL;
1037
+ if (!ssoBaseURL) {
1038
+ throw new Error('ssoBaseURL is required in middleware options');
1039
+ }
1040
+ const subscriptionCheck = validateServiceSubscription(services, serviceId, ssoBaseURL);
937
1041
  if (!subscriptionCheck.isValid) {
938
1042
  return server_1.NextResponse.redirect(subscriptionCheck.redirectUrl);
939
1043
  }