@dbx-tools/shared-auth 0.9.5 → 0.9.7

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/index.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  // Hand edits are overwritten on the next watch; this file is read-only.
4
4
 
5
5
  export const PACKAGE_IDENTIFIER = "@dbx-tools/shared-auth";
6
- export const PACKAGE_VERSION = "0.9.5";
6
+ export const PACKAGE_VERSION = "0.9.7";
7
7
  export * as auth from "./src/auth.ts";
8
- export { SESSION_COOKIE_NAME, authRequestSchema, authRequestResultSchema, authVerifySchema, authVerifyResultSchema, authLogoutResultSchema, authStatusSchema } from "./src/auth.ts";
8
+ export { AUTH_BASE_PATH, SESSION_COOKIE_NAME, authRequestSchema, authRequestResultSchema, authVerifySchema, authVerifyResultSchema, authLogoutResultSchema, authStatusSchema } from "./src/auth.ts";
9
9
  export type { AuthRequest, AuthRequestResult, AuthVerify, AuthVerifyResult, AuthLogoutResult, AuthStatus } from "./src/auth.ts";
package/lib/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export declare const PACKAGE_IDENTIFIER = "@dbx-tools/shared-auth";
2
- export declare const PACKAGE_VERSION = "0.9.5";
2
+ export declare const PACKAGE_VERSION = "0.9.7";
3
3
  export * as auth from "./src/auth.ts";
4
- export { SESSION_COOKIE_NAME, authRequestSchema, authRequestResultSchema, authVerifySchema, authVerifyResultSchema, authLogoutResultSchema, authStatusSchema } from "./src/auth.ts";
4
+ export { AUTH_BASE_PATH, SESSION_COOKIE_NAME, authRequestSchema, authRequestResultSchema, authVerifySchema, authVerifyResultSchema, authLogoutResultSchema, authStatusSchema } from "./src/auth.ts";
5
5
  export type { AuthRequest, AuthRequestResult, AuthVerify, AuthVerifyResult, AuthLogoutResult, AuthStatus } from "./src/auth.ts";
package/lib/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // Regenerated from the exporting modules in ./src.
3
3
  // Hand edits are overwritten on the next watch; this file is read-only.
4
4
  export const PACKAGE_IDENTIFIER = "@dbx-tools/shared-auth";
5
- export const PACKAGE_VERSION = "0.9.5";
5
+ export const PACKAGE_VERSION = "0.9.7";
6
6
  export * as auth from "./src/auth.js";
