@thinkingcat/auth-utils 1.0.47 → 1.0.49
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 +51 -124
- package/package.json +2 -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
|
}
|
|
@@ -732,7 +659,7 @@ async function verifyAndRefreshToken(req, secret, options) {
|
|
|
732
659
|
* @param errorPath 에러 페이지 경로 (기본값: '/error')
|
|
733
660
|
* @returns NextResponse 리다이렉트 응답
|
|
734
661
|
*/
|
|
735
|
-
async function redirectToError(req, code, message, errorPath = '/error') {
|
|
662
|
+
export async function redirectToError(req, code, message, errorPath = '/error') {
|
|
736
663
|
const url = new URL(errorPath, req.url);
|
|
737
664
|
url.searchParams.set('code', code);
|
|
738
665
|
url.searchParams.set('message', message);
|
|
@@ -745,7 +672,7 @@ async function redirectToError(req, code, message, errorPath = '/error') {
|
|
|
745
672
|
* @param cookiePrefix 쿠키 이름 접두사 (필수)
|
|
746
673
|
* @returns NextResponse 객체
|
|
747
674
|
*/
|
|
748
|
-
function clearAuthCookies(response, cookiePrefix) {
|
|
675
|
+
export function clearAuthCookies(response, cookiePrefix) {
|
|
749
676
|
response.cookies.delete(`${cookiePrefix}_access_token`);
|
|
750
677
|
response.cookies.delete(`${cookiePrefix}_refresh_token`);
|
|
751
678
|
return response;
|
|
@@ -756,7 +683,7 @@ function clearAuthCookies(response, cookiePrefix) {
|
|
|
756
683
|
* @param serviceId 서비스 ID (필수)
|
|
757
684
|
* @returns 추출된 역할 또는 undefined
|
|
758
685
|
*/
|
|
759
|
-
function getEffectiveRole(token, serviceId) {
|
|
686
|
+
export function getEffectiveRole(token, serviceId) {
|
|
760
687
|
if (!token)
|
|
761
688
|
return undefined;
|
|
762
689
|
// token이 이미 JWT 객체인 경우 role을 직접 사용
|
|
@@ -776,7 +703,7 @@ function getEffectiveRole(token, serviceId) {
|
|
|
776
703
|
* @param systemAdminRole 시스템 관리자 역할 (기본값: 'SYSTEM_ADMIN')
|
|
777
704
|
* @returns 구독이 필요한지 여부
|
|
778
705
|
*/
|
|
779
|
-
function requiresSubscription(pathname, role, subscriptionRequiredPaths, systemAdminRole = 'SYSTEM_ADMIN') {
|
|
706
|
+
export function requiresSubscription(pathname, role, subscriptionRequiredPaths, systemAdminRole = 'SYSTEM_ADMIN') {
|
|
780
707
|
// 시스템 관리자는 구독 확인 제외
|
|
781
708
|
if (role === systemAdminRole) {
|
|
782
709
|
return false;
|
|
@@ -798,7 +725,7 @@ const VALID_LICENSE_KEY_HASHES = new Set([
|
|
|
798
725
|
* @param options.cookieDomain 쿠키 도메인 (선택)
|
|
799
726
|
* @returns NextAuth 쿠키 설정 객체
|
|
800
727
|
*/
|
|
801
|
-
function createNextAuthCookies(options) {
|
|
728
|
+
export function createNextAuthCookies(options) {
|
|
802
729
|
const { isProduction = false, cookieDomain } = options;
|
|
803
730
|
const isSecure = isProduction;
|
|
804
731
|
// cookieDomain이 설정되어 있으면 같은 도메인/서브도메인 간 쿠키 공유를 위해 'lax' 사용
|
|
@@ -850,7 +777,7 @@ function createNextAuthCookies(options) {
|
|
|
850
777
|
* @param options.jwtMaxAge JWT 최대 유지 시간 (초, 기본값: 30일)
|
|
851
778
|
* @returns NextAuth 기본 설정 객체
|
|
852
779
|
*/
|
|
853
|
-
function createNextAuthBaseConfig(options) {
|
|
780
|
+
export function createNextAuthBaseConfig(options) {
|
|
854
781
|
const { secret, isProduction = false, cookieDomain, signInPath = '/login', errorPath = '/login', nextAuthUrl, sessionMaxAge = 30 * 24 * 60 * 60, // 30일
|
|
855
782
|
jwtMaxAge = 30 * 24 * 60 * 60, // 30일
|
|
856
783
|
} = options;
|
|
@@ -879,7 +806,7 @@ function createNextAuthBaseConfig(options) {
|
|
|
879
806
|
* @param account 계정 정보
|
|
880
807
|
* @returns 업데이트된 JWT 토큰
|
|
881
808
|
*/
|
|
882
|
-
function createInitialJWTToken(token, user, account) {
|
|
809
|
+
export function createInitialJWTToken(token, user, account) {
|
|
883
810
|
return {
|
|
884
811
|
...token,
|
|
885
812
|
id: user.id,
|
|
@@ -902,7 +829,7 @@ function createInitialJWTToken(token, user, account) {
|
|
|
902
829
|
* @param session 기존 세션
|
|
903
830
|
* @returns 빈 세션 객체
|
|
904
831
|
*/
|
|
905
|
-
function createEmptySession(session) {
|
|
832
|
+
export function createEmptySession(session) {
|
|
906
833
|
return {
|
|
907
834
|
...session,
|
|
908
835
|
user: {
|
|
@@ -920,7 +847,7 @@ function createEmptySession(session) {
|
|
|
920
847
|
* @param token JWT 토큰
|
|
921
848
|
* @returns 업데이트된 세션
|
|
922
849
|
*/
|
|
923
|
-
function mapTokenToSession(session, token) {
|
|
850
|
+
export function mapTokenToSession(session, token) {
|
|
924
851
|
if (!session.user) {
|
|
925
852
|
return session;
|
|
926
853
|
}
|
|
@@ -952,7 +879,7 @@ function mapTokenToSession(session, token) {
|
|
|
952
879
|
* @param options.debug 디버깅 로그 출력 여부 (기본값: false)
|
|
953
880
|
* @returns 업데이트된 JWT 토큰
|
|
954
881
|
*/
|
|
955
|
-
async function handleJWTCallback(token, user, account, options) {
|
|
882
|
+
export async function handleJWTCallback(token, user, account, options) {
|
|
956
883
|
const { secret, licenseKey, serviceId, cookieName, debug = false, ssoBaseURL, authServiceKey, } = options || {};
|
|
957
884
|
// 디버깅 로그
|
|
958
885
|
if (debug) {
|
|
@@ -1063,10 +990,10 @@ async function handleJWTCallback(token, user, account, options) {
|
|
|
1063
990
|
* @param licenseKey 라이센스 키 (필수)
|
|
1064
991
|
* @returns NextAuth JWT 객체 또는 null
|
|
1065
992
|
*/
|
|
1066
|
-
async function getJWTFromCustomTokenCookie(cookieName, secret, serviceId, licenseKey) {
|
|
993
|
+
export async function getJWTFromCustomTokenCookie(cookieName, secret, serviceId, licenseKey) {
|
|
1067
994
|
debugLog('getJWTFromCustomTokenCookie', `Reading cookie: ${cookieName}`);
|
|
1068
995
|
try {
|
|
1069
|
-
const { cookies } = await
|
|
996
|
+
const { cookies } = await import('next/headers');
|
|
1070
997
|
const cookieStore = await cookies();
|
|
1071
998
|
const accessToken = cookieStore.get(cookieName)?.value;
|
|
1072
999
|
if (!accessToken) {
|
|
@@ -1103,7 +1030,7 @@ async function getJWTFromCustomTokenCookie(cookieName, secret, serviceId, licens
|
|
|
1103
1030
|
// ============================================================================
|
|
1104
1031
|
// LICENSE & AUTHORIZATION FUNCTIONS
|
|
1105
1032
|
// ============================================================================
|
|
1106
|
-
async function checkLicenseKey(licenseKey) {
|
|
1033
|
+
export async function checkLicenseKey(licenseKey) {
|
|
1107
1034
|
if (!licenseKey || licenseKey.length < 10) {
|
|
1108
1035
|
throw new Error('License key is required');
|
|
1109
1036
|
}
|
|
@@ -1112,7 +1039,7 @@ async function checkLicenseKey(licenseKey) {
|
|
|
1112
1039
|
throw new Error('Invalid license key');
|
|
1113
1040
|
}
|
|
1114
1041
|
}
|
|
1115
|
-
function checkRoleAccess(pathname, role, roleConfig) {
|
|
1042
|
+
export function checkRoleAccess(pathname, role, roleConfig) {
|
|
1116
1043
|
// 각 역할 설정을 확인
|
|
1117
1044
|
for (const [configRole, config] of Object.entries(roleConfig)) {
|
|
1118
1045
|
const isPathMatch = config.paths.some(path => pathname.startsWith(path));
|
|
@@ -1138,7 +1065,7 @@ function checkRoleAccess(pathname, role, roleConfig) {
|
|
|
1138
1065
|
* @param ssoBaseURL SSO 서버 기본 URL (필수)
|
|
1139
1066
|
* @returns NextResponse 리다이렉트 응답
|
|
1140
1067
|
*/
|
|
1141
|
-
async function redirectToSSOLogin(req, serviceId, ssoBaseURL) {
|
|
1068
|
+
export async function redirectToSSOLogin(req, serviceId, ssoBaseURL) {
|
|
1142
1069
|
const { NextResponse: NextResponseClass } = await getNextServer();
|
|
1143
1070
|
return NextResponseClass.redirect(new URL(`${ssoBaseURL}/auth/login?serviceId=${serviceId}`, req.url));
|
|
1144
1071
|
}
|
|
@@ -1150,7 +1077,7 @@ async function redirectToSSOLogin(req, serviceId, ssoBaseURL) {
|
|
|
1150
1077
|
* @param defaultPath 기본 경로 (기본값: '/admin')
|
|
1151
1078
|
* @returns NextResponse 리다이렉트 응답
|
|
1152
1079
|
*/
|
|
1153
|
-
async function redirectToRoleDashboard(req, role, rolePaths, defaultPath = '/admin') {
|
|
1080
|
+
export async function redirectToRoleDashboard(req, role, rolePaths, defaultPath = '/admin') {
|
|
1154
1081
|
const redirectPath = rolePaths[role] || defaultPath;
|
|
1155
1082
|
const { NextResponse: NextResponseClass } = await getNextServer();
|
|
1156
1083
|
return NextResponseClass.redirect(new URL(redirectPath, req.url));
|
|
@@ -1163,7 +1090,7 @@ async function redirectToRoleDashboard(req, role, rolePaths, defaultPath = '/adm
|
|
|
1163
1090
|
* @param token NextAuth JWT 객체
|
|
1164
1091
|
* @returns 만료 여부
|
|
1165
1092
|
*/
|
|
1166
|
-
function isTokenExpired(token) {
|
|
1093
|
+
export function isTokenExpired(token) {
|
|
1167
1094
|
if (!token)
|
|
1168
1095
|
return true;
|
|
1169
1096
|
if (token.exp && typeof token.exp === 'number' && token.exp < Math.floor(Date.now() / 1000)) {
|
|
@@ -1176,7 +1103,7 @@ function isTokenExpired(token) {
|
|
|
1176
1103
|
* @param token NextAuth JWT 객체
|
|
1177
1104
|
* @returns 유효성 여부
|
|
1178
1105
|
*/
|
|
1179
|
-
function isValidToken(token) {
|
|
1106
|
+
export function isValidToken(token) {
|
|
1180
1107
|
if (!token)
|
|
1181
1108
|
return false;
|
|
1182
1109
|
if (isTokenExpired(token))
|
|
@@ -1192,7 +1119,7 @@ function isValidToken(token) {
|
|
|
1192
1119
|
* @param serviceId 서비스 ID (필수)
|
|
1193
1120
|
* @returns 역할 보유 여부
|
|
1194
1121
|
*/
|
|
1195
|
-
function hasRole(token, role, serviceId) {
|
|
1122
|
+
export function hasRole(token, role, serviceId) {
|
|
1196
1123
|
if (!token)
|
|
1197
1124
|
return false;
|
|
1198
1125
|
const effectiveRole = getEffectiveRole(token, serviceId);
|
|
@@ -1205,7 +1132,7 @@ function hasRole(token, role, serviceId) {
|
|
|
1205
1132
|
* @param serviceId 서비스 ID (필수)
|
|
1206
1133
|
* @returns 역할 보유 여부
|
|
1207
1134
|
*/
|
|
1208
|
-
function hasAnyRole(token, roles, serviceId) {
|
|
1135
|
+
export function hasAnyRole(token, roles, serviceId) {
|
|
1209
1136
|
if (!token)
|
|
1210
1137
|
return false;
|
|
1211
1138
|
const effectiveRole = getEffectiveRole(token, serviceId);
|
|
@@ -1220,7 +1147,7 @@ function hasAnyRole(token, roles, serviceId) {
|
|
|
1220
1147
|
* @param publicPaths 공개 경로 배열
|
|
1221
1148
|
* @returns 공개 경로 여부
|
|
1222
1149
|
*/
|
|
1223
|
-
function isPublicPath(pathname, publicPaths) {
|
|
1150
|
+
export function isPublicPath(pathname, publicPaths) {
|
|
1224
1151
|
return publicPaths.some(path => pathname === path || pathname.startsWith(path));
|
|
1225
1152
|
}
|
|
1226
1153
|
/**
|
|
@@ -1228,7 +1155,7 @@ function isPublicPath(pathname, publicPaths) {
|
|
|
1228
1155
|
* @param pathname 경로명
|
|
1229
1156
|
* @returns API 경로 여부
|
|
1230
1157
|
*/
|
|
1231
|
-
function isApiPath(pathname) {
|
|
1158
|
+
export function isApiPath(pathname) {
|
|
1232
1159
|
return pathname.startsWith('/api/');
|
|
1233
1160
|
}
|
|
1234
1161
|
/**
|
|
@@ -1237,7 +1164,7 @@ function isApiPath(pathname) {
|
|
|
1237
1164
|
* @param exemptPaths 제외할 경로 배열
|
|
1238
1165
|
* @returns 보호된 API 경로 여부
|
|
1239
1166
|
*/
|
|
1240
|
-
function isProtectedApiPath(pathname, exemptPaths = []) {
|
|
1167
|
+
export function isProtectedApiPath(pathname, exemptPaths = []) {
|
|
1241
1168
|
if (!isApiPath(pathname))
|
|
1242
1169
|
return false;
|
|
1243
1170
|
return !exemptPaths.some(path => pathname.startsWith(path));
|
|
@@ -1252,7 +1179,7 @@ function isProtectedApiPath(pathname, exemptPaths = []) {
|
|
|
1252
1179
|
* @param options 옵션
|
|
1253
1180
|
* @returns 인증 결과
|
|
1254
1181
|
*/
|
|
1255
|
-
async function checkAuthentication(req, secret, options) {
|
|
1182
|
+
export async function checkAuthentication(req, secret, options) {
|
|
1256
1183
|
const { cookiePrefix, serviceId, getNextAuthToken, } = options;
|
|
1257
1184
|
let nextAuthToken = null;
|
|
1258
1185
|
if (getNextAuthToken) {
|
|
@@ -1292,7 +1219,7 @@ async function checkAuthentication(req, secret, options) {
|
|
|
1292
1219
|
* @param options 옵션
|
|
1293
1220
|
* @returns 인증 결과
|
|
1294
1221
|
*/
|
|
1295
|
-
async function verifyAndRefreshTokenWithNextAuth(req, nextAuthToken, secret, options) {
|
|
1222
|
+
export async function verifyAndRefreshTokenWithNextAuth(req, nextAuthToken, secret, options) {
|
|
1296
1223
|
const { cookiePrefix, isProduction } = options;
|
|
1297
1224
|
// NextAuth 세션 토큰 쿠키 확인
|
|
1298
1225
|
const nextAuthSessionTokenCookieName = isProduction
|
|
@@ -1315,7 +1242,7 @@ async function verifyAndRefreshTokenWithNextAuth(req, nextAuthToken, secret, opt
|
|
|
1315
1242
|
if (accessToken) {
|
|
1316
1243
|
try {
|
|
1317
1244
|
const secretBytes = new TextEncoder().encode(secret);
|
|
1318
|
-
const { payload } = await
|
|
1245
|
+
const { payload } = await jwtVerify(accessToken, secretBytes);
|
|
1319
1246
|
if (payload && typeof payload === 'object' && payload.email) {
|
|
1320
1247
|
hasValidAccessToken = true;
|
|
1321
1248
|
}
|
|
@@ -1341,7 +1268,7 @@ async function verifyAndRefreshTokenWithNextAuth(req, nextAuthToken, secret, opt
|
|
|
1341
1268
|
if (accessToken) {
|
|
1342
1269
|
try {
|
|
1343
1270
|
const secretBytes = new TextEncoder().encode(secret);
|
|
1344
|
-
const result = await
|
|
1271
|
+
const result = await jwtVerify(accessToken, secretBytes);
|
|
1345
1272
|
payload = result.payload;
|
|
1346
1273
|
}
|
|
1347
1274
|
catch {
|
|
@@ -1380,7 +1307,7 @@ async function verifyAndRefreshTokenWithNextAuth(req, nextAuthToken, secret, opt
|
|
|
1380
1307
|
if (accessToken && hasValidAccessToken) {
|
|
1381
1308
|
try {
|
|
1382
1309
|
const secretBytes = new TextEncoder().encode(secret);
|
|
1383
|
-
const result = await
|
|
1310
|
+
const result = await jwtVerify(accessToken, secretBytes);
|
|
1384
1311
|
payload = result.payload;
|
|
1385
1312
|
}
|
|
1386
1313
|
catch {
|
|
@@ -1405,7 +1332,7 @@ async function verifyAndRefreshTokenWithNextAuth(req, nextAuthToken, secret, opt
|
|
|
1405
1332
|
* @param defaults 기본 설정값 (선택사항, 제공하지 않으면 최소 기본값 사용)
|
|
1406
1333
|
* @returns 미들웨어 설정 객체
|
|
1407
1334
|
*/
|
|
1408
|
-
function createMiddlewareConfig(config, defaults) {
|
|
1335
|
+
export function createMiddlewareConfig(config, defaults) {
|
|
1409
1336
|
// 기본값 설정
|
|
1410
1337
|
const defaultPublicPaths = defaults?.publicPaths || [
|
|
1411
1338
|
'/robots.txt',
|
|
@@ -1471,7 +1398,7 @@ function createMiddlewareConfig(config, defaults) {
|
|
|
1471
1398
|
* @param options 미들웨어 실행 옵션 (secret 필수)
|
|
1472
1399
|
* @returns NextResponse 또는 null (다음 미들웨어로 진행)
|
|
1473
1400
|
*/
|
|
1474
|
-
async function handleMiddleware(req, config, options) {
|
|
1401
|
+
export async function handleMiddleware(req, config, options) {
|
|
1475
1402
|
// Edge Runtime 호환을 위해 next/server를 한 번만 import
|
|
1476
1403
|
const { NextResponse: NextResponseClass } = await getNextServer();
|
|
1477
1404
|
try {
|
|
@@ -1491,7 +1418,7 @@ async function handleMiddleware(req, config, options) {
|
|
|
1491
1418
|
}
|
|
1492
1419
|
else {
|
|
1493
1420
|
try {
|
|
1494
|
-
const { getToken } = await
|
|
1421
|
+
const { getToken } = await import('next-auth/jwt');
|
|
1495
1422
|
token = await getToken({ req, secret });
|
|
1496
1423
|
debugLog('handleMiddleware', 'getToken result:', { hasToken: !!token });
|
|
1497
1424
|
}
|
|
@@ -1664,7 +1591,7 @@ async function handleMiddleware(req, config, options) {
|
|
|
1664
1591
|
}
|
|
1665
1592
|
else {
|
|
1666
1593
|
try {
|
|
1667
|
-
const { getToken } = await
|
|
1594
|
+
const { getToken } = await import('next-auth/jwt');
|
|
1668
1595
|
finalToken = await getToken({ req, secret });
|
|
1669
1596
|
}
|
|
1670
1597
|
catch {
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thinkingcat/auth-utils",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.49",
|
|
4
4
|
"description": "Authentication utilities for ThinkingCat SSO services with conditional logging",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": {
|
|
9
9
|
"types": "./dist/index.d.ts",
|
|
10
|
+
"edge-light": "./dist/index.js",
|
|
10
11
|
"default": "./dist/index.js"
|
|
11
12
|
}
|
|
12
13
|
},
|