@thinkingcat/auth-utils 1.0.34 → 1.0.35
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 +86 -49
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -211,50 +211,36 @@ function createNextAuthJWT(payload, serviceId) {
|
|
|
211
211
|
* @returns 인코딩된 세션 토큰
|
|
212
212
|
*/
|
|
213
213
|
async function encodeNextAuthToken(jwt, secret, maxAge = 30 * 24 * 60 * 60) {
|
|
214
|
+
// Edge Runtime과 Node.js Runtime 간 호환성을 위해
|
|
215
|
+
// next-auth/jwt의 encode 대신 jose를 직접 사용
|
|
216
|
+
// (Edge Runtime에서 encode한 토큰을 Node.js Runtime에서 decode 못하는 문제 방지)
|
|
217
|
+
debugLog('encodeNextAuthToken', 'Using jose EncryptJWT for cross-runtime compatibility');
|
|
218
|
+
// NextAuth는 secret을 SHA-256 해시하여 32바이트 키로 사용
|
|
219
|
+
const secretHash = await createHashSHA256(secret);
|
|
220
|
+
// SHA-256 해시는 64자 hex 문자열이므로, 32바이트로 변환
|
|
221
|
+
const keyBytes = new Uint8Array(32);
|
|
222
|
+
for (let i = 0; i < 32; i++) {
|
|
223
|
+
keyBytes[i] = parseInt(secretHash.slice(i * 2, i * 2 + 2), 16);
|
|
224
|
+
}
|
|
225
|
+
const now = Math.floor(Date.now() / 1000);
|
|
226
|
+
// EncryptJWT를 사용하여 JWE 토큰 생성
|
|
227
|
+
// NextAuth는 'dir' 키 관리와 'A256GCM' 암호화를 사용
|
|
214
228
|
try {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
229
|
+
const token = await new jose_1.EncryptJWT(jwt)
|
|
230
|
+
.setProtectedHeader({
|
|
231
|
+
alg: 'dir', // Direct key agreement
|
|
232
|
+
enc: 'A256GCM' // AES-256-GCM encryption
|
|
233
|
+
})
|
|
234
|
+
.setIssuedAt(now)
|
|
235
|
+
.setExpirationTime(now + maxAge)
|
|
236
|
+
.setJti(crypto.randomUUID())
|
|
237
|
+
.encrypt(keyBytes);
|
|
238
|
+
debugLog('encodeNextAuthToken', 'EncryptJWT successful, length:', token.length);
|
|
239
|
+
return token;
|
|
225
240
|
}
|
|
226
241
|
catch (error) {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
debugLog('encodeNextAuthToken', 'encode failed, using EncryptJWT fallback', error);
|
|
230
|
-
// NextAuth는 secret을 SHA-256 해시하여 32바이트 키로 사용
|
|
231
|
-
// jose의 EncryptJWT는 'dir' 알고리즘에서 Uint8Array 키를 직접 사용
|
|
232
|
-
const secretHash = await createHashSHA256(secret);
|
|
233
|
-
// SHA-256 해시는 64자 hex 문자열이므로, 32바이트로 변환
|
|
234
|
-
const keyBytes = new Uint8Array(32);
|
|
235
|
-
for (let i = 0; i < 32; i++) {
|
|
236
|
-
keyBytes[i] = parseInt(secretHash.slice(i * 2, i * 2 + 2), 16);
|
|
237
|
-
}
|
|
238
|
-
const now = Math.floor(Date.now() / 1000);
|
|
239
|
-
// EncryptJWT를 사용하여 JWE 토큰 생성
|
|
240
|
-
// NextAuth는 'dir' 키 관리와 'A256GCM' 암호화를 사용
|
|
241
|
-
// 'dir' 알고리즘은 Uint8Array 키를 직접 사용
|
|
242
|
-
try {
|
|
243
|
-
const token = await new jose_1.EncryptJWT(jwt)
|
|
244
|
-
.setProtectedHeader({
|
|
245
|
-
alg: 'dir', // Direct key agreement
|
|
246
|
-
enc: 'A256GCM' // AES-256-GCM encryption
|
|
247
|
-
})
|
|
248
|
-
.setIssuedAt(now)
|
|
249
|
-
.setExpirationTime(now + maxAge)
|
|
250
|
-
.setJti(crypto.randomUUID())
|
|
251
|
-
.encrypt(keyBytes);
|
|
252
|
-
return token;
|
|
253
|
-
}
|
|
254
|
-
catch (encryptError) {
|
|
255
|
-
debugError('encodeNextAuthToken', 'EncryptJWT also failed:', encryptError);
|
|
256
|
-
throw new Error(`Failed to encode NextAuth token: ${error instanceof Error ? error.message : String(error)}`);
|
|
257
|
-
}
|
|
242
|
+
debugError('encodeNextAuthToken', 'EncryptJWT failed:', error);
|
|
243
|
+
throw new Error(`Failed to encode NextAuth token: ${error instanceof Error ? error.message : String(error)}`);
|
|
258
244
|
}
|
|
259
245
|
}
|
|
260
246
|
function setCustomTokens(response, accessToken, optionsOrRefreshToken, options) {
|
|
@@ -449,19 +435,45 @@ async function createAuthResponse(accessToken, secret, options) {
|
|
|
449
435
|
}
|
|
450
436
|
// accessTokenExpires 추가 (15분)
|
|
451
437
|
jwt.accessTokenExpires = Date.now() + (15 * 60 * 1000);
|
|
452
|
-
debugLog('createAuthResponse', 'JWT created
|
|
438
|
+
debugLog('createAuthResponse', 'JWT created:', {
|
|
453
439
|
hasId: !!jwt.id,
|
|
454
440
|
hasEmail: !!jwt.email,
|
|
455
441
|
hasRole: !!jwt.role,
|
|
456
442
|
hasRefreshToken: !!jwt.refreshToken,
|
|
457
443
|
});
|
|
458
|
-
// 3.
|
|
444
|
+
// 3. NextAuth session cookie 생성 (jose 사용으로 Edge/Node.js Runtime 호환)
|
|
445
|
+
const nextAuthToken = await encodeNextAuthToken(jwt, secret);
|
|
446
|
+
debugLog('createAuthResponse', 'NextAuth session token encoded:', {
|
|
447
|
+
tokenLength: nextAuthToken.length,
|
|
448
|
+
tokenPrefix: nextAuthToken.substring(0, 30) + '...',
|
|
449
|
+
});
|
|
450
|
+
// 4. Response 생성 (HTTP 302 리다이렉트 사용)
|
|
459
451
|
const { NextResponse: NextResponseClass } = await getNextServer();
|
|
460
452
|
// redirectPath가 있으면 302 리다이렉트, 없으면 200 OK
|
|
461
453
|
const response = redirectPath
|
|
462
454
|
? NextResponseClass.redirect(new URL(redirectPath, req.url), { status: 302 })
|
|
463
455
|
: NextResponseClass.json({ success: true, message: text || 'Authentication successful' }, { status: 200 });
|
|
464
|
-
//
|
|
456
|
+
// 5. NextAuth session cookie 설정
|
|
457
|
+
const nextAuthCookieName = isProduction
|
|
458
|
+
? '__Secure-next-auth.session-token'
|
|
459
|
+
: 'next-auth.session-token';
|
|
460
|
+
const cookieOptions = {
|
|
461
|
+
httpOnly: true,
|
|
462
|
+
secure: isProduction,
|
|
463
|
+
sameSite: isProduction ? 'none' : 'lax',
|
|
464
|
+
path: '/',
|
|
465
|
+
maxAge: 30 * 24 * 60 * 60, // 30일
|
|
466
|
+
};
|
|
467
|
+
if (cookieDomain) {
|
|
468
|
+
cookieOptions.domain = cookieDomain;
|
|
469
|
+
}
|
|
470
|
+
response.cookies.set(nextAuthCookieName, nextAuthToken, cookieOptions);
|
|
471
|
+
debugLog('createAuthResponse', 'NextAuth session cookie set:', {
|
|
472
|
+
name: nextAuthCookieName,
|
|
473
|
+
valueLength: nextAuthToken.length,
|
|
474
|
+
...cookieOptions,
|
|
475
|
+
});
|
|
476
|
+
// 6. 커스텀 토큰 쿠키 설정
|
|
465
477
|
if (refreshToken) {
|
|
466
478
|
setCustomTokens(response, accessToken, refreshToken, {
|
|
467
479
|
cookiePrefix,
|
|
@@ -626,12 +638,37 @@ async function verifyAndRefreshToken(req, secret, options) {
|
|
|
626
638
|
catch {
|
|
627
639
|
// 토큰 검증 실패
|
|
628
640
|
}
|
|
629
|
-
debugLog('verifyAndRefreshToken', 'Updating
|
|
630
|
-
// NextResponse.next()를 생성하고
|
|
631
|
-
// NextAuth 쿠키는 생성하지 않음 - NextAuth가 자체적으로 처리하도록 함
|
|
641
|
+
debugLog('verifyAndRefreshToken', 'Updating cookies with NextAuth session...');
|
|
642
|
+
// NextResponse.next()를 생성하고 쿠키 설정
|
|
632
643
|
const { NextResponse: NextResponseClass } = await getNextServer();
|
|
633
644
|
const response = NextResponseClass.next();
|
|
634
|
-
//
|
|
645
|
+
// NextAuth JWT 생성
|
|
646
|
+
const jwt = createNextAuthJWT(payload, serviceId);
|
|
647
|
+
if (newRefreshToken) {
|
|
648
|
+
jwt.refreshToken = newRefreshToken;
|
|
649
|
+
}
|
|
650
|
+
jwt.accessTokenExpires = Date.now() + (15 * 60 * 1000);
|
|
651
|
+
// NextAuth 세션 쿠키 설정 (jose 사용으로 Edge/Node.js Runtime 호환)
|
|
652
|
+
const nextAuthToken = await encodeNextAuthToken(jwt, secret);
|
|
653
|
+
const nextAuthCookieName = isProduction
|
|
654
|
+
? '__Secure-next-auth.session-token'
|
|
655
|
+
: 'next-auth.session-token';
|
|
656
|
+
const cookieOptions = {
|
|
657
|
+
httpOnly: true,
|
|
658
|
+
secure: isProduction,
|
|
659
|
+
sameSite: isProduction ? 'none' : 'lax',
|
|
660
|
+
path: '/',
|
|
661
|
+
maxAge: 30 * 24 * 60 * 60,
|
|
662
|
+
};
|
|
663
|
+
if (cookieDomain) {
|
|
664
|
+
cookieOptions.domain = cookieDomain;
|
|
665
|
+
}
|
|
666
|
+
response.cookies.set(nextAuthCookieName, nextAuthToken, cookieOptions);
|
|
667
|
+
debugLog('verifyAndRefreshToken', 'NextAuth session cookie set:', {
|
|
668
|
+
name: nextAuthCookieName,
|
|
669
|
+
valueLength: nextAuthToken.length,
|
|
670
|
+
});
|
|
671
|
+
// 커스텀 토큰 쿠키 설정
|
|
635
672
|
if (newRefreshToken) {
|
|
636
673
|
setCustomTokens(response, refreshResult.accessToken, newRefreshToken, {
|
|
637
674
|
cookiePrefix,
|
|
@@ -646,7 +683,7 @@ async function verifyAndRefreshToken(req, secret, options) {
|
|
|
646
683
|
cookieDomain,
|
|
647
684
|
});
|
|
648
685
|
}
|
|
649
|
-
debugLog('verifyAndRefreshToken', '
|
|
686
|
+
debugLog('verifyAndRefreshToken', 'All cookies updated, continuing with current request');
|
|
650
687
|
return { isValid: true, response, payload };
|
|
651
688
|
}
|
|
652
689
|
catch (error) {
|
package/package.json
CHANGED