7
- export { SESSION_COOKIE_NAME, authRequestSchema, authRequestResultSchema, authVerifySchema, authVerifyResultSchema, authLogoutResultSchema, authStatusSchema } from "./src/auth.js";
8
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwyQ0FBMkM7QUFDM0MsbURBQW1EO0FBQ25ELHdFQUF3RTtBQUV4RSxNQUFNLENBQUMsTUFBTSxrQkFBa0IsR0FBRyx3QkFBd0IsQ0FBQztBQUMzRCxNQUFNLENBQUMsTUFBTSxlQUFlLEdBQUcsT0FBTyxDQUFDO0FBQ3ZDLE9BQU8sS0FBSyxJQUFJLE1BQU0sZUFBZSxDQUFDO0FBQ3RDLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxpQkFBaUIsRUFBRSx1QkFBdUIsRUFBRSxnQkFBZ0IsRUFBRSxzQkFBc0IsRUFBRSxzQkFBc0IsRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLGVBQWUsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIEdFTkVSQVRFRCBieSBwcm9qZW4gd2F0Y2ggLSBETyBOT1QgRURJVC5cbi8vIFJlZ2VuZXJhdGVkIGZyb20gdGhlIGV4cG9ydGluZyBtb2R1bGVzIGluIC4vc3JjLlxuLy8gSGFuZCBlZGl0cyBhcmUgb3ZlcndyaXR0ZW4gb24gdGhlIG5leHQgd2F0Y2g7IHRoaXMgZmlsZSBpcyByZWFkLW9ubHkuXG5cbmV4cG9ydCBjb25zdCBQQUNLQUdFX0lERU5USUZJRVIgPSBcIkBkYngtdG9vbHMvc2hhcmVkLWF1dGhcIjtcbmV4cG9ydCBjb25zdCBQQUNLQUdFX1ZFUlNJT04gPSBcIjAuOS41XCI7XG5leHBvcnQgKiBhcyBhdXRoIGZyb20gXCIuL3NyYy9hdXRoLnRzXCI7XG5leHBvcnQgeyBTRVNTSU9OX0NPT0tJRV9OQU1FLCBhdXRoUmVxdWVzdFNjaGVtYSwgYXV0aFJlcXVlc3RSZXN1bHRTY2hlbWEsIGF1dGhWZXJpZnlTY2hlbWEsIGF1dGhWZXJpZnlSZXN1bHRTY2hlbWEsIGF1dGhMb2dvdXRSZXN1bHRTY2hlbWEsIGF1dGhTdGF0dXNTY2hlbWEgfSBmcm9tIFwiLi9zcmMvYXV0aC50c1wiO1xuZXhwb3J0IHR5cGUgeyBBdXRoUmVxdWVzdCwgQXV0aFJlcXVlc3RSZXN1bHQsIEF1dGhWZXJpZnksIEF1dGhWZXJpZnlSZXN1bHQsIEF1dGhMb2dvdXRSZXN1bHQsIEF1dGhTdGF0dXMgfSBmcm9tIFwiLi9zcmMvYXV0aC50c1wiO1xuIl19
7
+ export { AUTH_BASE_PATH, SESSION_COOKIE_NAME, authRequestSchema, authRequestResultSchema, authVerifySchema, authVerifyResultSchema, authLogoutResultSchema, authStatusSchema } from "./src/auth.js";
8
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwyQ0FBMkM7QUFDM0MsbURBQW1EO0FBQ25ELHdFQUF3RTtBQUV4RSxNQUFNLENBQUMsTUFBTSxrQkFBa0IsR0FBRyx3QkFBd0IsQ0FBQztBQUMzRCxNQUFNLENBQUMsTUFBTSxlQUFlLEdBQUcsT0FBTyxDQUFDO0FBQ3ZDLE9BQU8sS0FBSyxJQUFJLE1BQU0sZUFBZSxDQUFDO0FBQ3RDLE9BQU8sRUFBRSxjQUFjLEVBQUUsbUJBQW1CLEVBQUUsaUJBQWlCLEVBQUUsdUJBQXVCLEVBQUUsZ0JBQWdCLEVBQUUsc0JBQXNCLEVBQUUsc0JBQXNCLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxlQUFlLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBHRU5FUkFURUQgYnkgcHJvamVuIHdhdGNoIC0gRE8gTk9UIEVESVQuXG4vLyBSZWdlbmVyYXRlZCBmcm9tIHRoZSBleHBvcnRpbmcgbW9kdWxlcyBpbiAuL3NyYy5cbi8vIEhhbmQgZWRpdHMgYXJlIG92ZXJ3cml0dGVuIG9uIHRoZSBuZXh0IHdhdGNoOyB0aGlzIGZpbGUgaXMgcmVhZC1vbmx5LlxuXG5leHBvcnQgY29uc3QgUEFDS0FHRV9JREVOVElGSUVSID0gXCJAZGJ4LXRvb2xzL3NoYXJlZC1hdXRoXCI7XG5leHBvcnQgY29uc3QgUEFDS0FHRV9WRVJTSU9OID0gXCIwLjkuN1wiO1xuZXhwb3J0ICogYXMgYXV0aCBmcm9tIFwiLi9zcmMvYXV0aC50c1wiO1xuZXhwb3J0IHsgQVVUSF9CQVNFX1BBVEgsIFNFU1NJT05fQ09PS0lFX05BTUUsIGF1dGhSZXF1ZXN0U2NoZW1hLCBhdXRoUmVxdWVzdFJlc3VsdFNjaGVtYSwgYXV0aFZlcmlmeVNjaGVtYSwgYXV0aFZlcmlmeVJlc3VsdFNjaGVtYSwgYXV0aExvZ291dFJlc3VsdFNjaGVtYSwgYXV0aFN0YXR1c1NjaGVtYSB9IGZyb20gXCIuL3NyYy9hdXRoLnRzXCI7XG5leHBvcnQgdHlwZSB7IEF1dGhSZXF1ZXN0LCBBdXRoUmVxdWVzdFJlc3VsdCwgQXV0aFZlcmlmeSwgQXV0aFZlcmlmeVJlc3VsdCwgQXV0aExvZ291dFJlc3VsdCwgQXV0aFN0YXR1cyB9IGZyb20gXCIuL3NyYy9hdXRoLnRzXCI7XG4iXX0=
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Browser-only Better Auth client shared by React and the hosted tunnel login.
3
+ *
4
+ * The operation helpers own both network cancellation and the active WebAuthn
5
+ * ceremony. Each operation carries an identity token so cleanup from an older
6
+ * React effect cannot cancel a newer manual attempt.
7
+ *
8
+ * @module
9
+ */
10
+ import { type Passkey } from "@better-auth/passkey/client";
11
+ import type { AuthStatus } from "./auth.ts";
12
+ export { AUTH_BASE_PATH } from "./_route.ts";
13
+ export type { Passkey } from "@better-auth/passkey/client";
14
+ /** A cancellable passkey request and WebAuthn ceremony. */
15
+ export interface PasskeyOperation {
16
+ readonly result: Promise<boolean>;
17
+ cancel(): void;
18
+ }
19
+ /** Start a passkey sign-in whose lifetime is explicitly owned by the caller. */
20
+ export declare function beginPasskeySignIn(autoFill?: boolean): PasskeyOperation;
21
+ /** Start a passkey registration whose lifetime is explicitly owned by the caller. */
22
+ export declare function beginPasskeyEnrollment(name?: string): PasskeyOperation;
23
+ /** Cancel the currently active passkey request and browser ceremony, if any. */
24
+ export declare function cancelPasskeyOperation(): void;
25
+ /** Read the current browser session status from the shared auth endpoint. */
26
+ export declare function getAuthStatus(): Promise<AuthStatus>;
27
+ /** End the current browser session and follow the server's same-origin redirect. */
28
+ export declare function logout(): Promise<boolean>;
29
+ /** Request an email one-time password for the supplied address. */
30
+ export declare function requestEmailOtp(email: string): Promise<boolean>;
31
+ /** Verify an email one-time password and establish a browser session. */
32
+ export declare function verifyEmailOtp(email: string, otp: string, name: string): Promise<boolean>;
33
+ /** Authenticate with a passkey, optionally using conditional browser mediation. */
34
+ export declare function signInPasskey(autoFill?: boolean): Promise<boolean>;
35
+ /** Enroll a passkey for the current authenticated account. */
36
+ export declare function addPasskey(name?: string): Promise<boolean>;
37
+ /** List passkeys enrolled for the current authenticated account. */
38
+ export declare function listPasskeys(): Promise<Passkey[]>;
39
+ /** Rename an enrolled passkey. */
40
+ export declare function renamePasskey(id: string, name: string): Promise<boolean>;
41
+ /** Remove an enrolled passkey. */
42
+ export declare function removePasskey(id: string): Promise<boolean>;
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Browser-only Better Auth client shared by React and the hosted tunnel login.
3
+ *
4
+ * The operation helpers own both network cancellation and the active WebAuthn
5
+ * ceremony. Each operation carries an identity token so cleanup from an older
6
+ * React effect cannot cancel a newer manual attempt.
7
+ *
8
+ * @module
9
+ */
10
+ import { passkeyClient } from "@better-auth/passkey/client";
11
+ import { WebAuthnAbortService } from "@simplewebauthn/browser";
12
+ import { createAuthClient } from "better-auth/client";
13
+ import { emailOTPClient } from "better-auth/client/plugins";
14
+ import { AUTH_BASE_PATH } from "./_route.js";
15
+ export { AUTH_BASE_PATH } from "./_route.js";
16
+ function browserLocation() {
17
+ return globalThis.location;
18
+ }
19
+ function baseURL() {
20
+ const origin = browserLocation()?.origin ?? "http://localhost";
21
+ return `${origin}${AUTH_BASE_PATH}`;
22
+ }
23
+ function browserClient(signal) {
24
+ return createAuthClient({
25
+ baseURL: baseURL(),
26
+ ...(signal ? { fetchOptions: { signal } } : {}),
27
+ plugins: [emailOTPClient(), passkeyClient()],
28
+ });
29
+ }
30
+ const client = browserClient();
31
+ function record(value) {
32
+ return typeof value === "object" && value !== null
33
+ ? value
34
+ : undefined;
35
+ }
36
+ function authStatus(value) {
37
+ const item = record(value);
38
+ if (!item ||
39
+ typeof item.authenticated !== "boolean" ||
40
+ typeof item.enabled !== "boolean" ||
41
+ (item.email !== undefined && typeof item.email !== "string") ||
42
+ (item.passkeysEnabled !== undefined && typeof item.passkeysEnabled !== "boolean")) {
43
+ throw new Error("Auth status returned an invalid response");
44
+ }
45
+ return {
46
+ authenticated: item.authenticated,
47
+ enabled: item.enabled,
48
+ ...(typeof item.email === "string" ? { email: item.email } : {}),
49
+ ...(typeof item.passkeysEnabled === "boolean" ? { passkeysEnabled: item.passkeysEnabled } : {}),
50
+ };
51
+ }
52
+ let activePasskeyOperation;
53
+ function isAbort(error) {
54
+ return error instanceof DOMException && error.name === "AbortError";
55
+ }
56
+ function beginPasskeyOperation(invoke) {
57
+ activePasskeyOperation?.cancel();
58
+ const id = Symbol("passkey-operation");
59
+ const controller = new AbortController();
60
+ let cancelled = false;
61
+ const cancel = () => {
62
+ if (activePasskeyOperation?.id !== id)
63
+ return;
64
+ activePasskeyOperation = undefined;
65
+ cancelled = true;
66
+ controller.abort();
67
+ WebAuthnAbortService.cancelCeremony();
68
+ };
69
+ activePasskeyOperation = { id, cancel };
70
+ const result = invoke(browserClient(controller.signal))
71
+ .then((response) => !cancelled && response.error === null)
72
+ .catch((error) => {
73
+ if (cancelled || isAbort(error))
74
+ return false;
75
+ throw error;
76
+ })
77
+ .finally(() => {
78
+ if (activePasskeyOperation?.id === id)
79
+ activePasskeyOperation = undefined;
80
+ });
81
+ return { result, cancel };
82
+ }
83
+ /** Start a passkey sign-in whose lifetime is explicitly owned by the caller. */
84
+ export function beginPasskeySignIn(autoFill = false) {
85
+ return beginPasskeyOperation((operationClient) => operationClient.signIn.passkey({ autoFill }));
86
+ }
87
+ /** Start a passkey registration whose lifetime is explicitly owned by the caller. */
88
+ export function beginPasskeyEnrollment(name) {
89
+ return beginPasskeyOperation((operationClient) => operationClient.passkey.addPasskey(name ? { name } : undefined));
90
+ }
91
+ /** Cancel the currently active passkey request and browser ceremony, if any. */
92
+ export function cancelPasskeyOperation() {
93
+ activePasskeyOperation?.cancel();
94
+ }
95
+ /** Read the current browser session status from the shared auth endpoint. */
96
+ export async function getAuthStatus() {
97
+ const response = await fetch(`${AUTH_BASE_PATH}/status`, { credentials: "include" });
98
+ if (!response.ok)
99
+ throw new Error(`Auth status failed with ${response.status}`);
100
+ return authStatus(await response.json());
101
+ }
102
+ /** End the current browser session and follow the server's same-origin redirect. */
103
+ export async function logout() {
104
+ const response = await fetch(`${AUTH_BASE_PATH}/logout`, {
105
+ method: "POST",
106
+ credentials: "include",
107
+ });
108
+ const result = record(await response.json());
109
+ if (!response.ok || result?.ok !== true || typeof result.redirectTo !== "string")
110
+ return false;
111
+ browserLocation()?.assign(result.redirectTo);
112
+ return true;
113
+ }
114
+ /** Request an email one-time password for the supplied address. */
115
+ export async function requestEmailOtp(email) {
116
+ const result = await client.emailOtp.sendVerificationOtp({ email, type: "sign-in" });
117
+ return result.error === null;
118
+ }
119
+ /** Verify an email one-time password and establish a browser session. */
120
+ export async function verifyEmailOtp(email, otp, name) {
121
+ const result = await client.signIn.emailOtp({ email, otp, name });
122
+ return result.error === null;
123
+ }
124
+ /** Authenticate with a passkey, optionally using conditional browser mediation. */
125
+ export async function signInPasskey(autoFill = false) {
126
+ return beginPasskeySignIn(autoFill).result;
127
+ }
128
+ /** Enroll a passkey for the current authenticated account. */
129
+ export async function addPasskey(name) {
130
+ return beginPasskeyEnrollment(name).result;
131
+ }
132
+ /** List passkeys enrolled for the current authenticated account. */
133
+ export async function listPasskeys() {
134
+ const result = await client.passkey.listUserPasskeys();
135
+ if (result.error) {
136
+ throw new Error(result.error.message ?? `Passkey listing failed with ${result.error.status}`);
137
+ }
138
+ return result.data ?? [];
139
+ }
140
+ /** Rename an enrolled passkey. */
141
+ export async function renamePasskey(id, name) {
142
+ const result = await client.passkey.updatePasskey({ id, name });
143
+ return result.error === null;
144
+ }
145
+ /** Remove an enrolled passkey. */
146
+ export async function removePasskey(id) {
147
+ const result = await client.passkey.deletePasskey({ id });
148
+ return result.error === null;
149
+ }
150
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiX2NsaWVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9fY2xpZW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7OztHQVFHO0FBRUgsT0FBTyxFQUFFLGFBQWEsRUFBZ0IsTUFBTSw2QkFBNkIsQ0FBQztBQUMxRSxPQUFPLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUMvRCxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUN0RCxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFFNUQsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUc3QyxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBUTdDLFNBQVMsZUFBZTtJQUN0QixPQUFRLFVBQXdELENBQUMsUUFBUSxDQUFDO0FBQzVFLENBQUM7QUFFRCxTQUFTLE9BQU87SUFDZCxNQUFNLE1BQU0sR0FBRyxlQUFlLEVBQUUsRUFBRSxNQUFNLElBQUksa0JBQWtCLENBQUM7SUFDL0QsT0FBTyxHQUFHLE1BQU0sR0FBRyxjQUFjLEVBQUUsQ0FBQztBQUN0QyxDQUFDO0FBRUQsU0FBUyxhQUFhLENBQUMsTUFBb0I7SUFDekMsT0FBTyxnQkFBZ0IsQ0FBQztRQUN0QixPQUFPLEVBQUUsT0FBTyxFQUFFO1FBQ2xCLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsWUFBWSxFQUFFLEVBQUUsTUFBTSxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1FBQy9DLE9BQU8sRUFBRSxDQUFDLGNBQWMsRUFBRSxFQUFFLGFBQWEsRUFBRSxDQUFDO0tBQzdDLENBQUMsQ0FBQztBQUNMLENBQUM7QUFFRCxNQUFNLE1BQU0sR0FBRyxhQUFhLEVBQUUsQ0FBQztBQUUvQixTQUFTLE1BQU0sQ0FBQyxLQUFjO0lBQzVCLE9BQU8sT0FBTyxLQUFLLEtBQUss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LHNCQUFzQixDQUFDLElBQUksQ0FBQyxDQUFDLE1BQU0sQ0FBQztBQUM3QyxDQUFDO0FBRUQsb0VBQW9FO0FBQ3BFLE1BQU0sQ0FBQyxLQUFLLFVBQVUsWUFBWTtJQUNoQyxNQUFNLE1BQU0sR0FBRyxNQUFNLE1BQU0sQ0FBQyxPQUFPLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQztJQUN2RCxJQUFJLE1BQU0sQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUNqQixNQUFNLElBQUksS0FBSyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsT0FBTyxJQUFJLCtCQUErQixNQUFNLENBQUMsS0FBSyxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUM7SUFDaEcsQ0FBQztJQUNELE9BQU8sTUFBTSxDQUFDLElBQUksSUFBSSxFQUFFLENBQUM7QUFDM0IsQ0FBQztBQUVELGtDQUFrQztBQUNsQyxNQUFNLENBQUMsS0FBSyxVQUFVLGFBQWEsQ0FBQyxFQUFVLEVBQUUsSUFBWTtJQUMxRCxNQUFNLE1BQU0sR0FBRyxNQUFNLE1BQU0sQ0FBQyxPQUFPLENBQUMsYUFBYSxDQUFDLEVBQUUsRUFBRSxFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7SUFDaEUsT0FBTyxNQUFNLENBQUMsS0FBSyxLQUFLLElBQUksQ0FBQztBQUMvQixDQUFDO0FBRUQsa0NBQWtDO0FBQ2xDLE1BQU0sQ0FBQyxLQUFLLFVBQVUsYUFBYSxDQUFDLEVBQVU7SUFDNUMsTUFBTSxNQUFNLEdBQUcsTUFBTSxNQUFNLENBQUMsT0FBTyxDQUFDLGFBQWEsQ0FBQyxFQUFFLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFDMUQsT0FBTyxNQUFNLENBQUMsS0FBSyxLQUFLLElBQUksQ0FBQztBQUMvQixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBCcm93c2VyLW9ubHkgQmV0dGVyIEF1dGggY2xpZW50IHNoYXJlZCBieSBSZWFjdCBhbmQgdGhlIGhvc3RlZCB0dW5uZWwgbG9naW4uXG4gKlxuICogVGhlIG9wZXJhdGlvbiBoZWxwZXJzIG93biBib3RoIG5ldHdvcmsgY2FuY2VsbGF0aW9uIGFuZCB0aGUgYWN0aXZlIFdlYkF1dGhuXG4gKiBjZXJlbW9ueS4gRWFjaCBvcGVyYXRpb24gY2FycmllcyBhbiBpZGVudGl0eSB0b2tlbiBzbyBjbGVhbnVwIGZyb20gYW4gb2xkZXJcbiAqIFJlYWN0IGVmZmVjdCBjYW5ub3QgY2FuY2VsIGEgbmV3ZXIgbWFudWFsIGF0dGVtcHQuXG4gKlxuICogQG1vZHVsZVxuICovXG5cbmltcG9ydCB7IHBhc3NrZXlDbGllbnQsIHR5cGUgUGFzc2tleSB9IGZyb20gXCJAYmV0dGVyLWF1dGgvcGFzc2tleS9jbGllbnRcIjtcbmltcG9ydCB7IFdlYkF1dGhuQWJvcnRTZXJ2aWNlIH0gZnJvbSBcIkBzaW1wbGV3ZWJhdXRobi9icm93c2VyXCI7XG5pbXBvcnQgeyBjcmVhdGVBdXRoQ2xpZW50IH0gZnJvbSBcImJldHRlci1hdXRoL2NsaWVudFwiO1xuaW1wb3J0IHsgZW1haWxPVFBDbGllbnQgfSBmcm9tIFwiYmV0dGVyLWF1dGgvY2xpZW50L3BsdWdpbnNcIjtcblxuaW1wb3J0IHsgQVVUSF9CQVNFX1BBVEggfSBmcm9tIFwiLi9fcm91dGUudHNcIjtcbmltcG9ydCB0eXBlIHsgQXV0aFN0YXR1cyB9IGZyb20gXCIuL2F1dGgudHNcIjtcblxuZXhwb3J0IHsgQVVUSF9CQVNFX1BBVEggfSBmcm9tIFwiLi9fcm91dGUudHNcIjtcbmV4cG9ydCB0eXBlIHsgUGFzc2tleSB9IGZyb20gXCJAYmV0dGVyLWF1dGgvcGFzc2tleS9jbGllbnRcIjtcblxuaW50ZXJmYWNlIEJyb3dzZXJMb2NhdGlvbiB7XG4gIHJlYWRvbmx5IG9yaWdpbjogc3RyaW5nO1xuICBhc3NpZ24odXJsOiBzdHJpbmcpOiB2b2lkO1xufVxuXG5mdW5jdGlvbiBicm93c2VyTG9jYXRpb24oKTogQnJvd3NlckxvY2F0aW9uIHwgdW5kZWZpbmVkIHtcbiAgcmV0dXJuIChnbG9iYWxUaGlzIGFzIHVua25vd24gYXMgeyBsb2NhdGlvbj86IEJyb3dzZXJMb2NhdGlvbiB9KS5sb2NhdGlvbjtcbn1cblxuZnVuY3Rpb24gYmFzZVVSTCgpOiBzdHJpbmcge1xuICBjb25zdCBvcmlnaW4gPSBicm93c2VyTG9jYXRpb24oKT8ub3JpZ2luID8/IFwiaHR0cDovL2xvY2FsaG9zdFwiO1xuICByZXR1cm4gYCR7b3JpZ2lufSR7QVVUSF9CQVNFX1BBVEh9YDtcbn1cblxuZnVuY3Rpb24gYnJvd3NlckNsaWVudChzaWduYWw/OiBBYm9ydFNpZ25hbCkge1xuICByZXR1cm4gY3JlYXRlQXV0aENsaWVudCh7XG4gICAgYmFzZVVSTDogYmFzZVVSTCgpLFxuICAgIC4uLihzaWduYWwgPyB7IGZldGNoT3B0aW9uczogeyBzaWduYWwgfSB9IDoge30pLFxuICAgIHBsdWdpbnM6IFtlbWFpbE9UUENsaWVudCgpLCBwYXNza2V5Q2xpZW50KCldLFxuICB9KTtcbn1cblxuY29uc3QgY2xpZW50ID0gYnJvd3NlckNsaWVudCgpO1xuXG5mdW5jdGlvbiByZWNvcmQodmFsdWU6IHVua25vd24pOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiB8IHVuZGVmaW5lZCB7XG4gIHJldHVybiB0eXBlb2YgdmFsdWUgPT09IFwib2JqZWN0XCIgJiYgdmFsdWUgIT09IG51bGxcbiAgICA/ICh2YWx1ZSBhcyBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPilcbiAgICA6IHVuZGVmaW5lZDtcbn1cblxuZnVuY3Rpb24gYXV0aFN0YXR1cyh2YWx1ZTogdW5rbm93bik6IEF1dGhTdGF0dXMge1xuICBjb25zdCBpdGVtID0gcmVjb3JkKHZhbHVlKTtcbiAgaWYgKFxuICAgICFpdGVtIHx8XG4gICAgdHlwZW9mIGl0ZW0uYXV0aGVudGljYXRlZCAhPT0gXCJib29sZWFuXCIgfHxcbiAgICB0eXBlb2YgaXRlbS5lbmFibGVkICE9PSBcImJvb2xlYW5cIiB8fFxuICAgIChpdGVtLmVtYWlsICE9PSB1bmRlZmluZWQgJiYgdHlwZW9mIGl0ZW0uZW1haWwgIT09IFwic3RyaW5nXCIpIHx8XG4gICAgKGl0ZW0ucGFzc2tleXNFbmFibGVkICE9PSB1bmRlZmluZWQgJiYgdHlwZW9mIGl0ZW0ucGFzc2tleXNFbmFibGVkICE9PSBcImJvb2xlYW5cIilcbiAgKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKFwiQXV0aCBzdGF0dXMgcmV0dXJuZWQgYW4gaW52YWxpZCByZXNwb25zZVwiKTtcbiAgfVxuICByZXR1cm4ge1xuICAgIGF1dGhlbnRpY2F0ZWQ6IGl0ZW0uYXV0aGVudGljYXRlZCxcbiAgICBlbmFibGVkOiBpdGVtLmVuYWJsZWQsXG4gICAgLi4uKHR5cGVvZiBpdGVtLmVtYWlsID09PSBcInN0cmluZ1wiID8geyBlbWFpbDogaXRlbS5lbWFpbCB9IDoge30pLFxuICAgIC4uLih0eXBlb2YgaXRlbS5wYXNza2V5c0VuYWJsZWQgPT09IFwiYm9vbGVhblwiID8geyBwYXNza2V5c0VuYWJsZWQ6IGl0ZW0ucGFzc2tleXNFbmFibGVkIH0gOiB7fSksXG4gIH07XG59XG5cbi8qKiBBIGNhbmNlbGxhYmxlIHBhc3NrZXkgcmVxdWVzdCBhbmQgV2ViQXV0aG4gY2VyZW1vbnkuICovXG5leHBvcnQgaW50ZXJmYWNlIFBhc3NrZXlPcGVyYXRpb24ge1xuICByZWFkb25seSByZXN1bHQ6IFByb21pc2U8Ym9vbGVhbj47XG4gIGNhbmNlbCgpOiB2b2lkO1xufVxuXG5pbnRlcmZhY2UgQWN0aXZlUGFzc2tleU9wZXJhdGlvbiB7XG4gIHJlYWRvbmx5IGlkOiBzeW1ib2w7XG4gIGNhbmNlbCgpOiB2b2lkO1xufVxuXG5sZXQgYWN0aXZlUGFzc2tleU9wZXJhdGlvbjogQWN0aXZlUGFzc2tleU9wZXJhdGlvbiB8IHVuZGVmaW5lZDtcblxuZnVuY3Rpb24gaXNBYm9ydChlcnJvcjogdW5rbm93bik6IGJvb2xlYW4ge1xuICByZXR1cm4gZXJyb3IgaW5zdGFuY2VvZiBET01FeGNlcHRpb24gJiYgZXJyb3IubmFtZSA9PT0gXCJBYm9ydEVycm9yXCI7XG59XG5cbmZ1bmN0aW9uIGJlZ2luUGFzc2tleU9wZXJhdGlvbihcbiAgaW52b2tlOiAob3BlcmF0aW9uQ2xpZW50OiBSZXR1cm5UeXBlPHR5cGVvZiBicm93c2VyQ2xpZW50PikgPT4gUHJvbWlzZTx7IGVycm9yOiB1bmtub3duIH0+LFxuKTogUGFzc2tleU9wZXJhdGlvbiB7XG4gIGFjdGl2ZVBhc3NrZXlPcGVyYXRpb24/LmNhbmNlbCgpO1xuXG4gIGNvbnN0IGlkID0gU3ltYm9sKFwicGFzc2tleS1vcGVyYXRpb25cIik7XG4gIGNvbnN0IGNvbnRyb2xsZXIgPSBuZXcgQWJvcnRDb250cm9sbGVyKCk7XG4gIGxldCBjYW5jZWxsZWQgPSBmYWxzZTtcbiAgY29uc3QgY2FuY2VsID0gKCk6IHZvaWQgPT4ge1xuICAgIGlmIChhY3RpdmVQYXNza2V5T3BlcmF0aW9uPy5pZCAhPT0gaWQpIHJldHVybjtcbiAgICBhY3RpdmVQYXNza2V5T3BlcmF0aW9uID0gdW5kZWZpbmVkO1xuICAgIGNhbmNlbGxlZCA9IHRydWU7XG4gICAgY29udHJvbGxlci5hYm9ydCgpO1xuICAgIFdlYkF1dGhuQWJvcnRTZXJ2aWNlLmNhbmNlbENlcmVtb255KCk7XG4gIH07XG4gIGFjdGl2ZVBhc3NrZXlPcGVyYXRpb24gPSB7IGlkLCBjYW5jZWwgfTtcblxuICBjb25zdCByZXN1bHQgPSBpbnZva2UoYnJvd3NlckNsaWVudChjb250cm9sbGVyLnNpZ25hbCkpXG4gICAgLnRoZW4oKHJlc3BvbnNlKSA9PiAhY2FuY2VsbGVkICYmIHJlc3BvbnNlLmVycm9yID09PSBudWxsKVxuICAgIC5jYXRjaCgoZXJyb3I6IHVua25vd24pID0+IHtcbiAgICAgIGlmIChjYW5jZWxsZWQgfHwgaXNBYm9ydChlcnJvcikpIHJldHVybiBmYWxzZTtcbiAgICAgIHRocm93IGVycm9yO1xuICAgIH0pXG4gICAgLmZpbmFsbHkoKCkgPT4ge1xuICAgICAgaWYgKGFjdGl2ZVBhc3NrZXlPcGVyYXRpb24/LmlkID09PSBpZCkgYWN0aXZlUGFzc2tleU9wZXJhdGlvbiA9IHVuZGVmaW5lZDtcbiAgICB9KTtcblxuICByZXR1cm4geyByZXN1bHQsIGNhbmNlbCB9O1xufVxuXG4vKiogU3RhcnQgYSBwYXNza2V5IHNpZ24taW4gd2hvc2UgbGlmZXRpbWUgaXMgZXhwbGljaXRseSBvd25lZCBieSB0aGUgY2FsbGVyLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGJlZ2luUGFzc2tleVNpZ25JbihhdXRvRmlsbCA9IGZhbHNlKTogUGFzc2tleU9wZXJhdGlvbiB7XG4gIHJldHVybiBiZWdpblBhc3NrZXlPcGVyYXRpb24oKG9wZXJhdGlvbkNsaWVudCkgPT4gb3BlcmF0aW9uQ2xpZW50LnNpZ25Jbi5wYXNza2V5KHsgYXV0b0ZpbGwgfSkpO1xufVxuXG4vKiogU3RhcnQgYSBwYXNza2V5IHJlZ2lzdHJhdGlvbiB3aG9zZSBsaWZldGltZSBpcyBleHBsaWNpdGx5IG93bmVkIGJ5IHRoZSBjYWxsZXIuICovXG5leHBvcnQgZnVuY3Rpb24gYmVnaW5QYXNza2V5RW5yb2xsbWVudChuYW1lPzogc3RyaW5nKTogUGFzc2tleU9wZXJhdGlvbiB7XG4gIHJldHVybiBiZWdpblBhc3NrZXlPcGVyYXRpb24oKG9wZXJhdGlvbkNsaWVudCkgPT5cbiAgICBvcGVyYXRpb25DbGllbnQucGFzc2tleS5hZGRQYXNza2V5KG5hbWUgPyB7IG5hbWUgfSA6IHVuZGVmaW5lZCksXG4gICk7XG59XG5cbi8qKiBDYW5jZWwgdGhlIGN1cnJlbnRseSBhY3RpdmUgcGFzc2tleSByZXF1ZXN0IGFuZCBicm93c2VyIGNlcmVtb255LCBpZiBhbnkuICovXG5leHBvcnQgZnVuY3Rpb24gY2FuY2VsUGFzc2tleU9wZXJhdGlvbigpOiB2b2lkIHtcbiAgYWN0aXZlUGFzc2tleU9wZXJhdGlvbj8uY2FuY2VsKCk7XG59XG5cbi8qKiBSZWFkIHRoZSBjdXJyZW50IGJyb3dzZXIgc2Vzc2lvbiBzdGF0dXMgZnJvbSB0aGUgc2hhcmVkIGF1dGggZW5kcG9pbnQuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZ2V0QXV0aFN0YXR1cygpOiBQcm9taXNlPEF1dGhTdGF0dXM+IHtcbiAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmZXRjaChgJHtBVVRIX0JBU0VfUEFUSH0vc3RhdHVzYCwgeyBjcmVkZW50aWFsczogXCJpbmNsdWRlXCIgfSk7XG4gIGlmICghcmVzcG9uc2Uub2spIHRocm93IG5ldyBFcnJvcihgQXV0aCBzdGF0dXMgZmFpbGVkIHdpdGggJHtyZXNwb25zZS5zdGF0dXN9YCk7XG4gIHJldHVybiBhdXRoU3RhdHVzKGF3YWl0IHJlc3BvbnNlLmpzb24oKSk7XG59XG5cbi8qKiBFbmQgdGhlIGN1cnJlbnQgYnJvd3NlciBzZXNzaW9uIGFuZCBmb2xsb3cgdGhlIHNlcnZlcidzIHNhbWUtb3JpZ2luIHJlZGlyZWN0LiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGxvZ291dCgpOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmZXRjaChgJHtBVVRIX0JBU0VfUEFUSH0vbG9nb3V0YCwge1xuICAgIG1ldGhvZDogXCJQT1NUXCIsXG4gICAgY3JlZGVudGlhbHM6IFwiaW5jbHVkZVwiLFxuICB9KTtcbiAgY29uc3QgcmVzdWx0ID0gcmVjb3JkKGF3YWl0IHJlc3BvbnNlLmpzb24oKSk7XG4gIGlmICghcmVzcG9uc2Uub2sgfHwgcmVzdWx0Py5vayAhPT0gdHJ1ZSB8fCB0eXBlb2YgcmVzdWx0LnJlZGlyZWN0VG8gIT09IFwic3RyaW5nXCIpIHJldHVybiBmYWxzZTtcbiAgYnJvd3NlckxvY2F0aW9uKCk/LmFzc2lnbihyZXN1bHQucmVkaXJlY3RUbyk7XG4gIHJldHVybiB0cnVlO1xufVxuXG4vKiogUmVxdWVzdCBhbiBlbWFpbCBvbmUtdGltZSBwYXNzd29yZCBmb3IgdGhlIHN1cHBsaWVkIGFkZHJlc3MuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gcmVxdWVzdEVtYWlsT3RwKGVtYWlsOiBzdHJpbmcpOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgY29uc3QgcmVzdWx0ID0gYXdhaXQgY2xpZW50LmVtYWlsT3RwLnNlbmRWZXJpZmljYXRpb25PdHAoeyBlbWFpbCwgdHlwZTogXCJzaWduLWluXCIgfSk7XG4gIHJldHVybiByZXN1bHQuZXJyb3IgPT09IG51bGw7XG59XG5cbi8qKiBWZXJpZnkgYW4gZW1haWwgb25lLXRpbWUgcGFzc3dvcmQgYW5kIGVzdGFibGlzaCBhIGJyb3dzZXIgc2Vzc2lvbi4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiB2ZXJpZnlFbWFpbE90cChlbWFpbDogc3RyaW5nLCBvdHA6IHN0cmluZywgbmFtZTogc3RyaW5nKTogUHJvbWlzZTxib29sZWFuPiB7XG4gIGNvbnN0IHJlc3VsdCA9IGF3YWl0IGNsaWVudC5zaWduSW4uZW1haWxPdHAoeyBlbWFpbCwgb3RwLCBuYW1lIH0pO1xuICByZXR1cm4gcmVzdWx0LmVycm9yID09PSBudWxsO1xufVxuXG4vKiogQXV0aGVudGljYXRlIHdpdGggYSBwYXNza2V5LCBvcHRpb25hbGx5IHVzaW5nIGNvbmRpdGlvbmFsIGJyb3dzZXIgbWVkaWF0aW9uLiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHNpZ25JblBhc3NrZXkoYXV0b0ZpbGwgPSBmYWxzZSk6IFByb21pc2U8Ym9vbGVhbj4ge1xuICByZXR1cm4gYmVnaW5QYXNza2V5U2lnbkluKGF1dG9GaWxsKS5yZXN1bHQ7XG59XG5cbi8qKiBFbnJvbGwgYSBwYXNza2V5IGZvciB0aGUgY3VycmVudCBhdXRoZW50aWNhdGVkIGFjY291bnQuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gYWRkUGFzc2tleShuYW1lPzogc3RyaW5nKTogUHJvbWlzZTxib29sZWFuPiB7XG4gIHJldHVybiBiZWdpblBhc3NrZXlFbnJvbGxtZW50KG5hbWUpLnJlc3VsdDtcbn1cblxuLyoqIExpc3QgcGFzc2tleXMgZW5yb2xsZWQgZm9yIHRoZSBjdXJyZW50IGF1dGhlbnRpY2F0ZWQgYWNjb3VudC4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBsaXN0UGFzc2tleXMoKTogUHJvbWlzZTxQYXNza2V5W10+IHtcbiAgY29uc3QgcmVzdWx0ID0gYXdhaXQgY2xpZW50LnBhc3NrZXkubGlzdFVzZXJQYXNza2V5cygpO1xuICBpZiAocmVzdWx0LmVycm9yKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKHJlc3VsdC5lcnJvci5tZXNzYWdlID8/IGBQYXNza2V5IGxpc3RpbmcgZmFpbGVkIHdpdGggJHtyZXN1bHQuZXJyb3Iuc3RhdHVzfWApO1xuICB9XG4gIHJldHVybiByZXN1bHQuZGF0YSA/PyBbXTtcbn1cblxuLyoqIFJlbmFtZSBhbiBlbnJvbGxlZCBwYXNza2V5LiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHJlbmFtZVBhc3NrZXkoaWQ6IHN0cmluZywgbmFtZTogc3RyaW5nKTogUHJvbWlzZTxib29sZWFuPiB7XG4gIGNvbnN0IHJlc3VsdCA9IGF3YWl0IGNsaWVudC5wYXNza2V5LnVwZGF0ZVBhc3NrZXkoeyBpZCwgbmFtZSB9KTtcbiAgcmV0dXJuIHJlc3VsdC5lcnJvciA9PT0gbnVsbDtcbn1cblxuLyoqIFJlbW92ZSBhbiBlbnJvbGxlZCBwYXNza2V5LiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHJlbW92ZVBhc3NrZXkoaWQ6IHN0cmluZyk6IFByb21pc2U8Ym9vbGVhbj4ge1xuICBjb25zdCByZXN1bHQgPSBhd2FpdCBjbGllbnQucGFzc2tleS5kZWxldGVQYXNza2V5KHsgaWQgfSk7XG4gIHJldHVybiByZXN1bHQuZXJyb3IgPT09IG51bGw7XG59XG4iXX0=
@@ -0,0 +1,2 @@
1
+ /** Canonical mount for Better Auth and the dbx-tools compatibility routes. */
2
+ export declare const AUTH_BASE_PATH = "/api/email/auth";
@@ -0,0 +1,3 @@
1
+ /** Canonical mount for Better Auth and the dbx-tools compatibility routes. */
2
+ export const AUTH_BASE_PATH = "/api/email/auth";
3
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiX3JvdXRlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL19yb3V0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSw4RUFBOEU7QUFDOUUsTUFBTSxDQUFDLE1BQU0sY0FBYyxHQUFHLGlCQUFpQixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqIENhbm9uaWNhbCBtb3VudCBmb3IgQmV0dGVyIEF1dGggYW5kIHRoZSBkYngtdG9vbHMgY29tcGF0aWJpbGl0eSByb3V0ZXMuICovXG5leHBvcnQgY29uc3QgQVVUSF9CQVNFX1BBVEggPSBcIi9hcGkvZW1haWwvYXV0aFwiO1xuIl19
package/lib/src/auth.d.ts CHANGED
@@ -8,6 +8,15 @@
8
8
  * @module
