@rttnd/gau 1.4.2 → 1.4.3
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/{chunk-RPFUKTQA.js → chunk-MRKRGLUT.js} +1 -1
- package/dist/{chunk-RPFUKTQA.js.map → chunk-MRKRGLUT.js.map} +1 -1
- package/dist/src/client/svelte/AuthProvider/index.svelte.d.ts +13 -0
- package/dist/src/client/svelte/AuthProvider/index.svelte.d.ts.map +1 -0
- package/dist/src/client/svelte/Protected/index.svelte.d.ts +10 -0
- package/dist/src/client/svelte/Protected/index.svelte.d.ts.map +1 -0
- package/dist/{client → src/client}/svelte/index.svelte.d.ts +1 -1
- package/dist/src/client/svelte/index.svelte.d.ts.map +1 -0
- package/dist/src/client/svelte/index.svelte.js.map +1 -1
- package/dist/src/core/handlers/index.js +1 -1
- package/dist/src/core/index.d.ts +3 -3
- package/dist/src/core/index.d.ts.map +1 -1
- package/dist/src/core/index.js +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/jwt/index.js +1 -1
- package/dist/src/solidstart/index.js +1 -1
- package/dist/src/sveltekit/index.js +1 -1
- package/package.json +8 -5
- package/dist/client/svelte/index.svelte.d.ts.map +0 -1
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { GauSession, ProviderIds } from '../../../core';
|
|
3
|
+
type Props<TAuth = unknown> = {
|
|
4
|
+
baseUrl?: string;
|
|
5
|
+
scheme?: string;
|
|
6
|
+
redirectTo?: string;
|
|
7
|
+
session?: GauSession<ProviderIds<TAuth>>;
|
|
8
|
+
children: Snippet;
|
|
9
|
+
};
|
|
10
|
+
declare const Index: import("svelte").Component<Props<unknown>, {}, "">;
|
|
11
|
+
type Index = ReturnType<typeof Index>;
|
|
12
|
+
export default Index;
|
|
13
|
+
//# sourceMappingURL=index.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.svelte.d.ts","sourceRoot":"","sources":["../../../../../src/client/svelte/AuthProvider/index.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAI1D,KAAK,KAAK,CAAC,KAAK,GAAG,OAAO,IAAI;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,UAAU,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAA;IACxC,QAAQ,EAAE,OAAO,CAAA;CAClB,CAAC;AAeJ,QAAA,MAAM,KAAK,oDAAwC,CAAC;AACpD,KAAK,KAAK,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AACtC,eAAe,KAAK,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type Props = {
|
|
3
|
+
redirectTo?: string;
|
|
4
|
+
fallback?: Snippet;
|
|
5
|
+
children: Snippet;
|
|
6
|
+
};
|
|
7
|
+
declare const Index: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type Index = ReturnType<typeof Index>;
|
|
9
|
+
export default Index;
|
|
10
|
+
//# sourceMappingURL=index.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.svelte.d.ts","sourceRoot":"","sources":["../../../../../src/client/svelte/Protected/index.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AAOnC,KAAK,KAAK,GAAG;IACX,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,EAAE,OAAO,CAAA;CAClB,CAAC;AA+BJ,QAAA,MAAM,KAAK,2CAAwC,CAAC;AACpD,KAAK,KAAK,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AACtC,eAAe,KAAK,CAAC"}
|
|
@@ -15,7 +15,7 @@ interface AuthContextValue<TAuth = unknown> {
|
|
|
15
15
|
refresh: () => Promise<void>;
|
|
16
16
|
fetch: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
17
17
|
}
|
|
18
|
-
export declare function createSvelteAuth<const TAuth = unknown>({ baseUrl, scheme, redirectTo: defaultRedirectTo, session: initialSession }?: {
|
|
18
|
+
export declare function createSvelteAuth<const TAuth = unknown>({ baseUrl, scheme, redirectTo: defaultRedirectTo, session: initialSession, }?: {
|
|
19
19
|
baseUrl?: string;
|
|
20
20
|
scheme?: string;
|
|
21
21
|
redirectTo?: string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.svelte.d.ts","sourceRoot":"","sources":["../../../../src/client/svelte/index.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAStE,UAAU,gBAAgB,CAAC,KAAK,GAAG,OAAO;IACxC,OAAO,EAAE,UAAU,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAA;IACvC,SAAS,EAAE,OAAO,CAAA;IAClB,MAAM,EAAE,CAAC,CAAC,SAAS,WAAW,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,WAAW,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACxI,WAAW,EAAE,CAAC,CAAC,SAAS,WAAW,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,WAAW,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7I,aAAa,EAAE,CAAC,QAAQ,EAAE,WAAW,CAAC,KAAK,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9D,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5B,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5B,KAAK,EAAE,CAAC,KAAK,EAAE,WAAW,GAAG,GAAG,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CAC3E;AAID,wBAAgB,gBAAgB,CAAC,KAAK,CAAC,KAAK,GAAG,OAAO,EAAE,EACtD,OAAqB,EACrB,MAAc,EACd,UAAU,EAAE,iBAAiB,EAC7B,OAAO,EAAE,cAAc,GACxB,GAAE;IACD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,UAAU,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAA;CACpC,QA6HL;AAED,wBAAgB,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,OAAO,KAAK,gBAAgB,CAAC,KAAK,CAAC,CAMxE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/core/templates.ts","../../../../src/client/token.ts","../../../../src/runtimes/tauri/index.ts","../../../../src/client/svelte/index.svelte.ts","../../../../src/core/cookies.ts","../../../../src/core/createAuth.ts","../../../../src/jwt/jwt.ts","../../../../src/core/errors.ts","../../../../src/core/handlers/callback.ts","../../../../src/oauth/utils.ts","../../../../src/core/index.ts","../../../../src/client/vanilla/index.ts"],"sourcesContent":["const baseStyles = `\n body {\n font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n background-color: #09090b;\n color: #fafafa;\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100vh;\n margin: 0;\n text-align: center;\n }\n .card {\n background-color: #18181b;\n border: 1px solid #27272a;\n border-radius: 0.75rem;\n padding: 2rem;\n max-width: 320px;\n }\n h1 {\n font-size: 1.25rem;\n font-weight: 600;\n margin: 0 0 0.5rem;\n }\n p {\n margin: 0;\n color: #a1a1aa;\n }\n .error-code {\n font-family: ui-monospace, monospace;\n font-size: 0.75rem;\n color: #71717a;\n margin-top: 0.5rem;\n }\n a {\n display: inline-block;\n margin-top: 1rem;\n color: #3b82f6;\n text-decoration: none;\n }\n a:hover {\n text-decoration: underline;\n }\n`\n\nexport interface ErrorPageOptions {\n /** Error title (default: \"Authentication Error\") */\n title?: string\n /** Error message to display */\n message: string\n /** Error code to display */\n code?: string\n /** URL to redirect to (shown as \"Go back\" link, also used for auto-redirect) */\n redirectUrl?: string\n /** Auto-redirect after showing error (default: true if redirectUrl provided) */\n autoRedirect?: boolean\n /** Delay before auto-redirect in ms (default: 3000) */\n redirectDelay?: number\n /** Attempt to close the window after redirect (for OAuth popups) */\n autoClose?: boolean\n}\n\nexport function renderErrorPage(options: ErrorPageOptions): string {\n const {\n title = 'Authentication Error',\n message,\n code,\n redirectUrl,\n autoRedirect = !!redirectUrl,\n redirectDelay = 3000,\n autoClose = true,\n } = options\n\n const closeScript = redirectUrl ? renderAutoCloseScript('url', autoClose) : ''\n\n const redirectScript = redirectUrl && autoRedirect\n ? `\n window.onload = function() {\n setTimeout(function() {\n const url = ${JSON.stringify(redirectUrl)};\n ${closeScript}\n window.location.href = url;\n }, ${redirectDelay});\n };\n `\n : ''\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <title>${escapeHtml(title)}</title>\n <style>${baseStyles}</style>\n ${redirectScript ? `<script>${redirectScript}</script>` : ''}\n</head>\n<body>\n <div class=\"card\">\n <h1>${escapeHtml(title)}</h1>\n <p>${escapeHtml(message)}</p>\n ${code ? `<p class=\"error-code\">${escapeHtml(code)}</p>` : ''}\n ${redirectUrl ? `<a href=\"${escapeHtml(redirectUrl)}\">Go back</a>` : ''}\n </div>\n</body>\n</html>`\n}\n\nexport interface SuccessPageOptions {\n /** Success title (default: \"Authentication Successful\") */\n title?: string\n /** Success message to display */\n message?: string\n /** URL to redirect to */\n redirectUrl: string\n /** Attempt to close the window after redirect (for OAuth popups) */\n autoClose?: boolean\n}\n\nexport function renderSuccessPage(options: SuccessPageOptions): string {\n const {\n title = 'Authentication Successful',\n message = 'You can now close this window.',\n redirectUrl,\n autoClose = true,\n } = options\n\n const closeScript = renderAutoCloseScript('url', autoClose)\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <title>${escapeHtml(title)}</title>\n <style>${baseStyles}</style>\n <script>\n window.onload = function() {\n const url = ${JSON.stringify(redirectUrl)};\n ${closeScript}\n window.location.href = url;\n };\n </script>\n</head>\n<body>\n <div class=\"card\">\n <h1>${escapeHtml(title)}</h1>\n <p>${escapeHtml(message)}</p>\n </div>\n</body>\n</html>`\n}\n\nexport interface CancelledPageOptions {\n /** Title (default: \"Authentication Cancelled\") */\n title?: string\n /** Message to display */\n message?: string\n /** URL to redirect to */\n redirectUrl?: string\n /** Attempt to close the window after redirect (for OAuth popups) */\n autoClose?: boolean\n}\n\nexport function renderCancelledPage(options: CancelledPageOptions = {}): string {\n const {\n title = 'Authentication Cancelled',\n message = 'Redirecting you back to the app...',\n redirectUrl = '/',\n autoClose = true,\n } = options\n\n const closeScript = renderAutoCloseScript('url', autoClose)\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <title>${escapeHtml(title)}</title>\n <style>${baseStyles}</style>\n <script>\n window.onload = function() {\n const url = ${JSON.stringify(redirectUrl)};\n ${closeScript}\n window.location.href = url;\n };\n </script>\n</head>\n<body>\n <div class=\"card\">\n <h1>${escapeHtml(title)}</h1>\n <p>${escapeHtml(message)}</p>\n </div>\n</body>\n</html>`\n}\n\nexport function htmlResponse(html: string, status = 200): Response {\n return new Response(html, {\n status,\n headers: { 'Content-Type': 'text/html; charset=utf-8' },\n })\n}\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n}\n\nfunction renderAutoCloseScript(redirectTargetExpression: string, autoClose: boolean): string {\n if (!autoClose)\n return ''\n\n return `\n (function() {\n let closeScheduled = false;\n const closeWindow = function() {\n window.close();\n };\n const scheduleClose = function(delay) {\n if (closeScheduled)\n return;\n closeScheduled = true;\n setTimeout(closeWindow, delay);\n };\n\n let protocol = '';\n try {\n protocol = new URL(${redirectTargetExpression}, window.location.href).protocol;\n }\n catch {}\n\n const isWebProtocol = protocol === 'http:' || protocol === 'https:';\n if (isWebProtocol) {\n scheduleClose(500);\n return;\n }\n\n const closeAfterHandoff = function() {\n if (document.visibilityState === 'hidden')\n scheduleClose(200);\n };\n const closeAfterBlurFallback = function() {\n setTimeout(function() {\n if (closeScheduled)\n return;\n if (typeof document.hasFocus === 'function' && !document.hasFocus())\n scheduleClose(200);\n }, 700);\n };\n const closeAfterInitialDelay = function() {\n setTimeout(function() {\n if (closeScheduled)\n return;\n if (typeof document.hasFocus === 'function' && !document.hasFocus())\n scheduleClose(0);\n }, 500);\n };\n\n document.addEventListener('visibilitychange', closeAfterHandoff);\n window.addEventListener('pagehide', function() {\n scheduleClose(0);\n }, { once: true });\n window.addEventListener('blur', closeAfterBlurFallback, { once: true });\n closeAfterInitialDelay();\n })();\n `\n}\n","import { BROWSER } from 'esm-env'\n\nexport const SESSION_TOKEN_KEY = '__gau-session-token'\n\nexport const REFRESHED_TOKEN_HEADER = 'X-Refreshed-Token'\n\nexport function storeSessionToken(token: string) {\n if (!BROWSER)\n return\n try {\n localStorage.setItem(SESSION_TOKEN_KEY, token)\n }\n catch {}\n}\n\nexport function getSessionToken(): string | null {\n if (!BROWSER)\n return null\n try {\n return localStorage.getItem(SESSION_TOKEN_KEY)\n }\n catch {\n return null\n }\n}\n\nexport function clearSessionToken() {\n if (!BROWSER)\n return\n try {\n localStorage.removeItem(SESSION_TOKEN_KEY)\n }\n catch {}\n}\n\nexport function handleRefreshedToken(response: Response): void {\n if (!BROWSER)\n return\n const refreshed = response.headers.get(REFRESHED_TOKEN_HEADER)\n if (refreshed)\n storeSessionToken(refreshed)\n}\n\nexport async function generatePKCE() {\n if (!BROWSER || !window.crypto || !window.crypto.subtle)\n throw new Error('PKCE relies on window.crypto, which is not available in this environment.')\n\n function base64UrlEncode(array: Uint8Array): string {\n return btoa(String.fromCharCode(...array))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '')\n }\n\n const verifierLength = 43\n const randomValues = new Uint8Array(verifierLength)\n window.crypto.getRandomValues(randomValues)\n const codeVerifier = base64UrlEncode(randomValues)\n\n const encoder = new TextEncoder()\n const data = encoder.encode(codeVerifier)\n const hash = await window.crypto.subtle.digest('SHA-256', data)\n const codeChallenge = base64UrlEncode(new Uint8Array(hash))\n\n return { codeVerifier, codeChallenge }\n}\n","import type { ProfileName, ProviderIds } from '../../core'\nimport { BROWSER } from 'esm-env'\nimport { generatePKCE, getSessionToken } from '../../client/token'\n\nexport function isTauri(): boolean {\n return BROWSER && '__TAURI_INTERNALS__' in globalThis\n}\n\nfunction resolveOrigin(baseUrl: string): string | null {\n try {\n return new URL(baseUrl).origin\n }\n catch {\n if (BROWSER && typeof window !== 'undefined') {\n try {\n return new URL(baseUrl, window.location.origin).origin\n }\n catch {\n return null\n }\n }\n return null\n }\n}\n\nexport async function signInWithTauri<const TAuth = unknown, P extends ProviderIds<TAuth> = ProviderIds<TAuth>, PR extends (ProfileName<TAuth, P> | string) | undefined = undefined>(\n provider: P,\n baseUrl: string,\n scheme: string = 'gau',\n redirectOverride?: string,\n profile?: PR,\n) {\n if (!isTauri())\n return\n\n const { openUrl } = await import('@tauri-apps/plugin-opener')\n\n function resolveAbsoluteBase(base: string): string {\n try {\n const u = new URL(base)\n return u.toString().replace(/\\/$/, '')\n }\n catch {\n if (BROWSER && typeof window !== 'undefined') {\n try {\n const u = new URL(base, window.location.origin)\n return u.toString().replace(/\\/$/, '')\n }\n catch {\n return base\n }\n }\n return base\n }\n }\n\n let redirectTo: string\n\n if (redirectOverride)\n redirectTo = redirectOverride\n else\n redirectTo = `${scheme}://oauth/callback`\n\n const { codeVerifier, codeChallenge } = await generatePKCE()\n localStorage.setItem('gau-pkce-verifier', codeVerifier)\n\n const params = new URLSearchParams()\n params.set('redirectTo', redirectTo)\n if (profile)\n params.set('profile', String(profile))\n params.set('code_challenge', codeChallenge)\n const resolvedBase = resolveAbsoluteBase(baseUrl)\n const authUrl = `${resolvedBase}/${provider}?${params.toString()}`\n await openUrl(authUrl)\n}\n\nexport async function setupTauriListener(\n handler: (url: string) => Promise<void>,\n): Promise<(() => void) | void> {\n if (!isTauri())\n return\n\n const { listen } = await import('@tauri-apps/api/event')\n try {\n const unlisten = await listen<string>('deep-link', async (event) => {\n await handler(event.payload)\n })\n return unlisten\n }\n catch (err) {\n console.error(err)\n }\n}\n\nexport async function handleTauriDeepLink(url: string, baseUrl: string, scheme: string, onToken: (token: string) => void) {\n const parsed = new URL(url)\n const baseOrigin = resolveOrigin(baseUrl)\n if (parsed.protocol !== `${scheme}:` && (!baseOrigin || parsed.origin !== baseOrigin))\n return\n\n const queryParams = new URLSearchParams(parsed.search)\n const code = queryParams.get('code')\n if (code) {\n const verifier = localStorage.getItem('gau-pkce-verifier')\n if (!verifier) {\n console.error('No PKCE verifier found')\n return\n }\n localStorage.removeItem('gau-pkce-verifier')\n\n try {\n const res = await fetch(`${baseUrl}/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ code, codeVerifier: verifier }),\n })\n if (res.ok) {\n const data = await res.json()\n if (data.token)\n onToken(data.token)\n }\n else {\n console.error('Failed to exchange code for token')\n }\n }\n catch (e) {\n console.error('Error exchanging code for token:', e)\n }\n }\n}\n\nexport async function linkAccountWithTauri<const TAuth = unknown, P extends ProviderIds<TAuth> = ProviderIds<TAuth>, PR extends (ProfileName<TAuth, P> | string) | undefined = undefined>(\n provider: P,\n baseUrl: string,\n scheme: string = 'gau',\n redirectOverride?: string,\n profile?: PR,\n) {\n if (!isTauri())\n return\n\n const { openUrl } = await import('@tauri-apps/plugin-opener')\n\n let redirectTo: string\n\n if (redirectOverride)\n redirectTo = redirectOverride\n else\n redirectTo = `${scheme}://oauth/callback`\n\n const token = getSessionToken()\n if (!token) {\n console.error('No session token found, cannot link account.')\n return\n }\n\n const params = new URLSearchParams()\n params.set('redirectTo', redirectTo)\n params.set('token', token)\n if (profile)\n params.set('profile', String(profile))\n const resolvedBase = (() => {\n try {\n const u = new URL(baseUrl)\n return u.toString().replace(/\\/$/, '')\n }\n catch {\n if (BROWSER && typeof window !== 'undefined') {\n try {\n const u = new URL(baseUrl, window.location.origin)\n return u.toString().replace(/\\/$/, '')\n }\n catch {\n return baseUrl\n }\n }\n return baseUrl\n }\n })()\n const linkUrl = `${resolvedBase}/link/${provider}?${params.toString()}`\n await openUrl(linkUrl)\n}\n\nexport async function startAuthBridge(\n baseUrl: string,\n scheme: string,\n onToken: (token: string) => Promise<void> | void,\n): Promise<(() => void) | void> {\n if (!isTauri())\n return\n\n const unlisten = await setupTauriListener(async (url) => {\n handleTauriDeepLink(url, baseUrl, scheme, onToken)\n })\n return unlisten\n}\n","import type { GauSession, ProfileName, ProviderIds } from '../../core'\n// @ts-expect-error svelte-kit\nimport { replaceState } from '$app/navigation'\nimport { BROWSER } from 'esm-env'\nimport { getContext, onMount, setContext } from 'svelte'\nimport { NULL_SESSION } from '../../core'\nimport { isTauri } from '../../runtimes/tauri'\nimport { createAuthClient } from '../vanilla'\n\ninterface AuthContextValue<TAuth = unknown> {\n session: GauSession<ProviderIds<TAuth>>\n isLoading: boolean\n signIn: <P extends ProviderIds<TAuth>>(provider: P, options?: { redirectTo?: string, profile?: ProfileName<TAuth, P> }) => Promise<void>\n linkAccount: <P extends ProviderIds<TAuth>>(provider: P, options?: { redirectTo?: string, profile?: ProfileName<TAuth, P> }) => Promise<void>\n unlinkAccount: (provider: ProviderIds<TAuth>) => Promise<void>\n signOut: () => Promise<void>\n refresh: () => Promise<void>\n fetch: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>\n}\n\nconst AUTH_CONTEXT_KEY = Symbol('gau-auth')\n\nexport function createSvelteAuth<const TAuth = unknown>({\n baseUrl = '/api/auth',\n scheme = 'gau',\n redirectTo: defaultRedirectTo,\n session: initialSession,\n}: {\n baseUrl?: string\n scheme?: string\n redirectTo?: string\n session?: GauSession<ProviderIds<TAuth>>\n} = {}) {\n type CurrentSession = GauSession<ProviderIds<TAuth>>\n\n const client = createAuthClient<TAuth>({\n baseUrl,\n })\n\n const fetchSession = async (): Promise<CurrentSession> => {\n if (!BROWSER)\n return { ...NULL_SESSION, providers: [] }\n return client.refreshSession()\n }\n\n let session: CurrentSession = $state(initialSession ?? { ...NULL_SESSION, providers: [] })\n let isLoading = $state(!initialSession)\n\n async function replaceUrlSafe(url: string) {\n try {\n replaceState(url, {})\n }\n catch {\n if (BROWSER)\n window.history.replaceState(null, '', url)\n }\n }\n\n async function signIn<P extends ProviderIds<TAuth>>(provider: P, { redirectTo, profile }: { redirectTo?: string, profile?: ProfileName<TAuth, P> } = {}) {\n const inTauri = isTauri()\n let finalRedirectTo = redirectTo ?? defaultRedirectTo\n\n if (inTauri) {\n const { signInWithTauri } = await import('../../runtimes/tauri')\n await signInWithTauri<TAuth, P, typeof profile>(provider, baseUrl, scheme, finalRedirectTo, profile)\n return\n }\n\n if (!finalRedirectTo && BROWSER)\n finalRedirectTo = window.location.origin\n\n const url = await client.signIn<P, typeof profile>(provider, { redirectTo: finalRedirectTo, profile })\n if (BROWSER)\n window.location.href = url\n }\n\n async function linkAccount<P extends ProviderIds<TAuth>>(provider: P, { redirectTo, profile }: { redirectTo?: string, profile?: ProfileName<TAuth, P> } = {}) {\n if (isTauri()) {\n const { linkAccountWithTauri } = await import('../../runtimes/tauri')\n await linkAccountWithTauri<TAuth, P, typeof profile>(provider, baseUrl, scheme, redirectTo, profile)\n return\n }\n\n let finalRedirectTo = redirectTo ?? defaultRedirectTo\n if (!finalRedirectTo && BROWSER)\n finalRedirectTo = window.location.href\n\n const url = await client.linkAccount<P, typeof profile>(provider, { redirectTo: finalRedirectTo, profile })\n if (BROWSER)\n window.location.href = url\n }\n\n async function unlinkAccount(provider: ProviderIds<TAuth>) {\n const ok = await client.unlinkAccount(provider)\n if (ok)\n session = await fetchSession()\n else\n console.error('Failed to unlink account')\n }\n\n async function signOut() {\n await client.signOut()\n session = await fetchSession()\n }\n\n onMount(() => {\n if (!BROWSER)\n return\n\n void (async () => {\n const handled = await client.handleRedirectCallback(async url => replaceUrlSafe(url))\n if (!handled)\n session = await fetchSession()\n\n isLoading = false\n })()\n\n let cleanup: (() => void) | void\n let disposed = false\n\n if (!isTauri())\n return\n\n void (async () => {\n const { startAuthBridge } = await import('../../runtimes/tauri')\n const unlisten = await startAuthBridge(baseUrl, scheme, async (token) => {\n await client.applySessionToken(token)\n session = await fetchSession()\n })\n if (disposed)\n unlisten?.()\n else\n cleanup = unlisten\n })()\n\n return () => {\n disposed = true\n cleanup?.()\n }\n })\n\n const contextValue: AuthContextValue<TAuth> = {\n get session() {\n return session\n },\n get isLoading() {\n return isLoading\n },\n signIn,\n linkAccount,\n unlinkAccount,\n signOut,\n refresh: async () => { session = await fetchSession() },\n fetch: client.fetch,\n }\n\n setContext(AUTH_CONTEXT_KEY, contextValue)\n}\n\nexport function useAuth<const TAuth = unknown>(): AuthContextValue<TAuth> {\n const context = getContext<AuthContextValue<TAuth>>(AUTH_CONTEXT_KEY)\n if (!context)\n throw new Error('useAuth must be used within an AuthProvider')\n\n return context\n}\n","import type { SerializeOptions } from 'cookie'\nimport { parse, serialize } from 'cookie'\n\nexport const DEFAULT_COOKIE_SERIALIZE_OPTIONS: SerializeOptions = {\n path: '/',\n sameSite: 'lax',\n secure: true,\n httpOnly: true,\n}\n\nexport type Cookie = [string, string, SerializeOptions]\n\nexport function parseCookies(cookieHeader: string | null | undefined): Map<string, string> {\n const cookies = new Map<string, string>()\n if (cookieHeader) {\n const parsed = parse(cookieHeader)\n for (const name in parsed)\n cookies.set(name, parsed[name]!)\n }\n return cookies\n}\n\nexport class Cookies {\n #new: Cookie[] = []\n\n constructor(\n private readonly requestCookies: Map<string, string>,\n private readonly defaultOptions: SerializeOptions,\n ) {}\n\n get(name: string): string | undefined {\n return this.requestCookies.get(name)\n }\n\n set(name: string, value: string, options?: SerializeOptions): void {\n const combinedOptions = { ...this.defaultOptions, ...options }\n this.#new.push([name, value, combinedOptions])\n }\n\n delete(name: string, options?: Omit<SerializeOptions, 'expires' | 'maxAge'>): void {\n this.set(name, '', { ...options, expires: new Date(0), maxAge: 0 })\n }\n\n toHeaders(): Headers {\n const headers = new Headers()\n for (const [name, value, options] of this.#new)\n headers.append('Set-Cookie', serialize(name, value, options))\n\n return headers\n }\n}\n\nexport const CSRF_COOKIE_NAME = '__gau-csrf-token'\nexport const SESSION_COOKIE_NAME = '__gau-session-token'\nexport const SESSION_STASH_COOKIE_NAME = '__gau-session-stash'\nexport const SESSION_STRATEGY_COOKIE_NAME = '__gau-session-strategy'\nexport const LINKING_TOKEN_COOKIE_NAME = '__gau-linking-token'\nexport const PKCE_COOKIE_NAME = '__gau-pkce-code-verifier'\nexport const CALLBACK_URI_COOKIE_NAME = '__gau-callback-uri'\nexport const PROVIDER_OPTIONS_COOKIE_NAME = '__gau-provider-options'\nexport const CLIENT_CHALLENGE_COOKIE_NAME = '__gau-client-challenge'\n\nexport const CSRF_MAX_AGE = 60 * 10 // 10 minutes\n","import type { OAuth2Tokens } from 'arctic'\nimport type { SerializeOptions } from 'cookie'\nimport type { SignOptions, VerifyOptions } from '../jwt'\nimport type { AuthUser, OAuthProvider, OAuthProviderConfig, ProviderProfileOverrides } from '../oauth'\nimport type { Cookies } from './cookies'\nimport type { Adapter, GauServerSession } from './index'\nimport { parse, serialize } from 'cookie'\nimport { sign, verify } from '../jwt'\nimport { DEFAULT_COOKIE_SERIALIZE_OPTIONS, SESSION_COOKIE_NAME, SESSION_STASH_COOKIE_NAME } from './cookies'\nimport { AuthError, ErrorCodes, GauError } from './index'\nimport { getSessionTokenFromRequest } from './utils'\n\ntype ProviderId<P> = P extends OAuthProvider<infer T> ? T : never\nexport type ProviderIds<T> = T extends { providerMap: Map<infer K extends string, any> } ? K : string\n\nexport type ProfileName<T, P extends string> = T extends { profiles: infer R }\n ? P extends keyof R\n ? keyof R[P]\n : never\n : never\n\nexport interface ImpersonationConfig {\n enabled: boolean\n /** Roles that can impersonate others. Defaults to adminRoles from roles config. */\n allowedRoles?: string[]\n /** Roles that cannot be impersonated. Defaults to adminRoles from roles config. */\n cannotImpersonate?: string[]\n /** Maximum impersonation duration in seconds. Defaults to 3600 (1 hour). */\n maxTTL?: number\n /**\n * Hook called when impersonation starts.\n * Use this to log impersonation events.\n */\n onImpersonate?: (context: {\n adminUserId: string\n targetUserId: string\n reason?: string\n timestamp: number\n }) => void | Promise<void>\n}\n\ntype ResolvedImpersonationConfig = Required<Pick<ImpersonationConfig, 'enabled' | 'allowedRoles' | 'cannotImpersonate' | 'maxTTL'>> & Pick<ImpersonationConfig, 'onImpersonate'>\n\nexport interface StartImpersonationOptions {\n /** Session duration in seconds (capped by maxTTL). */\n ttl?: number\n /** Reason for impersonation, passed to onImpersonate hook. */\n reason?: string\n}\n\nexport interface ImpersonationResult {\n /** The impersonation session JWT. */\n token: string\n /** Set-Cookie header for the impersonation session. */\n cookie: string\n /** Set-Cookie header for stashing the admin's original session. */\n originalCookie: string\n /** The maxAge in seconds. */\n maxAge: number\n}\n\nexport interface EndImpersonationResult {\n /** The restored admin session token. */\n token: string\n /** Set-Cookie header for restoring the admin session. */\n cookie: string\n /** Array of Set-Cookie headers to clear the stash cookie. */\n clearCookies: string[]\n}\n\nexport interface CreateAuthOptions<TProviders extends OAuthProvider[]> {\n /** The database adapter to use for storing users and accounts. */\n adapter: Adapter\n /** Array of OAuth providers to support. */\n providers: TProviders\n /** Base path for authentication routes (defaults to '/api/auth'). */\n basePath?: string\n /** Session management options */\n session?: {\n /** Strategy to use for sessions: 'auto' (default), 'cookie', or 'token'. */\n strategy?: 'auto' | 'cookie' | 'token'\n }\n /** Configuration for JWT signing and verification. */\n jwt?: {\n /** Signing algorithm: 'ES256' (default) or 'HS256'. */\n algorithm?: 'ES256' | 'HS256'\n /** Secret for HS256 or base64url-encoded private key for ES256 (overrides AUTH_SECRET). */\n secret?: string\n /** Issuer claim (iss) for JWTs. */\n iss?: string\n /** Audience claim (aud) for JWTs. */\n aud?: string\n /** Default time-to-live in seconds for JWTs (defaults to 1 day). */\n ttl?: number\n }\n /** Custom options for session cookies. */\n cookies?: Partial<SerializeOptions>\n /**\n * Hook that fires right after provider.validateCallback() returns tokens,\n * but before any user lookup/link/create logic. Return { handled: true, response }\n * to short-circuit the default flow and send a custom response.\n */\n onOAuthExchange?: (context: {\n request: Request\n providerId: string\n state: string\n code: string\n codeVerifier: string\n callbackUri?: string | null\n redirectTo: string\n cookies: Cookies\n providerUser: AuthUser\n tokens: OAuth2Tokens\n isLinking: boolean\n sessionUserId?: string\n }) => Promise<{ handled: true, response: Response } | { handled: false }>\n /** Map/override the provider's profile right after token exchange. */\n mapExternalProfile?: (context: {\n request: Request\n providerId: string\n providerUser: AuthUser\n tokens: OAuth2Tokens\n isLinking: boolean\n }) => Promise<AuthUser | Partial<AuthUser> | null | undefined>\n /** Gate the link action just before persisting an account. */\n onBeforeLinkAccount?: (context: {\n request: Request\n providerId: string\n userId: string\n providerUser: AuthUser\n tokens: OAuth2Tokens\n }) => Promise<{ allow: true } | { allow: false, response?: Response }>\n /** Observe or augment after link/update tokens. */\n onAfterLinkAccount?: (context: {\n request: Request\n providerId: string\n userId: string\n providerUser: AuthUser\n tokens: OAuth2Tokens\n action: 'link' | 'update'\n }) => Promise<void>\n /** Trusted hosts for CSRF protection: 'all' or array of hostnames (defaults to []). */\n trustHosts?: 'all' | string[]\n /** Account linking behavior: 'verifiedEmail' (default), 'always', or false. */\n autoLink?: 'verifiedEmail' | 'always' | false\n /** Allow linking providers whose primary emails differ from the user's current primary email. Defaults to true. */\n allowDifferentEmails?: boolean\n /** When linking a new provider, update missing user info (name/image/emailVerified) from provider profile. Defaults to false. */\n updateUserInfoOnLink?: boolean\n /** Optional configuration for role-based access control. */\n roles?: {\n /** Default role for newly created users. */\n defaultRole?: string\n /** Dynamically resolve the role at the moment of user creation. Return undefined to fall back to defaultRole. */\n resolveOnCreate?: (context: { providerId: string, profile: any, request: Request }) => string | undefined\n /** Roles that are considered admin-like for helper predicates and `session.user.isAdmin`. */\n adminRoles?: string[]\n /** Users that are always treated as admin for helper predicates and `session.user.isAdmin`. */\n adminUserIds?: string[]\n }\n /**\n * CORS configuration. When true (default): request Origin & allow credentials\n * When false, CORS headers are not added at all.\n * Provide an object to fine-tune behaviour.\n */\n cors?: true | false | {\n /**\n * Allowed origins.\n * - 'all' (default) allows any origin (reflected when credentials enabled),\n * - 'trust' reuses the createAuth trustHosts list\n * - specify an explicit array of full origins (e.g. https://app.example.com)\n * or hostnames (e.g. app.example.com).\n * When array contains '*', it's treated as 'all'.\n */\n allowedOrigins?: 'all' | 'trust' | string[]\n /** Whether to send Access-Control-Allow-Credentials (defaults to true). */\n allowCredentials?: boolean\n /** Allowed headers (defaults to ['Content-Type','Authorization','Cookie']). */\n allowedHeaders?: string[]\n /** Allowed methods (defaults to ['GET','POST','OPTIONS']). */\n allowedMethods?: string[]\n /** Exposed headers (optional). */\n exposeHeaders?: string[]\n /** Preflight max age in seconds (optional). */\n maxAge?: number\n }\n /**\n * Named, server-defined profiles that group provider specific settings.\n * Clients can reference a profile by name (e.g. signIn('github', { profile: 'myprofile' })).\n */\n profiles?: ProfilesConfig<TProviders>\n /**\n * Custom error handler. Return a Response to override default behavior.\n * Return undefined to use default handling (errorRedirect or HTML page).\n *\n * @example\n * onError: ({ error, request }) => {\n * console.error('Auth error:', error.code, error.message)\n * if (error.code === 'CSRF_INVALID') {\n * return redirect('/login?error=session_expired')\n * }\n * }\n */\n onError?: (context: {\n error: GauError\n request: Request\n }) => Response | Promise<Response | undefined> | undefined\n /**\n * URL to redirect user-facing errors to.\n * Error details passed as query params: ?code=...&message=...&status=...&redirect=...\n *\n * If not set, gau renders its built-in error page for user-facing errors,\n * or returns JSON for API errors.\n *\n * @example\n * errorRedirect: '/auth/error' // Your custom error page\n */\n errorRedirect?: string\n /**\n * User impersonation.\n * When enabled, admins can impersonate other users for support/debugging.\n */\n impersonation?: ImpersonationConfig\n}\n\nexport interface IssueSessionOptions {\n /** Custom claims to include in the session JWT. */\n data?: Record<string, unknown>\n /** Time-to-live in seconds (defaults to auth's configured jwt.ttl). */\n ttl?: number\n}\n\nexport interface IssueSessionResult {\n /** The raw JWT session token (for Bearer auth or storage). */\n token: string\n /** The serialized Set-Cookie header value (for web apps). */\n cookie: string\n /** The cookie name used by gau. */\n cookieName: string\n /** The maxAge in seconds. */\n maxAge: number\n}\n\nexport interface RefreshSessionOptions {\n /** Override the default TTL for the new token. */\n ttl?: number\n /**\n * Only refresh if past this fraction of TTL (0-1).\n * Example: 0.5 means only refresh if session is past 50% of its lifetime.\n */\n threshold?: number\n}\n\nexport interface RefreshSessionResult extends IssueSessionResult {\n /**\n * How the original token was provided.\n * - 'cookie': Token was extracted from the Cookie header\n * - 'bearer': Token was extracted from Authorization: Bearer header\n * - 'token': A raw token string was passed directly\n */\n source: 'cookie' | 'bearer' | 'token'\n}\n\nexport type Auth<TProviders extends OAuthProvider[] = any> = Adapter & {\n providerMap: Map<ProviderId<TProviders[number]>, TProviders[number]>\n basePath: string\n cookieOptions: SerializeOptions\n jwt: { ttl: number }\n onOAuthExchange?: CreateAuthOptions<TProviders>['onOAuthExchange']\n mapExternalProfile?: CreateAuthOptions<TProviders>['mapExternalProfile']\n onBeforeLinkAccount?: CreateAuthOptions<TProviders>['onBeforeLinkAccount']\n onAfterLinkAccount?: CreateAuthOptions<TProviders>['onAfterLinkAccount']\n signJWT: <U extends Record<string, unknown>>(payload: U, customOptions?: Partial<SignOptions>) => Promise<string>\n verifyJWT: <U = Record<string, unknown>>(token: string, customOptions?: Partial<VerifyOptions>) => Promise<U | null>\n createSession: (userId: string, data?: Record<string, unknown>, ttl?: number) => Promise<string>\n validateSession: (token: string) => Promise<GauServerSession | null>\n /**\n * Issue a session for a user, returning both the token and a Set-Cookie header.\n * Useful for guest login, invite redemption, admin impersonation, etc.\n */\n issueSession: (userId: string, options?: IssueSessionOptions) => Promise<IssueSessionResult>\n /**\n * Refresh an existing session, issuing a new token with extended TTL.\n * Preserves custom claims from the original token.\n *\n * @param tokenOrRequest - The existing session token, or a Request to extract the token from\n * @param options.ttl - Override the default TTL for the new token\n * @param options.threshold - Only refresh if past this fraction of TTL (0-1).\n * When set, returns null if below threshold.\n * Example: 0.5 means only refresh if session is past 50% of its lifetime.\n * @returns The refreshed session with source info, or null if invalid/expired/below threshold\n */\n refreshSession: (tokenOrRequest: string | Request, options?: RefreshSessionOptions) => Promise<RefreshSessionResult | null>\n /**\n * Get a valid access token for a linked provider. If the stored token is expired and a refresh token exists,\n * this will refresh it using the provider's refreshAccessToken and persist rotated tokens.\n */\n getAccessToken: (userId: string, providerId: string) => Promise<{ accessToken: string, expiresAt?: number | null } | null>\n /**\n * Start impersonating a target user.\n * Requires impersonation to be enabled and the admin user to have appropriate permissions.\n *\n * @param adminUserId - The ID of the user initiating impersonation (must have allowed role)\n * @param targetUserId - The ID of the user to impersonate\n * @param options - Optional configuration for the impersonation session\n * @returns ImpersonationResult with tokens and cookies, or null if impersonation is not allowed\n */\n startImpersonation: (adminUserId: string, targetUserId: string, options?: StartImpersonationOptions) => Promise<ImpersonationResult | null>\n /**\n * End an active impersonation session and restore the admin's original session.\n * Extracts the stashed session from the request cookies.\n *\n * @param request - The request containing the stashed session cookie\n * @returns EndImpersonationResult with restored session, or null if no stash found\n */\n endImpersonation: (request: Request) => Promise<EndImpersonationResult | null>\n trustHosts: 'all' | string[]\n autoLink: 'verifiedEmail' | 'always' | false\n allowDifferentEmails: boolean\n updateUserInfoOnLink: boolean\n sessionStrategy: 'auto' | 'cookie' | 'token'\n development: boolean\n roles: {\n defaultRole: string\n resolveOnCreate?: (context: { providerId: string, profile: any, request: Request }) => string | undefined\n adminRoles: string[]\n adminUserIds: string[]\n }\n cors: false | {\n allowedOrigins: 'all' | 'trust' | string[]\n allowCredentials: boolean\n allowedHeaders: string[]\n allowedMethods: string[]\n exposeHeaders?: string[]\n maxAge?: number\n }\n profiles: ResolvedProfiles<TProviders>\n onError?: CreateAuthOptions<TProviders>['onError']\n errorRedirect?: string\n impersonation: ImpersonationConfig | null\n}\n\nexport interface ProfileDefinition {\n scopes?: string[]\n redirectUri?: string\n /** When true, this profile can only be linked to an existing session; standalone sign-in is disabled. */\n linkOnly?: boolean\n /** Additional provider-specific authorization params. */\n params?: Record<string, string>\n}\n\ntype ProviderIdOfArray<TProviders extends OAuthProvider[]> = ProviderId<TProviders[number]>\ntype ProviderConfigFor<TProviders extends OAuthProvider[], K extends string>\n = Extract<TProviders[number], OAuthProvider<K, any>> extends OAuthProvider<any, infer C> ? C : OAuthProviderConfig\n\nexport type ProfilesConfig<TProviders extends OAuthProvider[]> = Partial<{\n [K in ProviderIdOfArray<TProviders>]: Record<string, ProfileDefinition & ProviderProfileOverrides<ProviderConfigFor<TProviders, K>>>\n}>\nexport type ResolvedProfiles<TProviders extends OAuthProvider[]> = ProfilesConfig<TProviders>\n\nexport function createAuth<const TProviders extends OAuthProvider[]>({\n adapter,\n providers,\n basePath = '/api/auth',\n jwt: jwtConfig = {},\n session: sessionConfig = {},\n cookies: cookieConfig = {},\n onOAuthExchange,\n mapExternalProfile,\n onBeforeLinkAccount,\n onAfterLinkAccount,\n trustHosts = [],\n autoLink = 'verifiedEmail',\n allowDifferentEmails = true,\n updateUserInfoOnLink = false,\n roles: rolesConfig = {},\n cors = true,\n profiles: profilesConfig,\n onError,\n errorRedirect,\n impersonation: impersonationConfig,\n}: CreateAuthOptions<TProviders>): Auth<TProviders> {\n const { algorithm = 'ES256', secret, iss, aud, ttl: defaultTTL = 3600 * 24 * 7 } = jwtConfig\n const cookieOptions = { ...DEFAULT_COOKIE_SERIALIZE_OPTIONS, ...cookieConfig }\n\n const sessionStrategy: 'auto' | 'cookie' | 'token' = sessionConfig.strategy ?? 'auto'\n\n if (algorithm === 'ES256' && secret !== undefined && typeof secret !== 'string')\n throw new AuthError('For ES256, the secret option must be a string.')\n\n const providerMap = new Map(providers.map(p => [p.id, p]))\n\n const resolvedCors: Auth['cors'] = cors === false\n ? false\n : {\n allowedOrigins: (cors === true ? 'all' : cors.allowedOrigins) ?? 'all',\n allowCredentials: (cors === true ? true : cors.allowCredentials) ?? true,\n allowedHeaders: (cors === true ? undefined : cors.allowedHeaders) ?? ['Content-Type', 'Authorization', 'Cookie'],\n allowedMethods: (cors === true ? undefined : cors.allowedMethods) ?? ['GET', 'POST', 'OPTIONS'],\n exposeHeaders: cors === true ? undefined : cors.exposeHeaders,\n maxAge: cors === true ? undefined : cors.maxAge,\n }\n\n const resolvedProfiles = (profilesConfig ?? {}) as ResolvedProfiles<TProviders>\n const resolvedRoles = {\n defaultRole: rolesConfig.defaultRole ?? 'user',\n resolveOnCreate: rolesConfig.resolveOnCreate,\n adminRoles: rolesConfig.adminRoles ?? ['admin'],\n adminUserIds: rolesConfig.adminUserIds ?? [],\n }\n\n const resolvedImpersonation: ResolvedImpersonationConfig | null = impersonationConfig?.enabled\n ? {\n enabled: true,\n allowedRoles: impersonationConfig.allowedRoles ?? resolvedRoles.adminRoles,\n cannotImpersonate: impersonationConfig.cannotImpersonate ?? resolvedRoles.adminRoles,\n maxTTL: impersonationConfig.maxTTL ?? 3600,\n onImpersonate: impersonationConfig.onImpersonate,\n }\n : null\n\n function buildSignOptions(custom: Partial<SignOptions> = {}): SignOptions {\n const base = { ttl: custom.ttl, iss: custom.iss ?? iss, aud: custom.aud ?? aud, sub: custom.sub }\n if (algorithm === 'HS256') {\n return { algorithm, secret: custom.secret ?? secret, ...base }\n }\n else {\n if (custom.secret !== undefined && typeof custom.secret !== 'string')\n throw new AuthError('For ES256, the secret option must be a string.')\n const esSecret = custom.secret ?? secret\n return { algorithm, privateKey: custom.privateKey, secret: esSecret, ...base }\n }\n }\n\n function buildVerifyOptions(custom: Partial<VerifyOptions> = {}): VerifyOptions {\n const base = { iss: custom.iss ?? iss, aud: custom.aud ?? aud }\n if (algorithm === 'HS256') {\n return { algorithm, secret: custom.secret ?? secret, ...base }\n }\n else {\n if (custom.secret !== undefined && typeof custom.secret !== 'string')\n throw new AuthError('For ES256, the secret option must be a string.')\n const esSecret = custom.secret ?? secret\n return { algorithm, publicKey: custom.publicKey, secret: esSecret, ...base }\n }\n }\n\n async function signJWT<U extends Record<string, unknown>>(payload: U, customOptions: Partial<SignOptions> = {}): Promise<string> {\n return sign(payload, buildSignOptions(customOptions))\n }\n\n async function verifyJWT<U = Record<string, unknown>>(token: string, customOptions: Partial<VerifyOptions> = {}): Promise<U | null> {\n const options = buildVerifyOptions(customOptions)\n try {\n return await verify<U>(token, options)\n }\n catch {\n return null\n }\n }\n\n async function createSession(userId: string, data: Record<string, unknown> = {}, ttl = defaultTTL): Promise<string> {\n const payload = { sub: userId, ...data }\n return signJWT(payload, { ttl })\n }\n\n async function issueSession(userId: string, options: IssueSessionOptions = {}): Promise<IssueSessionResult> {\n const { data = {}, ttl = defaultTTL } = options\n const token = await createSession(userId, data, ttl)\n\n const cookieOpts: SerializeOptions = {\n ...cookieOptions,\n maxAge: ttl,\n }\n\n const cookie = serialize(SESSION_COOKIE_NAME, token, cookieOpts)\n\n return {\n token,\n cookie,\n cookieName: SESSION_COOKIE_NAME,\n maxAge: ttl,\n }\n }\n\n async function refreshSession(tokenOrRequest: string | Request, options: RefreshSessionOptions = {}): Promise<RefreshSessionResult | null> {\n let token: string | undefined\n let source: RefreshSessionResult['source']\n\n if (typeof tokenOrRequest === 'string') {\n token = tokenOrRequest\n source = 'token'\n }\n else {\n const extracted = getSessionTokenFromRequest(tokenOrRequest)\n if (!extracted.token || !extracted.source)\n return null\n token = extracted.token\n source = extracted.source\n }\n\n const payload = await verifyJWT<{ sub: string, iat?: number } & Record<string, unknown>>(token)\n if (!payload || !payload.sub)\n return null\n\n if (options.threshold != null && options.threshold > 0 && options.threshold < 1) {\n const { iat } = payload\n if (iat) {\n const now = Math.floor(Date.now() / 1000)\n const sessionAge = now - iat\n const ttl = options.ttl ?? defaultTTL\n const thresholdSeconds = ttl * options.threshold\n\n if (sessionAge < thresholdSeconds)\n return null\n }\n }\n\n const user = await adapter.getUser(payload.sub)\n if (!user)\n return null\n const { sub, iat, exp, iss, aud, nbf, jti, ...customClaims } = payload\n\n const result = await issueSession(payload.sub, {\n data: customClaims,\n ttl: options.ttl,\n })\n\n return { ...result, source }\n }\n\n async function validateSession(token: string): Promise<GauServerSession | null> {\n const payload = await verifyJWT<{ sub: string } & Record<string, unknown>>(token)\n if (!payload)\n return null\n\n const userAndAccounts = await adapter.getUserAndAccounts(payload.sub)\n if (!userAndAccounts)\n return null\n\n const { user, accounts } = userAndAccounts\n const isAdmin = Boolean(\n user\n && (\n (user.role && resolvedRoles.adminRoles.includes(user.role))\n || (resolvedRoles.adminUserIds.length > 0 && resolvedRoles.adminUserIds.includes(user.id))\n ),\n )\n const sessionUser = user ? { ...user, isAdmin } : null\n\n return { user: sessionUser, session: { id: token, ...payload }, accounts }\n }\n\n async function getAccessToken(userId: string, providerId: string) {\n const provider = providerMap.get(providerId)\n if (!provider)\n return null\n\n const accounts = await adapter.getAccounts(userId)\n const account = accounts.find(a => a.provider === providerId)\n if (!account || !account.accessToken)\n return null\n\n const now = Math.floor(Date.now() / 1000)\n const isExpired = typeof account.expiresAt === 'number' ? account.expiresAt <= now : false\n\n if (!isExpired)\n return { accessToken: account.accessToken, expiresAt: account.expiresAt ?? null }\n\n if (!account.refreshToken || !provider.refreshAccessToken)\n return null\n\n try {\n const refreshed = await provider.refreshAccessToken(account.refreshToken, {})\n const updated = {\n userId,\n provider: account.provider,\n providerAccountId: account.providerAccountId,\n accessToken: refreshed.accessToken ?? account.accessToken,\n refreshToken: refreshed.refreshToken ?? account.refreshToken,\n expiresAt: refreshed.expiresAt ?? null,\n idToken: refreshed.idToken ?? account.idToken ?? null,\n tokenType: refreshed.tokenType ?? account.tokenType ?? null,\n scope: refreshed.scope ?? account.scope ?? null,\n }\n await adapter.updateAccount?.(updated)\n return { accessToken: updated.accessToken!, expiresAt: updated.expiresAt }\n }\n catch {\n return null\n }\n }\n\n async function startImpersonation(\n adminUserId: string,\n targetUserId: string,\n options: StartImpersonationOptions = {},\n ): Promise<ImpersonationResult | null> {\n if (!resolvedImpersonation)\n throw new GauError(ErrorCodes.IMPERSONATION_DISABLED)\n\n const adminUser = await adapter.getUser(adminUserId)\n if (!adminUser)\n throw new GauError(ErrorCodes.USER_NOT_FOUND, `Admin user \"${adminUserId}\" not found`)\n\n const hasAllowedRole = adminUser.role\n ? resolvedImpersonation.allowedRoles.includes(adminUser.role)\n : false\n const isAdminUserId = resolvedRoles.adminUserIds.includes(adminUserId)\n\n if (!hasAllowedRole && !isAdminUserId)\n throw new GauError(ErrorCodes.IMPERSONATION_NOT_ALLOWED)\n\n const targetUser = await adapter.getUser(targetUserId)\n if (!targetUser)\n throw new GauError(ErrorCodes.USER_NOT_FOUND, `Target user \"${targetUserId}\" not found`)\n\n if (targetUser.role && resolvedImpersonation.cannotImpersonate.includes(targetUser.role))\n throw new GauError(ErrorCodes.IMPERSONATION_TARGET_PROTECTED)\n\n if (resolvedImpersonation.onImpersonate) {\n await resolvedImpersonation.onImpersonate({\n adminUserId,\n targetUserId,\n reason: options.reason,\n timestamp: Date.now(),\n })\n }\n\n const ttl = Math.min(options.ttl ?? resolvedImpersonation.maxTTL, resolvedImpersonation.maxTTL)\n const expiresAt = Math.floor(Date.now() / 1000) + ttl\n\n const impersonationToken = await createSession(targetUserId, {\n impersonatedBy: adminUserId,\n impersonationExpiresAt: expiresAt,\n }, ttl)\n\n const cookieOpts: SerializeOptions = {\n ...cookieOptions,\n maxAge: ttl,\n }\n\n const cookie = serialize(SESSION_COOKIE_NAME, impersonationToken, cookieOpts)\n\n const stashToken = await signJWT({ adminUserId }, { ttl: resolvedImpersonation.maxTTL * 2 })\n const stashCookie = serialize(SESSION_STASH_COOKIE_NAME, stashToken, cookieOpts)\n\n return {\n token: impersonationToken,\n cookie,\n originalCookie: stashCookie,\n maxAge: ttl,\n }\n }\n\n async function endImpersonation(request: Request): Promise<EndImpersonationResult | null> {\n const cookieHeader = request.headers.get('cookie')\n if (!cookieHeader)\n return null\n\n const parsedCookies = parse(cookieHeader)\n const stashToken = parsedCookies[SESSION_STASH_COOKIE_NAME]\n\n if (!stashToken)\n return null\n\n const stashPayload = await verifyJWT<{ adminUserId: string }>(stashToken)\n if (!stashPayload?.adminUserId)\n return null\n const adminUser = await adapter.getUser(stashPayload.adminUserId)\n if (!adminUser)\n return null\n\n const restoredSession = await issueSession(stashPayload.adminUserId)\n\n const clearStashCookie = serialize(SESSION_STASH_COOKIE_NAME, '', {\n ...cookieOptions,\n expires: new Date(0),\n maxAge: 0,\n })\n\n return {\n token: restoredSession.token,\n cookie: restoredSession.cookie,\n clearCookies: [clearStashCookie],\n }\n }\n\n return {\n ...adapter,\n providerMap: providerMap as Map<ProviderId<TProviders[number]>, TProviders[number]>,\n basePath,\n cookieOptions,\n jwt: {\n ttl: defaultTTL,\n },\n onOAuthExchange,\n mapExternalProfile,\n onBeforeLinkAccount,\n onAfterLinkAccount,\n signJWT,\n verifyJWT,\n createSession,\n validateSession,\n issueSession,\n refreshSession,\n getAccessToken,\n trustHosts,\n autoLink,\n allowDifferentEmails,\n profiles: resolvedProfiles,\n updateUserInfoOnLink,\n sessionStrategy,\n development: false,\n roles: resolvedRoles,\n cors: resolvedCors,\n onError,\n errorRedirect,\n startImpersonation,\n endImpersonation,\n impersonation: resolvedImpersonation,\n }\n}\n","/// <reference types=\"node\" />\nimport {\n createJWTSignatureMessage,\n encodeJWT,\n JWSRegisteredHeaders,\n JWTRegisteredClaims,\n parseJWT,\n} from '@oslojs/jwt'\nimport { AuthError } from '../core/index'\nimport { constantTimeEqual, deriveKeysFromSecret, rawToDer } from './utils'\n\nexport type SupportedAlgorithm = 'ES256' | 'HS256'\n\ninterface CommonSignOptions {\n /** Time-to-live in seconds (exp claim). If omitted the token will not expire. */\n ttl?: number\n}\n\nexport type SignOptions\n = | ({ algorithm?: 'ES256', privateKey?: CryptoKey, secret?: string }\n & CommonSignOptions & { iss?: string, aud?: string | string[], sub?: string })\n | ({ algorithm: 'HS256', secret?: string | Uint8Array, privateKey?: never }\n & CommonSignOptions & { iss?: string, aud?: string | string[], sub?: string })\n\n/**\n * Create a signed JWT.\n * Defaults to ES256 when a privateKey is supplied. Falls back to HS256 when a secret is supplied.\n */\nexport async function sign<T extends Record<string, unknown>>(payload: T, options: SignOptions = {}): Promise<string> {\n let { algorithm = 'ES256', ttl, iss, aud, sub, privateKey, secret } = options\n\n if (algorithm === 'ES256') {\n if (!privateKey) {\n if (typeof secret !== 'string')\n throw new AuthError('Missing secret for ES256 signing. It must be a base64url-encoded string.');\n\n ({ privateKey } = await deriveKeysFromSecret(secret))\n }\n }\n else if (algorithm === 'HS256' && !secret) {\n throw new AuthError('Missing secret for HS256 signing')\n }\n\n const now = Math.floor(Date.now() / 1000)\n\n const jwtPayload: Record<string, unknown> = { iat: now, iss, aud, sub, ...payload }\n\n if (ttl != null && ttl > 0)\n jwtPayload.exp = now + ttl\n\n const isHS256 = algorithm === 'HS256'\n const alg: SupportedAlgorithm = isHS256 ? 'HS256' : 'ES256'\n\n const headerJSON = JSON.stringify({ alg, typ: 'JWT' })\n const payloadJSON = JSON.stringify(jwtPayload)\n\n const signatureMessage = createJWTSignatureMessage(headerJSON, payloadJSON)\n\n let signature: Uint8Array\n\n if (isHS256) {\n // HS256 (HMAC-SHA256)\n const secretBytes = typeof secret === 'string'\n ? new TextEncoder().encode(secret)\n : secret\n\n const cryptoKey = await crypto.subtle.importKey(\n 'raw',\n secretBytes as BufferSource,\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n )\n\n signature = new Uint8Array(await crypto.subtle.sign('HMAC', cryptoKey, signatureMessage as BufferSource))\n }\n else {\n // ES256 (ECDSA-SHA256)\n // Runtimes like Bun's return the raw (r||s) signature directly, not DER-encoded.\n signature = new Uint8Array(\n await crypto.subtle.sign(\n { name: 'ECDSA', hash: 'SHA-256' },\n privateKey!,\n signatureMessage as BufferSource,\n ),\n )\n }\n\n return encodeJWT(headerJSON, payloadJSON, signature)\n}\n\nexport type VerifyOptions\n = | { algorithm?: 'ES256', publicKey?: CryptoKey, secret?: string, iss?: string, aud?: string | string[] }\n | { algorithm: 'HS256', secret?: string | Uint8Array, publicKey?: never, iss?: string, aud?: string | string[] }\n\n/**\n * Verify a JWT and return its payload when the signature is valid.\n * The algorithm is inferred from options – ES256 by default.\n * Throws when verification fails or the token is expired.\n */\nexport async function verify<T = Record<string, unknown>>(token: string, options: VerifyOptions): Promise<T> {\n let { algorithm = 'ES256', publicKey, secret, iss, aud } = options\n\n if (algorithm === 'ES256') {\n if (!publicKey) {\n if (typeof secret !== 'string')\n throw new AuthError('Missing secret for ES256 verification. Must be a base64url-encoded string.');\n\n ({ publicKey } = await deriveKeysFromSecret(secret))\n }\n }\n\n if (algorithm === 'HS256' && !secret)\n throw new AuthError('Missing secret for HS256 verification')\n\n const [header, payload, signature, signatureMessage] = parseJWT(token)\n\n const headerParams = new JWSRegisteredHeaders(header)\n const headerAlg = headerParams.algorithm()\n\n let validSignature = false\n\n // HS256 verification path\n if (algorithm === 'HS256') {\n if (headerAlg !== 'HS256')\n throw new Error(`JWT algorithm is \"${headerAlg}\", but verifier was configured for \"HS256\"`)\n\n const secretBytes = typeof secret === 'string'\n ? new TextEncoder().encode(secret)\n : secret\n\n const cryptoKey = await crypto.subtle.importKey(\n 'raw',\n secretBytes as BufferSource,\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n )\n\n const expectedSig = new Uint8Array(await crypto.subtle.sign('HMAC', cryptoKey, signatureMessage as BufferSource))\n validSignature = constantTimeEqual(expectedSig, new Uint8Array(signature))\n }\n // ES256 verification path (default)\n else {\n if (headerAlg !== 'ES256')\n throw new AuthError(`JWT algorithm is \"${headerAlg}\", but verifier was configured for \"ES256\"`)\n\n const signatureBytes = new Uint8Array(signature)\n\n // Runtimes like Node.js return DER-encoded signatures. Others (Bun) return raw (r||s).\n // We try DER first, as it's more common in Node environments.\n validSignature = await crypto.subtle.verify(\n { name: 'ECDSA', hash: 'SHA-256' },\n publicKey!,\n signatureBytes as BufferSource,\n signatureMessage as BufferSource,\n )\n\n if (!validSignature && signatureBytes.length === 64) {\n // If DER verification fails and the signature is 64 bytes, it might be a raw signature.\n // Convert it to DER and try again.\n try {\n const derSig = rawToDer(signatureBytes)\n validSignature = await crypto.subtle.verify(\n { name: 'ECDSA', hash: 'SHA-256' },\n publicKey!,\n derSig as BufferSource,\n signatureMessage as BufferSource,\n )\n }\n catch {\n // rawToDer can throw if the signature is not 64 bytes, but we already checked.\n // This catch is for other unexpected errors.\n validSignature = false\n }\n }\n }\n\n if (!validSignature)\n throw new AuthError('Invalid JWT signature')\n\n const claims = new JWTRegisteredClaims(payload)\n if (claims.hasExpiration() && !claims.verifyExpiration())\n throw new AuthError('JWT expired')\n if (claims.hasNotBefore() && !claims.verifyNotBefore())\n throw new AuthError('JWT not yet valid')\n if (iss && (payload as any).iss !== iss)\n throw new AuthError('Invalid JWT issuer')\n\n if (aud) {\n const expectedAudience = Array.isArray(aud) ? aud : [aud]\n const tokenAudience = (payload as any).aud\n ? (Array.isArray((payload as any).aud) ? (payload as any).aud : [(payload as any).aud])\n : []\n\n if (!expectedAudience.some(audValue => tokenAudience.includes(audValue)))\n throw new AuthError('Invalid JWT audience')\n }\n\n return payload as T\n}\n","export const ErrorMessages = {\n // OAuth Flow Errors\n CSRF_INVALID: 'Invalid CSRF token',\n PKCE_MISSING: 'Missing PKCE code verifier',\n PKCE_CHALLENGE_MISSING: 'Missing PKCE challenge',\n OAUTH_CANCELLED: 'Authentication was cancelled',\n PROVIDER_NOT_FOUND: 'Provider not found',\n AUTHORIZATION_URL_FAILED: 'Could not create authorization URL',\n\n // User Errors\n USER_NOT_FOUND: 'User not found',\n USER_CREATE_FAILED: 'Failed to create user',\n ACCOUNT_ALREADY_LINKED: 'Account already linked to another user',\n ACCOUNT_LINK_FAILED: 'Failed to link account',\n ACCOUNT_NOT_LINKED: 'Account not linked',\n CANNOT_UNLINK_LAST_ACCOUNT: 'Cannot unlink the last account',\n EMAIL_ALREADY_EXISTS: 'An account with this email already exists',\n EMAIL_MISMATCH: 'Email mismatch between existing account and provider',\n LINKING_NOT_ALLOWED: 'Linking not allowed',\n LINK_ONLY_PROVIDER: 'Sign-in with this provider is disabled. Please link it to an existing account.',\n\n // Session Errors\n UNAUTHORIZED: 'Unauthorized',\n FORBIDDEN: 'Forbidden',\n SESSION_INVALID: 'Invalid session',\n SESSION_VALIDATION_FAILED: 'Failed to validate session',\n\n // Token Errors\n TOKEN_INVALID: 'Invalid token',\n TOKEN_EXPIRED: 'Token expired',\n CODE_VERIFIER_INVALID: 'Invalid code verifier',\n\n // Request Errors\n NOT_FOUND: 'Not found',\n METHOD_NOT_ALLOWED: 'Method not allowed',\n INVALID_REQUEST: 'Invalid request',\n INVALID_REDIRECT_URL: 'Invalid redirect URL',\n UNTRUSTED_HOST: 'Untrusted redirect host',\n UNKNOWN_PROFILE: 'Unknown profile',\n\n // Internal Errors\n INTERNAL_ERROR: 'An unexpected error occurred',\n\n // Impersonation Errors\n IMPERSONATION_DISABLED: 'Impersonation is not enabled',\n IMPERSONATION_NOT_ALLOWED: 'You are not allowed to impersonate users',\n IMPERSONATION_TARGET_PROTECTED: 'Cannot impersonate users with protected roles',\n} as const\n\nexport type ErrorCode = keyof typeof ErrorMessages\n\nexport const ErrorCodes: { [K in ErrorCode]: K } = Object.fromEntries(\n Object.keys(ErrorMessages).map(k => [k, k]),\n) as { [K in ErrorCode]: K }\n\n/**\n * Default HTTP status codes for each error code.\n * Errors not listed here default to 400.\n */\nexport const ErrorStatuses: Partial<Record<ErrorCode, number>> = {\n CSRF_INVALID: 403,\n UNAUTHORIZED: 401,\n FORBIDDEN: 403,\n NOT_FOUND: 404,\n METHOD_NOT_ALLOWED: 405,\n INTERNAL_ERROR: 500,\n USER_CREATE_FAILED: 500,\n ACCOUNT_LINK_FAILED: 500,\n AUTHORIZATION_URL_FAILED: 500,\n SESSION_VALIDATION_FAILED: 500,\n ACCOUNT_ALREADY_LINKED: 409,\n EMAIL_ALREADY_EXISTS: 409,\n LINKING_NOT_ALLOWED: 403,\n IMPERSONATION_DISABLED: 403,\n IMPERSONATION_NOT_ALLOWED: 403,\n IMPERSONATION_TARGET_PROTECTED: 403,\n}\n\nexport interface GauErrorOptions {\n /** HTTP status code (uses default for error code if not specified) */\n status?: number\n /** URL to redirect to after showing error (for OAuth flow errors) */\n redirectUrl?: string\n /** Original error that caused this error */\n cause?: unknown\n}\n\nexport class GauError extends Error {\n readonly code: ErrorCode\n readonly status: number\n readonly redirectUrl?: string\n override readonly cause?: unknown\n\n constructor(\n code: ErrorCode,\n messageOrOptions?: string | GauErrorOptions,\n options?: GauErrorOptions,\n ) {\n const message = typeof messageOrOptions === 'string'\n ? messageOrOptions\n : ErrorMessages[code]\n const opts = typeof messageOrOptions === 'object'\n ? messageOrOptions\n : options ?? {}\n\n super(message)\n this.name = 'GauError'\n this.code = code\n this.status = opts.status ?? ErrorStatuses[code] ?? 400\n this.redirectUrl = opts.redirectUrl\n this.cause = opts.cause\n }\n\n toJSON() {\n return {\n error: this.message,\n code: this.code,\n ...(this.redirectUrl && { redirectUrl: this.redirectUrl }),\n }\n }\n}\n\nexport function createErrorRedirectUrl(baseUrl: string, error: GauError): string {\n const url = new URL(baseUrl, 'http://placeholder')\n url.searchParams.set('code', error.code)\n url.searchParams.set('message', error.message)\n url.searchParams.set('status', String(error.status))\n if (error.redirectUrl)\n url.searchParams.set('redirect', error.redirectUrl)\n\n return url.pathname + url.search\n}\n\nexport interface ErrorContext {\n error: GauError\n request: Request\n}\n\nexport interface ErrorHandlerConfig {\n basePath: string\n onError?: (context: ErrorContext) => Response | Promise<Response | undefined> | undefined\n errorRedirect?: string\n}\n\nexport function isUserFacingRequest(request: Request, basePath: string): boolean {\n // POST requests are always API calls\n if (request.method !== 'GET')\n return false\n\n const url = new URL(request.url)\n const path = url.pathname.substring(basePath.length)\n const parts = path.split('/').filter(Boolean)\n\n // GET /session is an API call\n if (parts.length === 1 && parts[0] === 'session')\n return false\n\n // GET /:provider (sign-in start) - user-facing\n if (parts.length === 1)\n return true\n\n // GET /callback/:provider or GET /link/:provider - user-facing\n if (parts.length === 2 && (parts[0] === 'callback' || parts[0] === 'link'))\n return true\n\n return false\n}\n\nexport async function handleError(\n context: ErrorContext,\n config: ErrorHandlerConfig,\n): Promise<Response> {\n const { error, request } = context\n\n // 1. Try custom onError handler\n if (config.onError) {\n try {\n const response = await config.onError(context)\n if (response)\n return response\n }\n catch (e) {\n console.error('onError handler threw:', e)\n }\n }\n\n const userFacing = isUserFacingRequest(request, config.basePath)\n\n // 2. Try errorRedirect for user-facing requests\n if (config.errorRedirect && userFacing) {\n const redirectUrl = createErrorRedirectUrl(config.errorRedirect, error)\n return new Response(null, {\n status: 302,\n headers: { Location: redirectUrl },\n })\n }\n\n // 3. Default handling\n if (userFacing) {\n const { renderErrorPage, htmlResponse } = await import('./templates')\n const html = renderErrorPage({\n title: 'Authentication Error',\n message: error.message,\n code: error.code,\n redirectUrl: error.redirectUrl,\n })\n return htmlResponse(html, error.status)\n }\n\n return new Response(JSON.stringify(error.toJSON()), {\n status: error.status,\n headers: { 'Content-Type': 'application/json; charset=utf-8' },\n })\n}\n","import type { Auth } from '../createAuth'\nimport type { User } from '../index'\nimport {\n CALLBACK_URI_COOKIE_NAME,\n CLIENT_CHALLENGE_COOKIE_NAME,\n Cookies,\n CSRF_COOKIE_NAME,\n LINKING_TOKEN_COOKIE_NAME,\n parseCookies,\n PKCE_COOKIE_NAME,\n PROVIDER_OPTIONS_COOKIE_NAME,\n SESSION_COOKIE_NAME,\n} from '../cookies'\nimport { ErrorCodes, GauError } from '../errors'\nimport { maybeMapExternalProfile, runOnAfterLinkAccount, runOnBeforeLinkAccount, runOnOAuthExchange } from '../hooks'\nimport { json, redirect } from '../index'\nimport { htmlResponse, renderCancelledPage, renderSuccessPage } from '../templates'\n\nexport async function handleCallback(request: Request, auth: Auth, providerId: string): Promise<Response> {\n const provider = auth.providerMap.get(providerId)\n if (!provider)\n throw new GauError(ErrorCodes.PROVIDER_NOT_FOUND)\n\n const url = new URL(request.url)\n const code = url.searchParams.get('code')\n const state = url.searchParams.get('state')\n const error = url.searchParams.get('error')\n\n if (!code || !state || error) {\n let redirectTo = '/'\n if (state && state.includes('.')) {\n try {\n const encodedRedirect = state.split('.')[1]\n redirectTo = atob(encodedRedirect ?? '') || '/'\n }\n catch {\n redirectTo = '/'\n }\n }\n\n const html = renderCancelledPage({ redirectUrl: redirectTo })\n return htmlResponse(html)\n }\n\n const requestCookies = parseCookies(request.headers.get('Cookie'))\n const cookies = new Cookies(requestCookies, auth.cookieOptions)\n\n let savedState: string | undefined\n let redirectTo = '/'\n if (state.includes('.')) {\n const [originalSavedState, encodedRedirect] = state.split('.')\n savedState = originalSavedState\n try {\n redirectTo = atob(encodedRedirect ?? '') || '/'\n }\n catch {\n redirectTo = '/'\n }\n }\n else {\n savedState = state\n }\n\n const csrfToken = cookies.get(CSRF_COOKIE_NAME)\n\n if (!csrfToken || csrfToken !== savedState)\n throw new GauError(ErrorCodes.CSRF_INVALID, { redirectUrl: redirectTo })\n\n const codeVerifier = cookies.get(PKCE_COOKIE_NAME)\n if (!codeVerifier)\n throw new GauError(ErrorCodes.PKCE_MISSING, { redirectUrl: redirectTo })\n\n const callbackUri = cookies.get(CALLBACK_URI_COOKIE_NAME)\n const providerOptionsRaw = cookies.get(PROVIDER_OPTIONS_COOKIE_NAME)\n let providerOverrides: any | undefined\n if (providerOptionsRaw) {\n try {\n const decoded = atob(providerOptionsRaw)\n const parsed = JSON.parse(decoded)\n providerOverrides = parsed?.overrides\n }\n catch {}\n }\n const linkingToken = cookies.get(LINKING_TOKEN_COOKIE_NAME)\n\n if (linkingToken)\n cookies.delete(LINKING_TOKEN_COOKIE_NAME)\n\n const isLinking = !!linkingToken\n\n if (isLinking) {\n const session = await auth.validateSession(linkingToken!)\n if (!session) {\n cookies.delete(CSRF_COOKIE_NAME)\n cookies.delete(PKCE_COOKIE_NAME)\n if (callbackUri)\n cookies.delete(CALLBACK_URI_COOKIE_NAME)\n cookies.delete(PROVIDER_OPTIONS_COOKIE_NAME)\n const response = redirect(redirectTo)\n cookies.toHeaders().forEach((value, key) => response.headers.append(key, value))\n return response\n }\n }\n\n const { user: rawProviderUser, tokens } = await provider.validateCallback(code, codeVerifier, callbackUri ?? undefined, providerOverrides)\n\n {\n const session = isLinking ? await auth.validateSession(linkingToken!) : null\n const hookResult = await runOnOAuthExchange(auth, {\n request,\n providerId,\n state,\n code,\n codeVerifier,\n callbackUri,\n redirectTo,\n cookies,\n providerUser: rawProviderUser,\n tokens,\n isLinking,\n sessionUserId: session?.user?.id,\n })\n if (hookResult.handled) {\n cookies.delete(CSRF_COOKIE_NAME)\n cookies.delete(PKCE_COOKIE_NAME)\n if (callbackUri)\n cookies.delete(CALLBACK_URI_COOKIE_NAME)\n cookies.delete(PROVIDER_OPTIONS_COOKIE_NAME)\n const response = hookResult.response\n cookies.toHeaders().forEach((value, key) => response.headers.append(key, value))\n return response\n }\n }\n\n const providerUser = await maybeMapExternalProfile(auth, {\n request,\n providerId,\n providerUser: rawProviderUser,\n tokens,\n isLinking,\n })\n\n // Enforce provider-level link-only when not linking (profile-level enforced at redirect time)\n if (!isLinking && (auth.providerMap.get(providerId)?.linkOnly === true)) {\n cookies.delete(CSRF_COOKIE_NAME)\n cookies.delete(PKCE_COOKIE_NAME)\n if (callbackUri)\n cookies.delete(CALLBACK_URI_COOKIE_NAME)\n cookies.delete(PROVIDER_OPTIONS_COOKIE_NAME)\n throw new GauError(ErrorCodes.LINK_ONLY_PROVIDER, { redirectUrl: redirectTo })\n }\n\n let user: User | null = null\n\n const userFromAccount = await auth.getUserByAccount(providerId, providerUser.id)\n\n if (isLinking) {\n const session = await auth.validateSession(linkingToken)\n user = session!.user\n\n if (!user)\n throw new GauError(ErrorCodes.USER_NOT_FOUND, { redirectUrl: redirectTo })\n\n if (userFromAccount && userFromAccount.id !== user.id)\n throw new GauError(ErrorCodes.ACCOUNT_ALREADY_LINKED, { redirectUrl: redirectTo })\n\n if (auth.allowDifferentEmails === false) {\n const currentEmail = user.email\n const providerEmail = providerUser.email\n if (currentEmail && providerEmail && currentEmail !== providerEmail)\n throw new GauError(ErrorCodes.EMAIL_MISMATCH, { redirectUrl: redirectTo })\n }\n\n if (user) {\n const update: Partial<User> & { id: string } = { id: user.id }\n let needsUpdate = false\n\n if (auth.updateUserInfoOnLink) {\n if (providerUser.name && providerUser.name !== user.name) {\n update.name = providerUser.name\n needsUpdate = true\n }\n if (providerUser.avatar && providerUser.avatar !== user.image) {\n update.image = providerUser.avatar\n needsUpdate = true\n }\n }\n else {\n if (!user.name && providerUser.name) {\n update.name = providerUser.name\n needsUpdate = true\n }\n if (!user.image && providerUser.avatar) {\n update.image = providerUser.avatar\n needsUpdate = true\n }\n }\n\n if (\n user.email\n && providerUser.email\n && user.email === providerUser.email\n && providerUser.emailVerified === true\n && (!user.emailVerified || auth.updateUserInfoOnLink)\n ) {\n update.emailVerified = true\n needsUpdate = true\n }\n\n if (needsUpdate) {\n try {\n user = await auth.updateUser(update)\n }\n catch (e) {\n console.error('Failed to update user info on link:', e)\n }\n }\n }\n }\n else {\n user = userFromAccount\n }\n\n if (!user) {\n const autoLink = auth.autoLink ?? 'verifiedEmail'\n const shouldLinkByEmail = providerUser.email && (\n (autoLink === 'always')\n || (autoLink === 'verifiedEmail' && providerUser.emailVerified === true)\n )\n if (shouldLinkByEmail) {\n const existingUser = await auth.getUserByEmail(providerUser.email!)\n if (existingUser) {\n // If the email is verified by the new provider, and the existing user's email is not,\n // update the user's email verification status.\n if (providerUser.emailVerified && !existingUser.emailVerified) {\n user = await auth.updateUser({\n id: existingUser.id,\n emailVerified: true,\n })\n }\n else {\n user = existingUser\n }\n }\n }\n if (!user) {\n try {\n if (providerUser.email && providerUser.emailVerified === true && auth.autoLink === false) {\n const existingWithSameEmail = await auth.getUserByEmail(providerUser.email)\n if (existingWithSameEmail)\n throw new GauError(ErrorCodes.EMAIL_ALREADY_EXISTS, { redirectUrl: redirectTo })\n }\n\n let resolvedRole: string | undefined\n try {\n resolvedRole = auth.roles.resolveOnCreate?.({ providerId, profile: providerUser, request: request as unknown as Request })\n }\n catch (e) {\n console.error('roles.resolveOnCreate threw:', e)\n }\n\n const emailToStore = providerUser.emailVerified === true ? providerUser.email : null\n\n user = await auth.createUser({\n name: providerUser.name,\n email: emailToStore,\n image: providerUser.avatar,\n emailVerified: providerUser.emailVerified,\n role: resolvedRole ?? auth.roles.defaultRole,\n })\n }\n catch (error) {\n if (error instanceof GauError)\n throw error\n console.error('Failed to create user:', error)\n throw new GauError(ErrorCodes.USER_CREATE_FAILED, { cause: error, redirectUrl: redirectTo })\n }\n }\n }\n\n // self-healing: update user's email if it's missing or unverified and the provider returns a verified email\n if (user && providerUser.email) {\n const { email: currentEmail, emailVerified: currentEmailVerified } = user\n const { email: providerEmail, emailVerified: providerEmailVerified } = providerUser\n\n const update: Partial<User> & { id: string } = { id: user.id }\n let needsUpdate = false\n\n // user has no primary email. promote the provider's email but only if it's verified.\n if (!currentEmail && providerEmailVerified === true) {\n update.email = providerEmail\n update.emailVerified = true\n needsUpdate = true\n }\n // user has an unverified primary email, and the provider confirms this same email is verified.\n else if (\n currentEmail === providerEmail\n && providerEmailVerified === true\n && !currentEmailVerified\n ) {\n update.emailVerified = true\n needsUpdate = true\n }\n\n if (needsUpdate) {\n try {\n user = await auth.updateUser(update)\n }\n catch (error) {\n console.error('Failed to update user after sign-in:', error)\n }\n }\n }\n\n if (!userFromAccount) {\n // GitHub sometimes doesn't return these which causes arctic to throw an error\n let refreshToken: string | null\n try {\n refreshToken = tokens.refreshToken()\n }\n catch {\n refreshToken = null\n }\n\n let expiresAt: number | undefined\n try {\n const expiresAtDate = tokens.accessTokenExpiresAt()\n if (expiresAtDate)\n expiresAt = Math.floor(expiresAtDate.getTime() / 1000)\n }\n catch {\n }\n\n let idToken: string | null\n try {\n idToken = tokens.idToken()\n }\n catch {\n idToken = null\n }\n\n {\n const pre = await runOnBeforeLinkAccount(auth, {\n request,\n providerId,\n userId: user.id,\n providerUser,\n tokens,\n })\n if (pre.allow === false) {\n const response = pre.response ?? (() => {\n throw new GauError(ErrorCodes.LINKING_NOT_ALLOWED, { redirectUrl: redirectTo })\n })()\n cookies.toHeaders().forEach((value, key) => response.headers.append(key, value))\n return response\n }\n }\n\n try {\n let scope: string | null\n try {\n scope = tokens.scopes()?.join(' ') ?? null\n }\n catch {\n scope = null\n }\n\n await auth.linkAccount({\n userId: user.id,\n provider: providerId,\n providerAccountId: providerUser.id,\n accessToken: tokens.accessToken(),\n refreshToken,\n expiresAt,\n tokenType: tokens.tokenType?.() ?? null,\n scope,\n idToken,\n })\n await runOnAfterLinkAccount(auth, {\n request,\n providerId,\n userId: user.id,\n providerUser,\n tokens,\n action: 'link',\n })\n }\n catch (error) {\n console.error('Error linking account:', error)\n throw new GauError(ErrorCodes.ACCOUNT_LINK_FAILED, { cause: error, redirectUrl: redirectTo })\n }\n }\n else {\n // Existing account: update stored tokens on sign-in (access/refresh/expires/idToken/etc.)\n try {\n const accounts = await auth.getAccounts(user!.id)\n const existing = accounts.find(a => a.provider === providerId && a.providerAccountId === providerUser.id)\n\n if (existing && auth.updateAccount) {\n let refreshToken: string | null\n try {\n refreshToken = tokens.refreshToken()\n }\n catch {\n refreshToken = existing.refreshToken ?? null\n }\n\n let expiresAt: number | undefined\n try {\n const expiresAtDate = tokens.accessTokenExpiresAt()\n if (expiresAtDate)\n expiresAt = Math.floor(expiresAtDate.getTime() / 1000)\n }\n catch {\n expiresAt = existing.expiresAt ?? undefined\n }\n\n let idToken: string | null\n try {\n idToken = tokens.idToken()\n }\n catch {\n idToken = existing.idToken ?? null\n }\n\n let scope: string | null\n try {\n scope = tokens.scopes()?.join(' ') ?? existing.scope ?? null\n }\n catch {\n scope = existing.scope ?? null\n }\n\n await auth.updateAccount({\n userId: user!.id,\n provider: providerId,\n providerAccountId: providerUser.id,\n accessToken: tokens.accessToken() ?? existing.accessToken ?? undefined,\n refreshToken,\n expiresAt: expiresAt ?? existing.expiresAt ?? undefined,\n tokenType: tokens.tokenType?.() ?? existing.tokenType ?? null,\n scope,\n idToken,\n })\n await runOnAfterLinkAccount(auth, {\n request,\n providerId,\n userId: user!.id,\n providerUser,\n tokens,\n action: 'update',\n })\n }\n }\n catch (error) {\n console.error('Failed to update account tokens on sign-in:', error)\n }\n }\n\n const sessionToken = await auth.createSession(user.id)\n\n const requestUrl = new URL(request.url)\n const redirectUrl = new URL(redirectTo, request.url)\n\n const forceToken = auth.sessionStrategy === 'token'\n const forceCookie = auth.sessionStrategy === 'cookie'\n\n const isCustomScheme = redirectUrl.protocol !== 'http:' && redirectUrl.protocol !== 'https:'\n const isCrossHost = requestUrl.host !== redirectUrl.host\n\n // For Tauri, we can't set a cookie on a custom protocol or a different host,\n // so we pass the token in the URL. Additionally, return a small HTML page\n // that navigates to the deep-link and then closes after browser handoff\n // signals, which avoids racing first-run deep-link permission prompts.\n if (forceToken || (!forceCookie && (isCustomScheme || isCrossHost))) {\n const destination = new URL(redirectUrl)\n const clientChallenge = cookies.get(CLIENT_CHALLENGE_COOKIE_NAME)\n\n if (clientChallenge) {\n // PKCE\n const authCode = await auth.signJWT({\n sub: user.id,\n challenge: clientChallenge,\n }, { ttl: 60 })\n\n destination.searchParams.set('code', authCode)\n }\n else {\n throw new GauError(ErrorCodes.PKCE_CHALLENGE_MISSING, { redirectUrl: redirectTo })\n }\n\n const html = renderSuccessPage({ redirectUrl: destination.toString() })\n\n // Clear temporary cookies\n cookies.delete(CSRF_COOKIE_NAME)\n cookies.delete(PKCE_COOKIE_NAME)\n if (callbackUri)\n cookies.delete(CALLBACK_URI_COOKIE_NAME)\n cookies.delete(PROVIDER_OPTIONS_COOKIE_NAME)\n cookies.delete(CLIENT_CHALLENGE_COOKIE_NAME)\n\n const response = htmlResponse(html)\n cookies.toHeaders().forEach((value, key) => {\n response.headers.append(key, value)\n })\n return response\n }\n\n cookies.set(SESSION_COOKIE_NAME, sessionToken, {\n maxAge: auth.jwt.ttl,\n sameSite: auth.development ? 'lax' : 'none',\n secure: !auth.development,\n })\n cookies.delete(CSRF_COOKIE_NAME)\n cookies.delete(PKCE_COOKIE_NAME)\n if (callbackUri)\n cookies.delete(CALLBACK_URI_COOKIE_NAME)\n cookies.delete(PROVIDER_OPTIONS_COOKIE_NAME)\n\n const redirectParam = url.searchParams.get('redirect')\n\n let response: Response\n if (redirectParam === 'false') {\n const accounts = await auth.getAccounts(user.id)\n const isAdmin = Boolean(\n (user.role && auth.roles.adminRoles.includes(user.role))\n || auth.roles.adminUserIds.includes(user.id),\n )\n response = json({ user: { ...user, isAdmin, accounts } })\n }\n else {\n response = redirect(redirectTo)\n }\n\n cookies.toHeaders().forEach((value, key) => {\n response.headers.append(key, value)\n })\n\n return response\n}\n","import { generateCodeVerifier, generateState } from 'arctic'\n\nexport function createOAuthUris() {\n const state = generateState()\n const codeVerifier = generateCodeVerifier()\n\n return {\n state,\n codeVerifier,\n }\n}\n","export interface User {\n id: string\n name?: string | null\n email?: string | null\n emailVerified?: boolean | null\n image?: string | null\n role?: string | null\n isAdmin?: boolean\n}\n\nexport interface Session {\n id: string\n sub: string\n [key: string]: unknown\n}\n\nexport interface ClientAccount {\n provider: string\n providerAccountId: string\n}\n\n/**\n * Client-safe session data.\n */\nexport interface GauSession<TProviders extends string = string> {\n user: User | null\n session: Omit<Session, 'id'> | null\n accounts?: ClientAccount[] | null\n providers?: TProviders[]\n}\n\n/**\n * Full server-side session with complete account data including tokens.\n *\n * Never serialize this to the client - contains sensitive access/refresh tokens.\n */\nexport interface GauServerSession<TProviders extends string = string> {\n user: User | null\n session: Session | null\n accounts?: Account[] | null\n providers?: TProviders[]\n}\n\nexport const NULL_SESSION = {\n user: null,\n session: null,\n accounts: null,\n} as const\n\nexport function toClientSession<TProviders extends string = string>(\n serverSession: GauServerSession<TProviders>,\n): GauSession<TProviders> {\n const safeSession: Omit<Session, 'id'> | null = serverSession.session\n && (({ id: _id, ...rest }) => rest)(serverSession.session)\n\n return {\n user: serverSession.user,\n session: safeSession,\n accounts: serverSession.accounts?.map(acc => ({\n provider: acc.provider,\n providerAccountId: acc.providerAccountId,\n })) ?? null,\n providers: serverSession.providers,\n }\n}\n\nexport interface NewUser extends Omit<User, 'id' | 'accounts' | 'isAdmin'> {\n id?: string\n}\n\nexport interface Account {\n userId: string\n provider: string\n providerAccountId: string\n type?: string // e.g. \"oauth\"\n accessToken?: string | null\n refreshToken?: string | null\n expiresAt?: number | null // epoch seconds\n idToken?: string | null\n scope?: string | null\n tokenType?: string | null\n sessionState?: string | null\n}\n\nexport interface NewAccount extends Account {}\n\nexport interface Adapter {\n getUser: (id: string) => Promise<User | null>\n getUserByEmail: (email: string) => Promise<User | null>\n getUserByAccount: (provider: string, providerAccountId: string) => Promise<User | null>\n getAccounts: (userId: string) => Promise<Account[]>\n getUserAndAccounts: (userId: string) => Promise<{ user: User, accounts: Account[] } | null>\n createUser: (data: NewUser) => Promise<User>\n linkAccount: (data: NewAccount) => Promise<void>\n unlinkAccount: (provider: string, providerAccountId: string) => Promise<void>\n updateAccount?: (data: Partial<Account> & { userId: string, provider: string, providerAccountId: string }) => Promise<void>\n updateUser: (data: Partial<User> & { id: string }) => Promise<User>\n deleteUser: (id: string) => Promise<void>\n}\n\nexport class AuthError extends Error {\n override readonly cause?: unknown\n constructor(message: string, cause?: unknown) {\n super(message)\n this.name = 'AuthError'\n this.cause = cause\n }\n}\n\nexport function json<T>(data: T, init: ResponseInit = {}): Response {\n const headers = new Headers(init.headers)\n if (!headers.has('Content-Type'))\n headers.set('Content-Type', 'application/json; charset=utf-8')\n return new Response(JSON.stringify(data), { ...init, headers })\n}\n\nexport function redirect(url: string, status: 302 | 303 = 302): Response {\n return new Response(null, {\n status,\n headers: {\n Location: url,\n },\n })\n}\n\nexport * from './cookies'\nexport * from './createAuth'\nexport * from './errors'\nexport * from './handler'\nexport * from './templates'\nexport * from './utils'\n\nexport const REFRESHED_TOKEN_HEADER = 'X-Refreshed-Token'\n\n/**\n * Helper to check if a session is an impersonation session.\n */\nexport function isImpersonating(session: Session | null): boolean {\n return session?.impersonatedBy != null\n}\n","import type { GauSession, ProfileName, ProviderIds } from '../../core'\nimport { isTauri } from '../../runtimes/tauri/index'\nimport { clearSessionToken, getSessionToken, handleRefreshedToken, storeSessionToken } from '../token'\n\nexport { clearSessionToken, getSessionToken, handleRefreshedToken, REFRESHED_TOKEN_HEADER, SESSION_TOKEN_KEY, storeSessionToken } from '../token'\n\nexport interface AuthClientOptions {\n baseUrl: string\n scheme?: string\n}\n\ntype SessionListener<TAuth = unknown> = (session: GauSession<ProviderIds<TAuth>>) => void\n\nfunction buildQuery(params: Record<string, string | undefined | null>): string {\n const q = new URLSearchParams()\n for (const [k, v] of Object.entries(params)) {\n if (v != null && v !== '')\n q.set(k, String(v))\n }\n const s = q.toString()\n return s ? `?${s}` : ''\n}\n\nexport function createAuthClient<const TAuth = unknown>({ baseUrl, scheme = 'gau' }: AuthClientOptions) {\n let currentSession: GauSession<ProviderIds<TAuth>> = { user: null, session: null, accounts: null, providers: [] }\n const listeners = new Set<SessionListener<TAuth>>()\n\n const notify = () => {\n for (const l of listeners)\n l(currentSession)\n }\n\n async function fetchSession(): Promise<GauSession<ProviderIds<TAuth>>> {\n const token = getSessionToken()\n const headers = token ? { Authorization: `Bearer ${token}` } : undefined\n const res = await fetch(`${baseUrl}/session`, token ? { headers } : { credentials: 'include' })\n const contentType = res.headers.get('content-type')\n if (contentType?.includes('application/json'))\n return await res.json()\n return { user: null, session: null, accounts: null, providers: [] }\n }\n\n async function refreshSession(): Promise<GauSession<ProviderIds<TAuth>>> {\n const next = await fetchSession()\n currentSession = next\n notify()\n return next\n }\n\n async function applySessionToken(token: string): Promise<void> {\n try {\n storeSessionToken(token)\n }\n finally {\n await refreshSession()\n }\n }\n\n function onSessionChange(listener: SessionListener<TAuth>): () => void {\n listeners.add(listener)\n return () => listeners.delete(listener)\n }\n\n async function handleRedirectCallback(replaceUrl?: (url: string) => void): Promise<boolean> {\n if (typeof window === 'undefined')\n return false\n\n if (window.location.hash === '#_=_') {\n const cleanUrl = window.location.pathname + window.location.search\n if (replaceUrl)\n replaceUrl(cleanUrl)\n else\n window.history.replaceState(null, '', cleanUrl)\n return false\n }\n\n const hash = window.location.hash?.substring(1) ?? ''\n if (!hash)\n return false\n\n const params = new URLSearchParams(hash)\n const token = params.get('token')\n if (!token)\n return false\n\n await applySessionToken(token)\n\n const cleanUrl = window.location.pathname + window.location.search\n if (replaceUrl)\n replaceUrl(cleanUrl)\n else\n window.history.replaceState(null, '', cleanUrl)\n\n return true\n }\n\n function makeProviderUrl<P extends ProviderIds<TAuth>, PR extends (ProfileName<TAuth, P> | string) | undefined>(provider: P, params?: { redirectTo?: string, profile?: PR }): string {\n const q = buildQuery({\n redirectTo: params?.redirectTo,\n profile: params?.profile != null ? String(params.profile) : undefined,\n })\n return `${baseUrl}/${provider}${q}`\n }\n\n function makeLinkUrl<P extends ProviderIds<TAuth>, PR extends (ProfileName<TAuth, P> | string) | undefined>(provider: P, params: { redirectTo?: string, profile?: PR, redirect?: 'false' | 'true' }): string {\n const q = buildQuery({\n redirectTo: params.redirectTo,\n profile: params.profile != null ? String(params.profile) : undefined,\n redirect: params.redirect,\n })\n return `${baseUrl}/link/${provider}${q}`\n }\n\n async function signIn<P extends ProviderIds<TAuth>, PR extends (ProfileName<TAuth, P> | string) | undefined>(provider: P, options?: { redirectTo?: string, profile?: PR }): Promise<string> {\n const url = makeProviderUrl<P, PR>(provider, options)\n\n if (isTauri()) {\n const { signInWithTauri } = await import('../../runtimes/tauri/index')\n await signInWithTauri<TAuth, P, PR>(provider, baseUrl, scheme, options?.redirectTo, options?.profile)\n }\n\n return url\n }\n\n async function linkAccount<P extends ProviderIds<TAuth>, PR extends (ProfileName<TAuth, P> | string) | undefined>(provider: P, options?: { redirectTo?: string, profile?: PR }): Promise<string> {\n if (isTauri()) {\n const { linkAccountWithTauri } = await import('../../runtimes/tauri/index')\n await linkAccountWithTauri<TAuth, P, PR>(provider, baseUrl, scheme, options?.redirectTo, options?.profile)\n return makeLinkUrl<P, PR>(provider, { redirectTo: options?.redirectTo, profile: options?.profile, redirect: 'false' })\n }\n\n const linkUrl = makeLinkUrl<P, PR>(provider, { redirectTo: options?.redirectTo, profile: options?.profile, redirect: 'false' })\n const token = getSessionToken()\n const fetchOptions: RequestInit = token ? { headers: { Authorization: `Bearer ${token}` } } : { credentials: 'include' }\n const res: Response = await fetch(linkUrl, fetchOptions)\n if (res.redirected)\n return res.url\n try {\n const data = await res.json()\n if (data?.url)\n return data.url\n }\n catch {}\n return linkUrl\n }\n\n async function unlinkAccount<P extends ProviderIds<TAuth>>(provider: P): Promise<boolean> {\n const token = getSessionToken()\n const fetchOptions: RequestInit = token ? { headers: { Authorization: `Bearer ${token}` } } : { credentials: 'include' }\n const res = await fetch(`${baseUrl}/unlink/${provider}`, { method: 'POST', ...fetchOptions })\n if (res.ok) {\n await refreshSession()\n return true\n }\n return false\n }\n\n async function signOut(): Promise<void> {\n clearSessionToken()\n const token = getSessionToken()\n const headers = token ? { Authorization: `Bearer ${token}` } : undefined\n await fetch(`${baseUrl}/signout`, token ? { method: 'POST', headers } : { method: 'POST', credentials: 'include' })\n await refreshSession()\n }\n\n async function startTauriBridge(): Promise<(() => void) | void> {\n if (!isTauri())\n return\n\n const { startAuthBridge } = await import('../../runtimes/tauri/index')\n const cleanup = await startAuthBridge(baseUrl, scheme, async (token) => {\n await applySessionToken(token)\n })\n return cleanup\n }\n\n /**\n * Fetch wrapper that automatically handles authentication:\n * - Adds Authorization header if a token is stored (Tauri/mobile)\n * - Falls back to credentials: 'include' for cookie-based auth (web)\n * - Automatically stores refreshed tokens from X-Refreshed-Token header\n */\n async function authFetch(input: RequestInfo | URL, init: RequestInit = {}): Promise<Response> {\n const token = getSessionToken()\n const headers = new Headers(init.headers)\n\n if (token)\n headers.set('Authorization', `Bearer ${token}`)\n\n const res = await globalThis.fetch(input, {\n ...init,\n headers,\n ...(!token && { credentials: 'include' as RequestCredentials }),\n })\n\n handleRefreshedToken(res)\n\n return res\n }\n\n return {\n get session() {\n return currentSession\n },\n fetch: authFetch,\n fetchSession,\n refreshSession,\n applySessionToken,\n handleRedirectCallback,\n onSessionChange,\n signIn,\n linkAccount,\n unlinkAccount,\n signOut,\n startTauriBridge,\n }\n}\n"],"mappings":"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","names":["BROWSER","BROWSER","parse","serialize","cleanUrl","signInWithTauri","linkAccountWithTauri","startAuthBridge","BROWSER","signInWithTauri","linkAccountWithTauri","startAuthBridge"]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/core/templates.ts","../../../../src/client/token.ts","../../../../src/runtimes/tauri/index.ts","../../../../src/client/svelte/index.svelte.ts","../../../../src/core/cookies.ts","../../../../src/core/createAuth.ts","../../../../src/jwt/jwt.ts","../../../../src/core/errors.ts","../../../../src/core/handlers/callback.ts","../../../../src/oauth/utils.ts","../../../../src/core/index.ts","../../../../src/client/vanilla/index.ts"],"sourcesContent":["const baseStyles = `\n body {\n font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n background-color: #09090b;\n color: #fafafa;\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100vh;\n margin: 0;\n text-align: center;\n }\n .card {\n background-color: #18181b;\n border: 1px solid #27272a;\n border-radius: 0.75rem;\n padding: 2rem;\n max-width: 320px;\n }\n h1 {\n font-size: 1.25rem;\n font-weight: 600;\n margin: 0 0 0.5rem;\n }\n p {\n margin: 0;\n color: #a1a1aa;\n }\n .error-code {\n font-family: ui-monospace, monospace;\n font-size: 0.75rem;\n color: #71717a;\n margin-top: 0.5rem;\n }\n a {\n display: inline-block;\n margin-top: 1rem;\n color: #3b82f6;\n text-decoration: none;\n }\n a:hover {\n text-decoration: underline;\n }\n`\n\nexport interface ErrorPageOptions {\n /** Error title (default: \"Authentication Error\") */\n title?: string\n /** Error message to display */\n message: string\n /** Error code to display */\n code?: string\n /** URL to redirect to (shown as \"Go back\" link, also used for auto-redirect) */\n redirectUrl?: string\n /** Auto-redirect after showing error (default: true if redirectUrl provided) */\n autoRedirect?: boolean\n /** Delay before auto-redirect in ms (default: 3000) */\n redirectDelay?: number\n /** Attempt to close the window after redirect (for OAuth popups) */\n autoClose?: boolean\n}\n\nexport function renderErrorPage(options: ErrorPageOptions): string {\n const {\n title = 'Authentication Error',\n message,\n code,\n redirectUrl,\n autoRedirect = !!redirectUrl,\n redirectDelay = 3000,\n autoClose = true,\n } = options\n\n const closeScript = redirectUrl ? renderAutoCloseScript('url', autoClose) : ''\n\n const redirectScript = redirectUrl && autoRedirect\n ? `\n window.onload = function() {\n setTimeout(function() {\n const url = ${JSON.stringify(redirectUrl)};\n ${closeScript}\n window.location.href = url;\n }, ${redirectDelay});\n };\n `\n : ''\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <title>${escapeHtml(title)}</title>\n <style>${baseStyles}</style>\n ${redirectScript ? `<script>${redirectScript}</script>` : ''}\n</head>\n<body>\n <div class=\"card\">\n <h1>${escapeHtml(title)}</h1>\n <p>${escapeHtml(message)}</p>\n ${code ? `<p class=\"error-code\">${escapeHtml(code)}</p>` : ''}\n ${redirectUrl ? `<a href=\"${escapeHtml(redirectUrl)}\">Go back</a>` : ''}\n </div>\n</body>\n</html>`\n}\n\nexport interface SuccessPageOptions {\n /** Success title (default: \"Authentication Successful\") */\n title?: string\n /** Success message to display */\n message?: string\n /** URL to redirect to */\n redirectUrl: string\n /** Attempt to close the window after redirect (for OAuth popups) */\n autoClose?: boolean\n}\n\nexport function renderSuccessPage(options: SuccessPageOptions): string {\n const {\n title = 'Authentication Successful',\n message = 'You can now close this window.',\n redirectUrl,\n autoClose = true,\n } = options\n\n const closeScript = renderAutoCloseScript('url', autoClose)\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <title>${escapeHtml(title)}</title>\n <style>${baseStyles}</style>\n <script>\n window.onload = function() {\n const url = ${JSON.stringify(redirectUrl)};\n ${closeScript}\n window.location.href = url;\n };\n </script>\n</head>\n<body>\n <div class=\"card\">\n <h1>${escapeHtml(title)}</h1>\n <p>${escapeHtml(message)}</p>\n </div>\n</body>\n</html>`\n}\n\nexport interface CancelledPageOptions {\n /** Title (default: \"Authentication Cancelled\") */\n title?: string\n /** Message to display */\n message?: string\n /** URL to redirect to */\n redirectUrl?: string\n /** Attempt to close the window after redirect (for OAuth popups) */\n autoClose?: boolean\n}\n\nexport function renderCancelledPage(options: CancelledPageOptions = {}): string {\n const {\n title = 'Authentication Cancelled',\n message = 'Redirecting you back to the app...',\n redirectUrl = '/',\n autoClose = true,\n } = options\n\n const closeScript = renderAutoCloseScript('url', autoClose)\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <title>${escapeHtml(title)}</title>\n <style>${baseStyles}</style>\n <script>\n window.onload = function() {\n const url = ${JSON.stringify(redirectUrl)};\n ${closeScript}\n window.location.href = url;\n };\n </script>\n</head>\n<body>\n <div class=\"card\">\n <h1>${escapeHtml(title)}</h1>\n <p>${escapeHtml(message)}</p>\n </div>\n</body>\n</html>`\n}\n\nexport function htmlResponse(html: string, status = 200): Response {\n return new Response(html, {\n status,\n headers: { 'Content-Type': 'text/html; charset=utf-8' },\n })\n}\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n}\n\nfunction renderAutoCloseScript(redirectTargetExpression: string, autoClose: boolean): string {\n if (!autoClose)\n return ''\n\n return `\n (function() {\n let closeScheduled = false;\n const closeWindow = function() {\n window.close();\n };\n const scheduleClose = function(delay) {\n if (closeScheduled)\n return;\n closeScheduled = true;\n setTimeout(closeWindow, delay);\n };\n\n let protocol = '';\n try {\n protocol = new URL(${redirectTargetExpression}, window.location.href).protocol;\n }\n catch {}\n\n const isWebProtocol = protocol === 'http:' || protocol === 'https:';\n if (isWebProtocol) {\n scheduleClose(500);\n return;\n }\n\n const closeAfterHandoff = function() {\n if (document.visibilityState === 'hidden')\n scheduleClose(200);\n };\n const closeAfterBlurFallback = function() {\n setTimeout(function() {\n if (closeScheduled)\n return;\n if (typeof document.hasFocus === 'function' && !document.hasFocus())\n scheduleClose(200);\n }, 700);\n };\n const closeAfterInitialDelay = function() {\n setTimeout(function() {\n if (closeScheduled)\n return;\n if (typeof document.hasFocus === 'function' && !document.hasFocus())\n scheduleClose(0);\n }, 500);\n };\n\n document.addEventListener('visibilitychange', closeAfterHandoff);\n window.addEventListener('pagehide', function() {\n scheduleClose(0);\n }, { once: true });\n window.addEventListener('blur', closeAfterBlurFallback, { once: true });\n closeAfterInitialDelay();\n })();\n `\n}\n","import { BROWSER } from 'esm-env'\n\nexport const SESSION_TOKEN_KEY = '__gau-session-token'\n\nexport const REFRESHED_TOKEN_HEADER = 'X-Refreshed-Token'\n\nexport function storeSessionToken(token: string) {\n if (!BROWSER)\n return\n try {\n localStorage.setItem(SESSION_TOKEN_KEY, token)\n }\n catch {}\n}\n\nexport function getSessionToken(): string | null {\n if (!BROWSER)\n return null\n try {\n return localStorage.getItem(SESSION_TOKEN_KEY)\n }\n catch {\n return null\n }\n}\n\nexport function clearSessionToken() {\n if (!BROWSER)\n return\n try {\n localStorage.removeItem(SESSION_TOKEN_KEY)\n }\n catch {}\n}\n\nexport function handleRefreshedToken(response: Response): void {\n if (!BROWSER)\n return\n const refreshed = response.headers.get(REFRESHED_TOKEN_HEADER)\n if (refreshed)\n storeSessionToken(refreshed)\n}\n\nexport async function generatePKCE() {\n if (!BROWSER || !window.crypto || !window.crypto.subtle)\n throw new Error('PKCE relies on window.crypto, which is not available in this environment.')\n\n function base64UrlEncode(array: Uint8Array): string {\n return btoa(String.fromCharCode(...array))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '')\n }\n\n const verifierLength = 43\n const randomValues = new Uint8Array(verifierLength)\n window.crypto.getRandomValues(randomValues)\n const codeVerifier = base64UrlEncode(randomValues)\n\n const encoder = new TextEncoder()\n const data = encoder.encode(codeVerifier)\n const hash = await window.crypto.subtle.digest('SHA-256', data)\n const codeChallenge = base64UrlEncode(new Uint8Array(hash))\n\n return { codeVerifier, codeChallenge }\n}\n","import type { ProfileName, ProviderIds } from '../../core'\nimport { BROWSER } from 'esm-env'\nimport { generatePKCE, getSessionToken } from '../../client/token'\n\nexport function isTauri(): boolean {\n return BROWSER && '__TAURI_INTERNALS__' in globalThis\n}\n\nfunction resolveOrigin(baseUrl: string): string | null {\n try {\n return new URL(baseUrl).origin\n }\n catch {\n if (BROWSER && typeof window !== 'undefined') {\n try {\n return new URL(baseUrl, window.location.origin).origin\n }\n catch {\n return null\n }\n }\n return null\n }\n}\n\nexport async function signInWithTauri<const TAuth = unknown, P extends ProviderIds<TAuth> = ProviderIds<TAuth>, PR extends (ProfileName<TAuth, P> | string) | undefined = undefined>(\n provider: P,\n baseUrl: string,\n scheme: string = 'gau',\n redirectOverride?: string,\n profile?: PR,\n) {\n if (!isTauri())\n return\n\n const { openUrl } = await import('@tauri-apps/plugin-opener')\n\n function resolveAbsoluteBase(base: string): string {\n try {\n const u = new URL(base)\n return u.toString().replace(/\\/$/, '')\n }\n catch {\n if (BROWSER && typeof window !== 'undefined') {\n try {\n const u = new URL(base, window.location.origin)\n return u.toString().replace(/\\/$/, '')\n }\n catch {\n return base\n }\n }\n return base\n }\n }\n\n let redirectTo: string\n\n if (redirectOverride)\n redirectTo = redirectOverride\n else\n redirectTo = `${scheme}://oauth/callback`\n\n const { codeVerifier, codeChallenge } = await generatePKCE()\n localStorage.setItem('gau-pkce-verifier', codeVerifier)\n\n const params = new URLSearchParams()\n params.set('redirectTo', redirectTo)\n if (profile)\n params.set('profile', String(profile))\n params.set('code_challenge', codeChallenge)\n const resolvedBase = resolveAbsoluteBase(baseUrl)\n const authUrl = `${resolvedBase}/${provider}?${params.toString()}`\n await openUrl(authUrl)\n}\n\nexport async function setupTauriListener(\n handler: (url: string) => Promise<void>,\n): Promise<(() => void) | void> {\n if (!isTauri())\n return\n\n const { listen } = await import('@tauri-apps/api/event')\n try {\n const unlisten = await listen<string>('deep-link', async (event) => {\n await handler(event.payload)\n })\n return unlisten\n }\n catch (err) {\n console.error(err)\n }\n}\n\nexport async function handleTauriDeepLink(url: string, baseUrl: string, scheme: string, onToken: (token: string) => void) {\n const parsed = new URL(url)\n const baseOrigin = resolveOrigin(baseUrl)\n if (parsed.protocol !== `${scheme}:` && (!baseOrigin || parsed.origin !== baseOrigin))\n return\n\n const queryParams = new URLSearchParams(parsed.search)\n const code = queryParams.get('code')\n if (code) {\n const verifier = localStorage.getItem('gau-pkce-verifier')\n if (!verifier) {\n console.error('No PKCE verifier found')\n return\n }\n localStorage.removeItem('gau-pkce-verifier')\n\n try {\n const res = await fetch(`${baseUrl}/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ code, codeVerifier: verifier }),\n })\n if (res.ok) {\n const data = await res.json()\n if (data.token)\n onToken(data.token)\n }\n else {\n console.error('Failed to exchange code for token')\n }\n }\n catch (e) {\n console.error('Error exchanging code for token:', e)\n }\n }\n}\n\nexport async function linkAccountWithTauri<const TAuth = unknown, P extends ProviderIds<TAuth> = ProviderIds<TAuth>, PR extends (ProfileName<TAuth, P> | string) | undefined = undefined>(\n provider: P,\n baseUrl: string,\n scheme: string = 'gau',\n redirectOverride?: string,\n profile?: PR,\n) {\n if (!isTauri())\n return\n\n const { openUrl } = await import('@tauri-apps/plugin-opener')\n\n let redirectTo: string\n\n if (redirectOverride)\n redirectTo = redirectOverride\n else\n redirectTo = `${scheme}://oauth/callback`\n\n const token = getSessionToken()\n if (!token) {\n console.error('No session token found, cannot link account.')\n return\n }\n\n const params = new URLSearchParams()\n params.set('redirectTo', redirectTo)\n params.set('token', token)\n if (profile)\n params.set('profile', String(profile))\n const resolvedBase = (() => {\n try {\n const u = new URL(baseUrl)\n return u.toString().replace(/\\/$/, '')\n }\n catch {\n if (BROWSER && typeof window !== 'undefined') {\n try {\n const u = new URL(baseUrl, window.location.origin)\n return u.toString().replace(/\\/$/, '')\n }\n catch {\n return baseUrl\n }\n }\n return baseUrl\n }\n })()\n const linkUrl = `${resolvedBase}/link/${provider}?${params.toString()}`\n await openUrl(linkUrl)\n}\n\nexport async function startAuthBridge(\n baseUrl: string,\n scheme: string,\n onToken: (token: string) => Promise<void> | void,\n): Promise<(() => void) | void> {\n if (!isTauri())\n return\n\n const unlisten = await setupTauriListener(async (url) => {\n handleTauriDeepLink(url, baseUrl, scheme, onToken)\n })\n return unlisten\n}\n","import type { GauSession, ProfileName, ProviderIds } from '../../core'\n// @ts-expect-error svelte-kit\nimport { replaceState } from '$app/navigation'\nimport { BROWSER } from 'esm-env'\nimport { getContext, onMount, setContext } from 'svelte'\nimport { NULL_SESSION } from '../../core'\nimport { isTauri } from '../../runtimes/tauri'\nimport { createAuthClient } from '../vanilla'\n\ninterface AuthContextValue<TAuth = unknown> {\n session: GauSession<ProviderIds<TAuth>>\n isLoading: boolean\n signIn: <P extends ProviderIds<TAuth>>(provider: P, options?: { redirectTo?: string, profile?: ProfileName<TAuth, P> }) => Promise<void>\n linkAccount: <P extends ProviderIds<TAuth>>(provider: P, options?: { redirectTo?: string, profile?: ProfileName<TAuth, P> }) => Promise<void>\n unlinkAccount: (provider: ProviderIds<TAuth>) => Promise<void>\n signOut: () => Promise<void>\n refresh: () => Promise<void>\n fetch: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>\n}\n\nconst AUTH_CONTEXT_KEY = Symbol('gau-auth')\n\nexport function createSvelteAuth<const TAuth = unknown>({\n baseUrl = '/api/auth',\n scheme = 'gau',\n redirectTo: defaultRedirectTo,\n session: initialSession,\n}: {\n baseUrl?: string\n scheme?: string\n redirectTo?: string\n session?: GauSession<ProviderIds<TAuth>>\n} = {}) {\n type CurrentSession = GauSession<ProviderIds<TAuth>>\n\n const client = createAuthClient<TAuth>({\n baseUrl,\n })\n\n const fetchSession = async (): Promise<CurrentSession> => {\n if (!BROWSER)\n return { ...NULL_SESSION, providers: [] }\n return client.refreshSession()\n }\n\n let session: CurrentSession = $state(initialSession ?? { ...NULL_SESSION, providers: [] })\n let isLoading = $state(!initialSession)\n\n async function replaceUrlSafe(url: string) {\n try {\n replaceState(url, {})\n }\n catch {\n if (BROWSER)\n window.history.replaceState(null, '', url)\n }\n }\n\n async function signIn<P extends ProviderIds<TAuth>>(provider: P, { redirectTo, profile }: { redirectTo?: string, profile?: ProfileName<TAuth, P> } = {}) {\n const inTauri = isTauri()\n let finalRedirectTo = redirectTo ?? defaultRedirectTo\n\n if (inTauri) {\n const { signInWithTauri } = await import('../../runtimes/tauri')\n await signInWithTauri<TAuth, P, typeof profile>(provider, baseUrl, scheme, finalRedirectTo, profile)\n return\n }\n\n if (!finalRedirectTo && BROWSER)\n finalRedirectTo = window.location.origin\n\n const url = await client.signIn<P, typeof profile>(provider, { redirectTo: finalRedirectTo, profile })\n if (BROWSER)\n window.location.href = url\n }\n\n async function linkAccount<P extends ProviderIds<TAuth>>(provider: P, { redirectTo, profile }: { redirectTo?: string, profile?: ProfileName<TAuth, P> } = {}) {\n if (isTauri()) {\n const { linkAccountWithTauri } = await import('../../runtimes/tauri')\n await linkAccountWithTauri<TAuth, P, typeof profile>(provider, baseUrl, scheme, redirectTo, profile)\n return\n }\n\n let finalRedirectTo = redirectTo ?? defaultRedirectTo\n if (!finalRedirectTo && BROWSER)\n finalRedirectTo = window.location.href\n\n const url = await client.linkAccount<P, typeof profile>(provider, { redirectTo: finalRedirectTo, profile })\n if (BROWSER)\n window.location.href = url\n }\n\n async function unlinkAccount(provider: ProviderIds<TAuth>) {\n const ok = await client.unlinkAccount(provider)\n if (ok)\n session = await fetchSession()\n else\n console.error('Failed to unlink account')\n }\n\n async function signOut() {\n await client.signOut()\n session = await fetchSession()\n }\n\n onMount(() => {\n if (!BROWSER)\n return\n\n void (async () => {\n const handled = await client.handleRedirectCallback(async url => replaceUrlSafe(url))\n if (!handled)\n session = await fetchSession()\n\n isLoading = false\n })()\n\n let cleanup: (() => void) | void\n let disposed = false\n\n if (!isTauri())\n return\n\n void (async () => {\n const { startAuthBridge } = await import('../../runtimes/tauri')\n const unlisten = await startAuthBridge(baseUrl, scheme, async (token) => {\n await client.applySessionToken(token)\n session = await fetchSession()\n })\n if (disposed)\n unlisten?.()\n else\n cleanup = unlisten\n })()\n\n return () => {\n disposed = true\n cleanup?.()\n }\n })\n\n const contextValue: AuthContextValue<TAuth> = {\n get session() {\n return session\n },\n get isLoading() {\n return isLoading\n },\n signIn,\n linkAccount,\n unlinkAccount,\n signOut,\n refresh: async () => { session = await fetchSession() },\n fetch: client.fetch,\n }\n\n setContext(AUTH_CONTEXT_KEY, contextValue)\n}\n\nexport function useAuth<const TAuth = unknown>(): AuthContextValue<TAuth> {\n const context = getContext<AuthContextValue<TAuth>>(AUTH_CONTEXT_KEY)\n if (!context)\n throw new Error('useAuth must be used within an AuthProvider')\n\n return context\n}\n","import type { SerializeOptions } from 'cookie'\nimport { parse, serialize } from 'cookie'\n\nexport const DEFAULT_COOKIE_SERIALIZE_OPTIONS: SerializeOptions = {\n path: '/',\n sameSite: 'lax',\n secure: true,\n httpOnly: true,\n}\n\nexport type Cookie = [string, string, SerializeOptions]\n\nexport function parseCookies(cookieHeader: string | null | undefined): Map<string, string> {\n const cookies = new Map<string, string>()\n if (cookieHeader) {\n const parsed = parse(cookieHeader)\n for (const name in parsed)\n cookies.set(name, parsed[name]!)\n }\n return cookies\n}\n\nexport class Cookies {\n #new: Cookie[] = []\n\n constructor(\n private readonly requestCookies: Map<string, string>,\n private readonly defaultOptions: SerializeOptions,\n ) {}\n\n get(name: string): string | undefined {\n return this.requestCookies.get(name)\n }\n\n set(name: string, value: string, options?: SerializeOptions): void {\n const combinedOptions = { ...this.defaultOptions, ...options }\n this.#new.push([name, value, combinedOptions])\n }\n\n delete(name: string, options?: Omit<SerializeOptions, 'expires' | 'maxAge'>): void {\n this.set(name, '', { ...options, expires: new Date(0), maxAge: 0 })\n }\n\n toHeaders(): Headers {\n const headers = new Headers()\n for (const [name, value, options] of this.#new)\n headers.append('Set-Cookie', serialize(name, value, options))\n\n return headers\n }\n}\n\nexport const CSRF_COOKIE_NAME = '__gau-csrf-token'\nexport const SESSION_COOKIE_NAME = '__gau-session-token'\nexport const SESSION_STASH_COOKIE_NAME = '__gau-session-stash'\nexport const SESSION_STRATEGY_COOKIE_NAME = '__gau-session-strategy'\nexport const LINKING_TOKEN_COOKIE_NAME = '__gau-linking-token'\nexport const PKCE_COOKIE_NAME = '__gau-pkce-code-verifier'\nexport const CALLBACK_URI_COOKIE_NAME = '__gau-callback-uri'\nexport const PROVIDER_OPTIONS_COOKIE_NAME = '__gau-provider-options'\nexport const CLIENT_CHALLENGE_COOKIE_NAME = '__gau-client-challenge'\n\nexport const CSRF_MAX_AGE = 60 * 10 // 10 minutes\n","import type { OAuth2Tokens } from 'arctic'\nimport type { SerializeOptions } from 'cookie'\nimport type { SignOptions, VerifyOptions } from '../jwt'\nimport type { AuthUser, OAuthProvider, OAuthProviderConfig, ProviderProfileOverrides } from '../oauth'\nimport type { Cookies } from './cookies'\nimport type { Adapter, GauServerSession } from './index'\nimport { parse, serialize } from 'cookie'\nimport { sign, verify } from '../jwt'\nimport { DEFAULT_COOKIE_SERIALIZE_OPTIONS, SESSION_COOKIE_NAME, SESSION_STASH_COOKIE_NAME } from './cookies'\nimport { AuthError, ErrorCodes, GauError } from './index'\nimport { getSessionTokenFromRequest } from './utils'\n\ntype ProviderId<P> = P extends OAuthProvider<infer T> ? T : never\nexport type ProviderIds<T> = T extends { providerMap: Map<infer K extends string, any> } ? K : string\n\nexport type ProfileName<T, P extends string> = T extends { profiles: infer R }\n ? P extends keyof R\n ? keyof R[P]\n : never\n : never\n\nexport interface ImpersonationConfig {\n enabled: boolean\n /** Roles that can impersonate others. Defaults to adminRoles from roles config. */\n allowedRoles?: string[]\n /** Roles that cannot be impersonated. Defaults to adminRoles from roles config. */\n cannotImpersonate?: string[]\n /** Maximum impersonation duration in seconds. Defaults to 3600 (1 hour). */\n maxTTL?: number\n /**\n * Hook called when impersonation starts.\n * Use this to log impersonation events.\n */\n onImpersonate?: (context: {\n adminUserId: string\n targetUserId: string\n reason?: string\n timestamp: number\n }) => void | Promise<void>\n}\n\ntype ResolvedImpersonationConfig = Required<Pick<ImpersonationConfig, 'enabled' | 'allowedRoles' | 'cannotImpersonate' | 'maxTTL'>> & Pick<ImpersonationConfig, 'onImpersonate'>\n\nexport interface StartImpersonationOptions {\n /** Session duration in seconds (capped by maxTTL). */\n ttl?: number\n /** Reason for impersonation, passed to onImpersonate hook. */\n reason?: string\n}\n\nexport interface ImpersonationResult {\n /** The impersonation session JWT. */\n token: string\n /** Set-Cookie header for the impersonation session. */\n cookie: string\n /** Set-Cookie header for stashing the admin's original session. */\n originalCookie: string\n /** The maxAge in seconds. */\n maxAge: number\n}\n\nexport interface EndImpersonationResult {\n /** The restored admin session token. */\n token: string\n /** Set-Cookie header for restoring the admin session. */\n cookie: string\n /** Array of Set-Cookie headers to clear the stash cookie. */\n clearCookies: string[]\n}\n\nexport interface CreateAuthOptions<TProviders extends OAuthProvider[]> {\n /** The database adapter to use for storing users and accounts. */\n adapter: Adapter\n /** Array of OAuth providers to support. */\n providers: TProviders\n /** Base path for authentication routes (defaults to '/api/auth'). */\n basePath?: string\n /** Session management options */\n session?: {\n /** Strategy to use for sessions: 'auto' (default), 'cookie', or 'token'. */\n strategy?: 'auto' | 'cookie' | 'token'\n }\n /** Configuration for JWT signing and verification. */\n jwt?: {\n /** Signing algorithm: 'ES256' (default) or 'HS256'. */\n algorithm?: 'ES256' | 'HS256'\n /** Secret for HS256 or base64url-encoded private key for ES256 (overrides AUTH_SECRET). */\n secret?: string\n /** Issuer claim (iss) for JWTs. */\n iss?: string\n /** Audience claim (aud) for JWTs. */\n aud?: string\n /** Default time-to-live in seconds for JWTs (defaults to 1 day). */\n ttl?: number\n }\n /** Custom options for session cookies. */\n cookies?: Partial<SerializeOptions>\n /**\n * Hook that fires right after provider.validateCallback() returns tokens,\n * but before any user lookup/link/create logic. Return { handled: true, response }\n * to short-circuit the default flow and send a custom response.\n */\n onOAuthExchange?: (context: {\n request: Request\n providerId: string\n state: string\n code: string\n codeVerifier: string\n callbackUri?: string | null\n redirectTo: string\n cookies: Cookies\n providerUser: AuthUser\n tokens: OAuth2Tokens\n isLinking: boolean\n sessionUserId?: string\n }) => Promise<{ handled: true, response: Response } | { handled: false }>\n /** Map/override the provider's profile right after token exchange. */\n mapExternalProfile?: (context: {\n request: Request\n providerId: string\n providerUser: AuthUser\n tokens: OAuth2Tokens\n isLinking: boolean\n }) => Promise<AuthUser | Partial<AuthUser> | null | undefined>\n /** Gate the link action just before persisting an account. */\n onBeforeLinkAccount?: (context: {\n request: Request\n providerId: string\n userId: string\n providerUser: AuthUser\n tokens: OAuth2Tokens\n }) => Promise<{ allow: true } | { allow: false, response?: Response }>\n /** Observe or augment after link/update tokens. */\n onAfterLinkAccount?: (context: {\n request: Request\n providerId: string\n userId: string\n providerUser: AuthUser\n tokens: OAuth2Tokens\n action: 'link' | 'update'\n }) => Promise<void>\n /** Trusted hosts for CSRF protection: 'all' or array of hostnames (defaults to []). */\n trustHosts?: 'all' | string[]\n /** Account linking behavior: 'verifiedEmail' (default), 'always', or false. */\n autoLink?: 'verifiedEmail' | 'always' | false\n /** Allow linking providers whose primary emails differ from the user's current primary email. Defaults to true. */\n allowDifferentEmails?: boolean\n /** When linking a new provider, update missing user info (name/image/emailVerified) from provider profile. Defaults to false. */\n updateUserInfoOnLink?: boolean\n /** Optional configuration for role-based access control. */\n roles?: {\n /** Default role for newly created users. */\n defaultRole?: string\n /** Dynamically resolve the role at the moment of user creation. Return undefined to fall back to defaultRole. */\n resolveOnCreate?: (context: { providerId: string, profile: any, request: Request }) => string | undefined\n /** Roles that are considered admin-like for helper predicates and `session.user.isAdmin`. */\n adminRoles?: string[]\n /** Users that are always treated as admin for helper predicates and `session.user.isAdmin`. */\n adminUserIds?: string[]\n }\n /**\n * CORS configuration. When true (default): request Origin & allow credentials\n * When false, CORS headers are not added at all.\n * Provide an object to fine-tune behaviour.\n */\n cors?: true | false | {\n /**\n * Allowed origins.\n * - 'all' (default) allows any origin (reflected when credentials enabled),\n * - 'trust' reuses the createAuth trustHosts list\n * - specify an explicit array of full origins (e.g. https://app.example.com)\n * or hostnames (e.g. app.example.com).\n * When array contains '*', it's treated as 'all'.\n */\n allowedOrigins?: 'all' | 'trust' | string[]\n /** Whether to send Access-Control-Allow-Credentials (defaults to true). */\n allowCredentials?: boolean\n /** Allowed headers (defaults to ['Content-Type','Authorization','Cookie']). */\n allowedHeaders?: string[]\n /** Allowed methods (defaults to ['GET','POST','OPTIONS']). */\n allowedMethods?: string[]\n /** Exposed headers (optional). */\n exposeHeaders?: string[]\n /** Preflight max age in seconds (optional). */\n maxAge?: number\n }\n /**\n * Named, server-defined profiles that group provider specific settings.\n * Clients can reference a profile by name (e.g. signIn('github', { profile: 'myprofile' })).\n */\n profiles?: ProfilesConfig<TProviders>\n /**\n * Custom error handler. Return a Response to override default behavior.\n * Return undefined to use default handling (errorRedirect or HTML page).\n *\n * @example\n * onError: ({ error, request }) => {\n * console.error('Auth error:', error.code, error.message)\n * if (error.code === 'CSRF_INVALID') {\n * return redirect('/login?error=session_expired')\n * }\n * }\n */\n onError?: (context: {\n error: GauError\n request: Request\n }) => Response | Promise<Response | undefined> | undefined\n /**\n * URL to redirect user-facing errors to.\n * Error details passed as query params: ?code=...&message=...&status=...&redirect=...\n *\n * If not set, gau renders its built-in error page for user-facing errors,\n * or returns JSON for API errors.\n *\n * @example\n * errorRedirect: '/auth/error' // Your custom error page\n */\n errorRedirect?: string\n /**\n * User impersonation.\n * When enabled, admins can impersonate other users for support/debugging.\n */\n impersonation?: ImpersonationConfig\n}\n\nexport interface IssueSessionOptions {\n /** Custom claims to include in the session JWT. */\n data?: Record<string, unknown>\n /** Time-to-live in seconds (defaults to auth's configured jwt.ttl). */\n ttl?: number\n}\n\nexport interface IssueSessionResult {\n /** The raw JWT session token (for Bearer auth or storage). */\n token: string\n /** The serialized Set-Cookie header value (for web apps). */\n cookie: string\n /** The cookie name used by gau. */\n cookieName: string\n /** The maxAge in seconds. */\n maxAge: number\n}\n\nexport interface RefreshSessionOptions {\n /** Override the default TTL for the new token. */\n ttl?: number\n /**\n * Only refresh if past this fraction of TTL (0-1).\n * Example: 0.5 means only refresh if session is past 50% of its lifetime.\n */\n threshold?: number\n}\n\nexport interface RefreshSessionResult extends IssueSessionResult {\n /**\n * How the original token was provided.\n * - 'cookie': Token was extracted from the Cookie header\n * - 'bearer': Token was extracted from Authorization: Bearer header\n * - 'token': A raw token string was passed directly\n */\n source: 'cookie' | 'bearer' | 'token'\n}\n\nexport type Auth<TProviders extends OAuthProvider[] = any> = Adapter & {\n providerMap: Map<ProviderId<TProviders[number]>, TProviders[number]>\n basePath: string\n cookieOptions: SerializeOptions\n jwt: { ttl: number }\n onOAuthExchange?: CreateAuthOptions<TProviders>['onOAuthExchange']\n mapExternalProfile?: CreateAuthOptions<TProviders>['mapExternalProfile']\n onBeforeLinkAccount?: CreateAuthOptions<TProviders>['onBeforeLinkAccount']\n onAfterLinkAccount?: CreateAuthOptions<TProviders>['onAfterLinkAccount']\n signJWT: <U extends Record<string, unknown>>(payload: U, customOptions?: Partial<SignOptions>) => Promise<string>\n verifyJWT: <U = Record<string, unknown>>(token: string, customOptions?: Partial<VerifyOptions>) => Promise<U | null>\n createSession: (userId: string, data?: Record<string, unknown>, ttl?: number) => Promise<string>\n validateSession: (token: string) => Promise<GauServerSession | null>\n /**\n * Issue a session for a user, returning both the token and a Set-Cookie header.\n * Useful for guest login, invite redemption, admin impersonation, etc.\n */\n issueSession: (userId: string, options?: IssueSessionOptions) => Promise<IssueSessionResult>\n /**\n * Refresh an existing session, issuing a new token with extended TTL.\n * Preserves custom claims from the original token.\n *\n * @param tokenOrRequest - The existing session token, or a Request to extract the token from\n * @param options.ttl - Override the default TTL for the new token\n * @param options.threshold - Only refresh if past this fraction of TTL (0-1).\n * When set, returns null if below threshold.\n * Example: 0.5 means only refresh if session is past 50% of its lifetime.\n * @returns The refreshed session with source info, or null if invalid/expired/below threshold\n */\n refreshSession: (tokenOrRequest: string | Request, options?: RefreshSessionOptions) => Promise<RefreshSessionResult | null>\n /**\n * Get a valid access token for a linked provider. If the stored token is expired and a refresh token exists,\n * this will refresh it using the provider's refreshAccessToken and persist rotated tokens.\n */\n getAccessToken: (userId: string, providerId: string) => Promise<{ accessToken: string, expiresAt?: number | null } | null>\n /**\n * Start impersonating a target user.\n * Requires impersonation to be enabled and the admin user to have appropriate permissions.\n *\n * @param adminUserId - The ID of the user initiating impersonation (must have allowed role)\n * @param targetUserId - The ID of the user to impersonate\n * @param options - Optional configuration for the impersonation session\n * @returns ImpersonationResult with tokens and cookies, or null if impersonation is not allowed\n */\n startImpersonation: (adminUserId: string, targetUserId: string, options?: StartImpersonationOptions) => Promise<ImpersonationResult | null>\n /**\n * End an active impersonation session and restore the admin's original session.\n * Extracts the stashed session from the request cookies.\n *\n * @param request - The request containing the stashed session cookie\n * @returns EndImpersonationResult with restored session, or null if no stash found\n */\n endImpersonation: (request: Request) => Promise<EndImpersonationResult | null>\n trustHosts: 'all' | string[]\n autoLink: 'verifiedEmail' | 'always' | false\n allowDifferentEmails: boolean\n updateUserInfoOnLink: boolean\n sessionStrategy: 'auto' | 'cookie' | 'token'\n development: boolean\n roles: {\n defaultRole: string\n resolveOnCreate?: (context: { providerId: string, profile: any, request: Request }) => string | undefined\n adminRoles: string[]\n adminUserIds: string[]\n }\n cors: false | {\n allowedOrigins: 'all' | 'trust' | string[]\n allowCredentials: boolean\n allowedHeaders: string[]\n allowedMethods: string[]\n exposeHeaders?: string[]\n maxAge?: number\n }\n profiles: ResolvedProfiles<TProviders>\n onError?: CreateAuthOptions<TProviders>['onError']\n errorRedirect?: string\n impersonation: ImpersonationConfig | null\n}\n\nexport interface ProfileDefinition {\n scopes?: string[]\n redirectUri?: string\n /** When true, this profile can only be linked to an existing session; standalone sign-in is disabled. */\n linkOnly?: boolean\n /** Additional provider-specific authorization params. */\n params?: Record<string, string>\n}\n\ntype ProviderIdOfArray<TProviders extends OAuthProvider[]> = ProviderId<TProviders[number]>\ntype ProviderConfigFor<TProviders extends OAuthProvider[], K extends string>\n = Extract<TProviders[number], OAuthProvider<K, any>> extends OAuthProvider<any, infer C> ? C : OAuthProviderConfig\n\nexport type ProfilesConfig<TProviders extends OAuthProvider[]> = Partial<{\n [K in ProviderIdOfArray<TProviders>]: Record<string, ProfileDefinition & ProviderProfileOverrides<ProviderConfigFor<TProviders, K>>>\n}>\nexport type ResolvedProfiles<TProviders extends OAuthProvider[]> = ProfilesConfig<TProviders>\n\nexport function createAuth<const TProviders extends OAuthProvider[]>({\n adapter,\n providers,\n basePath = '/api/auth',\n jwt: jwtConfig = {},\n session: sessionConfig = {},\n cookies: cookieConfig = {},\n onOAuthExchange,\n mapExternalProfile,\n onBeforeLinkAccount,\n onAfterLinkAccount,\n trustHosts = [],\n autoLink = 'verifiedEmail',\n allowDifferentEmails = true,\n updateUserInfoOnLink = false,\n roles: rolesConfig = {},\n cors = true,\n profiles: profilesConfig,\n onError,\n errorRedirect,\n impersonation: impersonationConfig,\n}: CreateAuthOptions<TProviders>): Auth<TProviders> {\n const { algorithm = 'ES256', secret, iss, aud, ttl: defaultTTL = 3600 * 24 * 7 } = jwtConfig\n const cookieOptions = { ...DEFAULT_COOKIE_SERIALIZE_OPTIONS, ...cookieConfig }\n\n const sessionStrategy: 'auto' | 'cookie' | 'token' = sessionConfig.strategy ?? 'auto'\n\n if (algorithm === 'ES256' && secret !== undefined && typeof secret !== 'string')\n throw new AuthError('For ES256, the secret option must be a string.')\n\n const providerMap = new Map(providers.map(p => [p.id, p]))\n\n const resolvedCors: Auth['cors'] = cors === false\n ? false\n : {\n allowedOrigins: (cors === true ? 'all' : cors.allowedOrigins) ?? 'all',\n allowCredentials: (cors === true ? true : cors.allowCredentials) ?? true,\n allowedHeaders: (cors === true ? undefined : cors.allowedHeaders) ?? ['Content-Type', 'Authorization', 'Cookie'],\n allowedMethods: (cors === true ? undefined : cors.allowedMethods) ?? ['GET', 'POST', 'OPTIONS'],\n exposeHeaders: cors === true ? undefined : cors.exposeHeaders,\n maxAge: cors === true ? undefined : cors.maxAge,\n }\n\n const resolvedProfiles = (profilesConfig ?? {}) as ResolvedProfiles<TProviders>\n const resolvedRoles = {\n defaultRole: rolesConfig.defaultRole ?? 'user',\n resolveOnCreate: rolesConfig.resolveOnCreate,\n adminRoles: rolesConfig.adminRoles ?? ['admin'],\n adminUserIds: rolesConfig.adminUserIds ?? [],\n }\n\n const resolvedImpersonation: ResolvedImpersonationConfig | null = impersonationConfig?.enabled\n ? {\n enabled: true,\n allowedRoles: impersonationConfig.allowedRoles ?? resolvedRoles.adminRoles,\n cannotImpersonate: impersonationConfig.cannotImpersonate ?? resolvedRoles.adminRoles,\n maxTTL: impersonationConfig.maxTTL ?? 3600,\n onImpersonate: impersonationConfig.onImpersonate,\n }\n : null\n\n function buildSignOptions(custom: Partial<SignOptions> = {}): SignOptions {\n const base = { ttl: custom.ttl, iss: custom.iss ?? iss, aud: custom.aud ?? aud, sub: custom.sub }\n if (algorithm === 'HS256') {\n return { algorithm, secret: custom.secret ?? secret, ...base }\n }\n else {\n if (custom.secret !== undefined && typeof custom.secret !== 'string')\n throw new AuthError('For ES256, the secret option must be a string.')\n const esSecret = custom.secret ?? secret\n return { algorithm, privateKey: custom.privateKey, secret: esSecret, ...base }\n }\n }\n\n function buildVerifyOptions(custom: Partial<VerifyOptions> = {}): VerifyOptions {\n const base = { iss: custom.iss ?? iss, aud: custom.aud ?? aud }\n if (algorithm === 'HS256') {\n return { algorithm, secret: custom.secret ?? secret, ...base }\n }\n else {\n if (custom.secret !== undefined && typeof custom.secret !== 'string')\n throw new AuthError('For ES256, the secret option must be a string.')\n const esSecret = custom.secret ?? secret\n return { algorithm, publicKey: custom.publicKey, secret: esSecret, ...base }\n }\n }\n\n async function signJWT<U extends Record<string, unknown>>(payload: U, customOptions: Partial<SignOptions> = {}): Promise<string> {\n return sign(payload, buildSignOptions(customOptions))\n }\n\n async function verifyJWT<U = Record<string, unknown>>(token: string, customOptions: Partial<VerifyOptions> = {}): Promise<U | null> {\n const options = buildVerifyOptions(customOptions)\n try {\n return await verify<U>(token, options)\n }\n catch {\n return null\n }\n }\n\n async function createSession(userId: string, data: Record<string, unknown> = {}, ttl = defaultTTL): Promise<string> {\n const payload = { sub: userId, ...data }\n return signJWT(payload, { ttl })\n }\n\n async function issueSession(userId: string, options: IssueSessionOptions = {}): Promise<IssueSessionResult> {\n const { data = {}, ttl = defaultTTL } = options\n const token = await createSession(userId, data, ttl)\n\n const cookieOpts: SerializeOptions = {\n ...cookieOptions,\n maxAge: ttl,\n }\n\n const cookie = serialize(SESSION_COOKIE_NAME, token, cookieOpts)\n\n return {\n token,\n cookie,\n cookieName: SESSION_COOKIE_NAME,\n maxAge: ttl,\n }\n }\n\n async function refreshSession(tokenOrRequest: string | Request, options: RefreshSessionOptions = {}): Promise<RefreshSessionResult | null> {\n let token: string | undefined\n let source: RefreshSessionResult['source']\n\n if (typeof tokenOrRequest === 'string') {\n token = tokenOrRequest\n source = 'token'\n }\n else {\n const extracted = getSessionTokenFromRequest(tokenOrRequest)\n if (!extracted.token || !extracted.source)\n return null\n token = extracted.token\n source = extracted.source\n }\n\n const payload = await verifyJWT<{ sub: string, iat?: number } & Record<string, unknown>>(token)\n if (!payload || !payload.sub)\n return null\n\n if (options.threshold != null && options.threshold > 0 && options.threshold < 1) {\n const { iat } = payload\n if (iat) {\n const now = Math.floor(Date.now() / 1000)\n const sessionAge = now - iat\n const ttl = options.ttl ?? defaultTTL\n const thresholdSeconds = ttl * options.threshold\n\n if (sessionAge < thresholdSeconds)\n return null\n }\n }\n\n const user = await adapter.getUser(payload.sub)\n if (!user)\n return null\n const { sub, iat, exp, iss, aud, nbf, jti, ...customClaims } = payload\n\n const result = await issueSession(payload.sub, {\n data: customClaims,\n ttl: options.ttl,\n })\n\n return { ...result, source }\n }\n\n async function validateSession(token: string): Promise<GauServerSession | null> {\n const payload = await verifyJWT<{ sub: string } & Record<string, unknown>>(token)\n if (!payload)\n return null\n\n const userAndAccounts = await adapter.getUserAndAccounts(payload.sub)\n if (!userAndAccounts)\n return null\n\n const { user, accounts } = userAndAccounts\n const isAdmin = Boolean(\n user\n && (\n (user.role && resolvedRoles.adminRoles.includes(user.role))\n || (resolvedRoles.adminUserIds.length > 0 && resolvedRoles.adminUserIds.includes(user.id))\n ),\n )\n const sessionUser = user ? { ...user, isAdmin } : null\n\n return { user: sessionUser, session: { id: token, ...payload }, accounts }\n }\n\n async function getAccessToken(userId: string, providerId: string) {\n const provider = providerMap.get(providerId)\n if (!provider)\n return null\n\n const accounts = await adapter.getAccounts(userId)\n const account = accounts.find(a => a.provider === providerId)\n if (!account || !account.accessToken)\n return null\n\n const now = Math.floor(Date.now() / 1000)\n const isExpired = typeof account.expiresAt === 'number' ? account.expiresAt <= now : false\n\n if (!isExpired)\n return { accessToken: account.accessToken, expiresAt: account.expiresAt ?? null }\n\n if (!account.refreshToken || !provider.refreshAccessToken)\n return null\n\n try {\n const refreshed = await provider.refreshAccessToken(account.refreshToken, {})\n const updated = {\n userId,\n provider: account.provider,\n providerAccountId: account.providerAccountId,\n accessToken: refreshed.accessToken ?? account.accessToken,\n refreshToken: refreshed.refreshToken ?? account.refreshToken,\n expiresAt: refreshed.expiresAt ?? null,\n idToken: refreshed.idToken ?? account.idToken ?? null,\n tokenType: refreshed.tokenType ?? account.tokenType ?? null,\n scope: refreshed.scope ?? account.scope ?? null,\n }\n await adapter.updateAccount?.(updated)\n return { accessToken: updated.accessToken!, expiresAt: updated.expiresAt }\n }\n catch {\n return null\n }\n }\n\n async function startImpersonation(\n adminUserId: string,\n targetUserId: string,\n options: StartImpersonationOptions = {},\n ): Promise<ImpersonationResult | null> {\n if (!resolvedImpersonation)\n throw new GauError(ErrorCodes.IMPERSONATION_DISABLED)\n\n const adminUser = await adapter.getUser(adminUserId)\n if (!adminUser)\n throw new GauError(ErrorCodes.USER_NOT_FOUND, `Admin user \"${adminUserId}\" not found`)\n\n const hasAllowedRole = adminUser.role\n ? resolvedImpersonation.allowedRoles.includes(adminUser.role)\n : false\n const isAdminUserId = resolvedRoles.adminUserIds.includes(adminUserId)\n\n if (!hasAllowedRole && !isAdminUserId)\n throw new GauError(ErrorCodes.IMPERSONATION_NOT_ALLOWED)\n\n const targetUser = await adapter.getUser(targetUserId)\n if (!targetUser)\n throw new GauError(ErrorCodes.USER_NOT_FOUND, `Target user \"${targetUserId}\" not found`)\n\n if (targetUser.role && resolvedImpersonation.cannotImpersonate.includes(targetUser.role))\n throw new GauError(ErrorCodes.IMPERSONATION_TARGET_PROTECTED)\n\n if (resolvedImpersonation.onImpersonate) {\n await resolvedImpersonation.onImpersonate({\n adminUserId,\n targetUserId,\n reason: options.reason,\n timestamp: Date.now(),\n })\n }\n\n const ttl = Math.min(options.ttl ?? resolvedImpersonation.maxTTL, resolvedImpersonation.maxTTL)\n const expiresAt = Math.floor(Date.now() / 1000) + ttl\n\n const impersonationToken = await createSession(targetUserId, {\n impersonatedBy: adminUserId,\n impersonationExpiresAt: expiresAt,\n }, ttl)\n\n const cookieOpts: SerializeOptions = {\n ...cookieOptions,\n maxAge: ttl,\n }\n\n const cookie = serialize(SESSION_COOKIE_NAME, impersonationToken, cookieOpts)\n\n const stashToken = await signJWT({ adminUserId }, { ttl: resolvedImpersonation.maxTTL * 2 })\n const stashCookie = serialize(SESSION_STASH_COOKIE_NAME, stashToken, cookieOpts)\n\n return {\n token: impersonationToken,\n cookie,\n originalCookie: stashCookie,\n maxAge: ttl,\n }\n }\n\n async function endImpersonation(request: Request): Promise<EndImpersonationResult | null> {\n const cookieHeader = request.headers.get('cookie')\n if (!cookieHeader)\n return null\n\n const parsedCookies = parse(cookieHeader)\n const stashToken = parsedCookies[SESSION_STASH_COOKIE_NAME]\n\n if (!stashToken)\n return null\n\n const stashPayload = await verifyJWT<{ adminUserId: string }>(stashToken)\n if (!stashPayload?.adminUserId)\n return null\n const adminUser = await adapter.getUser(stashPayload.adminUserId)\n if (!adminUser)\n return null\n\n const restoredSession = await issueSession(stashPayload.adminUserId)\n\n const clearStashCookie = serialize(SESSION_STASH_COOKIE_NAME, '', {\n ...cookieOptions,\n expires: new Date(0),\n maxAge: 0,\n })\n\n return {\n token: restoredSession.token,\n cookie: restoredSession.cookie,\n clearCookies: [clearStashCookie],\n }\n }\n\n return {\n ...adapter,\n providerMap: providerMap as Map<ProviderId<TProviders[number]>, TProviders[number]>,\n basePath,\n cookieOptions,\n jwt: {\n ttl: defaultTTL,\n },\n onOAuthExchange,\n mapExternalProfile,\n onBeforeLinkAccount,\n onAfterLinkAccount,\n signJWT,\n verifyJWT,\n createSession,\n validateSession,\n issueSession,\n refreshSession,\n getAccessToken,\n trustHosts,\n autoLink,\n allowDifferentEmails,\n profiles: resolvedProfiles,\n updateUserInfoOnLink,\n sessionStrategy,\n development: false,\n roles: resolvedRoles,\n cors: resolvedCors,\n onError,\n errorRedirect,\n startImpersonation,\n endImpersonation,\n impersonation: resolvedImpersonation,\n }\n}\n","/// <reference types=\"node\" />\nimport {\n createJWTSignatureMessage,\n encodeJWT,\n JWSRegisteredHeaders,\n JWTRegisteredClaims,\n parseJWT,\n} from '@oslojs/jwt'\nimport { AuthError } from '../core/index'\nimport { constantTimeEqual, deriveKeysFromSecret, rawToDer } from './utils'\n\nexport type SupportedAlgorithm = 'ES256' | 'HS256'\n\ninterface CommonSignOptions {\n /** Time-to-live in seconds (exp claim). If omitted the token will not expire. */\n ttl?: number\n}\n\nexport type SignOptions\n = | ({ algorithm?: 'ES256', privateKey?: CryptoKey, secret?: string }\n & CommonSignOptions & { iss?: string, aud?: string | string[], sub?: string })\n | ({ algorithm: 'HS256', secret?: string | Uint8Array, privateKey?: never }\n & CommonSignOptions & { iss?: string, aud?: string | string[], sub?: string })\n\n/**\n * Create a signed JWT.\n * Defaults to ES256 when a privateKey is supplied. Falls back to HS256 when a secret is supplied.\n */\nexport async function sign<T extends Record<string, unknown>>(payload: T, options: SignOptions = {}): Promise<string> {\n let { algorithm = 'ES256', ttl, iss, aud, sub, privateKey, secret } = options\n\n if (algorithm === 'ES256') {\n if (!privateKey) {\n if (typeof secret !== 'string')\n throw new AuthError('Missing secret for ES256 signing. It must be a base64url-encoded string.');\n\n ({ privateKey } = await deriveKeysFromSecret(secret))\n }\n }\n else if (algorithm === 'HS256' && !secret) {\n throw new AuthError('Missing secret for HS256 signing')\n }\n\n const now = Math.floor(Date.now() / 1000)\n\n const jwtPayload: Record<string, unknown> = { iat: now, iss, aud, sub, ...payload }\n\n if (ttl != null && ttl > 0)\n jwtPayload.exp = now + ttl\n\n const isHS256 = algorithm === 'HS256'\n const alg: SupportedAlgorithm = isHS256 ? 'HS256' : 'ES256'\n\n const headerJSON = JSON.stringify({ alg, typ: 'JWT' })\n const payloadJSON = JSON.stringify(jwtPayload)\n\n const signatureMessage = createJWTSignatureMessage(headerJSON, payloadJSON)\n\n let signature: Uint8Array\n\n if (isHS256) {\n // HS256 (HMAC-SHA256)\n const secretBytes = typeof secret === 'string'\n ? new TextEncoder().encode(secret)\n : secret\n\n const cryptoKey = await crypto.subtle.importKey(\n 'raw',\n secretBytes as BufferSource,\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n )\n\n signature = new Uint8Array(await crypto.subtle.sign('HMAC', cryptoKey, signatureMessage as BufferSource))\n }\n else {\n // ES256 (ECDSA-SHA256)\n // Runtimes like Bun's return the raw (r||s) signature directly, not DER-encoded.\n signature = new Uint8Array(\n await crypto.subtle.sign(\n { name: 'ECDSA', hash: 'SHA-256' },\n privateKey!,\n signatureMessage as BufferSource,\n ),\n )\n }\n\n return encodeJWT(headerJSON, payloadJSON, signature)\n}\n\nexport type VerifyOptions\n = | { algorithm?: 'ES256', publicKey?: CryptoKey, secret?: string, iss?: string, aud?: string | string[] }\n | { algorithm: 'HS256', secret?: string | Uint8Array, publicKey?: never, iss?: string, aud?: string | string[] }\n\n/**\n * Verify a JWT and return its payload when the signature is valid.\n * The algorithm is inferred from options – ES256 by default.\n * Throws when verification fails or the token is expired.\n */\nexport async function verify<T = Record<string, unknown>>(token: string, options: VerifyOptions): Promise<T> {\n let { algorithm = 'ES256', publicKey, secret, iss, aud } = options\n\n if (algorithm === 'ES256') {\n if (!publicKey) {\n if (typeof secret !== 'string')\n throw new AuthError('Missing secret for ES256 verification. Must be a base64url-encoded string.');\n\n ({ publicKey } = await deriveKeysFromSecret(secret))\n }\n }\n\n if (algorithm === 'HS256' && !secret)\n throw new AuthError('Missing secret for HS256 verification')\n\n const [header, payload, signature, signatureMessage] = parseJWT(token)\n\n const headerParams = new JWSRegisteredHeaders(header)\n const headerAlg = headerParams.algorithm()\n\n let validSignature = false\n\n // HS256 verification path\n if (algorithm === 'HS256') {\n if (headerAlg !== 'HS256')\n throw new Error(`JWT algorithm is \"${headerAlg}\", but verifier was configured for \"HS256\"`)\n\n const secretBytes = typeof secret === 'string'\n ? new TextEncoder().encode(secret)\n : secret\n\n const cryptoKey = await crypto.subtle.importKey(\n 'raw',\n secretBytes as BufferSource,\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n )\n\n const expectedSig = new Uint8Array(await crypto.subtle.sign('HMAC', cryptoKey, signatureMessage as BufferSource))\n validSignature = constantTimeEqual(expectedSig, new Uint8Array(signature))\n }\n // ES256 verification path (default)\n else {\n if (headerAlg !== 'ES256')\n throw new AuthError(`JWT algorithm is \"${headerAlg}\", but verifier was configured for \"ES256\"`)\n\n const signatureBytes = new Uint8Array(signature)\n\n // Runtimes like Node.js return DER-encoded signatures. Others (Bun) return raw (r||s).\n // We try DER first, as it's more common in Node environments.\n validSignature = await crypto.subtle.verify(\n { name: 'ECDSA', hash: 'SHA-256' },\n publicKey!,\n signatureBytes as BufferSource,\n signatureMessage as BufferSource,\n )\n\n if (!validSignature && signatureBytes.length === 64) {\n // If DER verification fails and the signature is 64 bytes, it might be a raw signature.\n // Convert it to DER and try again.\n try {\n const derSig = rawToDer(signatureBytes)\n validSignature = await crypto.subtle.verify(\n { name: 'ECDSA', hash: 'SHA-256' },\n publicKey!,\n derSig as BufferSource,\n signatureMessage as BufferSource,\n )\n }\n catch {\n // rawToDer can throw if the signature is not 64 bytes, but we already checked.\n // This catch is for other unexpected errors.\n validSignature = false\n }\n }\n }\n\n if (!validSignature)\n throw new AuthError('Invalid JWT signature')\n\n const claims = new JWTRegisteredClaims(payload)\n if (claims.hasExpiration() && !claims.verifyExpiration())\n throw new AuthError('JWT expired')\n if (claims.hasNotBefore() && !claims.verifyNotBefore())\n throw new AuthError('JWT not yet valid')\n if (iss && (payload as any).iss !== iss)\n throw new AuthError('Invalid JWT issuer')\n\n if (aud) {\n const expectedAudience = Array.isArray(aud) ? aud : [aud]\n const tokenAudience = (payload as any).aud\n ? (Array.isArray((payload as any).aud) ? (payload as any).aud : [(payload as any).aud])\n : []\n\n if (!expectedAudience.some(audValue => tokenAudience.includes(audValue)))\n throw new AuthError('Invalid JWT audience')\n }\n\n return payload as T\n}\n","export const ErrorMessages = {\n // OAuth Flow Errors\n CSRF_INVALID: 'Invalid CSRF token',\n PKCE_MISSING: 'Missing PKCE code verifier',\n PKCE_CHALLENGE_MISSING: 'Missing PKCE challenge',\n OAUTH_CANCELLED: 'Authentication was cancelled',\n PROVIDER_NOT_FOUND: 'Provider not found',\n AUTHORIZATION_URL_FAILED: 'Could not create authorization URL',\n\n // User Errors\n USER_NOT_FOUND: 'User not found',\n USER_CREATE_FAILED: 'Failed to create user',\n ACCOUNT_ALREADY_LINKED: 'Account already linked to another user',\n ACCOUNT_LINK_FAILED: 'Failed to link account',\n ACCOUNT_NOT_LINKED: 'Account not linked',\n CANNOT_UNLINK_LAST_ACCOUNT: 'Cannot unlink the last account',\n EMAIL_ALREADY_EXISTS: 'An account with this email already exists',\n EMAIL_MISMATCH: 'Email mismatch between existing account and provider',\n LINKING_NOT_ALLOWED: 'Linking not allowed',\n LINK_ONLY_PROVIDER: 'Sign-in with this provider is disabled. Please link it to an existing account.',\n\n // Session Errors\n UNAUTHORIZED: 'Unauthorized',\n FORBIDDEN: 'Forbidden',\n SESSION_INVALID: 'Invalid session',\n SESSION_VALIDATION_FAILED: 'Failed to validate session',\n\n // Token Errors\n TOKEN_INVALID: 'Invalid token',\n TOKEN_EXPIRED: 'Token expired',\n CODE_VERIFIER_INVALID: 'Invalid code verifier',\n\n // Request Errors\n NOT_FOUND: 'Not found',\n METHOD_NOT_ALLOWED: 'Method not allowed',\n INVALID_REQUEST: 'Invalid request',\n INVALID_REDIRECT_URL: 'Invalid redirect URL',\n UNTRUSTED_HOST: 'Untrusted redirect host',\n UNKNOWN_PROFILE: 'Unknown profile',\n\n // Internal Errors\n INTERNAL_ERROR: 'An unexpected error occurred',\n\n // Impersonation Errors\n IMPERSONATION_DISABLED: 'Impersonation is not enabled',\n IMPERSONATION_NOT_ALLOWED: 'You are not allowed to impersonate users',\n IMPERSONATION_TARGET_PROTECTED: 'Cannot impersonate users with protected roles',\n} as const\n\nexport type ErrorCode = keyof typeof ErrorMessages\n\nexport const ErrorCodes: { [K in ErrorCode]: K } = Object.fromEntries(\n Object.keys(ErrorMessages).map(k => [k, k]),\n) as { [K in ErrorCode]: K }\n\n/**\n * Default HTTP status codes for each error code.\n * Errors not listed here default to 400.\n */\nexport const ErrorStatuses: Partial<Record<ErrorCode, number>> = {\n CSRF_INVALID: 403,\n UNAUTHORIZED: 401,\n FORBIDDEN: 403,\n NOT_FOUND: 404,\n METHOD_NOT_ALLOWED: 405,\n INTERNAL_ERROR: 500,\n USER_CREATE_FAILED: 500,\n ACCOUNT_LINK_FAILED: 500,\n AUTHORIZATION_URL_FAILED: 500,\n SESSION_VALIDATION_FAILED: 500,\n ACCOUNT_ALREADY_LINKED: 409,\n EMAIL_ALREADY_EXISTS: 409,\n LINKING_NOT_ALLOWED: 403,\n IMPERSONATION_DISABLED: 403,\n IMPERSONATION_NOT_ALLOWED: 403,\n IMPERSONATION_TARGET_PROTECTED: 403,\n}\n\nexport interface GauErrorOptions {\n /** HTTP status code (uses default for error code if not specified) */\n status?: number\n /** URL to redirect to after showing error (for OAuth flow errors) */\n redirectUrl?: string\n /** Original error that caused this error */\n cause?: unknown\n}\n\nexport class GauError extends Error {\n readonly code: ErrorCode\n readonly status: number\n readonly redirectUrl?: string\n override readonly cause?: unknown\n\n constructor(\n code: ErrorCode,\n messageOrOptions?: string | GauErrorOptions,\n options?: GauErrorOptions,\n ) {\n const message = typeof messageOrOptions === 'string'\n ? messageOrOptions\n : ErrorMessages[code]\n const opts = typeof messageOrOptions === 'object'\n ? messageOrOptions\n : options ?? {}\n\n super(message)\n this.name = 'GauError'\n this.code = code\n this.status = opts.status ?? ErrorStatuses[code] ?? 400\n this.redirectUrl = opts.redirectUrl\n this.cause = opts.cause\n }\n\n toJSON() {\n return {\n error: this.message,\n code: this.code,\n ...(this.redirectUrl && { redirectUrl: this.redirectUrl }),\n }\n }\n}\n\nexport function createErrorRedirectUrl(baseUrl: string, error: GauError): string {\n const url = new URL(baseUrl, 'http://placeholder')\n url.searchParams.set('code', error.code)\n url.searchParams.set('message', error.message)\n url.searchParams.set('status', String(error.status))\n if (error.redirectUrl)\n url.searchParams.set('redirect', error.redirectUrl)\n\n return url.pathname + url.search\n}\n\nexport interface ErrorContext {\n error: GauError\n request: Request\n}\n\nexport interface ErrorHandlerConfig {\n basePath: string\n onError?: (context: ErrorContext) => Response | Promise<Response | undefined> | undefined\n errorRedirect?: string\n}\n\nexport function isUserFacingRequest(request: Request, basePath: string): boolean {\n // POST requests are always API calls\n if (request.method !== 'GET')\n return false\n\n const url = new URL(request.url)\n const path = url.pathname.substring(basePath.length)\n const parts = path.split('/').filter(Boolean)\n\n // GET /session is an API call\n if (parts.length === 1 && parts[0] === 'session')\n return false\n\n // GET /:provider (sign-in start) - user-facing\n if (parts.length === 1)\n return true\n\n // GET /callback/:provider or GET /link/:provider - user-facing\n if (parts.length === 2 && (parts[0] === 'callback' || parts[0] === 'link'))\n return true\n\n return false\n}\n\nexport async function handleError(\n context: ErrorContext,\n config: ErrorHandlerConfig,\n): Promise<Response> {\n const { error, request } = context\n\n // 1. Try custom onError handler\n if (config.onError) {\n try {\n const response = await config.onError(context)\n if (response)\n return response\n }\n catch (e) {\n console.error('onError handler threw:', e)\n }\n }\n\n const userFacing = isUserFacingRequest(request, config.basePath)\n\n // 2. Try errorRedirect for user-facing requests\n if (config.errorRedirect && userFacing) {\n const redirectUrl = createErrorRedirectUrl(config.errorRedirect, error)\n return new Response(null, {\n status: 302,\n headers: { Location: redirectUrl },\n })\n }\n\n // 3. Default handling\n if (userFacing) {\n const { renderErrorPage, htmlResponse } = await import('./templates')\n const html = renderErrorPage({\n title: 'Authentication Error',\n message: error.message,\n code: error.code,\n redirectUrl: error.redirectUrl,\n })\n return htmlResponse(html, error.status)\n }\n\n return new Response(JSON.stringify(error.toJSON()), {\n status: error.status,\n headers: { 'Content-Type': 'application/json; charset=utf-8' },\n })\n}\n","import type { Auth } from '../createAuth'\nimport type { User } from '../index'\nimport {\n CALLBACK_URI_COOKIE_NAME,\n CLIENT_CHALLENGE_COOKIE_NAME,\n Cookies,\n CSRF_COOKIE_NAME,\n LINKING_TOKEN_COOKIE_NAME,\n parseCookies,\n PKCE_COOKIE_NAME,\n PROVIDER_OPTIONS_COOKIE_NAME,\n SESSION_COOKIE_NAME,\n} from '../cookies'\nimport { ErrorCodes, GauError } from '../errors'\nimport { maybeMapExternalProfile, runOnAfterLinkAccount, runOnBeforeLinkAccount, runOnOAuthExchange } from '../hooks'\nimport { json, redirect } from '../index'\nimport { htmlResponse, renderCancelledPage, renderSuccessPage } from '../templates'\n\nexport async function handleCallback(request: Request, auth: Auth, providerId: string): Promise<Response> {\n const provider = auth.providerMap.get(providerId)\n if (!provider)\n throw new GauError(ErrorCodes.PROVIDER_NOT_FOUND)\n\n const url = new URL(request.url)\n const code = url.searchParams.get('code')\n const state = url.searchParams.get('state')\n const error = url.searchParams.get('error')\n\n if (!code || !state || error) {\n let redirectTo = '/'\n if (state && state.includes('.')) {\n try {\n const encodedRedirect = state.split('.')[1]\n redirectTo = atob(encodedRedirect ?? '') || '/'\n }\n catch {\n redirectTo = '/'\n }\n }\n\n const html = renderCancelledPage({ redirectUrl: redirectTo })\n return htmlResponse(html)\n }\n\n const requestCookies = parseCookies(request.headers.get('Cookie'))\n const cookies = new Cookies(requestCookies, auth.cookieOptions)\n\n let savedState: string | undefined\n let redirectTo = '/'\n if (state.includes('.')) {\n const [originalSavedState, encodedRedirect] = state.split('.')\n savedState = originalSavedState\n try {\n redirectTo = atob(encodedRedirect ?? '') || '/'\n }\n catch {\n redirectTo = '/'\n }\n }\n else {\n savedState = state\n }\n\n const csrfToken = cookies.get(CSRF_COOKIE_NAME)\n\n if (!csrfToken || csrfToken !== savedState)\n throw new GauError(ErrorCodes.CSRF_INVALID, { redirectUrl: redirectTo })\n\n const codeVerifier = cookies.get(PKCE_COOKIE_NAME)\n if (!codeVerifier)\n throw new GauError(ErrorCodes.PKCE_MISSING, { redirectUrl: redirectTo })\n\n const callbackUri = cookies.get(CALLBACK_URI_COOKIE_NAME)\n const providerOptionsRaw = cookies.get(PROVIDER_OPTIONS_COOKIE_NAME)\n let providerOverrides: any | undefined\n if (providerOptionsRaw) {\n try {\n const decoded = atob(providerOptionsRaw)\n const parsed = JSON.parse(decoded)\n providerOverrides = parsed?.overrides\n }\n catch {}\n }\n const linkingToken = cookies.get(LINKING_TOKEN_COOKIE_NAME)\n\n if (linkingToken)\n cookies.delete(LINKING_TOKEN_COOKIE_NAME)\n\n const isLinking = !!linkingToken\n\n if (isLinking) {\n const session = await auth.validateSession(linkingToken!)\n if (!session) {\n cookies.delete(CSRF_COOKIE_NAME)\n cookies.delete(PKCE_COOKIE_NAME)\n if (callbackUri)\n cookies.delete(CALLBACK_URI_COOKIE_NAME)\n cookies.delete(PROVIDER_OPTIONS_COOKIE_NAME)\n const response = redirect(redirectTo)\n cookies.toHeaders().forEach((value, key) => response.headers.append(key, value))\n return response\n }\n }\n\n const { user: rawProviderUser, tokens } = await provider.validateCallback(code, codeVerifier, callbackUri ?? undefined, providerOverrides)\n\n {\n const session = isLinking ? await auth.validateSession(linkingToken!) : null\n const hookResult = await runOnOAuthExchange(auth, {\n request,\n providerId,\n state,\n code,\n codeVerifier,\n callbackUri,\n redirectTo,\n cookies,\n providerUser: rawProviderUser,\n tokens,\n isLinking,\n sessionUserId: session?.user?.id,\n })\n if (hookResult.handled) {\n cookies.delete(CSRF_COOKIE_NAME)\n cookies.delete(PKCE_COOKIE_NAME)\n if (callbackUri)\n cookies.delete(CALLBACK_URI_COOKIE_NAME)\n cookies.delete(PROVIDER_OPTIONS_COOKIE_NAME)\n const response = hookResult.response\n cookies.toHeaders().forEach((value, key) => response.headers.append(key, value))\n return response\n }\n }\n\n const providerUser = await maybeMapExternalProfile(auth, {\n request,\n providerId,\n providerUser: rawProviderUser,\n tokens,\n isLinking,\n })\n\n // Enforce provider-level link-only when not linking (profile-level enforced at redirect time)\n if (!isLinking && (auth.providerMap.get(providerId)?.linkOnly === true)) {\n cookies.delete(CSRF_COOKIE_NAME)\n cookies.delete(PKCE_COOKIE_NAME)\n if (callbackUri)\n cookies.delete(CALLBACK_URI_COOKIE_NAME)\n cookies.delete(PROVIDER_OPTIONS_COOKIE_NAME)\n throw new GauError(ErrorCodes.LINK_ONLY_PROVIDER, { redirectUrl: redirectTo })\n }\n\n let user: User | null = null\n\n const userFromAccount = await auth.getUserByAccount(providerId, providerUser.id)\n\n if (isLinking) {\n const session = await auth.validateSession(linkingToken)\n user = session!.user\n\n if (!user)\n throw new GauError(ErrorCodes.USER_NOT_FOUND, { redirectUrl: redirectTo })\n\n if (userFromAccount && userFromAccount.id !== user.id)\n throw new GauError(ErrorCodes.ACCOUNT_ALREADY_LINKED, { redirectUrl: redirectTo })\n\n if (auth.allowDifferentEmails === false) {\n const currentEmail = user.email\n const providerEmail = providerUser.email\n if (currentEmail && providerEmail && currentEmail !== providerEmail)\n throw new GauError(ErrorCodes.EMAIL_MISMATCH, { redirectUrl: redirectTo })\n }\n\n if (user) {\n const update: Partial<User> & { id: string } = { id: user.id }\n let needsUpdate = false\n\n if (auth.updateUserInfoOnLink) {\n if (providerUser.name && providerUser.name !== user.name) {\n update.name = providerUser.name\n needsUpdate = true\n }\n if (providerUser.avatar && providerUser.avatar !== user.image) {\n update.image = providerUser.avatar\n needsUpdate = true\n }\n }\n else {\n if (!user.name && providerUser.name) {\n update.name = providerUser.name\n needsUpdate = true\n }\n if (!user.image && providerUser.avatar) {\n update.image = providerUser.avatar\n needsUpdate = true\n }\n }\n\n if (\n user.email\n && providerUser.email\n && user.email === providerUser.email\n && providerUser.emailVerified === true\n && (!user.emailVerified || auth.updateUserInfoOnLink)\n ) {\n update.emailVerified = true\n needsUpdate = true\n }\n\n if (needsUpdate) {\n try {\n user = await auth.updateUser(update)\n }\n catch (e) {\n console.error('Failed to update user info on link:', e)\n }\n }\n }\n }\n else {\n user = userFromAccount\n }\n\n if (!user) {\n const autoLink = auth.autoLink ?? 'verifiedEmail'\n const shouldLinkByEmail = providerUser.email && (\n (autoLink === 'always')\n || (autoLink === 'verifiedEmail' && providerUser.emailVerified === true)\n )\n if (shouldLinkByEmail) {\n const existingUser = await auth.getUserByEmail(providerUser.email!)\n if (existingUser) {\n // If the email is verified by the new provider, and the existing user's email is not,\n // update the user's email verification status.\n if (providerUser.emailVerified && !existingUser.emailVerified) {\n user = await auth.updateUser({\n id: existingUser.id,\n emailVerified: true,\n })\n }\n else {\n user = existingUser\n }\n }\n }\n if (!user) {\n try {\n if (providerUser.email && providerUser.emailVerified === true && auth.autoLink === false) {\n const existingWithSameEmail = await auth.getUserByEmail(providerUser.email)\n if (existingWithSameEmail)\n throw new GauError(ErrorCodes.EMAIL_ALREADY_EXISTS, { redirectUrl: redirectTo })\n }\n\n let resolvedRole: string | undefined\n try {\n resolvedRole = auth.roles.resolveOnCreate?.({ providerId, profile: providerUser, request: request as unknown as Request })\n }\n catch (e) {\n console.error('roles.resolveOnCreate threw:', e)\n }\n\n const emailToStore = providerUser.emailVerified === true ? providerUser.email : null\n\n user = await auth.createUser({\n name: providerUser.name,\n email: emailToStore,\n image: providerUser.avatar,\n emailVerified: providerUser.emailVerified,\n role: resolvedRole ?? auth.roles.defaultRole,\n })\n }\n catch (error) {\n if (error instanceof GauError)\n throw error\n console.error('Failed to create user:', error)\n throw new GauError(ErrorCodes.USER_CREATE_FAILED, { cause: error, redirectUrl: redirectTo })\n }\n }\n }\n\n // self-healing: update user's email if it's missing or unverified and the provider returns a verified email\n if (user && providerUser.email) {\n const { email: currentEmail, emailVerified: currentEmailVerified } = user\n const { email: providerEmail, emailVerified: providerEmailVerified } = providerUser\n\n const update: Partial<User> & { id: string } = { id: user.id }\n let needsUpdate = false\n\n // user has no primary email. promote the provider's email but only if it's verified.\n if (!currentEmail && providerEmailVerified === true) {\n update.email = providerEmail\n update.emailVerified = true\n needsUpdate = true\n }\n // user has an unverified primary email, and the provider confirms this same email is verified.\n else if (\n currentEmail === providerEmail\n && providerEmailVerified === true\n && !currentEmailVerified\n ) {\n update.emailVerified = true\n needsUpdate = true\n }\n\n if (needsUpdate) {\n try {\n user = await auth.updateUser(update)\n }\n catch (error) {\n console.error('Failed to update user after sign-in:', error)\n }\n }\n }\n\n if (!userFromAccount) {\n // GitHub sometimes doesn't return these which causes arctic to throw an error\n let refreshToken: string | null\n try {\n refreshToken = tokens.refreshToken()\n }\n catch {\n refreshToken = null\n }\n\n let expiresAt: number | undefined\n try {\n const expiresAtDate = tokens.accessTokenExpiresAt()\n if (expiresAtDate)\n expiresAt = Math.floor(expiresAtDate.getTime() / 1000)\n }\n catch {\n }\n\n let idToken: string | null\n try {\n idToken = tokens.idToken()\n }\n catch {\n idToken = null\n }\n\n {\n const pre = await runOnBeforeLinkAccount(auth, {\n request,\n providerId,\n userId: user.id,\n providerUser,\n tokens,\n })\n if (pre.allow === false) {\n const response = pre.response ?? (() => {\n throw new GauError(ErrorCodes.LINKING_NOT_ALLOWED, { redirectUrl: redirectTo })\n })()\n cookies.toHeaders().forEach((value, key) => response.headers.append(key, value))\n return response\n }\n }\n\n try {\n let scope: string | null\n try {\n scope = tokens.scopes()?.join(' ') ?? null\n }\n catch {\n scope = null\n }\n\n await auth.linkAccount({\n userId: user.id,\n provider: providerId,\n providerAccountId: providerUser.id,\n accessToken: tokens.accessToken(),\n refreshToken,\n expiresAt,\n tokenType: tokens.tokenType?.() ?? null,\n scope,\n idToken,\n })\n await runOnAfterLinkAccount(auth, {\n request,\n providerId,\n userId: user.id,\n providerUser,\n tokens,\n action: 'link',\n })\n }\n catch (error) {\n console.error('Error linking account:', error)\n throw new GauError(ErrorCodes.ACCOUNT_LINK_FAILED, { cause: error, redirectUrl: redirectTo })\n }\n }\n else {\n // Existing account: update stored tokens on sign-in (access/refresh/expires/idToken/etc.)\n try {\n const accounts = await auth.getAccounts(user!.id)\n const existing = accounts.find(a => a.provider === providerId && a.providerAccountId === providerUser.id)\n\n if (existing && auth.updateAccount) {\n let refreshToken: string | null\n try {\n refreshToken = tokens.refreshToken()\n }\n catch {\n refreshToken = existing.refreshToken ?? null\n }\n\n let expiresAt: number | undefined\n try {\n const expiresAtDate = tokens.accessTokenExpiresAt()\n if (expiresAtDate)\n expiresAt = Math.floor(expiresAtDate.getTime() / 1000)\n }\n catch {\n expiresAt = existing.expiresAt ?? undefined\n }\n\n let idToken: string | null\n try {\n idToken = tokens.idToken()\n }\n catch {\n idToken = existing.idToken ?? null\n }\n\n let scope: string | null\n try {\n scope = tokens.scopes()?.join(' ') ?? existing.scope ?? null\n }\n catch {\n scope = existing.scope ?? null\n }\n\n await auth.updateAccount({\n userId: user!.id,\n provider: providerId,\n providerAccountId: providerUser.id,\n accessToken: tokens.accessToken() ?? existing.accessToken ?? undefined,\n refreshToken,\n expiresAt: expiresAt ?? existing.expiresAt ?? undefined,\n tokenType: tokens.tokenType?.() ?? existing.tokenType ?? null,\n scope,\n idToken,\n })\n await runOnAfterLinkAccount(auth, {\n request,\n providerId,\n userId: user!.id,\n providerUser,\n tokens,\n action: 'update',\n })\n }\n }\n catch (error) {\n console.error('Failed to update account tokens on sign-in:', error)\n }\n }\n\n const sessionToken = await auth.createSession(user.id)\n\n const requestUrl = new URL(request.url)\n const redirectUrl = new URL(redirectTo, request.url)\n\n const forceToken = auth.sessionStrategy === 'token'\n const forceCookie = auth.sessionStrategy === 'cookie'\n\n const isCustomScheme = redirectUrl.protocol !== 'http:' && redirectUrl.protocol !== 'https:'\n const isCrossHost = requestUrl.host !== redirectUrl.host\n\n // For Tauri, we can't set a cookie on a custom protocol or a different host,\n // so we pass the token in the URL. Additionally, return a small HTML page\n // that navigates to the deep-link and then closes after browser handoff\n // signals, which avoids racing first-run deep-link permission prompts.\n if (forceToken || (!forceCookie && (isCustomScheme || isCrossHost))) {\n const destination = new URL(redirectUrl)\n const clientChallenge = cookies.get(CLIENT_CHALLENGE_COOKIE_NAME)\n\n if (clientChallenge) {\n // PKCE\n const authCode = await auth.signJWT({\n sub: user.id,\n challenge: clientChallenge,\n }, { ttl: 60 })\n\n destination.searchParams.set('code', authCode)\n }\n else {\n throw new GauError(ErrorCodes.PKCE_CHALLENGE_MISSING, { redirectUrl: redirectTo })\n }\n\n const html = renderSuccessPage({ redirectUrl: destination.toString() })\n\n // Clear temporary cookies\n cookies.delete(CSRF_COOKIE_NAME)\n cookies.delete(PKCE_COOKIE_NAME)\n if (callbackUri)\n cookies.delete(CALLBACK_URI_COOKIE_NAME)\n cookies.delete(PROVIDER_OPTIONS_COOKIE_NAME)\n cookies.delete(CLIENT_CHALLENGE_COOKIE_NAME)\n\n const response = htmlResponse(html)\n cookies.toHeaders().forEach((value, key) => {\n response.headers.append(key, value)\n })\n return response\n }\n\n cookies.set(SESSION_COOKIE_NAME, sessionToken, {\n maxAge: auth.jwt.ttl,\n sameSite: auth.development ? 'lax' : 'none',\n secure: !auth.development,\n })\n cookies.delete(CSRF_COOKIE_NAME)\n cookies.delete(PKCE_COOKIE_NAME)\n if (callbackUri)\n cookies.delete(CALLBACK_URI_COOKIE_NAME)\n cookies.delete(PROVIDER_OPTIONS_COOKIE_NAME)\n\n const redirectParam = url.searchParams.get('redirect')\n\n let response: Response\n if (redirectParam === 'false') {\n const accounts = await auth.getAccounts(user.id)\n const isAdmin = Boolean(\n (user.role && auth.roles.adminRoles.includes(user.role))\n || auth.roles.adminUserIds.includes(user.id),\n )\n response = json({ user: { ...user, isAdmin, accounts } })\n }\n else {\n response = redirect(redirectTo)\n }\n\n cookies.toHeaders().forEach((value, key) => {\n response.headers.append(key, value)\n })\n\n return response\n}\n","import { generateCodeVerifier, generateState } from 'arctic'\n\nexport function createOAuthUris() {\n const state = generateState()\n const codeVerifier = generateCodeVerifier()\n\n return {\n state,\n codeVerifier,\n }\n}\n","export interface User {\n id: string\n name?: string | null\n email?: string | null\n emailVerified?: boolean | null\n image?: string | null\n role?: string | null\n isAdmin?: boolean\n}\n\nexport interface Session {\n id: string\n sub: string\n [key: string]: unknown\n}\n\nexport interface ClientAccount<TProviders extends string = string> {\n provider: TProviders\n providerAccountId: string\n}\n\n/**\n * Client-safe session data.\n */\nexport interface GauSession<TProviders extends string = string> {\n user: User | null\n session: Omit<Session, 'id'> | null\n accounts?: ClientAccount<TProviders>[] | null\n providers?: TProviders[]\n}\n\n/**\n * Full server-side session with complete account data including tokens.\n *\n * Never serialize this to the client - contains sensitive access/refresh tokens.\n */\nexport interface GauServerSession<TProviders extends string = string> {\n user: User | null\n session: Session | null\n accounts?: Account[] | null\n providers?: TProviders[]\n}\n\nexport const NULL_SESSION = {\n user: null,\n session: null,\n accounts: null,\n} as const\n\nexport function toClientSession<TProviders extends string = string>(\n serverSession: GauServerSession<TProviders>,\n): GauSession<TProviders> {\n const safeSession: Omit<Session, 'id'> | null = serverSession.session\n && (({ id: _id, ...rest }) => rest)(serverSession.session)\n\n return {\n user: serverSession.user,\n session: safeSession,\n accounts: serverSession.accounts?.map(acc => ({\n provider: acc.provider as TProviders,\n providerAccountId: acc.providerAccountId,\n })) ?? null,\n providers: serverSession.providers,\n }\n}\n\nexport interface NewUser extends Omit<User, 'id' | 'accounts' | 'isAdmin'> {\n id?: string\n}\n\nexport interface Account {\n userId: string\n provider: string\n providerAccountId: string\n type?: string // e.g. \"oauth\"\n accessToken?: string | null\n refreshToken?: string | null\n expiresAt?: number | null // epoch seconds\n idToken?: string | null\n scope?: string | null\n tokenType?: string | null\n sessionState?: string | null\n}\n\nexport interface NewAccount extends Account {}\n\nexport interface Adapter {\n getUser: (id: string) => Promise<User | null>\n getUserByEmail: (email: string) => Promise<User | null>\n getUserByAccount: (provider: string, providerAccountId: string) => Promise<User | null>\n getAccounts: (userId: string) => Promise<Account[]>\n getUserAndAccounts: (userId: string) => Promise<{ user: User, accounts: Account[] } | null>\n createUser: (data: NewUser) => Promise<User>\n linkAccount: (data: NewAccount) => Promise<void>\n unlinkAccount: (provider: string, providerAccountId: string) => Promise<void>\n updateAccount?: (data: Partial<Account> & { userId: string, provider: string, providerAccountId: string }) => Promise<void>\n updateUser: (data: Partial<User> & { id: string }) => Promise<User>\n deleteUser: (id: string) => Promise<void>\n}\n\nexport class AuthError extends Error {\n override readonly cause?: unknown\n constructor(message: string, cause?: unknown) {\n super(message)\n this.name = 'AuthError'\n this.cause = cause\n }\n}\n\nexport function json<T>(data: T, init: ResponseInit = {}): Response {\n const headers = new Headers(init.headers)\n if (!headers.has('Content-Type'))\n headers.set('Content-Type', 'application/json; charset=utf-8')\n return new Response(JSON.stringify(data), { ...init, headers })\n}\n\nexport function redirect(url: string, status: 302 | 303 = 302): Response {\n return new Response(null, {\n status,\n headers: {\n Location: url,\n },\n })\n}\n\nexport * from './cookies'\nexport * from './createAuth'\nexport * from './errors'\nexport * from './handler'\nexport * from './templates'\nexport * from './utils'\n\nexport const REFRESHED_TOKEN_HEADER = 'X-Refreshed-Token'\n\n/**\n * Helper to check if a session is an impersonation session.\n */\nexport function isImpersonating(session: Session | null): boolean {\n return session?.impersonatedBy != null\n}\n","import type { GauSession, ProfileName, ProviderIds } from '../../core'\nimport { isTauri } from '../../runtimes/tauri/index'\nimport { clearSessionToken, getSessionToken, handleRefreshedToken, storeSessionToken } from '../token'\n\nexport { clearSessionToken, getSessionToken, handleRefreshedToken, REFRESHED_TOKEN_HEADER, SESSION_TOKEN_KEY, storeSessionToken } from '../token'\n\nexport interface AuthClientOptions {\n baseUrl: string\n scheme?: string\n}\n\ntype SessionListener<TAuth = unknown> = (session: GauSession<ProviderIds<TAuth>>) => void\n\nfunction buildQuery(params: Record<string, string | undefined | null>): string {\n const q = new URLSearchParams()\n for (const [k, v] of Object.entries(params)) {\n if (v != null && v !== '')\n q.set(k, String(v))\n }\n const s = q.toString()\n return s ? `?${s}` : ''\n}\n\nexport function createAuthClient<const TAuth = unknown>({ baseUrl, scheme = 'gau' }: AuthClientOptions) {\n let currentSession: GauSession<ProviderIds<TAuth>> = { user: null, session: null, accounts: null, providers: [] }\n const listeners = new Set<SessionListener<TAuth>>()\n\n const notify = () => {\n for (const l of listeners)\n l(currentSession)\n }\n\n async function fetchSession(): Promise<GauSession<ProviderIds<TAuth>>> {\n const token = getSessionToken()\n const headers = token ? { Authorization: `Bearer ${token}` } : undefined\n const res = await fetch(`${baseUrl}/session`, token ? { headers } : { credentials: 'include' })\n const contentType = res.headers.get('content-type')\n if (contentType?.includes('application/json'))\n return await res.json()\n return { user: null, session: null, accounts: null, providers: [] }\n }\n\n async function refreshSession(): Promise<GauSession<ProviderIds<TAuth>>> {\n const next = await fetchSession()\n currentSession = next\n notify()\n return next\n }\n\n async function applySessionToken(token: string): Promise<void> {\n try {\n storeSessionToken(token)\n }\n finally {\n await refreshSession()\n }\n }\n\n function onSessionChange(listener: SessionListener<TAuth>): () => void {\n listeners.add(listener)\n return () => listeners.delete(listener)\n }\n\n async function handleRedirectCallback(replaceUrl?: (url: string) => void): Promise<boolean> {\n if (typeof window === 'undefined')\n return false\n\n if (window.location.hash === '#_=_') {\n const cleanUrl = window.location.pathname + window.location.search\n if (replaceUrl)\n replaceUrl(cleanUrl)\n else\n window.history.replaceState(null, '', cleanUrl)\n return false\n }\n\n const hash = window.location.hash?.substring(1) ?? ''\n if (!hash)\n return false\n\n const params = new URLSearchParams(hash)\n const token = params.get('token')\n if (!token)\n return false\n\n await applySessionToken(token)\n\n const cleanUrl = window.location.pathname + window.location.search\n if (replaceUrl)\n replaceUrl(cleanUrl)\n else\n window.history.replaceState(null, '', cleanUrl)\n\n return true\n }\n\n function makeProviderUrl<P extends ProviderIds<TAuth>, PR extends (ProfileName<TAuth, P> | string) | undefined>(provider: P, params?: { redirectTo?: string, profile?: PR }): string {\n const q = buildQuery({\n redirectTo: params?.redirectTo,\n profile: params?.profile != null ? String(params.profile) : undefined,\n })\n return `${baseUrl}/${provider}${q}`\n }\n\n function makeLinkUrl<P extends ProviderIds<TAuth>, PR extends (ProfileName<TAuth, P> | string) | undefined>(provider: P, params: { redirectTo?: string, profile?: PR, redirect?: 'false' | 'true' }): string {\n const q = buildQuery({\n redirectTo: params.redirectTo,\n profile: params.profile != null ? String(params.profile) : undefined,\n redirect: params.redirect,\n })\n return `${baseUrl}/link/${provider}${q}`\n }\n\n async function signIn<P extends ProviderIds<TAuth>, PR extends (ProfileName<TAuth, P> | string) | undefined>(provider: P, options?: { redirectTo?: string, profile?: PR }): Promise<string> {\n const url = makeProviderUrl<P, PR>(provider, options)\n\n if (isTauri()) {\n const { signInWithTauri } = await import('../../runtimes/tauri/index')\n await signInWithTauri<TAuth, P, PR>(provider, baseUrl, scheme, options?.redirectTo, options?.profile)\n }\n\n return url\n }\n\n async function linkAccount<P extends ProviderIds<TAuth>, PR extends (ProfileName<TAuth, P> | string) | undefined>(provider: P, options?: { redirectTo?: string, profile?: PR }): Promise<string> {\n if (isTauri()) {\n const { linkAccountWithTauri } = await import('../../runtimes/tauri/index')\n await linkAccountWithTauri<TAuth, P, PR>(provider, baseUrl, scheme, options?.redirectTo, options?.profile)\n return makeLinkUrl<P, PR>(provider, { redirectTo: options?.redirectTo, profile: options?.profile, redirect: 'false' })\n }\n\n const linkUrl = makeLinkUrl<P, PR>(provider, { redirectTo: options?.redirectTo, profile: options?.profile, redirect: 'false' })\n const token = getSessionToken()\n const fetchOptions: RequestInit = token ? { headers: { Authorization: `Bearer ${token}` } } : { credentials: 'include' }\n const res: Response = await fetch(linkUrl, fetchOptions)\n if (res.redirected)\n return res.url\n try {\n const data = await res.json()\n if (data?.url)\n return data.url\n }\n catch {}\n return linkUrl\n }\n\n async function unlinkAccount<P extends ProviderIds<TAuth>>(provider: P): Promise<boolean> {\n const token = getSessionToken()\n const fetchOptions: RequestInit = token ? { headers: { Authorization: `Bearer ${token}` } } : { credentials: 'include' }\n const res = await fetch(`${baseUrl}/unlink/${provider}`, { method: 'POST', ...fetchOptions })\n if (res.ok) {\n await refreshSession()\n return true\n }\n return false\n }\n\n async function signOut(): Promise<void> {\n clearSessionToken()\n const token = getSessionToken()\n const headers = token ? { Authorization: `Bearer ${token}` } : undefined\n await fetch(`${baseUrl}/signout`, token ? { method: 'POST', headers } : { method: 'POST', credentials: 'include' })\n await refreshSession()\n }\n\n async function startTauriBridge(): Promise<(() => void) | void> {\n if (!isTauri())\n return\n\n const { startAuthBridge } = await import('../../runtimes/tauri/index')\n const cleanup = await startAuthBridge(baseUrl, scheme, async (token) => {\n await applySessionToken(token)\n })\n return cleanup\n }\n\n /**\n * Fetch wrapper that automatically handles authentication:\n * - Adds Authorization header if a token is stored (Tauri/mobile)\n * - Falls back to credentials: 'include' for cookie-based auth (web)\n * - Automatically stores refreshed tokens from X-Refreshed-Token header\n */\n async function authFetch(input: RequestInfo | URL, init: RequestInit = {}): Promise<Response> {\n const token = getSessionToken()\n const headers = new Headers(init.headers)\n\n if (token)\n headers.set('Authorization', `Bearer ${token}`)\n\n const res = await globalThis.fetch(input, {\n ...init,\n headers,\n ...(!token && { credentials: 'include' as RequestCredentials }),\n })\n\n handleRefreshedToken(res)\n\n return res\n }\n\n return {\n get session() {\n return currentSession\n },\n fetch: authFetch,\n fetchSession,\n refreshSession,\n applySessionToken,\n handleRedirectCallback,\n onSessionChange,\n signIn,\n linkAccount,\n unlinkAccount,\n signOut,\n startTauriBridge,\n }\n}\n"],"mappings":"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","names":["BROWSER","BROWSER","parse","serialize","cleanUrl","signInWithTauri","linkAccountWithTauri","startAuthBridge","BROWSER","signInWithTauri","linkAccountWithTauri","startAuthBridge"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{applyCors as o,handleCallback as r,handleLink as m,handlePreflight as p,handleSession as t,handleSignIn as c,handleSignOut as h,handleToken as i,handleUnlink as j,verifyRequestOrigin as k}from"../../../chunk-
|
|
1
|
+
import{applyCors as o,handleCallback as r,handleLink as m,handlePreflight as p,handleSession as t,handleSignIn as c,handleSignOut as h,handleToken as i,handleUnlink as j,verifyRequestOrigin as k}from"../../../chunk-MRKRGLUT.js";import"../../../chunk-VB2ML4Q5.js";export{o as applyCors,r as handleCallback,m as handleLink,p as handlePreflight,t as handleSession,c as handleSignIn,h as handleSignOut,i as handleToken,j as handleUnlink,k as verifyRequestOrigin};//# sourceMappingURL=index.js.map
|