@robelest/convex-auth 0.0.2 → 0.0.3-preview.1

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.
Files changed (173) hide show
  1. package/dist/bin.cjs +1 -1
  2. package/dist/client/index.d.ts +33 -9
  3. package/dist/client/index.d.ts.map +1 -1
  4. package/dist/client/index.js +79 -13
  5. package/dist/client/index.js.map +1 -1
  6. package/dist/component/_generated/component.d.ts +48 -0
  7. package/dist/component/_generated/component.d.ts.map +1 -1
  8. package/dist/component/index.d.ts +10 -4
  9. package/dist/component/index.d.ts.map +1 -1
  10. package/dist/component/index.js +8 -3
  11. package/dist/component/index.js.map +1 -1
  12. package/dist/component/public.d.ts +163 -3
  13. package/dist/component/public.d.ts.map +1 -1
  14. package/dist/component/public.js +124 -0
  15. package/dist/component/public.js.map +1 -1
  16. package/dist/component/schema.d.ts +81 -2
  17. package/dist/component/schema.d.ts.map +1 -1
  18. package/dist/component/schema.js +45 -0
  19. package/dist/component/schema.js.map +1 -1
  20. package/dist/providers/anonymous.d.ts +3 -0
  21. package/dist/providers/anonymous.d.ts.map +1 -1
  22. package/dist/providers/anonymous.js +3 -0
  23. package/dist/providers/anonymous.js.map +1 -1
  24. package/dist/providers/credentials.d.ts +3 -0
  25. package/dist/providers/credentials.d.ts.map +1 -1
  26. package/dist/providers/credentials.js +3 -0
  27. package/dist/providers/credentials.js.map +1 -1
  28. package/dist/providers/email.d.ts +3 -0
  29. package/dist/providers/email.d.ts.map +1 -1
  30. package/dist/providers/email.js +3 -0
  31. package/dist/providers/email.js.map +1 -1
  32. package/dist/providers/passkey.d.ts +7 -1
  33. package/dist/providers/passkey.d.ts.map +1 -1
  34. package/dist/providers/passkey.js +7 -1
  35. package/dist/providers/passkey.js.map +1 -1
  36. package/dist/providers/password.d.ts +3 -0
  37. package/dist/providers/password.d.ts.map +1 -1
  38. package/dist/providers/password.js +3 -0
  39. package/dist/providers/password.js.map +1 -1
  40. package/dist/providers/phone.d.ts +3 -0
  41. package/dist/providers/phone.d.ts.map +1 -1
  42. package/dist/providers/phone.js +3 -0
  43. package/dist/providers/phone.js.map +1 -1
  44. package/dist/providers/totp.d.ts +8 -0
  45. package/dist/providers/totp.d.ts.map +1 -1
  46. package/dist/providers/totp.js +8 -0
  47. package/dist/providers/totp.js.map +1 -1
  48. package/dist/server/convex-auth.d.ts +185 -25
  49. package/dist/server/convex-auth.d.ts.map +1 -1
  50. package/dist/server/convex-auth.js +317 -58
  51. package/dist/server/convex-auth.js.map +1 -1
  52. package/dist/server/email-templates.d.ts +18 -0
  53. package/dist/server/email-templates.d.ts.map +1 -0
  54. package/dist/server/email-templates.js +74 -0
  55. package/dist/server/email-templates.js.map +1 -0
  56. package/dist/server/errors.d.ts +146 -0
  57. package/dist/server/errors.d.ts.map +1 -0
  58. package/dist/server/errors.js +176 -0
  59. package/dist/server/errors.js.map +1 -0
  60. package/dist/server/implementation/apiKey.d.ts +74 -0
  61. package/dist/server/implementation/apiKey.d.ts.map +1 -0
  62. package/dist/server/implementation/apiKey.js +139 -0
  63. package/dist/server/implementation/apiKey.js.map +1 -0
  64. package/dist/server/implementation/index.d.ts +151 -14
  65. package/dist/server/implementation/index.d.ts.map +1 -1
  66. package/dist/server/implementation/index.js +216 -24
  67. package/dist/server/implementation/index.js.map +1 -1
  68. package/dist/server/implementation/mutations/createAccountFromCredentials.d.ts.map +1 -1
  69. package/dist/server/implementation/mutations/createAccountFromCredentials.js +2 -1
  70. package/dist/server/implementation/mutations/createAccountFromCredentials.js.map +1 -1
  71. package/dist/server/implementation/mutations/createVerificationCode.d.ts +2 -2
  72. package/dist/server/implementation/mutations/index.d.ts +6 -6
  73. package/dist/server/implementation/mutations/modifyAccount.d.ts.map +1 -1
  74. package/dist/server/implementation/mutations/modifyAccount.js +2 -1
  75. package/dist/server/implementation/mutations/modifyAccount.js.map +1 -1
  76. package/dist/server/implementation/mutations/userOAuth.d.ts.map +1 -1
  77. package/dist/server/implementation/mutations/userOAuth.js +2 -1
  78. package/dist/server/implementation/mutations/userOAuth.js.map +1 -1
  79. package/dist/server/implementation/mutations/verifierSignature.d.ts.map +1 -1
  80. package/dist/server/implementation/mutations/verifierSignature.js +2 -1
  81. package/dist/server/implementation/mutations/verifierSignature.js.map +1 -1
  82. package/dist/server/implementation/passkey.d.ts.map +1 -1
  83. package/dist/server/implementation/passkey.js +28 -29
  84. package/dist/server/implementation/passkey.js.map +1 -1
  85. package/dist/server/implementation/provider.d.ts.map +1 -1
  86. package/dist/server/implementation/provider.js +5 -4
  87. package/dist/server/implementation/provider.js.map +1 -1
  88. package/dist/server/implementation/redirects.d.ts.map +1 -1
  89. package/dist/server/implementation/redirects.js +2 -1
  90. package/dist/server/implementation/redirects.js.map +1 -1
  91. package/dist/server/implementation/refreshTokens.d.ts.map +1 -1
  92. package/dist/server/implementation/refreshTokens.js +2 -1
  93. package/dist/server/implementation/refreshTokens.js.map +1 -1
  94. package/dist/server/implementation/signIn.d.ts.map +1 -1
  95. package/dist/server/implementation/signIn.js +8 -18
  96. package/dist/server/implementation/signIn.js.map +1 -1
  97. package/dist/server/implementation/totp.d.ts.map +1 -1
  98. package/dist/server/implementation/totp.js +16 -17
  99. package/dist/server/implementation/totp.js.map +1 -1
  100. package/dist/server/implementation/users.d.ts.map +1 -1
  101. package/dist/server/implementation/users.js +3 -2
  102. package/dist/server/implementation/users.js.map +1 -1
  103. package/dist/server/index.d.ts +157 -3
  104. package/dist/server/index.d.ts.map +1 -1
  105. package/dist/server/index.js +180 -17
  106. package/dist/server/index.js.map +1 -1
  107. package/dist/server/oauth/authorizationUrl.d.ts.map +1 -1
  108. package/dist/server/oauth/authorizationUrl.js +2 -1
  109. package/dist/server/oauth/authorizationUrl.js.map +1 -1
  110. package/dist/server/oauth/callback.d.ts.map +1 -1
  111. package/dist/server/oauth/callback.js +5 -4
  112. package/dist/server/oauth/callback.js.map +1 -1
  113. package/dist/server/oauth/checks.d.ts.map +1 -1
  114. package/dist/server/oauth/checks.js +2 -1
  115. package/dist/server/oauth/checks.js.map +1 -1
  116. package/dist/server/oauth/convexAuth.d.ts.map +1 -1
  117. package/dist/server/oauth/convexAuth.js +3 -2
  118. package/dist/server/oauth/convexAuth.js.map +1 -1
  119. package/dist/server/provider_utils.d.ts +2 -0
  120. package/dist/server/provider_utils.d.ts.map +1 -1
  121. package/dist/server/types.d.ts +240 -5
  122. package/dist/server/types.d.ts.map +1 -1
  123. package/dist/server/utils.d.ts.map +1 -1
  124. package/dist/server/utils.js +2 -1
  125. package/dist/server/utils.js.map +1 -1
  126. package/dist/server/version.d.ts +2 -0
  127. package/dist/server/version.d.ts.map +1 -0
  128. package/dist/server/version.js +3 -0
  129. package/dist/server/version.js.map +1 -0
  130. package/package.json +7 -2
  131. package/src/cli/index.ts +1 -1
  132. package/src/cli/utils.ts +248 -0
  133. package/src/client/index.ts +105 -15
  134. package/src/component/_generated/component.ts +61 -0
  135. package/src/component/index.ts +11 -2
  136. package/src/component/public.ts +142 -0
  137. package/src/component/schema.ts +52 -0
  138. package/src/providers/anonymous.ts +3 -0
  139. package/src/providers/credentials.ts +3 -0
  140. package/src/providers/email.ts +3 -0
  141. package/src/providers/passkey.ts +8 -1
  142. package/src/providers/password.ts +3 -0
  143. package/src/providers/phone.ts +3 -0
  144. package/src/providers/totp.ts +9 -0
  145. package/src/server/convex-auth.ts +385 -73
  146. package/src/server/email-templates.ts +77 -0
  147. package/src/server/errors.ts +269 -0
  148. package/src/server/implementation/apiKey.ts +186 -0
  149. package/src/server/implementation/index.ts +288 -28
  150. package/src/server/implementation/mutations/createAccountFromCredentials.ts +2 -1
  151. package/src/server/implementation/mutations/modifyAccount.ts +2 -3
  152. package/src/server/implementation/mutations/userOAuth.ts +2 -1
  153. package/src/server/implementation/mutations/verifierSignature.ts +2 -1
  154. package/src/server/implementation/passkey.ts +33 -35
  155. package/src/server/implementation/provider.ts +5 -8
  156. package/src/server/implementation/redirects.ts +2 -3
  157. package/src/server/implementation/refreshTokens.ts +2 -1
  158. package/src/server/implementation/signIn.ts +9 -18
  159. package/src/server/implementation/totp.ts +18 -21
  160. package/src/server/implementation/users.ts +4 -7
  161. package/src/server/index.ts +240 -37
  162. package/src/server/oauth/authorizationUrl.ts +2 -1
  163. package/src/server/oauth/callback.ts +5 -4
  164. package/src/server/oauth/checks.ts +3 -1
  165. package/src/server/oauth/convexAuth.ts +6 -3
  166. package/src/server/types.ts +254 -5
  167. package/src/server/utils.ts +3 -1
  168. package/src/server/version.ts +2 -0
  169. package/dist/server/portal.d.ts +0 -116
  170. package/dist/server/portal.d.ts.map +0 -1
  171. package/dist/server/portal.js +0 -294
  172. package/dist/server/portal.js.map +0 -1
  173. package/src/server/portal.ts +0 -375
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Shared CLI utilities — logging, subprocess execution, file helpers.
3
+ *
4
+ * Eliminates duplication across index.ts, portal-upload.ts, portal-link.ts.
5
+ * All output goes to stderr so stdout can be piped cleanly.
6
+ */
7
+
8
+ import chalk from "chalk";
9
+ import { execFileSync } from "child_process";
10
+ import { existsSync, readFileSync, writeFileSync } from "fs";
11
+ import { extname } from "path";
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Logging — unified output to stderr with chalk prefixes
15
+ // ---------------------------------------------------------------------------
16
+
17
+ const write = (msg: string) => process.stderr.write(msg + "\n");
18
+
19
+ export const log = {
20
+ step: (n: number, msg: string) => write(`${chalk.blue.bold(`[${n}]`)} ${chalk.bold(msg)}`),
21
+ success: (msg: string) => write(`${chalk.green("✔")} ${msg}`),
22
+ warn: (msg: string) => write(`${chalk.yellow.bold("!")} ${msg}`),
23
+ error: (msg: string, detail?: string) =>
24
+ write(`${chalk.red("✖")} ${msg}${detail ? `\n ${chalk.grey(`Error: ${detail}`)}` : ""}`),
25
+ info: (msg: string) => write(`${chalk.blue.bold("i")} ${msg}`),
26
+ blank: () => write(""),
27
+ raw: (msg: string) => write(msg),
28
+ indent: (msg: string) => write(` ${msg}`),
29
+ } as const;
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // Subprocess — safe execFile with argument arrays (no shell injection)
33
+ // ---------------------------------------------------------------------------
34
+
35
+ export type DeploymentOptions = {
36
+ prod?: boolean;
37
+ adminKey?: string;
38
+ url?: string;
39
+ previewName?: string;
40
+ deploymentName?: string;
41
+ };
42
+
43
+ /** Build CLI args array for Convex deployment selection. */
44
+ export const deploymentArgs = (opts: DeploymentOptions): string[] => {
45
+ const args: string[] = [];
46
+ if (opts.adminKey) args.push("--admin-key", opts.adminKey);
47
+ if (opts.url) args.push("--url", opts.url);
48
+ else if (opts.prod) args.push("--prod");
49
+ else if (opts.previewName) args.push("--preview-name", opts.previewName);
50
+ else if (opts.deploymentName) args.push("--deployment-name", opts.deploymentName);
51
+ return args;
52
+ };
53
+
54
+ /** Run `npx convex env get <name>` and return the value. */
55
+ export const envGet = (name: string, opts: DeploymentOptions): string =>
56
+ execFileSync("npx", ["convex", "env", "get", ...deploymentArgs(opts), name], {
57
+ encoding: "utf-8",
58
+ stdio: ["pipe", "pipe", "pipe"],
59
+ }).slice(0, -1); // strip trailing newline
60
+
61
+ /** Run `npx convex env set <name> <value>`. */
62
+ export const envSet = (
63
+ name: string,
64
+ value: string,
65
+ opts: DeploymentOptions & { hideValue?: boolean },
66
+ ): void => {
67
+ execFileSync(
68
+ "npx",
69
+ ["convex", "env", "set", ...deploymentArgs(opts), "--", name, value],
70
+ { stdio: opts.hideValue ? "ignore" : "inherit" },
71
+ );
72
+ };
73
+
74
+ /**
75
+ * Run a Convex function via `npx convex run` and return parsed JSON output.
76
+ * Uses execFile with argument arrays — no shell injection.
77
+ */
78
+ export const convexRun = <T = unknown>(
79
+ functionPath: string,
80
+ args: Record<string, unknown>,
81
+ opts: { prod?: boolean } = {},
82
+ ): Promise<T> =>
83
+ new Promise((resolve, reject) => {
84
+ const { execFile } = require("child_process");
85
+ const cmdArgs = [
86
+ "convex", "run", functionPath,
87
+ JSON.stringify(args),
88
+ "--typecheck=disable",
89
+ "--codegen=disable",
90
+ ...(opts.prod ? ["--prod"] : []),
91
+ ];
92
+ execFile("npx", cmdArgs, { encoding: "utf-8" }, (error: any, stdout: string, stderr: string) => {
93
+ if (error) {
94
+ reject(new Error(`convex run ${functionPath} failed: ${stderr || stdout}`));
95
+ return;
96
+ }
97
+ try {
98
+ resolve(JSON.parse(stdout.trim()) as T);
99
+ } catch {
100
+ // If output is not JSON, return raw string as-is
101
+ resolve(stdout.trim() as T);
102
+ }
103
+ });
104
+ });
105
+
106
+ // ---------------------------------------------------------------------------
107
+ // MIME types
108
+ // ---------------------------------------------------------------------------
109
+
110
+ const MIME_TYPES: Record<string, string> = {
111
+ ".html": "text/html; charset=utf-8",
112
+ ".js": "application/javascript; charset=utf-8",
113
+ ".mjs": "application/javascript; charset=utf-8",
114
+ ".css": "text/css; charset=utf-8",
115
+ ".json": "application/json; charset=utf-8",
116
+ ".png": "image/png",
117
+ ".jpg": "image/jpeg",
118
+ ".jpeg": "image/jpeg",
119
+ ".gif": "image/gif",
120
+ ".svg": "image/svg+xml",
121
+ ".ico": "image/x-icon",
122
+ ".webp": "image/webp",
123
+ ".woff": "font/woff",
124
+ ".woff2":"font/woff2",
125
+ ".ttf": "font/ttf",
126
+ ".txt": "text/plain; charset=utf-8",
127
+ ".map": "application/json",
128
+ ".webmanifest": "application/manifest+json",
129
+ ".xml": "application/xml",
130
+ ".br": "application/octet-stream",
131
+ ".gz": "application/gzip",
132
+ };
133
+
134
+ export const getMimeType = (path: string): string =>
135
+ MIME_TYPES[extname(path).toLowerCase()] ?? "application/octet-stream";
136
+
137
+ // ---------------------------------------------------------------------------
138
+ // File helpers
139
+ // ---------------------------------------------------------------------------
140
+
141
+ /**
142
+ * Check for an existing non-empty source file (.ts or .js) at the given
143
+ * base path (without extension). Returns the path if found, null otherwise.
144
+ */
145
+ export const findExistingSource = (basePath: string): string | null =>
146
+ [".ts", ".js"]
147
+ .map((ext) => basePath + ext)
148
+ .find((p) => existsSync(p) && readFileSync(p, "utf-8").trim() !== "")
149
+ ?? null;
150
+
151
+ /**
152
+ * Test whether an existing file already matches a template.
153
+ * Templates use `$$` as wildcards and `;` followed by newline as flexible separators.
154
+ */
155
+ export const matchesTemplate = (existing: string, template: string): boolean =>
156
+ new RegExp(
157
+ template
158
+ .replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
159
+ .replace(/\\\$\\\$/g, ".*")
160
+ .replace(/;\n/g, ";.*"),
161
+ "s",
162
+ ).test(existing);
163
+
164
+ /** Strip template markers from a source template. */
165
+ export const stripMarkers = (template: string): string =>
166
+ template.replace(/\$\$/g, "");
167
+
168
+ /**
169
+ * Higher-order function: ensure a file matches a template.
170
+ *
171
+ * - If the file doesn't exist → create it
172
+ * - If it already matches → log success
173
+ * - If it exists but doesn't match → show instructions and prompt
174
+ *
175
+ * Returns a configured function bound to the convex folder path + TS preference.
176
+ */
177
+ export const createFileEnsurer = (
178
+ convexFolderPath: string,
179
+ usesTypeScript: boolean,
180
+ promptFn: (message: string) => Promise<void>,
181
+ ) => {
182
+ const path = require("path");
183
+
184
+ return async (
185
+ baseName: string,
186
+ template: string,
187
+ description: string,
188
+ ): Promise<void> => {
189
+ const source = stripMarkers(template);
190
+ const filePath = path.join(convexFolderPath, baseName);
191
+ const existing = findExistingSource(filePath);
192
+
193
+ if (existing) {
194
+ const content = readFileSync(existing, "utf-8");
195
+ if (matchesTemplate(content, template)) {
196
+ log.success(`${chalk.bold(existing)} already configured.`);
197
+ return;
198
+ }
199
+ log.info(`${chalk.bold(existing)} needs ${description}:`);
200
+ log.raw(`\n${indentBlock(source)}\n`);
201
+ await promptFn("Ready to continue?");
202
+ return;
203
+ }
204
+
205
+ const ext = usesTypeScript ? ".ts" : ".js";
206
+ const newPath = filePath + ext;
207
+ writeFileSync(newPath, source);
208
+ log.success(`Created ${chalk.bold(newPath)}`);
209
+ };
210
+ };
211
+
212
+ /** Indent a multiline string (2 spaces, first line not indented). */
213
+ export const indentBlock = (s: string): string =>
214
+ s.replace(/^/gm, " ").slice(2);
215
+
216
+ // ---------------------------------------------------------------------------
217
+ // Crypto helpers (for portal invite links)
218
+ // ---------------------------------------------------------------------------
219
+
220
+ export { randomBytes, createHash } from "crypto";
221
+
222
+ /** Generate a URL-safe random token (32 bytes → 43 chars base64url). */
223
+ export const generateToken = (): string =>
224
+ require("crypto").randomBytes(32).toString("base64url");
225
+
226
+ /** SHA-256 hash a string and return the hex digest. */
227
+ export const hashToken = (token: string): string =>
228
+ require("crypto").createHash("sha256").update(token).digest("hex");
229
+
230
+ // ---------------------------------------------------------------------------
231
+ // Package version
232
+ // ---------------------------------------------------------------------------
233
+
234
+ /** Read the auth package version from its own package.json. */
235
+ export const getPackageVersion = (): string => {
236
+ try {
237
+ const pkgPath = require("path").resolve(__dirname, "..", "package.json");
238
+ return JSON.parse(readFileSync(pkgPath, "utf-8")).version;
239
+ } catch {
240
+ // Fallback: if running from dist/bin.cjs, package.json is two levels up
241
+ try {
242
+ const pkgPath = require("path").resolve(__dirname, "..", "..", "package.json");
243
+ return JSON.parse(readFileSync(pkgPath, "utf-8")).version;
244
+ } catch {
245
+ return "unknown";
246
+ }
247
+ }
248
+ };
@@ -1,5 +1,13 @@
1
1
  import { ConvexHttpClient } from "convex/browser";