9
9
  */
10
10
  import { z } from "zod";
11
+ import { AUTH_BASE_PATH } from "./_route.ts";
12
+ export { AUTH_BASE_PATH };
13
+ /**
14
+ * True only for the auth mount itself or one of its descendants.
15
+ *
16
+ * Segment-aware matching prevents lookalike application routes such as
17
+ * `/api/email/authz` from being treated as public authentication endpoints.
18
+ */
19
+ export declare function isAuthPath(path: string): boolean;
11
20
  export declare const SESSION_COOKIE_NAME = "dbx-tools-auth";
12
21
  export declare const authRequestSchema: z.ZodObject<{
13
22
  email: z.ZodString;
package/lib/src/auth.js CHANGED
@@ -8,6 +8,18 @@
8
8
  * @module
9
9
  */
10
10
  import { z } from "zod";
11
+ import { AUTH_BASE_PATH } from "./_route.js";
12
+ export { AUTH_BASE_PATH };
13
+ /**
14
+ * True only for the auth mount itself or one of its descendants.
15
+ *
16
+ * Segment-aware matching prevents lookalike application routes such as
17
+ * `/api/email/authz` from being treated as public authentication endpoints.
18
+ */
19
+ export function isAuthPath(path) {
20
+ const pathname = path.split("?", 1)[0] ?? path;
21
+ return pathname === AUTH_BASE_PATH || pathname.startsWith(`${AUTH_BASE_PATH}/`);
22
+ }
11
23
  export const SESSION_COOKIE_NAME = "dbx-tools-auth";
12
24
  export const authRequestSchema = z.object({
13
25
  email: z.string().describe("Address to email a one-time code to, if it is authorized."),
@@ -34,4 +46,4 @@ export const authStatusSchema = z.object({
34
46
  enabled: z.boolean(),
35
47
  passkeysEnabled: z.boolean().optional(),
36
48
  });
37
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0aC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hdXRoLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7OztHQVFHO0FBRUgsT0FBTyxFQUFFLENBQUMsRUFBRSxNQUFNLEtBQUssQ0FBQztBQUV4QixNQUFNLENBQUMsTUFBTSxtQkFBbUIsR0FBRyxnQkFBZ0IsQ0FBQztBQUVwRCxNQUFNLENBQUMsTUFBTSxpQkFBaUIsR0FBRyxDQUFDLENBQUMsTUFBTSxDQUFDO0lBQ3hDLEtBQUssRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxDQUFDLDJEQUEyRCxDQUFDO0NBQ3hGLENBQUMsQ0FBQztBQUdILE1BQU0sQ0FBQyxNQUFNLHVCQUF1QixHQUFHLENBQUMsQ0FBQyxNQUFNLENBQUM7SUFDOUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDO0lBQ25CLFVBQVUsRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxFQUFFO0NBQ2xDLENBQUMsQ0FBQztBQUdILE1BQU0sQ0FBQyxNQUFNLGdCQUFnQixHQUFHLENBQUMsQ0FBQyxNQUFNLENBQUM7SUFDdkMsS0FBSyxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUU7SUFDakIsSUFBSSxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUU7Q0FDakIsQ0FBQyxDQUFDO0FBR0gsTUFBTSxDQUFDLE1BQU0sc0JBQXNCLEdBQUcsQ0FBQyxDQUFDLE1BQU0sQ0FBQztJQUM3QyxFQUFFLEVBQUUsQ0FBQyxDQUFDLE9BQU8sRUFBRTtJQUNmLFVBQVUsRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxFQUFFO0NBQ2xDLENBQUMsQ0FBQztBQUdILE1BQU0sQ0FBQyxNQUFNLHNCQUFzQixHQUFHLENBQUMsQ0FBQyxNQUFNLENBQUM7SUFDN0MsRUFBRSxFQUFFLENBQUMsQ0FBQyxPQUFPLEVBQUU7SUFDZixVQUFVLEVBQUUsQ0FBQyxDQUFDLE1BQU0sRUFBRTtDQUN2QixDQUFDLENBQUM7QUFHSCxNQUFNLENBQUMsTUFBTSxnQkFBZ0IsR0FBRyxDQUFDLENBQUMsTUFBTSxDQUFDO0lBQ3ZDLGFBQWEsRUFBRSxDQUFDLENBQUMsT0FBTyxFQUFFO0lBQzFCLEtBQUssRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxFQUFFO0lBQzVCLE9BQU8sRUFBRSxDQUFDLENBQUMsT0FBTyxFQUFFO0lBQ3BCLGVBQWUsRUFBRSxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUMsUUFBUSxFQUFFO0NBQ3hDLENBQUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQnJvd3Nlci1zYWZlIHBhc3N3b3JkbGVzcyBhdXRoZW50aWNhdGlvbiB3aXJlIGNvbnRyYWN0cy5cbiAqXG4gKiBCZXR0ZXIgQXV0aCBvd25zIGl0cyBuYXRpdmUgZW5kcG9pbnQgcGF5bG9hZHMuIFRoZXNlIHNjaGVtYXMgY292ZXIgdGhlXG4gKiBkYngtdG9vbHMgY29tcGF0aWJpbGl0eSBhbmQgZ2F0ZS1zdGF0dXMgc3VyZmFjZSBzaGFyZWQgYnkgdHVubmVsIHRyYW5zcG9ydHNcbiAqIGFuZCBSZWFjdCBob3N0cy5cbiAqXG4gKiBAbW9kdWxlXG4gKi9cblxuaW1wb3J0IHsgeiB9IGZyb20gXCJ6b2RcIjtcblxuZXhwb3J0IGNvbnN0IFNFU1NJT05fQ09PS0lFX05BTUUgPSBcImRieC10b29scy1hdXRoXCI7XG5cbmV4cG9ydCBjb25zdCBhdXRoUmVxdWVzdFNjaGVtYSA9IHoub2JqZWN0KHtcbiAgZW1haWw6IHouc3RyaW5nKCkuZGVzY3JpYmUoXCJBZGRyZXNzIHRvIGVtYWlsIGEgb25lLXRpbWUgY29kZSB0bywgaWYgaXQgaXMgYXV0aG9yaXplZC5cIiksXG59KTtcbmV4cG9ydCB0eXBlIEF1dGhSZXF1ZXN0ID0gei5pbmZlcjx0eXBlb2YgYXV0aFJlcXVlc3RTY2hlbWE+O1xuXG5leHBvcnQgY29uc3QgYXV0aFJlcXVlc3RSZXN1bHRTY2hlbWEgPSB6Lm9iamVjdCh7XG4gIG9rOiB6LmxpdGVyYWwodHJ1ZSksXG4gIHJldHJ5QWZ0ZXI6IHoubnVtYmVyKCkub3B0aW9uYWwoKSxcbn0pO1xuZXhwb3J0IHR5cGUgQXV0aFJlcXVlc3RSZXN1bHQgPSB6LmluZmVyPHR5cGVvZiBhdXRoUmVxdWVzdFJlc3VsdFNjaGVtYT47XG5cbmV4cG9ydCBjb25zdCBhdXRoVmVyaWZ5U2NoZW1hID0gei5vYmplY3Qoe1xuICBlbWFpbDogei5zdHJpbmcoKSxcbiAgY29kZTogei5zdHJpbmcoKSxcbn0pO1xuZXhwb3J0IHR5cGUgQXV0aFZlcmlmeSA9IHouaW5mZXI8dHlwZW9mIGF1dGhWZXJpZnlTY2hlbWE+O1xuXG5leHBvcnQgY29uc3QgYXV0aFZlcmlmeVJlc3VsdFNjaGVtYSA9IHoub2JqZWN0KHtcbiAgb2s6IHouYm9vbGVhbigpLFxuICByZXRyeUFmdGVyOiB6Lm51bWJlcigpLm9wdGlvbmFsKCksXG59KTtcbmV4cG9ydCB0eXBlIEF1dGhWZXJpZnlSZXN1bHQgPSB6LmluZmVyPHR5cGVvZiBhdXRoVmVyaWZ5UmVzdWx0U2NoZW1hPjtcblxuZXhwb3J0IGNvbnN0IGF1dGhMb2dvdXRSZXN1bHRTY2hlbWEgPSB6Lm9iamVjdCh7XG4gIG9rOiB6LmJvb2xlYW4oKSxcbiAgcmVkaXJlY3RUbzogei5zdHJpbmcoKSxcbn0pO1xuZXhwb3J0IHR5cGUgQXV0aExvZ291dFJlc3VsdCA9IHouaW5mZXI8dHlwZW9mIGF1dGhMb2dvdXRSZXN1bHRTY2hlbWE+O1xuXG5leHBvcnQgY29uc3QgYXV0aFN0YXR1c1NjaGVtYSA9IHoub2JqZWN0KHtcbiAgYXV0aGVudGljYXRlZDogei5ib29sZWFuKCksXG4gIGVtYWlsOiB6LnN0cmluZygpLm9wdGlvbmFsKCksXG4gIGVuYWJsZWQ6IHouYm9vbGVhbigpLFxuICBwYXNza2V5c0VuYWJsZWQ6IHouYm9vbGVhbigpLm9wdGlvbmFsKCksXG59KTtcbmV4cG9ydCB0eXBlIEF1dGhTdGF0dXMgPSB6LmluZmVyPHR5cGVvZiBhdXRoU3RhdHVzU2NoZW1hPjtcbiJdfQ==
49
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0aC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hdXRoLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7OztHQVFHO0FBRUgsT0FBTyxFQUFFLENBQUMsRUFBRSxNQUFNLEtBQUssQ0FBQztBQUV4QixPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBRTdDLE9BQU8sRUFBRSxjQUFjLEVBQUUsQ0FBQztBQUUxQjs7Ozs7R0FLRztBQUNILE1BQU0sVUFBVSxVQUFVLENBQUMsSUFBWTtJQUNyQyxNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLEdBQUcsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxJQUFJLENBQUM7SUFDL0MsT0FBTyxRQUFRLEtBQUssY0FBYyxJQUFJLFFBQVEsQ0FBQyxVQUFVLENBQUMsR0FBRyxjQUFjLEdBQUcsQ0FBQyxDQUFDO0FBQ2xGLENBQUM7QUFFRCxNQUFNLENBQUMsTUFBTSxtQkFBbUIsR0FBRyxnQkFBZ0IsQ0FBQztBQUVwRCxNQUFNLENBQUMsTUFBTSxpQkFBaUIsR0FBRyxDQUFDLENBQUMsTUFBTSxDQUFDO0lBQ3hDLEtBQUssRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxDQUFDLDJEQUEyRCxDQUFDO0NBQ3hGLENBQUMsQ0FBQztBQUdILE1BQU0sQ0FBQyxNQUFNLHVCQUF1QixHQUFHLENBQUMsQ0FBQyxNQUFNLENBQUM7SUFDOUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDO0lBQ25CLFVBQVUsRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxFQUFFO0NBQ2xDLENBQUMsQ0FBQztBQUdILE1BQU0sQ0FBQyxNQUFNLGdCQUFnQixHQUFHLENBQUMsQ0FBQyxNQUFNLENBQUM7SUFDdkMsS0FBSyxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUU7SUFDakIsSUFBSSxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUU7Q0FDakIsQ0FBQyxDQUFDO0FBR0gsTUFBTSxDQUFDLE1BQU0sc0JBQXNCLEdBQUcsQ0FBQyxDQUFDLE1BQU0sQ0FBQztJQUM3QyxFQUFFLEVBQUUsQ0FBQyxDQUFDLE9BQU8sRUFBRTtJQUNmLFVBQVUsRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxFQUFFO0NBQ2xDLENBQUMsQ0FBQztBQUdILE1BQU0sQ0FBQyxNQUFNLHNCQUFzQixHQUFHLENBQUMsQ0FBQyxNQUFNLENBQUM7SUFDN0MsRUFBRSxFQUFFLENBQUMsQ0FBQyxPQUFPLEVBQUU7SUFDZixVQUFVLEVBQUUsQ0FBQyxDQUFDLE1BQU0sRUFBRTtDQUN2QixDQUFDLENBQUM7QUFHSCxNQUFNLENBQUMsTUFBTSxnQkFBZ0IsR0FBRyxDQUFDLENBQUMsTUFBTSxDQUFDO0lBQ3ZDLGFBQWEsRUFBRSxDQUFDLENBQUMsT0FBTyxFQUFFO0lBQzFCLEtBQUssRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxFQUFFO0lBQzVCLE9BQU8sRUFBRSxDQUFDLENBQUMsT0FBTyxFQUFFO0lBQ3BCLGVBQWUsRUFBRSxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUMsUUFBUSxFQUFFO0NBQ3hDLENBQUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQnJvd3Nlci1zYWZlIHBhc3N3b3JkbGVzcyBhdXRoZW50aWNhdGlvbiB3aXJlIGNvbnRyYWN0cy5cbiAqXG4gKiBCZXR0ZXIgQXV0aCBvd25zIGl0cyBuYXRpdmUgZW5kcG9pbnQgcGF5bG9hZHMuIFRoZXNlIHNjaGVtYXMgY292ZXIgdGhlXG4gKiBkYngtdG9vbHMgY29tcGF0aWJpbGl0eSBhbmQgZ2F0ZS1zdGF0dXMgc3VyZmFjZSBzaGFyZWQgYnkgdHVubmVsIHRyYW5zcG9ydHNcbiAqIGFuZCBSZWFjdCBob3N0cy5cbiAqXG4gKiBAbW9kdWxlXG4gKi9cblxuaW1wb3J0IHsgeiB9IGZyb20gXCJ6b2RcIjtcblxuaW1wb3J0IHsgQVVUSF9CQVNFX1BBVEggfSBmcm9tIFwiLi9fcm91dGUudHNcIjtcblxuZXhwb3J0IHsgQVVUSF9CQVNFX1BBVEggfTtcblxuLyoqXG4gKiBUcnVlIG9ubHkgZm9yIHRoZSBhdXRoIG1vdW50IGl0c2VsZiBvciBvbmUgb2YgaXRzIGRlc2NlbmRhbnRzLlxuICpcbiAqIFNlZ21lbnQtYXdhcmUgbWF0Y2hpbmcgcHJldmVudHMgbG9va2FsaWtlIGFwcGxpY2F0aW9uIHJvdXRlcyBzdWNoIGFzXG4gKiBgL2FwaS9lbWFpbC9hdXRoemAgZnJvbSBiZWluZyB0cmVhdGVkIGFzIHB1YmxpYyBhdXRoZW50aWNhdGlvbiBlbmRwb2ludHMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBpc0F1dGhQYXRoKHBhdGg6IHN0cmluZyk6IGJvb2xlYW4ge1xuICBjb25zdCBwYXRobmFtZSA9IHBhdGguc3BsaXQoXCI/XCIsIDEpWzBdID8/IHBhdGg7XG4gIHJldHVybiBwYXRobmFtZSA9PT0gQVVUSF9CQVNFX1BBVEggfHwgcGF0aG5hbWUuc3RhcnRzV2l0aChgJHtBVVRIX0JBU0VfUEFUSH0vYCk7XG59XG5cbmV4cG9ydCBjb25zdCBTRVNTSU9OX0NPT0tJRV9OQU1FID0gXCJkYngtdG9vbHMtYXV0aFwiO1xuXG5leHBvcnQgY29uc3QgYXV0aFJlcXVlc3RTY2hlbWEgPSB6Lm9iamVjdCh7XG4gIGVtYWlsOiB6LnN0cmluZygpLmRlc2NyaWJlKFwiQWRkcmVzcyB0byBlbWFpbCBhIG9uZS10aW1lIGNvZGUgdG8sIGlmIGl0IGlzIGF1dGhvcml6ZWQuXCIpLFxufSk7XG5leHBvcnQgdHlwZSBBdXRoUmVxdWVzdCA9IHouaW5mZXI8dHlwZW9mIGF1dGhSZXF1ZXN0U2NoZW1hPjtcblxuZXhwb3J0IGNvbnN0IGF1dGhSZXF1ZXN0UmVzdWx0U2NoZW1hID0gei5vYmplY3Qoe1xuICBvazogei5saXRlcmFsKHRydWUpLFxuICByZXRyeUFmdGVyOiB6Lm51bWJlcigpLm9wdGlvbmFsKCksXG59KTtcbmV4cG9ydCB0eXBlIEF1dGhSZXF1ZXN0UmVzdWx0ID0gei5pbmZlcjx0eXBlb2YgYXV0aFJlcXVlc3RSZXN1bHRTY2hlbWE+O1xuXG5leHBvcnQgY29uc3QgYXV0aFZlcmlmeVNjaGVtYSA9IHoub2JqZWN0KHtcbiAgZW1haWw6IHouc3RyaW5nKCksXG4gIGNvZGU6IHouc3RyaW5nKCksXG59KTtcbmV4cG9ydCB0eXBlIEF1dGhWZXJpZnkgPSB6LmluZmVyPHR5cGVvZiBhdXRoVmVyaWZ5U2NoZW1hPjtcblxuZXhwb3J0IGNvbnN0IGF1dGhWZXJpZnlSZXN1bHRTY2hlbWEgPSB6Lm9iamVjdCh7XG4gIG9rOiB6LmJvb2xlYW4oKSxcbiAgcmV0cnlBZnRlcjogei5udW1iZXIoKS5vcHRpb25hbCgpLFxufSk7XG5leHBvcnQgdHlwZSBBdXRoVmVyaWZ5UmVzdWx0ID0gei5pbmZlcjx0eXBlb2YgYXV0aFZlcmlmeVJlc3VsdFNjaGVtYT47XG5cbmV4cG9ydCBjb25zdCBhdXRoTG9nb3V0UmVzdWx0U2NoZW1hID0gei5vYmplY3Qoe1xuICBvazogei5ib29sZWFuKCksXG4gIHJlZGlyZWN0VG86IHouc3RyaW5nKCksXG59KTtcbmV4cG9ydCB0eXBlIEF1dGhMb2dvdXRSZXN1bHQgPSB6LmluZmVyPHR5cGVvZiBhdXRoTG9nb3V0UmVzdWx0U2NoZW1hPjtcblxuZXhwb3J0IGNvbnN0IGF1dGhTdGF0dXNTY2hlbWEgPSB6Lm9iamVjdCh7XG4gIGF1dGhlbnRpY2F0ZWQ6IHouYm9vbGVhbigpLFxuICBlbWFpbDogei5zdHJpbmcoKS5vcHRpb25hbCgpLFxuICBlbmFibGVkOiB6LmJvb2xlYW4oKSxcbiAgcGFzc2tleXNFbmFibGVkOiB6LmJvb2xlYW4oKS5vcHRpb25hbCgpLFxufSk7XG5leHBvcnQgdHlwZSBBdXRoU3RhdHVzID0gei5pbmZlcjx0eXBlb2YgYXV0aFN0YXR1c1NjaGVtYT47XG4iXX0=
@@ -1 +1 @@
1
- {"root":["../src/auth.ts","../index.ts"],"version":"5.9.3"}
1
+ {"root":["../src/_client.ts","../src/_route.ts","../src/auth.ts","../index.ts"],"version":"5.9.3"}
package/package.json CHANGED
@@ -24,6 +24,9 @@
24
24
  "typescript": "^5.9.3"
25
25
  },
26
26
  "dependencies": {
27
+ "@better-auth/passkey": "1.7.6",
28
+ "@simplewebauthn/browser": "13.3.0",
29
+ "better-auth": "1.7.6",
27
30
  "zod": "4.3.6"
28
31
  },
29
32
  "main": "./lib/index.js",
@@ -37,10 +40,14 @@
37
40
  "types": "./lib/index.d.ts",
38
41
  "default": "./lib/index.js"
39
42
  },
43
+ "./client": {
44
+ "types": "./lib/src/_client.d.ts",
45
+ "default": "./lib/src/_client.js"
46
+ },
40
47
  "./package.json": "./package.json"
41
48
  }
