{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/plugins/oauth-proxy/index.ts"],"sourcesContent":["import type { BetterAuthPlugin } from \"@better-auth/core\";\nimport {\n\tcreateAuthEndpoint,\n\tcreateAuthMiddleware,\n} from \"@better-auth/core/api\";\nimport type { OAuth2Tokens } from \"@better-auth/core/oauth2\";\nimport * as z from \"zod\";\nimport { originCheck } from \"../../api\";\nimport { parseJSON } from \"../../client/parser\";\nimport { setSessionCookie } from \"../../cookies\";\nimport { parseSetCookieHeader } from \"../../cookies/cookie-utils\";\nimport { symmetricDecrypt, symmetricEncrypt } from \"../../crypto\";\nimport { handleOAuthUserInfo } from \"../../oauth2/link-account\";\nimport type { StateData } from \"../../state\";\nimport { parseGenericState } from \"../../state\";\nimport type { Account, User } from \"../../types\";\nimport { getOrigin } from \"../../utils/url\";\nimport {\n\tcheckSkipProxy,\n\tredirectOnError,\n\tresolveCurrentURL,\n\tstripTrailingSlash,\n} from \"./utils\";\n\ndeclare module \"@better-auth/core\" {\n\tinterface BetterAuthPluginRegistry<AuthOptions, Options> {\n\t\t\"oauth-proxy\": {\n\t\t\tcreator: typeof oAuthProxy;\n\t\t};\n\t}\n}\n\nexport interface OAuthProxyOptions {\n\t/**\n\t * The current URL of the application.\n\t * The plugin will attempt to infer the current URL from your environment\n\t * by checking the base URL from popular hosting providers,\n\t * from the request URL if invoked by a client,\n\t * or as a fallback, from the `baseURL` in your auth config.\n\t * If the URL is not inferred correctly, you can provide a value here.\"\n\t */\n\tcurrentURL?: string | undefined;\n\t/**\n\t * If a request in a production url it won't be proxied.\n\t *\n\t * default to `BETTER_AUTH_URL`\n\t */\n\tproductionURL?: string | undefined;\n\t/**\n\t * Maximum age in seconds for the encrypted payload.\n\t * Payloads older than this will be rejected to prevent replay attacks.\n\t *\n\t * Keep this value short (e.g., 30-60 seconds) to minimize the window\n\t * for potential replay attacks while still allowing normal OAuth flows.\n\t *\n\t * @default 60 (1 minute)\n\t */\n\tmaxAge?: number | undefined;\n}\n\n/**\n * Encrypted state package for cross-origin OAuth proxy flow\n * @internal\n */\ntype OAuthProxyStatePackage = {\n\tstate: string;\n\tstateCookie: string;\n\tisOAuthProxy: boolean;\n};\n\n/**\n * Passthrough payload containing OAuth profile data.\n * Used to transfer OAuth credentials from production to preview\n * without creating user/session on production.\n * @internal\n */\ntype PassthroughPayload = {\n\tuserInfo: Omit<User, \"createdAt\" | \"updatedAt\">;\n\taccount: Omit<Account, \"id\" | \"userId\" | \"createdAt\" | \"updatedAt\">;\n\tstate: string;\n\tcallbackURL: string;\n\tnewUserURL?: string;\n\terrorURL?: string;\n\tdisableSignUp?: boolean;\n\ttimestamp: number;\n};\n\nconst oauthProxyQuerySchema = z.object({\n\tcallbackURL: z.string().meta({\n\t\tdescription: \"The URL to redirect to after the proxy\",\n\t}),\n\tprofile: z.string().optional().meta({\n\t\tdescription: \"Encrypted OAuth profile data\",\n\t}),\n});\n\nconst oauthCallbackQuerySchema = z.object({\n\tcode: z.string().optional(),\n\terror: z.string().optional(),\n});\n\nexport const oAuthProxy = <O extends OAuthProxyOptions>(opts?: O) => {\n\tconst maxAge = opts?.maxAge ?? 60; // Default 60 seconds\n\n\treturn {\n\t\tid: \"oauth-proxy\",\n\t\toptions: opts as NoInfer<O>,\n\t\tendpoints: {\n\t\t\toAuthProxy: createAuthEndpoint(\n\t\t\t\t\"/oauth-proxy-callback\",\n\t\t\t\t{\n\t\t\t\t\tmethod: \"GET\",\n\t\t\t\t\toperationId: \"oauthProxyCallback\",\n\t\t\t\t\tquery: oauthProxyQuerySchema,\n\t\t\t\t\tuse: [originCheck((ctx) => ctx.query.callbackURL)],\n\t\t\t\t\tmetadata: {\n\t\t\t\t\t\topenapi: {\n\t\t\t\t\t\t\toperationId: \"oauthProxyCallback\",\n\t\t\t\t\t\t\tdescription: \"OAuth Proxy Callback\",\n\t\t\t\t\t\t\tparameters: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tin: \"query\",\n\t\t\t\t\t\t\t\t\tname: \"callbackURL\",\n\t\t\t\t\t\t\t\t\trequired: true,\n\t\t\t\t\t\t\t\t\tdescription: \"The URL to redirect to after the proxy\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tin: \"query\",\n\t\t\t\t\t\t\t\t\tname: \"profile\",\n\t\t\t\t\t\t\t\t\trequired: false,\n\t\t\t\t\t\t\t\t\tdescription: \"Encrypted OAuth profile data\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tresponses: {\n\t\t\t\t\t\t\t\t302: {\n\t\t\t\t\t\t\t\t\tdescription: \"Redirect\",\n\t\t\t\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t\t\t\tLocation: {\n\t\t\t\t\t\t\t\t\t\t\tdescription: \"The URL to redirect to\",\n\t\t\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tasync (ctx) => {\n\t\t\t\t\tconst baseURLStr =\n\t\t\t\t\t\ttypeof ctx.context.options.baseURL === \"string\"\n\t\t\t\t\t\t\t? ctx.context.options.baseURL\n\t\t\t\t\t\t\t: getOrigin(ctx.context.baseURL) || \"\";\n\t\t\t\t\tconst defaultErrorURL =\n\t\t\t\t\t\tctx.context.options.onAPIError?.errorURL ||\n\t\t\t\t\t\t`${stripTrailingSlash(baseURLStr)}/api/auth/error`;\n\n\t\t\t\t\tconst encryptedProfile = ctx.query.profile;\n\t\t\t\t\tif (!encryptedProfile) {\n\t\t\t\t\t\tctx.context.logger.error(\n\t\t\t\t\t\t\t\"OAuth proxy callback missing profile data\",\n\t\t\t\t\t\t);\n\t\t\t\t\t\tthrow redirectOnError(ctx, defaultErrorURL, \"missing_profile\");\n\t\t\t\t\t}\n\n\t\t\t\t\t// Decrypt profile payload\n\t\t\t\t\tlet decryptedPayload: string;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tdecryptedPayload = await symmetricDecrypt({\n\t\t\t\t\t\t\tkey: ctx.context.secretConfig,\n\t\t\t\t\t\t\tdata: encryptedProfile,\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tctx.context.logger.error(\n\t\t\t\t\t\t\t\"Failed to decrypt OAuth proxy profile\",\n\t\t\t\t\t\t\te,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tthrow redirectOnError(ctx, defaultErrorURL, \"invalid_profile\");\n\t\t\t\t\t}\n\n\t\t\t\t\tlet payload: PassthroughPayload;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tpayload = parseJSON<PassthroughPayload>(decryptedPayload);\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tctx.context.logger.error(\"Failed to parse OAuth proxy payload\", e);\n\t\t\t\t\t\tthrow redirectOnError(ctx, defaultErrorURL, \"invalid_payload\");\n\t\t\t\t\t}\n\n\t\t\t\t\t// Validate required payload fields\n\t\t\t\t\tif (\n\t\t\t\t\t\ttypeof payload.timestamp !== \"number\" ||\n\t\t\t\t\t\t!payload.userInfo ||\n\t\t\t\t\t\t!payload.account ||\n\t\t\t\t\t\t!payload.callbackURL\n\t\t\t\t\t) {\n\t\t\t\t\t\tctx.context.logger.error(\"Failed to parse OAuth proxy payload\");\n\t\t\t\t\t\tthrow redirectOnError(ctx, defaultErrorURL, \"invalid_payload\");\n\t\t\t\t\t}\n\n\t\t\t\t\tconst errorURL = payload.errorURL || defaultErrorURL;\n\n\t\t\t\t\t// Allow up to 10 seconds of future skew for clock skew\n\t\t\t\t\tconst now = Date.now();\n\t\t\t\t\tconst age = (now - payload.timestamp) / 1000;\n\t\t\t\t\tif (age > maxAge || age < -10) {\n\t\t\t\t\t\tctx.context.logger.error(\n\t\t\t\t\t\t\t`OAuth proxy payload expired or invalid (age: ${age}s, maxAge: ${maxAge}s)`,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tthrow redirectOnError(ctx, errorURL, \"payload_expired\");\n\t\t\t\t\t}\n\n\t\t\t\t\t// Clean up OAuth state\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait parseGenericState(ctx, payload.state);\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tctx.context.logger.warn(\"Failed to clean up OAuth state\", e);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst result = await handleOAuthUserInfo(ctx, {\n\t\t\t\t\t\tuserInfo: payload.userInfo,\n\t\t\t\t\t\taccount: payload.account,\n\t\t\t\t\t\tcallbackURL: payload.callbackURL,\n\t\t\t\t\t\tdisableSignUp: payload.disableSignUp,\n\t\t\t\t\t});\n\t\t\t\t\tif (result.error || !result.data) {\n\t\t\t\t\t\tctx.context.logger.error(\n\t\t\t\t\t\t\t\"Failed to create user or session\",\n\t\t\t\t\t\t\tresult.error,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tthrow redirectOnError(ctx, errorURL, \"user_creation_failed\");\n\t\t\t\t\t}\n\n\t\t\t\t\tawait setSessionCookie(ctx, result.data);\n\n\t\t\t\t\t// Redirect to final callback URL\n\t\t\t\t\tconst finalURL = result.isRegister\n\t\t\t\t\t\t? payload.newUserURL || payload.callbackURL\n\t\t\t\t\t\t: payload.callbackURL;\n\n\t\t\t\t\tthrow ctx.redirect(finalURL);\n\t\t\t\t},\n\t\t\t),\n\t\t},\n\t\thooks: {\n\t\t\tbefore: [\n\t\t\t\t{\n\t\t\t\t\tmatcher(context) {\n\t\t\t\t\t\treturn !!(\n\t\t\t\t\t\t\tcontext.path?.startsWith(\"/sign-in/social\") ||\n\t\t\t\t\t\t\tcontext.path?.startsWith(\"/sign-in/oauth2\")\n\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t\thandler: createAuthMiddleware(async (ctx) => {\n\t\t\t\t\t\tconst skipProxy = checkSkipProxy(ctx, opts);\n\t\t\t\t\t\tif (skipProxy) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst currentURL = resolveCurrentURL(ctx, opts);\n\t\t\t\t\t\tconst productionURL = opts?.productionURL;\n\t\t\t\t\t\tconst originalCallbackURL =\n\t\t\t\t\t\t\tctx.body?.callbackURL || ctx.context.baseURL;\n\n\t\t\t\t\t\t// Override baseURL to production so redirect_uri points to production\n\t\t\t\t\t\t// This ensures OAuth provider callbacks go to the production server\n\t\t\t\t\t\tif (productionURL) {\n\t\t\t\t\t\t\tconst productionBaseURL = `${stripTrailingSlash(productionURL)}${ctx.context.options.basePath || \"/api/auth\"}`;\n\t\t\t\t\t\t\tctx.context.baseURL = productionBaseURL;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Construct proxy callback URL\n\t\t\t\t\t\tconst newCallbackURL = `${stripTrailingSlash(currentURL.origin)}${\n\t\t\t\t\t\t\tctx.context.options.basePath || \"/api/auth\"\n\t\t\t\t\t\t}/oauth-proxy-callback?callbackURL=${encodeURIComponent(\n\t\t\t\t\t\t\toriginalCallbackURL,\n\t\t\t\t\t\t)}`;\n\n\t\t\t\t\t\tif (!ctx.body) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tctx.body.callbackURL = newCallbackURL;\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t// Intercept OAuth callback on production to handle passthrough\n\t\t\t\t\tmatcher(context) {\n\t\t\t\t\t\treturn context.path === \"/callback/:id\";\n\t\t\t\t\t},\n\t\t\t\t\thandler: createAuthMiddleware(async (ctx) => {\n\t\t\t\t\t\tconst state = ctx.query?.state || ctx.body?.state;\n\t\t\t\t\t\tif (!state || typeof state !== \"string\") {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Try to decrypt and parse OAuth proxy state package\n\t\t\t\t\t\tlet statePackage: OAuthProxyStatePackage | undefined;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst decryptedPackage = await symmetricDecrypt({\n\t\t\t\t\t\t\t\tkey: ctx.context.secretConfig,\n\t\t\t\t\t\t\t\tdata: state,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tstatePackage =\n\t\t\t\t\t\t\t\tparseJSON<OAuthProxyStatePackage>(decryptedPackage);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// Not an OAuth proxy state, continue normally\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t!statePackage.isOAuthProxy ||\n\t\t\t\t\t\t\t!statePackage.state ||\n\t\t\t\t\t\t\t!statePackage.stateCookie\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tctx.context.logger.warn(\"Invalid OAuth proxy state package\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst query = oauthCallbackQuerySchema.safeParse(ctx.query);\n\t\t\t\t\t\tif (!query.success) {\n\t\t\t\t\t\t\tctx.context.logger.warn(\n\t\t\t\t\t\t\t\t\"Invalid OAuth callback query\",\n\t\t\t\t\t\t\t\tquery.error,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst { code, error } = query.data;\n\n\t\t\t\t\t\t// Decrypt state to get codeVerifier and callbackURL\n\t\t\t\t\t\tlet stateData: StateData;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst decryptedState = await symmetricDecrypt({\n\t\t\t\t\t\t\t\tkey: ctx.context.secretConfig,\n\t\t\t\t\t\t\t\tdata: statePackage.stateCookie,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tstateData = parseJSON<StateData>(decryptedState);\n\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\tctx.context.logger.error(\n\t\t\t\t\t\t\t\t\"Failed to decrypt OAuth proxy state cookie:\",\n\t\t\t\t\t\t\t\te,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst errorURL =\n\t\t\t\t\t\t\tstateData.errorURL ||\n\t\t\t\t\t\t\tctx.context.options.onAPIError?.errorURL ||\n\t\t\t\t\t\t\t`${ctx.context.baseURL}/error`;\n\t\t\t\t\t\tif (error) {\n\t\t\t\t\t\t\tthrow redirectOnError(ctx, errorURL, error);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!code) {\n\t\t\t\t\t\t\tctx.context.logger.error(\n\t\t\t\t\t\t\t\t\"OAuth callback missing authorization code\",\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tthrow redirectOnError(ctx, errorURL, \"no_code\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Find the OAuth provider\n\t\t\t\t\t\tconst providerId = ctx.params?.id;\n\t\t\t\t\t\tconst provider = ctx.context.socialProviders.find(\n\t\t\t\t\t\t\t(p) => p.id === providerId,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (!provider) {\n\t\t\t\t\t\t\tctx.context.logger.error(\"OAuth provider not found\", providerId);\n\t\t\t\t\t\t\tthrow redirectOnError(ctx, errorURL, \"oauth_provider_not_found\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Exchange code for tokens\n\t\t\t\t\t\tlet tokens: OAuth2Tokens | null;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\ttokens = await provider.validateAuthorizationCode({\n\t\t\t\t\t\t\t\tcode,\n\t\t\t\t\t\t\t\tcodeVerifier: stateData.codeVerifier,\n\t\t\t\t\t\t\t\tredirectURI: `${ctx.context.baseURL}/callback/${provider.id}`,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\tctx.context.logger.error(\n\t\t\t\t\t\t\t\t\"Failed to validate authorization code\",\n\t\t\t\t\t\t\t\te,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tthrow redirectOnError(ctx, errorURL, \"invalid_code\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!tokens) {\n\t\t\t\t\t\t\tthrow redirectOnError(ctx, errorURL, \"invalid_code\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Get user info from provider\n\t\t\t\t\t\tconst userInfoResult = await provider.getUserInfo(tokens);\n\t\t\t\t\t\tconst userInfo = userInfoResult?.user;\n\n\t\t\t\t\t\tif (!userInfo) {\n\t\t\t\t\t\t\tctx.context.logger.error(\"Unable to get user info from provider\");\n\t\t\t\t\t\t\tthrow redirectOnError(ctx, errorURL, \"unable_to_get_user_info\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!userInfo.email) {\n\t\t\t\t\t\t\tctx.context.logger.error(\"Provider did not return email\");\n\t\t\t\t\t\t\tthrow redirectOnError(ctx, errorURL, \"email_not_found\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst proxyCallbackURL = new URL(stateData.callbackURL);\n\t\t\t\t\t\tconst finalCallbackURL =\n\t\t\t\t\t\t\tproxyCallbackURL.searchParams.get(\"callbackURL\") ||\n\t\t\t\t\t\t\tstateData.callbackURL;\n\n\t\t\t\t\t\tconst payload: PassthroughPayload = {\n\t\t\t\t\t\t\tuserInfo: {\n\t\t\t\t\t\t\t\tid: String(userInfo.id),\n\t\t\t\t\t\t\t\temail: userInfo.email,\n\t\t\t\t\t\t\t\tname: userInfo.name || \"\",\n\t\t\t\t\t\t\t\timage: userInfo.image,\n\t\t\t\t\t\t\t\temailVerified: userInfo.emailVerified,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\taccount: {\n\t\t\t\t\t\t\t\tproviderId: provider.id,\n\t\t\t\t\t\t\t\taccountId: String(userInfo.id),\n\t\t\t\t\t\t\t\taccessToken: tokens.accessToken,\n\t\t\t\t\t\t\t\trefreshToken: tokens.refreshToken,\n\t\t\t\t\t\t\t\tidToken: tokens.idToken,\n\t\t\t\t\t\t\t\taccessTokenExpiresAt: tokens.accessTokenExpiresAt,\n\t\t\t\t\t\t\t\trefreshTokenExpiresAt: tokens.refreshTokenExpiresAt,\n\t\t\t\t\t\t\t\tscope: tokens.scopes?.join(\",\"),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tstate: statePackage.state,\n\t\t\t\t\t\t\tcallbackURL: finalCallbackURL,\n\t\t\t\t\t\t\tnewUserURL: stateData.newUserURL,\n\t\t\t\t\t\t\terrorURL: stateData.errorURL,\n\t\t\t\t\t\t\tdisableSignUp:\n\t\t\t\t\t\t\t\t(provider.disableImplicitSignUp && !stateData.requestSignUp) ||\n\t\t\t\t\t\t\t\tprovider.options?.disableSignUp,\n\t\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tconst encryptedPayload = await symmetricEncrypt({\n\t\t\t\t\t\t\tkey: ctx.context.secretConfig,\n\t\t\t\t\t\t\tdata: JSON.stringify(payload),\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// Add the profile parameter to proxy callback URL\n\t\t\t\t\t\tproxyCallbackURL.searchParams.set(\"profile\", encryptedPayload);\n\n\t\t\t\t\t\t// Redirect to preview's oauth-proxy-callback with profile data\n\t\t\t\t\t\tthrow ctx.redirect(proxyCallbackURL.toString());\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t],\n\t\t\tafter: [\n\t\t\t\t{\n\t\t\t\t\tmatcher(context) {\n\t\t\t\t\t\treturn !!(\n\t\t\t\t\t\t\tcontext.path?.startsWith(\"/sign-in/social\") ||\n\t\t\t\t\t\t\tcontext.path?.startsWith(\"/sign-in/oauth2\")\n\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t\thandler: createAuthMiddleware(async (ctx) => {\n\t\t\t\t\t\tconst skipProxy = checkSkipProxy(ctx, opts);\n\t\t\t\t\t\tif (skipProxy) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Extract OAuth provider URL from sign-in response\n\t\t\t\t\t\tconst signInResponse = ctx.context.returned;\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t!signInResponse ||\n\t\t\t\t\t\t\ttypeof signInResponse !== \"object\" ||\n\t\t\t\t\t\t\t!(\"url\" in signInResponse)\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst { url: providerURL } = signInResponse;\n\t\t\t\t\t\tif (typeof providerURL !== \"string\") {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Parse provider URL and extract state parameter\n\t\t\t\t\t\tconst oauthURL = new URL(providerURL);\n\t\t\t\t\t\tconst originalState = oauthURL.searchParams.get(\"state\");\n\t\t\t\t\t\tif (!originalState) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Get state value based on storage strategy\n\t\t\t\t\t\tlet stateCookieValue: string | undefined;\n\t\t\t\t\t\tif (ctx.context.oauthConfig.storeStateStrategy === \"cookie\") {\n\t\t\t\t\t\t\t// Cookie mode - extract from response headers\n\t\t\t\t\t\t\tconst headers = ctx.context.responseHeaders;\n\t\t\t\t\t\t\tconst setCookieHeader = headers?.get(\"set-cookie\");\n\t\t\t\t\t\t\tif (setCookieHeader) {\n\t\t\t\t\t\t\t\tconst parsedCookies = parseSetCookieHeader(setCookieHeader);\n\t\t\t\t\t\t\t\tconst stateCookie = ctx.context.createAuthCookie(\"oauth_state\");\n\t\t\t\t\t\t\t\tconst stateCookieAttrs = parsedCookies.get(stateCookie.name);\n\t\t\t\t\t\t\t\tstateCookieValue = stateCookieAttrs?.value;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Database mode - read from DB\n\t\t\t\t\t\t\tconst verification =\n\t\t\t\t\t\t\t\tawait ctx.context.internalAdapter.findVerificationValue(\n\t\t\t\t\t\t\t\t\toriginalState,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tif (verification) {\n\t\t\t\t\t\t\t\t// Encrypt the verification value so it matches cookie mode format\n\t\t\t\t\t\t\t\tstateCookieValue = await symmetricEncrypt({\n\t\t\t\t\t\t\t\t\tkey: ctx.context.secretConfig,\n\t\t\t\t\t\t\t\t\tdata: verification.value,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!stateCookieValue) {\n\t\t\t\t\t\t\tctx.context.logger.warn(\"No OAuth state cookie value found\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t// Create and encrypt state package\n\t\t\t\t\t\t\tconst statePackage: OAuthProxyStatePackage = {\n\t\t\t\t\t\t\t\tstate: originalState,\n\t\t\t\t\t\t\t\tstateCookie: stateCookieValue,\n\t\t\t\t\t\t\t\tisOAuthProxy: true,\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tconst encryptedPackage = await symmetricEncrypt({\n\t\t\t\t\t\t\t\tkey: ctx.context.secretConfig,\n\t\t\t\t\t\t\t\tdata: JSON.stringify(statePackage),\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t// Replace state parameter with encrypted package\n\t\t\t\t\t\t\toauthURL.searchParams.set(\"state\", encryptedPackage);\n\n\t\t\t\t\t\t\t// Update response with modified URL\n\t\t\t\t\t\t\tctx.context.returned = {\n\t\t\t\t\t\t\t\t...signInResponse,\n\t\t\t\t\t\t\t\turl: oauthURL.toString(),\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\tctx.context.logger.error(\n\t\t\t\t\t\t\t\t\"Failed to encrypt OAuth proxy state package:\",\n\t\t\t\t\t\t\t\te,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t// Continue without proxy\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tmatcher(context) {\n\t\t\t\t\t\treturn context.path === \"/callback/:id\";\n\t\t\t\t\t},\n\t\t\t\t\thandler: createAuthMiddleware(async (ctx) => {\n\t\t\t\t\t\tconst headers = ctx.context.responseHeaders;\n\t\t\t\t\t\tconst location = headers?.get(\"location\");\n\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t!location?.includes(\"/oauth-proxy-callback?callbackURL\") ||\n\t\t\t\t\t\t\t!location.startsWith(\"http\")\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst productionURL =\n\t\t\t\t\t\t\topts?.productionURL ||\n\t\t\t\t\t\t\t(typeof ctx.context.options.baseURL === \"string\"\n\t\t\t\t\t\t\t\t? ctx.context.options.baseURL\n\t\t\t\t\t\t\t\t: undefined) ||\n\t\t\t\t\t\t\tctx.context.baseURL;\n\t\t\t\t\t\tconst productionOrigin = getOrigin(productionURL);\n\n\t\t\t\t\t\tconst locationURL = new URL(location);\n\t\t\t\t\t\tconst locationOrigin = locationURL.origin;\n\n\t\t\t\t\t\t// Same origin: unwrap proxy redirect to original destination\n\t\t\t\t\t\tif (locationOrigin === productionOrigin) {\n\t\t\t\t\t\t\tconst newLocation = locationURL.searchParams.get(\"callbackURL\");\n\t\t\t\t\t\t\tif (!newLocation) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tctx.setHeader(\"location\", newLocation);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Cross-origin should have been handled by before hook\n\t\t\t\t\t\tctx.context.logger.warn(\n\t\t\t\t\t\t\t\"OAuth proxy: cross-origin callback reached after hook unexpectedly\",\n\t\t\t\t\t\t);\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t} satisfies BetterAuthPlugin;\n};\n"],"mappings":"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"}