2
- import { Value } from "convex/values";
2
+ import { ConvexError, Value } from "convex/values";
3
+
4
+ // Re-export error utilities so consumers can import from `@robelest/convex-auth/client`.
5
+ export {
6
+ isAuthError,
7
+ parseAuthError,
8
+ AUTH_ERRORS,
9
+ type AuthErrorCode,
10
+ } from "../server/errors.js";
3
11
 
4
12
  /**
5
13
  * Structural interface for any Convex client.
@@ -31,17 +39,32 @@ type AuthSession = {
31
39
  refreshToken: string;
32
40
  };
33
41
 
34
- type SignInResult = {
42
+ /**
43
+ * Result of a `signIn` call.
44
+ *
45
+ * - `signingIn: true` — credentials were accepted and the user is authenticated.
46
+ * - `redirect` — OAuth flow initiated; redirect the user to `redirect.toString()`.
47
+ * - `totpRequired` — credentials valid but 2FA is needed; call `auth.totp.verify()`.
48
+ * - `verifier` — opaque string for multi-step flows (TOTP, passkey).
49
+ */
50
+ export type SignInResult = {
51
+ /** `true` when sign-in completed and the user is authenticated. */
35
52
  signingIn: boolean;
53
+ /** OAuth redirect URL. Present when the provider requires a browser redirect. */
36
54
  redirect?: URL;
55
+ /** `true` when the account has TOTP enabled and a code is required. */
37
56
  totpRequired?: boolean;
57
+ /** Opaque verifier for multi-step flows (pass to `totp.verify` or passkey phase 2). */
38
58
  verifier?: string;
39
59
  };