42
49
  },
43
- "version": "0.9.5",
50
+ "version": "0.9.7",
44
51
  "types": "./lib/index.d.ts",
45
52
  "type": "module",
46
53
  "exports": {
@@ -48,6 +55,10 @@
48
55
  "types": "./lib/index.d.ts",
49
56
  "default": "./lib/index.js"
50
57
  },
58
+ "./client": {
59
+ "types": "./lib/src/_client.d.ts",
60
+ "default": "./lib/src/_client.js"
61
+ },
51
62
  "./package.json": "./package.json"
52
63
  },
53
64
  "files": [
package/src/_client.ts ADDED
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Browser-only Better Auth client shared by React and the hosted tunnel login.
3
+ *
4
+ * The operation helpers own both network cancellation and the active WebAuthn
5
+ * ceremony. Each operation carries an identity token so cleanup from an older
6
+ * React effect cannot cancel a newer manual attempt.
7
+ *
8
+ * @module
9
+ */
10
+
11
+ import { passkeyClient, type Passkey } from "@better-auth/passkey/client";
12
+ import { WebAuthnAbortService } from "@simplewebauthn/browser";
13
+ import { createAuthClient } from "better-auth/client";
14
+ import { emailOTPClient } from "better-auth/client/plugins";
15
+
16
+ import { AUTH_BASE_PATH } from "./_route.ts";
17
+ import type { AuthStatus } from "./auth.ts";
18
+
19
+ export { AUTH_BASE_PATH } from "./_route.ts";
20
+ export type { Passkey } from "@better-auth/passkey/client";
21
+
22
+ interface BrowserLocation {
23
+ readonly origin: string;
24
+ assign(url: string): void;
25
+ }
26
+
27
+ function browserLocation(): BrowserLocation | undefined {
28
+ return (globalThis as unknown as { location?: BrowserLocation }).location;
29
+ }
30
+
31
+ function baseURL(): string {
32
+ const origin = browserLocation()?.origin ?? "http://localhost";
33
+ return `${origin}${AUTH_BASE_PATH}`;
34
+ }
35
+
36
+ function browserClient(signal?: AbortSignal) {
37
+ return createAuthClient({
38
+ baseURL: baseURL(),
39
+ ...(signal ? { fetchOptions: { signal } } : {}),
40
+ plugins: [emailOTPClient(), passkeyClient()],
41
+ });
42
+ }
43
+
44
+ const client = browserClient();
45
+
46
+ function record(value: unknown): Record<string, unknown> | undefined {
47
+ return typeof value === "object" && value !== null
48
+ ? (value as Record<string, unknown>)
49
+ : undefined;
50
+ }
51
+
52
+ function authStatus(value: unknown): AuthStatus {
53
+ const item = record(value);
54
+ if (
55
+ !item ||
56
+ typeof item.authenticated !== "boolean" ||
57
+ typeof item.enabled !== "boolean" ||
58
+ (item.email !== undefined && typeof item.email !== "string") ||
59
+ (item.passkeysEnabled !== undefined && typeof item.passkeysEnabled !== "boolean")
60
+ ) {
61
+ throw new Error("Auth status returned an invalid response");
62
+ }
63
+ return {
64
+ authenticated: item.authenticated,
65
+ enabled: item.enabled,
66
+ ...(typeof item.email === "string" ? { email: item.email } : {}),
67
+ ...(typeof item.passkeysEnabled === "boolean" ? { passkeysEnabled: item.passkeysEnabled } : {}),
68
+ };
69
+ }
70
+
71
+ /** A cancellable passkey request and WebAuthn ceremony. */
72
+ export interface PasskeyOperation {
73
+ readonly result: Promise<boolean>;
74
+ cancel(): void;
75
+ }
76
+
77
+ interface ActivePasskeyOperation {
78
+ readonly id: symbol;
79
+ cancel(): void;
80
+ }
81
+
82
+ let activePasskeyOperation: ActivePasskeyOperation | undefined;
83
+
84
+ function isAbort(error: unknown): boolean {
85
+ return error instanceof DOMException && error.name === "AbortError";
86
+ }
87
+
88
+ function beginPasskeyOperation(
89
+ invoke: (operationClient: ReturnType<typeof browserClient>) => Promise<{ error: unknown }>,
90
+ ): PasskeyOperation {
91
+ activePasskeyOperation?.cancel();
92
+
93
+ const id = Symbol("passkey-operation");
94
+ const controller = new AbortController();
95
+ let cancelled = false;
96
+ const cancel = (): void => {
97
+ if (activePasskeyOperation?.id !== id) return;
98
+ activePasskeyOperation = undefined;
99
+ cancelled = true;
100
+ controller.abort();
101
+ WebAuthnAbortService.cancelCeremony();
102
+ };
103
+ activePasskeyOperation = { id, cancel };
104
+
105
+ const result = invoke(browserClient(controller.signal))
106
+ .then((response) => !cancelled && response.error === null)
107
+ .catch((error: unknown) => {
108
+ if (cancelled || isAbort(error)) return false;
109
+ throw error;
110
+ })
111
+ .finally(() => {
112
+ if (activePasskeyOperation?.id === id) activePasskeyOperation = undefined;
113
+ });
114
+
115
+ return { result, cancel };
116
+ }
117
+
118
+ /** Start a passkey sign-in whose lifetime is explicitly owned by the caller. */
119
+ export function beginPasskeySignIn(autoFill = false): PasskeyOperation {
120
+ return beginPasskeyOperation((operationClient) => operationClient.signIn.passkey({ autoFill }));
121
+ }
122
+
123
+ /** Start a passkey registration whose lifetime is explicitly owned by the caller. */
124
+ export function beginPasskeyEnrollment(name?: string): PasskeyOperation {
125
+ return beginPasskeyOperation((operationClient) =>
126
+ operationClient.passkey.addPasskey(name ? { name } : undefined),
127
+ );
128
+ }
129
+
130
+ /** Cancel the currently active passkey request and browser ceremony, if any. */
131
+ export function cancelPasskeyOperation(): void {
132
+ activePasskeyOperation?.cancel();
133
+ }
134
+
135
+ /** Read the current browser session status from the shared auth endpoint. */
136
+ export async function getAuthStatus(): Promise<AuthStatus> {
137
+ const response = await fetch(`${AUTH_BASE_PATH}/status`, { credentials: "include" });
138
+ if (!response.ok) throw new Error(`Auth status failed with ${response.status}`);
139
+ return authStatus(await response.json());
140
+ }
141
+
142
+ /** End the current browser session and follow the server's same-origin redirect. */
143
+ export async function logout(): Promise<boolean> {
144
+ const response = await fetch(`${AUTH_BASE_PATH}/logout`, {
145
+ method: "POST",
146
+ credentials: "include",
147
+ });
148
+ const result = record(await response.json());
149
+ if (!response.ok || result?.ok !== true || typeof result.redirectTo !== "string") return false;
150
+ browserLocation()?.assign(result.redirectTo);
151
+ return true;
152
+ }
153
+
154
+ /** Request an email one-time password for the supplied address. */
155
+ export async function requestEmailOtp(email: string): Promise<boolean> {
156
+ const result = await client.emailOtp.sendVerificationOtp({ email, type: "sign-in" });
157
+ return result.error === null;
158
+ }
159
+
160
+ /** Verify an email one-time password and establish a browser session. */
161
+ export async function verifyEmailOtp(email: string, otp: string, name: string): Promise<boolean> {
162
+ const result = await client.signIn.emailOtp({ email, otp, name });
163
+ return result.error === null;
164
+ }
165
+
166
+ /** Authenticate with a passkey, optionally using conditional browser mediation. */
167
+ export async function signInPasskey(autoFill = false): Promise<boolean> {
168
+ return beginPasskeySignIn(autoFill).result;
169
+ }
170
+
171
+ /** Enroll a passkey for the current authenticated account. */
172
+ export async function addPasskey(name?: string): Promise<boolean> {
173
+ return beginPasskeyEnrollment(name).result;
174
+ }
175
+
176
+ /** List passkeys enrolled for the current authenticated account. */
177
+ export async function listPasskeys(): Promise<Passkey[]> {
178
+ const result = await client.passkey.listUserPasskeys();
179
+ if (result.error) {
180
+ throw new Error(result.error.message ?? `Passkey listing failed with ${result.error.status}`);
181
+ }
182
+ return result.data ?? [];
183
+ }
184
+
185
+ /** Rename an enrolled passkey. */
186
+ export async function renamePasskey(id: string, name: string): Promise<boolean> {
187
+ const result = await client.passkey.updatePasskey({ id, name });
188
+ return result.error === null;
189
+ }
190
+
191
+ /** Remove an enrolled passkey. */
192
+ export async function removePasskey(id: string): Promise<boolean> {
193
+ const result = await client.passkey.deletePasskey({ id });
194
+ return result.error === null;
195
+ }
package/src/_route.ts ADDED
@@ -0,0 +1,2 @@
1
+ /** Canonical mount for Better Auth and the dbx-tools compatibility routes. */
2
+ export const AUTH_BASE_PATH = "/api/email/auth";
package/src/auth.ts CHANGED
@@ -10,6 +10,21 @@
10
10
 
11
11
  import { z } from "zod";
12
12
 
13
+ import { AUTH_BASE_PATH } from "./_route.ts";
14
+
15
+ export { AUTH_BASE_PATH };
16
+
17
+ /**
18
+ * True only for the auth mount itself or one of its descendants.
19
+ *
20
+ * Segment-aware matching prevents lookalike application routes such as
21
+ * `/api/email/authz` from being treated as public authentication endpoints.
22
+ */
23
+ export function isAuthPath(path: string): boolean {
24
+ const pathname = path.split("?", 1)[0] ?? path;
25
+ return pathname === AUTH_BASE_PATH || pathname.startsWith(`${AUTH_BASE_PATH}/`);
26
+ }
27
+
13
28
  export const SESSION_COOKIE_NAME = "dbx-tools-auth";
14
29
 
15
30
  export const authRequestSchema = z.object({