@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.
- package/dist/bin.cjs +1 -1
- package/dist/client/index.d.ts +33 -9
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +79 -13
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/component.d.ts +48 -0
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/index.d.ts +10 -4
- package/dist/component/index.d.ts.map +1 -1
- package/dist/component/index.js +8 -3
- package/dist/component/index.js.map +1 -1
- package/dist/component/public.d.ts +163 -3
- package/dist/component/public.d.ts.map +1 -1
- package/dist/component/public.js +124 -0
- package/dist/component/public.js.map +1 -1
- package/dist/component/schema.d.ts +81 -2
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +45 -0
- package/dist/component/schema.js.map +1 -1
- package/dist/providers/anonymous.d.ts +3 -0
- package/dist/providers/anonymous.d.ts.map +1 -1
- package/dist/providers/anonymous.js +3 -0
- package/dist/providers/anonymous.js.map +1 -1
- package/dist/providers/credentials.d.ts +3 -0
- package/dist/providers/credentials.d.ts.map +1 -1
- package/dist/providers/credentials.js +3 -0
- package/dist/providers/credentials.js.map +1 -1
- package/dist/providers/email.d.ts +3 -0
- package/dist/providers/email.d.ts.map +1 -1
- package/dist/providers/email.js +3 -0
- package/dist/providers/email.js.map +1 -1
- package/dist/providers/passkey.d.ts +7 -1
- package/dist/providers/passkey.d.ts.map +1 -1
- package/dist/providers/passkey.js +7 -1
- package/dist/providers/passkey.js.map +1 -1
- package/dist/providers/password.d.ts +3 -0
- package/dist/providers/password.d.ts.map +1 -1
- package/dist/providers/password.js +3 -0
- package/dist/providers/password.js.map +1 -1
- package/dist/providers/phone.d.ts +3 -0
- package/dist/providers/phone.d.ts.map +1 -1
- package/dist/providers/phone.js +3 -0
- package/dist/providers/phone.js.map +1 -1
- package/dist/providers/totp.d.ts +8 -0
- package/dist/providers/totp.d.ts.map +1 -1
- package/dist/providers/totp.js +8 -0
- package/dist/providers/totp.js.map +1 -1
- package/dist/server/convex-auth.d.ts +185 -25
- package/dist/server/convex-auth.d.ts.map +1 -1
- package/dist/server/convex-auth.js +317 -58
- package/dist/server/convex-auth.js.map +1 -1
- package/dist/server/email-templates.d.ts +18 -0
- package/dist/server/email-templates.d.ts.map +1 -0
- package/dist/server/email-templates.js +74 -0
- package/dist/server/email-templates.js.map +1 -0
- package/dist/server/errors.d.ts +146 -0
- package/dist/server/errors.d.ts.map +1 -0
- package/dist/server/errors.js +176 -0
- package/dist/server/errors.js.map +1 -0
- package/dist/server/implementation/apiKey.d.ts +74 -0
- package/dist/server/implementation/apiKey.d.ts.map +1 -0
- package/dist/server/implementation/apiKey.js +139 -0
- package/dist/server/implementation/apiKey.js.map +1 -0
- package/dist/server/implementation/index.d.ts +151 -14
- package/dist/server/implementation/index.d.ts.map +1 -1
- package/dist/server/implementation/index.js +216 -24
- package/dist/server/implementation/index.js.map +1 -1
- package/dist/server/implementation/mutations/createAccountFromCredentials.d.ts.map +1 -1
- package/dist/server/implementation/mutations/createAccountFromCredentials.js +2 -1
- package/dist/server/implementation/mutations/createAccountFromCredentials.js.map +1 -1
- package/dist/server/implementation/mutations/createVerificationCode.d.ts +2 -2
- package/dist/server/implementation/mutations/index.d.ts +6 -6
- package/dist/server/implementation/mutations/modifyAccount.d.ts.map +1 -1
- package/dist/server/implementation/mutations/modifyAccount.js +2 -1
- package/dist/server/implementation/mutations/modifyAccount.js.map +1 -1
- package/dist/server/implementation/mutations/userOAuth.d.ts.map +1 -1
- package/dist/server/implementation/mutations/userOAuth.js +2 -1
- package/dist/server/implementation/mutations/userOAuth.js.map +1 -1
- package/dist/server/implementation/mutations/verifierSignature.d.ts.map +1 -1
- package/dist/server/implementation/mutations/verifierSignature.js +2 -1
- package/dist/server/implementation/mutations/verifierSignature.js.map +1 -1
- package/dist/server/implementation/passkey.d.ts.map +1 -1
- package/dist/server/implementation/passkey.js +28 -29
- package/dist/server/implementation/passkey.js.map +1 -1
- package/dist/server/implementation/provider.d.ts.map +1 -1
- package/dist/server/implementation/provider.js +5 -4
- package/dist/server/implementation/provider.js.map +1 -1
- package/dist/server/implementation/redirects.d.ts.map +1 -1
- package/dist/server/implementation/redirects.js +2 -1
- package/dist/server/implementation/redirects.js.map +1 -1
- package/dist/server/implementation/refreshTokens.d.ts.map +1 -1
- package/dist/server/implementation/refreshTokens.js +2 -1
- package/dist/server/implementation/refreshTokens.js.map +1 -1
- package/dist/server/implementation/signIn.d.ts.map +1 -1
- package/dist/server/implementation/signIn.js +8 -18
- package/dist/server/implementation/signIn.js.map +1 -1
- package/dist/server/implementation/totp.d.ts.map +1 -1
- package/dist/server/implementation/totp.js +16 -17
- package/dist/server/implementation/totp.js.map +1 -1
- package/dist/server/implementation/users.d.ts.map +1 -1
- package/dist/server/implementation/users.js +3 -2
- package/dist/server/implementation/users.js.map +1 -1
- package/dist/server/index.d.ts +157 -3
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +180 -17
- package/dist/server/index.js.map +1 -1
- package/dist/server/oauth/authorizationUrl.d.ts.map +1 -1
- package/dist/server/oauth/authorizationUrl.js +2 -1
- package/dist/server/oauth/authorizationUrl.js.map +1 -1
- package/dist/server/oauth/callback.d.ts.map +1 -1
- package/dist/server/oauth/callback.js +5 -4
- package/dist/server/oauth/callback.js.map +1 -1
- package/dist/server/oauth/checks.d.ts.map +1 -1
- package/dist/server/oauth/checks.js +2 -1
- package/dist/server/oauth/checks.js.map +1 -1
- package/dist/server/oauth/convexAuth.d.ts.map +1 -1
- package/dist/server/oauth/convexAuth.js +3 -2
- package/dist/server/oauth/convexAuth.js.map +1 -1
- package/dist/server/provider_utils.d.ts +2 -0
- package/dist/server/provider_utils.d.ts.map +1 -1
- package/dist/server/types.d.ts +240 -5
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/utils.d.ts.map +1 -1
- package/dist/server/utils.js +2 -1
- package/dist/server/utils.js.map +1 -1
- package/dist/server/version.d.ts +2 -0
- package/dist/server/version.d.ts.map +1 -0
- package/dist/server/version.js +3 -0
- package/dist/server/version.js.map +1 -0
- package/package.json +7 -2
- package/src/cli/index.ts +1 -1
- package/src/cli/utils.ts +248 -0
- package/src/client/index.ts +105 -15
- package/src/component/_generated/component.ts +61 -0
- package/src/component/index.ts +11 -2
- package/src/component/public.ts +142 -0
- package/src/component/schema.ts +52 -0
- package/src/providers/anonymous.ts +3 -0
- package/src/providers/credentials.ts +3 -0
- package/src/providers/email.ts +3 -0
- package/src/providers/passkey.ts +8 -1
- package/src/providers/password.ts +3 -0
- package/src/providers/phone.ts +3 -0
- package/src/providers/totp.ts +9 -0
- package/src/server/convex-auth.ts +385 -73
- package/src/server/email-templates.ts +77 -0
- package/src/server/errors.ts +269 -0
- package/src/server/implementation/apiKey.ts +186 -0
- package/src/server/implementation/index.ts +288 -28
- package/src/server/implementation/mutations/createAccountFromCredentials.ts +2 -1
- package/src/server/implementation/mutations/modifyAccount.ts +2 -3
- package/src/server/implementation/mutations/userOAuth.ts +2 -1
- package/src/server/implementation/mutations/verifierSignature.ts +2 -1
- package/src/server/implementation/passkey.ts +33 -35
- package/src/server/implementation/provider.ts +5 -8
- package/src/server/implementation/redirects.ts +2 -3
- package/src/server/implementation/refreshTokens.ts +2 -1
- package/src/server/implementation/signIn.ts +9 -18
- package/src/server/implementation/totp.ts +18 -21
- package/src/server/implementation/users.ts +4 -7
- package/src/server/index.ts +240 -37
- package/src/server/oauth/authorizationUrl.ts +2 -1
- package/src/server/oauth/callback.ts +5 -4
- package/src/server/oauth/checks.ts +3 -1
- package/src/server/oauth/convexAuth.ts +6 -3
- package/src/server/types.ts +254 -5
- package/src/server/utils.ts +3 -1
- package/src/server/version.ts +2 -0
- package/dist/server/portal.d.ts +0 -116
- package/dist/server/portal.d.ts.map +0 -1
- package/dist/server/portal.js +0 -294
- package/dist/server/portal.js.map +0 -1
- package/src/server/portal.ts +0 -375
package/src/cli/utils.ts
ADDED
|
@@ -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
|
+
};
|
package/src/client/index.ts
CHANGED
|
@@ -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
|
-
|
|
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 '
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
-
(
|
|
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.
|
|
506
|
-
* with the current state
|
|
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(() =>
|
|
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",
|
package/src/component/index.ts
CHANGED
|
@@ -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
|
|
19
|
-
export {
|
|
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";
|