@thinkingcat/auth-utils 1.0.13 → 1.0.16
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/README.md +166 -15
- package/dist/index.d.ts +162 -9
- package/dist/index.js +226 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -60,7 +60,7 @@ npm install @thinkingcat/auth-utils
|
|
|
60
60
|
```json
|
|
61
61
|
{
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@thinkingcat/auth-utils": "^1.0.
|
|
63
|
+
"@thinkingcat/auth-utils": "^1.0.16"
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
```
|
|
@@ -140,6 +140,15 @@ import {
|
|
|
140
140
|
handleMiddleware,
|
|
141
141
|
verifyAndRefreshTokenWithNextAuth,
|
|
142
142
|
|
|
143
|
+
// NextAuth 설정 및 콜백
|
|
144
|
+
createNextAuthBaseConfig,
|
|
145
|
+
createNextAuthCookies,
|
|
146
|
+
handleJWTCallback,
|
|
147
|
+
createInitialJWTToken,
|
|
148
|
+
createEmptySession,
|
|
149
|
+
mapTokenToSession,
|
|
150
|
+
getJWTFromCustomTokenCookie,
|
|
151
|
+
|
|
143
152
|
// 라이센스
|
|
144
153
|
checkLicenseKey,
|
|
145
154
|
|
|
@@ -249,7 +258,7 @@ const role = extractRoleFromPayload(payload, "myservice", "ADMIN");
|
|
|
249
258
|
// 'ADMIN', 'TEACHER', 'STUDENT' 등
|
|
250
259
|
```
|
|
251
260
|
|
|
252
|
-
#### `createNextAuthJWT(payload: JWTPayload, serviceId: string
|
|
261
|
+
#### `createNextAuthJWT(payload: JWTPayload, serviceId: string)`
|
|
253
262
|
|
|
254
263
|
NextAuth와 호환되는 JWT 객체를 생성합니다.
|
|
255
264
|
|
|
@@ -257,7 +266,6 @@ NextAuth와 호환되는 JWT 객체를 생성합니다.
|
|
|
257
266
|
|
|
258
267
|
- `payload`: 원본 JWT payload (JWTPayload 타입)
|
|
259
268
|
- `serviceId`: 서비스 ID (필수)
|
|
260
|
-
- `includeAcademies`: academies 정보 포함 여부 (기본값: false)
|
|
261
269
|
|
|
262
270
|
**반환값:**
|
|
263
271
|
|
|
@@ -266,8 +274,7 @@ NextAuth와 호환되는 JWT 객체를 생성합니다.
|
|
|
266
274
|
**사용 예시:**
|
|
267
275
|
|
|
268
276
|
```typescript
|
|
269
|
-
const jwt = createNextAuthJWT(payload, "myservice"
|
|
270
|
-
// academies 정보가 포함된 JWT 객체
|
|
277
|
+
const jwt = createNextAuthJWT(payload, "myservice");
|
|
271
278
|
```
|
|
272
279
|
|
|
273
280
|
#### `encodeNextAuthToken(jwt: JWT, secret: string, maxAge?: number)`
|
|
@@ -522,6 +529,159 @@ SSO 서버에서 토큰을 검증합니다.
|
|
|
522
529
|
|
|
523
530
|
- `{ isValid: boolean; redirectUrl?: string }`
|
|
524
531
|
|
|
532
|
+
### NextAuth 설정 및 콜백
|
|
533
|
+
|
|
534
|
+
#### `createNextAuthBaseConfig(options)`
|
|
535
|
+
|
|
536
|
+
NextAuth 기본 설정을 생성합니다.
|
|
537
|
+
|
|
538
|
+
**파라미터:**
|
|
539
|
+
|
|
540
|
+
- `options.secret`: NextAuth secret (필수)
|
|
541
|
+
- `options.isProduction`: 프로덕션 환경 여부 (기본값: false)
|
|
542
|
+
- `options.cookieDomain`: 쿠키 도메인 (선택)
|
|
543
|
+
- `options.signInPath`: 로그인 페이지 경로 (기본값: '/login')
|
|
544
|
+
- `options.errorPath`: 에러 페이지 경로 (기본값: '/login')
|
|
545
|
+
- `options.nextAuthUrl`: NextAuth URL (선택)
|
|
546
|
+
- `options.sessionMaxAge`: 세션 최대 유지 시간 (초, 기본값: 30일)
|
|
547
|
+
- `options.jwtMaxAge`: JWT 최대 유지 시간 (초, 기본값: 30일)
|
|
548
|
+
|
|
549
|
+
**반환값:**
|
|
550
|
+
|
|
551
|
+
- NextAuth 기본 설정 객체
|
|
552
|
+
|
|
553
|
+
**사용 예시:**
|
|
554
|
+
|
|
555
|
+
```typescript
|
|
556
|
+
import { createNextAuthBaseConfig } from "@thinkingcat/auth-utils";
|
|
557
|
+
|
|
558
|
+
const baseConfig = createNextAuthBaseConfig({
|
|
559
|
+
secret: process.env.NEXTAUTH_SECRET!,
|
|
560
|
+
isProduction: process.env.NODE_ENV === 'production',
|
|
561
|
+
cookieDomain: process.env.COOKIE_DOMAIN,
|
|
562
|
+
signInPath: "/login",
|
|
563
|
+
errorPath: "/login",
|
|
564
|
+
nextAuthUrl: process.env.NEXTAUTH_URL,
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
export const authOptions: NextAuthOptions = {
|
|
568
|
+
...baseConfig,
|
|
569
|
+
// ... 기타 설정
|
|
570
|
+
};
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
#### `createNextAuthCookies(options)`
|
|
574
|
+
|
|
575
|
+
NextAuth 쿠키 설정을 생성합니다.
|
|
576
|
+
|
|
577
|
+
**파라미터:**
|
|
578
|
+
|
|
579
|
+
- `options.isProduction`: 프로덕션 환경 여부 (기본값: false)
|
|
580
|
+
- `options.cookieDomain`: 쿠키 도메인 (선택)
|
|
581
|
+
|
|
582
|
+
**반환값:**
|
|
583
|
+
|
|
584
|
+
- NextAuth 쿠키 설정 객체
|
|
585
|
+
|
|
586
|
+
#### `handleJWTCallback(token, user?, account?, options?)`
|
|
587
|
+
|
|
588
|
+
JWT 콜백을 위한 통합 헬퍼 함수입니다. 초기 로그인, 토큰 갱신, 커스텀 토큰 읽기를 모두 처리합니다.
|
|
589
|
+
|
|
590
|
+
**파라미터:**
|
|
591
|
+
|
|
592
|
+
- `token`: 기존 JWT 토큰
|
|
593
|
+
- `user`: 사용자 정보 (초기 로그인 시)
|
|
594
|
+
- `account`: 계정 정보 (초기 로그인 시)
|
|
595
|
+
- `options.secret`: NextAuth secret (커스텀 토큰 읽기용)
|
|
596
|
+
- `options.licenseKey`: 라이센스 키 (커스텀 토큰 읽기용)
|
|
597
|
+
- `options.serviceId`: 서비스 ID (커스텀 토큰 읽기용)
|
|
598
|
+
- `options.cookieName`: 커스텀 토큰 쿠키 이름 (기본값: '{serviceId}_access_token')
|
|
599
|
+
- `options.debug`: 디버깅 로그 출력 여부 (기본값: false)
|
|
600
|
+
|
|
601
|
+
**반환값:**
|
|
602
|
+
|
|
603
|
+
- 업데이트된 JWT 토큰
|
|
604
|
+
|
|
605
|
+
**사용 예시:**
|
|
606
|
+
|
|
607
|
+
```typescript
|
|
608
|
+
import { handleJWTCallback } from "@thinkingcat/auth-utils";
|
|
609
|
+
|
|
610
|
+
async jwt({ token, user, account }) {
|
|
611
|
+
return handleJWTCallback(
|
|
612
|
+
token,
|
|
613
|
+
user ? {
|
|
614
|
+
id: user.id,
|
|
615
|
+
email: user.email,
|
|
616
|
+
role: user.role,
|
|
617
|
+
// ... 기타 사용자 정보
|
|
618
|
+
} : null,
|
|
619
|
+
account,
|
|
620
|
+
{
|
|
621
|
+
secret: process.env.NEXTAUTH_SECRET!,
|
|
622
|
+
licenseKey: process.env.LICENSE_KEY!,
|
|
623
|
+
serviceId: 'myservice',
|
|
624
|
+
cookieName: 'myservice_access_token',
|
|
625
|
+
debug: true,
|
|
626
|
+
}
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
#### `createInitialJWTToken(token, user, account?)`
|
|
632
|
+
|
|
633
|
+
JWT 콜백에서 초기 로그인 시 토큰 생성 헬퍼입니다.
|
|
634
|
+
|
|
635
|
+
**파라미터:**
|
|
636
|
+
|
|
637
|
+
- `token`: 기존 토큰
|
|
638
|
+
- `user`: 사용자 정보
|
|
639
|
+
- `account`: 계정 정보 (선택)
|
|
640
|
+
|
|
641
|
+
**반환값:**
|
|
642
|
+
|
|
643
|
+
- 업데이트된 JWT 토큰
|
|
644
|
+
|
|
645
|
+
#### `createEmptySession(session)`
|
|
646
|
+
|
|
647
|
+
Session 콜백에서 빈 세션 반환 헬퍼입니다.
|
|
648
|
+
|
|
649
|
+
**파라미터:**
|
|
650
|
+
|
|
651
|
+
- `session`: 기존 세션
|
|
652
|
+
|
|
653
|
+
**반환값:**
|
|
654
|
+
|
|
655
|
+
- 빈 세션 객체
|
|
656
|
+
|
|
657
|
+
#### `mapTokenToSession(session, token)`
|
|
658
|
+
|
|
659
|
+
Session 콜백에서 토큰 정보를 세션에 매핑하는 헬퍼입니다.
|
|
660
|
+
|
|
661
|
+
**파라미터:**
|
|
662
|
+
|
|
663
|
+
- `session`: 기존 세션
|
|
664
|
+
- `token`: JWT 토큰
|
|
665
|
+
|
|
666
|
+
**반환값:**
|
|
667
|
+
|
|
668
|
+
- 업데이트된 세션
|
|
669
|
+
|
|
670
|
+
#### `getJWTFromCustomTokenCookie(cookieName, secret, serviceId, licenseKey)`
|
|
671
|
+
|
|
672
|
+
쿠키에서 커스텀 토큰을 읽어서 NextAuth JWT로 변환하는 헬퍼 함수입니다.
|
|
673
|
+
|
|
674
|
+
**파라미터:**
|
|
675
|
+
|
|
676
|
+
- `cookieName`: 쿠키 이름 (예: 'checkon_access_token')
|
|
677
|
+
- `secret`: JWT 서명에 사용할 secret key
|
|
678
|
+
- `serviceId`: 서비스 ID (필수)
|
|
679
|
+
- `licenseKey`: 라이센스 키 (필수)
|
|
680
|
+
|
|
681
|
+
**반환값:**
|
|
682
|
+
|
|
683
|
+
- NextAuth JWT 객체 또는 null
|
|
684
|
+
|
|
525
685
|
### 미들웨어
|
|
526
686
|
|
|
527
687
|
#### `createMiddlewareConfig(config: Partial<MiddlewareConfig> & { serviceId: string }, defaults?)`
|
|
@@ -1087,13 +1247,6 @@ interface JWTPayload {
|
|
|
1087
1247
|
// 서비스 정보
|
|
1088
1248
|
services?: ServiceInfo[];
|
|
1089
1249
|
|
|
1090
|
-
// 학원 정보
|
|
1091
|
-
academies?: Array<{
|
|
1092
|
-
id: string;
|
|
1093
|
-
name: string;
|
|
1094
|
-
role: string;
|
|
1095
|
-
}>;
|
|
1096
|
-
|
|
1097
1250
|
// 인증 상태
|
|
1098
1251
|
phoneVerified?: boolean;
|
|
1099
1252
|
emailVerified?: boolean;
|
|
@@ -1101,8 +1254,6 @@ interface JWTPayload {
|
|
|
1101
1254
|
|
|
1102
1255
|
// 선택적 필드
|
|
1103
1256
|
phone?: string;
|
|
1104
|
-
academyId?: string;
|
|
1105
|
-
academyName?: string;
|
|
1106
1257
|
isPasswordReset?: boolean;
|
|
1107
1258
|
decryptedEmail?: string;
|
|
1108
1259
|
decryptedPhone?: string;
|
|
@@ -1308,7 +1459,7 @@ const response = await handleMiddleware(req, middlewareConfig, {
|
|
|
1308
1459
|
## 📦 패키지 정보
|
|
1309
1460
|
|
|
1310
1461
|
- **패키지명**: `@thinkingcat/auth-utils`
|
|
1311
|
-
- **버전**: `1.0.
|
|
1462
|
+
- **버전**: `1.0.16`
|
|
1312
1463
|
- **라이선스**: MIT
|
|
1313
1464
|
- **저장소**: npm registry
|
|
1314
1465
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { JWT } from "next-auth/jwt";
|
|
2
|
+
import type { Session } from "next-auth";
|
|
2
3
|
import { NextResponse, NextRequest } from 'next/server';
|
|
3
4
|
export interface ResponseLike {
|
|
4
5
|
cookies: {
|
|
@@ -68,17 +69,10 @@ export interface JWTPayload {
|
|
|
68
69
|
iat?: number;
|
|
69
70
|
exp?: number;
|
|
70
71
|
services?: ServiceInfo[];
|
|
71
|
-
academies?: Array<{
|
|
72
|
-
id: string;
|
|
73
|
-
name: string;
|
|
74
|
-
role: string;
|
|
75
|
-
}>;
|
|
76
72
|
phoneVerified?: boolean;
|
|
77
73
|
emailVerified?: boolean;
|
|
78
74
|
smsVerified?: boolean;
|
|
79
75
|
phone?: string;
|
|
80
|
-
academyId?: string;
|
|
81
|
-
academyName?: string;
|
|
82
76
|
isPasswordReset?: boolean;
|
|
83
77
|
decryptedEmail?: string;
|
|
84
78
|
decryptedPhone?: string;
|
|
@@ -113,10 +107,9 @@ export declare function extractRoleFromPayload(payload: JWTPayload, serviceId: s
|
|
|
113
107
|
* payload에서 NextAuth JWT 객체 생성
|
|
114
108
|
* @param payload JWT payload
|
|
115
109
|
* @param serviceId 서비스 ID (필수)
|
|
116
|
-
* @param includeAcademies academies 정보 포함 여부
|
|
117
110
|
* @returns NextAuth JWT 객체
|
|
118
111
|
*/
|
|
119
|
-
export declare function createNextAuthJWT(payload: JWTPayload, serviceId: string
|
|
112
|
+
export declare function createNextAuthJWT(payload: JWTPayload, serviceId: string): JWT;
|
|
120
113
|
/**
|
|
121
114
|
* NextAuth JWT를 인코딩된 세션 토큰으로 변환
|
|
122
115
|
* @param jwt NextAuth JWT 객체
|
|
@@ -325,6 +318,166 @@ export interface MiddlewareOptions {
|
|
|
325
318
|
/** 라이센스 키 (필수) */
|
|
326
319
|
licenseKey: string;
|
|
327
320
|
}
|
|
321
|
+
/**
|
|
322
|
+
* NextAuth 쿠키 설정 생성
|
|
323
|
+
* @param options 옵션
|
|
324
|
+
* @param options.isProduction 프로덕션 환경 여부
|
|
325
|
+
* @param options.cookieDomain 쿠키 도메인 (선택)
|
|
326
|
+
* @returns NextAuth 쿠키 설정 객체
|
|
327
|
+
*/
|
|
328
|
+
export declare function createNextAuthCookies(options: {
|
|
329
|
+
isProduction?: boolean;
|
|
330
|
+
cookieDomain?: string;
|
|
331
|
+
}): {
|
|
332
|
+
sessionToken: {
|
|
333
|
+
name: string;
|
|
334
|
+
options: {
|
|
335
|
+
httpOnly: boolean;
|
|
336
|
+
sameSite: 'lax' | 'none';
|
|
337
|
+
path: string;
|
|
338
|
+
secure: boolean;
|
|
339
|
+
domain?: string;
|
|
340
|
+
};
|
|
341
|
+
};
|
|
342
|
+
callbackUrl: {
|
|
343
|
+
name: string;
|
|
344
|
+
options: {
|
|
345
|
+
sameSite: 'lax' | 'none';
|
|
346
|
+
path: string;
|
|
347
|
+
secure: boolean;
|
|
348
|
+
domain?: string;
|
|
349
|
+
};
|
|
350
|
+
};
|
|
351
|
+
csrfToken: {
|
|
352
|
+
name: string;
|
|
353
|
+
options: {
|
|
354
|
+
httpOnly: boolean;
|
|
355
|
+
sameSite: 'lax' | 'none';
|
|
356
|
+
path: string;
|
|
357
|
+
secure: boolean;
|
|
358
|
+
domain?: string;
|
|
359
|
+
};
|
|
360
|
+
};
|
|
361
|
+
};
|
|
362
|
+
/**
|
|
363
|
+
* NextAuth 기본 설정 생성
|
|
364
|
+
* @param options 옵션
|
|
365
|
+
* @param options.secret NextAuth secret
|
|
366
|
+
* @param options.isProduction 프로덕션 환경 여부
|
|
367
|
+
* @param options.cookieDomain 쿠키 도메인 (선택)
|
|
368
|
+
* @param options.signInPath 로그인 페이지 경로 (기본값: '/login')
|
|
369
|
+
* @param options.errorPath 에러 페이지 경로 (기본값: '/login')
|
|
370
|
+
* @param options.nextAuthUrl NextAuth URL (선택)
|
|
371
|
+
* @param options.sessionMaxAge 세션 최대 유지 시간 (초, 기본값: 30일)
|
|
372
|
+
* @param options.jwtMaxAge JWT 최대 유지 시간 (초, 기본값: 30일)
|
|
373
|
+
* @returns NextAuth 기본 설정 객체
|
|
374
|
+
*/
|
|
375
|
+
export declare function createNextAuthBaseConfig(options: {
|
|
376
|
+
secret: string;
|
|
377
|
+
isProduction?: boolean;
|
|
378
|
+
cookieDomain?: string;
|
|
379
|
+
signInPath?: string;
|
|
380
|
+
errorPath?: string;
|
|
381
|
+
nextAuthUrl?: string;
|
|
382
|
+
sessionMaxAge?: number;
|
|
383
|
+
jwtMaxAge?: number;
|
|
384
|
+
}): {
|
|
385
|
+
session: {
|
|
386
|
+
strategy: 'jwt';
|
|
387
|
+
maxAge: number;
|
|
388
|
+
};
|
|
389
|
+
jwt: {
|
|
390
|
+
maxAge: number;
|
|
391
|
+
};
|
|
392
|
+
providers: never[];
|
|
393
|
+
url?: string;
|
|
394
|
+
pages: {
|
|
395
|
+
signIn: string;
|
|
396
|
+
error: string;
|
|
397
|
+
};
|
|
398
|
+
cookies: ReturnType<typeof createNextAuthCookies>;
|
|
399
|
+
secret: string;
|
|
400
|
+
};
|
|
401
|
+
/**
|
|
402
|
+
* JWT 콜백에서 초기 로그인 시 토큰 생성 헬퍼
|
|
403
|
+
* @param token 기존 토큰
|
|
404
|
+
* @param user 사용자 정보
|
|
405
|
+
* @param account 계정 정보
|
|
406
|
+
* @returns 업데이트된 JWT 토큰
|
|
407
|
+
*/
|
|
408
|
+
export declare function createInitialJWTToken(token: JWT, user: {
|
|
409
|
+
id: string;
|
|
410
|
+
email?: string | null;
|
|
411
|
+
emailHash?: string | null;
|
|
412
|
+
maskedEmail?: string | null;
|
|
413
|
+
phoneHash?: string | null;
|
|
414
|
+
maskedPhone?: string | null;
|
|
415
|
+
role?: string;
|
|
416
|
+
phone?: string | null;
|
|
417
|
+
decryptedEmail?: string | null;
|
|
418
|
+
decryptedPhone?: string | null;
|
|
419
|
+
refreshToken?: string | null;
|
|
420
|
+
}, account?: {
|
|
421
|
+
serviceId?: string;
|
|
422
|
+
} | null): JWT;
|
|
423
|
+
/**
|
|
424
|
+
* Session 콜백에서 빈 세션 반환 헬퍼
|
|
425
|
+
* @param session 기존 세션
|
|
426
|
+
* @returns 빈 세션 객체
|
|
427
|
+
*/
|
|
428
|
+
export declare function createEmptySession(session: Session): Session;
|
|
429
|
+
/**
|
|
430
|
+
* Session 콜백에서 토큰 정보를 세션에 매핑하는 헬퍼
|
|
431
|
+
* @param session 기존 세션
|
|
432
|
+
* @param token JWT 토큰
|
|
433
|
+
* @returns 업데이트된 세션
|
|
434
|
+
*/
|
|
435
|
+
export declare function mapTokenToSession(session: Session, token: JWT): Session;
|
|
436
|
+
/**
|
|
437
|
+
* JWT 콜백을 위한 통합 헬퍼 함수
|
|
438
|
+
* 초기 로그인, 토큰 갱신, 커스텀 토큰 읽기를 모두 처리
|
|
439
|
+
* @param token 기존 JWT 토큰
|
|
440
|
+
* @param user 사용자 정보 (초기 로그인 시)
|
|
441
|
+
* @param account 계정 정보 (초기 로그인 시)
|
|
442
|
+
* @param options 옵션
|
|
443
|
+
* @param options.secret NextAuth secret (커스텀 토큰 읽기용)
|
|
444
|
+
* @param options.licenseKey 라이센스 키 (커스텀 토큰 읽기용)
|
|
445
|
+
* @param options.serviceId 서비스 ID (커스텀 토큰 읽기용)
|
|
446
|
+
* @param options.cookieName 커스텀 토큰 쿠키 이름 (기본값: '{serviceId}_access_token')
|
|
447
|
+
* @param options.debug 디버깅 로그 출력 여부 (기본값: false)
|
|
448
|
+
* @returns 업데이트된 JWT 토큰
|
|
449
|
+
*/
|
|
450
|
+
export declare function handleJWTCallback(token: JWT, user?: {
|
|
451
|
+
id: string;
|
|
452
|
+
email?: string | null;
|
|
453
|
+
emailHash?: string | null;
|
|
454
|
+
maskedEmail?: string | null;
|
|
455
|
+
phoneHash?: string | null;
|
|
456
|
+
maskedPhone?: string | null;
|
|
457
|
+
role?: string;
|
|
458
|
+
phone?: string | null;
|
|
459
|
+
decryptedEmail?: string | null;
|
|
460
|
+
decryptedPhone?: string | null;
|
|
461
|
+
refreshToken?: string | null;
|
|
462
|
+
} | null, account?: {
|
|
463
|
+
serviceId?: string;
|
|
464
|
+
} | null, options?: {
|
|
465
|
+
secret?: string;
|
|
466
|
+
licenseKey?: string;
|
|
467
|
+
serviceId?: string;
|
|
468
|
+
cookieName?: string;
|
|
469
|
+
debug?: boolean;
|
|
470
|
+
}): Promise<JWT>;
|
|
471
|
+
/**
|
|
472
|
+
* 쿠키에서 커스텀 토큰을 읽어서 NextAuth JWT로 변환하는 헬퍼 함수
|
|
473
|
+
* NextAuth JWT 콜백에서 사용
|
|
474
|
+
* @param cookieName 쿠키 이름 (예: 'checkon_access_token')
|
|
475
|
+
* @param secret JWT 서명에 사용할 secret key
|
|
476
|
+
* @param serviceId 서비스 ID (필수)
|
|
477
|
+
* @param licenseKey 라이센스 키 (필수)
|
|
478
|
+
* @returns NextAuth JWT 객체 또는 null
|
|
479
|
+
*/
|
|
480
|
+
export declare function getJWTFromCustomTokenCookie(cookieName: string, secret: string, serviceId: string, licenseKey: string): Promise<JWT | null>;
|
|
328
481
|
export declare function checkLicenseKey(licenseKey: string): Promise<void>;
|
|
329
482
|
export declare function checkRoleAccess(pathname: string, role: string, roleConfig: RoleAccessConfig): {
|
|
330
483
|
allowed: boolean;
|
package/dist/index.js
CHANGED
|
@@ -49,6 +49,13 @@ exports.redirectToError = redirectToError;
|
|
|
49
49
|
exports.clearAuthCookies = clearAuthCookies;
|
|
50
50
|
exports.getEffectiveRole = getEffectiveRole;
|
|
51
51
|
exports.requiresSubscription = requiresSubscription;
|
|
52
|
+
exports.createNextAuthCookies = createNextAuthCookies;
|
|
53
|
+
exports.createNextAuthBaseConfig = createNextAuthBaseConfig;
|
|
54
|
+
exports.createInitialJWTToken = createInitialJWTToken;
|
|
55
|
+
exports.createEmptySession = createEmptySession;
|
|
56
|
+
exports.mapTokenToSession = mapTokenToSession;
|
|
57
|
+
exports.handleJWTCallback = handleJWTCallback;
|
|
58
|
+
exports.getJWTFromCustomTokenCookie = getJWTFromCustomTokenCookie;
|
|
52
59
|
exports.checkLicenseKey = checkLicenseKey;
|
|
53
60
|
exports.checkRoleAccess = checkRoleAccess;
|
|
54
61
|
exports.redirectToSSOLogin = redirectToSSOLogin;
|
|
@@ -110,10 +117,9 @@ function extractRoleFromPayload(payload, serviceId, defaultRole = 'ADMIN') {
|
|
|
110
117
|
* payload에서 NextAuth JWT 객체 생성
|
|
111
118
|
* @param payload JWT payload
|
|
112
119
|
* @param serviceId 서비스 ID (필수)
|
|
113
|
-
* @param includeAcademies academies 정보 포함 여부
|
|
114
120
|
* @returns NextAuth JWT 객체
|
|
115
121
|
*/
|
|
116
|
-
function createNextAuthJWT(payload, serviceId
|
|
122
|
+
function createNextAuthJWT(payload, serviceId) {
|
|
117
123
|
const services = payload.services || [];
|
|
118
124
|
const service = services.find((s) => s.serviceId === serviceId);
|
|
119
125
|
const effectiveRole = service?.role || payload.role || 'ADMIN';
|
|
@@ -123,9 +129,6 @@ function createNextAuthJWT(payload, serviceId, includeAcademies = false) {
|
|
|
123
129
|
name: payload.name,
|
|
124
130
|
role: effectiveRole, // Role enum 타입 (string으로 캐스팅)
|
|
125
131
|
services: payload.services,
|
|
126
|
-
academies: includeAcademies
|
|
127
|
-
? payload.academies || []
|
|
128
|
-
: [],
|
|
129
132
|
phoneVerified: payload.phoneVerified ?? false,
|
|
130
133
|
emailVerified: payload.emailVerified ?? false,
|
|
131
134
|
smsVerified: payload.smsVerified ?? false,
|
|
@@ -135,10 +138,6 @@ function createNextAuthJWT(payload, serviceId, includeAcademies = false) {
|
|
|
135
138
|
// 선택적 필드들
|
|
136
139
|
if (payload.phone)
|
|
137
140
|
jwt.phone = payload.phone;
|
|
138
|
-
if (payload.academyId)
|
|
139
|
-
jwt.academyId = payload.academyId;
|
|
140
|
-
if (payload.academyName)
|
|
141
|
-
jwt.academyName = payload.academyName;
|
|
142
141
|
if (payload.isPasswordReset)
|
|
143
142
|
jwt.isPasswordReset = payload.isPasswordReset;
|
|
144
143
|
if (payload.decryptedEmail)
|
|
@@ -607,6 +606,223 @@ function requiresSubscription(pathname, role, subscriptionRequiredPaths, systemA
|
|
|
607
606
|
const VALID_LICENSE_KEY_HASHES = new Set([
|
|
608
607
|
'73bce4f3b64804c255cdab450d759a8b53038f9edb59ae42d9988b08dfd007e2',
|
|
609
608
|
]);
|
|
609
|
+
/**
|
|
610
|
+
* NextAuth 쿠키 설정 생성
|
|
611
|
+
* @param options 옵션
|
|
612
|
+
* @param options.isProduction 프로덕션 환경 여부
|
|
613
|
+
* @param options.cookieDomain 쿠키 도메인 (선택)
|
|
614
|
+
* @returns NextAuth 쿠키 설정 객체
|
|
615
|
+
*/
|
|
616
|
+
function createNextAuthCookies(options) {
|
|
617
|
+
const { isProduction = false, cookieDomain } = options;
|
|
618
|
+
const isSecure = isProduction;
|
|
619
|
+
return {
|
|
620
|
+
sessionToken: {
|
|
621
|
+
name: isSecure ? `__Secure-next-auth.session-token` : `next-auth.session-token`,
|
|
622
|
+
options: {
|
|
623
|
+
httpOnly: true,
|
|
624
|
+
sameSite: isSecure ? 'none' : 'lax',
|
|
625
|
+
path: '/',
|
|
626
|
+
secure: isSecure,
|
|
627
|
+
...(cookieDomain && { domain: cookieDomain }),
|
|
628
|
+
},
|
|
629
|
+
},
|
|
630
|
+
callbackUrl: {
|
|
631
|
+
name: isSecure ? `__Secure-next-auth.callback-url` : `next-auth.callback-url`,
|
|
632
|
+
options: {
|
|
633
|
+
sameSite: isSecure ? 'none' : 'lax',
|
|
634
|
+
path: '/',
|
|
635
|
+
secure: isSecure,
|
|
636
|
+
...(cookieDomain && { domain: cookieDomain }),
|
|
637
|
+
},
|
|
638
|
+
},
|
|
639
|
+
csrfToken: {
|
|
640
|
+
name: isSecure ? `__Secure-next-auth.csrf-token` : `next-auth.csrf-token`,
|
|
641
|
+
options: {
|
|
642
|
+
httpOnly: true,
|
|
643
|
+
sameSite: isSecure ? 'none' : 'lax',
|
|
644
|
+
path: '/',
|
|
645
|
+
secure: isSecure,
|
|
646
|
+
...(cookieDomain && { domain: cookieDomain }),
|
|
647
|
+
},
|
|
648
|
+
},
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* NextAuth 기본 설정 생성
|
|
653
|
+
* @param options 옵션
|
|
654
|
+
* @param options.secret NextAuth secret
|
|
655
|
+
* @param options.isProduction 프로덕션 환경 여부
|
|
656
|
+
* @param options.cookieDomain 쿠키 도메인 (선택)
|
|
657
|
+
* @param options.signInPath 로그인 페이지 경로 (기본값: '/login')
|
|
658
|
+
* @param options.errorPath 에러 페이지 경로 (기본값: '/login')
|
|
659
|
+
* @param options.nextAuthUrl NextAuth URL (선택)
|
|
660
|
+
* @param options.sessionMaxAge 세션 최대 유지 시간 (초, 기본값: 30일)
|
|
661
|
+
* @param options.jwtMaxAge JWT 최대 유지 시간 (초, 기본값: 30일)
|
|
662
|
+
* @returns NextAuth 기본 설정 객체
|
|
663
|
+
*/
|
|
664
|
+
function createNextAuthBaseConfig(options) {
|
|
665
|
+
const { secret, isProduction = false, cookieDomain, signInPath = '/login', errorPath = '/login', nextAuthUrl, sessionMaxAge = 30 * 24 * 60 * 60, // 30일
|
|
666
|
+
jwtMaxAge = 30 * 24 * 60 * 60, // 30일
|
|
667
|
+
} = options;
|
|
668
|
+
return {
|
|
669
|
+
session: {
|
|
670
|
+
strategy: 'jwt',
|
|
671
|
+
maxAge: sessionMaxAge,
|
|
672
|
+
},
|
|
673
|
+
jwt: {
|
|
674
|
+
maxAge: jwtMaxAge,
|
|
675
|
+
},
|
|
676
|
+
providers: [],
|
|
677
|
+
...(nextAuthUrl && { url: nextAuthUrl }),
|
|
678
|
+
pages: {
|
|
679
|
+
signIn: signInPath,
|
|
680
|
+
error: errorPath,
|
|
681
|
+
},
|
|
682
|
+
cookies: createNextAuthCookies({ isProduction, cookieDomain }),
|
|
683
|
+
secret,
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* JWT 콜백에서 초기 로그인 시 토큰 생성 헬퍼
|
|
688
|
+
* @param token 기존 토큰
|
|
689
|
+
* @param user 사용자 정보
|
|
690
|
+
* @param account 계정 정보
|
|
691
|
+
* @returns 업데이트된 JWT 토큰
|
|
692
|
+
*/
|
|
693
|
+
function createInitialJWTToken(token, user, account) {
|
|
694
|
+
return {
|
|
695
|
+
...token,
|
|
696
|
+
id: user.id,
|
|
697
|
+
email: user.email ?? undefined,
|
|
698
|
+
emailHash: user.emailHash ?? undefined,
|
|
699
|
+
maskedEmail: user.maskedEmail ?? undefined,
|
|
700
|
+
phoneHash: user.phoneHash ?? undefined,
|
|
701
|
+
maskedPhone: user.maskedPhone ?? undefined,
|
|
702
|
+
role: user.role,
|
|
703
|
+
phone: user.phone ?? undefined,
|
|
704
|
+
decryptedEmail: user.decryptedEmail ?? undefined,
|
|
705
|
+
decryptedPhone: user.decryptedPhone ?? undefined,
|
|
706
|
+
refreshToken: user.refreshToken ?? undefined,
|
|
707
|
+
accessTokenExpires: Date.now() + (15 * 60 * 1000), // 15분
|
|
708
|
+
serviceId: account?.serviceId ?? undefined,
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Session 콜백에서 빈 세션 반환 헬퍼
|
|
713
|
+
* @param session 기존 세션
|
|
714
|
+
* @returns 빈 세션 객체
|
|
715
|
+
*/
|
|
716
|
+
function createEmptySession(session) {
|
|
717
|
+
return {
|
|
718
|
+
...session,
|
|
719
|
+
user: {
|
|
720
|
+
...session.user,
|
|
721
|
+
id: '',
|
|
722
|
+
email: null,
|
|
723
|
+
role: 'GUEST',
|
|
724
|
+
},
|
|
725
|
+
expires: new Date().toISOString(),
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* Session 콜백에서 토큰 정보를 세션에 매핑하는 헬퍼
|
|
730
|
+
* @param session 기존 세션
|
|
731
|
+
* @param token JWT 토큰
|
|
732
|
+
* @returns 업데이트된 세션
|
|
733
|
+
*/
|
|
734
|
+
function mapTokenToSession(session, token) {
|
|
735
|
+
if (!session.user) {
|
|
736
|
+
return session;
|
|
737
|
+
}
|
|
738
|
+
const user = session.user;
|
|
739
|
+
user.id = token.id;
|
|
740
|
+
user.email = token.email;
|
|
741
|
+
user.name = token.name;
|
|
742
|
+
user.role = token.role;
|
|
743
|
+
user.smsVerified = token.smsVerified;
|
|
744
|
+
user.emailVerified = token.emailVerified;
|
|
745
|
+
user.phone = token.phone;
|
|
746
|
+
user.phoneVerified = token.phoneVerified;
|
|
747
|
+
user.isPasswordReset = token.isPasswordReset || false;
|
|
748
|
+
user.decryptedEmail = token.decryptedEmail;
|
|
749
|
+
user.decryptedPhone = token.decryptedPhone;
|
|
750
|
+
return session;
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* JWT 콜백을 위한 통합 헬퍼 함수
|
|
754
|
+
* 초기 로그인, 토큰 갱신, 커스텀 토큰 읽기를 모두 처리
|
|
755
|
+
* @param token 기존 JWT 토큰
|
|
756
|
+
* @param user 사용자 정보 (초기 로그인 시)
|
|
757
|
+
* @param account 계정 정보 (초기 로그인 시)
|
|
758
|
+
* @param options 옵션
|
|
759
|
+
* @param options.secret NextAuth secret (커스텀 토큰 읽기용)
|
|
760
|
+
* @param options.licenseKey 라이센스 키 (커스텀 토큰 읽기용)
|
|
761
|
+
* @param options.serviceId 서비스 ID (커스텀 토큰 읽기용)
|
|
762
|
+
* @param options.cookieName 커스텀 토큰 쿠키 이름 (기본값: '{serviceId}_access_token')
|
|
763
|
+
* @param options.debug 디버깅 로그 출력 여부 (기본값: false)
|
|
764
|
+
* @returns 업데이트된 JWT 토큰
|
|
765
|
+
*/
|
|
766
|
+
async function handleJWTCallback(token, user, account, options) {
|
|
767
|
+
const { secret, licenseKey, serviceId, cookieName, debug = false, } = options || {};
|
|
768
|
+
// 디버깅 로그
|
|
769
|
+
if (debug) {
|
|
770
|
+
console.log('[JWT Callback] Token received:', {
|
|
771
|
+
hasId: !!token.id,
|
|
772
|
+
hasEmail: !!token.email,
|
|
773
|
+
hasRole: !!token.role,
|
|
774
|
+
tokenKeys: Object.keys(token),
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
// 1. 초기 로그인 시 (providers를 통한 로그인)
|
|
778
|
+
if (account && user) {
|
|
779
|
+
return createInitialJWTToken(token, user, account);
|
|
780
|
+
}
|
|
781
|
+
// 2. 이미 토큰에 정보가 있으면 그대로 사용
|
|
782
|
+
if (token.id) {
|
|
783
|
+
return token;
|
|
784
|
+
}
|
|
785
|
+
// 3. 토큰에 id가 없는 경우 - 커스텀 토큰 쿠키에서 정보 읽기
|
|
786
|
+
if (secret && licenseKey && serviceId) {
|
|
787
|
+
const cookieNameToUse = cookieName || `${serviceId}_access_token`;
|
|
788
|
+
const jwt = await getJWTFromCustomTokenCookie(cookieNameToUse, secret, serviceId, licenseKey);
|
|
789
|
+
if (jwt) {
|
|
790
|
+
return jwt;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
return token;
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* 쿠키에서 커스텀 토큰을 읽어서 NextAuth JWT로 변환하는 헬퍼 함수
|
|
797
|
+
* NextAuth JWT 콜백에서 사용
|
|
798
|
+
* @param cookieName 쿠키 이름 (예: 'checkon_access_token')
|
|
799
|
+
* @param secret JWT 서명에 사용할 secret key
|
|
800
|
+
* @param serviceId 서비스 ID (필수)
|
|
801
|
+
* @param licenseKey 라이센스 키 (필수)
|
|
802
|
+
* @returns NextAuth JWT 객체 또는 null
|
|
803
|
+
*/
|
|
804
|
+
async function getJWTFromCustomTokenCookie(cookieName, secret, serviceId, licenseKey) {
|
|
805
|
+
try {
|
|
806
|
+
const { cookies } = await Promise.resolve().then(() => __importStar(require('next/headers')));
|
|
807
|
+
const cookieStore = await cookies();
|
|
808
|
+
const accessToken = cookieStore.get(cookieName)?.value;
|
|
809
|
+
if (!accessToken) {
|
|
810
|
+
return null;
|
|
811
|
+
}
|
|
812
|
+
await checkLicenseKey(licenseKey);
|
|
813
|
+
const tokenResult = await verifyToken(accessToken, secret);
|
|
814
|
+
if (!tokenResult) {
|
|
815
|
+
return null;
|
|
816
|
+
}
|
|
817
|
+
const { payload } = tokenResult;
|
|
818
|
+
const jwt = createNextAuthJWT(payload, serviceId);
|
|
819
|
+
return jwt;
|
|
820
|
+
}
|
|
821
|
+
catch (error) {
|
|
822
|
+
console.error(`[getJWTFromCustomTokenCookie] Failed to read ${cookieName}:`, error);
|
|
823
|
+
return null;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
610
826
|
async function checkLicenseKey(licenseKey) {
|
|
611
827
|
if (!licenseKey || licenseKey.length < 10) {
|
|
612
828
|
throw new Error('License key is required');
|
|
@@ -1011,7 +1227,7 @@ async function handleMiddleware(req, config, options) {
|
|
|
1011
1227
|
const tokenResult = await verifyToken(accessToken, secret);
|
|
1012
1228
|
if (tokenResult) {
|
|
1013
1229
|
const { payload } = tokenResult;
|
|
1014
|
-
finalToken = createNextAuthJWT(payload, serviceId
|
|
1230
|
+
finalToken = createNextAuthJWT(payload, serviceId);
|
|
1015
1231
|
}
|
|
1016
1232
|
}
|
|
1017
1233
|
}
|