@thinkingcat/auth-utils 1.0.46 → 1.0.48
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 +68 -130
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,77 +1,4 @@
|
|
|
1
|
-
|
|
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
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.verifyToken = verifyToken;
|
|
37
|
-
exports.extractRoleFromPayload = extractRoleFromPayload;
|
|
38
|
-
exports.createNextAuthJWT = createNextAuthJWT;
|
|
39
|
-
exports.encodeNextAuthToken = encodeNextAuthToken;
|
|
40
|
-
exports.setCustomTokens = setCustomTokens;
|
|
41
|
-
exports.setNextAuthToken = setNextAuthToken;
|
|
42
|
-
exports.createRedirectHTML = createRedirectHTML;
|
|
43
|
-
exports.createAuthResponse = createAuthResponse;
|
|
44
|
-
exports.validateServiceSubscription = validateServiceSubscription;
|
|
45
|
-
exports.refreshSSOToken = refreshSSOToken;
|
|
46
|
-
exports.getRefreshTokenFromSSO = getRefreshTokenFromSSO;
|
|
47
|
-
exports.verifyAndRefreshToken = verifyAndRefreshToken;
|
|
48
|
-
exports.redirectToError = redirectToError;
|
|
49
|
-
exports.clearAuthCookies = clearAuthCookies;
|
|
50
|
-
exports.getEffectiveRole = getEffectiveRole;
|
|
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;
|
|
59
|
-
exports.checkLicenseKey = checkLicenseKey;
|
|
60
|
-
exports.checkRoleAccess = checkRoleAccess;
|
|
61
|
-
exports.redirectToSSOLogin = redirectToSSOLogin;
|
|
62
|
-
exports.redirectToRoleDashboard = redirectToRoleDashboard;
|
|
63
|
-
exports.isTokenExpired = isTokenExpired;
|
|
64
|
-
exports.isValidToken = isValidToken;
|
|
65
|
-
exports.hasRole = hasRole;
|
|
66
|
-
exports.hasAnyRole = hasAnyRole;
|
|
67
|
-
exports.isPublicPath = isPublicPath;
|
|
68
|
-
exports.isApiPath = isApiPath;
|
|
69
|
-
exports.isProtectedApiPath = isProtectedApiPath;
|
|
70
|
-
exports.checkAuthentication = checkAuthentication;
|
|
71
|
-
exports.verifyAndRefreshTokenWithNextAuth = verifyAndRefreshTokenWithNextAuth;
|
|
72
|
-
exports.createMiddlewareConfig = createMiddlewareConfig;
|
|
73
|
-
exports.handleMiddleware = handleMiddleware;
|
|
74
|
-
const jose_1 = require("jose");
|
|
1
|
+
import { jwtVerify, EncryptJWT } from 'jose';
|
|
75
2
|
// ============================================================================
|
|
76
3
|
// UTILITY FUNCTIONS
|
|
77
4
|
// ============================================================================
|
|
@@ -102,7 +29,7 @@ async function createHashSHA256(data) {
|
|
|
102
29
|
* Edge Runtime 호환을 위해 next/server를 동적 import
|
|
103
30
|
*/
|
|
104
31
|
async function getNextServer() {
|
|
105
|
-
return await
|
|
32
|
+
return await import('next/server');
|
|
106
33
|
}
|
|
107
34
|
/**
|
|
108
35
|
* NextAuth 세션 토큰 쿠키를 삭제하는 헬퍼 함수
|
|
@@ -129,10 +56,10 @@ function clearAllAuthCookies(response, cookiePrefix, isProduction) {
|
|
|
129
56
|
* @param secret JWT 서명에 사용할 secret key
|
|
130
57
|
* @returns 검증된 payload 또는 null
|
|
131
58
|
*/
|
|
132
|
-
async function verifyToken(accessToken, secret) {
|
|
59
|
+
export async function verifyToken(accessToken, secret) {
|
|
133
60
|
try {
|
|
134
61
|
const secretBytes = new TextEncoder().encode(secret);
|
|
135
|
-
const { payload } = await
|
|
62
|
+
const { payload } = await jwtVerify(accessToken, secretBytes);
|
|
136
63
|
if (payload && typeof payload === 'object' && payload.email) {
|
|
137
64
|
return { payload: payload };
|
|
138
65
|
}
|
|
@@ -149,7 +76,7 @@ async function verifyToken(accessToken, secret) {
|
|
|
149
76
|
* @param defaultRole 기본 역할 (기본값: 'ADMIN')
|
|
150
77
|
* @returns 추출된 역할
|
|
151
78
|
*/
|
|
152
|
-
function extractRoleFromPayload(payload, serviceId, defaultRole = 'ADMIN') {
|
|
79
|
+
export function extractRoleFromPayload(payload, serviceId, defaultRole = 'ADMIN') {
|
|
153
80
|
const services = payload.services || [];
|
|
154
81
|
const service = services.find((s) => s.serviceId === serviceId);
|
|
155
82
|
return service?.role || payload.role || defaultRole;
|
|
@@ -160,7 +87,7 @@ function extractRoleFromPayload(payload, serviceId, defaultRole = 'ADMIN') {
|
|
|
160
87
|
* @param serviceId 서비스 ID (필수)
|
|
161
88
|
* @returns NextAuth JWT 객체
|
|
162
89
|
*/
|
|
163
|
-
function createNextAuthJWT(payload, serviceId) {
|
|
90
|
+
export function createNextAuthJWT(payload, serviceId) {
|
|
164
91
|
const services = payload.services || [];
|
|
165
92
|
const service = services.find((s) => s.serviceId === serviceId);
|
|
166
93
|
const effectiveRole = service?.role || payload.role || 'ADMIN';
|
|
@@ -216,10 +143,10 @@ function createNextAuthJWT(payload, serviceId) {
|
|
|
216
143
|
* @param maxAge 토큰 유효 기간 (초, 기본값: 30일)
|
|
217
144
|
* @returns 인코딩된 세션 토큰
|
|
218
145
|
*/
|
|
219
|
-
async function encodeNextAuthToken(jwt, secret, maxAge = 30 * 24 * 60 * 60) {
|
|
146
|
+
export async function encodeNextAuthToken(jwt, secret, maxAge = 30 * 24 * 60 * 60) {
|
|
220
147
|
// NextAuth의 encode() 함수를 우선적으로 사용 (가장 호환성 좋음)
|
|
221
148
|
try {
|
|
222
|
-
const { encode } = await
|
|
149
|
+
const { encode } = await import('next-auth/jwt');
|
|
223
150
|
debugLog('encodeNextAuthToken', 'Using next-auth/jwt encode');
|
|
224
151
|
const encoded = await encode({
|
|
225
152
|
token: jwt,
|
|
@@ -244,7 +171,7 @@ async function encodeNextAuthToken(jwt, secret, maxAge = 30 * 24 * 60 * 60) {
|
|
|
244
171
|
// EncryptJWT를 사용하여 JWE 토큰 생성
|
|
245
172
|
// NextAuth는 'dir' 키 관리와 'A256GCM' 암호화를 사용
|
|
246
173
|
try {
|
|
247
|
-
const token = await new
|
|
174
|
+
const token = await new EncryptJWT(jwt)
|
|
248
175
|
.setProtectedHeader({
|
|
249
176
|
alg: 'dir', // Direct key agreement
|
|
250
177
|
enc: 'A256GCM' // AES-256-GCM encryption
|
|
@@ -262,7 +189,7 @@ async function encodeNextAuthToken(jwt, secret, maxAge = 30 * 24 * 60 * 60) {
|
|
|
262
189
|
}
|
|
263
190
|
}
|
|
264
191
|
}
|
|
265
|
-
function setCustomTokens(response, accessToken, optionsOrRefreshToken, options) {
|
|
192
|
+
export function setCustomTokens(response, accessToken, optionsOrRefreshToken, options) {
|
|
266
193
|
// 옵션 파라미터 처리: refreshToken과 options를 분리
|
|
267
194
|
let refreshTokenValue;
|
|
268
195
|
let cookiePrefix;
|
|
@@ -335,7 +262,7 @@ function setCustomTokens(response, accessToken, optionsOrRefreshToken, options)
|
|
|
335
262
|
* @param options.isProduction 프로덕션 환경 여부 (기본값: false)
|
|
336
263
|
* @param options.cookieDomain 쿠키 도메인 (선택)
|
|
337
264
|
*/
|
|
338
|
-
function setNextAuthToken(response, sessionToken, options = {}) {
|
|
265
|
+
export function setNextAuthToken(response, sessionToken, options = {}) {
|
|
339
266
|
const { isProduction = false, cookieDomain, } = options;
|
|
340
267
|
// createNextAuthCookies와 동일한 로직 사용
|
|
341
268
|
const cookies = createNextAuthCookies({ isProduction, cookieDomain });
|
|
@@ -353,7 +280,7 @@ function setNextAuthToken(response, sessionToken, options = {}) {
|
|
|
353
280
|
* @param text 표시할 텍스트 (필수)
|
|
354
281
|
* @returns HTML 문자열
|
|
355
282
|
*/
|
|
356
|
-
function createRedirectHTML(redirectPath, text) {
|
|
283
|
+
export function createRedirectHTML(redirectPath, text) {
|
|
357
284
|
return `
|
|
358
285
|
<!DOCTYPE html>
|
|
359
286
|
<html>
|
|
@@ -437,7 +364,7 @@ function createRedirectHTML(redirectPath, text) {
|
|
|
437
364
|
* @param options.licenseKey 라이센스 키 (필수)
|
|
438
365
|
* @returns NextResponse 객체 (리다이렉트 또는 JSON 응답)
|
|
439
366
|
*/
|
|
440
|
-
async function createAuthResponse(accessToken, secret, options) {
|
|
367
|
+
export async function createAuthResponse(accessToken, secret, options) {
|
|
441
368
|
await checkLicenseKey(options.licenseKey);
|
|
442
369
|
const { req, refreshToken, redirectPath, text, cookiePrefix, isProduction = false, cookieDomain, serviceId, } = options;
|
|
443
370
|
// 1. 토큰 검증
|
|
@@ -515,7 +442,7 @@ async function createAuthResponse(accessToken, secret, options) {
|
|
|
515
442
|
* @param ssoBaseURL SSO 서버 기본 URL (필수)
|
|
516
443
|
* @returns 구독 유효성 결과
|
|
517
444
|
*/
|
|
518
|
-
function validateServiceSubscription(services, serviceId, ssoBaseURL) {
|
|
445
|
+
export function validateServiceSubscription(services, serviceId, ssoBaseURL) {
|
|
519
446
|
const filteredServices = services.filter(service => service.serviceId === serviceId);
|
|
520
447
|
if (filteredServices.length === 0) {
|
|
521
448
|
return {
|
|
@@ -544,7 +471,7 @@ function validateServiceSubscription(services, serviceId, ssoBaseURL) {
|
|
|
544
471
|
* @param options.authServiceKey 인증 서비스 키 (기본값: 환경 변수)
|
|
545
472
|
* @returns SSO refresh token 응답
|
|
546
473
|
*/
|
|
547
|
-
async function refreshSSOToken(refreshToken, options) {
|
|
474
|
+
export async function refreshSSOToken(refreshToken, options) {
|
|
548
475
|
const { ssoBaseURL, authServiceKey } = options;
|
|
549
476
|
if (!authServiceKey) {
|
|
550
477
|
throw new Error('AUTH_SERVICE_SECRET_KEY not configured');
|
|
@@ -568,7 +495,7 @@ async function refreshSSOToken(refreshToken, options) {
|
|
|
568
495
|
* @param options.authServiceKey 인증 서비스 키 (기본값: 환경 변수)
|
|
569
496
|
* @returns refresh token 또는 null
|
|
570
497
|
*/
|
|
571
|
-
async function getRefreshTokenFromSSO(userId, accessToken, options) {
|
|
498
|
+
export async function getRefreshTokenFromSSO(userId, accessToken, options) {
|
|
572
499
|
const { ssoBaseURL, authServiceKey } = options;
|
|
573
500
|
if (!authServiceKey) {
|
|
574
501
|
return null;
|
|
@@ -598,7 +525,7 @@ async function getRefreshTokenFromSSO(userId, accessToken, options) {
|
|
|
598
525
|
// ============================================================================
|
|
599
526
|
// TOKEN REFRESH & VERIFICATION FUNCTIONS
|
|
600
527
|
// ============================================================================
|
|
601
|
-
async function verifyAndRefreshToken(req, secret, options) {
|
|
528
|
+
export async function verifyAndRefreshToken(req, secret, options) {
|
|
602
529
|
const { cookiePrefix, serviceId, isProduction, cookieDomain, text, ssoBaseURL, authServiceKey, forceRefresh = false, } = options;
|
|
603
530
|
// 1. access_token 쿠키 확인
|
|
604
531
|
// forceRefresh가 true이면 access token이 있어도 refresh를 시도
|
|
@@ -607,7 +534,7 @@ async function verifyAndRefreshToken(req, secret, options) {
|
|
|
607
534
|
if (accessToken && !forceRefresh) {
|
|
608
535
|
try {
|
|
609
536
|
const secretBytes = new TextEncoder().encode(secret);
|
|
610
|
-
const { payload } = await
|
|
537
|
+
const { payload } = await jwtVerify(accessToken, secretBytes);
|
|
611
538
|
if (payload && typeof payload === 'object' && payload.email) {
|
|
612
539
|
return { isValid: true, payload: payload };
|
|
613
540
|
}
|
|
@@ -644,7 +571,7 @@ async function verifyAndRefreshToken(req, secret, options) {
|
|
|
644
571
|
let payload;
|
|
645
572
|
try {
|
|
646
573
|
const secretBytes = new TextEncoder().encode(secret);
|
|
647
|
-
const { payload: tokenPayload } = await
|
|
574
|
+
const { payload: tokenPayload } = await jwtVerify(refreshResult.accessToken, secretBytes);
|
|
648
575
|
if (tokenPayload && typeof tokenPayload === 'object' && tokenPayload.email) {
|
|
649
576
|
payload = tokenPayload;
|
|
650
577
|
}
|
|
@@ -663,12 +590,23 @@ async function verifyAndRefreshToken(req, secret, options) {
|
|
|
663
590
|
}
|
|
664
591
|
jwt.accessTokenExpires = Date.now() + (15 * 60 * 1000);
|
|
665
592
|
// NextAuth 세션 쿠키 생성
|
|
666
|
-
//
|
|
667
|
-
//
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
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
|
+
}
|
|
672
610
|
// 커스텀 토큰 쿠키 설정
|
|
673
611
|
if (newRefreshToken) {
|
|
674
612
|
setCustomTokens(response, refreshResult.accessToken, newRefreshToken, {
|
|
@@ -721,7 +659,7 @@ async function verifyAndRefreshToken(req, secret, options) {
|
|
|
721
659
|
* @param errorPath 에러 페이지 경로 (기본값: '/error')
|
|
722
660
|
* @returns NextResponse 리다이렉트 응답
|
|
723
661
|
*/
|
|
724
|
-
async function redirectToError(req, code, message, errorPath = '/error') {
|
|
662
|
+
export async function redirectToError(req, code, message, errorPath = '/error') {
|
|
725
663
|
const url = new URL(errorPath, req.url);
|
|
726
664
|
url.searchParams.set('code', code);
|
|
727
665
|
url.searchParams.set('message', message);
|
|
@@ -734,7 +672,7 @@ async function redirectToError(req, code, message, errorPath = '/error') {
|
|
|
734
672
|
* @param cookiePrefix 쿠키 이름 접두사 (필수)
|
|
735
673
|
* @returns NextResponse 객체
|
|
736
674
|
*/
|
|
737
|
-
function clearAuthCookies(response, cookiePrefix) {
|
|
675
|
+
export function clearAuthCookies(response, cookiePrefix) {
|
|
738
676
|
response.cookies.delete(`${cookiePrefix}_access_token`);
|
|
739
677
|
response.cookies.delete(`${cookiePrefix}_refresh_token`);
|
|
740
678
|
return response;
|
|
@@ -745,7 +683,7 @@ function clearAuthCookies(response, cookiePrefix) {
|
|
|
745
683
|
* @param serviceId 서비스 ID (필수)
|
|
746
684
|
* @returns 추출된 역할 또는 undefined
|
|
747
685
|
*/
|
|
748
|
-
function getEffectiveRole(token, serviceId) {
|
|
686
|
+
export function getEffectiveRole(token, serviceId) {
|
|
749
687
|
if (!token)
|
|
750
688
|
return undefined;
|
|
751
689
|
// token이 이미 JWT 객체인 경우 role을 직접 사용
|
|
@@ -765,7 +703,7 @@ function getEffectiveRole(token, serviceId) {
|
|
|
765
703
|
* @param systemAdminRole 시스템 관리자 역할 (기본값: 'SYSTEM_ADMIN')
|
|
766
704
|
* @returns 구독이 필요한지 여부
|
|
767
705
|
*/
|
|
768
|
-
function requiresSubscription(pathname, role, subscriptionRequiredPaths, systemAdminRole = 'SYSTEM_ADMIN') {
|
|
706
|
+
export function requiresSubscription(pathname, role, subscriptionRequiredPaths, systemAdminRole = 'SYSTEM_ADMIN') {
|
|
769
707
|
// 시스템 관리자는 구독 확인 제외
|
|
770
708
|
if (role === systemAdminRole) {
|
|
771
709
|
return false;
|
|
@@ -787,7 +725,7 @@ const VALID_LICENSE_KEY_HASHES = new Set([
|
|
|
787
725
|
* @param options.cookieDomain 쿠키 도메인 (선택)
|
|
788
726
|
* @returns NextAuth 쿠키 설정 객체
|
|
789
727
|
*/
|
|
790
|
-
function createNextAuthCookies(options) {
|
|
728
|
+
export function createNextAuthCookies(options) {
|
|
791
729
|
const { isProduction = false, cookieDomain } = options;
|
|
792
730
|
const isSecure = isProduction;
|
|
793
731
|
// cookieDomain이 설정되어 있으면 같은 도메인/서브도메인 간 쿠키 공유를 위해 'lax' 사용
|
|
@@ -839,7 +777,7 @@ function createNextAuthCookies(options) {
|
|
|
839
777
|
* @param options.jwtMaxAge JWT 최대 유지 시간 (초, 기본값: 30일)
|
|
840
778
|
* @returns NextAuth 기본 설정 객체
|
|
841
779
|
*/
|
|
842
|
-
function createNextAuthBaseConfig(options) {
|
|
780
|
+
export function createNextAuthBaseConfig(options) {
|
|
843
781
|
const { secret, isProduction = false, cookieDomain, signInPath = '/login', errorPath = '/login', nextAuthUrl, sessionMaxAge = 30 * 24 * 60 * 60, // 30일
|
|
844
782
|
jwtMaxAge = 30 * 24 * 60 * 60, // 30일
|
|
845
783
|
} = options;
|
|
@@ -868,7 +806,7 @@ function createNextAuthBaseConfig(options) {
|
|
|
868
806
|
* @param account 계정 정보
|
|
869
807
|
* @returns 업데이트된 JWT 토큰
|
|
870
808
|
*/
|
|
871
|
-
function createInitialJWTToken(token, user, account) {
|
|
809
|
+
export function createInitialJWTToken(token, user, account) {
|
|
872
810
|
return {
|
|
873
811
|
...token,
|
|
874
812
|
id: user.id,
|
|
@@ -891,7 +829,7 @@ function createInitialJWTToken(token, user, account) {
|
|
|
891
829
|
* @param session 기존 세션
|
|
892
830
|
* @returns 빈 세션 객체
|
|
893
831
|
*/
|
|
894
|
-
function createEmptySession(session) {
|
|
832
|
+
export function createEmptySession(session) {
|
|
895
833
|
return {
|
|
896
834
|
...session,
|
|
897
835
|
user: {
|
|
@@ -909,7 +847,7 @@ function createEmptySession(session) {
|
|
|
909
847
|
* @param token JWT 토큰
|
|
910
848
|
* @returns 업데이트된 세션
|
|
911
849
|
*/
|
|
912
|
-
function mapTokenToSession(session, token) {
|
|
850
|
+
export function mapTokenToSession(session, token) {
|
|
913
851
|
if (!session.user) {
|
|
914
852
|
return session;
|
|
915
853
|
}
|
|
@@ -941,7 +879,7 @@ function mapTokenToSession(session, token) {
|
|
|
941
879
|
* @param options.debug 디버깅 로그 출력 여부 (기본값: false)
|
|
942
880
|
* @returns 업데이트된 JWT 토큰
|
|
943
881
|
*/
|
|
944
|
-
async function handleJWTCallback(token, user, account, options) {
|
|
882
|
+
export async function handleJWTCallback(token, user, account, options) {
|
|
945
883
|
const { secret, licenseKey, serviceId, cookieName, debug = false, ssoBaseURL, authServiceKey, } = options || {};
|
|
946
884
|
// 디버깅 로그
|
|
947
885
|
if (debug) {
|
|
@@ -1052,10 +990,10 @@ async function handleJWTCallback(token, user, account, options) {
|
|
|
1052
990
|
* @param licenseKey 라이센스 키 (필수)
|
|
1053
991
|
* @returns NextAuth JWT 객체 또는 null
|
|
1054
992
|
*/
|
|
1055
|
-
async function getJWTFromCustomTokenCookie(cookieName, secret, serviceId, licenseKey) {
|
|
993
|
+
export async function getJWTFromCustomTokenCookie(cookieName, secret, serviceId, licenseKey) {
|
|
1056
994
|
debugLog('getJWTFromCustomTokenCookie', `Reading cookie: ${cookieName}`);
|
|
1057
995
|
try {
|
|
1058
|
-
const { cookies } = await
|
|
996
|
+
const { cookies } = await import('next/headers');
|
|
1059
997
|
const cookieStore = await cookies();
|
|
1060
998
|
const accessToken = cookieStore.get(cookieName)?.value;
|
|
1061
999
|
if (!accessToken) {
|
|
@@ -1092,7 +1030,7 @@ async function getJWTFromCustomTokenCookie(cookieName, secret, serviceId, licens
|
|
|
1092
1030
|
// ============================================================================
|
|
1093
1031
|
// LICENSE & AUTHORIZATION FUNCTIONS
|
|
1094
1032
|
// ============================================================================
|
|
1095
|
-
async function checkLicenseKey(licenseKey) {
|
|
1033
|
+
export async function checkLicenseKey(licenseKey) {
|
|
1096
1034
|
if (!licenseKey || licenseKey.length < 10) {
|
|
1097
1035
|
throw new Error('License key is required');
|
|
1098
1036
|
}
|
|
@@ -1101,7 +1039,7 @@ async function checkLicenseKey(licenseKey) {
|
|
|
1101
1039
|
throw new Error('Invalid license key');
|
|
1102
1040
|
}
|
|
1103
1041
|
}
|
|
1104
|
-
function checkRoleAccess(pathname, role, roleConfig) {
|
|
1042
|
+
export function checkRoleAccess(pathname, role, roleConfig) {
|
|
1105
1043
|
// 각 역할 설정을 확인
|
|
1106
1044
|
for (const [configRole, config] of Object.entries(roleConfig)) {
|
|
1107
1045
|
const isPathMatch = config.paths.some(path => pathname.startsWith(path));
|
|
@@ -1127,7 +1065,7 @@ function checkRoleAccess(pathname, role, roleConfig) {
|
|
|
1127
1065
|
* @param ssoBaseURL SSO 서버 기본 URL (필수)
|
|
1128
1066
|
* @returns NextResponse 리다이렉트 응답
|
|
1129
1067
|
*/
|
|
1130
|
-
async function redirectToSSOLogin(req, serviceId, ssoBaseURL) {
|
|
1068
|
+
export async function redirectToSSOLogin(req, serviceId, ssoBaseURL) {
|
|
1131
1069
|
const { NextResponse: NextResponseClass } = await getNextServer();
|
|
1132
1070
|
return NextResponseClass.redirect(new URL(`${ssoBaseURL}/auth/login?serviceId=${serviceId}`, req.url));
|
|
1133
1071
|
}
|
|
@@ -1139,7 +1077,7 @@ async function redirectToSSOLogin(req, serviceId, ssoBaseURL) {
|
|
|
1139
1077
|
* @param defaultPath 기본 경로 (기본값: '/admin')
|
|
1140
1078
|
* @returns NextResponse 리다이렉트 응답
|
|
1141
1079
|
*/
|
|
1142
|
-
async function redirectToRoleDashboard(req, role, rolePaths, defaultPath = '/admin') {
|
|
1080
|
+
export async function redirectToRoleDashboard(req, role, rolePaths, defaultPath = '/admin') {
|
|
1143
1081
|
const redirectPath = rolePaths[role] || defaultPath;
|
|
1144
1082
|
const { NextResponse: NextResponseClass } = await getNextServer();
|
|
1145
1083
|
return NextResponseClass.redirect(new URL(redirectPath, req.url));
|
|
@@ -1152,7 +1090,7 @@ async function redirectToRoleDashboard(req, role, rolePaths, defaultPath = '/adm
|
|
|
1152
1090
|
* @param token NextAuth JWT 객체
|
|
1153
1091
|
* @returns 만료 여부
|
|
1154
1092
|
*/
|
|
1155
|
-
function isTokenExpired(token) {
|
|
1093
|
+
export function isTokenExpired(token) {
|
|
1156
1094
|
if (!token)
|
|
1157
1095
|
return true;
|
|
1158
1096
|
if (token.exp && typeof token.exp === 'number' && token.exp < Math.floor(Date.now() / 1000)) {
|
|
@@ -1165,7 +1103,7 @@ function isTokenExpired(token) {
|
|
|
1165
1103
|
* @param token NextAuth JWT 객체
|
|
1166
1104
|
* @returns 유효성 여부
|
|
1167
1105
|
*/
|
|
1168
|
-
function isValidToken(token) {
|
|
1106
|
+
export function isValidToken(token) {
|
|
1169
1107
|
if (!token)
|
|
1170
1108
|
return false;
|
|
1171
1109
|
if (isTokenExpired(token))
|
|
@@ -1181,7 +1119,7 @@ function isValidToken(token) {
|
|
|
1181
1119
|
* @param serviceId 서비스 ID (필수)
|
|
1182
1120
|
* @returns 역할 보유 여부
|
|
1183
1121
|
*/
|
|
1184
|
-
function hasRole(token, role, serviceId) {
|
|
1122
|
+
export function hasRole(token, role, serviceId) {
|
|
1185
1123
|
if (!token)
|
|
1186
1124
|
return false;
|
|
1187
1125
|
const effectiveRole = getEffectiveRole(token, serviceId);
|
|
@@ -1194,7 +1132,7 @@ function hasRole(token, role, serviceId) {
|
|
|
1194
1132
|
* @param serviceId 서비스 ID (필수)
|
|
1195
1133
|
* @returns 역할 보유 여부
|
|
1196
1134
|
*/
|
|
1197
|
-
function hasAnyRole(token, roles, serviceId) {
|
|
1135
|
+
export function hasAnyRole(token, roles, serviceId) {
|
|
1198
1136
|
if (!token)
|
|
1199
1137
|
return false;
|
|
1200
1138
|
const effectiveRole = getEffectiveRole(token, serviceId);
|
|
@@ -1209,7 +1147,7 @@ function hasAnyRole(token, roles, serviceId) {
|
|
|
1209
1147
|
* @param publicPaths 공개 경로 배열
|
|
1210
1148
|
* @returns 공개 경로 여부
|
|
1211
1149
|
*/
|
|
1212
|
-
function isPublicPath(pathname, publicPaths) {
|
|
1150
|
+
export function isPublicPath(pathname, publicPaths) {
|
|
1213
1151
|
return publicPaths.some(path => pathname === path || pathname.startsWith(path));
|
|
1214
1152
|
}
|
|
1215
1153
|
/**
|
|
@@ -1217,7 +1155,7 @@ function isPublicPath(pathname, publicPaths) {
|
|
|
1217
1155
|
* @param pathname 경로명
|
|
1218
1156
|
* @returns API 경로 여부
|
|
1219
1157
|
*/
|
|
1220
|
-
function isApiPath(pathname) {
|
|
1158
|
+
export function isApiPath(pathname) {
|
|
1221
1159
|
return pathname.startsWith('/api/');
|
|
1222
1160
|
}
|
|
1223
1161
|
/**
|
|
@@ -1226,7 +1164,7 @@ function isApiPath(pathname) {
|
|
|
1226
1164
|
* @param exemptPaths 제외할 경로 배열
|
|
1227
1165
|
* @returns 보호된 API 경로 여부
|
|
1228
1166
|
*/
|
|
1229
|
-
function isProtectedApiPath(pathname, exemptPaths = []) {
|
|
1167
|
+
export function isProtectedApiPath(pathname, exemptPaths = []) {
|
|
1230
1168
|
if (!isApiPath(pathname))
|
|
1231
1169
|
return false;
|
|
1232
1170
|
return !exemptPaths.some(path => pathname.startsWith(path));
|
|
@@ -1241,7 +1179,7 @@ function isProtectedApiPath(pathname, exemptPaths = []) {
|
|
|
1241
1179
|
* @param options 옵션
|
|
1242
1180
|
* @returns 인증 결과
|
|
1243
1181
|
*/
|
|
1244
|
-
async function checkAuthentication(req, secret, options) {
|
|
1182
|
+
export async function checkAuthentication(req, secret, options) {
|
|
1245
1183
|
const { cookiePrefix, serviceId, getNextAuthToken, } = options;
|
|
1246
1184
|
let nextAuthToken = null;
|
|
1247
1185
|
if (getNextAuthToken) {
|
|
@@ -1281,7 +1219,7 @@ async function checkAuthentication(req, secret, options) {
|
|
|
1281
1219
|
* @param options 옵션
|
|
1282
1220
|
* @returns 인증 결과
|
|
1283
1221
|
*/
|
|
1284
|
-
async function verifyAndRefreshTokenWithNextAuth(req, nextAuthToken, secret, options) {
|
|
1222
|
+
export async function verifyAndRefreshTokenWithNextAuth(req, nextAuthToken, secret, options) {
|
|
1285
1223
|
const { cookiePrefix, isProduction } = options;
|
|
1286
1224
|
// NextAuth 세션 토큰 쿠키 확인
|
|
1287
1225
|
const nextAuthSessionTokenCookieName = isProduction
|
|
@@ -1304,7 +1242,7 @@ async function verifyAndRefreshTokenWithNextAuth(req, nextAuthToken, secret, opt
|
|
|
1304
1242
|
if (accessToken) {
|
|
1305
1243
|
try {
|
|
1306
1244
|
const secretBytes = new TextEncoder().encode(secret);
|
|
1307
|
-
const { payload } = await
|
|
1245
|
+
const { payload } = await jwtVerify(accessToken, secretBytes);
|
|
1308
1246
|
if (payload && typeof payload === 'object' && payload.email) {
|
|
1309
1247
|
hasValidAccessToken = true;
|
|
1310
1248
|
}
|
|
@@ -1330,7 +1268,7 @@ async function verifyAndRefreshTokenWithNextAuth(req, nextAuthToken, secret, opt
|
|
|
1330
1268
|
if (accessToken) {
|
|
1331
1269
|
try {
|
|
1332
1270
|
const secretBytes = new TextEncoder().encode(secret);
|
|
1333
|
-
const result = await
|
|
1271
|
+
const result = await jwtVerify(accessToken, secretBytes);
|
|
1334
1272
|
payload = result.payload;
|
|
1335
1273
|
}
|
|
1336
1274
|
catch {
|
|
@@ -1369,7 +1307,7 @@ async function verifyAndRefreshTokenWithNextAuth(req, nextAuthToken, secret, opt
|
|
|
1369
1307
|
if (accessToken && hasValidAccessToken) {
|
|
1370
1308
|
try {
|
|
1371
1309
|
const secretBytes = new TextEncoder().encode(secret);
|
|
1372
|
-
const result = await
|
|
1310
|
+
const result = await jwtVerify(accessToken, secretBytes);
|
|
1373
1311
|
payload = result.payload;
|
|
1374
1312
|
}
|
|
1375
1313
|
catch {
|
|
@@ -1394,7 +1332,7 @@ async function verifyAndRefreshTokenWithNextAuth(req, nextAuthToken, secret, opt
|
|
|
1394
1332
|
* @param defaults 기본 설정값 (선택사항, 제공하지 않으면 최소 기본값 사용)
|
|
1395
1333
|
* @returns 미들웨어 설정 객체
|
|
1396
1334
|
*/
|
|
1397
|
-
function createMiddlewareConfig(config, defaults) {
|
|
1335
|
+
export function createMiddlewareConfig(config, defaults) {
|
|
1398
1336
|
// 기본값 설정
|
|
1399
1337
|
const defaultPublicPaths = defaults?.publicPaths || [
|
|
1400
1338
|
'/robots.txt',
|
|
@@ -1460,7 +1398,7 @@ function createMiddlewareConfig(config, defaults) {
|
|
|
1460
1398
|
* @param options 미들웨어 실행 옵션 (secret 필수)
|
|
1461
1399
|
* @returns NextResponse 또는 null (다음 미들웨어로 진행)
|
|
1462
1400
|
*/
|
|
1463
|
-
async function handleMiddleware(req, config, options) {
|
|
1401
|
+
export async function handleMiddleware(req, config, options) {
|
|
1464
1402
|
// Edge Runtime 호환을 위해 next/server를 한 번만 import
|
|
1465
1403
|
const { NextResponse: NextResponseClass } = await getNextServer();
|
|
1466
1404
|
try {
|
|
@@ -1480,7 +1418,7 @@ async function handleMiddleware(req, config, options) {
|
|
|
1480
1418
|
}
|
|
1481
1419
|
else {
|
|
1482
1420
|
try {
|
|
1483
|
-
const { getToken } = await
|
|
1421
|
+
const { getToken } = await import('next-auth/jwt');
|
|
1484
1422
|
token = await getToken({ req, secret });
|
|
1485
1423
|
debugLog('handleMiddleware', 'getToken result:', { hasToken: !!token });
|
|
1486
1424
|
}
|
|
@@ -1653,7 +1591,7 @@ async function handleMiddleware(req, config, options) {
|
|
|
1653
1591
|
}
|
|
1654
1592
|
else {
|
|
1655
1593
|
try {
|
|
1656
|
-
const { getToken } = await
|
|
1594
|
+
const { getToken } = await import('next-auth/jwt');
|
|
1657
1595
|
finalToken = await getToken({ req, secret });
|
|
1658
1596
|
}
|
|
1659
1597
|
catch {
|
package/package.json
CHANGED