@zitadel/astro-auth 1.2.0

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/client.js ADDED
@@ -0,0 +1,157 @@
1
+ // src/client.ts
2
+ async function __getCsrfToken(prefix) {
3
+ const res = await fetch(`${prefix}/csrf`, {
4
+ method: "GET",
5
+ credentials: "same-origin",
6
+ headers: {
7
+ Accept: "application/json",
8
+ "X-Requested-With": "XMLHttpRequest"
9
+ },
10
+ cache: "no-store"
11
+ });
12
+ if (!res.ok) {
13
+ throw new Error(`Failed to fetch CSRF token (${res.status})`);
14
+ }
15
+ const json = await res.json().catch(() => {
16
+ throw new Error("CSRF endpoint returned non-JSON response");
17
+ });
18
+ const token = json?.csrfToken;
19
+ if (typeof token !== "string" || token.length === 0) {
20
+ throw new Error("Missing or invalid CSRF token");
21
+ }
22
+ return token;
23
+ }
24
+ function __normalizePathOnly(href) {
25
+ try {
26
+ const u = new URL(href, window.location.origin);
27
+ if (u.origin !== window.location.origin) {
28
+ return window.location.pathname + window.location.search + window.location.hash;
29
+ }
30
+ return u.pathname + u.search + u.hash;
31
+ } catch {
32
+ return window.location.pathname + window.location.search + window.location.hash;
33
+ }
34
+ }
35
+ function __safeRedirect(target, fallbackPathOnly) {
36
+ if (!target) return fallbackPathOnly;
37
+ try {
38
+ const u = new URL(target, window.location.origin);
39
+ if (u.origin !== window.location.origin) return fallbackPathOnly;
40
+ return u.href;
41
+ } catch {
42
+ return fallbackPathOnly;
43
+ }
44
+ }
45
+ async function signIn(providerId, options, authorizationParams) {
46
+ const initialCallbackUrl = options?.callbackUrl ?? window.location.href;
47
+ const redirect = options?.redirect ?? true;
48
+ const callbackUrl = __normalizePathOnly(initialCallbackUrl);
49
+ const prefix = options?.prefix ?? "/api/auth";
50
+ const { prefix: _p, callbackUrl: _c, redirect: _r, ...opts } = options ?? {};
51
+ const isCredentials = providerId === "credentials";
52
+ const isEmail = providerId === "email";
53
+ const isSupportingReturn = isCredentials || isEmail;
54
+ const signInUrl = isCredentials ? `${prefix}/callback/${providerId}` : `${prefix}/signin/${providerId}`;
55
+ if (!isSupportingReturn) {
56
+ const csrfToken2 = await __getCsrfToken(prefix);
57
+ const action = new URL(signInUrl, window.location.origin);
58
+ if (authorizationParams) {
59
+ for (const [k, v] of Object.entries(authorizationParams)) {
60
+ action.searchParams.set(k, v);
61
+ }
62
+ }
63
+ const form = document.createElement("form");
64
+ form.method = "POST";
65
+ form.action = action.pathname + action.search;
66
+ form.style.display = "none";
67
+ const fields = {
68
+ csrfToken: csrfToken2,
69
+ callbackUrl,
70
+ ...Object.fromEntries(
71
+ Object.entries(opts).map(([k, v]) => [k, String(v)])
72
+ )
73
+ };
74
+ for (const [name, value] of Object.entries(fields)) {
75
+ const input = document.createElement("input");
76
+ input.type = "hidden";
77
+ input.name = name;
78
+ input.value = value;
79
+ form.appendChild(input);
80
+ }
81
+ document.body.appendChild(form);
82
+ form.submit();
83
+ return;
84
+ }
85
+ const signInUrlWithParams = (() => {
86
+ const url = new URL(signInUrl, window.location.origin);
87
+ if (authorizationParams) {
88
+ for (const [k, v] of Object.entries(authorizationParams)) {
89
+ url.searchParams.set(k, v);
90
+ }
91
+ }
92
+ return url.pathname + url.search;
93
+ })();
94
+ const csrfToken = await __getCsrfToken(prefix);
95
+ const res = await fetch(signInUrlWithParams, {
96
+ method: "post",
97
+ credentials: "same-origin",
98
+ cache: "no-store",
99
+ headers: {
100
+ Accept: "application/json",
101
+ "Content-Type": "application/x-www-form-urlencoded",
102
+ "X-Auth-Return-Redirect": "1",
103
+ "X-Requested-With": "XMLHttpRequest"
104
+ },
105
+ body: new URLSearchParams({
106
+ ...opts,
107
+ csrfToken,
108
+ callbackUrl
109
+ })
110
+ });
111
+ const data = await res.clone().json().catch(() => ({}));
112
+ const error = data.url ? new URL(data.url).searchParams.get("error") : null;
113
+ if (redirect !== false || !isSupportingReturn || !error) {
114
+ const candidate = data.url ?? (res.redirected ? res.url : void 0);
115
+ const target = __safeRedirect(candidate, callbackUrl);
116
+ window.location.assign(target);
117
+ if (target && target.includes("#")) {
118
+ window.location.reload();
119
+ }
120
+ return;
121
+ } else {
122
+ return res;
123
+ }
124
+ }
125
+ async function signOut(options) {
126
+ const initialCallbackUrl = options?.callbackUrl ?? window.location.href;
127
+ const prefix = options?.prefix ?? "/api/auth";
128
+ const callbackUrl = __normalizePathOnly(initialCallbackUrl);
129
+ const csrfToken = await __getCsrfToken(prefix);
130
+ const res = await fetch(`${prefix}/signout`, {
131
+ method: "post",
132
+ credentials: "same-origin",
133
+ cache: "no-store",
134
+ headers: {
135
+ Accept: "application/json",
136
+ "Content-Type": "application/x-www-form-urlencoded",
137
+ "X-Auth-Return-Redirect": "1",
138
+ "X-Requested-With": "XMLHttpRequest"
139
+ },
140
+ body: new URLSearchParams({
141
+ csrfToken,
142
+ callbackUrl
143
+ })
144
+ });
145
+ const data = await res.json().catch(() => ({}));
146
+ const candidate = data.url ?? (res.redirected ? res.url : void 0);
147
+ const url = __safeRedirect(candidate, callbackUrl);
148
+ window.location.assign(url);
149
+ if (url.includes("#")) {
150
+ window.location.reload();
151
+ }
152
+ }
153
+ export {
154
+ signIn,
155
+ signOut
156
+ };
157
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts"],"sourcesContent":["import type {\n AstroSignInOptions,\n AstroSignOutParams,\n LiteralUnion,\n SignInAuthorizationParams,\n} from './types.js';\n\nasync function __getCsrfToken(prefix: string): Promise<string> {\n const res = await fetch(`${prefix}/csrf`, {\n method: 'GET',\n credentials: 'same-origin',\n headers: {\n Accept: 'application/json',\n 'X-Requested-With': 'XMLHttpRequest',\n },\n cache: 'no-store',\n });\n if (!res.ok) {\n throw new Error(`Failed to fetch CSRF token (${res.status})`);\n }\n const json: unknown = await res.json().catch(() => {\n throw new Error('CSRF endpoint returned non-JSON response');\n });\n const token = (json as { csrfToken?: string })?.csrfToken;\n if (typeof token !== 'string' || token.length === 0) {\n throw new Error('Missing or invalid CSRF token');\n }\n return token;\n}\n\nfunction __normalizePathOnly(href: string): string {\n try {\n const u = new URL(href, window.location.origin);\n if (u.origin !== window.location.origin) {\n return (\n window.location.pathname + window.location.search + window.location.hash\n );\n }\n return u.pathname + u.search + u.hash;\n } catch {\n return (\n window.location.pathname + window.location.search + window.location.hash\n );\n }\n}\n\nfunction __safeRedirect(\n target: string | null | undefined,\n fallbackPathOnly: string,\n): string {\n if (!target) return fallbackPathOnly;\n try {\n const u = new URL(target, window.location.origin);\n if (u.origin !== window.location.origin) return fallbackPathOnly;\n return u.href;\n } catch {\n return fallbackPathOnly;\n }\n}\n\n/**\n * Initiates a sign-in flow with the specified authentication provider.\n *\n * This function handles authentication for different provider types using\n * the appropriate mechanism for each. OAuth providers require browser form\n * submission to properly handle redirect chains, while credential-based\n * providers can use fetch for JSON responses.\n *\n * @typeParam P - The provider identifier type for type-safe provider names\n * @param providerId - The authentication provider identifier (e.g., 'github',\n * 'google', 'credentials')\n * @param options - Configuration options for the sign-in flow\n * @param authorizationParams - Additional OAuth authorization parameters to\n * pass to the provider\n * @returns Promise that resolves to Response for credential providers when\n * redirect is false and an error occurs, otherwise void\n *\n * @remarks\n * **Why Different Mechanisms for Different Providers:**\n *\n * OAuth Providers (github, google, etc.):\n * - Auth.js responds with a 302 redirect to the OAuth provider's login page\n * - Using `fetch()` causes the browser to follow redirects in JS context\n * - The OAuth provider expects a real browser navigation, not XHR\n * - Result: fetch completes but no visible navigation occurs\n * - Solution: Use real form submission to let browser handle redirects\n *\n * Credential/Email Providers:\n * - Auth.js responds with JSON containing success/error information\n * - These providers need to return Response objects for error handling\n * - Using `fetch()` allows access to response data and conditional redirects\n * - Result: Can detect errors and optionally suppress redirect\n * - Solution: Use fetch with JSON response handling\n *\n * **Security Considerations:**\n * - All requests include CSRF token protection\n * - Callback URLs are normalized to prevent open redirect attacks\n * - Only same-origin URLs are allowed for redirects\n *\n * **Browser Compatibility:**\n * - Form submission approach works in all browsers including those that\n * restrict fetch() behavior for cross-origin redirects\n * - Hash-based navigation triggers explicit reload for proper routing\n *\n * @example OAuth Provider Sign-In\n * ```ts\n * // Triggers form submission and browser navigation to GitHub\n * await signIn('github')\n * ```\n *\n * @example OAuth with Custom Callback\n * ```ts\n * await signIn('google', {\n * callbackUrl: '/dashboard'\n * })\n * ```\n *\n * @example OAuth with Authorization Parameters\n * ```ts\n * await signIn('google', undefined, {\n * prompt: 'consent',\n * login_hint: 'user@example.com'\n * })\n * ```\n *\n * @example Credential Provider with Error Handling\n * ```ts\n * const response = await signIn('credentials', {\n * redirect: false\n * }, {\n * username: 'user',\n * password: 'pass'\n * })\n *\n * if (response) {\n * const data = await response.json()\n * if (data.error) {\n * console.error('Sign in failed:', data.error)\n * }\n * }\n * ```\n *\n * @public\n */\nexport async function signIn<P extends string | undefined = undefined>(\n providerId?: LiteralUnion<P extends string ? P | string : string>,\n options?: AstroSignInOptions,\n authorizationParams?: SignInAuthorizationParams,\n): Promise<Response | void> {\n const initialCallbackUrl = options?.callbackUrl ?? window.location.href;\n const redirect = options?.redirect ?? true;\n\n const callbackUrl = __normalizePathOnly(initialCallbackUrl);\n\n const prefix = options?.prefix ?? '/api/auth';\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { prefix: _p, callbackUrl: _c, redirect: _r, ...opts } = options ?? {};\n\n const isCredentials = providerId === 'credentials';\n const isEmail = providerId === 'email';\n const isSupportingReturn = isCredentials || isEmail;\n\n const signInUrl = isCredentials\n ? `${prefix}/callback/${providerId}`\n : `${prefix}/signin/${providerId}`;\n\n // ========================================================================\n // OAuth Provider Flow: Use Form Submission\n // ========================================================================\n // OAuth providers (GitHub, Google, etc.) require browser navigation to\n // follow the 302 redirect chain to the provider's authorization page.\n // Using fetch() keeps the redirect in JavaScript context, preventing the\n // actual navigation from occurring. Form submission allows the browser to\n // handle the redirect naturally.\n if (!isSupportingReturn) {\n const csrfToken: string = await __getCsrfToken(prefix);\n\n // Build the form action URL with authorization parameters\n const action = new URL(signInUrl, window.location.origin);\n if (authorizationParams) {\n for (const [k, v] of Object.entries(authorizationParams)) {\n action.searchParams.set(k, v);\n }\n }\n\n // Create a hidden form to submit the authentication request\n const form = document.createElement('form');\n form.method = 'POST';\n form.action = action.pathname + action.search;\n form.style.display = 'none';\n\n // Add required fields for Auth.js\n const fields: Record<string, string> = {\n csrfToken,\n callbackUrl,\n ...Object.fromEntries(\n Object.entries(opts).map(([k, v]) => [k, String(v)]),\n ),\n };\n\n for (const [name, value] of Object.entries(fields)) {\n const input = document.createElement('input');\n input.type = 'hidden';\n input.name = name;\n input.value = value;\n form.appendChild(input);\n }\n\n document.body.appendChild(form);\n form.submit();\n return;\n }\n\n // ========================================================================\n // Credential/Email Provider Flow: Use Fetch\n // ========================================================================\n // Credential and email providers return JSON responses that may contain\n // error information. Using fetch() allows us to inspect the response and\n // conditionally handle redirects based on the redirect option and error\n // state. This enables the redirect: false pattern for error handling.\n\n const signInUrlWithParams = (() => {\n const url = new URL(signInUrl, window.location.origin);\n if (authorizationParams) {\n for (const [k, v] of Object.entries(authorizationParams)) {\n url.searchParams.set(k, v);\n }\n }\n return url.pathname + url.search;\n })();\n\n const csrfToken: string = await __getCsrfToken(prefix);\n\n const res = await fetch(signInUrlWithParams, {\n method: 'post',\n credentials: 'same-origin',\n cache: 'no-store',\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/x-www-form-urlencoded',\n 'X-Auth-Return-Redirect': '1',\n 'X-Requested-With': 'XMLHttpRequest',\n },\n body: new URLSearchParams({\n ...opts,\n csrfToken,\n callbackUrl,\n }),\n });\n\n const data: { url?: string } = await res\n .clone()\n .json()\n .catch(() => ({}));\n const error = data.url ? new URL(data.url).searchParams.get('error') : null;\n\n // Redirect unless explicitly disabled and an error occurred\n if (redirect !== false || !isSupportingReturn || !error) {\n const candidate = data.url ?? (res.redirected ? res.url : undefined);\n const target = __safeRedirect(candidate, callbackUrl);\n window.location.assign(target);\n\n // Force reload for hash-based navigation\n if (target && target.includes('#')) {\n window.location.reload();\n }\n return;\n } else {\n // Return response for error handling when redirect is false\n return res;\n }\n}\n\nexport async function signOut(options?: AstroSignOutParams): Promise<void> {\n const initialCallbackUrl = options?.callbackUrl ?? window.location.href;\n const prefix = options?.prefix ?? '/api/auth';\n\n const callbackUrl = __normalizePathOnly(initialCallbackUrl);\n\n const csrfToken: string = await __getCsrfToken(prefix);\n\n const res = await fetch(`${prefix}/signout`, {\n method: 'post',\n credentials: 'same-origin',\n cache: 'no-store',\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/x-www-form-urlencoded',\n 'X-Auth-Return-Redirect': '1',\n 'X-Requested-With': 'XMLHttpRequest',\n },\n body: new URLSearchParams({\n csrfToken,\n callbackUrl,\n }),\n });\n\n const data: { url?: string } = await res.json().catch(() => ({}));\n const candidate = data.url ?? (res.redirected ? res.url : undefined);\n const url = __safeRedirect(candidate, callbackUrl);\n\n window.location.assign(url);\n\n if (url.includes('#')) {\n window.location.reload();\n }\n}\n"],"mappings":"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","names":["csrfToken"]}
@@ -0,0 +1,38 @@
1
+ ---
2
+ import { getSession } from '../server.js';
3
+ import { type FullAuthConfig } from '../config.js';
4
+ import authDefaultConfig from 'auth:config';
5
+
6
+ /**
7
+ * Props for the Session component.
8
+ *
9
+ * Allows overriding the authentication configuration for retrieving
10
+ * session data.
11
+ */
12
+ interface Props {
13
+ /**
14
+ * Optional authentication configuration override.
15
+ *
16
+ * If not provided, uses the default configuration from the
17
+ * auth:config virtual module.
18
+ */
19
+ authConfig?: FullAuthConfig;
20
+ }
21
+
22
+ const { authConfig = authDefaultConfig } = Astro.props as Props;
23
+
24
+ // Retrieve the current session using the provided or default auth config
25
+ let session = await getSession(Astro.request, authConfig);
26
+ ---
27
+
28
+ <!--
29
+ Render the default slot with session data passed as an argument.
30
+ This allows child components to access session information.
31
+
32
+ Note: Slots are rendered by Astro's template engine. User-provided
33
+ content is automatically escaped. Do not use set:html with user data
34
+ to prevent XSS vulnerabilities.
35
+ -->
36
+ <div>
37
+ <Fragment set:html={Astro.slots.render('default', [session])} />
38
+ </div>
@@ -0,0 +1,133 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types';
3
+ import type {
4
+ BuiltInProviders,
5
+ SignInOptions,
6
+ SignInAuthorizationParams,
7
+ } from '../types.js';
8
+
9
+ /**
10
+ * Props for the SignIn component.
11
+ *
12
+ * Extends standard HTML button attributes with authentication-specific
13
+ * options for configuring the sign-in behavior.
14
+ */
15
+ interface Props extends HTMLAttributes<'button'> {
16
+ /**
17
+ * The authentication provider to sign in with.
18
+ *
19
+ * Can be a built-in provider (e.g., 'google', 'github') or a custom
20
+ * provider identifier.
21
+ */
22
+ provider?: BuiltInProviders | string;
23
+
24
+ /**
25
+ * Additional options to pass to the signIn function.
26
+ *
27
+ * These options control the sign-in behavior, such as redirect URLs
28
+ * and callback handling.
29
+ */
30
+ options?: SignInOptions;
31
+
32
+ /**
33
+ * Authorization parameters to pass to the authentication provider.
34
+ *
35
+ * These are provider-specific parameters that customize the
36
+ * authentication flow.
37
+ */
38
+ authParams?: SignInAuthorizationParams;
39
+ }
40
+
41
+ // Generate a unique identifier for this component instance to avoid
42
+ // conflicts when multiple SignIn buttons are present on the same page
43
+ const key = crypto.randomUUID();
44
+
45
+ // noinspection JSUnusedGlobalSymbols
46
+ const { provider, options, authParams, ...attrs } = Astro.props;
47
+ attrs.class = `signin-button ${attrs.class ?? ''}`;
48
+ ---
49
+
50
+ <button
51
+ {...attrs}
52
+ data-signin-key={key}
53
+ data-signin-provider={provider}
54
+ data-signin-options={options ? JSON.stringify(options) : undefined}
55
+ data-signin-auth-params={authParams ? JSON.stringify(authParams) : undefined}
56
+ >
57
+ <slot />
58
+ </button>
59
+
60
+ <script>
61
+ import { signIn } from '../client.js';
62
+
63
+ function initializeSignInButton(button: Element) {
64
+ if (button instanceof HTMLElement && !button.dataset.signinInitialized) {
65
+ button.dataset.signinInitialized = 'true';
66
+
67
+ button.addEventListener(
68
+ 'click',
69
+ () => {
70
+ const provider = button.dataset.signinProvider;
71
+
72
+ // Guard JSON parsing to prevent errors from malformed data
73
+ let options;
74
+ let authParams;
75
+
76
+ try {
77
+ options = button.dataset.signinOptions
78
+ ? JSON.parse(button.dataset.signinOptions)
79
+ : undefined;
80
+ } catch (e) {
81
+ console.warn('[astro-auth] Invalid signin options JSON:', e);
82
+ options = undefined;
83
+ }
84
+
85
+ try {
86
+ authParams = button.dataset.signinAuthParams
87
+ ? JSON.parse(button.dataset.signinAuthParams)
88
+ : undefined;
89
+ } catch (e) {
90
+ console.warn('[astro-auth] Invalid signin authParams JSON:', e);
91
+ authParams = undefined;
92
+ }
93
+
94
+ signIn(provider, options, authParams);
95
+ },
96
+ { passive: true, capture: true },
97
+ );
98
+ }
99
+ }
100
+
101
+ document
102
+ .querySelectorAll('[data-signin-key]')
103
+ .forEach(initializeSignInButton);
104
+
105
+ const observer = new MutationObserver((mutations) => {
106
+ mutations.forEach((mutation) => {
107
+ mutation.addedNodes.forEach((node) => {
108
+ if (node instanceof HTMLElement) {
109
+ if (node.hasAttribute('data-signin-key')) {
110
+ initializeSignInButton(node);
111
+ }
112
+ node
113
+ .querySelectorAll('[data-signin-key]')
114
+ .forEach(initializeSignInButton);
115
+ }
116
+ });
117
+ });
118
+ });
119
+
120
+ observer.observe(document.body, { childList: true, subtree: true });
121
+
122
+ // Clean up observer when page becomes hidden or before unload
123
+ const cleanup = () => {
124
+ observer.disconnect();
125
+ };
126
+
127
+ window.addEventListener('beforeunload', cleanup);
128
+ document.addEventListener('visibilitychange', () => {
129
+ if (document.hidden) {
130
+ cleanup();
131
+ }
132
+ });
133
+ </script>
@@ -0,0 +1,99 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types';
3
+ import type { SignOutParams } from '../types.js';
4
+
5
+ /**
6
+ * Props for the SignOut component.
7
+ *
8
+ * Extends standard HTML button attributes with authentication-specific
9
+ * options for configuring the sign-out behavior.
10
+ */
11
+ interface Props extends HTMLAttributes<'button'> {
12
+ /**
13
+ * Optional parameters to pass to the signOut function.
14
+ *
15
+ * These parameters control the sign-out behavior, such as redirect
16
+ * URLs and callback handling.
17
+ */
18
+ params?: SignOutParams;
19
+ }
20
+
21
+ // Generate a unique identifier for this component instance to avoid
22
+ // conflicts when multiple SignOut buttons are present on the same page
23
+ const key = crypto.randomUUID();
24
+
25
+ // noinspection JSUnusedGlobalSymbols
26
+ const { params, ...attrs } = Astro.props;
27
+ attrs.class = `signout-button ${attrs.class ?? ''}`;
28
+ ---
29
+
30
+ <button
31
+ {...attrs}
32
+ data-signout-key={key}
33
+ data-signout-params={params ? JSON.stringify(params) : undefined}
34
+ >
35
+ <slot />
36
+ </button>
37
+
38
+ <script>
39
+ import { signOut } from '../client.js';
40
+
41
+ function initializeSignOutButton(button: Element) {
42
+ if (button instanceof HTMLElement && !button.dataset.signoutInitialized) {
43
+ button.dataset.signoutInitialized = 'true';
44
+
45
+ button.addEventListener(
46
+ 'click',
47
+ () => {
48
+ // Guard JSON parsing to prevent errors from malformed data
49
+ let params;
50
+
51
+ try {
52
+ params = button.dataset.signoutParams
53
+ ? JSON.parse(button.dataset.signoutParams)
54
+ : undefined;
55
+ } catch (e) {
56
+ console.warn('[astro-auth] Invalid signout params JSON:', e);
57
+ params = undefined;
58
+ }
59
+
60
+ signOut(params);
61
+ },
62
+ { passive: true, capture: true },
63
+ );
64
+ }
65
+ }
66
+
67
+ document
68
+ .querySelectorAll('[data-signout-key]')
69
+ .forEach(initializeSignOutButton);
70
+
71
+ const observer = new MutationObserver((mutations) => {
72
+ mutations.forEach((mutation) => {
73
+ mutation.addedNodes.forEach((node) => {
74
+ if (node instanceof HTMLElement) {
75
+ if (node.hasAttribute('data-signout-key')) {
76
+ initializeSignOutButton(node);
77
+ }
78
+ node
79
+ .querySelectorAll('[data-signout-key]')
80
+ .forEach(initializeSignOutButton);
81
+ }
82
+ });
83
+ });
84
+ });
85
+
86
+ observer.observe(document.body, { childList: true, subtree: true });
87
+
88
+ // Clean up observer when page becomes hidden or before unload
89
+ const cleanup = () => {
90
+ observer.disconnect();
91
+ };
92
+
93
+ window.addEventListener('beforeunload', cleanup);
94
+ document.addEventListener('visibilitychange', () => {
95
+ if (document.hidden) {
96
+ cleanup();
97
+ }
98
+ });
99
+ </script>
@@ -0,0 +1,9 @@
1
+ import { AstroComponentFactory } from 'astro/runtime/server/index.js';
2
+
3
+ declare const Auth: AstroComponentFactory;
4
+ // noinspection JSUnusedGlobalSymbols
5
+ declare const SignIn: AstroComponentFactory;
6
+ // noinspection JSUnusedGlobalSymbols
7
+ declare const SignOut: AstroComponentFactory;
8
+
9
+ export { Auth, SignIn, SignOut };
@@ -0,0 +1,10 @@
1
+ // src/components/index.ts
2
+ import { default as default2 } from "./Auth.astro";
3
+ import { default as default3 } from "./SignIn.astro";
4
+ import { default as default4 } from "./SignOut.astro";
5
+ export {
6
+ default2 as Auth,
7
+ default3 as SignIn,
8
+ default4 as SignOut
9
+ };
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/index.ts"],"sourcesContent":["/**\n * Re-exports Astro components for authentication UI.\n *\n * These components provide pre-built UI elements for sign-in, sign-out,\n * and session management in Astro applications.\n *\n * @public\n */\n\n// Component exports are consumed by end users importing from the package.\n// IDEs cannot detect usage across package boundaries, resulting in false\n// positive unused export warnings.\n// noinspection JSUnusedGlobalSymbols\nexport { default as Auth } from './Auth.astro';\n// noinspection JSUnusedGlobalSymbols\nexport { default as SignIn } from './SignIn.astro';\n// noinspection JSUnusedGlobalSymbols\nexport { default as SignOut } from './SignOut.astro';\n"],"mappings":";AAaA,SAAoB,WAAXA,gBAAuB;AAEhC,SAAoB,WAAXA,gBAAyB;AAElC,SAAoB,WAAXA,gBAA0B;","names":["default"]}
@@ -0,0 +1,104 @@
1
+ import { AuthConfig } from '@auth/core/types';
2
+
3
+ /**
4
+ * Configuration options specific to the Astro integration.
5
+ *
6
+ * @public
7
+ */
8
+ interface AstroIntegrationOptions {
9
+ /**
10
+ * Base path for authentication routes.
11
+ *
12
+ * @defaultValue '/api/auth'
13
+ *
14
+ * @example
15
+ * ```js
16
+ * authAstro({ prefix: '/auth' })
17
+ * // Routes will be available at /auth/signin, /auth/signout, etc.
18
+ * ```
19
+ */
20
+ prefix?: string;
21
+ /**
22
+ * Whether the integration should automatically inject authentication
23
+ * endpoints.
24
+ *
25
+ * Set to `false` if you want to manually define authentication routes.
26
+ *
27
+ * @defaultValue true
28
+ */
29
+ injectEndpoints?: boolean;
30
+ /**
31
+ * Path to the authentication configuration file.
32
+ *
33
+ * @defaultValue './auth.config'
34
+ *
35
+ * @example
36
+ * ```js
37
+ * authAstro({ configFile: './config/authentication.ts' })
38
+ * ```
39
+ */
40
+ configFile?: string;
41
+ }
42
+ /**
43
+ * Complete authentication configuration merging Astro-specific options
44
+ * with Auth.js core configuration.
45
+ *
46
+ * @public
47
+ */
48
+ interface FullAuthConfig extends AstroIntegrationOptions, Omit<AuthConfig, 'raw'> {
49
+ }
50
+ /**
51
+ * Environment variables structure used by Auth.js configuration.
52
+ *
53
+ * @public
54
+ */
55
+ interface AuthEnvVars {
56
+ AUTH_SECRET?: string;
57
+ AUTH_TRUST_HOST?: string;
58
+ VERCEL?: string;
59
+ CF_PAGES?: string;
60
+ NODE_ENV?: string;
61
+ }
62
+ /**
63
+ * Helper function to define authentication configuration with type safety.
64
+ *
65
+ * This function applies default values and reads environment variables to
66
+ * provide a complete configuration object. Environment variables are only
67
+ * used as fallbacks when values are not explicitly provided in the config.
68
+ *
69
+ * Environment variables read (in order of precedence for `trustHost`):
70
+ * - `config.trustHost`: Explicit config value (highest priority)
71
+ * - `AUTH_TRUST_HOST`: Whether to trust the X-Forwarded-Host header ("1"/"true"/"yes"/"on")
72
+ * - `VERCEL`: Automatically set by Vercel (any truthy value enables trustHost)
73
+ * - `CF_PAGES`: Automatically set by Cloudflare Pages (any truthy value enables trustHost)
74
+ * - `NODE_ENV`: Node environment (trustHost enabled if not 'production')
75
+ *
76
+ * For `secret`:
77
+ * - `config.secret`: Explicit config value (highest priority)
78
+ * - `AUTH_SECRET`: Environment variable fallback
79
+ *
80
+ * @param config - Authentication configuration object
81
+ * @param envOverride - Optional environment variables for testing
82
+ * @returns Configuration with defaults applied
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * import { defineConfig } from 'astro-auth'
87
+ * import GitHub from '@auth/core/providers/github'
88
+ *
89
+ * export default defineConfig({
90
+ * providers: [
91
+ * GitHub({
92
+ * clientId: process.env.GITHUB_ID,
93
+ * clientSecret: process.env.GITHUB_SECRET
94
+ * })
95
+ * ],
96
+ * secret: process.env.AUTH_SECRET
97
+ * })
98
+ * ```
99
+ *
100
+ * @public
101
+ */
102
+ declare const defineConfig: (config: Readonly<FullAuthConfig>, envOverride?: AuthEnvVars) => FullAuthConfig;
103
+
104
+ export { type AstroIntegrationOptions as A, type FullAuthConfig as F, type AuthEnvVars as a, defineConfig as d };
@@ -0,0 +1,27 @@
1
+ import { AstroIntegration } from 'astro';
2
+ import { A as AstroIntegrationOptions } from './config-lAdfi49m.js';
3
+ export { a as AuthEnvVars, F as FullAuthConfig, d as defineConfig } from './config-lAdfi49m.js';
4
+ import '@auth/core/types';
5
+
6
+ /**
7
+ * Creates an Astro integration for authentication using Auth.js.
8
+ *
9
+ * This integration handles:
10
+ * - Virtual module configuration for auth settings
11
+ * - Automatic route injection for authentication endpoints
12
+ * - Vite configuration for proper module resolution
13
+ *
14
+ * @param config - Configuration options for the integration
15
+ * @returns Configured Astro integration
16
+ * @throws {Error} When no adapter is configured (server-side rendering is required).
17
+ *
18
+ * @remarks
19
+ * This integration requires server-side rendering. Ensure you have
20
+ * configured an Astro adapter (e.g., `@astrojs/node`, `@astrojs/vercel`)
21
+ * in your Astro config.
22
+ *
23
+ * @public
24
+ */
25
+ declare const _default: (config?: AstroIntegrationOptions) => AstroIntegration;
26
+
27
+ export { AstroIntegrationOptions, _default as default };