{"version":3,"file":"_provider_.mjs","names":[],"sources":["../../../../../../src/astro/routes/api/auth/oauth/[provider].ts"],"sourcesContent":["/**\n * GET /_emdash/api/auth/oauth/[provider]\n *\n * Start OAuth flow - redirects to provider authorization URL\n */\n\nimport type { APIRoute } from \"astro\";\n\nexport const prerender = false;\n\nimport { createAuthorizationUrl, type OAuthConsumerConfig } from \"@premium-cms/auth\";\n\nimport { getPublicOrigin } from \"#api/public-url.js\";\nimport { createOAuthStateStore } from \"#auth/oauth-state-store.js\";\n\ntype ProviderName = \"github\" | \"google\";\n\nconst VALID_PROVIDERS = new Set<string>([\"github\", \"google\"]);\n\n/** Invite tokens are base64url; clamp shape and length before persisting to state. */\nconst INVITE_TOKEN_REGEX = /^[A-Za-z0-9_-]{1,256}$/;\n\nfunction isValidProvider(provider: string): provider is ProviderName {\n\treturn VALID_PROVIDERS.has(provider);\n}\n\n/** Safely extract a string value from an env-like record */\nfunction envString(env: Record<string, unknown>, ...keys: string[]): string | undefined {\n\tfor (const key of keys) {\n\t\tconst val = env[key];\n\t\tif (typeof val === \"string\" && val) return val;\n\t}\n\treturn undefined;\n}\n\n/**\n * Get OAuth config from environment variables\n */\nfunction getOAuthConfig(env: Record<string, unknown>): OAuthConsumerConfig[\"providers\"] {\n\tconst providers: OAuthConsumerConfig[\"providers\"] = {};\n\n\t// GitHub\n\tconst githubClientId = envString(env, \"EMDASH_OAUTH_GITHUB_CLIENT_ID\", \"GITHUB_CLIENT_ID\");\n\tconst githubClientSecret = envString(\n\t\tenv,\n\t\t\"EMDASH_OAUTH_GITHUB_CLIENT_SECRET\",\n\t\t\"GITHUB_CLIENT_SECRET\",\n\t);\n\tif (githubClientId && githubClientSecret) {\n\t\tproviders.github = {\n\t\t\tclientId: githubClientId,\n\t\t\tclientSecret: githubClientSecret,\n\t\t};\n\t}\n\n\t// Google\n\tconst googleClientId = envString(env, \"EMDASH_OAUTH_GOOGLE_CLIENT_ID\", \"GOOGLE_CLIENT_ID\");\n\tconst googleClientSecret = envString(\n\t\tenv,\n\t\t\"EMDASH_OAUTH_GOOGLE_CLIENT_SECRET\",\n\t\t\"GOOGLE_CLIENT_SECRET\",\n\t);\n\tif (googleClientId && googleClientSecret) {\n\t\tproviders.google = {\n\t\t\tclientId: googleClientId,\n\t\t\tclientSecret: googleClientSecret,\n\t\t};\n\t}\n\n\treturn providers;\n}\n\nexport const GET: APIRoute = async ({ params, request, locals, redirect }) => {\n\tconst { emdash } = locals;\n\tconst provider = params.provider;\n\n\t// Determine where to redirect errors (setup wizard or login page)\n\tconst referer = request.headers.get(\"referer\") ?? \"\";\n\tconst errorRedirectBase = referer.includes(\"/setup\")\n\t\t? \"/_emdash/admin/setup\"\n\t\t: \"/_emdash/admin/login\";\n\n\t// Validate provider\n\tif (!provider || !isValidProvider(provider)) {\n\t\treturn redirect(\n\t\t\t`${errorRedirectBase}?error=invalid_provider&message=${encodeURIComponent(\"Invalid OAuth provider\")}`,\n\t\t);\n\t}\n\n\tif (!emdash?.db) {\n\t\treturn redirect(\n\t\t\t`${errorRedirectBase}?error=server_error&message=${encodeURIComponent(\"Database not configured\")}`,\n\t\t);\n\t}\n\n\ttry {\n\t\tconst url = new URL(request.url);\n\n\t\t// Get OAuth providers from environment. Astro 6 removed\n\t\t// `Astro.locals.runtime.env` (accessing it throws rather than\n\t\t// returning undefined, so optional-chaining doesn't help) -- read\n\t\t// Cloudflare bindings via the emdash virtual module instead, which\n\t\t// re-exports `cloudflare:workers`' `env` under that adapter and\n\t\t// falls back to `import.meta.env` on Node (#1736).\n\t\t// @ts-ignore - virtual module, generated by the Astro integration\n\t\tconst { env: cfEnv } = (await import(\"virtual:emdash/env\")) as {\n\t\t\tenv?: Record<string, unknown>;\n\t\t};\n\t\tconst env = cfEnv ?? import.meta.env;\n\t\tconst providers = getOAuthConfig(env);\n\n\t\tif (!providers[provider]) {\n\t\t\treturn redirect(\n\t\t\t\t`${errorRedirectBase}?error=provider_not_configured&message=${encodeURIComponent(`OAuth provider ${provider} is not configured. Set either EMDASH_OAUTH_${provider.toUpperCase()}_CLIENT_ID and EMDASH_OAUTH_${provider.toUpperCase()}_CLIENT_SECRET, or ${provider.toUpperCase()}_CLIENT_ID and ${provider.toUpperCase()}_CLIENT_SECRET.`)}`,\n\t\t\t);\n\t\t}\n\n\t\tconst config: OAuthConsumerConfig = {\n\t\t\tbaseUrl: `${getPublicOrigin(url, emdash?.config)}/_emdash`,\n\t\t\tproviders,\n\t\t};\n\n\t\tconst stateStore = createOAuthStateStore(emdash.db);\n\n\t\t// When the flow starts from an invite link, carry the invite token so the\n\t\t// callback can complete the invite for a matching, verified email. Validate\n\t\t// its shape/length first: this endpoint is unauthenticated, so we avoid\n\t\t// persisting arbitrary or oversized values into the short-lived state store.\n\t\tconst rawInvite = url.searchParams.get(\"invite\");\n\t\tconst inviteToken = rawInvite && INVITE_TOKEN_REGEX.test(rawInvite) ? rawInvite : undefined;\n\n\t\tconst { url: authUrl } = await createAuthorizationUrl(config, provider, stateStore, {\n\t\t\tinviteToken,\n\t\t});\n\n\t\treturn redirect(authUrl);\n\t} catch (error) {\n\t\tconsole.error(\"OAuth initiation error:\", error);\n\t\treturn redirect(\n\t\t\t`${errorRedirectBase}?error=oauth_error&message=${encodeURIComponent(\"Failed to start OAuth flow. Please try again.\")}`,\n\t\t);\n\t}\n};\n"],"mappings":";;;;;AAQA,MAAa,YAAY;AASzB,MAAM,kBAAkB,IAAI,IAAY,CAAC,UAAU,SAAS,CAAC;;AAG7D,MAAM,qBAAqB;AAE3B,SAAS,gBAAgB,UAA4C;AACpE,QAAO,gBAAgB,IAAI,SAAS;;;AAIrC,SAAS,UAAU,KAA8B,GAAG,MAAoC;AACvF,MAAK,MAAM,OAAO,MAAM;EACvB,MAAM,MAAM,IAAI;AAChB,MAAI,OAAO,QAAQ,YAAY,IAAK,QAAO;;;;;;AAQ7C,SAAS,eAAe,KAAgE;CACvF,MAAM,YAA8C,EAAE;CAGtD,MAAM,iBAAiB,UAAU,KAAK,iCAAiC,mBAAmB;CAC1F,MAAM,qBAAqB,UAC1B,KACA,qCACA,uBACA;AACD,KAAI,kBAAkB,mBACrB,WAAU,SAAS;EAClB,UAAU;EACV,cAAc;EACd;CAIF,MAAM,iBAAiB,UAAU,KAAK,iCAAiC,mBAAmB;CAC1F,MAAM,qBAAqB,UAC1B,KACA,qCACA,uBACA;AACD,KAAI,kBAAkB,mBACrB,WAAU,SAAS;EAClB,UAAU;EACV,cAAc;EACd;AAGF,QAAO;;AAGR,MAAa,MAAgB,OAAO,EAAE,QAAQ,SAAS,QAAQ,eAAe;CAC7E,MAAM,EAAE,WAAW;CACnB,MAAM,WAAW,OAAO;CAIxB,MAAM,qBADU,QAAQ,QAAQ,IAAI,UAAU,IAAI,IAChB,SAAS,SAAS,GACjD,yBACA;AAGH,KAAI,CAAC,YAAY,CAAC,gBAAgB,SAAS,CAC1C,QAAO,SACN,GAAG,kBAAkB,kCAAkC,mBAAmB,yBAAyB,GACnG;AAGF,KAAI,CAAC,QAAQ,GACZ,QAAO,SACN,GAAG,kBAAkB,8BAA8B,mBAAmB,0BAA0B,GAChG;AAGF,KAAI;EACH,MAAM,MAAM,IAAI,IAAI,QAAQ,IAAI;EAShC,MAAM,EAAE,KAAK,UAAW,MAAM,OAAO;EAIrC,MAAM,YAAY,eADN,SAAS,OAAO,KAAK,IACI;AAErC,MAAI,CAAC,UAAU,UACd,QAAO,SACN,GAAG,kBAAkB,yCAAyC,mBAAmB,kBAAkB,SAAS,8CAA8C,SAAS,aAAa,CAAC,8BAA8B,SAAS,aAAa,CAAC,qBAAqB,SAAS,aAAa,CAAC,iBAAiB,SAAS,aAAa,CAAC,iBAAiB,GAC3U;EAGF,MAAM,SAA8B;GACnC,SAAS,GAAG,gBAAgB,KAAK,QAAQ,OAAO,CAAC;GACjD;GACA;EAED,MAAM,aAAa,sBAAsB,OAAO,GAAG;EAMnD,MAAM,YAAY,IAAI,aAAa,IAAI,SAAS;EAGhD,MAAM,EAAE,KAAK,YAAY,MAAM,uBAAuB,QAAQ,UAAU,YAAY,EACnF,aAHmB,aAAa,mBAAmB,KAAK,UAAU,GAAG,YAAY,QAIjF,CAAC;AAEF,SAAO,SAAS,QAAQ;UAChB,OAAO;AACf,UAAQ,MAAM,2BAA2B,MAAM;AAC/C,SAAO,SACN,GAAG,kBAAkB,6BAA6B,mBAAmB,gDAAgD,GACrH"}