@spfn/auth 0.2.0-beta.13 → 0.2.0-beta.15
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/nextjs/api.js.map +1 -1
- package/dist/nextjs/client.d.ts +28 -0
- package/dist/server.d.ts +61 -2
- package/dist/server.js +60 -3
- package/dist/server.js.map +1 -1
- package/package.json +4 -4
package/dist/nextjs/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/nextjs/api.ts","../../src/nextjs/interceptors/login-register.ts","../../src/nextjs/interceptors/general-auth.ts","../../src/nextjs/interceptors/key-rotation.ts","../../src/nextjs/interceptors/oauth.ts","../../src/nextjs/session-helpers.ts","../../src/nextjs/interceptors/index.ts"],"sourcesContent":["/**\n * @spfn/auth/adapters/nextjs/api\n *\n * Next.js Adapter for SPFN Auth\n *\n * Provides automatic interceptor registration for seamless auth flow:\n * - Session management (HttpOnly cookies)\n * - JWT generation and signing\n * - Public key encryption\n *\n * @requires next >= 13.0.0\n *\n * @example\n * ```typescript\n * // Just import to auto-register interceptors\n * import '@spfn/auth/nextjs/api';\n * ```\n */\n\n// Re-export interceptors for advanced usage\nimport { registerInterceptors } from \"@spfn/core/nextjs/server\";\nimport { authInterceptors } from './interceptors';\n\n// Auto-register interceptors on import\nregisterInterceptors('auth', authInterceptors);","/**\n * Login/Register Interceptor\n *\n * Automatically handles key generation and session management\n * for login and register endpoints\n */\n\nimport type { InterceptorRule } from '@spfn/core/nextjs';\nimport { generateKeyPair, sealSession, getSessionTtl, COOKIE_NAMES, authLogger } from '@spfn/auth/server';\nimport { env } from '@spfn/core/config';\n\n/**\n * Login and Register Interceptor\n *\n * Request: Generates key pair and adds publicKey to request body\n * Response: Saves privateKey to HttpOnly cookie\n */\nexport const loginRegisterInterceptor: InterceptorRule =\n {\n pathPattern: /^\\/_auth\\/(login|register)$/,\n method: 'POST',\n\n request: async (ctx, next) =>\n {\n // Get old session if exists (for key rotation on login)\n const oldKeyId = ctx.cookies.get(COOKIE_NAMES.SESSION_KEY_ID);\n\n // Extract remember option from request body (if provided)\n const remember = ctx.body?.remember;\n\n // Generate new key pair\n const keyPair = generateKeyPair('ES256');\n\n // Add publicKey data to request body\n if (!ctx.body)\n {\n ctx.body = {};\n }\n\n ctx.body.publicKey = keyPair.publicKey;\n ctx.body.keyId = keyPair.keyId;\n ctx.body.fingerprint = keyPair.fingerprint;\n ctx.body.algorithm = keyPair.algorithm;\n ctx.body.keySize = Buffer.from(keyPair.publicKey, 'base64').length;\n\n // Add oldKeyId for login (key rotation)\n if (ctx.path === '/_auth/login' && oldKeyId)\n {\n ctx.body.oldKeyId = oldKeyId;\n }\n\n // Remove remember from body (not part of contract)\n delete ctx.body.remember;\n\n // Store privateKey and remember in metadata for response interceptor\n ctx.metadata.privateKey = keyPair.privateKey;\n ctx.metadata.keyId = keyPair.keyId;\n ctx.metadata.algorithm = keyPair.algorithm;\n ctx.metadata.remember = remember;\n\n await next();\n },\n\n response: async (ctx, next) =>\n {\n // Only process successful responses\n if (ctx.response.status !== 200)\n {\n await next();\n return;\n }\n\n // Handle both wrapped ({ data: { userId } }) and direct ({ userId }) responses\n const userData = ctx.response.body?.data || ctx.response.body;\n if (!userData?.userId)\n {\n authLogger.interceptor.login.error('No userId in response');\n await next();\n return;\n }\n\n try\n {\n // Get session TTL (priority: runtime > global > env > default)\n const ttl = getSessionTtl(ctx.metadata.remember);\n\n // Encrypt session data\n const sessionData =\n {\n userId: userData.userId,\n privateKey: ctx.metadata.privateKey,\n keyId: ctx.metadata.keyId,\n algorithm: ctx.metadata.algorithm,\n };\n\n const sealed = await sealSession(sessionData, ttl);\n\n // Set HttpOnly session cookie\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION,\n value: sealed,\n options: {\n httpOnly: true,\n secure: env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n\n // Set keyId cookie (for oldKeyId lookup)\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION_KEY_ID,\n value: ctx.metadata.keyId,\n options: {\n httpOnly: true,\n secure: env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n }\n catch (error)\n {\n const err = error as Error;\n authLogger.interceptor.login.error('Failed to save session', err);\n }\n\n await next();\n },\n };","/**\n * General Authentication Interceptor\n *\n * Handles authentication for all API requests except login/register\n * - Session validation and renewal\n * - JWT generation and signing\n * - Expired session cleanup\n */\n\nimport type { InterceptorRule } from '@spfn/core/nextjs';\nimport { unsealSession, sealSession, shouldRefreshSession, generateClientToken, getSessionTtl, COOKIE_NAMES, authLogger } from '@spfn/auth/server';\nimport { env } from '@spfn/core/config';\n\n/**\n * Check if path requires authentication\n */\nfunction requiresAuth(path: string): boolean\n{\n // Paths that don't require auth\n const publicPaths = [\n /^\\/_auth\\/login$/,\n /^\\/_auth\\/register$/,\n /^\\/_auth\\/codes$/, // Send verification code\n /^\\/_auth\\/codes\\/verify$/, // Verify code\n /^\\/_auth\\/exists$/, // Check account exists\n ];\n\n return !publicPaths.some((pattern) => pattern.test(path));\n}\n\n/**\n * General Authentication Interceptor\n *\n * Applies to all paths except login/register/codes\n * - Validates session\n * - Generates JWT token\n * - Refreshes session if needed\n * - Clears expired sessions\n */\nexport const generalAuthInterceptor: InterceptorRule =\n{\n pathPattern: '*', // Match all paths, filter by requiresAuth()\n method: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],\n\n request: async (ctx, next) =>\n {\n // Skip if path doesn't require auth\n if (!requiresAuth(ctx.path))\n {\n authLogger.interceptor.general.debug(`Public path, skipping auth: ${ctx.path}`);\n await next();\n return;\n }\n\n // Log available cookies\n const cookieNames = Array.from(ctx.cookies.keys());\n authLogger.interceptor.general.debug('Available cookies:', {\n cookieNames,\n totalCount: cookieNames.length,\n lookingFor: COOKIE_NAMES.SESSION,\n });\n\n const sessionCookie = ctx.cookies.get(COOKIE_NAMES.SESSION);\n\n authLogger.interceptor.general.debug('Request', {\n method: ctx.method,\n path: ctx.path,\n hasSession: !!sessionCookie,\n sessionCookieValue: sessionCookie ? '***EXISTS***' : 'NOT_FOUND',\n });\n\n // No session cookie\n if (!sessionCookie)\n {\n authLogger.interceptor.general.debug('No session cookie, proceeding without auth');\n // Let request proceed - server will return 401\n await next();\n return;\n }\n\n try\n {\n // Decrypt and validate session\n const session = await unsealSession(sessionCookie);\n\n authLogger.interceptor.general.debug('Session valid', {\n userId: session.userId,\n keyId: session.keyId,\n });\n\n // Check if session should be refreshed (within 24h of expiry)\n const needsRefresh = await shouldRefreshSession(sessionCookie, 24);\n\n if (needsRefresh)\n {\n authLogger.interceptor.general.debug('Session needs refresh (within 24h of expiry)');\n // Mark for session renewal in response interceptor\n ctx.metadata.refreshSession = true;\n ctx.metadata.sessionData = session;\n }\n\n // Generate JWT token\n const token = generateClientToken(\n {\n userId: session.userId,\n keyId: session.keyId,\n timestamp: Date.now(),\n },\n session.privateKey,\n session.algorithm,\n { expiresIn: '15m' }\n );\n\n authLogger.interceptor.general.debug('Generated JWT token (expires in 15m)');\n\n // Add authentication headers\n ctx.headers['Authorization'] = `Bearer ${token}`;\n ctx.headers['X-Key-Id'] = session.keyId;\n\n // Store session info in metadata\n ctx.metadata.userId = session.userId;\n ctx.metadata.sessionValid = true;\n }\n catch (error)\n {\n const err = error as Error;\n\n // Session expired or invalid\n if (err.message.includes('expired') || err.message.includes('invalid'))\n {\n authLogger.interceptor.general.warn('Session expired or invalid', { message: err.message });\n authLogger.interceptor.general.debug('Marking session for cleanup');\n\n // Mark for cleanup in response interceptor\n ctx.metadata.clearSession = true;\n ctx.metadata.sessionValid = false;\n }\n else\n {\n authLogger.interceptor.general.error('Failed to process session', err);\n }\n }\n\n await next();\n },\n\n response: async (ctx, next) =>\n {\n // Clear expired/invalid session\n if (ctx.metadata.clearSession)\n {\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION,\n value: '',\n options: {\n maxAge: 0,\n path: '/',\n },\n });\n\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION_KEY_ID,\n value: '',\n options: {\n maxAge: 0,\n path: '/',\n },\n });\n }\n // Refresh session if needed and request was successful\n else if (ctx.metadata.refreshSession && ctx.response.status === 200)\n {\n try\n {\n const sessionData = ctx.metadata.sessionData;\n const ttl = getSessionTtl();\n\n // Re-encrypt session with new TTL\n const sealed = await sealSession(sessionData, ttl);\n\n // Update session cookie\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION,\n value: sealed,\n options: {\n httpOnly: true,\n secure: env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n\n // Update keyId cookie\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION_KEY_ID,\n value: sessionData.keyId,\n options: {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n\n authLogger.interceptor.general.info('Session refreshed', { userId: sessionData.userId });\n }\n catch (error)\n {\n const err = error as Error;\n authLogger.interceptor.general.error('Failed to refresh session', err);\n }\n }\n // Handle logout (clear session)\n else if (ctx.path === '/_auth/logout' && ctx.response.ok)\n {\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION,\n value: '',\n options: {\n maxAge: 0,\n path: '/',\n },\n });\n\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION_KEY_ID,\n value: '',\n options: {\n maxAge: 0,\n path: '/',\n },\n });\n }\n\n await next();\n },\n};","/**\n * Key Rotation Interceptor\n *\n * Handles key rotation with new key generation and session update\n */\n\nimport type { InterceptorRule } from '@spfn/core/nextjs';\nimport { generateKeyPair, unsealSession, sealSession, generateClientToken, getSessionTtl, COOKIE_NAMES, authLogger } from '@spfn/auth/server';\n\n/**\n * Key Rotation Interceptor\n *\n * Request: Generates new key pair and adds to body, authenticates with current key\n * Response: Updates session with new privateKey\n */\nexport const keyRotationInterceptor: InterceptorRule =\n{\n pathPattern: '/_auth/keys/rotate',\n method: 'POST',\n\n request: async (ctx, next) =>\n {\n const sessionCookie = ctx.cookies.get(COOKIE_NAMES.SESSION);\n\n if (!sessionCookie)\n {\n await next();\n return;\n }\n\n try\n {\n // Get current session\n const currentSession = await unsealSession(sessionCookie);\n\n // Generate new key pair\n const newKeyPair = generateKeyPair('ES256');\n\n // Add new publicKey to request body\n if (!ctx.body)\n {\n ctx.body = {};\n }\n\n ctx.body.publicKey = newKeyPair.publicKey;\n ctx.body.keyId = newKeyPair.keyId;\n ctx.body.fingerprint = newKeyPair.fingerprint;\n ctx.body.algorithm = newKeyPair.algorithm;\n ctx.body.keySize = Buffer.from(newKeyPair.publicKey, 'base64').length;\n\n console.log('New key generated:', newKeyPair);\n console.log('publicKey:', newKeyPair.publicKey);\n console.log('keyId:', newKeyPair.keyId);\n console.log('fingerprint:', newKeyPair.fingerprint);\n\n // Authenticate with CURRENT key\n const token = generateClientToken(\n {\n userId: currentSession.userId,\n keyId: currentSession.keyId,\n action: 'rotate_key',\n timestamp: Date.now(),\n },\n currentSession.privateKey,\n currentSession.algorithm,\n {expiresIn: '15m'}\n );\n\n ctx.headers['Authorization'] = `Bearer ${token}`;\n ctx.headers['X-Key-Id'] = currentSession.keyId;\n\n // Store new key and userId in metadata\n ctx.metadata.newPrivateKey = newKeyPair.privateKey;\n ctx.metadata.newKeyId = newKeyPair.keyId;\n ctx.metadata.newAlgorithm = newKeyPair.algorithm;\n ctx.metadata.userId = currentSession.userId;\n }\n catch (error)\n {\n const err = error as Error;\n authLogger.interceptor.keyRotation.error('Failed to prepare key rotation', err);\n }\n\n await next();\n },\n\n response: async (ctx, next) =>\n {\n // Only update session on successful rotation\n if (ctx.response.status !== 200)\n {\n await next();\n return;\n }\n\n if (!ctx.metadata.newPrivateKey || !ctx.metadata.userId)\n {\n authLogger.interceptor.keyRotation.error('Missing key rotation metadata');\n await next();\n return;\n }\n\n try\n {\n // Get session TTL\n const ttl = getSessionTtl();\n\n // Create new session with rotated key\n const newSessionData =\n {\n userId: ctx.metadata.userId,\n privateKey: ctx.metadata.newPrivateKey,\n keyId: ctx.metadata.newKeyId,\n algorithm: ctx.metadata.newAlgorithm,\n };\n\n const sealed = await sealSession(newSessionData, ttl);\n\n // Update session cookie\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION,\n value: sealed,\n options: {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n\n // Update keyId cookie\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION_KEY_ID,\n value: ctx.metadata.newKeyId,\n options: {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n }\n catch (error)\n {\n const err = error as Error;\n authLogger.interceptor.keyRotation.error('Failed to update session after rotation', err);\n }\n\n await next();\n },\n};","/**\n * OAuth Interceptors\n *\n * 1. oauthUrlInterceptor: OAuth URL 요청 시 키쌍 생성 및 state 주입\n * 2. oauthFinalizeInterceptor: OAuth 완료 시 pending session에서 세션 저장\n */\n\nimport type { InterceptorRule } from '@spfn/core/nextjs';\nimport {\n generateKeyPair,\n createOAuthState,\n sealSession,\n COOKIE_NAMES,\n getSessionTtl,\n authLogger,\n} from '@spfn/auth/server';\nimport { sealPendingSession, unsealPendingSession } from '../session-helpers';\nimport { env } from '@spfn/core/config';\n\n/**\n * OAuth URL Interceptor\n *\n * POST /_auth/oauth/:provider/url 요청을 가로채서\n * 키쌍 생성 및 state 주입 처리\n */\nexport const oauthUrlInterceptor: InterceptorRule = {\n pathPattern: /^\\/_auth\\/oauth\\/\\w+\\/url$/,\n method: 'POST',\n\n request: async (ctx, next) =>\n {\n const provider = ctx.path.split('/')[3]; // google, github, etc.\n const returnUrl = ctx.body?.returnUrl || '/';\n\n // 키쌍 생성\n const keyPair = generateKeyPair('ES256');\n\n // state 생성 (publicKey 포함)\n const state = await createOAuthState({\n provider,\n returnUrl,\n publicKey: keyPair.publicKey,\n keyId: keyPair.keyId,\n fingerprint: keyPair.fingerprint,\n algorithm: keyPair.algorithm,\n });\n\n // body에 state 주입\n if (!ctx.body)\n {\n ctx.body = {};\n }\n ctx.body.state = state;\n\n // pending session 저장용 metadata\n ctx.metadata.pendingSession = {\n privateKey: keyPair.privateKey,\n keyId: keyPair.keyId,\n algorithm: keyPair.algorithm,\n };\n\n authLogger.interceptor.oauth?.debug?.('OAuth state created', {\n provider,\n keyId: keyPair.keyId,\n });\n\n await next();\n },\n\n response: async (ctx, next) =>\n {\n // 성공 응답이고 pending session이 있으면 쿠키 설정\n if (ctx.response.ok && ctx.metadata.pendingSession)\n {\n try\n {\n const sealed = await sealPendingSession(ctx.metadata.pendingSession);\n\n ctx.setCookies.push({\n name: COOKIE_NAMES.OAUTH_PENDING,\n value: sealed,\n options: {\n httpOnly: true,\n secure: env.NODE_ENV === 'production',\n sameSite: 'lax', // OAuth 리다이렉트 허용\n maxAge: 600, // 10분\n path: '/',\n },\n });\n\n authLogger.interceptor.oauth?.debug?.('Pending session cookie set', {\n keyId: ctx.metadata.pendingSession.keyId,\n });\n }\n catch (error)\n {\n const err = error as Error;\n authLogger.interceptor.oauth?.error?.('Failed to set pending session', err);\n }\n }\n\n await next();\n },\n};\n\n/**\n * OAuth Finalize Interceptor\n *\n * POST /_auth/oauth/finalize 요청을 가로채서\n * pending session에서 세션 저장\n */\nexport const oauthFinalizeInterceptor: InterceptorRule = {\n pathPattern: /^\\/_auth\\/oauth\\/finalize$/,\n method: 'POST',\n\n response: async (ctx, next) =>\n {\n // 성공 응답일 때만 처리\n if (!ctx.response.ok)\n {\n await next();\n return;\n }\n\n const pendingCookie = ctx.cookies.get(COOKIE_NAMES.OAUTH_PENDING);\n if (!pendingCookie)\n {\n authLogger.interceptor.oauth?.warn?.('No pending session cookie found');\n await next();\n return;\n }\n\n try\n {\n // pending session에서 privateKey 복원\n const pendingSession = await unsealPendingSession(pendingCookie);\n\n // body에서 userId, keyId 추출\n const { userId, keyId } = ctx.response.body || {};\n\n if (!userId || !keyId)\n {\n authLogger.interceptor.oauth?.error?.('Missing userId or keyId in response');\n await next();\n return;\n }\n\n // keyId 일치 확인\n if (pendingSession.keyId !== keyId)\n {\n authLogger.interceptor.oauth?.error?.('KeyId mismatch', {\n expected: pendingSession.keyId,\n received: keyId,\n });\n await next();\n return;\n }\n\n // 세션 생성\n const ttl = getSessionTtl();\n const sessionToken = await sealSession({\n userId,\n privateKey: pendingSession.privateKey,\n keyId: pendingSession.keyId,\n algorithm: pendingSession.algorithm,\n }, ttl);\n\n // 세션 쿠키 설정\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION,\n value: sessionToken,\n options: {\n httpOnly: true,\n secure: env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n\n // keyId 쿠키 설정\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION_KEY_ID,\n value: keyId,\n options: {\n httpOnly: true,\n secure: env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n\n // pending session 쿠키 삭제 (maxAge: 0)\n ctx.setCookies.push({\n name: COOKIE_NAMES.OAUTH_PENDING,\n value: '',\n options: {\n httpOnly: true,\n secure: env.NODE_ENV === 'production',\n sameSite: 'lax',\n maxAge: 0,\n path: '/',\n },\n });\n\n authLogger.interceptor.oauth?.debug?.('OAuth session finalized', {\n userId,\n keyId,\n });\n }\n catch (error)\n {\n const err = error as Error;\n authLogger.interceptor.oauth?.error?.('Failed to finalize OAuth session', err);\n }\n\n await next();\n },\n};\n","/**\n * Session helpers for Next.js\n *\n * Server-side only (uses next/headers)\n */\n\nimport * as jose from 'jose';\nimport { cookies } from 'next/headers.js';\nimport {\n sealSession,\n unsealSession,\n COOKIE_NAMES,\n getSessionTtl,\n parseDuration,\n type SessionData,\n type KeyAlgorithmType,\n} from '@spfn/auth/server';\nimport { env } from '@spfn/auth/config';\nimport { logger } from '@spfn/core/logger';\n\nexport type { SessionData };\n\n/**\n * Pending OAuth session data (before user ID is known)\n */\nexport interface PendingSessionData\n{\n privateKey: string;\n keyId: string;\n algorithm: KeyAlgorithmType;\n}\n\n/**\n * Public session information (excludes sensitive data)\n */\nexport interface PublicSession\n{\n /** User ID */\n userId: string;\n}\n\n/**\n * Options for saveSession\n */\nexport interface SaveSessionOptions\n{\n /**\n * Session TTL (time to live)\n *\n * Supports:\n * - Number: seconds (e.g., 2592000)\n * - String: duration format ('30d', '12h', '45m', '3600s')\n *\n * If not provided, uses global configuration:\n * 1. Global config (configureAuth)\n * 2. Environment variable (SPFN_AUTH_SESSION_TTL)\n * 3. Default (7d)\n */\n maxAge?: number | string;\n\n /**\n * Remember me option\n *\n * When true, uses extended session duration (if configured)\n */\n remember?: boolean;\n}\n\n/**\n * Save session to HttpOnly cookie\n *\n * @param data - Session data to save\n * @param options - Session options (maxAge, remember)\n *\n * @example\n * ```typescript\n * // Use global configuration\n * await saveSession(sessionData);\n *\n * // Custom TTL with duration string\n * await saveSession(sessionData, { maxAge: '30d' });\n *\n * // Custom TTL in seconds\n * await saveSession(sessionData, { maxAge: 2592000 });\n *\n * // Remember me\n * await saveSession(sessionData, { remember: true });\n * ```\n */\nexport async function saveSession(\n data: SessionData,\n options?: SaveSessionOptions\n): Promise<void>\n{\n // Calculate maxAge\n let maxAge: number;\n\n if (options?.maxAge !== undefined)\n {\n // Custom maxAge provided\n maxAge = typeof options.maxAge === 'number'\n ? options.maxAge\n : parseDuration(options.maxAge);\n }\n else\n {\n // Use getSessionTtl for consistent configuration\n maxAge = getSessionTtl();\n }\n\n const token = await sealSession(data, maxAge);\n const cookieStore = await cookies();\n\n cookieStore.set(COOKIE_NAMES.SESSION, token, {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'strict',\n path: '/',\n maxAge\n });\n}\n\n/**\n * Get session from HttpOnly cookie\n *\n * Returns public session info only (excludes privateKey, algorithm, keyId)\n */\nexport async function getSession(): Promise<PublicSession | null>\n{\n const cookieStore = await cookies();\n const sessionCookie = cookieStore.get(COOKIE_NAMES.SESSION);\n\n if (!sessionCookie)\n {\n return null;\n }\n\n try\n {\n logger.debug('Validating session cookie', { cookie: sessionCookie.value });\n const session = await unsealSession(sessionCookie.value);\n // Return only public information\n return {\n userId: session.userId,\n };\n }\n catch (error)\n {\n // Session expired or invalid\n // Note: Cannot delete cookies in Server Components (read-only)\n // Use validateSessionMiddleware() in Next.js middleware for automatic cleanup\n logger.debug('Session validation failed', {\n error: error instanceof Error ? error.message : String(error)\n });\n\n return null;\n }\n}\n\n/**\n * Clear session cookie\n */\nexport async function clearSession(): Promise<void>\n{\n const cookieStore = await cookies();\n cookieStore.delete(COOKIE_NAMES.SESSION);\n cookieStore.delete(COOKIE_NAMES.SESSION_KEY_ID);\n}\n\n// ============================================================================\n// Pending OAuth Session (for OAuth flow)\n// ============================================================================\n\n/**\n * Get encryption key for pending session\n */\nasync function getPendingSessionKey(): Promise<Uint8Array>\n{\n const secret = env.SPFN_AUTH_SESSION_SECRET;\n const encoder = new TextEncoder();\n const data = encoder.encode(`oauth-pending:${secret}`);\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n return new Uint8Array(hashBuffer);\n}\n\n/**\n * Seal pending session data (for OAuth flow)\n *\n * @param data - Pending session data (privateKey, keyId, algorithm)\n * @param ttl - Time to live in seconds (default: 10 minutes)\n */\nexport async function sealPendingSession(\n data: PendingSessionData,\n ttl: number = 600\n): Promise<string>\n{\n const key = await getPendingSessionKey();\n\n return await new jose.EncryptJWT({ data })\n .setProtectedHeader({ alg: 'dir', enc: 'A256GCM' })\n .setIssuedAt()\n .setExpirationTime(`${ttl}s`)\n .setIssuer('spfn-auth')\n .setAudience('spfn-oauth')\n .encrypt(key);\n}\n\n/**\n * Unseal pending session data\n *\n * @param jwt - Encrypted pending session token\n */\nexport async function unsealPendingSession(jwt: string): Promise<PendingSessionData>\n{\n const key = await getPendingSessionKey();\n\n const { payload } = await jose.jwtDecrypt(jwt, key, {\n issuer: 'spfn-auth',\n audience: 'spfn-oauth',\n });\n\n return payload.data as PendingSessionData;\n}\n\n/**\n * Get pending session from cookie\n */\nexport async function getPendingSession(): Promise<PendingSessionData | null>\n{\n const cookieStore = await cookies();\n const pendingCookie = cookieStore.get(COOKIE_NAMES.OAUTH_PENDING);\n\n if (!pendingCookie)\n {\n return null;\n }\n\n try\n {\n return await unsealPendingSession(pendingCookie.value);\n }\n catch (error)\n {\n logger.debug('Pending session validation failed', {\n error: error instanceof Error ? error.message : String(error),\n });\n return null;\n }\n}\n\n/**\n * Clear pending session cookie\n */\nexport async function clearPendingSession(): Promise<void>\n{\n const cookieStore = await cookies();\n cookieStore.delete(COOKIE_NAMES.OAUTH_PENDING);\n}\n","/**\n * Auth Interceptors for Next.js Proxy\n *\n * Automatically registers interceptors for authentication flow\n *\n * Order matters - more specific interceptors first:\n * 1. loginRegisterInterceptor - Most specific (login/register only)\n * 2. keyRotationInterceptor - Specific (key rotation only)\n * 3. oauthUrlInterceptor - OAuth URL generation (key generation + state injection)\n * 4. generalAuthInterceptor - General (all authenticated requests)\n */\n\nimport { loginRegisterInterceptor } from './login-register';\nimport { generalAuthInterceptor } from './general-auth';\nimport { keyRotationInterceptor } from './key-rotation';\nimport { oauthUrlInterceptor, oauthFinalizeInterceptor } from './oauth';\n\n/**\n * All auth interceptors\n *\n * Execution order:\n * 1. loginRegisterInterceptor - Handles login/register (key generation + session save)\n * 2. keyRotationInterceptor - Handles key rotation (new key generation + session update)\n * 3. oauthUrlInterceptor - Handles OAuth URL requests (key generation + state injection + pending session)\n * 4. oauthFinalizeInterceptor - Handles OAuth finalize (pending session → full session)\n * 5. generalAuthInterceptor - Handles all authenticated requests (session validation + JWT injection + session renewal)\n */\nexport const authInterceptors = [\n loginRegisterInterceptor,\n keyRotationInterceptor,\n oauthUrlInterceptor,\n oauthFinalizeInterceptor,\n generalAuthInterceptor,\n];\n\nexport { loginRegisterInterceptor } from './login-register';\nexport { generalAuthInterceptor } from './general-auth';\nexport { keyRotationInterceptor } from './key-rotation';\nexport { oauthUrlInterceptor, oauthFinalizeInterceptor } from './oauth';\n\n// Deprecated: use generalAuthInterceptor instead\nexport { generalAuthInterceptor as authenticationInterceptor };"],"mappings":"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","names":["sealSession","getSessionTtl","COOKIE_NAMES","authLogger","env","generateKeyPair","unsealSession","sealSession","generateClientToken","getSessionTtl","COOKIE_NAMES","authLogger","generateKeyPair","sealSession","COOKIE_NAMES","getSessionTtl","authLogger","sealSession","unsealSession","COOKIE_NAMES","getSessionTtl","env","env","env","generateKeyPair","authLogger","COOKIE_NAMES","getSessionTtl","sealSession"]}
|
|
1
|
+
{"version":3,"sources":["../../src/nextjs/api.ts","../../src/nextjs/interceptors/login-register.ts","../../src/nextjs/interceptors/general-auth.ts","../../src/nextjs/interceptors/key-rotation.ts","../../src/nextjs/interceptors/oauth.ts","../../src/nextjs/session-helpers.ts","../../src/nextjs/interceptors/index.ts"],"sourcesContent":["/**\n * @spfn/auth/adapters/nextjs/api\n *\n * Next.js Adapter for SPFN Auth\n *\n * Provides automatic interceptor registration for seamless auth flow:\n * - Session management (HttpOnly cookies)\n * - JWT generation and signing\n * - Public key encryption\n *\n * @requires next >= 13.0.0\n *\n * @example\n * ```typescript\n * // Just import to auto-register interceptors\n * import '@spfn/auth/nextjs/api';\n * ```\n */\n\n// Re-export interceptors for advanced usage\nimport { registerInterceptors } from \"@spfn/core/nextjs/server\";\nimport { authInterceptors } from './interceptors';\n\n// Auto-register interceptors on import\nregisterInterceptors('auth', authInterceptors);","/**\n * Login/Register Interceptor\n *\n * Automatically handles key generation and session management\n * for login and register endpoints\n */\n\nimport type { InterceptorRule } from '@spfn/core/nextjs/server';\nimport { generateKeyPair, sealSession, getSessionTtl, COOKIE_NAMES, authLogger } from '@spfn/auth/server';\nimport { env } from '@spfn/core/config';\n\n/**\n * Login and Register Interceptor\n *\n * Request: Generates key pair and adds publicKey to request body\n * Response: Saves privateKey to HttpOnly cookie\n */\nexport const loginRegisterInterceptor: InterceptorRule =\n {\n pathPattern: /^\\/_auth\\/(login|register)$/,\n method: 'POST',\n\n request: async (ctx, next) =>\n {\n // Get old session if exists (for key rotation on login)\n const oldKeyId = ctx.cookies.get(COOKIE_NAMES.SESSION_KEY_ID);\n\n // Extract remember option from request body (if provided)\n const remember = ctx.body?.remember;\n\n // Generate new key pair\n const keyPair = generateKeyPair('ES256');\n\n // Add publicKey data to request body\n if (!ctx.body)\n {\n ctx.body = {};\n }\n\n ctx.body.publicKey = keyPair.publicKey;\n ctx.body.keyId = keyPair.keyId;\n ctx.body.fingerprint = keyPair.fingerprint;\n ctx.body.algorithm = keyPair.algorithm;\n ctx.body.keySize = Buffer.from(keyPair.publicKey, 'base64').length;\n\n // Add oldKeyId for login (key rotation)\n if (ctx.path === '/_auth/login' && oldKeyId)\n {\n ctx.body.oldKeyId = oldKeyId;\n }\n\n // Remove remember from body (not part of contract)\n delete ctx.body.remember;\n\n // Store privateKey and remember in metadata for response interceptor\n ctx.metadata.privateKey = keyPair.privateKey;\n ctx.metadata.keyId = keyPair.keyId;\n ctx.metadata.algorithm = keyPair.algorithm;\n ctx.metadata.remember = remember;\n\n await next();\n },\n\n response: async (ctx, next) =>\n {\n // Only process successful responses\n if (ctx.response.status !== 200)\n {\n await next();\n return;\n }\n\n // Handle both wrapped ({ data: { userId } }) and direct ({ userId }) responses\n const userData = ctx.response.body?.data || ctx.response.body;\n if (!userData?.userId)\n {\n authLogger.interceptor.login.error('No userId in response');\n await next();\n return;\n }\n\n try\n {\n // Get session TTL (priority: runtime > global > env > default)\n const ttl = getSessionTtl(ctx.metadata.remember);\n\n // Encrypt session data\n const sessionData =\n {\n userId: userData.userId,\n privateKey: ctx.metadata.privateKey,\n keyId: ctx.metadata.keyId,\n algorithm: ctx.metadata.algorithm,\n };\n\n const sealed = await sealSession(sessionData, ttl);\n\n // Set HttpOnly session cookie\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION,\n value: sealed,\n options: {\n httpOnly: true,\n secure: env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n\n // Set keyId cookie (for oldKeyId lookup)\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION_KEY_ID,\n value: ctx.metadata.keyId,\n options: {\n httpOnly: true,\n secure: env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n }\n catch (error)\n {\n const err = error as Error;\n authLogger.interceptor.login.error('Failed to save session', err);\n }\n\n await next();\n },\n };","/**\n * General Authentication Interceptor\n *\n * Handles authentication for all API requests except login/register\n * - Session validation and renewal\n * - JWT generation and signing\n * - Expired session cleanup\n */\n\nimport type { InterceptorRule } from '@spfn/core/nextjs/server';\nimport { unsealSession, sealSession, shouldRefreshSession, generateClientToken, getSessionTtl, COOKIE_NAMES, authLogger } from '@spfn/auth/server';\nimport { env } from '@spfn/core/config';\n\n/**\n * Check if path requires authentication\n */\nfunction requiresAuth(path: string): boolean\n{\n // Paths that don't require auth\n const publicPaths = [\n /^\\/_auth\\/login$/,\n /^\\/_auth\\/register$/,\n /^\\/_auth\\/codes$/, // Send verification code\n /^\\/_auth\\/codes\\/verify$/, // Verify code\n /^\\/_auth\\/exists$/, // Check account exists\n ];\n\n return !publicPaths.some((pattern) => pattern.test(path));\n}\n\n/**\n * General Authentication Interceptor\n *\n * Applies to all paths except login/register/codes\n * - Validates session\n * - Generates JWT token\n * - Refreshes session if needed\n * - Clears expired sessions\n */\nexport const generalAuthInterceptor: InterceptorRule =\n{\n pathPattern: '*', // Match all paths, filter by requiresAuth()\n method: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],\n\n request: async (ctx, next) =>\n {\n // Skip if path doesn't require auth\n if (!requiresAuth(ctx.path))\n {\n authLogger.interceptor.general.debug(`Public path, skipping auth: ${ctx.path}`);\n await next();\n return;\n }\n\n // Log available cookies\n const cookieNames = Array.from(ctx.cookies.keys());\n authLogger.interceptor.general.debug('Available cookies:', {\n cookieNames,\n totalCount: cookieNames.length,\n lookingFor: COOKIE_NAMES.SESSION,\n });\n\n const sessionCookie = ctx.cookies.get(COOKIE_NAMES.SESSION);\n\n authLogger.interceptor.general.debug('Request', {\n method: ctx.method,\n path: ctx.path,\n hasSession: !!sessionCookie,\n sessionCookieValue: sessionCookie ? '***EXISTS***' : 'NOT_FOUND',\n });\n\n // No session cookie\n if (!sessionCookie)\n {\n authLogger.interceptor.general.debug('No session cookie, proceeding without auth');\n // Let request proceed - server will return 401\n await next();\n return;\n }\n\n try\n {\n // Decrypt and validate session\n const session = await unsealSession(sessionCookie);\n\n authLogger.interceptor.general.debug('Session valid', {\n userId: session.userId,\n keyId: session.keyId,\n });\n\n // Check if session should be refreshed (within 24h of expiry)\n const needsRefresh = await shouldRefreshSession(sessionCookie, 24);\n\n if (needsRefresh)\n {\n authLogger.interceptor.general.debug('Session needs refresh (within 24h of expiry)');\n // Mark for session renewal in response interceptor\n ctx.metadata.refreshSession = true;\n ctx.metadata.sessionData = session;\n }\n\n // Generate JWT token\n const token = generateClientToken(\n {\n userId: session.userId,\n keyId: session.keyId,\n timestamp: Date.now(),\n },\n session.privateKey,\n session.algorithm,\n { expiresIn: '15m' }\n );\n\n authLogger.interceptor.general.debug('Generated JWT token (expires in 15m)');\n\n // Add authentication headers\n ctx.headers['Authorization'] = `Bearer ${token}`;\n ctx.headers['X-Key-Id'] = session.keyId;\n\n // Store session info in metadata\n ctx.metadata.userId = session.userId;\n ctx.metadata.sessionValid = true;\n }\n catch (error)\n {\n const err = error as Error;\n\n // Session expired or invalid\n if (err.message.includes('expired') || err.message.includes('invalid'))\n {\n authLogger.interceptor.general.warn('Session expired or invalid', { message: err.message });\n authLogger.interceptor.general.debug('Marking session for cleanup');\n\n // Mark for cleanup in response interceptor\n ctx.metadata.clearSession = true;\n ctx.metadata.sessionValid = false;\n }\n else\n {\n authLogger.interceptor.general.error('Failed to process session', err);\n }\n }\n\n await next();\n },\n\n response: async (ctx, next) =>\n {\n // Clear expired/invalid session\n if (ctx.metadata.clearSession)\n {\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION,\n value: '',\n options: {\n maxAge: 0,\n path: '/',\n },\n });\n\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION_KEY_ID,\n value: '',\n options: {\n maxAge: 0,\n path: '/',\n },\n });\n }\n // Refresh session if needed and request was successful\n else if (ctx.metadata.refreshSession && ctx.response.status === 200)\n {\n try\n {\n const sessionData = ctx.metadata.sessionData;\n const ttl = getSessionTtl();\n\n // Re-encrypt session with new TTL\n const sealed = await sealSession(sessionData, ttl);\n\n // Update session cookie\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION,\n value: sealed,\n options: {\n httpOnly: true,\n secure: env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n\n // Update keyId cookie\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION_KEY_ID,\n value: sessionData.keyId,\n options: {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n\n authLogger.interceptor.general.info('Session refreshed', { userId: sessionData.userId });\n }\n catch (error)\n {\n const err = error as Error;\n authLogger.interceptor.general.error('Failed to refresh session', err);\n }\n }\n // Handle logout (clear session)\n else if (ctx.path === '/_auth/logout' && ctx.response.ok)\n {\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION,\n value: '',\n options: {\n maxAge: 0,\n path: '/',\n },\n });\n\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION_KEY_ID,\n value: '',\n options: {\n maxAge: 0,\n path: '/',\n },\n });\n }\n\n await next();\n },\n};","/**\n * Key Rotation Interceptor\n *\n * Handles key rotation with new key generation and session update\n */\n\nimport type { InterceptorRule } from '@spfn/core/nextjs/server';\nimport { generateKeyPair, unsealSession, sealSession, generateClientToken, getSessionTtl, COOKIE_NAMES, authLogger } from '@spfn/auth/server';\n\n/**\n * Key Rotation Interceptor\n *\n * Request: Generates new key pair and adds to body, authenticates with current key\n * Response: Updates session with new privateKey\n */\nexport const keyRotationInterceptor: InterceptorRule =\n{\n pathPattern: '/_auth/keys/rotate',\n method: 'POST',\n\n request: async (ctx, next) =>\n {\n const sessionCookie = ctx.cookies.get(COOKIE_NAMES.SESSION);\n\n if (!sessionCookie)\n {\n await next();\n return;\n }\n\n try\n {\n // Get current session\n const currentSession = await unsealSession(sessionCookie);\n\n // Generate new key pair\n const newKeyPair = generateKeyPair('ES256');\n\n // Add new publicKey to request body\n if (!ctx.body)\n {\n ctx.body = {};\n }\n\n ctx.body.publicKey = newKeyPair.publicKey;\n ctx.body.keyId = newKeyPair.keyId;\n ctx.body.fingerprint = newKeyPair.fingerprint;\n ctx.body.algorithm = newKeyPair.algorithm;\n ctx.body.keySize = Buffer.from(newKeyPair.publicKey, 'base64').length;\n\n console.log('New key generated:', newKeyPair);\n console.log('publicKey:', newKeyPair.publicKey);\n console.log('keyId:', newKeyPair.keyId);\n console.log('fingerprint:', newKeyPair.fingerprint);\n\n // Authenticate with CURRENT key\n const token = generateClientToken(\n {\n userId: currentSession.userId,\n keyId: currentSession.keyId,\n action: 'rotate_key',\n timestamp: Date.now(),\n },\n currentSession.privateKey,\n currentSession.algorithm,\n {expiresIn: '15m'}\n );\n\n ctx.headers['Authorization'] = `Bearer ${token}`;\n ctx.headers['X-Key-Id'] = currentSession.keyId;\n\n // Store new key and userId in metadata\n ctx.metadata.newPrivateKey = newKeyPair.privateKey;\n ctx.metadata.newKeyId = newKeyPair.keyId;\n ctx.metadata.newAlgorithm = newKeyPair.algorithm;\n ctx.metadata.userId = currentSession.userId;\n }\n catch (error)\n {\n const err = error as Error;\n authLogger.interceptor.keyRotation.error('Failed to prepare key rotation', err);\n }\n\n await next();\n },\n\n response: async (ctx, next) =>\n {\n // Only update session on successful rotation\n if (ctx.response.status !== 200)\n {\n await next();\n return;\n }\n\n if (!ctx.metadata.newPrivateKey || !ctx.metadata.userId)\n {\n authLogger.interceptor.keyRotation.error('Missing key rotation metadata');\n await next();\n return;\n }\n\n try\n {\n // Get session TTL\n const ttl = getSessionTtl();\n\n // Create new session with rotated key\n const newSessionData =\n {\n userId: ctx.metadata.userId,\n privateKey: ctx.metadata.newPrivateKey,\n keyId: ctx.metadata.newKeyId,\n algorithm: ctx.metadata.newAlgorithm,\n };\n\n const sealed = await sealSession(newSessionData, ttl);\n\n // Update session cookie\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION,\n value: sealed,\n options: {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n\n // Update keyId cookie\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION_KEY_ID,\n value: ctx.metadata.newKeyId,\n options: {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n }\n catch (error)\n {\n const err = error as Error;\n authLogger.interceptor.keyRotation.error('Failed to update session after rotation', err);\n }\n\n await next();\n },\n};","/**\n * OAuth Interceptors\n *\n * 1. oauthUrlInterceptor: OAuth URL 요청 시 키쌍 생성 및 state 주입\n * 2. oauthFinalizeInterceptor: OAuth 완료 시 pending session에서 세션 저장\n */\n\nimport type { InterceptorRule } from '@spfn/core/nextjs/server';\nimport {\n generateKeyPair,\n createOAuthState,\n sealSession,\n COOKIE_NAMES,\n getSessionTtl,\n authLogger,\n} from '@spfn/auth/server';\nimport { sealPendingSession, unsealPendingSession } from '../session-helpers';\nimport { env } from '@spfn/core/config';\n\n/**\n * OAuth URL Interceptor\n *\n * POST /_auth/oauth/:provider/url 요청을 가로채서\n * 키쌍 생성 및 state 주입 처리\n */\nexport const oauthUrlInterceptor: InterceptorRule = {\n pathPattern: /^\\/_auth\\/oauth\\/\\w+\\/url$/,\n method: 'POST',\n\n request: async (ctx, next) =>\n {\n const provider = ctx.path.split('/')[3]; // google, github, etc.\n const returnUrl = ctx.body?.returnUrl || '/';\n\n // 키쌍 생성\n const keyPair = generateKeyPair('ES256');\n\n // state 생성 (publicKey 포함)\n const state = await createOAuthState({\n provider,\n returnUrl,\n publicKey: keyPair.publicKey,\n keyId: keyPair.keyId,\n fingerprint: keyPair.fingerprint,\n algorithm: keyPair.algorithm,\n });\n\n // body에 state 주입\n if (!ctx.body)\n {\n ctx.body = {};\n }\n ctx.body.state = state;\n\n // pending session 저장용 metadata\n ctx.metadata.pendingSession = {\n privateKey: keyPair.privateKey,\n keyId: keyPair.keyId,\n algorithm: keyPair.algorithm,\n };\n\n authLogger.interceptor.oauth?.debug?.('OAuth state created', {\n provider,\n keyId: keyPair.keyId,\n });\n\n await next();\n },\n\n response: async (ctx, next) =>\n {\n // 성공 응답이고 pending session이 있으면 쿠키 설정\n if (ctx.response.ok && ctx.metadata.pendingSession)\n {\n try\n {\n const sealed = await sealPendingSession(ctx.metadata.pendingSession);\n\n ctx.setCookies.push({\n name: COOKIE_NAMES.OAUTH_PENDING,\n value: sealed,\n options: {\n httpOnly: true,\n secure: env.NODE_ENV === 'production',\n sameSite: 'lax', // OAuth 리다이렉트 허용\n maxAge: 600, // 10분\n path: '/',\n },\n });\n\n authLogger.interceptor.oauth?.debug?.('Pending session cookie set', {\n keyId: ctx.metadata.pendingSession.keyId,\n });\n }\n catch (error)\n {\n const err = error as Error;\n authLogger.interceptor.oauth?.error?.('Failed to set pending session', err);\n }\n }\n\n await next();\n },\n};\n\n/**\n * OAuth Finalize Interceptor\n *\n * POST /_auth/oauth/finalize 요청을 가로채서\n * pending session에서 세션 저장\n */\nexport const oauthFinalizeInterceptor: InterceptorRule = {\n pathPattern: /^\\/_auth\\/oauth\\/finalize$/,\n method: 'POST',\n\n response: async (ctx, next) =>\n {\n // 성공 응답일 때만 처리\n if (!ctx.response.ok)\n {\n await next();\n return;\n }\n\n const pendingCookie = ctx.cookies.get(COOKIE_NAMES.OAUTH_PENDING);\n if (!pendingCookie)\n {\n authLogger.interceptor.oauth?.warn?.('No pending session cookie found');\n await next();\n return;\n }\n\n try\n {\n // pending session에서 privateKey 복원\n const pendingSession = await unsealPendingSession(pendingCookie);\n\n // body에서 userId, keyId 추출\n const { userId, keyId } = ctx.response.body || {};\n\n if (!userId || !keyId)\n {\n authLogger.interceptor.oauth?.error?.('Missing userId or keyId in response');\n await next();\n return;\n }\n\n // keyId 일치 확인\n if (pendingSession.keyId !== keyId)\n {\n authLogger.interceptor.oauth?.error?.('KeyId mismatch', {\n expected: pendingSession.keyId,\n received: keyId,\n });\n await next();\n return;\n }\n\n // 세션 생성\n const ttl = getSessionTtl();\n const sessionToken = await sealSession({\n userId,\n privateKey: pendingSession.privateKey,\n keyId: pendingSession.keyId,\n algorithm: pendingSession.algorithm,\n }, ttl);\n\n // 세션 쿠키 설정\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION,\n value: sessionToken,\n options: {\n httpOnly: true,\n secure: env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n\n // keyId 쿠키 설정\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION_KEY_ID,\n value: keyId,\n options: {\n httpOnly: true,\n secure: env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n\n // pending session 쿠키 삭제 (maxAge: 0)\n ctx.setCookies.push({\n name: COOKIE_NAMES.OAUTH_PENDING,\n value: '',\n options: {\n httpOnly: true,\n secure: env.NODE_ENV === 'production',\n sameSite: 'lax',\n maxAge: 0,\n path: '/',\n },\n });\n\n authLogger.interceptor.oauth?.debug?.('OAuth session finalized', {\n userId,\n keyId,\n });\n }\n catch (error)\n {\n const err = error as Error;\n authLogger.interceptor.oauth?.error?.('Failed to finalize OAuth session', err);\n }\n\n await next();\n },\n};\n","/**\n * Session helpers for Next.js\n *\n * Server-side only (uses next/headers)\n */\n\nimport * as jose from 'jose';\nimport { cookies } from 'next/headers.js';\nimport {\n sealSession,\n unsealSession,\n COOKIE_NAMES,\n getSessionTtl,\n parseDuration,\n type SessionData,\n type KeyAlgorithmType,\n} from '@spfn/auth/server';\nimport { env } from '@spfn/auth/config';\nimport { logger } from '@spfn/core/logger';\n\nexport type { SessionData };\n\n/**\n * Pending OAuth session data (before user ID is known)\n */\nexport interface PendingSessionData\n{\n privateKey: string;\n keyId: string;\n algorithm: KeyAlgorithmType;\n}\n\n/**\n * Public session information (excludes sensitive data)\n */\nexport interface PublicSession\n{\n /** User ID */\n userId: string;\n}\n\n/**\n * Options for saveSession\n */\nexport interface SaveSessionOptions\n{\n /**\n * Session TTL (time to live)\n *\n * Supports:\n * - Number: seconds (e.g., 2592000)\n * - String: duration format ('30d', '12h', '45m', '3600s')\n *\n * If not provided, uses global configuration:\n * 1. Global config (configureAuth)\n * 2. Environment variable (SPFN_AUTH_SESSION_TTL)\n * 3. Default (7d)\n */\n maxAge?: number | string;\n\n /**\n * Remember me option\n *\n * When true, uses extended session duration (if configured)\n */\n remember?: boolean;\n}\n\n/**\n * Save session to HttpOnly cookie\n *\n * @param data - Session data to save\n * @param options - Session options (maxAge, remember)\n *\n * @example\n * ```typescript\n * // Use global configuration\n * await saveSession(sessionData);\n *\n * // Custom TTL with duration string\n * await saveSession(sessionData, { maxAge: '30d' });\n *\n * // Custom TTL in seconds\n * await saveSession(sessionData, { maxAge: 2592000 });\n *\n * // Remember me\n * await saveSession(sessionData, { remember: true });\n * ```\n */\nexport async function saveSession(\n data: SessionData,\n options?: SaveSessionOptions\n): Promise<void>\n{\n // Calculate maxAge\n let maxAge: number;\n\n if (options?.maxAge !== undefined)\n {\n // Custom maxAge provided\n maxAge = typeof options.maxAge === 'number'\n ? options.maxAge\n : parseDuration(options.maxAge);\n }\n else\n {\n // Use getSessionTtl for consistent configuration\n maxAge = getSessionTtl();\n }\n\n const token = await sealSession(data, maxAge);\n const cookieStore = await cookies();\n\n cookieStore.set(COOKIE_NAMES.SESSION, token, {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'strict',\n path: '/',\n maxAge\n });\n}\n\n/**\n * Get session from HttpOnly cookie\n *\n * Returns public session info only (excludes privateKey, algorithm, keyId)\n */\nexport async function getSession(): Promise<PublicSession | null>\n{\n const cookieStore = await cookies();\n const sessionCookie = cookieStore.get(COOKIE_NAMES.SESSION);\n\n if (!sessionCookie)\n {\n return null;\n }\n\n try\n {\n logger.debug('Validating session cookie', { cookie: sessionCookie.value });\n const session = await unsealSession(sessionCookie.value);\n // Return only public information\n return {\n userId: session.userId,\n };\n }\n catch (error)\n {\n // Session expired or invalid\n // Note: Cannot delete cookies in Server Components (read-only)\n // Use validateSessionMiddleware() in Next.js middleware for automatic cleanup\n logger.debug('Session validation failed', {\n error: error instanceof Error ? error.message : String(error)\n });\n\n return null;\n }\n}\n\n/**\n * Clear session cookie\n */\nexport async function clearSession(): Promise<void>\n{\n const cookieStore = await cookies();\n cookieStore.delete(COOKIE_NAMES.SESSION);\n cookieStore.delete(COOKIE_NAMES.SESSION_KEY_ID);\n}\n\n// ============================================================================\n// Pending OAuth Session (for OAuth flow)\n// ============================================================================\n\n/**\n * Get encryption key for pending session\n */\nasync function getPendingSessionKey(): Promise<Uint8Array>\n{\n const secret = env.SPFN_AUTH_SESSION_SECRET;\n const encoder = new TextEncoder();\n const data = encoder.encode(`oauth-pending:${secret}`);\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n return new Uint8Array(hashBuffer);\n}\n\n/**\n * Seal pending session data (for OAuth flow)\n *\n * @param data - Pending session data (privateKey, keyId, algorithm)\n * @param ttl - Time to live in seconds (default: 10 minutes)\n */\nexport async function sealPendingSession(\n data: PendingSessionData,\n ttl: number = 600\n): Promise<string>\n{\n const key = await getPendingSessionKey();\n\n return await new jose.EncryptJWT({ data })\n .setProtectedHeader({ alg: 'dir', enc: 'A256GCM' })\n .setIssuedAt()\n .setExpirationTime(`${ttl}s`)\n .setIssuer('spfn-auth')\n .setAudience('spfn-oauth')\n .encrypt(key);\n}\n\n/**\n * Unseal pending session data\n *\n * @param jwt - Encrypted pending session token\n */\nexport async function unsealPendingSession(jwt: string): Promise<PendingSessionData>\n{\n const key = await getPendingSessionKey();\n\n const { payload } = await jose.jwtDecrypt(jwt, key, {\n issuer: 'spfn-auth',\n audience: 'spfn-oauth',\n });\n\n return payload.data as PendingSessionData;\n}\n\n/**\n * Get pending session from cookie\n */\nexport async function getPendingSession(): Promise<PendingSessionData | null>\n{\n const cookieStore = await cookies();\n const pendingCookie = cookieStore.get(COOKIE_NAMES.OAUTH_PENDING);\n\n if (!pendingCookie)\n {\n return null;\n }\n\n try\n {\n return await unsealPendingSession(pendingCookie.value);\n }\n catch (error)\n {\n logger.debug('Pending session validation failed', {\n error: error instanceof Error ? error.message : String(error),\n });\n return null;\n }\n}\n\n/**\n * Clear pending session cookie\n */\nexport async function clearPendingSession(): Promise<void>\n{\n const cookieStore = await cookies();\n cookieStore.delete(COOKIE_NAMES.OAUTH_PENDING);\n}\n","/**\n * Auth Interceptors for Next.js Proxy\n *\n * Automatically registers interceptors for authentication flow\n *\n * Order matters - more specific interceptors first:\n * 1. loginRegisterInterceptor - Most specific (login/register only)\n * 2. keyRotationInterceptor - Specific (key rotation only)\n * 3. oauthUrlInterceptor - OAuth URL generation (key generation + state injection)\n * 4. generalAuthInterceptor - General (all authenticated requests)\n */\n\nimport { loginRegisterInterceptor } from './login-register';\nimport { generalAuthInterceptor } from './general-auth';\nimport { keyRotationInterceptor } from './key-rotation';\nimport { oauthUrlInterceptor, oauthFinalizeInterceptor } from './oauth';\n\n/**\n * All auth interceptors\n *\n * Execution order:\n * 1. loginRegisterInterceptor - Handles login/register (key generation + session save)\n * 2. keyRotationInterceptor - Handles key rotation (new key generation + session update)\n * 3. oauthUrlInterceptor - Handles OAuth URL requests (key generation + state injection + pending session)\n * 4. oauthFinalizeInterceptor - Handles OAuth finalize (pending session → full session)\n * 5. generalAuthInterceptor - Handles all authenticated requests (session validation + JWT injection + session renewal)\n */\nexport const authInterceptors = [\n loginRegisterInterceptor,\n keyRotationInterceptor,\n oauthUrlInterceptor,\n oauthFinalizeInterceptor,\n generalAuthInterceptor,\n];\n\nexport { loginRegisterInterceptor } from './login-register';\nexport { generalAuthInterceptor } from './general-auth';\nexport { keyRotationInterceptor } from './key-rotation';\nexport { oauthUrlInterceptor, oauthFinalizeInterceptor } from './oauth';\n\n// Deprecated: use generalAuthInterceptor instead\nexport { generalAuthInterceptor as authenticationInterceptor };"],"mappings":"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","names":["sealSession","getSessionTtl","COOKIE_NAMES","authLogger","env","generateKeyPair","unsealSession","sealSession","generateClientToken","getSessionTtl","COOKIE_NAMES","authLogger","generateKeyPair","sealSession","COOKIE_NAMES","getSessionTtl","authLogger","sealSession","unsealSession","COOKIE_NAMES","getSessionTtl","env","env","env","generateKeyPair","authLogger","COOKIE_NAMES","getSessionTtl","sealSession"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
interface OAuthCallbackProps {
|
|
4
|
+
/**
|
|
5
|
+
* API base path for RPC calls
|
|
6
|
+
* @default '/api/rpc'
|
|
7
|
+
*/
|
|
8
|
+
apiBasePath?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Custom loading component
|
|
11
|
+
*/
|
|
12
|
+
loadingComponent?: React.ReactNode;
|
|
13
|
+
/**
|
|
14
|
+
* Custom error component
|
|
15
|
+
*/
|
|
16
|
+
errorComponent?: (error: string) => React.ReactNode;
|
|
17
|
+
/**
|
|
18
|
+
* Callback after successful OAuth
|
|
19
|
+
*/
|
|
20
|
+
onSuccess?: (userId: string) => void;
|
|
21
|
+
/**
|
|
22
|
+
* Callback on error
|
|
23
|
+
*/
|
|
24
|
+
onError?: (error: string) => void;
|
|
25
|
+
}
|
|
26
|
+
declare function OAuthCallback({ apiBasePath, loadingComponent, errorComponent, onSuccess, onError, }: OAuthCallbackProps): react_jsx_runtime.JSX.Element | null;
|
|
27
|
+
|
|
28
|
+
export { OAuthCallback, type OAuthCallbackProps };
|
package/dist/server.d.ts
CHANGED
|
@@ -7,7 +7,8 @@ import { Context } from 'hono';
|
|
|
7
7
|
import * as _spfn_core_route from '@spfn/core/route';
|
|
8
8
|
import { Algorithm } from 'jsonwebtoken';
|
|
9
9
|
import * as _spfn_core_logger from '@spfn/core/logger';
|
|
10
|
-
import '@
|
|
10
|
+
import * as _spfn_core_event from '@spfn/core/event';
|
|
11
|
+
import * as _sinclair_typebox from '@sinclair/typebox';
|
|
11
12
|
import '@spfn/auth/server';
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -4993,6 +4994,7 @@ declare const authLogger: {
|
|
|
4993
4994
|
general: _spfn_core_logger.Logger;
|
|
4994
4995
|
login: _spfn_core_logger.Logger;
|
|
4995
4996
|
keyRotation: _spfn_core_logger.Logger;
|
|
4997
|
+
oauth: _spfn_core_logger.Logger;
|
|
4996
4998
|
};
|
|
4997
4999
|
service: _spfn_core_logger.Logger;
|
|
4998
5000
|
setup: _spfn_core_logger.Logger;
|
|
@@ -5076,4 +5078,61 @@ interface AuthLifecycleConfig {
|
|
|
5076
5078
|
*/
|
|
5077
5079
|
declare function createAuthLifecycle(options?: AuthInitOptions): AuthLifecycleConfig;
|
|
5078
5080
|
|
|
5079
|
-
|
|
5081
|
+
/**
|
|
5082
|
+
* @spfn/auth - Auth Events
|
|
5083
|
+
*
|
|
5084
|
+
* 인증 관련 이벤트 정의
|
|
5085
|
+
* - auth.login: 로그인 성공 시 (기존 사용자만)
|
|
5086
|
+
* - auth.register: 회원가입 성공 시 (OAuth 신규 가입 포함)
|
|
5087
|
+
*/
|
|
5088
|
+
/**
|
|
5089
|
+
* Auth provider type
|
|
5090
|
+
*/
|
|
5091
|
+
declare const AuthProviderSchema: _sinclair_typebox.TUnion<[_sinclair_typebox.TLiteral<"email">, _sinclair_typebox.TLiteral<"phone">, _sinclair_typebox.TLiteral<"google">]>;
|
|
5092
|
+
/**
|
|
5093
|
+
* auth.login - 로그인 성공 이벤트
|
|
5094
|
+
*
|
|
5095
|
+
* 발행 시점:
|
|
5096
|
+
* - 이메일/전화 로그인 성공 시
|
|
5097
|
+
* - OAuth 기존 사용자 로그인 시
|
|
5098
|
+
*
|
|
5099
|
+
* @example
|
|
5100
|
+
* ```typescript
|
|
5101
|
+
* authLoginEvent.subscribe(async (payload) => {
|
|
5102
|
+
* await analytics.trackLogin(payload.userId, payload.provider);
|
|
5103
|
+
* });
|
|
5104
|
+
* ```
|
|
5105
|
+
*/
|
|
5106
|
+
declare const authLoginEvent: _spfn_core_event.EventDef<{
|
|
5107
|
+
email?: string | undefined;
|
|
5108
|
+
phone?: string | undefined;
|
|
5109
|
+
userId: string;
|
|
5110
|
+
provider: "email" | "phone" | "google";
|
|
5111
|
+
}>;
|
|
5112
|
+
/**
|
|
5113
|
+
* auth.register - 회원가입 성공 이벤트
|
|
5114
|
+
*
|
|
5115
|
+
* 발행 시점:
|
|
5116
|
+
* - 이메일/전화 회원가입 성공 시
|
|
5117
|
+
* - OAuth 신규 사용자 가입 시
|
|
5118
|
+
*
|
|
5119
|
+
* @example
|
|
5120
|
+
* ```typescript
|
|
5121
|
+
* authRegisterEvent.subscribe(async (payload) => {
|
|
5122
|
+
* await emailService.sendWelcome(payload.email);
|
|
5123
|
+
* });
|
|
5124
|
+
* ```
|
|
5125
|
+
*/
|
|
5126
|
+
declare const authRegisterEvent: _spfn_core_event.EventDef<{
|
|
5127
|
+
email?: string | undefined;
|
|
5128
|
+
phone?: string | undefined;
|
|
5129
|
+
userId: string;
|
|
5130
|
+
provider: "email" | "phone" | "google";
|
|
5131
|
+
}>;
|
|
5132
|
+
/**
|
|
5133
|
+
* Auth event payload types
|
|
5134
|
+
*/
|
|
5135
|
+
type AuthLoginPayload = typeof authLoginEvent._payload;
|
|
5136
|
+
type AuthRegisterPayload = typeof authRegisterEvent._payload;
|
|
5137
|
+
|
|
5138
|
+
export { type AuthConfig, AuthContext, type AuthLoginPayload, AuthProviderSchema, type AuthRegisterPayload, COOKIE_NAMES, type CreateOAuthStateParams, type GoogleTokenResponse, type GoogleUserInfo, type Invitation, InvitationStatus, InvitationsRepository, KeyAlgorithmType, type KeyPair, KeysRepository, type NewInvitation, type NewPermission, type NewPermissionEntity, type NewRole, type NewRoleEntity, type NewRolePermission, type NewUser, type NewUserPermission, type NewUserProfile, type NewUserPublicKey, type NewUserSocialAccount, type NewVerificationCode, type OAuthState, type Permission, type PermissionEntity, PermissionsRepository, type Role, type RoleEntity, type RoleGuardOptions, type RolePermission, RolePermissionsRepository, RolesRepository, type SessionData, type SessionPayload, SocialAccountsRepository, SocialProvider, type TokenPayload, type UpdateProfileParams, type User, type UserPermission, UserPermissionsRepository, type UserProfile, UserProfilesRepository, type UserPublicKey, type UserSocialAccount, UsersRepository, type VerificationCode, VerificationCodesRepository, VerificationPurpose, acceptInvitation, addPermissionToRole, authLogger, authLoginEvent, authRegisterEvent, authSchema, cancelInvitation, configureAuth, createAuthLifecycle, createInvitation, createOAuthState, createRole, decodeToken, deleteInvitation, deleteRole, exchangeCodeForTokens, expireOldInvitations, generateClientToken, generateKeyPair, generateKeyPairES256, generateKeyPairRS256, generateToken, getAllRoles, getAuth, getAuthConfig, getAuthSessionService, getGoogleAuthUrl, getGoogleOAuthConfig, getGoogleUserInfo, getInvitationByToken, getInvitationWithDetails, getKeyId, getKeySize, getRoleByName, getRolePermissions, getSessionInfo, getSessionTtl, getUser, getUserByEmailService, getUserByIdService, getUserByPhoneService, getUserId, getUserPermissions, getUserProfileService, getUserRole, hasAllPermissions, hasAnyPermission, hasAnyRole, hasPermission, hasRole, hashPassword, initializeAuth, invitationsRepository, isGoogleOAuthEnabled, keysRepository, listInvitations, parseDuration, permissions, permissionsRepository, refreshAccessToken, removePermissionFromRole, requireAnyPermission, requirePermissions, requireRole, resendInvitation, roleGuard, rolePermissions, rolePermissionsRepository, roles, rolesRepository, sealSession, setRolePermissions, shouldRefreshSession, shouldRotateKey, socialAccountsRepository, unsealSession, updateLastLoginService, updateRole, updateUserProfileService, updateUserService, userInvitations, userPermissions, userPermissionsRepository, userProfiles, userProfilesRepository, userPublicKeys, userSocialAccounts, users, usersRepository, validateInvitation, validatePasswordStrength, verificationCodes, verificationCodesRepository, verifyClientToken, verifyKeyFingerprint, verifyOAuthState, verifyPassword, verifyToken };
|
package/dist/server.js
CHANGED
|
@@ -6799,7 +6799,8 @@ var authLogger = {
|
|
|
6799
6799
|
interceptor: {
|
|
6800
6800
|
general: rootLogger.child("@spfn/auth:interceptor:general"),
|
|
6801
6801
|
login: rootLogger.child("@spfn/auth:interceptor:login"),
|
|
6802
|
-
keyRotation: rootLogger.child("@spfn/auth:interceptor:key-rotation")
|
|
6802
|
+
keyRotation: rootLogger.child("@spfn/auth:interceptor:key-rotation"),
|
|
6803
|
+
oauth: rootLogger.child("@spfn/auth:interceptor:oauth")
|
|
6803
6804
|
},
|
|
6804
6805
|
service: rootLogger.child("@spfn/auth:service"),
|
|
6805
6806
|
setup: rootLogger.child("@spfn/auth:setup"),
|
|
@@ -7015,6 +7016,33 @@ async function updateUserService(userId, updates) {
|
|
|
7015
7016
|
await usersRepository.updateById(userId, updates);
|
|
7016
7017
|
}
|
|
7017
7018
|
|
|
7019
|
+
// src/server/events/index.ts
|
|
7020
|
+
init_esm();
|
|
7021
|
+
import { defineEvent } from "@spfn/core/event";
|
|
7022
|
+
var AuthProviderSchema = Type.Union([
|
|
7023
|
+
Type.Literal("email"),
|
|
7024
|
+
Type.Literal("phone"),
|
|
7025
|
+
Type.Literal("google")
|
|
7026
|
+
]);
|
|
7027
|
+
var authLoginEvent = defineEvent(
|
|
7028
|
+
"auth.login",
|
|
7029
|
+
Type.Object({
|
|
7030
|
+
userId: Type.String(),
|
|
7031
|
+
provider: AuthProviderSchema,
|
|
7032
|
+
email: Type.Optional(Type.String()),
|
|
7033
|
+
phone: Type.Optional(Type.String())
|
|
7034
|
+
})
|
|
7035
|
+
);
|
|
7036
|
+
var authRegisterEvent = defineEvent(
|
|
7037
|
+
"auth.register",
|
|
7038
|
+
Type.Object({
|
|
7039
|
+
userId: Type.String(),
|
|
7040
|
+
provider: AuthProviderSchema,
|
|
7041
|
+
email: Type.Optional(Type.String()),
|
|
7042
|
+
phone: Type.Optional(Type.String())
|
|
7043
|
+
})
|
|
7044
|
+
);
|
|
7045
|
+
|
|
7018
7046
|
// src/server/services/auth.service.ts
|
|
7019
7047
|
async function checkAccountExistsService(params) {
|
|
7020
7048
|
const { email, phone } = params;
|
|
@@ -7081,11 +7109,18 @@ async function registerService(params) {
|
|
|
7081
7109
|
fingerprint,
|
|
7082
7110
|
algorithm
|
|
7083
7111
|
});
|
|
7084
|
-
|
|
7112
|
+
const result = {
|
|
7085
7113
|
userId: String(newUser.id),
|
|
7086
7114
|
email: newUser.email || void 0,
|
|
7087
7115
|
phone: newUser.phone || void 0
|
|
7088
7116
|
};
|
|
7117
|
+
await authRegisterEvent.emit({
|
|
7118
|
+
userId: result.userId,
|
|
7119
|
+
provider: email ? "email" : "phone",
|
|
7120
|
+
email: result.email,
|
|
7121
|
+
phone: result.phone
|
|
7122
|
+
});
|
|
7123
|
+
return result;
|
|
7089
7124
|
}
|
|
7090
7125
|
async function loginService(params) {
|
|
7091
7126
|
const { email, phone, password, publicKey, keyId, fingerprint, oldKeyId, algorithm } = params;
|
|
@@ -7118,12 +7153,19 @@ async function loginService(params) {
|
|
|
7118
7153
|
algorithm
|
|
7119
7154
|
});
|
|
7120
7155
|
await updateLastLoginService(user.id);
|
|
7121
|
-
|
|
7156
|
+
const result = {
|
|
7122
7157
|
userId: String(user.id),
|
|
7123
7158
|
email: user.email || void 0,
|
|
7124
7159
|
phone: user.phone || void 0,
|
|
7125
7160
|
passwordChangeRequired: user.passwordChangeRequired
|
|
7126
7161
|
};
|
|
7162
|
+
await authLoginEvent.emit({
|
|
7163
|
+
userId: result.userId,
|
|
7164
|
+
provider: email ? "email" : "phone",
|
|
7165
|
+
email: result.email,
|
|
7166
|
+
phone: result.phone
|
|
7167
|
+
});
|
|
7168
|
+
return result;
|
|
7127
7169
|
}
|
|
7128
7170
|
async function logoutService(params) {
|
|
7129
7171
|
const { userId, keyId } = params;
|
|
@@ -7869,6 +7911,18 @@ async function handleGoogleCallback(code, stateData) {
|
|
|
7869
7911
|
returnUrl: stateData.returnUrl,
|
|
7870
7912
|
isNewUser: String(isNewUser)
|
|
7871
7913
|
});
|
|
7914
|
+
const user = await usersRepository.findById(userId);
|
|
7915
|
+
const eventPayload = {
|
|
7916
|
+
userId: String(userId),
|
|
7917
|
+
provider: "google",
|
|
7918
|
+
email: user?.email || void 0,
|
|
7919
|
+
phone: user?.phone || void 0
|
|
7920
|
+
};
|
|
7921
|
+
if (isNewUser) {
|
|
7922
|
+
await authRegisterEvent.emit(eventPayload);
|
|
7923
|
+
} else {
|
|
7924
|
+
await authLoginEvent.emit(eventPayload);
|
|
7925
|
+
}
|
|
7872
7926
|
return {
|
|
7873
7927
|
redirectUrl,
|
|
7874
7928
|
userId: String(userId),
|
|
@@ -9058,6 +9112,7 @@ function createAuthLifecycle(options = {}) {
|
|
|
9058
9112
|
};
|
|
9059
9113
|
}
|
|
9060
9114
|
export {
|
|
9115
|
+
AuthProviderSchema,
|
|
9061
9116
|
COOKIE_NAMES,
|
|
9062
9117
|
EmailSchema,
|
|
9063
9118
|
INVITATION_STATUSES,
|
|
@@ -9083,6 +9138,8 @@ export {
|
|
|
9083
9138
|
acceptInvitation,
|
|
9084
9139
|
addPermissionToRole,
|
|
9085
9140
|
authLogger,
|
|
9141
|
+
authLoginEvent,
|
|
9142
|
+
authRegisterEvent,
|
|
9086
9143
|
mainAuthRouter as authRouter,
|
|
9087
9144
|
authSchema,
|
|
9088
9145
|
authenticate,
|