@lupinum/better-convex-nuxt 1.0.0-beta.2 → 1.0.0-beta.4
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/module.json +1 -1
- package/dist/runtime/auth/better-auth-browser-adapter.d.ts +1 -0
- package/dist/runtime/auth/better-auth-browser-adapter.js +18 -3
- package/dist/runtime/auth/integrated-client.d.ts +1 -1
- package/dist/runtime/auth/token-fetcher.d.ts +5 -1
- package/dist/runtime/auth/token-fetcher.js +3 -0
- package/dist/runtime/cli/auth-schema.js +3 -2
- package/dist/runtime/cli/convex.d.ts +8 -0
- package/dist/runtime/cli/convex.js +23 -0
- package/dist/runtime/cli/init.d.ts +1 -0
- package/dist/runtime/cli/init.js +34 -9
- package/dist/runtime/convex-auth/adapter/create-adapter.js +36 -8
- package/dist/runtime/convex-auth/adapter/define-functions.d.ts +212 -7
- package/dist/runtime/convex-auth/adapter/define-functions.js +201 -21
- package/dist/runtime/convex-auth/adapter/generate-schema.js +35 -1
- package/dist/runtime/convex-auth/adapter/query.d.ts +8 -3
- package/dist/runtime/convex-auth/adapter/query.js +20 -3
- package/dist/runtime/convex-auth/adapter/relationships.d.ts +2 -0
- package/dist/runtime/convex-auth/adapter/relationships.js +3 -1
- package/dist/runtime/convex-auth/auth-jwt.d.ts +3 -0
- package/dist/runtime/convex-auth/auth-jwt.js +12 -0
- package/dist/runtime/convex-auth/component/_generated/component.d.ts +340 -79
- package/dist/runtime/convex-auth/component/_generated/server.js +3 -3
- package/dist/runtime/convex-auth/component/adapter.d.ts +205 -8
- package/dist/runtime/convex-auth/component/adapter.js +7 -0
- package/dist/runtime/convex-auth/component/schema.js +5 -3
- package/dist/runtime/convex-auth/component/schemaMetadata.d.ts +25 -1
- package/dist/runtime/convex-auth/component/schemaMetadata.js +25 -1
- package/dist/runtime/convex-auth/create-auth-component.d.ts +24 -2
- package/dist/runtime/convex-auth/create-auth-component.js +33 -15
- package/dist/runtime/convex-auth/create-better-convex-auth.d.ts +14 -5
- package/dist/runtime/convex-auth/create-better-convex-auth.js +99 -35
- package/dist/runtime/convex-auth/index.d.ts +3 -1
- package/dist/runtime/convex-auth/index.js +2 -0
- package/dist/runtime/convex-auth/oauth-live-access.js +6 -10
- package/dist/runtime/convex-auth/plugin.js +30 -1
- package/dist/runtime/convex-auth/session-generation.d.ts +28 -0
- package/dist/runtime/convex-auth/session-generation.js +98 -0
- package/dist/runtime/convex-auth/test.d.ts +20 -407
- package/dist/runtime/convex-auth/test.js +31 -1
- package/dist/runtime/convex-auth/workforce/adapter-policy.d.ts +33 -0
- package/dist/runtime/convex-auth/workforce/adapter-policy.js +135 -0
- package/dist/runtime/convex-auth/workforce/adapter-transport.d.ts +40 -0
- package/dist/runtime/convex-auth/workforce/adapter-transport.js +31 -0
- package/dist/runtime/convex-auth/workforce/admission.d.ts +13 -0
- package/dist/runtime/convex-auth/workforce/admission.js +17 -0
- package/dist/runtime/convex-auth/workforce/credential-generation.d.ts +9 -0
- package/dist/runtime/convex-auth/workforce/credential-generation.js +41 -0
- package/dist/runtime/convex-auth/workforce/factor-fingerprint.d.ts +2 -0
- package/dist/runtime/convex-auth/workforce/factor-fingerprint.js +4 -0
- package/dist/runtime/convex-auth/workforce/factor-transitions.d.ts +36 -0
- package/dist/runtime/convex-auth/workforce/factor-transitions.js +180 -0
- package/dist/runtime/convex-auth/workforce/operations.d.ts +150 -0
- package/dist/runtime/convex-auth/workforce/operations.js +45 -0
- package/dist/runtime/convex-auth/workforce/otp-binding.d.ts +8 -0
- package/dist/runtime/convex-auth/workforce/otp-binding.js +48 -0
- package/dist/runtime/convex-auth/workforce/pending-factor-view.d.ts +4 -0
- package/dist/runtime/convex-auth/workforce/pending-factor-view.js +12 -0
- package/dist/runtime/convex-auth/workforce/profile.d.ts +9 -0
- package/dist/runtime/convex-auth/workforce/profile.js +24 -0
- package/dist/runtime/convex-auth/workforce/provider-hooks.d.ts +6 -0
- package/dist/runtime/convex-auth/workforce/provider-hooks.js +191 -0
- package/dist/runtime/convex-auth/workforce/replay.d.ts +32 -0
- package/dist/runtime/convex-auth/workforce/replay.js +75 -0
- package/dist/runtime/convex-auth/workforce/request-context.d.ts +29 -0
- package/dist/runtime/convex-auth/workforce/request-context.js +88 -0
- package/dist/runtime/convex-auth/workforce/schema.d.ts +93 -0
- package/dist/runtime/convex-auth/workforce/schema.js +110 -0
- package/dist/runtime/convex-auth/workforce/session-assurance.d.ts +29 -0
- package/dist/runtime/convex-auth/workforce/session-assurance.js +43 -0
- package/dist/runtime/convex-auth/workforce/session-management.d.ts +91 -0
- package/dist/runtime/convex-auth/workforce/session-management.js +133 -0
- package/dist/runtime/convex-auth/workforce/session-transitions.d.ts +11 -0
- package/dist/runtime/convex-auth/workforce/session-transitions.js +106 -0
- package/dist/runtime/plugin.auth.client.js +11 -1
- package/dist/runtime/shared/release-fingerprint.js +1 -1
- package/package.json +18 -13
- package/security/upstream-convex-better-auth.json +251 -42
- package/dist/runtime/convex-auth/component/schema.d.ts +0 -805
package/dist/module.json
CHANGED
|
@@ -27,6 +27,7 @@ export declare function createBetterAuthBrowserAdapter(source: BetterAuthBrowser
|
|
|
27
27
|
}, options?: {
|
|
28
28
|
initialIdentityKey?: string;
|
|
29
29
|
}): BetterConvexAuthAdapter & {
|
|
30
|
+
isRestrictedSession(): boolean;
|
|
30
31
|
failClosed(message: string): void;
|
|
31
32
|
dispose(): void;
|
|
32
33
|
};
|
|
@@ -18,6 +18,7 @@ export function createBetterAuthBrowserAdapter(source, callbacks = { authenticat
|
|
|
18
18
|
let observedSessionToken;
|
|
19
19
|
let observedIdentityKey = options.initialIdentityKey;
|
|
20
20
|
let cachedToken = null;
|
|
21
|
+
let restrictedGeneration;
|
|
21
22
|
let snapshot = options.initialIdentityKey ? {
|
|
22
23
|
status: "authenticated",
|
|
23
24
|
identityKey: options.initialIdentityKey,
|
|
@@ -60,6 +61,7 @@ export function createBetterAuthBrowserAdapter(source, callbacks = { authenticat
|
|
|
60
61
|
const retainsEstablishedSession = Boolean(value.error) && !isUnauthorized(value.error) && snapshot.status === "authenticated" && observedSessionToken === sessionToken && observedIdentityKey === key;
|
|
61
62
|
if (retainsEstablishedSession) return;
|
|
62
63
|
if (value.error || malformed) {
|
|
64
|
+
restrictedGeneration = void 0;
|
|
63
65
|
cachedToken = null;
|
|
64
66
|
sessionGeneration += 1;
|
|
65
67
|
observedSessionToken = null;
|
|
@@ -80,10 +82,11 @@ export function createBetterAuthBrowserAdapter(source, callbacks = { authenticat
|
|
|
80
82
|
if (changed) {
|
|
81
83
|
sessionGeneration += 1;
|
|
82
84
|
cachedToken = null;
|
|
85
|
+
restrictedGeneration = void 0;
|
|
83
86
|
}
|
|
84
87
|
observedSessionToken = sessionToken;
|
|
85
88
|
observedIdentityKey = key;
|
|
86
|
-
snapshot = sessionToken && key ? {
|
|
89
|
+
snapshot = sessionToken && key && restrictedGeneration !== sessionGeneration ? {
|
|
87
90
|
status: "authenticated",
|
|
88
91
|
identityKey: key,
|
|
89
92
|
sessionGeneration,
|
|
@@ -94,7 +97,7 @@ export function createBetterAuthBrowserAdapter(source, callbacks = { authenticat
|
|
|
94
97
|
sessionGeneration,
|
|
95
98
|
error: null
|
|
96
99
|
};
|
|
97
|
-
if (
|
|
100
|
+
if (snapshot.status === "anonymous") callbacks.anonymous(null);
|
|
98
101
|
callbacks.sessionChanged?.(sessionToken, null, sessionGeneration);
|
|
99
102
|
notify();
|
|
100
103
|
};
|
|
@@ -145,6 +148,7 @@ export function createBetterAuthBrowserAdapter(source, callbacks = { authenticat
|
|
|
145
148
|
};
|
|
146
149
|
return Object.freeze({
|
|
147
150
|
snapshot: () => snapshot,
|
|
151
|
+
isRestrictedSession: () => !disposed && restrictedGeneration === sessionGeneration && snapshot.status === "anonymous",
|
|
148
152
|
subscribe(listener) {
|
|
149
153
|
if (disposed) return () => {
|
|
150
154
|
};
|
|
@@ -154,8 +158,17 @@ export function createBetterAuthBrowserAdapter(source, callbacks = { authenticat
|
|
|
154
158
|
async fetchToken() {
|
|
155
159
|
if (disposed || snapshot.status !== "authenticated") return null;
|
|
156
160
|
const expectedKey = snapshot.identityKey;
|
|
161
|
+
const expectedGeneration = sessionGeneration;
|
|
157
162
|
const outcome = await fetchConvexToken(source);
|
|
158
|
-
if (disposed || snapshot.status !== "authenticated" || snapshot.identityKey !== expectedKey) {
|
|
163
|
+
if (disposed || snapshot.status !== "authenticated" || snapshot.identityKey !== expectedKey || sessionGeneration !== expectedGeneration) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
if (outcome.restricted) {
|
|
167
|
+
cachedToken = null;
|
|
168
|
+
restrictedGeneration = expectedGeneration;
|
|
169
|
+
snapshot = { status: "anonymous", identityKey: null, sessionGeneration, error: null };
|
|
170
|
+
callbacks.anonymous(null);
|
|
171
|
+
notify();
|
|
159
172
|
return null;
|
|
160
173
|
}
|
|
161
174
|
if (outcome.identity) {
|
|
@@ -187,6 +200,7 @@ export function createBetterAuthBrowserAdapter(source, callbacks = { authenticat
|
|
|
187
200
|
},
|
|
188
201
|
failClosed(message) {
|
|
189
202
|
if (disposed) return;
|
|
203
|
+
restrictedGeneration = void 0;
|
|
190
204
|
cachedToken = null;
|
|
191
205
|
sessionGeneration += 1;
|
|
192
206
|
snapshot = {
|
|
@@ -204,6 +218,7 @@ export function createBetterAuthBrowserAdapter(source, callbacks = { authenticat
|
|
|
204
218
|
dispose() {
|
|
205
219
|
if (disposed) return;
|
|
206
220
|
disposed = true;
|
|
221
|
+
restrictedGeneration = void 0;
|
|
207
222
|
stop();
|
|
208
223
|
for (const cancel of [...cancelSessionSettlement]) cancel();
|
|
209
224
|
cancelSessionSettlement.clear();
|
|
@@ -10,7 +10,7 @@ export interface CanonicalSessionReconciler {
|
|
|
10
10
|
* without changing its synchronous contract.
|
|
11
11
|
*/
|
|
12
12
|
cancel(checkpoint: SessionCheckpoint): void;
|
|
13
|
-
/** Re-read the
|
|
13
|
+
/** Re-read the provider session, then await matching Convex settlement (possibly anonymous). */
|
|
14
14
|
reconcile(checkpoint: SessionCheckpoint): Promise<void>;
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
@@ -39,7 +39,9 @@ export declare function decodeFetchedIdentity(token: string): FetchedIdentity |
|
|
|
39
39
|
* The outcome of one total token fetch. `identity: null` with `authError: null`
|
|
40
40
|
* is a clean anonymous result when the exchange succeeds with no token. A
|
|
41
41
|
* definitive 401/403 means presented credentials were rejected and is reported
|
|
42
|
-
* as an authentication error
|
|
42
|
+
* as an authentication error, except the exact live workforce continuation
|
|
43
|
+
* refusal. That result is restricted and grants no Convex identity.
|
|
44
|
+
* `authError` is also set when a token decodes
|
|
43
45
|
* without a stable user id, or a transient transport failure exhausts the retry
|
|
44
46
|
* loop. `definitive`
|
|
45
47
|
* distinguishes such a transient (non-definitive) failure — over which a usable
|
|
@@ -49,6 +51,8 @@ export interface FetchOutcome {
|
|
|
49
51
|
identity: FetchedIdentity | null;
|
|
50
52
|
authError: string | null;
|
|
51
53
|
definitive: boolean;
|
|
54
|
+
/** A live workforce cookie may continue setup, but cannot authenticate Convex. */
|
|
55
|
+
restricted?: true;
|
|
52
56
|
}
|
|
53
57
|
/**
|
|
54
58
|
* Total Convex-token fetch; never throws.
|
|
@@ -75,6 +75,9 @@ export async function fetchConvexToken(source, options = {}) {
|
|
|
75
75
|
break;
|
|
76
76
|
}
|
|
77
77
|
if (response.error) {
|
|
78
|
+
if (typeof response.error === "object" && "status" in response.error && response.error.status === 403 && "code" in response.error && response.error.code === "AUTH_WORKFORCE_FULL_AUTH_REQUIRED") {
|
|
79
|
+
return { identity: null, authError: null, definitive: true, restricted: true };
|
|
80
|
+
}
|
|
78
81
|
if (isDefinitiveAuthStatus(response.error)) {
|
|
79
82
|
return {
|
|
80
83
|
identity: null,
|
|
@@ -21,7 +21,8 @@ const SUPPORTED_SCHEMA_PLUGIN_IDS = /* @__PURE__ */ new Set([
|
|
|
21
21
|
"two-factor",
|
|
22
22
|
"email-otp",
|
|
23
23
|
"jwt",
|
|
24
|
-
"oauth-provider"
|
|
24
|
+
"oauth-provider",
|
|
25
|
+
"bcn-workforce-schema"
|
|
25
26
|
]);
|
|
26
27
|
function usage() {
|
|
27
28
|
return [
|
|
@@ -88,7 +89,7 @@ function requireSchemaOptions(value) {
|
|
|
88
89
|
const id = isRecord(plugin) && typeof plugin.id === "string" ? plugin.id : void 0;
|
|
89
90
|
if (!id || !SUPPORTED_SCHEMA_PLUGIN_IDS.has(id) || seen.has(id)) {
|
|
90
91
|
throw new Error(
|
|
91
|
-
`Schema config plugin at index ${index} is unsupported; the 1.0 profile admits organization, two-factor, email-otp, jwt,
|
|
92
|
+
`Schema config plugin at index ${index} is unsupported; the 1.0 profile admits organization, two-factor, email-otp, jwt, oauth-provider, and bcn-workforce-schema once each.`
|
|
92
93
|
);
|
|
93
94
|
}
|
|
94
95
|
seen.add(id);
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
type ConvexAuthorityKind = 'deployment-key' | 'deployment-name' | 'self-hosted';
|
|
3
|
+
export interface ConvexAuthoritySnapshot {
|
|
4
|
+
digest: string;
|
|
5
|
+
development: boolean;
|
|
6
|
+
label: string;
|
|
7
|
+
}
|
|
3
8
|
/** Validate that the fixed file contains one, and only one, deployment selector class. */
|
|
4
9
|
export declare function assertConvexAuthoritySelection(environment: Readonly<Record<string, string>>): ConvexAuthorityKind;
|
|
5
10
|
/** Build a child environment whose only Convex authority comes from the fixed file. */
|
|
6
11
|
export declare function buildConvexCommandEnvironment(inherited: Readonly<Record<string, string | undefined>>, authorityFile: Readonly<Record<string, string>>): Record<string, string>;
|
|
12
|
+
export declare function inspectConvexAuthority(cwd?: string): Promise<ConvexAuthoritySnapshot>;
|
|
7
13
|
export declare function runConvexCommand(arguments_: readonly string[], options?: {
|
|
8
14
|
cwd?: string;
|
|
15
|
+
developmentOnly?: boolean;
|
|
16
|
+
expectedAuthorityDigest?: string;
|
|
9
17
|
inheritedEnvironment?: NodeJS.ProcessEnv;
|
|
10
18
|
input?: string;
|
|
11
19
|
onStdout?: (output: string) => void;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
3
4
|
import { realpathSync } from "node:fs";
|
|
4
5
|
import { stat, readFile } from "node:fs/promises";
|
|
5
6
|
import { resolve } from "node:path";
|
|
@@ -176,6 +177,21 @@ async function readAuthorityFile(cwd) {
|
|
|
176
177
|
throw new Error("The Convex authority file is missing, invalid, or too large.");
|
|
177
178
|
}
|
|
178
179
|
}
|
|
180
|
+
function snapshotAuthority(environment) {
|
|
181
|
+
const kind = assertConvexAuthoritySelection(environment);
|
|
182
|
+
const selector = TOKEN_SELECTORS.map((name) => environment[name]).find(Boolean) ?? environment.CONVEX_DEPLOYMENT ?? "self-hosted";
|
|
183
|
+
const publicSelector = selector.includes("|") ? selector.slice(0, selector.indexOf("|")) : selector;
|
|
184
|
+
const development = kind === "deployment-key" && publicSelector.startsWith("dev:") || kind === "deployment-name" && /^(?:anonymous:anonymous-|dev:|local:)/u.test(publicSelector);
|
|
185
|
+
const canonicalAuthority = Object.entries(environment).filter(([name]) => ALLOWED_CONVEX_FILE_NAMES.has(name)).sort(([left], [right]) => left.localeCompare(right));
|
|
186
|
+
return {
|
|
187
|
+
digest: createHash("sha256").update(JSON.stringify(canonicalAuthority)).digest("hex"),
|
|
188
|
+
development,
|
|
189
|
+
label: kind === "self-hosted" ? "self-hosted deployment" : publicSelector
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
export async function inspectConvexAuthority(cwd = process.cwd()) {
|
|
193
|
+
return snapshotAuthority(await readAuthorityFile(resolve(cwd)));
|
|
194
|
+
}
|
|
179
195
|
function usage() {
|
|
180
196
|
return [
|
|
181
197
|
"Run the pinned Convex CLI with one explicit deployment authority.",
|
|
@@ -224,6 +240,13 @@ export async function runConvexCommand(arguments_, options = {}) {
|
|
|
224
240
|
assertFileBoundArguments(commandArguments);
|
|
225
241
|
const authorityFile = await readAuthorityFile(cwd);
|
|
226
242
|
const authorityKind = assertConvexAuthoritySelection(authorityFile);
|
|
243
|
+
const authority = snapshotAuthority(authorityFile);
|
|
244
|
+
if (options.developmentOnly && !authority.development) {
|
|
245
|
+
throw new Error("This command requires development, local, or anonymous authority.");
|
|
246
|
+
}
|
|
247
|
+
if (options.expectedAuthorityDigest !== void 0 && authority.digest !== options.expectedAuthorityDigest) {
|
|
248
|
+
throw new Error("The Convex authority file changed after the operation was confirmed.");
|
|
249
|
+
}
|
|
227
250
|
const deploymentKey = TOKEN_SELECTORS.map((name) => authorityFile[name]).find(Boolean);
|
|
228
251
|
if (command === "deploy" && authorityKind === "deployment-name") {
|
|
229
252
|
throw new Error("Deploy requires a deployment-scoped key or self-hosted authority.");
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export interface InitDependencies {
|
|
2
2
|
confirm(message: string): Promise<boolean>;
|
|
3
3
|
prompt(message: string, defaultValue: string): Promise<string>;
|
|
4
|
+
readDevelopmentAuthorityLabel(): Promise<string>;
|
|
4
5
|
runConvex(args: readonly string[], input?: string): Promise<number>;
|
|
5
6
|
readEnvironmentNames(): Promise<ReadonlySet<string>>;
|
|
6
7
|
generateSchema(args: readonly string[]): Promise<number>;
|
package/dist/runtime/cli/init.js
CHANGED
|
@@ -3,7 +3,7 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { createInterface } from "node:readline/promises";
|
|
5
5
|
import { runAuthSchemaCommand } from "./auth-schema.js";
|
|
6
|
-
import { runConvexCommand } from "./convex.js";
|
|
6
|
+
import { inspectConvexAuthority, runConvexCommand } from "./convex.js";
|
|
7
7
|
const templates = {
|
|
8
8
|
"convex/auth.config.ts": `import { getConvexAuthProvider } from '@lupinum/better-convex-nuxt/better-auth/server'
|
|
9
9
|
import type { AuthConfig } from 'convex/server'
|
|
@@ -41,7 +41,8 @@ export default app
|
|
|
41
41
|
import schema from './schema'
|
|
42
42
|
import schemaMetadata from './schemaMetadata'
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
// This module is inside the isolated betterAuth component, not the public app API.
|
|
45
|
+
export const { assertProfile, consumeOne, count, create, deleteMany, deleteOne, findMany, findOne, incrementOne, rotateSigningKey, sessionAdmission, expireWorkforceSession, listWorkforceSessions, revokeAllWorkforceSessions, revokeWorkforceSession, touchWorkforceSession, updateMany, updateOne } = defineAuthAdapterFunctions({ metadata: schemaMetadata, schema })
|
|
45
46
|
`,
|
|
46
47
|
"convex/betterAuth/convex.config.ts": `import { defineComponent } from 'convex/server'
|
|
47
48
|
|
|
@@ -87,7 +88,26 @@ async function readOptional(path) {
|
|
|
87
88
|
throw error;
|
|
88
89
|
}
|
|
89
90
|
}
|
|
90
|
-
function defaultDependencies() {
|
|
91
|
+
function defaultDependencies(root) {
|
|
92
|
+
let authorityPromise;
|
|
93
|
+
const developmentAuthority = async () => {
|
|
94
|
+
authorityPromise ??= inspectConvexAuthority(root);
|
|
95
|
+
const authority = await authorityPromise;
|
|
96
|
+
if (!authority.development) {
|
|
97
|
+
throw new Error("better-convex init refuses production or unclassified authority");
|
|
98
|
+
}
|
|
99
|
+
return authority;
|
|
100
|
+
};
|
|
101
|
+
const runDevelopmentConvex = async (args, options = {}) => {
|
|
102
|
+
const authority = await developmentAuthority();
|
|
103
|
+
return await runConvexCommand(args, {
|
|
104
|
+
...options,
|
|
105
|
+
cwd: root,
|
|
106
|
+
developmentOnly: true,
|
|
107
|
+
expectedAuthorityDigest: authority.digest,
|
|
108
|
+
quiet: true
|
|
109
|
+
});
|
|
110
|
+
};
|
|
91
111
|
async function question(message) {
|
|
92
112
|
const terminal = createInterface({ input: process.stdin, output: process.stdout });
|
|
93
113
|
try {
|
|
@@ -105,11 +125,13 @@ function defaultDependencies() {
|
|
|
105
125
|
const answer = (await question(`${message} [${defaultValue}] `)).trim();
|
|
106
126
|
return answer || defaultValue;
|
|
107
127
|
},
|
|
108
|
-
|
|
128
|
+
async readDevelopmentAuthorityLabel() {
|
|
129
|
+
return (await developmentAuthority()).label;
|
|
130
|
+
},
|
|
131
|
+
runConvex: async (args, input) => await runDevelopmentConvex(args, { input }),
|
|
109
132
|
async readEnvironmentNames() {
|
|
110
133
|
let output = "";
|
|
111
|
-
const status = await
|
|
112
|
-
quiet: true,
|
|
134
|
+
const status = await runDevelopmentConvex(["env", "list", "--names-only"], {
|
|
113
135
|
onStdout: (value) => {
|
|
114
136
|
output += value;
|
|
115
137
|
}
|
|
@@ -217,9 +239,9 @@ export async function runInitCommand(args, overrides = {}) {
|
|
|
217
239
|
console.log("Usage: better-convex init [--typed-client]");
|
|
218
240
|
return 0;
|
|
219
241
|
}
|
|
220
|
-
const defaults = defaultDependencies();
|
|
221
|
-
const dependencies = { ...defaults, ...overrides };
|
|
222
242
|
const root = process.cwd();
|
|
243
|
+
const defaults = defaultDependencies(root);
|
|
244
|
+
const dependencies = { ...defaults, ...overrides };
|
|
223
245
|
const schemaPath = join(root, "convex/betterAuth/schema.ts");
|
|
224
246
|
const metadataPath = join(root, "convex/betterAuth/schemaMetadata.ts");
|
|
225
247
|
const [schema, metadata] = await Promise.all([
|
|
@@ -255,7 +277,10 @@ export async function runInitCommand(args, overrides = {}) {
|
|
|
255
277
|
"Generated auth schema conflicts with the reviewed plugin profile. Run better-convex auth schema and review the diff manually."
|
|
256
278
|
);
|
|
257
279
|
}
|
|
258
|
-
|
|
280
|
+
const authorityLabel = await dependencies.readDevelopmentAuthorityLabel();
|
|
281
|
+
if (!await dependencies.confirm(
|
|
282
|
+
`Provision development secrets and the first signing key in ${authorityLabel}?`
|
|
283
|
+
)) {
|
|
259
284
|
return 0;
|
|
260
285
|
}
|
|
261
286
|
await provisionDevelopment(dependencies);
|
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createAdapterFactory
|
|
3
3
|
} from "better-auth/adapters";
|
|
4
|
+
import { APIError } from "better-auth/api";
|
|
4
5
|
import { symmetricDecrypt, symmetricEncrypt } from "better-auth/crypto";
|
|
5
6
|
import { createFunctionHandle } from "convex/server";
|
|
7
|
+
import { ConvexError } from "convex/values";
|
|
6
8
|
import { isWritableAuthCtx, requireWritableAuthCtx } from "../context.js";
|
|
7
|
-
import {
|
|
9
|
+
import { createWorkforceAdapterTransport } from "../workforce/adapter-transport.js";
|
|
10
|
+
import { hasWorkforceSchema, workforceSchemaPlugin } from "../workforce/schema.js";
|
|
11
|
+
import { createAuthSchema, generateAuthSchemaArtifacts } from "./generate-schema.js";
|
|
12
|
+
function rejectReplay(error) {
|
|
13
|
+
if (error instanceof ConvexError && error.data === "AUTH_WORKFORCE_TOTP_REPLAYED") {
|
|
14
|
+
throw new APIError("FORBIDDEN", {
|
|
15
|
+
code: "INVALID_TWO_FACTOR_CODE",
|
|
16
|
+
message: "This code cannot be used. Wait for a new code and try again."
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
8
21
|
function toComponentWhere(where) {
|
|
9
22
|
return where?.map((condition) => ({
|
|
10
23
|
...condition,
|
|
@@ -144,9 +157,14 @@ export function createConvexAuthAdapter(ctx, component, adapterOptions = {}) {
|
|
|
144
157
|
customTransformInput: ({ data, fieldAttributes }) => fieldAttributes.type === "date" ? toEpochMilliseconds(data) : data,
|
|
145
158
|
customTransformOutput: ({ data, fieldAttributes }) => fieldAttributes.type === "date" ? toDate(data) : data
|
|
146
159
|
},
|
|
147
|
-
adapter: ({ getFieldName, getModelName, options }) => {
|
|
160
|
+
adapter: ({ getFieldName, getModelName, options, schema }) => {
|
|
148
161
|
options.telemetry = { enabled: false };
|
|
149
162
|
if (options.advanced?.database?.joins) throw new Error("AUTH_JOINS_UNSUPPORTED");
|
|
163
|
+
const workforce = options.plugins?.some((plugin) => plugin.id === workforceSchemaPlugin.id) === true;
|
|
164
|
+
if (workforce && !hasWorkforceSchema(generateAuthSchemaArtifacts(schema).metadata)) {
|
|
165
|
+
throw new Error("AUTH_WORKFORCE_SCHEMA_MISMATCH");
|
|
166
|
+
}
|
|
167
|
+
const workforceTransport = createWorkforceAdapterTransport(workforce);
|
|
150
168
|
const idTokens = createAccountIdTokenProtector(options);
|
|
151
169
|
const triggerModels = {
|
|
152
170
|
onCreate: configuredTriggerModels(adapterOptions.triggers, "onCreate").map(getModelName),
|
|
@@ -181,11 +199,14 @@ export function createConvexAuthAdapter(ctx, component, adapterOptions = {}) {
|
|
|
181
199
|
data
|
|
182
200
|
}) => {
|
|
183
201
|
requireWritableAuthCtx(ctx);
|
|
202
|
+
const { operation, consumedChallenge } = await workforceTransport.create(model, data);
|
|
184
203
|
const created = await ctx.runMutation(component.adapter.create, {
|
|
185
204
|
model,
|
|
186
205
|
data: await idTokens.protect(model, data),
|
|
187
|
-
onCreateHandle: await triggerHandle(model, "onCreate")
|
|
188
|
-
|
|
206
|
+
onCreateHandle: await triggerHandle(model, "onCreate"),
|
|
207
|
+
...operation ? { workforce: operation } : {},
|
|
208
|
+
...consumedChallenge ? { workforceConsumedChallenge: consumedChallenge } : {}
|
|
209
|
+
}).catch(rejectReplay);
|
|
189
210
|
return idTokens.reveal(model, created);
|
|
190
211
|
},
|
|
191
212
|
findOne: async ({
|
|
@@ -193,10 +214,12 @@ export function createConvexAuthAdapter(ctx, component, adapterOptions = {}) {
|
|
|
193
214
|
where,
|
|
194
215
|
select
|
|
195
216
|
}) => {
|
|
217
|
+
const operation = await workforceTransport.operation(model);
|
|
196
218
|
const found = await ctx.runQuery(component.adapter.findOne, {
|
|
197
219
|
model,
|
|
198
220
|
where: toComponentWhere(where),
|
|
199
|
-
select: mapSelect(model, select)
|
|
221
|
+
select: mapSelect(model, select),
|
|
222
|
+
...operation?.operation === "confirm-enrollment" ? { workforce: operation } : {}
|
|
200
223
|
});
|
|
201
224
|
return idTokens.reveal(model, found);
|
|
202
225
|
},
|
|
@@ -231,12 +254,14 @@ export function createConvexAuthAdapter(ctx, component, adapterOptions = {}) {
|
|
|
231
254
|
}) => {
|
|
232
255
|
requireWritableAuthCtx(ctx);
|
|
233
256
|
if (where.length === 0) return null;
|
|
257
|
+
const operation = await workforceTransport.operation();
|
|
234
258
|
const updated = await ctx.runMutation(component.adapter.updateOne, {
|
|
235
259
|
model,
|
|
236
260
|
where: toComponentWhere(where),
|
|
237
261
|
update: await idTokens.protect(model, update),
|
|
238
|
-
onUpdateHandle: await triggerHandle(model, "onUpdate")
|
|
239
|
-
|
|
262
|
+
onUpdateHandle: await triggerHandle(model, "onUpdate"),
|
|
263
|
+
...operation ? { workforce: operation } : {}
|
|
264
|
+
}).catch(rejectReplay);
|
|
240
265
|
return idTokens.reveal(model, updated);
|
|
241
266
|
},
|
|
242
267
|
updateMany: async ({ model, where, update }) => {
|
|
@@ -283,6 +308,7 @@ export function createConvexAuthAdapter(ctx, component, adapterOptions = {}) {
|
|
|
283
308
|
onUpdateHandle: await relationshipTriggerHandle("onUpdate"),
|
|
284
309
|
onUpdateModels: triggerModels.onUpdate
|
|
285
310
|
});
|
|
311
|
+
await workforceTransport.consumed(model, consumed);
|
|
286
312
|
return idTokens.reveal(model, consumed);
|
|
287
313
|
},
|
|
288
314
|
incrementOne: async ({
|
|
@@ -292,12 +318,14 @@ export function createConvexAuthAdapter(ctx, component, adapterOptions = {}) {
|
|
|
292
318
|
set
|
|
293
319
|
}) => {
|
|
294
320
|
requireWritableAuthCtx(ctx);
|
|
321
|
+
const operation = await workforceTransport.operation();
|
|
295
322
|
const incremented = await ctx.runMutation(component.adapter.incrementOne, {
|
|
296
323
|
model,
|
|
297
324
|
where: toComponentWhere(where),
|
|
298
325
|
increment,
|
|
299
326
|
set: await idTokens.protect(model, set),
|
|
300
|
-
onUpdateHandle: await triggerHandle(model, "onUpdate")
|
|
327
|
+
onUpdateHandle: await triggerHandle(model, "onUpdate"),
|
|
328
|
+
...operation ? { workforce: operation } : {}
|
|
301
329
|
});
|
|
302
330
|
return idTokens.reveal(model, incremented);
|
|
303
331
|
}
|