@thinkingcat/auth-utils 1.0.37 → 1.0.39

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.
Files changed (2) hide show
  1. package/dist/index.js +104 -36
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -211,36 +211,49 @@ 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
+ // NextAuth의 encode() 함수를 우선적으로 사용 (가장 호환성 좋음)
228
215
  try {
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;
216
+ const { encode } = await Promise.resolve().then(() => __importStar(require('next-auth/jwt')));
217
+ debugLog('encodeNextAuthToken', 'Using next-auth/jwt encode');
218
+ const encoded = await encode({
219
+ token: jwt,
220
+ secret: secret,
221
+ maxAge: maxAge,
222
+ });
223
+ debugLog('encodeNextAuthToken', 'NextAuth encode successful, length:', encoded.length);
224
+ return encoded;
240
225
  }
241
226
  catch (error) {
242
- debugError('encodeNextAuthToken', 'EncryptJWT failed:', error);
243
- throw new Error(`Failed to encode NextAuth token: ${error instanceof Error ? error.message : String(error)}`);
227
+ // Edge Runtime에서 encode가 작동하지 않을 수 있으므로
228
+ // jose의 EncryptJWT를 fallback으로 사용
229
+ debugLog('encodeNextAuthToken', 'NextAuth encode failed, using jose EncryptJWT fallback', error);
230
+ // NextAuth는 secret을 SHA-256 해시하여 32바이트 키로 사용
231
+ const secretHash = await createHashSHA256(secret);
232
+ // SHA-256 해시는 64자 hex 문자열이므로, 32바이트로 변환
233
+ const keyBytes = new Uint8Array(32);
234
+ for (let i = 0; i < 32; i++) {
235
+ keyBytes[i] = parseInt(secretHash.slice(i * 2, i * 2 + 2), 16);
236
+ }
237
+ const now = Math.floor(Date.now() / 1000);
238
+ // EncryptJWT를 사용하여 JWE 토큰 생성
239
+ // NextAuth는 'dir' 키 관리와 'A256GCM' 암호화를 사용
240
+ try {
241
+ const token = await new jose_1.EncryptJWT(jwt)
242
+ .setProtectedHeader({
243
+ alg: 'dir', // Direct key agreement
244
+ enc: 'A256GCM' // AES-256-GCM encryption
245
+ })
246
+ .setIssuedAt(now)
247
+ .setExpirationTime(now + maxAge)
248
+ .setJti(crypto.randomUUID())
249
+ .encrypt(keyBytes);
250
+ debugLog('encodeNextAuthToken', 'EncryptJWT fallback successful, length:', token.length);
251
+ return token;
252
+ }
253
+ catch (encryptError) {
254
+ debugError('encodeNextAuthToken', 'EncryptJWT also failed:', encryptError);
255
+ throw new Error(`Failed to encode NextAuth token: ${error instanceof Error ? error.message : String(error)}`);
256
+ }
244
257
  }
245
258
  }
