@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.
Files changed (79) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/auth/better-auth-browser-adapter.d.ts +1 -0
  3. package/dist/runtime/auth/better-auth-browser-adapter.js +18 -3
  4. package/dist/runtime/auth/integrated-client.d.ts +1 -1
  5. package/dist/runtime/auth/token-fetcher.d.ts +5 -1
  6. package/dist/runtime/auth/token-fetcher.js +3 -0
  7. package/dist/runtime/cli/auth-schema.js +3 -2
  8. package/dist/runtime/cli/convex.d.ts +8 -0
  9. package/dist/runtime/cli/convex.js +23 -0
  10. package/dist/runtime/cli/init.d.ts +1 -0
  11. package/dist/runtime/cli/init.js +34 -9
  12. package/dist/runtime/convex-auth/adapter/create-adapter.js +36 -8
  13. package/dist/runtime/convex-auth/adapter/define-functions.d.ts +212 -7
  14. package/dist/runtime/convex-auth/adapter/define-functions.js +201 -21
  15. package/dist/runtime/convex-auth/adapter/generate-schema.js +35 -1
  16. package/dist/runtime/convex-auth/adapter/query.d.ts +8 -3
  17. package/dist/runtime/convex-auth/adapter/query.js +20 -3
  18. package/dist/runtime/convex-auth/adapter/relationships.d.ts +2 -0
  19. package/dist/runtime/convex-auth/adapter/relationships.js +3 -1
  20. package/dist/runtime/convex-auth/auth-jwt.d.ts +3 -0
  21. package/dist/runtime/convex-auth/auth-jwt.js +12 -0
  22. package/dist/runtime/convex-auth/component/_generated/component.d.ts +340 -79
  23. package/dist/runtime/convex-auth/component/_generated/server.js +3 -3
  24. package/dist/runtime/convex-auth/component/adapter.d.ts +205 -8
  25. package/dist/runtime/convex-auth/component/adapter.js +7 -0
  26. package/dist/runtime/convex-auth/component/schema.js +5 -3
  27. package/dist/runtime/convex-auth/component/schemaMetadata.d.ts +25 -1
  28. package/dist/runtime/convex-auth/component/schemaMetadata.js +25 -1
  29. package/dist/runtime/convex-auth/create-auth-component.d.ts +24 -2
  30. package/dist/runtime/convex-auth/create-auth-component.js +33 -15
  31. package/dist/runtime/convex-auth/create-better-convex-auth.d.ts +14 -5
  32. package/dist/runtime/convex-auth/create-better-convex-auth.js +99 -35
  33. package/dist/runtime/convex-auth/index.d.ts +3 -1
  34. package/dist/runtime/convex-auth/index.js +2 -0
  35. package/dist/runtime/convex-auth/oauth-live-access.js +6 -10
  36. package/dist/runtime/convex-auth/plugin.js +30 -1
  37. package/dist/runtime/convex-auth/session-generation.d.ts +28 -0
  38. package/dist/runtime/convex-auth/session-generation.js +98 -0
  39. package/dist/runtime/convex-auth/test.d.ts +20 -407
  40. package/dist/runtime/convex-auth/test.js +31 -1
  41. package/dist/runtime/convex-auth/workforce/adapter-policy.d.ts +33 -0
  42. package/dist/runtime/convex-auth/workforce/adapter-policy.js +135 -0
  43. package/dist/runtime/convex-auth/workforce/adapter-transport.d.ts +40 -0
  44. package/dist/runtime/convex-auth/workforce/adapter-transport.js +31 -0
  45. package/dist/runtime/convex-auth/workforce/admission.d.ts +13 -0
  46. package/dist/runtime/convex-auth/workforce/admission.js +17 -0
  47. package/dist/runtime/convex-auth/workforce/credential-generation.d.ts +9 -0
  48. package/dist/runtime/convex-auth/workforce/credential-generation.js +41 -0
  49. package/dist/runtime/convex-auth/workforce/factor-fingerprint.d.ts +2 -0
  50. package/dist/runtime/convex-auth/workforce/factor-fingerprint.js +4 -0
  51. package/dist/runtime/convex-auth/workforce/factor-transitions.d.ts +36 -0
  52. package/dist/runtime/convex-auth/workforce/factor-transitions.js +180 -0
  53. package/dist/runtime/convex-auth/workforce/operations.d.ts +150 -0
  54. package/dist/runtime/convex-auth/workforce/operations.js +45 -0
  55. package/dist/runtime/convex-auth/workforce/otp-binding.d.ts +8 -0
  56. package/dist/runtime/convex-auth/workforce/otp-binding.js +48 -0
  57. package/dist/runtime/convex-auth/workforce/pending-factor-view.d.ts +4 -0
  58. package/dist/runtime/convex-auth/workforce/pending-factor-view.js +12 -0
  59. package/dist/runtime/convex-auth/workforce/profile.d.ts +9 -0
  60. package/dist/runtime/convex-auth/workforce/profile.js +24 -0
  61. package/dist/runtime/convex-auth/workforce/provider-hooks.d.ts +6 -0
  62. package/dist/runtime/convex-auth/workforce/provider-hooks.js +191 -0
  63. package/dist/runtime/convex-auth/workforce/replay.d.ts +32 -0
  64. package/dist/runtime/convex-auth/workforce/replay.js +75 -0
  65. package/dist/runtime/convex-auth/workforce/request-context.d.ts +29 -0
  66. package/dist/runtime/convex-auth/workforce/request-context.js +88 -0
  67. package/dist/runtime/convex-auth/workforce/schema.d.ts +93 -0
  68. package/dist/runtime/convex-auth/workforce/schema.js +110 -0
  69. package/dist/runtime/convex-auth/workforce/session-assurance.d.ts +29 -0
  70. package/dist/runtime/convex-auth/workforce/session-assurance.js +43 -0
  71. package/dist/runtime/convex-auth/workforce/session-management.d.ts +91 -0
  72. package/dist/runtime/convex-auth/workforce/session-management.js +133 -0
  73. package/dist/runtime/convex-auth/workforce/session-transitions.d.ts +11 -0
  74. package/dist/runtime/convex-auth/workforce/session-transitions.js +106 -0
  75. package/dist/runtime/plugin.auth.client.js +11 -1
  76. package/dist/runtime/shared/release-fingerprint.js +1 -1
  77. package/package.json +18 -13
  78. package/security/upstream-convex-better-auth.json +251 -42
  79. package/dist/runtime/convex-auth/component/schema.d.ts +0 -805
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=4.5.2 <5"
6
6
  },
7
- "version": "1.0.0-beta.2",
7
+ "version": "1.0.0-beta.4",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
@@ -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 (!sessionToken) callbacks.anonymous(null);
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 canonical provider session, then await its Convex acceptance. */
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. `authError` is also set when a token decodes
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, and oauth-provider once each.`
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>;
@@ -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
- export const { consumeOne, count, create, deleteMany, deleteOne, findMany, findOne, incrementOne, rotateSigningKey, updateMany, updateOne } = defineAuthAdapterFunctions({ metadata: schemaMetadata, schema })
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
- runConvex: async (args, input) => await runConvexCommand(args, { input, quiet: true }),
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 runConvexCommand(["env", "list", "--names-only"], {
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
- if (!await dependencies.confirm("Provision development secrets and the first signing key?")) {
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 { createAuthSchema } from "./generate-schema.js";
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
  }