@spfn/auth 0.2.0-beta.1 → 0.2.0-beta.11
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/README.md +169 -168
- package/dist/{dto-81uR9gzF.d.ts → authenticate-CU6_zQaa.d.ts} +184 -169
- package/dist/config.d.ts +4 -0
- package/dist/config.js +4 -0
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +146 -119
- package/dist/index.js +24 -1
- package/dist/index.js.map +1 -1
- package/dist/nextjs/api.js +1 -1
- package/dist/nextjs/api.js.map +1 -1
- package/dist/nextjs/server.js +0 -2
- package/dist/nextjs/server.js.map +1 -1
- package/dist/server.d.ts +171 -403
- package/dist/server.js +217 -461
- package/dist/server.js.map +1 -1
- package/migrations/0000_premium_famine.sql +292 -0
- package/migrations/meta/0000_snapshot.json +1 -1
- package/migrations/meta/_journal.json +2 -2
- package/package.json +8 -11
- package/migrations/0000_mysterious_colossus.sql +0 -197
package/dist/nextjs/api.js
CHANGED
|
@@ -213,7 +213,7 @@ var generalAuthInterceptor = {
|
|
|
213
213
|
const err = error;
|
|
214
214
|
authLogger2.interceptor.general.error("Failed to refresh session", err);
|
|
215
215
|
}
|
|
216
|
-
} else if (ctx.path === "/_auth/logout" && ctx.response.
|
|
216
|
+
} else if (ctx.path === "/_auth/logout" && ctx.response.ok) {
|
|
217
217
|
ctx.setCookies.push({
|
|
218
218
|
name: COOKIE_NAMES2.SESSION,
|
|
219
219
|
value: "",
|
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/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.status === 200)\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 * 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. generalAuthInterceptor - General (all authenticated requests)\n */\n\nimport { loginRegisterInterceptor } from './login-register';\nimport { generalAuthInterceptor } from './general-auth';\nimport { keyRotationInterceptor } from './key-rotation';\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. generalAuthInterceptor - Handles all authenticated requests (session validation + JWT injection + session renewal)\n */\nexport const authInterceptors = [\n loginRegisterInterceptor,\n keyRotationInterceptor,\n generalAuthInterceptor,\n];\n\nexport { loginRegisterInterceptor } from './login-register';\nexport { generalAuthInterceptor } from './general-auth';\nexport { keyRotationInterceptor } from './key-rotation';\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"]}
|
|
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/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 * 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. generalAuthInterceptor - General (all authenticated requests)\n */\n\nimport { loginRegisterInterceptor } from './login-register';\nimport { generalAuthInterceptor } from './general-auth';\nimport { keyRotationInterceptor } from './key-rotation';\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. generalAuthInterceptor - Handles all authenticated requests (session validation + JWT injection + session renewal)\n */\nexport const authInterceptors = [\n loginRegisterInterceptor,\n keyRotationInterceptor,\n generalAuthInterceptor,\n];\n\nexport { loginRegisterInterceptor } from './login-register';\nexport { generalAuthInterceptor } from './general-auth';\nexport { keyRotationInterceptor } from './key-rotation';\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"]}
|
package/dist/nextjs/server.js
CHANGED
|
@@ -38,7 +38,6 @@ async function getSession() {
|
|
|
38
38
|
userId: session.userId
|
|
39
39
|
};
|
|
40
40
|
} catch (error) {
|
|
41
|
-
console.error(error);
|
|
42
41
|
logger.debug("Session validation failed", {
|
|
43
42
|
error: error instanceof Error ? error.message : String(error)
|
|
44
43
|
});
|
|
@@ -59,7 +58,6 @@ async function RequireAuth({
|
|
|
59
58
|
fallback
|
|
60
59
|
}) {
|
|
61
60
|
const session = await getSession();
|
|
62
|
-
console.log("session", session);
|
|
63
61
|
if (!session) {
|
|
64
62
|
if (fallback) {
|
|
65
63
|
return /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/nextjs/server.ts","../../src/nextjs/guards/require-auth.tsx","../../src/nextjs/session-helpers.ts","../../src/nextjs/guards/require-role.tsx","../../src/nextjs/guards/auth-utils.ts","../../src/nextjs/guards/require-permission.tsx"],"sourcesContent":["import \"server-only\";\n\nexport { RequireAuth } from './guards/require-auth';\nexport type { RequireAuthProps } from './guards/require-auth';\n\nexport { RequireRole } from './guards/require-role';\nexport type { RequireRoleProps } from './guards/require-role';\n\nexport { RequirePermission } from './guards/require-permission';\nexport type { RequirePermissionProps } from './guards/require-permission';\n\nexport { getUserRole, getUserPermissions, hasAnyRole, hasAnyPermission } from './guards/auth-utils';\n\n// Session helpers\nexport {\n saveSession,\n getSession,\n clearSession,\n type SessionData,\n type PublicSession,\n type SaveSessionOptions\n} from './session-helpers';","/**\n * RequireAuth Guard Component\n *\n * Requires user to be authenticated\n */\n\nimport { redirect } from 'next/navigation';\nimport { getSession } from '../session-helpers';\nimport type { ReactNode } from 'react';\n\nexport interface RequireAuthProps\n{\n /**\n * Children to render if authenticated\n */\n children: ReactNode;\n\n /**\n * Path to redirect to if not authenticated\n * @default '/login'\n */\n redirectTo?: string;\n\n /**\n * Fallback UI to show instead of redirecting\n */\n fallback?: ReactNode;\n}\n\n/**\n * Require Authentication Guard\n *\n * Ensures user is logged in before rendering children\n *\n * @example\n * ```tsx\n * <RequireAuth redirectTo=\"/login\">\n * <DashboardContent />\n * </RequireAuth>\n * ```\n *\n * @example With fallback\n * ```tsx\n * <RequireAuth fallback={<LoginPrompt />}>\n * <PrivateContent />\n * </RequireAuth>\n * ```\n */\nexport async function RequireAuth({\n children,\n redirectTo = '/auth/login',\n fallback,\n}: RequireAuthProps)\n{\n const session = await getSession();\n console.log('session', session);\n\n if (!session)\n {\n if (fallback)\n {\n return <>{fallback}</>;\n }\n\n redirect(redirectTo);\n }\n\n return <>{children}</>;\n}","/**\n * Session helpers for Next.js\n *\n * Server-side only (uses next/headers)\n */\n\nimport { cookies } from 'next/headers.js';\nimport { sealSession, unsealSession, COOKIE_NAMES, getSessionTtl, parseDuration, type SessionData } from '@spfn/auth/server';\nimport { logger } from '@spfn/core/logger';\n\nexport type { SessionData };\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 console.error(error);\n\n // Session expired or invalid - log in dev mode\n // Note: Cannot delete cookies in Server Components (read-only)\n // Invalid cookies will be cleaned up on next login/logout via Route Handler or Server Action\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 * RequireRole Guard Component\n *\n * Requires user to have at least one of the specified roles\n */\n\nimport { redirect } from 'next/navigation';\nimport { getSession } from '../session-helpers';\nimport { hasAnyRole } from './auth-utils';\nimport type { ReactNode } from 'react';\n\nexport interface RequireRoleProps\n{\n /**\n * Required role(s) - user must have at least one\n */\n roles: string | string[];\n\n /**\n * Children to render if user has required role\n */\n children: ReactNode;\n\n /**\n * Path to redirect to if user doesn't have role\n * @default '/unauthorized'\n */\n redirectTo?: string;\n\n /**\n * Fallback UI to show instead of redirecting\n */\n fallback?: ReactNode;\n}\n\n/**\n * Require Role Guard\n *\n * Ensures user has at least one of the specified roles\n *\n * @example Single role\n * ```tsx\n * <RequireRole roles=\"admin\">\n * <AdminPanel />\n * </RequireRole>\n * ```\n *\n * @example Multiple roles (OR condition)\n * ```tsx\n * <RequireRole roles={['admin', 'manager']}>\n * <ManagementDashboard />\n * </RequireRole>\n * ```\n *\n * @example With fallback\n * ```tsx\n * <RequireRole roles=\"admin\" fallback={<AccessDenied />}>\n * <AdminContent />\n * </RequireRole>\n * ```\n */\nexport async function RequireRole({\n roles,\n children,\n redirectTo = '/unauthorized',\n fallback,\n}: RequireRoleProps)\n{\n const session = await getSession();\n\n // Not authenticated\n if (!session)\n {\n if (fallback)\n {\n return <>{fallback}</>;\n }\n\n redirect('/login');\n }\n\n // Normalize to array\n const requiredRoles = Array.isArray(roles) ? roles : [roles];\n\n // Check if user has any of the required roles\n const hasRole = await hasAnyRole(requiredRoles);\n\n if (!hasRole)\n {\n if (fallback)\n {\n return <>{fallback}</>;\n }\n\n redirect(redirectTo);\n }\n\n return <>{children}</>;\n}","/**\n * Server-side auth utilities for guards\n *\n * Uses authApi to check permissions in real-time\n */\n\nimport { authApi } from '@spfn/auth';\nimport { authLogger } from '@spfn/auth/server';\n\n/**\n * Get current auth session with roles and permissions via API\n */\nasync function getAuthSessionData()\n{\n try\n {\n const session = await authApi.getAuthSession.call();\n authLogger.middleware.debug('Auth session retrieved', { name: session.role?.name });\n\n return session;\n }\n catch (error)\n {\n authLogger.middleware.error('Failed to get auth session', { error });\n return null;\n }\n}\n\n/**\n * Get user role\n */\nexport async function getUserRole(): Promise<string | null>\n{\n const session = await getAuthSessionData();\n return session?.role?.name || null;\n}\n\n/**\n * Get user permissions\n */\nexport async function getUserPermissions(): Promise<string[]>\n{\n const session = await getAuthSessionData();\n\n if (!session)\n {\n return [];\n }\n\n return session.permissions?.map((p: any) => p.name) || [];\n}\n\n/**\n * Check if user has any of the specified roles\n */\nexport async function hasAnyRole(requiredRoles: string[]): Promise<boolean>\n{\n const session = await getAuthSessionData();\n if (!session)\n {\n return false;\n }\n\n return requiredRoles.includes(session.role?.name);\n}\n\n/**\n * Check if user has any of the specified permissions\n */\nexport async function hasAnyPermission(requiredPermissions: string[]): Promise<boolean>\n{\n const session = await getAuthSessionData();\n\n if (!session)\n {\n return false;\n }\n\n const userPermissionNames = session.permissions?.map((p: any) => p.name) || [];\n return requiredPermissions.some(permission => userPermissionNames.includes(permission));\n}\n","/**\n * RequirePermission Guard Component\n *\n * Requires user to have at least one of the specified permissions\n */\n\nimport { redirect } from 'next/navigation';\nimport { getSession } from '../session-helpers';\nimport { hasAnyPermission } from './auth-utils';\nimport type { ReactNode } from 'react';\n\nexport interface RequirePermissionProps\n{\n /**\n * Required permission(s) - user must have at least one\n */\n permissions: string | string[];\n\n /**\n * Children to render if user has required permission\n */\n children: ReactNode;\n\n /**\n * Path to redirect to if user doesn't have permission\n * @default '/unauthorized'\n */\n redirectTo?: string;\n\n /**\n * Fallback UI to show instead of redirecting\n */\n fallback?: ReactNode;\n}\n\n/**\n * Require Permission Guard\n *\n * Ensures user has at least one of the specified permissions\n *\n * @example Single permission\n * ```tsx\n * <RequirePermission permissions=\"user:delete\">\n * <DeleteUserButton />\n * </RequirePermission>\n * ```\n *\n * @example Multiple permissions (OR condition)\n * ```tsx\n * <RequirePermission permissions={['user:delete', 'user:update']}>\n * <UserManagement />\n * </RequirePermission>\n * ```\n *\n * @example With fallback\n * ```tsx\n * <RequirePermission permissions=\"project:create\" fallback={<UpgradePrompt />}>\n * <CreateProject />\n * </RequirePermission>\n * ```\n */\nexport async function RequirePermission({\n permissions,\n children,\n redirectTo = '/unauthorized',\n fallback,\n}: RequirePermissionProps)\n{\n const session = await getSession();\n\n // Not authenticated\n if (!session)\n {\n if (fallback)\n {\n return <>{fallback}</>;\n }\n\n redirect('/login');\n }\n\n // Normalize to array\n const requiredPermissions = Array.isArray(permissions) ? permissions : [permissions];\n\n // Check if user has any of the required permissions\n const hasPermission = await hasAnyPermission(requiredPermissions);\n\n if (!hasPermission)\n {\n if (fallback)\n {\n return <>{fallback}</>;\n }\n\n redirect(redirectTo);\n }\n\n return <>{children}</>;\n}"],"mappings":";AAAA,OAAO;;;ACMP,SAAS,gBAAgB;;;ACAzB,SAAS,eAAe;AACxB,SAAS,aAAa,eAAe,cAAc,eAAe,qBAAuC;AACzG,SAAS,cAAc;AA6DvB,eAAsB,YAClB,MACA,SAEJ;AAEI,MAAI;AAEJ,MAAI,SAAS,WAAW,QACxB;AAEI,aAAS,OAAO,QAAQ,WAAW,WAC7B,QAAQ,SACR,cAAc,QAAQ,MAAM;AAAA,EACtC,OAEA;AAEI,aAAS,cAAc;AAAA,EAC3B;AAEA,QAAM,QAAQ,MAAM,YAAY,MAAM,MAAM;AAC5C,QAAM,cAAc,MAAM,QAAQ;AAElC,cAAY,IAAI,aAAa,SAAS,OAAO;AAAA,IACzC,UAAU;AAAA,IACV,QAAQ,QAAQ,IAAI,aAAa;AAAA,IACjC,UAAU;AAAA,IACV,MAAM;AAAA,IACN;AAAA,EACJ,CAAC;AACL;AAOA,eAAsB,aACtB;AACI,QAAM,cAAc,MAAM,QAAQ;AAClC,QAAM,gBAAgB,YAAY,IAAI,aAAa,OAAO;AAE1D,MAAI,CAAC,eACL;AACI,WAAO;AAAA,EACX;AAEA,MACA;AACI,WAAO,MAAM,6BAA6B,EAAE,QAAQ,cAAc,MAAM,CAAC;AACzE,UAAM,UAAU,MAAM,cAAc,cAAc,KAAK;AAEvD,WAAO;AAAA,MACH,QAAQ,QAAQ;AAAA,IACpB;AAAA,EACJ,SACO,OACP;AACI,YAAQ,MAAM,KAAK;AAKnB,WAAO,MAAM,6BAA6B;AAAA,MACtC,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,IAChE,CAAC;AAED,WAAO;AAAA,EACX;AACJ;AAKA,eAAsB,eACtB;AACI,QAAM,cAAc,MAAM,QAAQ;AAClC,cAAY,OAAO,aAAa,OAAO;AACvC,cAAY,OAAO,aAAa,cAAc;AAClD;;;ADxFmB;AAbnB,eAAsB,YAAY;AAAA,EAC9B;AAAA,EACA,aAAa;AAAA,EACb;AACJ,GACA;AACI,QAAM,UAAU,MAAM,WAAW;AACjC,UAAQ,IAAI,WAAW,OAAO;AAE9B,MAAI,CAAC,SACL;AACI,QAAI,UACJ;AACI,aAAO,gCAAG,oBAAS;AAAA,IACvB;AAEA,aAAS,UAAU;AAAA,EACvB;AAEA,SAAO,gCAAG,UAAS;AACvB;;;AE9DA,SAAS,YAAAA,iBAAgB;;;ACAzB,SAAS,eAAe;AACxB,SAAS,kBAAkB;AAK3B,eAAe,qBACf;AACI,MACA;AACI,UAAM,UAAU,MAAM,QAAQ,eAAe,KAAK;AAClD,eAAW,WAAW,MAAM,0BAA0B,EAAE,MAAM,QAAQ,MAAM,KAAK,CAAC;AAElF,WAAO;AAAA,EACX,SACO,OACP;AACI,eAAW,WAAW,MAAM,8BAA8B,EAAE,MAAM,CAAC;AACnE,WAAO;AAAA,EACX;AACJ;AAKA,eAAsB,cACtB;AACI,QAAM,UAAU,MAAM,mBAAmB;AACzC,SAAO,SAAS,MAAM,QAAQ;AAClC;AAKA,eAAsB,qBACtB;AACI,QAAM,UAAU,MAAM,mBAAmB;AAEzC,MAAI,CAAC,SACL;AACI,WAAO,CAAC;AAAA,EACZ;AAEA,SAAO,QAAQ,aAAa,IAAI,CAAC,MAAW,EAAE,IAAI,KAAK,CAAC;AAC5D;AAKA,eAAsB,WAAW,eACjC;AACI,QAAM,UAAU,MAAM,mBAAmB;AACzC,MAAI,CAAC,SACL;AACI,WAAO;AAAA,EACX;AAEA,SAAO,cAAc,SAAS,QAAQ,MAAM,IAAI;AACpD;AAKA,eAAsB,iBAAiB,qBACvC;AACI,QAAM,UAAU,MAAM,mBAAmB;AAEzC,MAAI,CAAC,SACL;AACI,WAAO;AAAA,EACX;AAEA,QAAM,sBAAsB,QAAQ,aAAa,IAAI,CAAC,MAAW,EAAE,IAAI,KAAK,CAAC;AAC7E,SAAO,oBAAoB,KAAK,gBAAc,oBAAoB,SAAS,UAAU,CAAC;AAC1F;;;ADLmB,qBAAAC,WAAA,OAAAC,YAAA;AAdnB,eAAsB,YAAY;AAAA,EAC9B;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb;AACJ,GACA;AACI,QAAM,UAAU,MAAM,WAAW;AAGjC,MAAI,CAAC,SACL;AACI,QAAI,UACJ;AACI,aAAO,gBAAAA,KAAAD,WAAA,EAAG,oBAAS;AAAA,IACvB;AAEA,IAAAE,UAAS,QAAQ;AAAA,EACrB;AAGA,QAAM,gBAAgB,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;AAG3D,QAAM,UAAU,MAAM,WAAW,aAAa;AAE9C,MAAI,CAAC,SACL;AACI,QAAI,UACJ;AACI,aAAO,gBAAAD,KAAAD,WAAA,EAAG,oBAAS;AAAA,IACvB;AAEA,IAAAE,UAAS,UAAU;AAAA,EACvB;AAEA,SAAO,gBAAAD,KAAAD,WAAA,EAAG,UAAS;AACvB;;;AE5FA,SAAS,YAAAG,iBAAgB;AAqEN,qBAAAC,WAAA,OAAAC,YAAA;AAdnB,eAAsB,kBAAkB;AAAA,EACpC;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb;AACJ,GACA;AACI,QAAM,UAAU,MAAM,WAAW;AAGjC,MAAI,CAAC,SACL;AACI,QAAI,UACJ;AACI,aAAO,gBAAAA,KAAAD,WAAA,EAAG,oBAAS;AAAA,IACvB;AAEA,IAAAE,UAAS,QAAQ;AAAA,EACrB;AAGA,QAAM,sBAAsB,MAAM,QAAQ,WAAW,IAAI,cAAc,CAAC,WAAW;AAGnF,QAAM,gBAAgB,MAAM,iBAAiB,mBAAmB;AAEhE,MAAI,CAAC,eACL;AACI,QAAI,UACJ;AACI,aAAO,gBAAAD,KAAAD,WAAA,EAAG,oBAAS;AAAA,IACvB;AAEA,IAAAE,UAAS,UAAU;AAAA,EACvB;AAEA,SAAO,gBAAAD,KAAAD,WAAA,EAAG,UAAS;AACvB;","names":["redirect","Fragment","jsx","redirect","redirect","Fragment","jsx","redirect"]}
|
|
1
|
+
{"version":3,"sources":["../../src/nextjs/server.ts","../../src/nextjs/guards/require-auth.tsx","../../src/nextjs/session-helpers.ts","../../src/nextjs/guards/require-role.tsx","../../src/nextjs/guards/auth-utils.ts","../../src/nextjs/guards/require-permission.tsx"],"sourcesContent":["import \"server-only\";\n\nexport { RequireAuth } from './guards/require-auth';\nexport type { RequireAuthProps } from './guards/require-auth';\n\nexport { RequireRole } from './guards/require-role';\nexport type { RequireRoleProps } from './guards/require-role';\n\nexport { RequirePermission } from './guards/require-permission';\nexport type { RequirePermissionProps } from './guards/require-permission';\n\nexport { getUserRole, getUserPermissions, hasAnyRole, hasAnyPermission } from './guards/auth-utils';\n\n// Session helpers\nexport {\n saveSession,\n getSession,\n clearSession,\n type SessionData,\n type PublicSession,\n type SaveSessionOptions\n} from './session-helpers';","/**\n * RequireAuth Guard Component\n *\n * Requires user to be authenticated\n */\n\nimport { redirect } from 'next/navigation';\nimport { getSession } from '../session-helpers';\nimport type { ReactNode } from 'react';\n\nexport interface RequireAuthProps\n{\n /**\n * Children to render if authenticated\n */\n children: ReactNode;\n\n /**\n * Path to redirect to if not authenticated\n * @default '/login'\n */\n redirectTo?: string;\n\n /**\n * Fallback UI to show instead of redirecting\n */\n fallback?: ReactNode;\n}\n\n/**\n * Require Authentication Guard\n *\n * Ensures user is logged in before rendering children\n *\n * @example\n * ```tsx\n * <RequireAuth redirectTo=\"/login\">\n * <DashboardContent />\n * </RequireAuth>\n * ```\n *\n * @example With fallback\n * ```tsx\n * <RequireAuth fallback={<LoginPrompt />}>\n * <PrivateContent />\n * </RequireAuth>\n * ```\n */\nexport async function RequireAuth({\n children,\n redirectTo = '/auth/login',\n fallback,\n}: RequireAuthProps)\n{\n const session = await getSession();\n\n if (!session)\n {\n if (fallback)\n {\n return <>{fallback}</>;\n }\n\n redirect(redirectTo);\n }\n\n return <>{children}</>;\n}","/**\n * Session helpers for Next.js\n *\n * Server-side only (uses next/headers)\n */\n\nimport { cookies } from 'next/headers.js';\nimport { sealSession, unsealSession, COOKIE_NAMES, getSessionTtl, parseDuration, type SessionData } from '@spfn/auth/server';\nimport { logger } from '@spfn/core/logger';\n\nexport type { SessionData };\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 * RequireRole Guard Component\n *\n * Requires user to have at least one of the specified roles\n */\n\nimport { redirect } from 'next/navigation';\nimport { getSession } from '../session-helpers';\nimport { hasAnyRole } from './auth-utils';\nimport type { ReactNode } from 'react';\n\nexport interface RequireRoleProps\n{\n /**\n * Required role(s) - user must have at least one\n */\n roles: string | string[];\n\n /**\n * Children to render if user has required role\n */\n children: ReactNode;\n\n /**\n * Path to redirect to if user doesn't have role\n * @default '/unauthorized'\n */\n redirectTo?: string;\n\n /**\n * Fallback UI to show instead of redirecting\n */\n fallback?: ReactNode;\n}\n\n/**\n * Require Role Guard\n *\n * Ensures user has at least one of the specified roles\n *\n * @example Single role\n * ```tsx\n * <RequireRole roles=\"admin\">\n * <AdminPanel />\n * </RequireRole>\n * ```\n *\n * @example Multiple roles (OR condition)\n * ```tsx\n * <RequireRole roles={['admin', 'manager']}>\n * <ManagementDashboard />\n * </RequireRole>\n * ```\n *\n * @example With fallback\n * ```tsx\n * <RequireRole roles=\"admin\" fallback={<AccessDenied />}>\n * <AdminContent />\n * </RequireRole>\n * ```\n */\nexport async function RequireRole({\n roles,\n children,\n redirectTo = '/unauthorized',\n fallback,\n}: RequireRoleProps)\n{\n const session = await getSession();\n\n // Not authenticated\n if (!session)\n {\n if (fallback)\n {\n return <>{fallback}</>;\n }\n\n redirect('/login');\n }\n\n // Normalize to array\n const requiredRoles = Array.isArray(roles) ? roles : [roles];\n\n // Check if user has any of the required roles\n const hasRole = await hasAnyRole(requiredRoles);\n\n if (!hasRole)\n {\n if (fallback)\n {\n return <>{fallback}</>;\n }\n\n redirect(redirectTo);\n }\n\n return <>{children}</>;\n}","/**\n * Server-side auth utilities for guards\n *\n * Uses authApi to check permissions in real-time\n */\n\nimport { authApi } from '@spfn/auth';\nimport { authLogger } from '@spfn/auth/server';\n\n/**\n * Get current auth session with roles and permissions via API\n */\nasync function getAuthSessionData()\n{\n try\n {\n const session = await authApi.getAuthSession.call();\n authLogger.middleware.debug('Auth session retrieved', { name: session.role?.name });\n\n return session;\n }\n catch (error)\n {\n authLogger.middleware.error('Failed to get auth session', { error });\n return null;\n }\n}\n\n/**\n * Get user role\n */\nexport async function getUserRole(): Promise<string | null>\n{\n const session = await getAuthSessionData();\n return session?.role?.name || null;\n}\n\n/**\n * Get user permissions\n */\nexport async function getUserPermissions(): Promise<string[]>\n{\n const session = await getAuthSessionData();\n\n if (!session)\n {\n return [];\n }\n\n return session.permissions?.map((p: any) => p.name) || [];\n}\n\n/**\n * Check if user has any of the specified roles\n */\nexport async function hasAnyRole(requiredRoles: string[]): Promise<boolean>\n{\n const session = await getAuthSessionData();\n if (!session)\n {\n return false;\n }\n\n return requiredRoles.includes(session.role?.name);\n}\n\n/**\n * Check if user has any of the specified permissions\n */\nexport async function hasAnyPermission(requiredPermissions: string[]): Promise<boolean>\n{\n const session = await getAuthSessionData();\n\n if (!session)\n {\n return false;\n }\n\n const userPermissionNames = session.permissions?.map((p: any) => p.name) || [];\n return requiredPermissions.some(permission => userPermissionNames.includes(permission));\n}\n","/**\n * RequirePermission Guard Component\n *\n * Requires user to have at least one of the specified permissions\n */\n\nimport { redirect } from 'next/navigation';\nimport { getSession } from '../session-helpers';\nimport { hasAnyPermission } from './auth-utils';\nimport type { ReactNode } from 'react';\n\nexport interface RequirePermissionProps\n{\n /**\n * Required permission(s) - user must have at least one\n */\n permissions: string | string[];\n\n /**\n * Children to render if user has required permission\n */\n children: ReactNode;\n\n /**\n * Path to redirect to if user doesn't have permission\n * @default '/unauthorized'\n */\n redirectTo?: string;\n\n /**\n * Fallback UI to show instead of redirecting\n */\n fallback?: ReactNode;\n}\n\n/**\n * Require Permission Guard\n *\n * Ensures user has at least one of the specified permissions\n *\n * @example Single permission\n * ```tsx\n * <RequirePermission permissions=\"user:delete\">\n * <DeleteUserButton />\n * </RequirePermission>\n * ```\n *\n * @example Multiple permissions (OR condition)\n * ```tsx\n * <RequirePermission permissions={['user:delete', 'user:update']}>\n * <UserManagement />\n * </RequirePermission>\n * ```\n *\n * @example With fallback\n * ```tsx\n * <RequirePermission permissions=\"project:create\" fallback={<UpgradePrompt />}>\n * <CreateProject />\n * </RequirePermission>\n * ```\n */\nexport async function RequirePermission({\n permissions,\n children,\n redirectTo = '/unauthorized',\n fallback,\n}: RequirePermissionProps)\n{\n const session = await getSession();\n\n // Not authenticated\n if (!session)\n {\n if (fallback)\n {\n return <>{fallback}</>;\n }\n\n redirect('/login');\n }\n\n // Normalize to array\n const requiredPermissions = Array.isArray(permissions) ? permissions : [permissions];\n\n // Check if user has any of the required permissions\n const hasPermission = await hasAnyPermission(requiredPermissions);\n\n if (!hasPermission)\n {\n if (fallback)\n {\n return <>{fallback}</>;\n }\n\n redirect(redirectTo);\n }\n\n return <>{children}</>;\n}"],"mappings":";AAAA,OAAO;;;ACMP,SAAS,gBAAgB;;;ACAzB,SAAS,eAAe;AACxB,SAAS,aAAa,eAAe,cAAc,eAAe,qBAAuC;AACzG,SAAS,cAAc;AA6DvB,eAAsB,YAClB,MACA,SAEJ;AAEI,MAAI;AAEJ,MAAI,SAAS,WAAW,QACxB;AAEI,aAAS,OAAO,QAAQ,WAAW,WAC7B,QAAQ,SACR,cAAc,QAAQ,MAAM;AAAA,EACtC,OAEA;AAEI,aAAS,cAAc;AAAA,EAC3B;AAEA,QAAM,QAAQ,MAAM,YAAY,MAAM,MAAM;AAC5C,QAAM,cAAc,MAAM,QAAQ;AAElC,cAAY,IAAI,aAAa,SAAS,OAAO;AAAA,IACzC,UAAU;AAAA,IACV,QAAQ,QAAQ,IAAI,aAAa;AAAA,IACjC,UAAU;AAAA,IACV,MAAM;AAAA,IACN;AAAA,EACJ,CAAC;AACL;AAOA,eAAsB,aACtB;AACI,QAAM,cAAc,MAAM,QAAQ;AAClC,QAAM,gBAAgB,YAAY,IAAI,aAAa,OAAO;AAE1D,MAAI,CAAC,eACL;AACI,WAAO;AAAA,EACX;AAEA,MACA;AACI,WAAO,MAAM,6BAA6B,EAAE,QAAQ,cAAc,MAAM,CAAC;AACzE,UAAM,UAAU,MAAM,cAAc,cAAc,KAAK;AAEvD,WAAO;AAAA,MACH,QAAQ,QAAQ;AAAA,IACpB;AAAA,EACJ,SACO,OACP;AAII,WAAO,MAAM,6BAA6B;AAAA,MACtC,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,IAChE,CAAC;AAED,WAAO;AAAA,EACX;AACJ;AAKA,eAAsB,eACtB;AACI,QAAM,cAAc,MAAM,QAAQ;AAClC,cAAY,OAAO,aAAa,OAAO;AACvC,cAAY,OAAO,aAAa,cAAc;AAClD;;;ADvFmB;AAZnB,eAAsB,YAAY;AAAA,EAC9B;AAAA,EACA,aAAa;AAAA,EACb;AACJ,GACA;AACI,QAAM,UAAU,MAAM,WAAW;AAEjC,MAAI,CAAC,SACL;AACI,QAAI,UACJ;AACI,aAAO,gCAAG,oBAAS;AAAA,IACvB;AAEA,aAAS,UAAU;AAAA,EACvB;AAEA,SAAO,gCAAG,UAAS;AACvB;;;AE7DA,SAAS,YAAAA,iBAAgB;;;ACAzB,SAAS,eAAe;AACxB,SAAS,kBAAkB;AAK3B,eAAe,qBACf;AACI,MACA;AACI,UAAM,UAAU,MAAM,QAAQ,eAAe,KAAK;AAClD,eAAW,WAAW,MAAM,0BAA0B,EAAE,MAAM,QAAQ,MAAM,KAAK,CAAC;AAElF,WAAO;AAAA,EACX,SACO,OACP;AACI,eAAW,WAAW,MAAM,8BAA8B,EAAE,MAAM,CAAC;AACnE,WAAO;AAAA,EACX;AACJ;AAKA,eAAsB,cACtB;AACI,QAAM,UAAU,MAAM,mBAAmB;AACzC,SAAO,SAAS,MAAM,QAAQ;AAClC;AAKA,eAAsB,qBACtB;AACI,QAAM,UAAU,MAAM,mBAAmB;AAEzC,MAAI,CAAC,SACL;AACI,WAAO,CAAC;AAAA,EACZ;AAEA,SAAO,QAAQ,aAAa,IAAI,CAAC,MAAW,EAAE,IAAI,KAAK,CAAC;AAC5D;AAKA,eAAsB,WAAW,eACjC;AACI,QAAM,UAAU,MAAM,mBAAmB;AACzC,MAAI,CAAC,SACL;AACI,WAAO;AAAA,EACX;AAEA,SAAO,cAAc,SAAS,QAAQ,MAAM,IAAI;AACpD;AAKA,eAAsB,iBAAiB,qBACvC;AACI,QAAM,UAAU,MAAM,mBAAmB;AAEzC,MAAI,CAAC,SACL;AACI,WAAO;AAAA,EACX;AAEA,QAAM,sBAAsB,QAAQ,aAAa,IAAI,CAAC,MAAW,EAAE,IAAI,KAAK,CAAC;AAC7E,SAAO,oBAAoB,KAAK,gBAAc,oBAAoB,SAAS,UAAU,CAAC;AAC1F;;;ADLmB,qBAAAC,WAAA,OAAAC,YAAA;AAdnB,eAAsB,YAAY;AAAA,EAC9B;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb;AACJ,GACA;AACI,QAAM,UAAU,MAAM,WAAW;AAGjC,MAAI,CAAC,SACL;AACI,QAAI,UACJ;AACI,aAAO,gBAAAA,KAAAD,WAAA,EAAG,oBAAS;AAAA,IACvB;AAEA,IAAAE,UAAS,QAAQ;AAAA,EACrB;AAGA,QAAM,gBAAgB,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;AAG3D,QAAM,UAAU,MAAM,WAAW,aAAa;AAE9C,MAAI,CAAC,SACL;AACI,QAAI,UACJ;AACI,aAAO,gBAAAD,KAAAD,WAAA,EAAG,oBAAS;AAAA,IACvB;AAEA,IAAAE,UAAS,UAAU;AAAA,EACvB;AAEA,SAAO,gBAAAD,KAAAD,WAAA,EAAG,UAAS;AACvB;;;AE5FA,SAAS,YAAAG,iBAAgB;AAqEN,qBAAAC,WAAA,OAAAC,YAAA;AAdnB,eAAsB,kBAAkB;AAAA,EACpC;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb;AACJ,GACA;AACI,QAAM,UAAU,MAAM,WAAW;AAGjC,MAAI,CAAC,SACL;AACI,QAAI,UACJ;AACI,aAAO,gBAAAA,KAAAD,WAAA,EAAG,oBAAS;AAAA,IACvB;AAEA,IAAAE,UAAS,QAAQ;AAAA,EACrB;AAGA,QAAM,sBAAsB,MAAM,QAAQ,WAAW,IAAI,cAAc,CAAC,WAAW;AAGnF,QAAM,gBAAgB,MAAM,iBAAiB,mBAAmB;AAEhE,MAAI,CAAC,eACL;AACI,QAAI,UACJ;AACI,aAAO,gBAAAD,KAAAD,WAAA,EAAG,oBAAS;AAAA,IACvB;AAEA,IAAAE,UAAS,UAAU;AAAA,EACvB;AAEA,SAAO,gBAAAD,KAAAD,WAAA,EAAG,UAAS;AACvB;","names":["redirect","Fragment","jsx","redirect","redirect","Fragment","jsx","redirect"]}
|