246
259
  function setCustomTokens(response, accessToken, optionsOrRefreshToken, options) {
@@ -435,20 +448,44 @@ async function createAuthResponse(accessToken, secret, options) {
435
448
  }
436
449
  // accessTokenExpires 추가 (15분)
437
450
  jwt.accessTokenExpires = Date.now() + (15 * 60 * 1000);
438
- debugLog('createAuthResponse', 'JWT prepared (NextAuth will encode it in API Routes):', {
451
+ debugLog('createAuthResponse', 'JWT created:', {
439
452
  hasId: !!jwt.id,
440
453
  hasEmail: !!jwt.email,
441
454
  hasRole: !!jwt.role,
442
455
  hasRefreshToken: !!jwt.refreshToken,
443
456
  });
444
- // 3. Response 생성 (HTTP 302 리다이렉트 사용)
457
+ // 3. NextAuth 세션 쿠키 생성 (NextAuth encode() 우선 사용)
458
+ const nextAuthToken = await encodeNextAuthToken(jwt, secret);
459
+ debugLog('createAuthResponse', 'NextAuth session token encoded:', {
460
+ tokenLength: nextAuthToken.length,
461
+ });
462
+ // 4. Response 생성 (HTTP 302 리다이렉트 사용)
445
463
  const { NextResponse: NextResponseClass } = await getNextServer();
446
464
  // redirectPath가 있으면 302 리다이렉트, 없으면 200 OK
447
465
  const response = redirectPath
448
466
  ? NextResponseClass.redirect(new URL(redirectPath, req.url), { status: 302 })
449
467
  : NextResponseClass.json({ success: true, message: text || 'Authentication successful' }, { status: 200 });
450
- // 4. 커스텀 토큰 쿠키만 설정
451
- // NextAuth 쿠키는 handleJWTCallback에서 커스텀 토큰을 읽어서 자동 생성됨
468
+ // 5. NextAuth 세션 쿠키 설정
469
+ const nextAuthCookieName = isProduction
470
+ ? '__Secure-next-auth.session-token'
471
+ : 'next-auth.session-token';
472
+ const cookieOptions = {
473
+ httpOnly: true,
474
+ secure: isProduction,
475
+ sameSite: isProduction ? 'none' : 'lax',
476
+ path: '/',
477
+ maxAge: 30 * 24 * 60 * 60, // 30일
478
+ };
479
+ if (cookieDomain) {
480
+ cookieOptions.domain = cookieDomain;
481
+ }
482
+ response.cookies.set(nextAuthCookieName, nextAuthToken, cookieOptions);
483
+ debugLog('createAuthResponse', 'NextAuth session cookie set:', {
484
+ name: nextAuthCookieName,
485
+ valueLength: nextAuthToken.length,
486
+ ...cookieOptions,
487
+ });
488
+ // 6. 커스텀 토큰 쿠키 설정
452
489
  if (refreshToken) {
453
490
  setCustomTokens(response, accessToken, refreshToken, {
454
491
  cookiePrefix,
@@ -613,12 +650,43 @@ async function verifyAndRefreshToken(req, secret, options) {
613
650
  catch {
614
651
  // 토큰 검증 실패
615
652
  }
616
- debugLog('verifyAndRefreshToken', 'Updating custom cookies only (NextAuth will handle session)...');
617
- // NextResponse.next()를 생성하고 커스텀 토큰만 설정
653
+ debugLog('verifyAndRefreshToken', 'Updating cookies including NextAuth session...');
654
+ // NextResponse.next()를 생성
618
655
  const { NextResponse: NextResponseClass } = await getNextServer();
619
656
  const response = NextResponseClass.next();
620
- // 커스텀 토큰 쿠키만 설정
621
- // NextAuth 쿠키는 handleJWTCallback에서 커스텀 토큰을 읽어서 자동 생성됨
657
+ // NextAuth JWT 생성
658
+ const jwt = createNextAuthJWT(payload, serviceId);
659
+ if (newRefreshToken) {
660
+ jwt.refreshToken = newRefreshToken;
661
+ }
662
+ jwt.accessTokenExpires = Date.now() + (15 * 60 * 1000);
663
+ // NextAuth 세션 쿠키 생성 (NextAuth encode() 우선 사용)
664
+ try {
665
+ const nextAuthToken = await encodeNextAuthToken(jwt, secret);
666
+ const nextAuthCookieName = isProduction
667
+ ? '__Secure-next-auth.session-token'
668
+ : 'next-auth.session-token';
669
+ const cookieOptions = {
670
+ httpOnly: true,
671
+ secure: isProduction,
672
+ sameSite: isProduction ? 'none' : 'lax',
673
+ path: '/',
674
+ maxAge: 30 * 24 * 60 * 60,
675
+ };
676
+ if (cookieDomain) {
677
+ cookieOptions.domain = cookieDomain;
678
+ }
679
+ response.cookies.set(nextAuthCookieName, nextAuthToken, cookieOptions);
680
+ debugLog('verifyAndRefreshToken', 'NextAuth session cookie set:', {
681
+ name: nextAuthCookieName,
682
+ valueLength: nextAuthToken.length,
683
+ });
684
+ }
685
+ catch (error) {
686
+ debugError('verifyAndRefreshToken', 'Failed to set NextAuth cookie:', error);
687
+ // Continue even if NextAuth cookie fails
688
+ }
689
+ // 커스텀 토큰 쿠키 설정
622
690
  if (newRefreshToken) {
623
691
  setCustomTokens(response, refreshResult.accessToken, newRefreshToken, {
624
692
  cookiePrefix,
@@ -633,7 +701,7 @@ async function verifyAndRefreshToken(req, secret, options) {
633
701
  cookieDomain,
634
702
  });
635
703
  }
636
- debugLog('verifyAndRefreshToken', 'Custom cookies updated, NextAuth will pick them up via handleJWTCallback');
704
+ debugLog('verifyAndRefreshToken', 'All cookies updated');
637
705
  return { isValid: true, response, payload };
638
706
  }
639
707
  catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thinkingcat/auth-utils",
3
- "version": "1.0.37",
3
+ "version": "1.0.39",
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",