@oauth42/next 0.4.5 → 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -119,7 +119,12 @@ async function getOAuth42Session(...args) {
119
119
  } catch {
120
120
  }
121
121
  if (refreshedToken) {
122
- console.log("[OAuth42 Session] Using refreshed token from middleware");
122
+ const sessionToken = session.accessToken;
123
+ console.log("[OAuth42 Session] Using refreshed token from middleware:", {
124
+ refreshedPrefix: refreshedToken.substring(0, 20),
125
+ sessionPrefix: sessionToken?.substring(0, 20),
126
+ tokensMatch: refreshedToken === sessionToken
127
+ });
123
128
  return {
124
129
  ...session,
125
130
  accessToken: refreshedToken
@@ -370,6 +375,9 @@ async function doRefresh(token, clientId, clientSecret, issuer) {
370
375
  // src/server/middleware.ts
371
376
  var import_server = require("next/server");
372
377
  var import_jwt = require("next-auth/jwt");
378
+ var ALLOWED_COOKIE_SIZE = 4096;
379
+ var ESTIMATED_EMPTY_COOKIE_SIZE = 163;
380
+ var CHUNK_SIZE = ALLOWED_COOKIE_SIZE - ESTIMATED_EMPTY_COOKIE_SIZE;
373
381
  var pendingRefreshes = /* @__PURE__ */ new Map();
374
382
  async function refreshTokens(refreshToken, clientId, clientSecret, issuer) {
375
383
  try {
@@ -436,6 +444,15 @@ function withOAuth42Auth(options = {}) {
436
444
  const now = Math.floor(Date.now() / 1e3);
437
445
  const bufferSeconds = 15;
438
446
  const needsRefresh = expiresAt && now >= expiresAt - bufferSeconds;
447
+ if (needsRefresh) {
448
+ console.log("[OAuth42 Middleware] Token needs refresh:", {
449
+ expiresAt,
450
+ now,
451
+ expiredAgo: expiresAt ? now - expiresAt : "n/a",
452
+ accessTokenPrefix: token.accessToken?.substring(0, 20),
453
+ path: pathname
454
+ });
455
+ }
439
456
  if (needsRefresh && token.refreshToken && clientId && clientSecret) {
440
457
  const userId = token.sub;
441
458
  let refreshPromise = pendingRefreshes.get(userId);
@@ -449,7 +466,7 @@ function withOAuth42Auth(options = {}) {
449
466
  clientSecret,
450
467
  issuer
451
468
  ).finally(() => {
452
- pendingRefreshes.delete(userId);
469
+ setTimeout(() => pendingRefreshes.delete(userId), 3e4);
453
470
  });
454
471
  pendingRefreshes.set(userId, refreshPromise);
455
472
  }
@@ -472,13 +489,56 @@ function withOAuth42Auth(options = {}) {
472
489
  headers: requestHeaders
473
490
  }
474
491
  });
475
- response.cookies.set(cookieName, newJwt, {
492
+ const cookieOptions = {
476
493
  httpOnly: true,
477
494
  sameSite: "lax",
478
495
  path: "/",
479
496
  secure: process.env.NODE_ENV === "production"
497
+ };
498
+ if (newJwt.length <= CHUNK_SIZE) {
499
+ response.cookies.set(cookieName, newJwt, cookieOptions);
500
+ for (let i = 0; ; i++) {
501
+ const chunkName = `${cookieName}.${i}`;
502
+ if (req.cookies.has(chunkName)) {
503
+ response.cookies.set(chunkName, "", { ...cookieOptions, maxAge: 0 });
504
+ } else {
505
+ break;
506
+ }
507
+ }
508
+ } else {
509
+ const chunkCount = Math.ceil(newJwt.length / CHUNK_SIZE);
510
+ for (let i = 0; i < chunkCount; i++) {
511
+ const chunkName = `${cookieName}.${i}`;
512
+ const chunkValue = newJwt.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);
513
+ response.cookies.set(chunkName, chunkValue, cookieOptions);
514
+ }
515
+ response.cookies.set(cookieName, "", { ...cookieOptions, maxAge: 0 });
516
+ for (let i = chunkCount; ; i++) {
517
+ const chunkName = `${cookieName}.${i}`;
518
+ if (req.cookies.has(chunkName)) {
519
+ response.cookies.set(chunkName, "", { ...cookieOptions, maxAge: 0 });
520
+ } else {
521
+ break;
522
+ }
523
+ }
524
+ }
525
+ const existingChunks = [];
526
+ for (let i = 0; ; i++) {
527
+ if (req.cookies.has(`${cookieName}.${i}`)) {
528
+ existingChunks.push(i);
529
+ } else {
530
+ break;
531
+ }
532
+ }
533
+ console.log(`[OAuth42 Middleware] Token refresh complete:`, {
534
+ jwtBytes: newJwt.length,
535
+ chunked: newJwt.length > CHUNK_SIZE,
536
+ existingBaseCooke: req.cookies.has(cookieName),
537
+ existingChunks: existingChunks.length > 0 ? existingChunks : "none",
538
+ newAccessTokenPrefix: refreshed.accessToken?.substring(0, 20),
539
+ newExpiresAt: refreshed.expiresAt,
540
+ cookieName
480
541
  });
481
- console.log("[OAuth42 Middleware] Cookie updated with refreshed tokens, header set for current request");
482
542
  return response;
483
543
  } else {
484
544
  console.log("[OAuth42 Middleware] Refresh failed (likely token blacklisted after deploy), clearing session");
@@ -487,6 +547,14 @@ function withOAuth42Auth(options = {}) {
487
547
  url.searchParams.set("callbackUrl", pathname);
488
548
  const response = import_server.NextResponse.redirect(url);
489
549
  response.cookies.delete(cookieName);
550
+ for (let i = 0; ; i++) {
551
+ const chunkName = `${cookieName}.${i}`;
552
+ if (req.cookies.has(chunkName)) {
553
+ response.cookies.delete(chunkName);
554
+ } else {
555
+ break;
556
+ }
557
+ }
490
558
  return response;
491
559
  }
492
560
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/provider.ts","../src/server/auth.ts","../src/server/session.ts","../src/server/middleware.ts"],"sourcesContent":["// Main exports\nexport { OAuth42Provider } from './provider';\nexport type { OAuth42Profile, OAuth42ProviderOptions } from './provider';\n\n// Server-side exports\nexport { createAuth, getServerSession, refreshAccessToken } from './server/auth';\nexport type { CreateAuthOptions } from './server/auth';\n\nexport { withOAuth42Auth, createMiddlewareConfig } from './server/middleware';\nexport type { OAuth42AuthOptions } from './server/middleware';\n\nexport { getOAuth42Session, withOAuth42Session, withOAuth42ServerSideProps } from './server/session';\n\n// Type exports\nexport type { OAuth42Config } from './types';\n\n// Re-export NextAuth types for convenience\nexport type { Session, User } from 'next-auth';","import type { OAuthConfig, OAuthUserConfig } from 'next-auth/providers/oauth';\n\nexport interface OAuth42Profile {\n sub: string;\n email: string;\n email_verified?: boolean;\n name?: string;\n given_name?: string;\n family_name?: string;\n picture?: string;\n username?: string;\n groups?: string[];\n id?: string;\n}\n\nexport interface OAuth42ProviderOptions {\n clientId: string;\n clientSecret: string;\n issuer?: string;\n authorizationUrl?: string;\n tokenUrl?: string;\n userinfoUrl?: string;\n scopes?: string[];\n pkceEnabled?: boolean;\n}\n\nexport function OAuth42Provider<P extends OAuth42Profile>(\n options: OAuthUserConfig<P> & Partial<OAuth42ProviderOptions>\n): OAuthConfig<P> {\n const issuer = options.issuer || process.env.OAUTH42_ISSUER || 'https://api.oauth42.com';\n const baseUrl = issuer.replace(/\\/$/, '');\n \n return {\n id: 'oauth42',\n name: 'OAuth42',\n type: 'oauth',\n version: '2.0',\n\n // Use OIDC discovery to automatically find endpoints\n wellKnown: `${baseUrl}/.well-known/openid-configuration`,\n\n // Use ID token for user profile (standard OIDC pattern)\n // This avoids an extra HTTP call to /userinfo on every auth flow\n // Apps can call /userinfo separately when they need extended claims (groups, company_id, etc.)\n idToken: true,\n\n // Also set individual endpoints for compatibility\n authorization: {\n url: `${baseUrl}/oauth2/authorize`,\n params: {\n scope: (options.scopes || ['openid', 'profile', 'email']).join(' '),\n response_type: 'code',\n },\n },\n token: `${baseUrl}/oauth2/token`,\n userinfo: `${baseUrl}/oauth2/userinfo`,\n\n client: {\n id: options.clientId,\n secret: options.clientSecret,\n token_endpoint_auth_method: 'client_secret_post',\n id_token_signed_response_alg: 'RS256', // OAuth42 uses RS256 for ID tokens\n },\n\n issuer: baseUrl,\n\n checks: options.pkceEnabled !== false ? ['pkce', 'state'] : ['state'],\n \n profile(profile: OAuth42Profile, tokens: any) {\n return {\n id: profile.sub || profile.id || profile.email,\n email: profile.email,\n emailVerified: profile.email_verified ? new Date() : null,\n name: profile.name || `${profile.given_name || ''} ${profile.family_name || ''}`.trim(),\n image: profile.picture,\n groups: profile.groups,\n };\n },\n \n style: {\n logo: '/oauth42-logo.svg',\n bg: '#1e40af',\n text: '#ffffff',\n },\n \n options,\n };\n}","import NextAuthDefault from 'next-auth';\nimport type { NextAuthOptions } from 'next-auth';\nimport { OAuth42Provider, OAuth42Profile } from '../provider';\nimport { getOAuth42Session } from './session';\n\n// Handle both CommonJS and ESM exports\nconst NextAuth = (NextAuthDefault as any).default || NextAuthDefault;\n\nexport { type NextAuthOptions };\n\n// Simple per-process lock for explicit refresh via getAccessToken()\nlet activeRefresh: Promise<any> | null = null;\n\nexport interface CreateAuthOptions {\n clientId?: string;\n clientSecret?: string;\n issuer?: string;\n scopes?: string[];\n pkceEnabled?: boolean;\n debug?: boolean;\n callbacks?: NextAuthOptions['callbacks'];\n pages?: NextAuthOptions['pages'];\n session?: NextAuthOptions['session'];\n /**\n * Unique prefix for cookie names to allow multiple apps on the same domain.\n * Each app should use a different prefix (e.g., 'portal', 'admin', 'bond').\n * This prevents session cookie conflicts when running multiple apps on localhost.\n */\n cookiePrefix?: string;\n}\n\n/**\n * Create a pre-configured NextAuth instance for OAuth42\n * This provides a simplified setup with sensible defaults\n */\nexport function createAuth(options: CreateAuthOptions = {}) {\n const clientId = options.clientId || process.env.OAUTH42_CLIENT_ID;\n const clientSecret = options.clientSecret || process.env.OAUTH42_CLIENT_SECRET;\n \n if (!clientId || !clientSecret) {\n throw new Error(\n 'OAuth42 client credentials are required. ' +\n 'Set OAUTH42_CLIENT_ID and OAUTH42_CLIENT_SECRET environment variables ' +\n 'or pass them in the options.'\n );\n }\n \n const authOptions: NextAuthOptions = {\n providers: [\n OAuth42Provider({\n clientId,\n clientSecret,\n issuer: options.issuer,\n scopes: options.scopes,\n pkceEnabled: options.pkceEnabled,\n }),\n ],\n \n callbacks: {\n async jwt({ token, account, profile }) {\n console.log('[OAuth42 SDK] JWT callback called', { hasAccount: !!account, hasProfile: !!profile });\n\n // Initial sign in - store OAuth tokens in the JWT\n if (account) {\n console.log('[OAuth42 SDK] Initial sign in - storing tokens in JWT');\n token.accessToken = account.access_token;\n token.refreshToken = account.refresh_token;\n token.expiresAt = account.expires_at;\n token.idToken = account.id_token;\n token.clientId = clientId;\n token.clientSecret = clientSecret;\n token.issuer = options.issuer || process.env.NEXT_PUBLIC_OAUTH_ISSUER || process.env.OAUTH42_ISSUER;\n }\n\n // Add user profile data\n if (profile) {\n const oauth42Profile = profile as OAuth42Profile;\n token.email = oauth42Profile.email;\n token.username = oauth42Profile.username;\n token.emailVerified = oauth42Profile.email_verified;\n token.groups = oauth42Profile.groups;\n }\n\n // NOTE: Token refresh is handled by middleware (withOAuth42Auth)\n // Middleware can properly set cookies after refresh, which JWT callback cannot\n // do in Next.js App Router Server Components.\n\n // Call custom callback if provided\n if (options.callbacks?.jwt) {\n return options.callbacks.jwt({ token, account, profile } as any);\n }\n\n console.log('[OAuth42 SDK] JWT callback complete, returning token');\n return token;\n },\n\n async session({ session, token }) {\n console.log('[OAuth42 SDK] Session callback called', { hasToken: !!token, hasSession: !!session });\n\n // Add OAuth42-specific data to session\n session.accessToken = token.accessToken as string;\n session.idToken = token.idToken as string;\n\n // Pass through any token refresh errors to the client\n if (token.error) {\n session.error = token.error as string;\n }\n\n if (session.user) {\n session.user.email = token.email as string;\n session.user.name = token.name as string;\n session.user.username = token.username as string;\n session.user.emailVerified = token.emailVerified as boolean;\n session.user.groups = token.groups as string[];\n }\n\n // Call custom callback if provided\n if (options.callbacks?.session) {\n return options.callbacks.session({ session, token } as any);\n }\n\n console.log('[OAuth42 SDK] Session callback complete, returning session');\n return session;\n },\n },\n \n pages: {\n signIn: '/auth/signin',\n signOut: '/auth/signout',\n error: '/auth/error',\n ...options.pages,\n },\n \n session: {\n strategy: 'jwt',\n ...options.session,\n },\n \n debug: options.debug || process.env.NODE_ENV === 'development',\n\n secret: process.env.NEXTAUTH_SECRET,\n\n // Configure unique cookie names per app to prevent session conflicts on localhost\n ...(options.cookiePrefix && {\n cookies: {\n sessionToken: {\n name: `${options.cookiePrefix}.session-token`,\n options: {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n },\n },\n callbackUrl: {\n name: `${options.cookiePrefix}.callback-url`,\n options: {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n },\n },\n csrfToken: {\n name: `${options.cookiePrefix}.csrf-token`,\n options: {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n },\n },\n // PKCE code_verifier cookie - essential for PKCE flow\n pkceCodeVerifier: {\n name: `${options.cookiePrefix}.pkce.code_verifier`,\n options: {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n maxAge: 900, // 15 minutes\n },\n },\n // State cookie for OAuth CSRF protection\n state: {\n name: `${options.cookiePrefix}.state`,\n options: {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n maxAge: 900, // 15 minutes\n },\n },\n // Nonce cookie for OpenID Connect\n nonce: {\n name: `${options.cookiePrefix}.nonce`,\n options: {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n },\n },\n },\n }),\n };\n \n // Return the configuration and handlers for API routes\n const handler = NextAuth(authOptions);\n return {\n auth: authOptions,\n handlers: { GET: handler, POST: handler },\n };\n}\n\n/**\n * Create NextAuth handlers for API routes\n */\nexport function createHandlers(authOptions: NextAuthOptions) {\n const handler = NextAuth(authOptions);\n return { GET: handler, POST: handler };\n}\n\n/**\n * Helper to get the current session server-side\n * @deprecated Use getOAuth42Session instead - this is now just an alias for backward compatibility\n * \n * This function is maintained for backward compatibility but internally\n * calls getOAuth42Session which properly handles both App Router and Pages Router\n */\nexport const getServerSession = getOAuth42Session;\n\n/**\n * Token refresh helper with simple per-process locking\n *\n * The lock prevents multiple concurrent refresh calls from the same process,\n * reducing unnecessary token churn. The backend also has a 10-second grace\n * period for blacklisted tokens, so concurrent requests across processes\n * will still succeed.\n */\nexport async function refreshAccessToken(token: any, clientId: string, clientSecret: string, issuer?: string): Promise<any> {\n // If a refresh is already in progress, wait for it\n if (activeRefresh) {\n console.log('[OAuth42] Refresh already in progress, waiting...');\n return await activeRefresh;\n }\n\n // Start the refresh and store the promise\n activeRefresh = doRefresh(token, clientId, clientSecret, issuer);\n try {\n return await activeRefresh;\n } finally {\n activeRefresh = null;\n }\n}\n\nasync function doRefresh(token: any, clientId: string, clientSecret: string, issuer?: string): Promise<any> {\n try {\n const baseUrl = issuer || process.env.OAUTH42_ISSUER || 'https://api.oauth42.com';\n const tokenUrl = `${baseUrl}/oauth2/token`;\n\n // In development, we need to handle self-signed certificates\n const fetchOptions: any = {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: token.refreshToken,\n client_id: clientId,\n client_secret: clientSecret,\n }),\n };\n\n // Add agent for self-signed certificates in development\n if (process.env.NODE_ENV !== 'production' && tokenUrl.startsWith('https://')) {\n const https = await import('https');\n fetchOptions.agent = new https.Agent({\n rejectUnauthorized: false\n });\n }\n\n const response = await fetch(tokenUrl, fetchOptions);\n const refreshedTokens = await response.json();\n\n if (!response.ok) {\n throw refreshedTokens;\n }\n\n console.log('[OAuth42] Token refreshed successfully');\n return {\n ...token,\n accessToken: refreshedTokens.access_token,\n refreshToken: refreshedTokens.refresh_token ?? token.refreshToken,\n // Store expiration time in seconds (Unix timestamp)\n expiresAt: Math.floor(Date.now() / 1000) + (refreshedTokens.expires_in || 3600),\n // Explicitly remove any error property on successful refresh\n error: undefined,\n };\n } catch (error) {\n console.error('[OAuth42] Failed to refresh access token:', error);\n return {\n ...token,\n error: 'RefreshAccessTokenError',\n };\n }\n}","import { getServerSession as getNextAuthSession } from 'next-auth';\nimport { NextAuthOptions } from 'next-auth';\nimport { GetServerSidePropsContext, NextApiRequest, NextApiResponse } from 'next';\nimport { headers } from 'next/headers';\n\n/** Header name used by middleware to pass refreshed tokens to API routes */\nconst REFRESHED_TOKEN_HEADER = 'x-oauth42-refreshed-token';\n\n/**\n * Get the OAuth42 session server-side\n *\n * This is the primary method for retrieving sessions in OAuth42 SDK.\n * Supports both Pages Router and App Router:\n *\n * App Router:\n * ```ts\n * const session = await getOAuth42Session(authOptions);\n * ```\n *\n * Pages Router:\n * ```ts\n * const session = await getOAuth42Session(req, res, authOptions);\n * ```\n *\n * **Token Refresh Support:**\n * When middleware refreshes an expired token, it passes the new token via\n * the `x-oauth42-refreshed-token` header. This function automatically detects\n * and uses that refreshed token, ensuring API routes always have a valid token.\n */\nexport async function getOAuth42Session(\n ...args:\n | [GetServerSidePropsContext['req'], GetServerSidePropsContext['res'], NextAuthOptions]\n | [NextApiRequest, NextApiResponse, NextAuthOptions]\n | [NextAuthOptions]\n) {\n const session = await getNextAuthSession(...args as any);\n\n if (!session) {\n return null;\n }\n\n // Check for refreshed token from middleware\n // This handles the race condition where middleware refreshes the token\n // but the API route still reads the old token from the session cookie\n let refreshedToken: string | null = null;\n\n try {\n // App Router: use headers() from next/headers\n if (args.length === 1) {\n const headersList = await headers();\n refreshedToken = headersList.get(REFRESHED_TOKEN_HEADER);\n }\n // Pages Router: check request headers\n else if (args.length === 3) {\n const req = args[0] as NextApiRequest | GetServerSidePropsContext['req'];\n refreshedToken = (req.headers[REFRESHED_TOKEN_HEADER] as string) || null;\n }\n } catch {\n // headers() may throw if called outside of a request context\n // This is fine - just use the session token as-is\n }\n\n if (refreshedToken) {\n console.log('[OAuth42 Session] Using refreshed token from middleware');\n return {\n ...session,\n accessToken: refreshedToken,\n };\n }\n\n return session;\n}\n\n/**\n * Helper for protecting API routes\n */\nexport function withOAuth42Session(\n handler: (req: NextApiRequest, res: NextApiResponse, session: any) => Promise<void> | void,\n authOptions: NextAuthOptions\n) {\n return async (req: NextApiRequest, res: NextApiResponse) => {\n const session = await getOAuth42Session(req, res, authOptions);\n \n if (!session) {\n return res.status(401).json({ error: 'Unauthorized' });\n }\n \n return handler(req, res, session);\n };\n}\n\n/**\n * Helper for protecting server-side props\n */\nexport function withOAuth42ServerSideProps(\n getServerSideProps: (\n context: GetServerSidePropsContext,\n session: any\n ) => Promise<any>,\n authOptions: NextAuthOptions\n) {\n return async (context: GetServerSidePropsContext) => {\n const session = await getOAuth42Session(\n context.req,\n context.res,\n authOptions\n );\n \n if (!session) {\n return {\n redirect: {\n destination: '/auth/signin',\n permanent: false,\n },\n };\n }\n \n return getServerSideProps(context, session);\n };\n}","import { NextRequest, NextResponse } from 'next/server';\nimport { getToken, encode } from 'next-auth/jwt';\n\n/**\n * In-flight refresh tracking to prevent parallel refresh calls.\n * Key: user ID (from token.sub), Value: Promise that resolves with refresh result\n */\nconst pendingRefreshes = new Map<string, Promise<{\n success: boolean;\n accessToken?: string;\n refreshToken?: string;\n expiresAt?: number;\n error?: string;\n}>>();\n\nexport interface OAuth42AuthOptions {\n pages?: {\n signIn?: string;\n error?: string;\n };\n callbacks?: {\n authorized?: (params: { token: any; req: NextRequest }) => boolean | Promise<boolean>;\n };\n protectedPaths?: string[];\n publicPaths?: string[];\n /**\n * Cookie prefix for custom cookie names. Must match the prefix used in createAuth().\n * E.g., 'oauth42-portal' will look for cookie 'oauth42-portal.session-token'\n */\n cookiePrefix?: string;\n}\n\n/**\n * Refresh tokens by calling the OAuth42 backend directly\n */\nasync function refreshTokens(\n refreshToken: string,\n clientId: string,\n clientSecret: string,\n issuer: string\n): Promise<{ success: boolean; accessToken?: string; refreshToken?: string; expiresAt?: number; error?: string }> {\n try {\n const tokenUrl = `${issuer}/oauth2/token`;\n\n const response = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n client_id: clientId,\n client_secret: clientSecret,\n }),\n });\n\n const data = await response.json();\n\n if (!response.ok) {\n console.error('[OAuth42 Middleware] Token refresh failed:', data);\n return { success: false, error: data.error || 'refresh_failed' };\n }\n\n console.log('[OAuth42 Middleware] Token refreshed successfully');\n return {\n success: true,\n accessToken: data.access_token,\n refreshToken: data.refresh_token,\n expiresAt: Math.floor(Date.now() / 1000) + (data.expires_in || 3600),\n };\n } catch (error) {\n console.error('[OAuth42 Middleware] Token refresh error:', error);\n return { success: false, error: 'refresh_error' };\n }\n}\n\n/**\n * Middleware helper for protecting routes with OAuth42\n *\n * This middleware handles:\n * 1. Route protection (redirect to login if no session)\n * 2. Token refresh (refresh expired tokens and update cookie)\n */\nexport function withOAuth42Auth(options: OAuth42AuthOptions = {}) {\n const secret = process.env.NEXTAUTH_SECRET;\n const clientId = process.env.OAUTH42_CLIENT_ID;\n const clientSecret = process.env.OAUTH42_CLIENT_SECRET;\n const issuer = process.env.OAUTH42_ISSUER || 'https://localhost:8443';\n\n if (!secret) {\n console.warn('[OAuth42 Middleware] NEXTAUTH_SECRET not set');\n }\n\n return async function middleware(req: NextRequest) {\n // Build cookie name - if prefix is provided, use custom name\n const cookieName = options.cookiePrefix\n ? `${options.cookiePrefix}.session-token`\n : 'next-auth.session-token';\n\n const token = await getToken({\n req: req as any,\n secret,\n cookieName,\n });\n\n const pathname = req.nextUrl.pathname;\n\n // Check if path is explicitly public\n if (options.publicPaths?.some(path => pathname.startsWith(path))) {\n return NextResponse.next();\n }\n\n // Check if path needs protection\n const needsProtection = options.protectedPaths\n ? options.protectedPaths.some(path => pathname.startsWith(path))\n : true; // Default to protecting all paths\n\n if (!needsProtection) {\n return NextResponse.next();\n }\n\n // No token at all - redirect to sign in\n if (!token) {\n const signInUrl = options.pages?.signIn || '/auth/signin';\n const url = new URL(signInUrl, req.url);\n url.searchParams.set('callbackUrl', pathname);\n return NextResponse.redirect(url);\n }\n\n // Check if access token is expired or expiring soon (15 second buffer)\n // Buffer must be less than the token TTL to have a valid window\n const expiresAt = token.expiresAt as number | undefined;\n const now = Math.floor(Date.now() / 1000);\n const bufferSeconds = 15;\n const needsRefresh = expiresAt && now >= expiresAt - bufferSeconds;\n\n if (needsRefresh && token.refreshToken && clientId && clientSecret) {\n const userId = token.sub as string;\n\n // Check if there's already a refresh in progress for this user\n let refreshPromise = pendingRefreshes.get(userId);\n\n if (refreshPromise) {\n console.log('[OAuth42 Middleware] Waiting for in-flight refresh...');\n } else {\n console.log('[OAuth42 Middleware] Access token expired, refreshing...');\n\n // Create the refresh promise and store it\n refreshPromise = refreshTokens(\n token.refreshToken as string,\n clientId,\n clientSecret,\n issuer\n ).finally(() => {\n // Clean up after refresh completes (success or failure)\n pendingRefreshes.delete(userId);\n });\n\n pendingRefreshes.set(userId, refreshPromise);\n }\n\n const refreshed = await refreshPromise;\n\n if (refreshed.success && refreshed.accessToken && refreshed.refreshToken) {\n // Update the token with new values\n const updatedToken = {\n ...token,\n accessToken: refreshed.accessToken,\n refreshToken: refreshed.refreshToken,\n expiresAt: refreshed.expiresAt,\n };\n\n // Re-encode the JWT\n const newJwt = await encode({\n token: updatedToken,\n secret: secret!,\n });\n\n // Create response with request headers that pass the new token to API routes\n // This is necessary because API routes read from the request, not the response cookie\n const requestHeaders = new Headers(req.headers);\n requestHeaders.set('x-oauth42-refreshed-token', refreshed.accessToken);\n\n const response = NextResponse.next({\n request: {\n headers: requestHeaders,\n },\n });\n\n // Set cookie with same settings NextAuth uses (for future requests)\n response.cookies.set(cookieName, newJwt, {\n httpOnly: true,\n sameSite: 'lax',\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n });\n\n console.log('[OAuth42 Middleware] Cookie updated with refreshed tokens, header set for current request');\n return response;\n } else {\n // Refresh failed - clear cookie and redirect to sign in (no error, just let user log in fresh)\n console.log('[OAuth42 Middleware] Refresh failed (likely token blacklisted after deploy), clearing session');\n const signInUrl = options.pages?.signIn || '/auth/signin';\n const url = new URL(signInUrl, req.url);\n url.searchParams.set('callbackUrl', pathname);\n // DO NOT set error parameter - just redirect silently\n\n const response = NextResponse.redirect(url);\n // Clear the old session cookie\n response.cookies.delete(cookieName);\n return response;\n }\n }\n\n // Check custom authorization callback\n if (options.callbacks?.authorized) {\n const isAuthorized = await options.callbacks.authorized({ token, req });\n if (!isAuthorized) {\n const signInUrl = options.pages?.signIn || '/auth/signin';\n const url = new URL(signInUrl, req.url);\n url.searchParams.set('callbackUrl', pathname);\n return NextResponse.redirect(url);\n }\n }\n\n return NextResponse.next();\n };\n}\n\n/**\n * Helper to create middleware configuration\n */\nexport function createMiddlewareConfig(\n protectedPaths: string[] = ['/protected'],\n publicPaths: string[] = ['/auth', '/api/auth']\n) {\n return {\n matcher: [\n /*\n * Match all request paths except for the ones starting with:\n * - _next/static (static files)\n * - _next/image (image optimization files)\n * - favicon.ico (favicon file)\n * - public folder\n */\n '/((?!_next/static|_next/image|favicon.ico|public).*)',\n ],\n protectedPaths,\n publicPaths,\n };\n}\n"],"mappings":"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","names":["import_next_auth","getNextAuthSession","NextAuthDefault"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/provider.ts","../src/server/auth.ts","../src/server/session.ts","../src/server/middleware.ts"],"sourcesContent":["// Main exports\nexport { OAuth42Provider } from './provider';\nexport type { OAuth42Profile, OAuth42ProviderOptions } from './provider';\n\n// Server-side exports\nexport { createAuth, getServerSession, refreshAccessToken } from './server/auth';\nexport type { CreateAuthOptions } from './server/auth';\n\nexport { withOAuth42Auth, createMiddlewareConfig } from './server/middleware';\nexport type { OAuth42AuthOptions } from './server/middleware';\n\nexport { getOAuth42Session, withOAuth42Session, withOAuth42ServerSideProps } from './server/session';\n\n// Type exports\nexport type { OAuth42Config } from './types';\n\n// Re-export NextAuth types for convenience\nexport type { Session, User } from 'next-auth';","import type { OAuthConfig, OAuthUserConfig } from 'next-auth/providers/oauth';\n\nexport interface OAuth42Profile {\n sub: string;\n email: string;\n email_verified?: boolean;\n name?: string;\n given_name?: string;\n family_name?: string;\n picture?: string;\n username?: string;\n groups?: string[];\n id?: string;\n}\n\nexport interface OAuth42ProviderOptions {\n clientId: string;\n clientSecret: string;\n issuer?: string;\n authorizationUrl?: string;\n tokenUrl?: string;\n userinfoUrl?: string;\n scopes?: string[];\n pkceEnabled?: boolean;\n}\n\nexport function OAuth42Provider<P extends OAuth42Profile>(\n options: OAuthUserConfig<P> & Partial<OAuth42ProviderOptions>\n): OAuthConfig<P> {\n const issuer = options.issuer || process.env.OAUTH42_ISSUER || 'https://api.oauth42.com';\n const baseUrl = issuer.replace(/\\/$/, '');\n \n return {\n id: 'oauth42',\n name: 'OAuth42',\n type: 'oauth',\n version: '2.0',\n\n // Use OIDC discovery to automatically find endpoints\n wellKnown: `${baseUrl}/.well-known/openid-configuration`,\n\n // Use ID token for user profile (standard OIDC pattern)\n // This avoids an extra HTTP call to /userinfo on every auth flow\n // Apps can call /userinfo separately when they need extended claims (groups, company_id, etc.)\n idToken: true,\n\n // Also set individual endpoints for compatibility\n authorization: {\n url: `${baseUrl}/oauth2/authorize`,\n params: {\n scope: (options.scopes || ['openid', 'profile', 'email']).join(' '),\n response_type: 'code',\n },\n },\n token: `${baseUrl}/oauth2/token`,\n userinfo: `${baseUrl}/oauth2/userinfo`,\n\n client: {\n id: options.clientId,\n secret: options.clientSecret,\n token_endpoint_auth_method: 'client_secret_post',\n id_token_signed_response_alg: 'RS256', // OAuth42 uses RS256 for ID tokens\n },\n\n issuer: baseUrl,\n\n checks: options.pkceEnabled !== false ? ['pkce', 'state'] : ['state'],\n \n profile(profile: OAuth42Profile, tokens: any) {\n return {\n id: profile.sub || profile.id || profile.email,\n email: profile.email,\n emailVerified: profile.email_verified ? new Date() : null,\n name: profile.name || `${profile.given_name || ''} ${profile.family_name || ''}`.trim(),\n image: profile.picture,\n groups: profile.groups,\n };\n },\n \n style: {\n logo: '/oauth42-logo.svg',\n bg: '#1e40af',\n text: '#ffffff',\n },\n \n options,\n };\n}","import NextAuthDefault from 'next-auth';\nimport type { NextAuthOptions } from 'next-auth';\nimport { OAuth42Provider, OAuth42Profile } from '../provider';\nimport { getOAuth42Session } from './session';\n\n// Handle both CommonJS and ESM exports\nconst NextAuth = (NextAuthDefault as any).default || NextAuthDefault;\n\nexport { type NextAuthOptions };\n\n// Simple per-process lock for explicit refresh via getAccessToken()\nlet activeRefresh: Promise<any> | null = null;\n\nexport interface CreateAuthOptions {\n clientId?: string;\n clientSecret?: string;\n issuer?: string;\n scopes?: string[];\n pkceEnabled?: boolean;\n debug?: boolean;\n callbacks?: NextAuthOptions['callbacks'];\n pages?: NextAuthOptions['pages'];\n session?: NextAuthOptions['session'];\n /**\n * Unique prefix for cookie names to allow multiple apps on the same domain.\n * Each app should use a different prefix (e.g., 'portal', 'admin', 'bond').\n * This prevents session cookie conflicts when running multiple apps on localhost.\n */\n cookiePrefix?: string;\n}\n\n/**\n * Create a pre-configured NextAuth instance for OAuth42\n * This provides a simplified setup with sensible defaults\n */\nexport function createAuth(options: CreateAuthOptions = {}) {\n const clientId = options.clientId || process.env.OAUTH42_CLIENT_ID;\n const clientSecret = options.clientSecret || process.env.OAUTH42_CLIENT_SECRET;\n \n if (!clientId || !clientSecret) {\n throw new Error(\n 'OAuth42 client credentials are required. ' +\n 'Set OAUTH42_CLIENT_ID and OAUTH42_CLIENT_SECRET environment variables ' +\n 'or pass them in the options.'\n );\n }\n \n const authOptions: NextAuthOptions = {\n providers: [\n OAuth42Provider({\n clientId,\n clientSecret,\n issuer: options.issuer,\n scopes: options.scopes,\n pkceEnabled: options.pkceEnabled,\n }),\n ],\n \n callbacks: {\n async jwt({ token, account, profile }) {\n console.log('[OAuth42 SDK] JWT callback called', { hasAccount: !!account, hasProfile: !!profile });\n\n // Initial sign in - store OAuth tokens in the JWT\n if (account) {\n console.log('[OAuth42 SDK] Initial sign in - storing tokens in JWT');\n token.accessToken = account.access_token;\n token.refreshToken = account.refresh_token;\n token.expiresAt = account.expires_at;\n token.idToken = account.id_token;\n token.clientId = clientId;\n token.clientSecret = clientSecret;\n token.issuer = options.issuer || process.env.NEXT_PUBLIC_OAUTH_ISSUER || process.env.OAUTH42_ISSUER;\n }\n\n // Add user profile data\n if (profile) {\n const oauth42Profile = profile as OAuth42Profile;\n token.email = oauth42Profile.email;\n token.username = oauth42Profile.username;\n token.emailVerified = oauth42Profile.email_verified;\n token.groups = oauth42Profile.groups;\n }\n\n // NOTE: Token refresh is handled by middleware (withOAuth42Auth)\n // Middleware can properly set cookies after refresh, which JWT callback cannot\n // do in Next.js App Router Server Components.\n\n // Call custom callback if provided\n if (options.callbacks?.jwt) {\n return options.callbacks.jwt({ token, account, profile } as any);\n }\n\n console.log('[OAuth42 SDK] JWT callback complete, returning token');\n return token;\n },\n\n async session({ session, token }) {\n console.log('[OAuth42 SDK] Session callback called', { hasToken: !!token, hasSession: !!session });\n\n // Add OAuth42-specific data to session\n session.accessToken = token.accessToken as string;\n session.idToken = token.idToken as string;\n\n // Pass through any token refresh errors to the client\n if (token.error) {\n session.error = token.error as string;\n }\n\n if (session.user) {\n session.user.email = token.email as string;\n session.user.name = token.name as string;\n session.user.username = token.username as string;\n session.user.emailVerified = token.emailVerified as boolean;\n session.user.groups = token.groups as string[];\n }\n\n // Call custom callback if provided\n if (options.callbacks?.session) {\n return options.callbacks.session({ session, token } as any);\n }\n\n console.log('[OAuth42 SDK] Session callback complete, returning session');\n return session;\n },\n },\n \n pages: {\n signIn: '/auth/signin',\n signOut: '/auth/signout',\n error: '/auth/error',\n ...options.pages,\n },\n \n session: {\n strategy: 'jwt',\n ...options.session,\n },\n \n debug: options.debug || process.env.NODE_ENV === 'development',\n\n secret: process.env.NEXTAUTH_SECRET,\n\n // Configure unique cookie names per app to prevent session conflicts on localhost\n ...(options.cookiePrefix && {\n cookies: {\n sessionToken: {\n name: `${options.cookiePrefix}.session-token`,\n options: {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n },\n },\n callbackUrl: {\n name: `${options.cookiePrefix}.callback-url`,\n options: {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n },\n },\n csrfToken: {\n name: `${options.cookiePrefix}.csrf-token`,\n options: {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n },\n },\n // PKCE code_verifier cookie - essential for PKCE flow\n pkceCodeVerifier: {\n name: `${options.cookiePrefix}.pkce.code_verifier`,\n options: {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n maxAge: 900, // 15 minutes\n },\n },\n // State cookie for OAuth CSRF protection\n state: {\n name: `${options.cookiePrefix}.state`,\n options: {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n maxAge: 900, // 15 minutes\n },\n },\n // Nonce cookie for OpenID Connect\n nonce: {\n name: `${options.cookiePrefix}.nonce`,\n options: {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n },\n },\n },\n }),\n };\n \n // Return the configuration and handlers for API routes\n const handler = NextAuth(authOptions);\n return {\n auth: authOptions,\n handlers: { GET: handler, POST: handler },\n };\n}\n\n/**\n * Create NextAuth handlers for API routes\n */\nexport function createHandlers(authOptions: NextAuthOptions) {\n const handler = NextAuth(authOptions);\n return { GET: handler, POST: handler };\n}\n\n/**\n * Helper to get the current session server-side\n * @deprecated Use getOAuth42Session instead - this is now just an alias for backward compatibility\n * \n * This function is maintained for backward compatibility but internally\n * calls getOAuth42Session which properly handles both App Router and Pages Router\n */\nexport const getServerSession = getOAuth42Session;\n\n/**\n * Token refresh helper with simple per-process locking\n *\n * The lock prevents multiple concurrent refresh calls from the same process,\n * reducing unnecessary token churn. The backend also has a 10-second grace\n * period for blacklisted tokens, so concurrent requests across processes\n * will still succeed.\n */\nexport async function refreshAccessToken(token: any, clientId: string, clientSecret: string, issuer?: string): Promise<any> {\n // If a refresh is already in progress, wait for it\n if (activeRefresh) {\n console.log('[OAuth42] Refresh already in progress, waiting...');\n return await activeRefresh;\n }\n\n // Start the refresh and store the promise\n activeRefresh = doRefresh(token, clientId, clientSecret, issuer);\n try {\n return await activeRefresh;\n } finally {\n activeRefresh = null;\n }\n}\n\nasync function doRefresh(token: any, clientId: string, clientSecret: string, issuer?: string): Promise<any> {\n try {\n const baseUrl = issuer || process.env.OAUTH42_ISSUER || 'https://api.oauth42.com';\n const tokenUrl = `${baseUrl}/oauth2/token`;\n\n // In development, we need to handle self-signed certificates\n const fetchOptions: any = {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: token.refreshToken,\n client_id: clientId,\n client_secret: clientSecret,\n }),\n };\n\n // Add agent for self-signed certificates in development\n if (process.env.NODE_ENV !== 'production' && tokenUrl.startsWith('https://')) {\n const https = await import('https');\n fetchOptions.agent = new https.Agent({\n rejectUnauthorized: false\n });\n }\n\n const response = await fetch(tokenUrl, fetchOptions);\n const refreshedTokens = await response.json();\n\n if (!response.ok) {\n throw refreshedTokens;\n }\n\n console.log('[OAuth42] Token refreshed successfully');\n return {\n ...token,\n accessToken: refreshedTokens.access_token,\n refreshToken: refreshedTokens.refresh_token ?? token.refreshToken,\n // Store expiration time in seconds (Unix timestamp)\n expiresAt: Math.floor(Date.now() / 1000) + (refreshedTokens.expires_in || 3600),\n // Explicitly remove any error property on successful refresh\n error: undefined,\n };\n } catch (error) {\n console.error('[OAuth42] Failed to refresh access token:', error);\n return {\n ...token,\n error: 'RefreshAccessTokenError',\n };\n }\n}","import { getServerSession as getNextAuthSession } from 'next-auth';\nimport { NextAuthOptions } from 'next-auth';\nimport { GetServerSidePropsContext, NextApiRequest, NextApiResponse } from 'next';\nimport { headers } from 'next/headers';\n\n/** Header name used by middleware to pass refreshed tokens to API routes */\nconst REFRESHED_TOKEN_HEADER = 'x-oauth42-refreshed-token';\n\n/**\n * Get the OAuth42 session server-side\n *\n * This is the primary method for retrieving sessions in OAuth42 SDK.\n * Supports both Pages Router and App Router:\n *\n * App Router:\n * ```ts\n * const session = await getOAuth42Session(authOptions);\n * ```\n *\n * Pages Router:\n * ```ts\n * const session = await getOAuth42Session(req, res, authOptions);\n * ```\n *\n * **Token Refresh Support:**\n * When middleware refreshes an expired token, it passes the new token via\n * the `x-oauth42-refreshed-token` header. This function automatically detects\n * and uses that refreshed token, ensuring API routes always have a valid token.\n */\nexport async function getOAuth42Session(\n ...args:\n | [GetServerSidePropsContext['req'], GetServerSidePropsContext['res'], NextAuthOptions]\n | [NextApiRequest, NextApiResponse, NextAuthOptions]\n | [NextAuthOptions]\n) {\n const session = await getNextAuthSession(...args as any);\n\n if (!session) {\n return null;\n }\n\n // Check for refreshed token from middleware\n // This handles the race condition where middleware refreshes the token\n // but the API route still reads the old token from the session cookie\n let refreshedToken: string | null = null;\n\n try {\n // App Router: use headers() from next/headers\n if (args.length === 1) {\n const headersList = await headers();\n refreshedToken = headersList.get(REFRESHED_TOKEN_HEADER);\n }\n // Pages Router: check request headers\n else if (args.length === 3) {\n const req = args[0] as NextApiRequest | GetServerSidePropsContext['req'];\n refreshedToken = (req.headers[REFRESHED_TOKEN_HEADER] as string) || null;\n }\n } catch {\n // headers() may throw if called outside of a request context\n // This is fine - just use the session token as-is\n }\n\n if (refreshedToken) {\n const sessionToken = (session as any).accessToken as string | undefined;\n console.log('[OAuth42 Session] Using refreshed token from middleware:', {\n refreshedPrefix: refreshedToken.substring(0, 20),\n sessionPrefix: sessionToken?.substring(0, 20),\n tokensMatch: refreshedToken === sessionToken,\n });\n return {\n ...session,\n accessToken: refreshedToken,\n };\n }\n\n return session;\n}\n\n/**\n * Helper for protecting API routes\n */\nexport function withOAuth42Session(\n handler: (req: NextApiRequest, res: NextApiResponse, session: any) => Promise<void> | void,\n authOptions: NextAuthOptions\n) {\n return async (req: NextApiRequest, res: NextApiResponse) => {\n const session = await getOAuth42Session(req, res, authOptions);\n \n if (!session) {\n return res.status(401).json({ error: 'Unauthorized' });\n }\n \n return handler(req, res, session);\n };\n}\n\n/**\n * Helper for protecting server-side props\n */\nexport function withOAuth42ServerSideProps(\n getServerSideProps: (\n context: GetServerSidePropsContext,\n session: any\n ) => Promise<any>,\n authOptions: NextAuthOptions\n) {\n return async (context: GetServerSidePropsContext) => {\n const session = await getOAuth42Session(\n context.req,\n context.res,\n authOptions\n );\n \n if (!session) {\n return {\n redirect: {\n destination: '/auth/signin',\n permanent: false,\n },\n };\n }\n \n return getServerSideProps(context, session);\n };\n}","import { NextRequest, NextResponse } from 'next/server';\nimport { getToken, encode } from 'next-auth/jwt';\n\n/**\n * Cookie chunking constants — must match NextAuth's SessionStore behavior.\n * NextAuth splits cookies at ALLOWED_COOKIE_SIZE (4096) minus overhead (163).\n * When middleware re-encodes the JWT after a token refresh, the cookie must be\n * chunked the same way, otherwise:\n * - A single cookie > ~4 KB is silently rejected by the browser\n * - Leftover chunk cookies from the original session cause SessionStore to\n * concatenate old chunks with the new base cookie, producing garbage\n */\nconst ALLOWED_COOKIE_SIZE = 4096;\nconst ESTIMATED_EMPTY_COOKIE_SIZE = 163;\nconst CHUNK_SIZE = ALLOWED_COOKIE_SIZE - ESTIMATED_EMPTY_COOKIE_SIZE;\n\n/**\n * In-flight refresh tracking to prevent parallel refresh calls.\n * Key: user ID (from token.sub), Value: Promise that resolves with refresh result\n */\nconst pendingRefreshes = new Map<string, Promise<{\n success: boolean;\n accessToken?: string;\n refreshToken?: string;\n expiresAt?: number;\n error?: string;\n}>>();\n\nexport interface OAuth42AuthOptions {\n pages?: {\n signIn?: string;\n error?: string;\n };\n callbacks?: {\n authorized?: (params: { token: any; req: NextRequest }) => boolean | Promise<boolean>;\n };\n protectedPaths?: string[];\n publicPaths?: string[];\n /**\n * Cookie prefix for custom cookie names. Must match the prefix used in createAuth().\n * E.g., 'oauth42-portal' will look for cookie 'oauth42-portal.session-token'\n */\n cookiePrefix?: string;\n}\n\n/**\n * Refresh tokens by calling the OAuth42 backend directly\n */\nasync function refreshTokens(\n refreshToken: string,\n clientId: string,\n clientSecret: string,\n issuer: string\n): Promise<{ success: boolean; accessToken?: string; refreshToken?: string; expiresAt?: number; error?: string }> {\n try {\n const tokenUrl = `${issuer}/oauth2/token`;\n\n const response = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n client_id: clientId,\n client_secret: clientSecret,\n }),\n });\n\n const data = await response.json();\n\n if (!response.ok) {\n console.error('[OAuth42 Middleware] Token refresh failed:', data);\n return { success: false, error: data.error || 'refresh_failed' };\n }\n\n console.log('[OAuth42 Middleware] Token refreshed successfully');\n return {\n success: true,\n accessToken: data.access_token,\n refreshToken: data.refresh_token,\n expiresAt: Math.floor(Date.now() / 1000) + (data.expires_in || 3600),\n };\n } catch (error) {\n console.error('[OAuth42 Middleware] Token refresh error:', error);\n return { success: false, error: 'refresh_error' };\n }\n}\n\n/**\n * Middleware helper for protecting routes with OAuth42\n *\n * This middleware handles:\n * 1. Route protection (redirect to login if no session)\n * 2. Token refresh (refresh expired tokens and update cookie)\n */\nexport function withOAuth42Auth(options: OAuth42AuthOptions = {}) {\n const secret = process.env.NEXTAUTH_SECRET;\n const clientId = process.env.OAUTH42_CLIENT_ID;\n const clientSecret = process.env.OAUTH42_CLIENT_SECRET;\n const issuer = process.env.OAUTH42_ISSUER || 'https://localhost:8443';\n\n if (!secret) {\n console.warn('[OAuth42 Middleware] NEXTAUTH_SECRET not set');\n }\n\n return async function middleware(req: NextRequest) {\n // Build cookie name - if prefix is provided, use custom name\n const cookieName = options.cookiePrefix\n ? `${options.cookiePrefix}.session-token`\n : 'next-auth.session-token';\n\n const token = await getToken({\n req: req as any,\n secret,\n cookieName,\n });\n\n const pathname = req.nextUrl.pathname;\n\n // Check if path is explicitly public\n if (options.publicPaths?.some(path => pathname.startsWith(path))) {\n return NextResponse.next();\n }\n\n // Check if path needs protection\n const needsProtection = options.protectedPaths\n ? options.protectedPaths.some(path => pathname.startsWith(path))\n : true; // Default to protecting all paths\n\n if (!needsProtection) {\n return NextResponse.next();\n }\n\n // No token at all - redirect to sign in\n if (!token) {\n const signInUrl = options.pages?.signIn || '/auth/signin';\n const url = new URL(signInUrl, req.url);\n url.searchParams.set('callbackUrl', pathname);\n return NextResponse.redirect(url);\n }\n\n // Check if access token is expired or expiring soon (15 second buffer)\n // Buffer must be less than the token TTL to have a valid window\n const expiresAt = token.expiresAt as number | undefined;\n const now = Math.floor(Date.now() / 1000);\n const bufferSeconds = 15;\n const needsRefresh = expiresAt && now >= expiresAt - bufferSeconds;\n\n if (needsRefresh) {\n console.log('[OAuth42 Middleware] Token needs refresh:', {\n expiresAt,\n now,\n expiredAgo: expiresAt ? now - expiresAt : 'n/a',\n accessTokenPrefix: (token.accessToken as string)?.substring(0, 20),\n path: pathname,\n });\n }\n\n if (needsRefresh && token.refreshToken && clientId && clientSecret) {\n const userId = token.sub as string;\n\n // Check if there's already a refresh in progress for this user\n let refreshPromise = pendingRefreshes.get(userId);\n\n if (refreshPromise) {\n console.log('[OAuth42 Middleware] Waiting for in-flight refresh...');\n } else {\n console.log('[OAuth42 Middleware] Access token expired, refreshing...');\n\n // Create the refresh promise and store it.\n // Keep the entry for 30s after completion so late-arriving requests\n // (with stale cookies) reuse the cached result instead of triggering\n // a new refresh with the already-rotated token. See issue #470.\n refreshPromise = refreshTokens(\n token.refreshToken as string,\n clientId,\n clientSecret,\n issuer\n ).finally(() => {\n setTimeout(() => pendingRefreshes.delete(userId), 30000);\n });\n\n pendingRefreshes.set(userId, refreshPromise);\n }\n\n const refreshed = await refreshPromise;\n\n if (refreshed.success && refreshed.accessToken && refreshed.refreshToken) {\n // Update the token with new values\n const updatedToken = {\n ...token,\n accessToken: refreshed.accessToken,\n refreshToken: refreshed.refreshToken,\n expiresAt: refreshed.expiresAt,\n };\n\n // Re-encode the JWT\n const newJwt = await encode({\n token: updatedToken,\n secret: secret!,\n });\n\n // Create response with request headers that pass the new token to API routes\n // This is necessary because API routes read from the request, not the response cookie\n const requestHeaders = new Headers(req.headers);\n requestHeaders.set('x-oauth42-refreshed-token', refreshed.accessToken);\n\n const response = NextResponse.next({\n request: {\n headers: requestHeaders,\n },\n });\n\n const cookieOptions = {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n };\n\n // Set cookie with chunking matching NextAuth's SessionStore behavior.\n // Without chunking, a JWT > ~4 KB is silently rejected by the browser,\n // causing an infinite refresh loop where every request re-triggers\n // token refresh but the cookie never persists.\n if (newJwt.length <= CHUNK_SIZE) {\n // Fits in a single cookie\n response.cookies.set(cookieName, newJwt, cookieOptions);\n // Clean up any leftover chunk cookies from a previously chunked session\n for (let i = 0; ; i++) {\n const chunkName = `${cookieName}.${i}`;\n if (req.cookies.has(chunkName)) {\n response.cookies.set(chunkName, '', { ...cookieOptions, maxAge: 0 });\n } else {\n break;\n }\n }\n } else {\n // Cookie too large — chunk it like NextAuth does\n const chunkCount = Math.ceil(newJwt.length / CHUNK_SIZE);\n for (let i = 0; i < chunkCount; i++) {\n const chunkName = `${cookieName}.${i}`;\n const chunkValue = newJwt.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);\n response.cookies.set(chunkName, chunkValue, cookieOptions);\n }\n // Delete the base cookie so SessionStore only reads chunks\n response.cookies.set(cookieName, '', { ...cookieOptions, maxAge: 0 });\n // Clean up any extra old chunks beyond the new count\n for (let i = chunkCount; ; i++) {\n const chunkName = `${cookieName}.${i}`;\n if (req.cookies.has(chunkName)) {\n response.cookies.set(chunkName, '', { ...cookieOptions, maxAge: 0 });\n } else {\n break;\n }\n }\n }\n\n // Log details to help diagnose cookie persistence issues\n const existingChunks = [];\n for (let i = 0; ; i++) {\n if (req.cookies.has(`${cookieName}.${i}`)) {\n existingChunks.push(i);\n } else {\n break;\n }\n }\n console.log(`[OAuth42 Middleware] Token refresh complete:`, {\n jwtBytes: newJwt.length,\n chunked: newJwt.length > CHUNK_SIZE,\n existingBaseCooke: req.cookies.has(cookieName),\n existingChunks: existingChunks.length > 0 ? existingChunks : 'none',\n newAccessTokenPrefix: refreshed.accessToken?.substring(0, 20),\n newExpiresAt: refreshed.expiresAt,\n cookieName,\n });\n return response;\n } else {\n // Refresh failed - clear cookie and redirect to sign in (no error, just let user log in fresh)\n console.log('[OAuth42 Middleware] Refresh failed (likely token blacklisted after deploy), clearing session');\n const signInUrl = options.pages?.signIn || '/auth/signin';\n const url = new URL(signInUrl, req.url);\n url.searchParams.set('callbackUrl', pathname);\n // DO NOT set error parameter - just redirect silently\n\n const response = NextResponse.redirect(url);\n // Clear the base session cookie and any chunk cookies\n response.cookies.delete(cookieName);\n for (let i = 0; ; i++) {\n const chunkName = `${cookieName}.${i}`;\n if (req.cookies.has(chunkName)) {\n response.cookies.delete(chunkName);\n } else {\n break;\n }\n }\n return response;\n }\n }\n\n // Check custom authorization callback\n if (options.callbacks?.authorized) {\n const isAuthorized = await options.callbacks.authorized({ token, req });\n if (!isAuthorized) {\n const signInUrl = options.pages?.signIn || '/auth/signin';\n const url = new URL(signInUrl, req.url);\n url.searchParams.set('callbackUrl', pathname);\n return NextResponse.redirect(url);\n }\n }\n\n return NextResponse.next();\n };\n}\n\n/**\n * Helper to create middleware configuration\n */\nexport function createMiddlewareConfig(\n protectedPaths: string[] = ['/protected'],\n publicPaths: string[] = ['/auth', '/api/auth']\n) {\n return {\n matcher: [\n /*\n * Match all request paths except for the ones starting with:\n * - _next/static (static files)\n * - _next/image (image optimization files)\n * - favicon.ico (favicon file)\n * - public folder\n */\n '/((?!_next/static|_next/image|favicon.ico|public).*)',\n ],\n protectedPaths,\n publicPaths,\n };\n}\n"],"mappings":"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","names":["import_next_auth","getNextAuthSession","NextAuthDefault"]}
package/dist/index.mjs CHANGED
@@ -75,7 +75,12 @@ async function getOAuth42Session(...args) {
75
75
  } catch {
76
76
  }
77
77
  if (refreshedToken) {
78
- console.log("[OAuth42 Session] Using refreshed token from middleware");
78
+ const sessionToken = session.accessToken;
79
+ console.log("[OAuth42 Session] Using refreshed token from middleware:", {
80
+ refreshedPrefix: refreshedToken.substring(0, 20),
81
+ sessionPrefix: sessionToken?.substring(0, 20),
82
+ tokensMatch: refreshedToken === sessionToken
83
+ });
79
84
  return {
80
85
  ...session,
81
86
  accessToken: refreshedToken
@@ -326,6 +331,9 @@ async function doRefresh(token, clientId, clientSecret, issuer) {
326
331
  // src/server/middleware.ts
327
332
  import { NextResponse } from "next/server";
328
333
  import { getToken, encode } from "next-auth/jwt";
334
+ var ALLOWED_COOKIE_SIZE = 4096;
335
+ var ESTIMATED_EMPTY_COOKIE_SIZE = 163;
336
+ var CHUNK_SIZE = ALLOWED_COOKIE_SIZE - ESTIMATED_EMPTY_COOKIE_SIZE;
329
337
  var pendingRefreshes = /* @__PURE__ */ new Map();
330
338
  async function refreshTokens(refreshToken, clientId, clientSecret, issuer) {
331
339
  try {
@@ -392,6 +400,15 @@ function withOAuth42Auth(options = {}) {
392
400
  const now = Math.floor(Date.now() / 1e3);
393
401
  const bufferSeconds = 15;
394
402
  const needsRefresh = expiresAt && now >= expiresAt - bufferSeconds;
403
+ if (needsRefresh) {
404
+ console.log("[OAuth42 Middleware] Token needs refresh:", {
405
+ expiresAt,
406
+ now,
407
+ expiredAgo: expiresAt ? now - expiresAt : "n/a",
408
+ accessTokenPrefix: token.accessToken?.substring(0, 20),
409
+ path: pathname
410
+ });
411
+ }
395
412
  if (needsRefresh && token.refreshToken && clientId && clientSecret) {
396
413
  const userId = token.sub;
397
414
  let refreshPromise = pendingRefreshes.get(userId);
@@ -405,7 +422,7 @@ function withOAuth42Auth(options = {}) {
405
422
  clientSecret,
406
423
  issuer
407
424
  ).finally(() => {
408
- pendingRefreshes.delete(userId);
425
+ setTimeout(() => pendingRefreshes.delete(userId), 3e4);
409
426
  });
410
427
  pendingRefreshes.set(userId, refreshPromise);
411
428
  }
@@ -428,13 +445,56 @@ function withOAuth42Auth(options = {}) {
428
445
  headers: requestHeaders
429
446
  }
430
447
  });
431
- response.cookies.set(cookieName, newJwt, {
448
+ const cookieOptions = {
432
449
  httpOnly: true,
433
450
  sameSite: "lax",
434
451
  path: "/",
435
452
  secure: process.env.NODE_ENV === "production"
453
+ };
454
+ if (newJwt.length <= CHUNK_SIZE) {
455
+ response.cookies.set(cookieName, newJwt, cookieOptions);
456
+ for (let i = 0; ; i++) {
457
+ const chunkName = `${cookieName}.${i}`;
458
+ if (req.cookies.has(chunkName)) {
459
+ response.cookies.set(chunkName, "", { ...cookieOptions, maxAge: 0 });
460
+ } else {
461
+ break;
462
+ }
463
+ }
464
+ } else {
465
+ const chunkCount = Math.ceil(newJwt.length / CHUNK_SIZE);
466
+ for (let i = 0; i < chunkCount; i++) {
467
+ const chunkName = `${cookieName}.${i}`;
468
+ const chunkValue = newJwt.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);
469
+ response.cookies.set(chunkName, chunkValue, cookieOptions);
470
+ }
471
+ response.cookies.set(cookieName, "", { ...cookieOptions, maxAge: 0 });
472
+ for (let i = chunkCount; ; i++) {
473
+ const chunkName = `${cookieName}.${i}`;
474
+ if (req.cookies.has(chunkName)) {
475
+ response.cookies.set(chunkName, "", { ...cookieOptions, maxAge: 0 });
476
+ } else {
477
+ break;
478
+ }
479
+ }
480
+ }
481
+ const existingChunks = [];
482
+ for (let i = 0; ; i++) {
483
+ if (req.cookies.has(`${cookieName}.${i}`)) {
484
+ existingChunks.push(i);
485
+ } else {
486
+ break;
487
+ }
488
+ }
489
+ console.log(`[OAuth42 Middleware] Token refresh complete:`, {
490
+ jwtBytes: newJwt.length,
491
+ chunked: newJwt.length > CHUNK_SIZE,
492
+ existingBaseCooke: req.cookies.has(cookieName),
493
+ existingChunks: existingChunks.length > 0 ? existingChunks : "none",
494
+ newAccessTokenPrefix: refreshed.accessToken?.substring(0, 20),
495
+ newExpiresAt: refreshed.expiresAt,
496
+ cookieName
436
497
  });
437
- console.log("[OAuth42 Middleware] Cookie updated with refreshed tokens, header set for current request");
438
498
  return response;
439
499
  } else {
440
500
  console.log("[OAuth42 Middleware] Refresh failed (likely token blacklisted after deploy), clearing session");
@@ -443,6 +503,14 @@ function withOAuth42Auth(options = {}) {
443
503
  url.searchParams.set("callbackUrl", pathname);
444
504
  const response = NextResponse.redirect(url);
445
505
  response.cookies.delete(cookieName);
506
+ for (let i = 0; ; i++) {
507
+ const chunkName = `${cookieName}.${i}`;
508
+ if (req.cookies.has(chunkName)) {
509
+ response.cookies.delete(chunkName);
510
+ } else {
511
+ break;
512
+ }
513
+ }
446
514
  return response;
447
515
  }
448
516
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/provider.ts","../src/server/auth.ts","../src/server/session.ts","../src/server/middleware.ts"],"sourcesContent":["import type { OAuthConfig, OAuthUserConfig } from 'next-auth/providers/oauth';\n\nexport interface OAuth42Profile {\n sub: string;\n email: string;\n email_verified?: boolean;\n name?: string;\n given_name?: string;\n family_name?: string;\n picture?: string;\n username?: string;\n groups?: string[];\n id?: string;\n}\n\nexport interface OAuth42ProviderOptions {\n clientId: string;\n clientSecret: string;\n issuer?: string;\n authorizationUrl?: string;\n tokenUrl?: string;\n userinfoUrl?: string;\n scopes?: string[];\n pkceEnabled?: boolean;\n}\n\nexport function OAuth42Provider<P extends OAuth42Profile>(\n options: OAuthUserConfig<P> & Partial<OAuth42ProviderOptions>\n): OAuthConfig<P> {\n const issuer = options.issuer || process.env.OAUTH42_ISSUER || 'https://api.oauth42.com';\n const baseUrl = issuer.replace(/\\/$/, '');\n \n return {\n id: 'oauth42',\n name: 'OAuth42',\n type: 'oauth',\n version: '2.0',\n\n // Use OIDC discovery to automatically find endpoints\n wellKnown: `${baseUrl}/.well-known/openid-configuration`,\n\n // Use ID token for user profile (standard OIDC pattern)\n // This avoids an extra HTTP call to /userinfo on every auth flow\n // Apps can call /userinfo separately when they need extended claims (groups, company_id, etc.)\n idToken: true,\n\n // Also set individual endpoints for compatibility\n authorization: {\n url: `${baseUrl}/oauth2/authorize`,\n params: {\n scope: (options.scopes || ['openid', 'profile', 'email']).join(' '),\n response_type: 'code',\n },\n },\n token: `${baseUrl}/oauth2/token`,\n userinfo: `${baseUrl}/oauth2/userinfo`,\n\n client: {\n id: options.clientId,\n secret: options.clientSecret,\n token_endpoint_auth_method: 'client_secret_post',\n id_token_signed_response_alg: 'RS256', // OAuth42 uses RS256 for ID tokens\n },\n\n issuer: baseUrl,\n\n checks: options.pkceEnabled !== false ? ['pkce', 'state'] : ['state'],\n \n profile(profile: OAuth42Profile, tokens: any) {\n return {\n id: profile.sub || profile.id || profile.email,\n email: profile.email,\n emailVerified: profile.email_verified ? new Date() : null,\n name: profile.name || `${profile.given_name || ''} ${profile.family_name || ''}`.trim(),\n image: profile.picture,\n groups: profile.groups,\n };\n },\n \n style: {\n logo: '/oauth42-logo.svg',\n bg: '#1e40af',\n text: '#ffffff',\n },\n \n options,\n };\n}","import NextAuthDefault from 'next-auth';\nimport type { NextAuthOptions } from 'next-auth';\nimport { OAuth42Provider, OAuth42Profile } from '../provider';\nimport { getOAuth42Session } from './session';\n\n// Handle both CommonJS and ESM exports\nconst NextAuth = (NextAuthDefault as any).default || NextAuthDefault;\n\nexport { type NextAuthOptions };\n\n// Simple per-process lock for explicit refresh via getAccessToken()\nlet activeRefresh: Promise<any> | null = null;\n\nexport interface CreateAuthOptions {\n clientId?: string;\n clientSecret?: string;\n issuer?: string;\n scopes?: string[];\n pkceEnabled?: boolean;\n debug?: boolean;\n callbacks?: NextAuthOptions['callbacks'];\n pages?: NextAuthOptions['pages'];\n session?: NextAuthOptions['session'];\n /**\n * Unique prefix for cookie names to allow multiple apps on the same domain.\n * Each app should use a different prefix (e.g., 'portal', 'admin', 'bond').\n * This prevents session cookie conflicts when running multiple apps on localhost.\n */\n cookiePrefix?: string;\n}\n\n/**\n * Create a pre-configured NextAuth instance for OAuth42\n * This provides a simplified setup with sensible defaults\n */\nexport function createAuth(options: CreateAuthOptions = {}) {\n const clientId = options.clientId || process.env.OAUTH42_CLIENT_ID;\n const clientSecret = options.clientSecret || process.env.OAUTH42_CLIENT_SECRET;\n \n if (!clientId || !clientSecret) {\n throw new Error(\n 'OAuth42 client credentials are required. ' +\n 'Set OAUTH42_CLIENT_ID and OAUTH42_CLIENT_SECRET environment variables ' +\n 'or pass them in the options.'\n );\n }\n \n const authOptions: NextAuthOptions = {\n providers: [\n OAuth42Provider({\n clientId,\n clientSecret,\n issuer: options.issuer,\n scopes: options.scopes,\n pkceEnabled: options.pkceEnabled,\n }),\n ],\n \n callbacks: {\n async jwt({ token, account, profile }) {\n console.log('[OAuth42 SDK] JWT callback called', { hasAccount: !!account, hasProfile: !!profile });\n\n // Initial sign in - store OAuth tokens in the JWT\n if (account) {\n console.log('[OAuth42 SDK] Initial sign in - storing tokens in JWT');\n token.accessToken = account.access_token;\n token.refreshToken = account.refresh_token;\n token.expiresAt = account.expires_at;\n token.idToken = account.id_token;\n token.clientId = clientId;\n token.clientSecret = clientSecret;\n token.issuer = options.issuer || process.env.NEXT_PUBLIC_OAUTH_ISSUER || process.env.OAUTH42_ISSUER;\n }\n\n // Add user profile data\n if (profile) {\n const oauth42Profile = profile as OAuth42Profile;\n token.email = oauth42Profile.email;\n token.username = oauth42Profile.username;\n token.emailVerified = oauth42Profile.email_verified;\n token.groups = oauth42Profile.groups;\n }\n\n // NOTE: Token refresh is handled by middleware (withOAuth42Auth)\n // Middleware can properly set cookies after refresh, which JWT callback cannot\n // do in Next.js App Router Server Components.\n\n // Call custom callback if provided\n if (options.callbacks?.jwt) {\n return options.callbacks.jwt({ token, account, profile } as any);\n }\n\n console.log('[OAuth42 SDK] JWT callback complete, returning token');\n return token;\n },\n\n async session({ session, token }) {\n console.log('[OAuth42 SDK] Session callback called', { hasToken: !!token, hasSession: !!session });\n\n // Add OAuth42-specific data to session\n session.accessToken = token.accessToken as string;\n session.idToken = token.idToken as string;\n\n // Pass through any token refresh errors to the client\n if (token.error) {\n session.error = token.error as string;\n }\n\n if (session.user) {\n session.user.email = token.email as string;\n session.user.name = token.name as string;\n session.user.username = token.username as string;\n session.user.emailVerified = token.emailVerified as boolean;\n session.user.groups = token.groups as string[];\n }\n\n // Call custom callback if provided\n if (options.callbacks?.session) {\n return options.callbacks.session({ session, token } as any);\n }\n\n console.log('[OAuth42 SDK] Session callback complete, returning session');\n return session;\n },\n },\n \n pages: {\n signIn: '/auth/signin',\n signOut: '/auth/signout',\n error: '/auth/error',\n ...options.pages,\n },\n \n session: {\n strategy: 'jwt',\n ...options.session,\n },\n \n debug: options.debug || process.env.NODE_ENV === 'development',\n\n secret: process.env.NEXTAUTH_SECRET,\n\n // Configure unique cookie names per app to prevent session conflicts on localhost\n ...(options.cookiePrefix && {\n cookies: {\n sessionToken: {\n name: `${options.cookiePrefix}.session-token`,\n options: {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n },\n },\n callbackUrl: {\n name: `${options.cookiePrefix}.callback-url`,\n options: {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n },\n },\n csrfToken: {\n name: `${options.cookiePrefix}.csrf-token`,\n options: {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n },\n },\n // PKCE code_verifier cookie - essential for PKCE flow\n pkceCodeVerifier: {\n name: `${options.cookiePrefix}.pkce.code_verifier`,\n options: {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n maxAge: 900, // 15 minutes\n },\n },\n // State cookie for OAuth CSRF protection\n state: {\n name: `${options.cookiePrefix}.state`,\n options: {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n maxAge: 900, // 15 minutes\n },\n },\n // Nonce cookie for OpenID Connect\n nonce: {\n name: `${options.cookiePrefix}.nonce`,\n options: {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n },\n },\n },\n }),\n };\n \n // Return the configuration and handlers for API routes\n const handler = NextAuth(authOptions);\n return {\n auth: authOptions,\n handlers: { GET: handler, POST: handler },\n };\n}\n\n/**\n * Create NextAuth handlers for API routes\n */\nexport function createHandlers(authOptions: NextAuthOptions) {\n const handler = NextAuth(authOptions);\n return { GET: handler, POST: handler };\n}\n\n/**\n * Helper to get the current session server-side\n * @deprecated Use getOAuth42Session instead - this is now just an alias for backward compatibility\n * \n * This function is maintained for backward compatibility but internally\n * calls getOAuth42Session which properly handles both App Router and Pages Router\n */\nexport const getServerSession = getOAuth42Session;\n\n/**\n * Token refresh helper with simple per-process locking\n *\n * The lock prevents multiple concurrent refresh calls from the same process,\n * reducing unnecessary token churn. The backend also has a 10-second grace\n * period for blacklisted tokens, so concurrent requests across processes\n * will still succeed.\n */\nexport async function refreshAccessToken(token: any, clientId: string, clientSecret: string, issuer?: string): Promise<any> {\n // If a refresh is already in progress, wait for it\n if (activeRefresh) {\n console.log('[OAuth42] Refresh already in progress, waiting...');\n return await activeRefresh;\n }\n\n // Start the refresh and store the promise\n activeRefresh = doRefresh(token, clientId, clientSecret, issuer);\n try {\n return await activeRefresh;\n } finally {\n activeRefresh = null;\n }\n}\n\nasync function doRefresh(token: any, clientId: string, clientSecret: string, issuer?: string): Promise<any> {\n try {\n const baseUrl = issuer || process.env.OAUTH42_ISSUER || 'https://api.oauth42.com';\n const tokenUrl = `${baseUrl}/oauth2/token`;\n\n // In development, we need to handle self-signed certificates\n const fetchOptions: any = {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: token.refreshToken,\n client_id: clientId,\n client_secret: clientSecret,\n }),\n };\n\n // Add agent for self-signed certificates in development\n if (process.env.NODE_ENV !== 'production' && tokenUrl.startsWith('https://')) {\n const https = await import('https');\n fetchOptions.agent = new https.Agent({\n rejectUnauthorized: false\n });\n }\n\n const response = await fetch(tokenUrl, fetchOptions);\n const refreshedTokens = await response.json();\n\n if (!response.ok) {\n throw refreshedTokens;\n }\n\n console.log('[OAuth42] Token refreshed successfully');\n return {\n ...token,\n accessToken: refreshedTokens.access_token,\n refreshToken: refreshedTokens.refresh_token ?? token.refreshToken,\n // Store expiration time in seconds (Unix timestamp)\n expiresAt: Math.floor(Date.now() / 1000) + (refreshedTokens.expires_in || 3600),\n // Explicitly remove any error property on successful refresh\n error: undefined,\n };\n } catch (error) {\n console.error('[OAuth42] Failed to refresh access token:', error);\n return {\n ...token,\n error: 'RefreshAccessTokenError',\n };\n }\n}","import { getServerSession as getNextAuthSession } from 'next-auth';\nimport { NextAuthOptions } from 'next-auth';\nimport { GetServerSidePropsContext, NextApiRequest, NextApiResponse } from 'next';\nimport { headers } from 'next/headers';\n\n/** Header name used by middleware to pass refreshed tokens to API routes */\nconst REFRESHED_TOKEN_HEADER = 'x-oauth42-refreshed-token';\n\n/**\n * Get the OAuth42 session server-side\n *\n * This is the primary method for retrieving sessions in OAuth42 SDK.\n * Supports both Pages Router and App Router:\n *\n * App Router:\n * ```ts\n * const session = await getOAuth42Session(authOptions);\n * ```\n *\n * Pages Router:\n * ```ts\n * const session = await getOAuth42Session(req, res, authOptions);\n * ```\n *\n * **Token Refresh Support:**\n * When middleware refreshes an expired token, it passes the new token via\n * the `x-oauth42-refreshed-token` header. This function automatically detects\n * and uses that refreshed token, ensuring API routes always have a valid token.\n */\nexport async function getOAuth42Session(\n ...args:\n | [GetServerSidePropsContext['req'], GetServerSidePropsContext['res'], NextAuthOptions]\n | [NextApiRequest, NextApiResponse, NextAuthOptions]\n | [NextAuthOptions]\n) {\n const session = await getNextAuthSession(...args as any);\n\n if (!session) {\n return null;\n }\n\n // Check for refreshed token from middleware\n // This handles the race condition where middleware refreshes the token\n // but the API route still reads the old token from the session cookie\n let refreshedToken: string | null = null;\n\n try {\n // App Router: use headers() from next/headers\n if (args.length === 1) {\n const headersList = await headers();\n refreshedToken = headersList.get(REFRESHED_TOKEN_HEADER);\n }\n // Pages Router: check request headers\n else if (args.length === 3) {\n const req = args[0] as NextApiRequest | GetServerSidePropsContext['req'];\n refreshedToken = (req.headers[REFRESHED_TOKEN_HEADER] as string) || null;\n }\n } catch {\n // headers() may throw if called outside of a request context\n // This is fine - just use the session token as-is\n }\n\n if (refreshedToken) {\n console.log('[OAuth42 Session] Using refreshed token from middleware');\n return {\n ...session,\n accessToken: refreshedToken,\n };\n }\n\n return session;\n}\n\n/**\n * Helper for protecting API routes\n */\nexport function withOAuth42Session(\n handler: (req: NextApiRequest, res: NextApiResponse, session: any) => Promise<void> | void,\n authOptions: NextAuthOptions\n) {\n return async (req: NextApiRequest, res: NextApiResponse) => {\n const session = await getOAuth42Session(req, res, authOptions);\n \n if (!session) {\n return res.status(401).json({ error: 'Unauthorized' });\n }\n \n return handler(req, res, session);\n };\n}\n\n/**\n * Helper for protecting server-side props\n */\nexport function withOAuth42ServerSideProps(\n getServerSideProps: (\n context: GetServerSidePropsContext,\n session: any\n ) => Promise<any>,\n authOptions: NextAuthOptions\n) {\n return async (context: GetServerSidePropsContext) => {\n const session = await getOAuth42Session(\n context.req,\n context.res,\n authOptions\n );\n \n if (!session) {\n return {\n redirect: {\n destination: '/auth/signin',\n permanent: false,\n },\n };\n }\n \n return getServerSideProps(context, session);\n };\n}","import { NextRequest, NextResponse } from 'next/server';\nimport { getToken, encode } from 'next-auth/jwt';\n\n/**\n * In-flight refresh tracking to prevent parallel refresh calls.\n * Key: user ID (from token.sub), Value: Promise that resolves with refresh result\n */\nconst pendingRefreshes = new Map<string, Promise<{\n success: boolean;\n accessToken?: string;\n refreshToken?: string;\n expiresAt?: number;\n error?: string;\n}>>();\n\nexport interface OAuth42AuthOptions {\n pages?: {\n signIn?: string;\n error?: string;\n };\n callbacks?: {\n authorized?: (params: { token: any; req: NextRequest }) => boolean | Promise<boolean>;\n };\n protectedPaths?: string[];\n publicPaths?: string[];\n /**\n * Cookie prefix for custom cookie names. Must match the prefix used in createAuth().\n * E.g., 'oauth42-portal' will look for cookie 'oauth42-portal.session-token'\n */\n cookiePrefix?: string;\n}\n\n/**\n * Refresh tokens by calling the OAuth42 backend directly\n */\nasync function refreshTokens(\n refreshToken: string,\n clientId: string,\n clientSecret: string,\n issuer: string\n): Promise<{ success: boolean; accessToken?: string; refreshToken?: string; expiresAt?: number; error?: string }> {\n try {\n const tokenUrl = `${issuer}/oauth2/token`;\n\n const response = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n client_id: clientId,\n client_secret: clientSecret,\n }),\n });\n\n const data = await response.json();\n\n if (!response.ok) {\n console.error('[OAuth42 Middleware] Token refresh failed:', data);\n return { success: false, error: data.error || 'refresh_failed' };\n }\n\n console.log('[OAuth42 Middleware] Token refreshed successfully');\n return {\n success: true,\n accessToken: data.access_token,\n refreshToken: data.refresh_token,\n expiresAt: Math.floor(Date.now() / 1000) + (data.expires_in || 3600),\n };\n } catch (error) {\n console.error('[OAuth42 Middleware] Token refresh error:', error);\n return { success: false, error: 'refresh_error' };\n }\n}\n\n/**\n * Middleware helper for protecting routes with OAuth42\n *\n * This middleware handles:\n * 1. Route protection (redirect to login if no session)\n * 2. Token refresh (refresh expired tokens and update cookie)\n */\nexport function withOAuth42Auth(options: OAuth42AuthOptions = {}) {\n const secret = process.env.NEXTAUTH_SECRET;\n const clientId = process.env.OAUTH42_CLIENT_ID;\n const clientSecret = process.env.OAUTH42_CLIENT_SECRET;\n const issuer = process.env.OAUTH42_ISSUER || 'https://localhost:8443';\n\n if (!secret) {\n console.warn('[OAuth42 Middleware] NEXTAUTH_SECRET not set');\n }\n\n return async function middleware(req: NextRequest) {\n // Build cookie name - if prefix is provided, use custom name\n const cookieName = options.cookiePrefix\n ? `${options.cookiePrefix}.session-token`\n : 'next-auth.session-token';\n\n const token = await getToken({\n req: req as any,\n secret,\n cookieName,\n });\n\n const pathname = req.nextUrl.pathname;\n\n // Check if path is explicitly public\n if (options.publicPaths?.some(path => pathname.startsWith(path))) {\n return NextResponse.next();\n }\n\n // Check if path needs protection\n const needsProtection = options.protectedPaths\n ? options.protectedPaths.some(path => pathname.startsWith(path))\n : true; // Default to protecting all paths\n\n if (!needsProtection) {\n return NextResponse.next();\n }\n\n // No token at all - redirect to sign in\n if (!token) {\n const signInUrl = options.pages?.signIn || '/auth/signin';\n const url = new URL(signInUrl, req.url);\n url.searchParams.set('callbackUrl', pathname);\n return NextResponse.redirect(url);\n }\n\n // Check if access token is expired or expiring soon (15 second buffer)\n // Buffer must be less than the token TTL to have a valid window\n const expiresAt = token.expiresAt as number | undefined;\n const now = Math.floor(Date.now() / 1000);\n const bufferSeconds = 15;\n const needsRefresh = expiresAt && now >= expiresAt - bufferSeconds;\n\n if (needsRefresh && token.refreshToken && clientId && clientSecret) {\n const userId = token.sub as string;\n\n // Check if there's already a refresh in progress for this user\n let refreshPromise = pendingRefreshes.get(userId);\n\n if (refreshPromise) {\n console.log('[OAuth42 Middleware] Waiting for in-flight refresh...');\n } else {\n console.log('[OAuth42 Middleware] Access token expired, refreshing...');\n\n // Create the refresh promise and store it\n refreshPromise = refreshTokens(\n token.refreshToken as string,\n clientId,\n clientSecret,\n issuer\n ).finally(() => {\n // Clean up after refresh completes (success or failure)\n pendingRefreshes.delete(userId);\n });\n\n pendingRefreshes.set(userId, refreshPromise);\n }\n\n const refreshed = await refreshPromise;\n\n if (refreshed.success && refreshed.accessToken && refreshed.refreshToken) {\n // Update the token with new values\n const updatedToken = {\n ...token,\n accessToken: refreshed.accessToken,\n refreshToken: refreshed.refreshToken,\n expiresAt: refreshed.expiresAt,\n };\n\n // Re-encode the JWT\n const newJwt = await encode({\n token: updatedToken,\n secret: secret!,\n });\n\n // Create response with request headers that pass the new token to API routes\n // This is necessary because API routes read from the request, not the response cookie\n const requestHeaders = new Headers(req.headers);\n requestHeaders.set('x-oauth42-refreshed-token', refreshed.accessToken);\n\n const response = NextResponse.next({\n request: {\n headers: requestHeaders,\n },\n });\n\n // Set cookie with same settings NextAuth uses (for future requests)\n response.cookies.set(cookieName, newJwt, {\n httpOnly: true,\n sameSite: 'lax',\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n });\n\n console.log('[OAuth42 Middleware] Cookie updated with refreshed tokens, header set for current request');\n return response;\n } else {\n // Refresh failed - clear cookie and redirect to sign in (no error, just let user log in fresh)\n console.log('[OAuth42 Middleware] Refresh failed (likely token blacklisted after deploy), clearing session');\n const signInUrl = options.pages?.signIn || '/auth/signin';\n const url = new URL(signInUrl, req.url);\n url.searchParams.set('callbackUrl', pathname);\n // DO NOT set error parameter - just redirect silently\n\n const response = NextResponse.redirect(url);\n // Clear the old session cookie\n response.cookies.delete(cookieName);\n return response;\n }\n }\n\n // Check custom authorization callback\n if (options.callbacks?.authorized) {\n const isAuthorized = await options.callbacks.authorized({ token, req });\n if (!isAuthorized) {\n const signInUrl = options.pages?.signIn || '/auth/signin';\n const url = new URL(signInUrl, req.url);\n url.searchParams.set('callbackUrl', pathname);\n return NextResponse.redirect(url);\n }\n }\n\n return NextResponse.next();\n };\n}\n\n/**\n * Helper to create middleware configuration\n */\nexport function createMiddlewareConfig(\n protectedPaths: string[] = ['/protected'],\n publicPaths: string[] = ['/auth', '/api/auth']\n) {\n return {\n matcher: [\n /*\n * Match all request paths except for the ones starting with:\n * - _next/static (static files)\n * - _next/image (image optimization files)\n * - favicon.ico (favicon file)\n * - public folder\n */\n '/((?!_next/static|_next/image|favicon.ico|public).*)',\n ],\n protectedPaths,\n publicPaths,\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/provider.ts","../src/server/auth.ts","../src/server/session.ts","../src/server/middleware.ts"],"sourcesContent":["import type { OAuthConfig, OAuthUserConfig } from 'next-auth/providers/oauth';\n\nexport interface OAuth42Profile {\n sub: string;\n email: string;\n email_verified?: boolean;\n name?: string;\n given_name?: string;\n family_name?: string;\n picture?: string;\n username?: string;\n groups?: string[];\n id?: string;\n}\n\nexport interface OAuth42ProviderOptions {\n clientId: string;\n clientSecret: string;\n issuer?: string;\n authorizationUrl?: string;\n tokenUrl?: string;\n userinfoUrl?: string;\n scopes?: string[];\n pkceEnabled?: boolean;\n}\n\nexport function OAuth42Provider<P extends OAuth42Profile>(\n options: OAuthUserConfig<P> & Partial<OAuth42ProviderOptions>\n): OAuthConfig<P> {\n const issuer = options.issuer || process.env.OAUTH42_ISSUER || 'https://api.oauth42.com';\n const baseUrl = issuer.replace(/\\/$/, '');\n \n return {\n id: 'oauth42',\n name: 'OAuth42',\n type: 'oauth',\n version: '2.0',\n\n // Use OIDC discovery to automatically find endpoints\n wellKnown: `${baseUrl}/.well-known/openid-configuration`,\n\n // Use ID token for user profile (standard OIDC pattern)\n // This avoids an extra HTTP call to /userinfo on every auth flow\n // Apps can call /userinfo separately when they need extended claims (groups, company_id, etc.)\n idToken: true,\n\n // Also set individual endpoints for compatibility\n authorization: {\n url: `${baseUrl}/oauth2/authorize`,\n params: {\n scope: (options.scopes || ['openid', 'profile', 'email']).join(' '),\n response_type: 'code',\n },\n },\n token: `${baseUrl}/oauth2/token`,\n userinfo: `${baseUrl}/oauth2/userinfo`,\n\n client: {\n id: options.clientId,\n secret: options.clientSecret,\n token_endpoint_auth_method: 'client_secret_post',\n id_token_signed_response_alg: 'RS256', // OAuth42 uses RS256 for ID tokens\n },\n\n issuer: baseUrl,\n\n checks: options.pkceEnabled !== false ? ['pkce', 'state'] : ['state'],\n \n profile(profile: OAuth42Profile, tokens: any) {\n return {\n id: profile.sub || profile.id || profile.email,\n email: profile.email,\n emailVerified: profile.email_verified ? new Date() : null,\n name: profile.name || `${profile.given_name || ''} ${profile.family_name || ''}`.trim(),\n image: profile.picture,\n groups: profile.groups,\n };\n },\n \n style: {\n logo: '/oauth42-logo.svg',\n bg: '#1e40af',\n text: '#ffffff',\n },\n \n options,\n };\n}","import NextAuthDefault from 'next-auth';\nimport type { NextAuthOptions } from 'next-auth';\nimport { OAuth42Provider, OAuth42Profile } from '../provider';\nimport { getOAuth42Session } from './session';\n\n// Handle both CommonJS and ESM exports\nconst NextAuth = (NextAuthDefault as any).default || NextAuthDefault;\n\nexport { type NextAuthOptions };\n\n// Simple per-process lock for explicit refresh via getAccessToken()\nlet activeRefresh: Promise<any> | null = null;\n\nexport interface CreateAuthOptions {\n clientId?: string;\n clientSecret?: string;\n issuer?: string;\n scopes?: string[];\n pkceEnabled?: boolean;\n debug?: boolean;\n callbacks?: NextAuthOptions['callbacks'];\n pages?: NextAuthOptions['pages'];\n session?: NextAuthOptions['session'];\n /**\n * Unique prefix for cookie names to allow multiple apps on the same domain.\n * Each app should use a different prefix (e.g., 'portal', 'admin', 'bond').\n * This prevents session cookie conflicts when running multiple apps on localhost.\n */\n cookiePrefix?: string;\n}\n\n/**\n * Create a pre-configured NextAuth instance for OAuth42\n * This provides a simplified setup with sensible defaults\n */\nexport function createAuth(options: CreateAuthOptions = {}) {\n const clientId = options.clientId || process.env.OAUTH42_CLIENT_ID;\n const clientSecret = options.clientSecret || process.env.OAUTH42_CLIENT_SECRET;\n \n if (!clientId || !clientSecret) {\n throw new Error(\n 'OAuth42 client credentials are required. ' +\n 'Set OAUTH42_CLIENT_ID and OAUTH42_CLIENT_SECRET environment variables ' +\n 'or pass them in the options.'\n );\n }\n \n const authOptions: NextAuthOptions = {\n providers: [\n OAuth42Provider({\n clientId,\n clientSecret,\n issuer: options.issuer,\n scopes: options.scopes,\n pkceEnabled: options.pkceEnabled,\n }),\n ],\n \n callbacks: {\n async jwt({ token, account, profile }) {\n console.log('[OAuth42 SDK] JWT callback called', { hasAccount: !!account, hasProfile: !!profile });\n\n // Initial sign in - store OAuth tokens in the JWT\n if (account) {\n console.log('[OAuth42 SDK] Initial sign in - storing tokens in JWT');\n token.accessToken = account.access_token;\n token.refreshToken = account.refresh_token;\n token.expiresAt = account.expires_at;\n token.idToken = account.id_token;\n token.clientId = clientId;\n token.clientSecret = clientSecret;\n token.issuer = options.issuer || process.env.NEXT_PUBLIC_OAUTH_ISSUER || process.env.OAUTH42_ISSUER;\n }\n\n // Add user profile data\n if (profile) {\n const oauth42Profile = profile as OAuth42Profile;\n token.email = oauth42Profile.email;\n token.username = oauth42Profile.username;\n token.emailVerified = oauth42Profile.email_verified;\n token.groups = oauth42Profile.groups;\n }\n\n // NOTE: Token refresh is handled by middleware (withOAuth42Auth)\n // Middleware can properly set cookies after refresh, which JWT callback cannot\n // do in Next.js App Router Server Components.\n\n // Call custom callback if provided\n if (options.callbacks?.jwt) {\n return options.callbacks.jwt({ token, account, profile } as any);\n }\n\n console.log('[OAuth42 SDK] JWT callback complete, returning token');\n return token;\n },\n\n async session({ session, token }) {\n console.log('[OAuth42 SDK] Session callback called', { hasToken: !!token, hasSession: !!session });\n\n // Add OAuth42-specific data to session\n session.accessToken = token.accessToken as string;\n session.idToken = token.idToken as string;\n\n // Pass through any token refresh errors to the client\n if (token.error) {\n session.error = token.error as string;\n }\n\n if (session.user) {\n session.user.email = token.email as string;\n session.user.name = token.name as string;\n session.user.username = token.username as string;\n session.user.emailVerified = token.emailVerified as boolean;\n session.user.groups = token.groups as string[];\n }\n\n // Call custom callback if provided\n if (options.callbacks?.session) {\n return options.callbacks.session({ session, token } as any);\n }\n\n console.log('[OAuth42 SDK] Session callback complete, returning session');\n return session;\n },\n },\n \n pages: {\n signIn: '/auth/signin',\n signOut: '/auth/signout',\n error: '/auth/error',\n ...options.pages,\n },\n \n session: {\n strategy: 'jwt',\n ...options.session,\n },\n \n debug: options.debug || process.env.NODE_ENV === 'development',\n\n secret: process.env.NEXTAUTH_SECRET,\n\n // Configure unique cookie names per app to prevent session conflicts on localhost\n ...(options.cookiePrefix && {\n cookies: {\n sessionToken: {\n name: `${options.cookiePrefix}.session-token`,\n options: {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n },\n },\n callbackUrl: {\n name: `${options.cookiePrefix}.callback-url`,\n options: {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n },\n },\n csrfToken: {\n name: `${options.cookiePrefix}.csrf-token`,\n options: {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n },\n },\n // PKCE code_verifier cookie - essential for PKCE flow\n pkceCodeVerifier: {\n name: `${options.cookiePrefix}.pkce.code_verifier`,\n options: {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n maxAge: 900, // 15 minutes\n },\n },\n // State cookie for OAuth CSRF protection\n state: {\n name: `${options.cookiePrefix}.state`,\n options: {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n maxAge: 900, // 15 minutes\n },\n },\n // Nonce cookie for OpenID Connect\n nonce: {\n name: `${options.cookiePrefix}.nonce`,\n options: {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n },\n },\n },\n }),\n };\n \n // Return the configuration and handlers for API routes\n const handler = NextAuth(authOptions);\n return {\n auth: authOptions,\n handlers: { GET: handler, POST: handler },\n };\n}\n\n/**\n * Create NextAuth handlers for API routes\n */\nexport function createHandlers(authOptions: NextAuthOptions) {\n const handler = NextAuth(authOptions);\n return { GET: handler, POST: handler };\n}\n\n/**\n * Helper to get the current session server-side\n * @deprecated Use getOAuth42Session instead - this is now just an alias for backward compatibility\n * \n * This function is maintained for backward compatibility but internally\n * calls getOAuth42Session which properly handles both App Router and Pages Router\n */\nexport const getServerSession = getOAuth42Session;\n\n/**\n * Token refresh helper with simple per-process locking\n *\n * The lock prevents multiple concurrent refresh calls from the same process,\n * reducing unnecessary token churn. The backend also has a 10-second grace\n * period for blacklisted tokens, so concurrent requests across processes\n * will still succeed.\n */\nexport async function refreshAccessToken(token: any, clientId: string, clientSecret: string, issuer?: string): Promise<any> {\n // If a refresh is already in progress, wait for it\n if (activeRefresh) {\n console.log('[OAuth42] Refresh already in progress, waiting...');\n return await activeRefresh;\n }\n\n // Start the refresh and store the promise\n activeRefresh = doRefresh(token, clientId, clientSecret, issuer);\n try {\n return await activeRefresh;\n } finally {\n activeRefresh = null;\n }\n}\n\nasync function doRefresh(token: any, clientId: string, clientSecret: string, issuer?: string): Promise<any> {\n try {\n const baseUrl = issuer || process.env.OAUTH42_ISSUER || 'https://api.oauth42.com';\n const tokenUrl = `${baseUrl}/oauth2/token`;\n\n // In development, we need to handle self-signed certificates\n const fetchOptions: any = {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: token.refreshToken,\n client_id: clientId,\n client_secret: clientSecret,\n }),\n };\n\n // Add agent for self-signed certificates in development\n if (process.env.NODE_ENV !== 'production' && tokenUrl.startsWith('https://')) {\n const https = await import('https');\n fetchOptions.agent = new https.Agent({\n rejectUnauthorized: false\n });\n }\n\n const response = await fetch(tokenUrl, fetchOptions);\n const refreshedTokens = await response.json();\n\n if (!response.ok) {\n throw refreshedTokens;\n }\n\n console.log('[OAuth42] Token refreshed successfully');\n return {\n ...token,\n accessToken: refreshedTokens.access_token,\n refreshToken: refreshedTokens.refresh_token ?? token.refreshToken,\n // Store expiration time in seconds (Unix timestamp)\n expiresAt: Math.floor(Date.now() / 1000) + (refreshedTokens.expires_in || 3600),\n // Explicitly remove any error property on successful refresh\n error: undefined,\n };\n } catch (error) {\n console.error('[OAuth42] Failed to refresh access token:', error);\n return {\n ...token,\n error: 'RefreshAccessTokenError',\n };\n }\n}","import { getServerSession as getNextAuthSession } from 'next-auth';\nimport { NextAuthOptions } from 'next-auth';\nimport { GetServerSidePropsContext, NextApiRequest, NextApiResponse } from 'next';\nimport { headers } from 'next/headers';\n\n/** Header name used by middleware to pass refreshed tokens to API routes */\nconst REFRESHED_TOKEN_HEADER = 'x-oauth42-refreshed-token';\n\n/**\n * Get the OAuth42 session server-side\n *\n * This is the primary method for retrieving sessions in OAuth42 SDK.\n * Supports both Pages Router and App Router:\n *\n * App Router:\n * ```ts\n * const session = await getOAuth42Session(authOptions);\n * ```\n *\n * Pages Router:\n * ```ts\n * const session = await getOAuth42Session(req, res, authOptions);\n * ```\n *\n * **Token Refresh Support:**\n * When middleware refreshes an expired token, it passes the new token via\n * the `x-oauth42-refreshed-token` header. This function automatically detects\n * and uses that refreshed token, ensuring API routes always have a valid token.\n */\nexport async function getOAuth42Session(\n ...args:\n | [GetServerSidePropsContext['req'], GetServerSidePropsContext['res'], NextAuthOptions]\n | [NextApiRequest, NextApiResponse, NextAuthOptions]\n | [NextAuthOptions]\n) {\n const session = await getNextAuthSession(...args as any);\n\n if (!session) {\n return null;\n }\n\n // Check for refreshed token from middleware\n // This handles the race condition where middleware refreshes the token\n // but the API route still reads the old token from the session cookie\n let refreshedToken: string | null = null;\n\n try {\n // App Router: use headers() from next/headers\n if (args.length === 1) {\n const headersList = await headers();\n refreshedToken = headersList.get(REFRESHED_TOKEN_HEADER);\n }\n // Pages Router: check request headers\n else if (args.length === 3) {\n const req = args[0] as NextApiRequest | GetServerSidePropsContext['req'];\n refreshedToken = (req.headers[REFRESHED_TOKEN_HEADER] as string) || null;\n }\n } catch {\n // headers() may throw if called outside of a request context\n // This is fine - just use the session token as-is\n }\n\n if (refreshedToken) {\n const sessionToken = (session as any).accessToken as string | undefined;\n console.log('[OAuth42 Session] Using refreshed token from middleware:', {\n refreshedPrefix: refreshedToken.substring(0, 20),\n sessionPrefix: sessionToken?.substring(0, 20),\n tokensMatch: refreshedToken === sessionToken,\n });\n return {\n ...session,\n accessToken: refreshedToken,\n };\n }\n\n return session;\n}\n\n/**\n * Helper for protecting API routes\n */\nexport function withOAuth42Session(\n handler: (req: NextApiRequest, res: NextApiResponse, session: any) => Promise<void> | void,\n authOptions: NextAuthOptions\n) {\n return async (req: NextApiRequest, res: NextApiResponse) => {\n const session = await getOAuth42Session(req, res, authOptions);\n \n if (!session) {\n return res.status(401).json({ error: 'Unauthorized' });\n }\n \n return handler(req, res, session);\n };\n}\n\n/**\n * Helper for protecting server-side props\n */\nexport function withOAuth42ServerSideProps(\n getServerSideProps: (\n context: GetServerSidePropsContext,\n session: any\n ) => Promise<any>,\n authOptions: NextAuthOptions\n) {\n return async (context: GetServerSidePropsContext) => {\n const session = await getOAuth42Session(\n context.req,\n context.res,\n authOptions\n );\n \n if (!session) {\n return {\n redirect: {\n destination: '/auth/signin',\n permanent: false,\n },\n };\n }\n \n return getServerSideProps(context, session);\n };\n}","import { NextRequest, NextResponse } from 'next/server';\nimport { getToken, encode } from 'next-auth/jwt';\n\n/**\n * Cookie chunking constants — must match NextAuth's SessionStore behavior.\n * NextAuth splits cookies at ALLOWED_COOKIE_SIZE (4096) minus overhead (163).\n * When middleware re-encodes the JWT after a token refresh, the cookie must be\n * chunked the same way, otherwise:\n * - A single cookie > ~4 KB is silently rejected by the browser\n * - Leftover chunk cookies from the original session cause SessionStore to\n * concatenate old chunks with the new base cookie, producing garbage\n */\nconst ALLOWED_COOKIE_SIZE = 4096;\nconst ESTIMATED_EMPTY_COOKIE_SIZE = 163;\nconst CHUNK_SIZE = ALLOWED_COOKIE_SIZE - ESTIMATED_EMPTY_COOKIE_SIZE;\n\n/**\n * In-flight refresh tracking to prevent parallel refresh calls.\n * Key: user ID (from token.sub), Value: Promise that resolves with refresh result\n */\nconst pendingRefreshes = new Map<string, Promise<{\n success: boolean;\n accessToken?: string;\n refreshToken?: string;\n expiresAt?: number;\n error?: string;\n}>>();\n\nexport interface OAuth42AuthOptions {\n pages?: {\n signIn?: string;\n error?: string;\n };\n callbacks?: {\n authorized?: (params: { token: any; req: NextRequest }) => boolean | Promise<boolean>;\n };\n protectedPaths?: string[];\n publicPaths?: string[];\n /**\n * Cookie prefix for custom cookie names. Must match the prefix used in createAuth().\n * E.g., 'oauth42-portal' will look for cookie 'oauth42-portal.session-token'\n */\n cookiePrefix?: string;\n}\n\n/**\n * Refresh tokens by calling the OAuth42 backend directly\n */\nasync function refreshTokens(\n refreshToken: string,\n clientId: string,\n clientSecret: string,\n issuer: string\n): Promise<{ success: boolean; accessToken?: string; refreshToken?: string; expiresAt?: number; error?: string }> {\n try {\n const tokenUrl = `${issuer}/oauth2/token`;\n\n const response = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n client_id: clientId,\n client_secret: clientSecret,\n }),\n });\n\n const data = await response.json();\n\n if (!response.ok) {\n console.error('[OAuth42 Middleware] Token refresh failed:', data);\n return { success: false, error: data.error || 'refresh_failed' };\n }\n\n console.log('[OAuth42 Middleware] Token refreshed successfully');\n return {\n success: true,\n accessToken: data.access_token,\n refreshToken: data.refresh_token,\n expiresAt: Math.floor(Date.now() / 1000) + (data.expires_in || 3600),\n };\n } catch (error) {\n console.error('[OAuth42 Middleware] Token refresh error:', error);\n return { success: false, error: 'refresh_error' };\n }\n}\n\n/**\n * Middleware helper for protecting routes with OAuth42\n *\n * This middleware handles:\n * 1. Route protection (redirect to login if no session)\n * 2. Token refresh (refresh expired tokens and update cookie)\n */\nexport function withOAuth42Auth(options: OAuth42AuthOptions = {}) {\n const secret = process.env.NEXTAUTH_SECRET;\n const clientId = process.env.OAUTH42_CLIENT_ID;\n const clientSecret = process.env.OAUTH42_CLIENT_SECRET;\n const issuer = process.env.OAUTH42_ISSUER || 'https://localhost:8443';\n\n if (!secret) {\n console.warn('[OAuth42 Middleware] NEXTAUTH_SECRET not set');\n }\n\n return async function middleware(req: NextRequest) {\n // Build cookie name - if prefix is provided, use custom name\n const cookieName = options.cookiePrefix\n ? `${options.cookiePrefix}.session-token`\n : 'next-auth.session-token';\n\n const token = await getToken({\n req: req as any,\n secret,\n cookieName,\n });\n\n const pathname = req.nextUrl.pathname;\n\n // Check if path is explicitly public\n if (options.publicPaths?.some(path => pathname.startsWith(path))) {\n return NextResponse.next();\n }\n\n // Check if path needs protection\n const needsProtection = options.protectedPaths\n ? options.protectedPaths.some(path => pathname.startsWith(path))\n : true; // Default to protecting all paths\n\n if (!needsProtection) {\n return NextResponse.next();\n }\n\n // No token at all - redirect to sign in\n if (!token) {\n const signInUrl = options.pages?.signIn || '/auth/signin';\n const url = new URL(signInUrl, req.url);\n url.searchParams.set('callbackUrl', pathname);\n return NextResponse.redirect(url);\n }\n\n // Check if access token is expired or expiring soon (15 second buffer)\n // Buffer must be less than the token TTL to have a valid window\n const expiresAt = token.expiresAt as number | undefined;\n const now = Math.floor(Date.now() / 1000);\n const bufferSeconds = 15;\n const needsRefresh = expiresAt && now >= expiresAt - bufferSeconds;\n\n if (needsRefresh) {\n console.log('[OAuth42 Middleware] Token needs refresh:', {\n expiresAt,\n now,\n expiredAgo: expiresAt ? now - expiresAt : 'n/a',\n accessTokenPrefix: (token.accessToken as string)?.substring(0, 20),\n path: pathname,\n });\n }\n\n if (needsRefresh && token.refreshToken && clientId && clientSecret) {\n const userId = token.sub as string;\n\n // Check if there's already a refresh in progress for this user\n let refreshPromise = pendingRefreshes.get(userId);\n\n if (refreshPromise) {\n console.log('[OAuth42 Middleware] Waiting for in-flight refresh...');\n } else {\n console.log('[OAuth42 Middleware] Access token expired, refreshing...');\n\n // Create the refresh promise and store it.\n // Keep the entry for 30s after completion so late-arriving requests\n // (with stale cookies) reuse the cached result instead of triggering\n // a new refresh with the already-rotated token. See issue #470.\n refreshPromise = refreshTokens(\n token.refreshToken as string,\n clientId,\n clientSecret,\n issuer\n ).finally(() => {\n setTimeout(() => pendingRefreshes.delete(userId), 30000);\n });\n\n pendingRefreshes.set(userId, refreshPromise);\n }\n\n const refreshed = await refreshPromise;\n\n if (refreshed.success && refreshed.accessToken && refreshed.refreshToken) {\n // Update the token with new values\n const updatedToken = {\n ...token,\n accessToken: refreshed.accessToken,\n refreshToken: refreshed.refreshToken,\n expiresAt: refreshed.expiresAt,\n };\n\n // Re-encode the JWT\n const newJwt = await encode({\n token: updatedToken,\n secret: secret!,\n });\n\n // Create response with request headers that pass the new token to API routes\n // This is necessary because API routes read from the request, not the response cookie\n const requestHeaders = new Headers(req.headers);\n requestHeaders.set('x-oauth42-refreshed-token', refreshed.accessToken);\n\n const response = NextResponse.next({\n request: {\n headers: requestHeaders,\n },\n });\n\n const cookieOptions = {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n };\n\n // Set cookie with chunking matching NextAuth's SessionStore behavior.\n // Without chunking, a JWT > ~4 KB is silently rejected by the browser,\n // causing an infinite refresh loop where every request re-triggers\n // token refresh but the cookie never persists.\n if (newJwt.length <= CHUNK_SIZE) {\n // Fits in a single cookie\n response.cookies.set(cookieName, newJwt, cookieOptions);\n // Clean up any leftover chunk cookies from a previously chunked session\n for (let i = 0; ; i++) {\n const chunkName = `${cookieName}.${i}`;\n if (req.cookies.has(chunkName)) {\n response.cookies.set(chunkName, '', { ...cookieOptions, maxAge: 0 });\n } else {\n break;\n }\n }\n } else {\n // Cookie too large — chunk it like NextAuth does\n const chunkCount = Math.ceil(newJwt.length / CHUNK_SIZE);\n for (let i = 0; i < chunkCount; i++) {\n const chunkName = `${cookieName}.${i}`;\n const chunkValue = newJwt.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);\n response.cookies.set(chunkName, chunkValue, cookieOptions);\n }\n // Delete the base cookie so SessionStore only reads chunks\n response.cookies.set(cookieName, '', { ...cookieOptions, maxAge: 0 });\n // Clean up any extra old chunks beyond the new count\n for (let i = chunkCount; ; i++) {\n const chunkName = `${cookieName}.${i}`;\n if (req.cookies.has(chunkName)) {\n response.cookies.set(chunkName, '', { ...cookieOptions, maxAge: 0 });\n } else {\n break;\n }\n }\n }\n\n // Log details to help diagnose cookie persistence issues\n const existingChunks = [];\n for (let i = 0; ; i++) {\n if (req.cookies.has(`${cookieName}.${i}`)) {\n existingChunks.push(i);\n } else {\n break;\n }\n }\n console.log(`[OAuth42 Middleware] Token refresh complete:`, {\n jwtBytes: newJwt.length,\n chunked: newJwt.length > CHUNK_SIZE,\n existingBaseCooke: req.cookies.has(cookieName),\n existingChunks: existingChunks.length > 0 ? existingChunks : 'none',\n newAccessTokenPrefix: refreshed.accessToken?.substring(0, 20),\n newExpiresAt: refreshed.expiresAt,\n cookieName,\n });\n return response;\n } else {\n // Refresh failed - clear cookie and redirect to sign in (no error, just let user log in fresh)\n console.log('[OAuth42 Middleware] Refresh failed (likely token blacklisted after deploy), clearing session');\n const signInUrl = options.pages?.signIn || '/auth/signin';\n const url = new URL(signInUrl, req.url);\n url.searchParams.set('callbackUrl', pathname);\n // DO NOT set error parameter - just redirect silently\n\n const response = NextResponse.redirect(url);\n // Clear the base session cookie and any chunk cookies\n response.cookies.delete(cookieName);\n for (let i = 0; ; i++) {\n const chunkName = `${cookieName}.${i}`;\n if (req.cookies.has(chunkName)) {\n response.cookies.delete(chunkName);\n } else {\n break;\n }\n }\n return response;\n }\n }\n\n // Check custom authorization callback\n if (options.callbacks?.authorized) {\n const isAuthorized = await options.callbacks.authorized({ token, req });\n if (!isAuthorized) {\n const signInUrl = options.pages?.signIn || '/auth/signin';\n const url = new URL(signInUrl, req.url);\n url.searchParams.set('callbackUrl', pathname);\n return NextResponse.redirect(url);\n }\n }\n\n return NextResponse.next();\n };\n}\n\n/**\n * Helper to create middleware configuration\n */\nexport function createMiddlewareConfig(\n protectedPaths: string[] = ['/protected'],\n publicPaths: string[] = ['/auth', '/api/auth']\n) {\n return {\n matcher: [\n /*\n * Match all request paths except for the ones starting with:\n * - _next/static (static files)\n * - _next/image (image optimization files)\n * - favicon.ico (favicon file)\n * - public folder\n */\n '/((?!_next/static|_next/image|favicon.ico|public).*)',\n ],\n protectedPaths,\n publicPaths,\n };\n}\n"],"mappings":";AA0BO,SAAS,gBACd,SACgB;AAChB,QAAM,SAAS,QAAQ,UAAU,QAAQ,IAAI,kBAAkB;AAC/D,QAAM,UAAU,OAAO,QAAQ,OAAO,EAAE;AAExC,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,SAAS;AAAA;AAAA,IAGT,WAAW,GAAG,OAAO;AAAA;AAAA;AAAA;AAAA,IAKrB,SAAS;AAAA;AAAA,IAGT,eAAe;AAAA,MACb,KAAK,GAAG,OAAO;AAAA,MACf,QAAQ;AAAA,QACN,QAAQ,QAAQ,UAAU,CAAC,UAAU,WAAW,OAAO,GAAG,KAAK,GAAG;AAAA,QAClE,eAAe;AAAA,MACjB;AAAA,IACF;AAAA,IACA,OAAO,GAAG,OAAO;AAAA,IACjB,UAAU,GAAG,OAAO;AAAA,IAEpB,QAAQ;AAAA,MACN,IAAI,QAAQ;AAAA,MACZ,QAAQ,QAAQ;AAAA,MAChB,4BAA4B;AAAA,MAC5B,8BAA8B;AAAA;AAAA,IAChC;AAAA,IAEA,QAAQ;AAAA,IAER,QAAQ,QAAQ,gBAAgB,QAAQ,CAAC,QAAQ,OAAO,IAAI,CAAC,OAAO;AAAA,IAEpE,QAAQ,SAAyB,QAAa;AAC5C,aAAO;AAAA,QACL,IAAI,QAAQ,OAAO,QAAQ,MAAM,QAAQ;AAAA,QACzC,OAAO,QAAQ;AAAA,QACf,eAAe,QAAQ,iBAAiB,oBAAI,KAAK,IAAI;AAAA,QACrD,MAAM,QAAQ,QAAQ,GAAG,QAAQ,cAAc,EAAE,IAAI,QAAQ,eAAe,EAAE,GAAG,KAAK;AAAA,QACtF,OAAO,QAAQ;AAAA,QACf,QAAQ,QAAQ;AAAA,MAClB;AAAA,IACF;AAAA,IAEA,OAAO;AAAA,MACL,MAAM;AAAA,MACN,IAAI;AAAA,MACJ,MAAM;AAAA,IACR;AAAA,IAEA;AAAA,EACF;AACF;;;ACvFA,OAAO,qBAAqB;;;ACA5B,SAAS,oBAAoB,0BAA0B;AAGvD,SAAS,eAAe;AAGxB,IAAM,yBAAyB;AAuB/B,eAAsB,qBACjB,MAIH;AACA,QAAM,UAAU,MAAM,mBAAmB,GAAG,IAAW;AAEvD,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,EACT;AAKA,MAAI,iBAAgC;AAEpC,MAAI;AAEF,QAAI,KAAK,WAAW,GAAG;AACrB,YAAM,cAAc,MAAM,QAAQ;AAClC,uBAAiB,YAAY,IAAI,sBAAsB;AAAA,IACzD,WAES,KAAK,WAAW,GAAG;AAC1B,YAAM,MAAM,KAAK,CAAC;AAClB,uBAAkB,IAAI,QAAQ,sBAAsB,KAAgB;AAAA,IACtE;AAAA,EACF,QAAQ;AAAA,EAGR;AAEA,MAAI,gBAAgB;AAClB,UAAM,eAAgB,QAAgB;AACtC,YAAQ,IAAI,4DAA4D;AAAA,MACtE,iBAAiB,eAAe,UAAU,GAAG,EAAE;AAAA,MAC/C,eAAe,cAAc,UAAU,GAAG,EAAE;AAAA,MAC5C,aAAa,mBAAmB;AAAA,IAClC,CAAC;AACD,WAAO;AAAA,MACL,GAAG;AAAA,MACH,aAAa;AAAA,IACf;AAAA,EACF;AAEA,SAAO;AACT;AAKO,SAAS,mBACd,SACA,aACA;AACA,SAAO,OAAO,KAAqB,QAAyB;AAC1D,UAAM,UAAU,MAAM,kBAAkB,KAAK,KAAK,WAAW;AAE7D,QAAI,CAAC,SAAS;AACZ,aAAO,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,eAAe,CAAC;AAAA,IACvD;AAEA,WAAO,QAAQ,KAAK,KAAK,OAAO;AAAA,EAClC;AACF;AAKO,SAAS,2BACd,oBAIA,aACA;AACA,SAAO,OAAO,YAAuC;AACnD,UAAM,UAAU,MAAM;AAAA,MACpB,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR;AAAA,IACF;AAEA,QAAI,CAAC,SAAS;AACZ,aAAO;AAAA,QACL,UAAU;AAAA,UACR,aAAa;AAAA,UACb,WAAW;AAAA,QACb;AAAA,MACF;AAAA,IACF;AAEA,WAAO,mBAAmB,SAAS,OAAO;AAAA,EAC5C;AACF;;;ADtHA,IAAM,WAAY,gBAAwB,WAAW;AAKrD,IAAI,gBAAqC;AAwBlC,SAAS,WAAW,UAA6B,CAAC,GAAG;AAC1D,QAAM,WAAW,QAAQ,YAAY,QAAQ,IAAI;AACjD,QAAM,eAAe,QAAQ,gBAAgB,QAAQ,IAAI;AAEzD,MAAI,CAAC,YAAY,CAAC,cAAc;AAC9B,UAAM,IAAI;AAAA,MACR;AAAA,IAGF;AAAA,EACF;AAEA,QAAM,cAA+B;AAAA,IACnC,WAAW;AAAA,MACT,gBAAgB;AAAA,QACd;AAAA,QACA;AAAA,QACA,QAAQ,QAAQ;AAAA,QAChB,QAAQ,QAAQ;AAAA,QAChB,aAAa,QAAQ;AAAA,MACvB,CAAC;AAAA,IACH;AAAA,IAEA,WAAW;AAAA,MACT,MAAM,IAAI,EAAE,OAAO,SAAS,QAAQ,GAAG;AACrC,gBAAQ,IAAI,qCAAqC,EAAE,YAAY,CAAC,CAAC,SAAS,YAAY,CAAC,CAAC,QAAQ,CAAC;AAGjG,YAAI,SAAS;AACX,kBAAQ,IAAI,uDAAuD;AACnE,gBAAM,cAAc,QAAQ;AAC5B,gBAAM,eAAe,QAAQ;AAC7B,gBAAM,YAAY,QAAQ;AAC1B,gBAAM,UAAU,QAAQ;AACxB,gBAAM,WAAW;AACjB,gBAAM,eAAe;AACrB,gBAAM,SAAS,QAAQ,UAAU,QAAQ,IAAI,4BAA4B,QAAQ,IAAI;AAAA,QACvF;AAGA,YAAI,SAAS;AACX,gBAAM,iBAAiB;AACvB,gBAAM,QAAQ,eAAe;AAC7B,gBAAM,WAAW,eAAe;AAChC,gBAAM,gBAAgB,eAAe;AACrC,gBAAM,SAAS,eAAe;AAAA,QAChC;AAOA,YAAI,QAAQ,WAAW,KAAK;AAC1B,iBAAO,QAAQ,UAAU,IAAI,EAAE,OAAO,SAAS,QAAQ,CAAQ;AAAA,QACjE;AAEA,gBAAQ,IAAI,sDAAsD;AAClE,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,QAAQ,EAAE,SAAS,MAAM,GAAG;AAChC,gBAAQ,IAAI,yCAAyC,EAAE,UAAU,CAAC,CAAC,OAAO,YAAY,CAAC,CAAC,QAAQ,CAAC;AAGjG,gBAAQ,cAAc,MAAM;AAC5B,gBAAQ,UAAU,MAAM;AAGxB,YAAI,MAAM,OAAO;AACf,kBAAQ,QAAQ,MAAM;AAAA,QACxB;AAEA,YAAI,QAAQ,MAAM;AAChB,kBAAQ,KAAK,QAAQ,MAAM;AAC3B,kBAAQ,KAAK,OAAO,MAAM;AAC1B,kBAAQ,KAAK,WAAW,MAAM;AAC9B,kBAAQ,KAAK,gBAAgB,MAAM;AACnC,kBAAQ,KAAK,SAAS,MAAM;AAAA,QAC9B;AAGA,YAAI,QAAQ,WAAW,SAAS;AAC9B,iBAAO,QAAQ,UAAU,QAAQ,EAAE,SAAS,MAAM,CAAQ;AAAA,QAC5D;AAEA,gBAAQ,IAAI,4DAA4D;AACxE,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,OAAO;AAAA,MACL,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,OAAO;AAAA,MACP,GAAG,QAAQ;AAAA,IACb;AAAA,IAEA,SAAS;AAAA,MACP,UAAU;AAAA,MACV,GAAG,QAAQ;AAAA,IACb;AAAA,IAEA,OAAO,QAAQ,SAAS,QAAQ,IAAI,aAAa;AAAA,IAEjD,QAAQ,QAAQ,IAAI;AAAA;AAAA,IAGpB,GAAI,QAAQ,gBAAgB;AAAA,MAC1B,SAAS;AAAA,QACP,cAAc;AAAA,UACZ,MAAM,GAAG,QAAQ,YAAY;AAAA,UAC7B,SAAS;AAAA,YACP,UAAU;AAAA,YACV,UAAU;AAAA,YACV,MAAM;AAAA,YACN,QAAQ,QAAQ,IAAI,aAAa;AAAA,UACnC;AAAA,QACF;AAAA,QACA,aAAa;AAAA,UACX,MAAM,GAAG,QAAQ,YAAY;AAAA,UAC7B,SAAS;AAAA,YACP,UAAU;AAAA,YACV,UAAU;AAAA,YACV,MAAM;AAAA,YACN,QAAQ,QAAQ,IAAI,aAAa;AAAA,UACnC;AAAA,QACF;AAAA,QACA,WAAW;AAAA,UACT,MAAM,GAAG,QAAQ,YAAY;AAAA,UAC7B,SAAS;AAAA,YACP,UAAU;AAAA,YACV,UAAU;AAAA,YACV,MAAM;AAAA,YACN,QAAQ,QAAQ,IAAI,aAAa;AAAA,UACnC;AAAA,QACF;AAAA;AAAA,QAEA,kBAAkB;AAAA,UAChB,MAAM,GAAG,QAAQ,YAAY;AAAA,UAC7B,SAAS;AAAA,YACP,UAAU;AAAA,YACV,UAAU;AAAA,YACV,MAAM;AAAA,YACN,QAAQ,QAAQ,IAAI,aAAa;AAAA,YACjC,QAAQ;AAAA;AAAA,UACV;AAAA,QACF;AAAA;AAAA,QAEA,OAAO;AAAA,UACL,MAAM,GAAG,QAAQ,YAAY;AAAA,UAC7B,SAAS;AAAA,YACP,UAAU;AAAA,YACV,UAAU;AAAA,YACV,MAAM;AAAA,YACN,QAAQ,QAAQ,IAAI,aAAa;AAAA,YACjC,QAAQ;AAAA;AAAA,UACV;AAAA,QACF;AAAA;AAAA,QAEA,OAAO;AAAA,UACL,MAAM,GAAG,QAAQ,YAAY;AAAA,UAC7B,SAAS;AAAA,YACP,UAAU;AAAA,YACV,UAAU;AAAA,YACV,MAAM;AAAA,YACN,QAAQ,QAAQ,IAAI,aAAa;AAAA,UACnC;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,QAAM,UAAU,SAAS,WAAW;AACpC,SAAO;AAAA,IACL,MAAM;AAAA,IACN,UAAU,EAAE,KAAK,SAAS,MAAM,QAAQ;AAAA,EAC1C;AACF;AAiBO,IAAM,mBAAmB;AAUhC,eAAsB,mBAAmB,OAAY,UAAkB,cAAsB,QAA+B;AAE1H,MAAI,eAAe;AACjB,YAAQ,IAAI,mDAAmD;AAC/D,WAAO,MAAM;AAAA,EACf;AAGA,kBAAgB,UAAU,OAAO,UAAU,cAAc,MAAM;AAC/D,MAAI;AACF,WAAO,MAAM;AAAA,EACf,UAAE;AACA,oBAAgB;AAAA,EAClB;AACF;AAEA,eAAe,UAAU,OAAY,UAAkB,cAAsB,QAA+B;AAC1G,MAAI;AACF,UAAM,UAAU,UAAU,QAAQ,IAAI,kBAAkB;AACxD,UAAM,WAAW,GAAG,OAAO;AAG3B,UAAM,eAAoB;AAAA,MACxB,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,MAClB;AAAA,MACA,MAAM,IAAI,gBAAgB;AAAA,QACxB,YAAY;AAAA,QACZ,eAAe,MAAM;AAAA,QACrB,WAAW;AAAA,QACX,eAAe;AAAA,MACjB,CAAC;AAAA,IACH;AAGA,QAAI,QAAQ,IAAI,aAAa,gBAAgB,SAAS,WAAW,UAAU,GAAG;AAC5E,YAAM,QAAQ,MAAM,OAAO,OAAO;AAClC,mBAAa,QAAQ,IAAI,MAAM,MAAM;AAAA,QACnC,oBAAoB;AAAA,MACtB,CAAC;AAAA,IACH;AAEA,UAAM,WAAW,MAAM,MAAM,UAAU,YAAY;AACnD,UAAM,kBAAkB,MAAM,SAAS,KAAK;AAE5C,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM;AAAA,IACR;AAEA,YAAQ,IAAI,wCAAwC;AACpD,WAAO;AAAA,MACL,GAAG;AAAA,MACH,aAAa,gBAAgB;AAAA,MAC7B,cAAc,gBAAgB,iBAAiB,MAAM;AAAA;AAAA,MAErD,WAAW,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,KAAK,gBAAgB,cAAc;AAAA;AAAA,MAE1E,OAAO;AAAA,IACT;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,MAAM,6CAA6C,KAAK;AAChE,WAAO;AAAA,MACL,GAAG;AAAA,MACH,OAAO;AAAA,IACT;AAAA,EACF;AACF;;;AEpTA,SAAsB,oBAAoB;AAC1C,SAAS,UAAU,cAAc;AAWjC,IAAM,sBAAsB;AAC5B,IAAM,8BAA8B;AACpC,IAAM,aAAa,sBAAsB;AAMzC,IAAM,mBAAmB,oBAAI,IAMzB;AAsBJ,eAAe,cACb,cACA,UACA,cACA,QACgH;AAChH,MAAI;AACF,UAAM,WAAW,GAAG,MAAM;AAE1B,UAAM,WAAW,MAAM,MAAM,UAAU;AAAA,MACrC,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,MAClB;AAAA,MACA,MAAM,IAAI,gBAAgB;AAAA,QACxB,YAAY;AAAA,QACZ,eAAe;AAAA,QACf,WAAW;AAAA,QACX,eAAe;AAAA,MACjB,CAAC;AAAA,IACH,CAAC;AAED,UAAM,OAAO,MAAM,SAAS,KAAK;AAEjC,QAAI,CAAC,SAAS,IAAI;AAChB,cAAQ,MAAM,8CAA8C,IAAI;AAChE,aAAO,EAAE,SAAS,OAAO,OAAO,KAAK,SAAS,iBAAiB;AAAA,IACjE;AAEA,YAAQ,IAAI,mDAAmD;AAC/D,WAAO;AAAA,MACL,SAAS;AAAA,MACT,aAAa,KAAK;AAAA,MAClB,cAAc,KAAK;AAAA,MACnB,WAAW,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,KAAK,KAAK,cAAc;AAAA,IACjE;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,MAAM,6CAA6C,KAAK;AAChE,WAAO,EAAE,SAAS,OAAO,OAAO,gBAAgB;AAAA,EAClD;AACF;AASO,SAAS,gBAAgB,UAA8B,CAAC,GAAG;AAChE,QAAM,SAAS,QAAQ,IAAI;AAC3B,QAAM,WAAW,QAAQ,IAAI;AAC7B,QAAM,eAAe,QAAQ,IAAI;AACjC,QAAM,SAAS,QAAQ,IAAI,kBAAkB;AAE7C,MAAI,CAAC,QAAQ;AACX,YAAQ,KAAK,8CAA8C;AAAA,EAC7D;AAEA,SAAO,eAAe,WAAW,KAAkB;AAEjD,UAAM,aAAa,QAAQ,eACvB,GAAG,QAAQ,YAAY,mBACvB;AAEJ,UAAM,QAAQ,MAAM,SAAS;AAAA,MAC3B;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED,UAAM,WAAW,IAAI,QAAQ;AAG7B,QAAI,QAAQ,aAAa,KAAK,UAAQ,SAAS,WAAW,IAAI,CAAC,GAAG;AAChE,aAAO,aAAa,KAAK;AAAA,IAC3B;AAGA,UAAM,kBAAkB,QAAQ,iBAC5B,QAAQ,eAAe,KAAK,UAAQ,SAAS,WAAW,IAAI,CAAC,IAC7D;AAEJ,QAAI,CAAC,iBAAiB;AACpB,aAAO,aAAa,KAAK;AAAA,IAC3B;AAGA,QAAI,CAAC,OAAO;AACV,YAAM,YAAY,QAAQ,OAAO,UAAU;AAC3C,YAAM,MAAM,IAAI,IAAI,WAAW,IAAI,GAAG;AACtC,UAAI,aAAa,IAAI,eAAe,QAAQ;AAC5C,aAAO,aAAa,SAAS,GAAG;AAAA,IAClC;AAIA,UAAM,YAAY,MAAM;AACxB,UAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACxC,UAAM,gBAAgB;AACtB,UAAM,eAAe,aAAa,OAAO,YAAY;AAErD,QAAI,cAAc;AAChB,cAAQ,IAAI,6CAA6C;AAAA,QACvD;AAAA,QACA;AAAA,QACA,YAAY,YAAY,MAAM,YAAY;AAAA,QAC1C,mBAAoB,MAAM,aAAwB,UAAU,GAAG,EAAE;AAAA,QACjE,MAAM;AAAA,MACR,CAAC;AAAA,IACH;AAEA,QAAI,gBAAgB,MAAM,gBAAgB,YAAY,cAAc;AAClE,YAAM,SAAS,MAAM;AAGrB,UAAI,iBAAiB,iBAAiB,IAAI,MAAM;AAEhD,UAAI,gBAAgB;AAClB,gBAAQ,IAAI,uDAAuD;AAAA,MACrE,OAAO;AACL,gBAAQ,IAAI,0DAA0D;AAMtE,yBAAiB;AAAA,UACf,MAAM;AAAA,UACN;AAAA,UACA;AAAA,UACA;AAAA,QACF,EAAE,QAAQ,MAAM;AACd,qBAAW,MAAM,iBAAiB,OAAO,MAAM,GAAG,GAAK;AAAA,QACzD,CAAC;AAED,yBAAiB,IAAI,QAAQ,cAAc;AAAA,MAC7C;AAEA,YAAM,YAAY,MAAM;AAExB,UAAI,UAAU,WAAW,UAAU,eAAe,UAAU,cAAc;AAExE,cAAM,eAAe;AAAA,UACnB,GAAG;AAAA,UACH,aAAa,UAAU;AAAA,UACvB,cAAc,UAAU;AAAA,UACxB,WAAW,UAAU;AAAA,QACvB;AAGA,cAAM,SAAS,MAAM,OAAO;AAAA,UAC1B,OAAO;AAAA,UACP;AAAA,QACF,CAAC;AAID,cAAM,iBAAiB,IAAI,QAAQ,IAAI,OAAO;AAC9C,uBAAe,IAAI,6BAA6B,UAAU,WAAW;AAErE,cAAM,WAAW,aAAa,KAAK;AAAA,UACjC,SAAS;AAAA,YACP,SAAS;AAAA,UACX;AAAA,QACF,CAAC;AAED,cAAM,gBAAgB;AAAA,UACpB,UAAU;AAAA,UACV,UAAU;AAAA,UACV,MAAM;AAAA,UACN,QAAQ,QAAQ,IAAI,aAAa;AAAA,QACnC;AAMA,YAAI,OAAO,UAAU,YAAY;AAE/B,mBAAS,QAAQ,IAAI,YAAY,QAAQ,aAAa;AAEtD,mBAAS,IAAI,KAAK,KAAK;AACrB,kBAAM,YAAY,GAAG,UAAU,IAAI,CAAC;AACpC,gBAAI,IAAI,QAAQ,IAAI,SAAS,GAAG;AAC9B,uBAAS,QAAQ,IAAI,WAAW,IAAI,EAAE,GAAG,eAAe,QAAQ,EAAE,CAAC;AAAA,YACrE,OAAO;AACL;AAAA,YACF;AAAA,UACF;AAAA,QACF,OAAO;AAEL,gBAAM,aAAa,KAAK,KAAK,OAAO,SAAS,UAAU;AACvD,mBAAS,IAAI,GAAG,IAAI,YAAY,KAAK;AACnC,kBAAM,YAAY,GAAG,UAAU,IAAI,CAAC;AACpC,kBAAM,aAAa,OAAO,MAAM,IAAI,aAAa,IAAI,KAAK,UAAU;AACpE,qBAAS,QAAQ,IAAI,WAAW,YAAY,aAAa;AAAA,UAC3D;AAEA,mBAAS,QAAQ,IAAI,YAAY,IAAI,EAAE,GAAG,eAAe,QAAQ,EAAE,CAAC;AAEpE,mBAAS,IAAI,cAAc,KAAK;AAC9B,kBAAM,YAAY,GAAG,UAAU,IAAI,CAAC;AACpC,gBAAI,IAAI,QAAQ,IAAI,SAAS,GAAG;AAC9B,uBAAS,QAAQ,IAAI,WAAW,IAAI,EAAE,GAAG,eAAe,QAAQ,EAAE,CAAC;AAAA,YACrE,OAAO;AACL;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAGA,cAAM,iBAAiB,CAAC;AACxB,iBAAS,IAAI,KAAK,KAAK;AACrB,cAAI,IAAI,QAAQ,IAAI,GAAG,UAAU,IAAI,CAAC,EAAE,GAAG;AACzC,2BAAe,KAAK,CAAC;AAAA,UACvB,OAAO;AACL;AAAA,UACF;AAAA,QACF;AACA,gBAAQ,IAAI,gDAAgD;AAAA,UAC1D,UAAU,OAAO;AAAA,UACjB,SAAS,OAAO,SAAS;AAAA,UACzB,mBAAmB,IAAI,QAAQ,IAAI,UAAU;AAAA,UAC7C,gBAAgB,eAAe,SAAS,IAAI,iBAAiB;AAAA,UAC7D,sBAAsB,UAAU,aAAa,UAAU,GAAG,EAAE;AAAA,UAC5D,cAAc,UAAU;AAAA,UACxB;AAAA,QACF,CAAC;AACD,eAAO;AAAA,MACT,OAAO;AAEL,gBAAQ,IAAI,+FAA+F;AAC3G,cAAM,YAAY,QAAQ,OAAO,UAAU;AAC3C,cAAM,MAAM,IAAI,IAAI,WAAW,IAAI,GAAG;AACtC,YAAI,aAAa,IAAI,eAAe,QAAQ;AAG5C,cAAM,WAAW,aAAa,SAAS,GAAG;AAE1C,iBAAS,QAAQ,OAAO,UAAU;AAClC,iBAAS,IAAI,KAAK,KAAK;AACrB,gBAAM,YAAY,GAAG,UAAU,IAAI,CAAC;AACpC,cAAI,IAAI,QAAQ,IAAI,SAAS,GAAG;AAC9B,qBAAS,QAAQ,OAAO,SAAS;AAAA,UACnC,OAAO;AACL;AAAA,UACF;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAGA,QAAI,QAAQ,WAAW,YAAY;AACjC,YAAM,eAAe,MAAM,QAAQ,UAAU,WAAW,EAAE,OAAO,IAAI,CAAC;AACtE,UAAI,CAAC,cAAc;AACjB,cAAM,YAAY,QAAQ,OAAO,UAAU;AAC3C,cAAM,MAAM,IAAI,IAAI,WAAW,IAAI,GAAG;AACtC,YAAI,aAAa,IAAI,eAAe,QAAQ;AAC5C,eAAO,aAAa,SAAS,GAAG;AAAA,MAClC;AAAA,IACF;AAEA,WAAO,aAAa,KAAK;AAAA,EAC3B;AACF;AAKO,SAAS,uBACd,iBAA2B,CAAC,YAAY,GACxC,cAAwB,CAAC,SAAS,WAAW,GAC7C;AACA,SAAO;AAAA,IACL,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAQP;AAAA,IACF;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}