40
60
 
41
61
  /** Reactive auth state snapshot returned by `auth.state` and `auth.onChange`. */
42
62
  export type AuthState = {
63
+ /** `true` during initial hydration before the first token is resolved. */
43
64
  isLoading: boolean;
65
+ /** `true` when a valid JWT exists (user is signed in). */
44
66
  isAuthenticated: boolean;
67
+ /** The raw JWT string, or `null` when not authenticated. */
45
68
  token: string | null;
46
69
  };
47
70
 
@@ -110,14 +133,17 @@ function resolveUrl(convex: ConvexTransport, explicit?: string): string {
110
133
  /**
111
134
  * Create a framework-agnostic auth client.
112
135
  *
136
+ * Returns an object with `signIn`, `signOut`, `onChange`, `state`,
137
+ * `passkey`, and `totp` — everything needed for client-side auth.
138
+ *
113
139
  * ### SPA mode (default)
114
140
  *
115
141
  * ```ts
116
- * import { ConvexClient } from 'convex/browser'
117
- * import { client } from '\@robelest/convex-auth/client'
142
+ * import { ConvexClient } from 'convex/browser';
143
+ * import { client } from '@robelest/convex-auth/client';
118
144
  *
119
- * const convex = new ConvexClient(CONVEX_URL)
120
- * const auth = client({ convex })
145
+ * const convex = new ConvexClient(CONVEX_URL);
146
+ * const auth = client({ convex });
121
147
  * ```
122
148
  *
123
149
  * ### SSR / proxy mode
@@ -126,13 +152,16 @@ function resolveUrl(convex: ConvexTransport, explicit?: string): string {
126
152
  * const auth = client({
127
153
  * convex,
128
154
  * proxy: '/api/auth',
129
- * initialToken: tokenFromServer, // read from httpOnly cookie during SSR
130
- * })
155
+ * token: tokenFromServer, // JWT read from httpOnly cookie during SSR
156
+ * });
131
157
  * ```
132
158
  *
133
159
  * In proxy mode all auth operations go through the proxy URL.
134
160
  * Tokens are stored in httpOnly cookies server-side — the client
135
- * only holds the JWT in memory.
161
+ * holds the JWT in memory only.
162
+ *
163
+ * @param options - Client configuration. See {@link ClientOptions}.
164
+ * @returns Auth client with `signIn`, `signOut`, `onChange`, `state`, `passkey`, and `totp`.
136
165
  */
137
166
  export function client(options: ClientOptions) {
138
167
  const { convex, proxy } = options;
@@ -243,6 +272,13 @@ export function client(options: ClientOptions) {
243
272
  isLoading = false;
244
273
  const changed = updateSnapshot();
245
274
  if (hadPendingLoad || changed) {
275
+ // Re-sync the Convex client so it picks up the new token immediately.
276
+ // Without this, the initial convex.setAuth(fetchAccessToken) from
277
+ // initialization never re-polls and queries run unauthenticated after
278
+ // magic link code exchange.
279
+ if (!proxy) {
280
+ convex.setAuth(fetchAccessToken);
281
+ }
246
282
  notify();
247
283
  }
248
284
  };
@@ -259,9 +295,19 @@ export function client(options: ClientOptions) {
259
295
  body: JSON.stringify(body),
260
296
  });
261
297
  if (!response.ok) {
262
- const error = await response.json().catch(() => ({}));
298
+ const errorBody = await response.json().catch(() => ({} as Record<string, unknown>));
299
+ // Reconstruct ConvexError when the proxy forwards structured auth error data.
300
+ if (
301
+ typeof errorBody === "object" &&
302
+ errorBody !== null &&
303
+ "authError" in errorBody &&
304
+ typeof (errorBody as Record<string, unknown>).authError === "object"
305
+ ) {
306
+ throw new ConvexError((errorBody as Record<string, unknown>).authError as Value);
307
+ }
263
308
  throw new Error(
264
- (error as any).error ?? `Proxy request failed: ${response.status}`,
309
+ (errorBody as Record<string, unknown>).error as string ??
310
+ `Proxy request failed: ${response.status}`,
265
311
  );
266
312
  }
267
313
  return response.json();
@@ -312,6 +358,33 @@ export function client(options: ClientOptions) {
312
358
  // signIn
313
359
  // ---------------------------------------------------------------------------
314
360
 
361
+ /**
362
+ * Sign in with a provider.
363
+ *
364
+ * @param provider - Provider ID (e.g. `"email"`, `"password"`, `"google"`).
365
+ * Omit when exchanging an OAuth code (the code carries the provider info).
366
+ * @param args - Provider-specific arguments. Pass a `Record<string, Value>`
367
+ * or `FormData`. Common fields: `email`, `password`, `code`, `redirectTo`.
368
+ * @returns A {@link SignInResult} indicating the outcome.
369
+ *
370
+ * @example Email magic link
371
+ * ```ts
372
+ * await auth.signIn('email', { email: 'user@example.com' });
373
+ * ```
374
+ *
375
+ * @example Password
376
+ * ```ts
377
+ * const result = await auth.signIn('password', { email, password, flow: 'signIn' });
378
+ * if (result.totpRequired) {
379
+ * await auth.totp.verify({ code: totpCode, verifier: result.verifier! });
380
+ * }
381
+ * ```
382
+ *
383
+ * @example OAuth (triggers redirect)
384
+ * ```ts
385
+ * await auth.signIn('google'); // redirects to Google
386
+ * ```
387
+ */
315
388
  const signIn = async (
316
389
  provider?: string,
317
390
  args?: FormData | Record<string, Value>,
@@ -390,6 +463,13 @@ export function client(options: ClientOptions) {
390
463
  // signOut
391
464
  // ---------------------------------------------------------------------------
392
465
 
466
+ /**
467
+ * Sign out the current user.
468
+ *
469
+ * Invalidates the server session and clears local token state.
470
+ * Errors are silently caught — calling `signOut` on an already
471
+ * signed-out user is a no-op.
472
+ */
393
473
  const signOut = async () => {
394
474
  if (proxy) {
395
475
  try {
@@ -502,12 +582,15 @@ export function client(options: ClientOptions) {
502
582
  // ---------------------------------------------------------------------------
503
583
 
504
584
  /**
505
- * Subscribe to auth state changes. Immediately invokes the callback
506
- * with the current state and returns an unsubscribe function.
585
+ * Subscribe to auth state changes. Invokes the callback immediately
586
+ * with the current state, then again on every state transition.
507
587
  *
508
588
  * ```ts
509
- * const unsub = auth.onChange(setState)
589
+ * const unsub = auth.onChange(setState);
510
590
  * ```
591
+ *
592
+ * @param cb - Callback receiving the latest {@link AuthState}.
593
+ * @returns An unsubscribe function.
511
594
  */
512
595
  const onChange = (cb: (state: AuthState) => void): (() => void) => {
513
596
  cb(snapshot);
@@ -555,7 +638,11 @@ export function client(options: ClientOptions) {
555
638
  }
556
639
  } else {
557
640
  // SPA mode: hydrate from localStorage, then handle OAuth code flow.
558
- void hydrateFromStorage().then(() => handleCodeFlow());
641
+ void hydrateFromStorage().then(() =>
642
+ handleCodeFlow().catch((error: unknown) => {
643
+ console.error("[convex-auth] Code exchange failed:", error);
644
+ }),
645
+ );
559
646
  }
560
647
  }
561
648
 
@@ -1029,8 +1116,11 @@ export function client(options: ClientOptions) {
1029
1116
  get state(): AuthState {
1030
1117
  return snapshot;
1031
1118
  },
1119
+ /** Sign in with a provider. See {@link SignInResult} for return shape. */
1032
1120
  signIn,
1121
+ /** Sign out and clear all token state. */
1033
1122
  signOut,
1123
+ /** Subscribe to auth state changes. Returns an unsubscribe function. */
1034
1124
  onChange,
1035
1125
  /** Passkey (WebAuthn) authentication helpers. */
1036
1126
  passkey,
@@ -210,6 +210,67 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
210
210
  any,
211
211
  Name
212
212
  >;
213
+ keyDelete: FunctionReference<
214
+ "mutation",
215
+ "internal",
216
+ { keyId: string },
217
+ any,
218
+ Name
219
+ >;
220
+ keyGetByHashedKey: FunctionReference<
221
+ "query",
222
+ "internal",
223
+ { hashedKey: string },
224
+ any,
225
+ Name
226
+ >;
227
+ keyGetById: FunctionReference<
228
+ "query",
229
+ "internal",
230
+ { keyId: string },
231
+ any,
232
+ Name
233
+ >;
234
+ keyInsert: FunctionReference<
235
+ "mutation",
236
+ "internal",
237
+ {
238
+ expiresAt?: number;
239
+ hashedKey: string;
240
+ name: string;
241
+ prefix: string;
242
+ rateLimit?: { maxRequests: number; windowMs: number };
243
+ scopes: Array<{ resource: string; actions: Array<string> }>;
244
+ userId: string;
245
+ },
246
+ any,
247
+ Name
248
+ >;
249
+ keyList: FunctionReference<"query", "internal", {}, any, Name>;
250
+ keyListByUserId: FunctionReference<
251
+ "query",
252
+ "internal",
253
+ { userId: string },
254
+ any,
255
+ Name
256
+ >;
257
+ keyPatch: FunctionReference<
258
+ "mutation",
259
+ "internal",
260
+ {
261
+ data: {
262
+ lastUsedAt?: number;
263
+ name?: string;
264
+ rateLimit?: { maxRequests: number; windowMs: number };
265
+ rateLimitState?: { attemptsLeft: number; lastAttemptTime: number };
266
+ revoked?: boolean;
267
+ scopes?: Array<{ resource: string; actions: Array<string> }>;
268
+ };
269
+ keyId: string;
270
+ },
271
+ any,
272
+ Name
273
+ >;
213
274
  memberAdd: FunctionReference<
214
275
  "mutation",
215
276
  "internal",
@@ -9,14 +9,19 @@
9
9
  */
10
10
 
11
11
  export {
12
+ /**
13
+ * The low-level factory function used internally by the `Auth` class.
14
+ * Re-exported as `AuthFactory` to avoid naming conflicts with the
15
+ * `Auth` class (the recommended public API). Prefer `new Auth(...)`.
16
+ */
12
17
  Auth as AuthFactory,
13
18
  Tokens,
14
19
  Doc,
15
20
  SignInAction,
16
21
  SignOutAction,
17
22
  } from "../server/implementation/index.js";
18
- export { Portal as PortalFactory } from "../server/portal.js";
19
- export { Auth, Portal } from "../server/convex-auth.js";
23
+ export { Auth, Portal, AuthCtx } from "../server/convex-auth.js";
24
+ export type { AuthCtxConfig } from "../server/convex-auth.js";
20
25
  export type {
21
26
  ConvexAuthConfig,
22
27
  AuthProviderConfig,
@@ -28,5 +33,9 @@ export type {
28
33
  GenericActionCtxWithAuthConfig,
29
34
  AuthProviderMaterializedConfig,
30
35
  ConvexAuthMaterializedConfig,
36
+ ApiKeyConfig,
37
+ KeyScope,
38
+ ScopeChecker,
39
+ KeyRecord,
31
40
  } from "../server/types.js";
32
41
  export type { GenericDoc } from "../server/convex_types.js";