@thinkingcat/auth-utils 1.0.18 → 1.0.19
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.d.ts +4 -4
- package/dist/index.js +40 -27
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { JWT } from "next-auth/jwt";
|
|
2
2
|
import type { Session } from "next-auth";
|
|
3
|
-
import { NextResponse, NextRequest } from 'next/server';
|
|
3
|
+
import type { NextResponse, NextRequest } from 'next/server';
|
|
4
4
|
export interface ResponseLike {
|
|
5
5
|
cookies: {
|
|
6
6
|
delete(name: string): void;
|
|
@@ -239,7 +239,7 @@ export declare function verifyAndRefreshToken(req: NextRequest, secret: string,
|
|
|
239
239
|
* @param errorPath 에러 페이지 경로 (기본값: '/error')
|
|
240
240
|
* @returns NextResponse 리다이렉트 응답
|
|
241
241
|
*/
|
|
242
|
-
export declare function redirectToError(req: NextRequest, code: string, message: string, errorPath?: string): NextResponse
|
|
242
|
+
export declare function redirectToError(req: NextRequest, code: string, message: string, errorPath?: string): Promise<NextResponse>;
|
|
243
243
|
/**
|
|
244
244
|
* 인증 쿠키를 삭제하는 헬퍼 함수
|
|
245
245
|
* @param response NextResponse 객체
|
|
@@ -491,7 +491,7 @@ export declare function checkRoleAccess(pathname: string, role: string, roleConf
|
|
|
491
491
|
* @param ssoBaseURL SSO 서버 기본 URL (필수)
|
|
492
492
|
* @returns NextResponse 리다이렉트 응답
|
|
493
493
|
*/
|
|
494
|
-
export declare function redirectToSSOLogin(req: NextRequest, serviceId: string, ssoBaseURL: string): NextResponse
|
|
494
|
+
export declare function redirectToSSOLogin(req: NextRequest, serviceId: string, ssoBaseURL: string): Promise<NextResponse>;
|
|
495
495
|
/**
|
|
496
496
|
* 역할별 대시보드 경로로 리다이렉트하는 헬퍼 함수
|
|
497
497
|
* @param req NextRequest 객체
|
|
@@ -500,7 +500,7 @@ export declare function redirectToSSOLogin(req: NextRequest, serviceId: string,
|
|
|
500
500
|
* @param defaultPath 기본 경로 (기본값: '/admin')
|
|
501
501
|
* @returns NextResponse 리다이렉트 응답
|
|
502
502
|
*/
|
|
503
|
-
export declare function redirectToRoleDashboard(req: NextRequest, role: string, rolePaths: Record<string, string>, defaultPath?: string): NextResponse
|
|
503
|
+
export declare function redirectToRoleDashboard(req: NextRequest, role: string, rolePaths: Record<string, string>, defaultPath?: string): Promise<NextResponse>;
|
|
504
504
|
/**
|
|
505
505
|
* 토큰이 만료되었는지 확인하는 함수
|
|
506
506
|
* @param token NextAuth JWT 객체
|
package/dist/index.js
CHANGED
|
@@ -72,7 +72,6 @@ exports.verifyAndRefreshTokenWithNextAuth = verifyAndRefreshTokenWithNextAuth;
|
|
|
72
72
|
exports.createMiddlewareConfig = createMiddlewareConfig;
|
|
73
73
|
exports.handleMiddleware = handleMiddleware;
|
|
74
74
|
const jose_1 = require("jose");
|
|
75
|
-
const server_1 = require("next/server");
|
|
76
75
|
// ============================================================================
|
|
77
76
|
// UTILITY FUNCTIONS
|
|
78
77
|
// ============================================================================
|
|
@@ -99,6 +98,12 @@ async function createHashSHA256(data) {
|
|
|
99
98
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
100
99
|
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
101
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Edge Runtime 호환을 위해 next/server를 동적 import
|
|
103
|
+
*/
|
|
104
|
+
async function getNextServer() {
|
|
105
|
+
return await Promise.resolve().then(() => __importStar(require('next/server')));
|
|
106
|
+
}
|
|
102
107
|
/**
|
|
103
108
|
* NextAuth 세션 토큰 쿠키를 삭제하는 헬퍼 함수
|
|
104
109
|
*/
|
|
@@ -428,7 +433,8 @@ async function createAuthResponse(accessToken, secret, options) {
|
|
|
428
433
|
? createRedirectHTML(redirectPath, displayText)
|
|
429
434
|
: createRedirectHTML('', displayText).replace("window.location.href = ''", "window.location.reload()");
|
|
430
435
|
// 6. Response 생성
|
|
431
|
-
const
|
|
436
|
+
const { NextResponse: NextResponseClass } = await getNextServer();
|
|
437
|
+
const response = new NextResponseClass(html, {
|
|
432
438
|
status: 200,
|
|
433
439
|
headers: {
|
|
434
440
|
'Content-Type': 'text/html',
|
|
@@ -617,14 +623,16 @@ async function verifyAndRefreshToken(req, secret, options) {
|
|
|
617
623
|
}
|
|
618
624
|
else {
|
|
619
625
|
debugLog('verifyAndRefreshToken', 'Refresh failed, clearing all cookies');
|
|
620
|
-
const
|
|
626
|
+
const { NextResponse: NextResponseClass } = await getNextServer();
|
|
627
|
+
const response = NextResponseClass.next();
|
|
621
628
|
clearAllAuthCookies(response, options.cookiePrefix, options.isProduction);
|
|
622
629
|
return { isValid: false, response, error: 'REFRESH_FAILED' };
|
|
623
630
|
}
|
|
624
631
|
}
|
|
625
632
|
catch (error) {
|
|
626
633
|
debugError('verifyAndRefreshToken', 'Token refresh error:', error);
|
|
627
|
-
const
|
|
634
|
+
const { NextResponse: NextResponseClass } = await getNextServer();
|
|
635
|
+
const response = NextResponseClass.next();
|
|
628
636
|
clearAllAuthCookies(response, options.cookiePrefix, options.isProduction);
|
|
629
637
|
return { isValid: false, response, error: 'REFRESH_ERROR' };
|
|
630
638
|
}
|
|
@@ -642,11 +650,12 @@ async function verifyAndRefreshToken(req, secret, options) {
|
|
|
642
650
|
* @param errorPath 에러 페이지 경로 (기본값: '/error')
|
|
643
651
|
* @returns NextResponse 리다이렉트 응답
|
|
644
652
|
*/
|
|
645
|
-
function redirectToError(req, code, message, errorPath = '/error') {
|
|
653
|
+
async function redirectToError(req, code, message, errorPath = '/error') {
|
|
646
654
|
const url = new URL(errorPath, req.url);
|
|
647
655
|
url.searchParams.set('code', code);
|
|
648
656
|
url.searchParams.set('message', message);
|
|
649
|
-
|
|
657
|
+
const { NextResponse: NextResponseClass } = await getNextServer();
|
|
658
|
+
return NextResponseClass.redirect(url);
|
|
650
659
|
}
|
|
651
660
|
/**
|
|
652
661
|
* 인증 쿠키를 삭제하는 헬퍼 함수
|
|
@@ -971,8 +980,9 @@ function checkRoleAccess(pathname, role, roleConfig) {
|
|
|
971
980
|
* @param ssoBaseURL SSO 서버 기본 URL (필수)
|
|
972
981
|
* @returns NextResponse 리다이렉트 응답
|
|
973
982
|
*/
|
|
974
|
-
function redirectToSSOLogin(req, serviceId, ssoBaseURL) {
|
|
975
|
-
|
|
983
|
+
async function redirectToSSOLogin(req, serviceId, ssoBaseURL) {
|
|
984
|
+
const { NextResponse: NextResponseClass } = await getNextServer();
|
|
985
|
+
return NextResponseClass.redirect(new URL(`${ssoBaseURL}/auth/login?serviceId=${serviceId}`, req.url));
|
|
976
986
|
}
|
|
977
987
|
/**
|
|
978
988
|
* 역할별 대시보드 경로로 리다이렉트하는 헬퍼 함수
|
|
@@ -982,9 +992,10 @@ function redirectToSSOLogin(req, serviceId, ssoBaseURL) {
|
|
|
982
992
|
* @param defaultPath 기본 경로 (기본값: '/admin')
|
|
983
993
|
* @returns NextResponse 리다이렉트 응답
|
|
984
994
|
*/
|
|
985
|
-
function redirectToRoleDashboard(req, role, rolePaths, defaultPath = '/admin') {
|
|
995
|
+
async function redirectToRoleDashboard(req, role, rolePaths, defaultPath = '/admin') {
|
|
986
996
|
const redirectPath = rolePaths[role] || defaultPath;
|
|
987
|
-
|
|
997
|
+
const { NextResponse: NextResponseClass } = await getNextServer();
|
|
998
|
+
return NextResponseClass.redirect(new URL(redirectPath, req.url));
|
|
988
999
|
}
|
|
989
1000
|
// ============================================================================
|
|
990
1001
|
// TOKEN VALIDATION UTILITIES
|
|
@@ -1250,6 +1261,8 @@ function createMiddlewareConfig(config, defaults) {
|
|
|
1250
1261
|
* @returns NextResponse 또는 null (다음 미들웨어로 진행)
|
|
1251
1262
|
*/
|
|
1252
1263
|
async function handleMiddleware(req, config, options) {
|
|
1264
|
+
// Edge Runtime 호환을 위해 next/server를 한 번만 import
|
|
1265
|
+
const { NextResponse: NextResponseClass } = await getNextServer();
|
|
1253
1266
|
try {
|
|
1254
1267
|
const pathname = req.nextUrl.pathname;
|
|
1255
1268
|
const { secret, isProduction, cookieDomain, getNextAuthToken, licenseKey } = options;
|
|
@@ -1282,10 +1295,10 @@ async function handleMiddleware(req, config, options) {
|
|
|
1282
1295
|
// 1. API 요청 처리
|
|
1283
1296
|
if (pathname.startsWith('/api/')) {
|
|
1284
1297
|
if (config.authApiPaths.includes(pathname)) {
|
|
1285
|
-
return
|
|
1298
|
+
return NextResponseClass.next();
|
|
1286
1299
|
}
|
|
1287
1300
|
if (config.subscriptionExemptApiPaths.some((path) => pathname.startsWith(path))) {
|
|
1288
|
-
return
|
|
1301
|
+
return NextResponseClass.next();
|
|
1289
1302
|
}
|
|
1290
1303
|
const authCheck = await verifyAndRefreshTokenWithNextAuth(req, token, secret, {
|
|
1291
1304
|
cookiePrefix,
|
|
@@ -1301,10 +1314,10 @@ async function handleMiddleware(req, config, options) {
|
|
|
1301
1314
|
return authCheck.response;
|
|
1302
1315
|
}
|
|
1303
1316
|
if (!authCheck.isValid) {
|
|
1304
|
-
const response = redirectToError(req, 'UNAUTHORIZED', '인증이 필요합니다.', config.errorPath);
|
|
1317
|
+
const response = await redirectToError(req, 'UNAUTHORIZED', '인증이 필요합니다.', config.errorPath);
|
|
1305
1318
|
return clearAuthCookies(response, cookiePrefix);
|
|
1306
1319
|
}
|
|
1307
|
-
return
|
|
1320
|
+
return NextResponseClass.next();
|
|
1308
1321
|
}
|
|
1309
1322
|
// 2. 루트 경로 처리 - SSO 토큰 처리 (인증 체크보다 먼저!)
|
|
1310
1323
|
if (pathname === '/') {
|
|
@@ -1347,26 +1360,26 @@ async function handleMiddleware(req, config, options) {
|
|
|
1347
1360
|
catch (error) {
|
|
1348
1361
|
debugError('handleMiddleware', 'Error processing token:', error);
|
|
1349
1362
|
const ssoBaseURL = options.ssoBaseURL;
|
|
1350
|
-
return redirectToSSOLogin(req, serviceId, ssoBaseURL);
|
|
1363
|
+
return await redirectToSSOLogin(req, serviceId, ssoBaseURL);
|
|
1351
1364
|
}
|
|
1352
1365
|
}
|
|
1353
1366
|
// 토큰이 없고 이미 인증된 경우 역할별 대시보드로 리다이렉트
|
|
1354
1367
|
if (token && effectiveRole) {
|
|
1355
|
-
return redirectToRoleDashboard(req, effectiveRole, config.rolePaths);
|
|
1368
|
+
return await redirectToRoleDashboard(req, effectiveRole, config.rolePaths);
|
|
1356
1369
|
}
|
|
1357
1370
|
// 인증되지 않은 경우 SSO 로그인 페이지로 리다이렉트
|
|
1358
1371
|
const ssoBaseURL = options.ssoBaseURL;
|
|
1359
|
-
return redirectToSSOLogin(req, serviceId, ssoBaseURL);
|
|
1372
|
+
return await redirectToSSOLogin(req, serviceId, ssoBaseURL);
|
|
1360
1373
|
}
|
|
1361
1374
|
// 3. 공개 경로 처리
|
|
1362
1375
|
if (config.publicPaths.some((path) => pathname === path || pathname.startsWith(path))) {
|
|
1363
1376
|
if (pathname === '/error' || pathname === '/verification') {
|
|
1364
|
-
return
|
|
1377
|
+
return NextResponseClass.next();
|
|
1365
1378
|
}
|
|
1366
1379
|
if (token && effectiveRole) {
|
|
1367
|
-
return redirectToRoleDashboard(req, effectiveRole, config.rolePaths);
|
|
1380
|
+
return await redirectToRoleDashboard(req, effectiveRole, config.rolePaths);
|
|
1368
1381
|
}
|
|
1369
|
-
return
|
|
1382
|
+
return NextResponseClass.next();
|
|
1370
1383
|
}
|
|
1371
1384
|
// 4. 인증 체크
|
|
1372
1385
|
const authCheck = await verifyAndRefreshTokenWithNextAuth(req, token, secret, {
|
|
@@ -1384,7 +1397,7 @@ async function handleMiddleware(req, config, options) {
|
|
|
1384
1397
|
}
|
|
1385
1398
|
if (!authCheck.isValid) {
|
|
1386
1399
|
const ssoBaseURL = options.ssoBaseURL;
|
|
1387
|
-
return redirectToSSOLogin(req, serviceId, ssoBaseURL);
|
|
1400
|
+
return await redirectToSSOLogin(req, serviceId, ssoBaseURL);
|
|
1388
1401
|
}
|
|
1389
1402
|
// 5. 토큰 확인 및 변환
|
|
1390
1403
|
let finalToken = token;
|
|
@@ -1413,24 +1426,24 @@ async function handleMiddleware(req, config, options) {
|
|
|
1413
1426
|
}
|
|
1414
1427
|
if (!finalToken) {
|
|
1415
1428
|
const ssoBaseURL = options.ssoBaseURL;
|
|
1416
|
-
return redirectToSSOLogin(req, serviceId, ssoBaseURL);
|
|
1429
|
+
return await redirectToSSOLogin(req, serviceId, ssoBaseURL);
|
|
1417
1430
|
}
|
|
1418
1431
|
// 6. 토큰 에러 체크
|
|
1419
1432
|
if (finalToken.error === "RefreshAccessTokenError") {
|
|
1420
1433
|
const ssoBaseURL = options.ssoBaseURL;
|
|
1421
|
-
return redirectToSSOLogin(req, serviceId, ssoBaseURL);
|
|
1434
|
+
return await redirectToSSOLogin(req, serviceId, ssoBaseURL);
|
|
1422
1435
|
}
|
|
1423
1436
|
// 7. 토큰 유효성 체크
|
|
1424
1437
|
if (!finalToken.role || !finalToken.email) {
|
|
1425
1438
|
const ssoBaseURL = options.ssoBaseURL;
|
|
1426
|
-
return redirectToSSOLogin(req, serviceId, ssoBaseURL);
|
|
1439
|
+
return await redirectToSSOLogin(req, serviceId, ssoBaseURL);
|
|
1427
1440
|
}
|
|
1428
1441
|
// 8. 역할 기반 접근 제어
|
|
1429
1442
|
const finalEffectiveRole = finalToken.role || getEffectiveRole(finalToken, serviceId) || effectiveRole || '';
|
|
1430
1443
|
if (config.roleAccessConfig && Object.keys(config.roleAccessConfig).length > 0 && finalEffectiveRole) {
|
|
1431
1444
|
const roleCheck = checkRoleAccess(pathname, finalEffectiveRole, config.roleAccessConfig);
|
|
1432
1445
|
if (!roleCheck.allowed) {
|
|
1433
|
-
return redirectToError(req, 'ACCESS_DENIED', roleCheck.message || '접근 권한이 없습니다.', config.errorPath);
|
|
1446
|
+
return await redirectToError(req, 'ACCESS_DENIED', roleCheck.message || '접근 권한이 없습니다.', config.errorPath);
|
|
1434
1447
|
}
|
|
1435
1448
|
}
|
|
1436
1449
|
// 9. 구독 상태 확인 (시스템 관리자 제외)
|
|
@@ -1442,13 +1455,13 @@ async function handleMiddleware(req, config, options) {
|
|
|
1442
1455
|
}
|
|
1443
1456
|
const subscriptionCheck = validateServiceSubscription(services, serviceId, ssoBaseURL);
|
|
1444
1457
|
if (!subscriptionCheck.isValid) {
|
|
1445
|
-
return
|
|
1458
|
+
return NextResponseClass.redirect(subscriptionCheck.redirectUrl);
|
|
1446
1459
|
}
|
|
1447
1460
|
}
|
|
1448
1461
|
return null;
|
|
1449
1462
|
}
|
|
1450
1463
|
catch (error) {
|
|
1451
1464
|
debugError('handleMiddleware', 'Middleware error:', error);
|
|
1452
|
-
return redirectToError(req, 'INTERNAL_ERROR', '서버 오류가 발생했습니다.', config.errorPath);
|
|
1465
|
+
return await redirectToError(req, 'INTERNAL_ERROR', '서버 오류가 발생했습니다.', config.errorPath);
|
|
1453
1466
|
}
|
|
1454
1467
|
}
|
package/package.json
CHANGED