UNPKG

better-auth

Version:

The most comprehensive authentication framework for TypeScript.

1 lines • 27.2 kB
{"version":3,"file":"index.mjs","names":["decryptedPayload: string | null","payload: EncryptedCookiesPayload","options: CookieOptions","statePackage: OAuthProxyStatePackage | undefined","stateCookieValue: string","parsedState: { callbackURL?: string } | undefined","statePackage: OAuthProxyStatePackage"],"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 { CookieOptions } from \"better-call\";\nimport * as z from \"zod\";\nimport { originCheck } from \"../../api\";\nimport { parseJSON } from \"../../client/parser\";\nimport { parseSetCookieHeader } from \"../../cookies\";\nimport { symmetricDecrypt, symmetricEncrypt } from \"../../crypto\";\nimport { getOrigin } from \"../../utils/url\";\nimport type { AuthContextWithSnapshot, OAuthProxyStatePackage } from \"./types\";\nimport { checkSkipProxy, resolveCurrentURL } from \"./utils\";\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 cookies 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\ninterface EncryptedCookiesPayload {\n\tcookies: string;\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\tcookies: z.string().meta({\n\t\tdescription: \"The cookies to set after the proxy\",\n\t}),\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: \"cookies\",\n\t\t\t\t\t\t\t\t\trequired: true,\n\t\t\t\t\t\t\t\t\tdescription: \"The cookies to set after the proxy\",\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\tlet decryptedPayload: string | null = null;\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.secret,\n\t\t\t\t\t\t\tdata: ctx.query.cookies,\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 cookies:\",\n\t\t\t\t\t\t\te,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!decryptedPayload) {\n\t\t\t\t\t\tconst errorURL =\n\t\t\t\t\t\t\tctx.context.options.onAPIError?.errorURL ||\n\t\t\t\t\t\t\t`${ctx.context.options.baseURL}/api/auth/error`;\n\n\t\t\t\t\t\tthrow ctx.redirect(\n\t\t\t\t\t\t\t`${errorURL}?error=OAuthProxy - Invalid cookies or secret`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tlet payload: EncryptedCookiesPayload;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tpayload = parseJSON<EncryptedCookiesPayload>(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\tconst errorURL =\n\t\t\t\t\t\t\tctx.context.options.onAPIError?.errorURL ||\n\t\t\t\t\t\t\t`${ctx.context.options.baseURL}/api/auth/error`;\n\n\t\t\t\t\t\tthrow ctx.redirect(\n\t\t\t\t\t\t\t`${errorURL}?error=OAuthProxy - Invalid payload format`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (\n\t\t\t\t\t\t!payload.cookies ||\n\t\t\t\t\t\ttypeof payload.cookies !== \"string\" ||\n\t\t\t\t\t\ttypeof payload.timestamp !== \"number\"\n\t\t\t\t\t) {\n\t\t\t\t\t\tctx.context.logger.error(\n\t\t\t\t\t\t\t\"OAuth proxy payload missing required fields\",\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst errorURL =\n\t\t\t\t\t\t\tctx.context.options.onAPIError?.errorURL ||\n\t\t\t\t\t\t\t`${ctx.context.options.baseURL}/api/auth/error`;\n\n\t\t\t\t\t\tthrow ctx.redirect(\n\t\t\t\t\t\t\t`${errorURL}?error=OAuthProxy - Invalid payload structure`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst now = Date.now();\n\t\t\t\t\tconst age = (now - payload.timestamp) / 1000;\n\n\t\t\t\t\t// Allow up to 10 seconds of future skew for clock differences\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\tconst errorURL =\n\t\t\t\t\t\t\tctx.context.options.onAPIError?.errorURL ||\n\t\t\t\t\t\t\t`${ctx.context.options.baseURL}/api/auth/error`;\n\n\t\t\t\t\t\tthrow ctx.redirect(\n\t\t\t\t\t\t\t`${errorURL}?error=OAuthProxy - Payload expired or invalid`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst decryptedCookies = payload.cookies;\n\n\t\t\t\t\tconst currentURL = resolveCurrentURL(ctx, opts);\n\t\t\t\t\tconst isSecureContext = currentURL.protocol === \"https:\";\n\n\t\t\t\t\tconst parsedCookies = parseSetCookieHeader(decryptedCookies);\n\t\t\t\t\tconst processedCookies = Array.from(parsedCookies.entries()).map(\n\t\t\t\t\t\t([name, attrs]) => {\n\t\t\t\t\t\t\tconst options: CookieOptions = {};\n\t\t\t\t\t\t\tif (attrs.path) {\n\t\t\t\t\t\t\t\toptions.path = attrs.path;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (attrs.expires) {\n\t\t\t\t\t\t\t\toptions.expires = attrs.expires;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (attrs.samesite) {\n\t\t\t\t\t\t\t\toptions.sameSite = attrs.samesite;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (attrs.httponly) {\n\t\t\t\t\t\t\t\toptions.httpOnly = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (attrs[\"max-age\"] !== undefined) {\n\t\t\t\t\t\t\t\toptions.maxAge = attrs[\"max-age\"];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (isSecureContext) {\n\t\t\t\t\t\t\t\toptions.secure = true;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\t\toptions,\n\t\t\t\t\t\t\t\t/**\n\t\t\t\t\t\t\t\t * URI-decoded value because `ctx.setCookie` will URI-encode it again\n\t\t\t\t\t\t\t\t */\n\t\t\t\t\t\t\t\tvalue: decodeURIComponent(attrs.value),\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\n\t\t\t\t\tfor (const cookie of processedCookies) {\n\t\t\t\t\t\t// using `ctx.setHeader` overrides previous Set-Cookie headers\n\t\t\t\t\t\t// so use ctx.setCookie helper instead\n\t\t\t\t\t\t// https://github.com/Bekacru/better-call/blob/d27ac20e64b329a4851e97adf864098a9bc2a260/src/context.ts#L217\n\t\t\t\t\t\tctx.setCookie(cookie.name, cookie.value, cookie.options);\n\t\t\t\t\t}\n\t\t\t\t\tthrow ctx.redirect(ctx.query.callbackURL);\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 originalCallbackURL =\n\t\t\t\t\t\t\tctx.body?.callbackURL || ctx.context.baseURL;\n\n\t\t\t\t\t\t// Construct proxy callback URL\n\t\t\t\t\t\tconst newCallbackURL = `${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\tmatcher(context) {\n\t\t\t\t\t\treturn !!(\n\t\t\t\t\t\t\tcontext.path?.startsWith(\"/callback\") ||\n\t\t\t\t\t\t\tcontext.path?.startsWith(\"/oauth2/callback\")\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 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.secret,\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\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Decrypt the state cookie content\n\t\t\t\t\t\tlet stateCookieValue: string;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tstateCookieValue = await symmetricDecrypt({\n\t\t\t\t\t\t\t\tkey: ctx.context.secret,\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\tparseJSON(stateCookieValue);\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\t// Snapshot original configuration for restoration in after hook\n\t\t\t\t\t\t(ctx.context as AuthContextWithSnapshot)._oauthProxySnapshot = {\n\t\t\t\t\t\t\tstoreStateStrategy: ctx.context.oauthConfig.storeStateStrategy,\n\t\t\t\t\t\t\tskipStateCookieCheck:\n\t\t\t\t\t\t\t\tctx.context.oauthConfig.skipStateCookieCheck,\n\t\t\t\t\t\t\tinternalAdapter: ctx.context.internalAdapter,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\t// Temporarily switch to database mode and inject verification value\n\t\t\t\t\t\t// This allows the OAuth callback handler to retrieve state data without database\n\t\t\t\t\t\tconst originalAdapter = ctx.context.internalAdapter;\n\t\t\t\t\t\tconst capturedStatePackage = statePackage;\n\t\t\t\t\t\tctx.context.oauthConfig.storeStateStrategy = \"database\";\n\t\t\t\t\t\tctx.context.internalAdapter = {\n\t\t\t\t\t\t\t...ctx.context.internalAdapter,\n\t\t\t\t\t\t\tfindVerificationValue: async (identifier: string) => {\n\t\t\t\t\t\t\t\tif (identifier === capturedStatePackage.state) {\n\t\t\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t\t\tid: `oauth-proxy-${capturedStatePackage.state}`,\n\t\t\t\t\t\t\t\t\t\tidentifier: capturedStatePackage.state,\n\t\t\t\t\t\t\t\t\t\tvalue: stateCookieValue,\n\t\t\t\t\t\t\t\t\t\tcreatedAt: new Date(),\n\t\t\t\t\t\t\t\t\t\tupdatedAt: new Date(),\n\t\t\t\t\t\t\t\t\t\t// Align expiration time with `generateState` in oauth2\n\t\t\t\t\t\t\t\t\t\texpiresAt: new Date(Date.now() + 10 * 60 * 1000),\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\treturn originalAdapter.findVerificationValue(identifier);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\t// Restore original state parameter\n\t\t\t\t\t\tif (ctx.query?.state) {\n\t\t\t\t\t\t\tctx.query.state = statePackage.state;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (ctx.body?.state) {\n\t\t\t\t\t\t\tctx.body.state = statePackage.state;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Enable skipStateCookieCheck for database mode\n\t\t\t\t\t\tctx.context.oauthConfig.skipStateCookieCheck = true;\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tmatcher() {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t},\n\t\t\t\t\thandler: createAuthMiddleware(async (ctx) => {\n\t\t\t\t\t\tif (ctx.path !== \"/callback/:id\") {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (ctx.context.oauthConfig.storeStateStrategy === \"cookie\") {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Skip if OAuth proxy stateless flow already handled by previous hook\n\t\t\t\t\t\tif ((ctx.context as AuthContextWithSnapshot)._oauthProxySnapshot) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst state = ctx.query?.state || ctx.body?.state;\n\t\t\t\t\t\tif (!state) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst data =\n\t\t\t\t\t\t\tawait ctx.context.internalAdapter.findVerificationValue(state);\n\t\t\t\t\t\tif (!data) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet parsedState: { callbackURL?: string } | undefined;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tparsedState = parseJSON<{ callbackURL?: string }>(data.value);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\tparsedState = undefined;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!parsedState?.callbackURL?.includes(\"/oauth-proxy-callback\")) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Snapshot original configuration for restoration in after hook\n\t\t\t\t\t\t(ctx.context as AuthContextWithSnapshot)._oauthProxySnapshot = {\n\t\t\t\t\t\t\tstoreStateStrategy: ctx.context.oauthConfig.storeStateStrategy,\n\t\t\t\t\t\t\tskipStateCookieCheck:\n\t\t\t\t\t\t\t\tctx.context.oauthConfig.skipStateCookieCheck,\n\t\t\t\t\t\t\tinternalAdapter: ctx.context.internalAdapter,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tctx.context.oauthConfig.skipStateCookieCheck = true;\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// Only process in stateless mode\n\t\t\t\t\t\tif (ctx.context.oauthConfig.storeStateStrategy !== \"cookie\") {\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// Extract state cookie from response headers\n\t\t\t\t\t\tconst headers = ctx.context.responseHeaders;\n\t\t\t\t\t\tconst setCookieHeader = headers?.get(\"set-cookie\");\n\t\t\t\t\t\tif (!setCookieHeader) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst stateCookie = ctx.context.createAuthCookie(\"oauth_state\");\n\t\t\t\t\t\tconst parsedStateCookies = parseSetCookieHeader(setCookieHeader);\n\t\t\t\t\t\tconst stateCookieAttrs = parsedStateCookies.get(stateCookie.name);\n\t\t\t\t\t\tif (!stateCookieAttrs?.value) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst stateCookieValue = stateCookieAttrs.value;\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.secret,\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 !!(\n\t\t\t\t\t\t\tcontext.path?.startsWith(\"/callback\") ||\n\t\t\t\t\t\t\tcontext.path?.startsWith(\"/oauth2/callback\")\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 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\tctx.context.options.baseURL ||\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//\n\t\t\t\t\t\t// Same origin: unwrap proxy redirect to original destination\n\t\t\t\t\t\t//\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//\n\t\t\t\t\t\t// Cross-origin: encrypt and forward cookies through proxy\n\t\t\t\t\t\t//\n\t\t\t\t\t\tconst setCookies = headers?.get(\"set-cookie\");\n\t\t\t\t\t\tif (!setCookies) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Create payload with timestamp for replay attack protection\n\t\t\t\t\t\tconst payload: EncryptedCookiesPayload = {\n\t\t\t\t\t\t\tcookies: setCookies,\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 encryptedCookies = await symmetricEncrypt({\n\t\t\t\t\t\t\tkey: ctx.context.secret,\n\t\t\t\t\t\t\tdata: JSON.stringify(payload),\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst locationWithCookies = `${location}&cookies=${encodeURIComponent(\n\t\t\t\t\t\t\tencryptedCookies,\n\t\t\t\t\t\t)}`;\n\n\t\t\t\t\t\tctx.setHeader(\"location\", locationWithCookies);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t// Restore OAuth config after processing callback\n\t\t\t\t\tmatcher(context) {\n\t\t\t\t\t\treturn !!(\n\t\t\t\t\t\t\tcontext.path?.startsWith(\"/callback\") ||\n\t\t\t\t\t\t\tcontext.path?.startsWith(\"/oauth2/callback\")\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 contextWithSnapshot = ctx.context as AuthContextWithSnapshot;\n\t\t\t\t\t\tconst snapshot = contextWithSnapshot._oauthProxySnapshot;\n\t\t\t\t\t\tif (snapshot) {\n\t\t\t\t\t\t\tctx.context.oauthConfig.storeStateStrategy =\n\t\t\t\t\t\t\t\tsnapshot.storeStateStrategy;\n\t\t\t\t\t\t\tctx.context.oauthConfig.skipStateCookieCheck =\n\t\t\t\t\t\t\t\tsnapshot.skipStateCookieCheck;\n\t\t\t\t\t\t\tctx.context.internalAdapter = snapshot.internalAdapter;\n\n\t\t\t\t\t\t\t// Clear the temporary extended context value\n\t\t\t\t\t\t\tcontextWithSnapshot._oauthProxySnapshot = undefined;\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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"}