@robelest/convex-auth 0.0.4-preview.22 → 0.0.4-preview.23
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/authorization/index.d.ts +1 -1
- package/dist/authorization/index.js +1 -1
- package/dist/authorization/index.js.map +1 -1
- package/dist/client/index.d.ts +1 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +36 -39
- package/dist/client/index.js.map +1 -1
- package/dist/component/client/index.d.ts +1 -2
- package/dist/component/model.d.ts +9 -9
- package/dist/component/model.d.ts.map +1 -1
- package/dist/component/public/enterprise/audit.d.ts.map +1 -1
- package/dist/component/public/enterprise/audit.js.map +1 -1
- package/dist/component/public/enterprise/core.d.ts.map +1 -1
- package/dist/component/public/enterprise/core.js.map +1 -1
- package/dist/component/public/enterprise/domains.d.ts.map +1 -1
- package/dist/component/public/enterprise/domains.js.map +1 -1
- package/dist/component/public/enterprise/scim.d.ts.map +1 -1
- package/dist/component/public/enterprise/scim.js.map +1 -1
- package/dist/component/public/enterprise/secrets.d.ts.map +1 -1
- package/dist/component/public/enterprise/secrets.js.map +1 -1
- package/dist/component/public/enterprise/webhooks.d.ts.map +1 -1
- package/dist/component/public/enterprise/webhooks.js.map +1 -1
- package/dist/component/public/factors/devices.d.ts.map +1 -1
- package/dist/component/public/factors/devices.js.map +1 -1
- package/dist/component/public/factors/passkeys.d.ts.map +1 -1
- package/dist/component/public/factors/passkeys.js.map +1 -1
- package/dist/component/public/factors/totp.d.ts.map +1 -1
- package/dist/component/public/factors/totp.js.map +1 -1
- package/dist/component/public/groups/core.js.map +1 -1
- package/dist/component/public/groups/invites.d.ts.map +1 -1
- package/dist/component/public/groups/invites.js.map +1 -1
- package/dist/component/public/groups/members.d.ts.map +1 -1
- package/dist/component/public/groups/members.js.map +1 -1
- package/dist/component/public/identity/accounts.d.ts.map +1 -1
- package/dist/component/public/identity/accounts.js.map +1 -1
- package/dist/component/public/identity/codes.d.ts.map +1 -1
- package/dist/component/public/identity/codes.js.map +1 -1
- package/dist/component/public/identity/sessions.d.ts.map +1 -1
- package/dist/component/public/identity/sessions.js.map +1 -1
- package/dist/component/public/identity/tokens.d.ts.map +1 -1
- package/dist/component/public/identity/tokens.js.map +1 -1
- package/dist/component/public/identity/users.d.ts.map +1 -1
- package/dist/component/public/identity/users.js.map +1 -1
- package/dist/component/public/identity/verifiers.d.ts.map +1 -1
- package/dist/component/public/identity/verifiers.js.map +1 -1
- package/dist/component/public/security/keys.d.ts.map +1 -1
- package/dist/component/public/security/keys.js.map +1 -1
- package/dist/component/public/security/limits.d.ts.map +1 -1
- package/dist/component/public/security/limits.js.map +1 -1
- package/dist/component/schema.d.ts +42 -42
- package/dist/component/server/auth.d.ts +37 -40
- package/dist/component/server/auth.d.ts.map +1 -1
- package/dist/component/server/auth.js +57 -23
- package/dist/component/server/auth.js.map +1 -1
- package/dist/component/server/core.js +116 -235
- package/dist/component/server/core.js.map +1 -1
- package/dist/component/server/crypto.js +25 -7
- package/dist/component/server/crypto.js.map +1 -1
- package/dist/component/server/device.js +58 -15
- package/dist/component/server/device.js.map +1 -1
- package/dist/component/server/enterprise/domain.js +148 -59
- package/dist/component/server/enterprise/domain.js.map +1 -1
- package/dist/component/server/enterprise/http.js +36 -15
- package/dist/component/server/enterprise/http.js.map +1 -1
- package/dist/component/server/enterprise/oidc.js +1 -1
- package/dist/component/server/http.js +26 -21
- package/dist/component/server/http.js.map +1 -1
- package/dist/component/server/identity.js +5 -2
- package/dist/component/server/identity.js.map +1 -1
- package/dist/component/server/limits.js +21 -30
- package/dist/component/server/limits.js.map +1 -1
- package/dist/component/server/mutations/account.js +12 -10
- package/dist/component/server/mutations/account.js.map +1 -1
- package/dist/component/server/mutations/code.js +5 -2
- package/dist/component/server/mutations/code.js.map +1 -1
- package/dist/component/server/mutations/invalidate.js +1 -1
- package/dist/component/server/mutations/invalidate.js.map +1 -1
- package/dist/component/server/mutations/oauth.js +10 -4
- package/dist/component/server/mutations/oauth.js.map +1 -1
- package/dist/component/server/mutations/refresh.js +2 -2
- package/dist/component/server/mutations/refresh.js.map +1 -1
- package/dist/component/server/mutations/register.js +46 -42
- package/dist/component/server/mutations/register.js.map +1 -1
- package/dist/component/server/mutations/retrieve.js +21 -25
- package/dist/component/server/mutations/retrieve.js.map +1 -1
- package/dist/component/server/mutations/signature.js +10 -4
- package/dist/component/server/mutations/signature.js.map +1 -1
- package/dist/component/server/mutations/signout.js.map +1 -1
- package/dist/component/server/mutations/store.js +9 -24
- package/dist/component/server/mutations/store.js.map +1 -1
- package/dist/component/server/mutations/verifier.js.map +1 -1
- package/dist/component/server/mutations/verify.js +1 -1
- package/dist/component/server/mutations/verify.js.map +1 -1
- package/dist/component/server/oauth.js +53 -16
- package/dist/component/server/oauth.js.map +1 -1
- package/dist/component/server/passkey.js +115 -31
- package/dist/component/server/passkey.js.map +1 -1
- package/dist/component/server/redirects.js +9 -3
- package/dist/component/server/redirects.js.map +1 -1
- package/dist/component/server/refresh.js +10 -7
- package/dist/component/server/refresh.js.map +1 -1
- package/dist/component/server/runtime.d.ts +1 -1
- package/dist/component/server/runtime.d.ts.map +1 -1
- package/dist/component/server/runtime.js +62 -20
- package/dist/component/server/runtime.js.map +1 -1
- package/dist/component/server/signin.js +34 -10
- package/dist/component/server/signin.js.map +1 -1
- package/dist/component/server/totp.js +79 -19
- package/dist/component/server/totp.js.map +1 -1
- package/dist/component/server/types.d.ts +12 -20
- package/dist/component/server/types.d.ts.map +1 -1
- package/dist/component/server/types.js.map +1 -1
- package/dist/component/server/users.js +6 -3
- package/dist/component/server/users.js.map +1 -1
- package/dist/component/server/utils.js +10 -4
- package/dist/component/server/utils.js.map +1 -1
- package/dist/core/types.d.ts +14 -22
- package/dist/core/types.d.ts.map +1 -1
- package/dist/factors/device.js +8 -9
- package/dist/factors/device.js.map +1 -1
- package/dist/factors/passkey.js +18 -21
- package/dist/factors/passkey.js.map +1 -1
- package/dist/providers/password.js +66 -81
- package/dist/providers/password.js.map +1 -1
- package/dist/runtime/invite.js +2 -8
- package/dist/runtime/invite.js.map +1 -1
- package/dist/server/auth.d.ts +37 -40
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +57 -23
- package/dist/server/auth.js.map +1 -1
- package/dist/server/core.d.ts +71 -159
- package/dist/server/core.d.ts.map +1 -1
- package/dist/server/core.js +116 -235
- package/dist/server/core.js.map +1 -1
- package/dist/server/crypto.d.ts.map +1 -1
- package/dist/server/crypto.js +25 -7
- package/dist/server/crypto.js.map +1 -1
- package/dist/server/device.js +58 -15
- package/dist/server/device.js.map +1 -1
- package/dist/server/enterprise/domain.d.ts +0 -8
- package/dist/server/enterprise/domain.d.ts.map +1 -1
- package/dist/server/enterprise/domain.js +148 -59
- package/dist/server/enterprise/domain.js.map +1 -1
- package/dist/server/enterprise/http.d.ts.map +1 -1
- package/dist/server/enterprise/http.js +35 -14
- package/dist/server/enterprise/http.js.map +1 -1
- package/dist/server/http.d.ts +2 -2
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.js +25 -20
- package/dist/server/http.js.map +1 -1
- package/dist/server/identity.js +5 -2
- package/dist/server/identity.js.map +1 -1
- package/dist/server/index.d.ts +2 -2
- package/dist/server/limits.js +21 -30
- package/dist/server/limits.js.map +1 -1
- package/dist/server/mounts.d.ts +24 -62
- package/dist/server/mounts.d.ts.map +1 -1
- package/dist/server/mounts.js +45 -106
- package/dist/server/mounts.js.map +1 -1
- package/dist/server/mutations/account.d.ts +8 -9
- package/dist/server/mutations/account.d.ts.map +1 -1
- package/dist/server/mutations/account.js +11 -9
- package/dist/server/mutations/account.js.map +1 -1
- package/dist/server/mutations/code.d.ts +12 -12
- package/dist/server/mutations/code.d.ts.map +1 -1
- package/dist/server/mutations/code.js +5 -2
- package/dist/server/mutations/code.js.map +1 -1
- package/dist/server/mutations/invalidate.d.ts +4 -4
- package/dist/server/mutations/invalidate.d.ts.map +1 -1
- package/dist/server/mutations/invalidate.js.map +1 -1
- package/dist/server/mutations/oauth.d.ts +14 -12
- package/dist/server/mutations/oauth.d.ts.map +1 -1
- package/dist/server/mutations/oauth.js +9 -3
- package/dist/server/mutations/oauth.js.map +1 -1
- package/dist/server/mutations/refresh.d.ts +3 -3
- package/dist/server/mutations/refresh.d.ts.map +1 -1
- package/dist/server/mutations/refresh.js +1 -1
- package/dist/server/mutations/refresh.js.map +1 -1
- package/dist/server/mutations/register.d.ts +11 -11
- package/dist/server/mutations/register.d.ts.map +1 -1
- package/dist/server/mutations/register.js +45 -41
- package/dist/server/mutations/register.js.map +1 -1
- package/dist/server/mutations/retrieve.d.ts +6 -6
- package/dist/server/mutations/retrieve.d.ts.map +1 -1
- package/dist/server/mutations/retrieve.js +20 -24
- package/dist/server/mutations/retrieve.js.map +1 -1
- package/dist/server/mutations/signature.d.ts +6 -7
- package/dist/server/mutations/signature.d.ts.map +1 -1
- package/dist/server/mutations/signature.js +9 -3
- package/dist/server/mutations/signature.js.map +1 -1
- package/dist/server/mutations/signin.d.ts +5 -5
- package/dist/server/mutations/signin.d.ts.map +1 -1
- package/dist/server/mutations/signout.js.map +1 -1
- package/dist/server/mutations/store.d.ts +83 -83
- package/dist/server/mutations/store.js +8 -23
- package/dist/server/mutations/store.js.map +1 -1
- package/dist/server/mutations/verifier.js.map +1 -1
- package/dist/server/mutations/verify.d.ts +7 -7
- package/dist/server/mutations/verify.d.ts.map +1 -1
- package/dist/server/mutations/verify.js.map +1 -1
- package/dist/server/oauth.js +53 -16
- package/dist/server/oauth.js.map +1 -1
- package/dist/server/passkey.d.ts +2 -2
- package/dist/server/passkey.d.ts.map +1 -1
- package/dist/server/passkey.js +114 -30
- package/dist/server/passkey.js.map +1 -1
- package/dist/server/redirects.js +9 -3
- package/dist/server/redirects.js.map +1 -1
- package/dist/server/refresh.js +10 -7
- package/dist/server/refresh.js.map +1 -1
- package/dist/server/runtime.d.ts +7 -7
- package/dist/server/runtime.d.ts.map +1 -1
- package/dist/server/runtime.js +61 -19
- package/dist/server/runtime.js.map +1 -1
- package/dist/server/signin.js +34 -10
- package/dist/server/signin.js.map +1 -1
- package/dist/server/ssr.d.ts.map +1 -1
- package/dist/server/ssr.js +175 -184
- package/dist/server/ssr.js.map +1 -1
- package/dist/server/totp.js +78 -18
- package/dist/server/totp.js.map +1 -1
- package/dist/server/types.d.ts +13 -21
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/types.js.map +1 -1
- package/dist/server/users.js +6 -3
- package/dist/server/users.js.map +1 -1
- package/dist/server/utils.js +10 -4
- package/dist/server/utils.js.map +1 -1
- package/package.json +1 -5
- package/src/authorization/index.ts +1 -1
- package/src/client/core/types.ts +14 -14
- package/src/client/factors/device.ts +10 -12
- package/src/client/factors/passkey.ts +23 -26
- package/src/client/index.ts +54 -64
- package/src/client/runtime/invite.ts +5 -7
- package/src/component/index.ts +1 -1
- package/src/component/public/enterprise/audit.ts +6 -1
- package/src/component/public/enterprise/core.ts +1 -0
- package/src/component/public/enterprise/domains.ts +5 -1
- package/src/component/public/enterprise/scim.ts +1 -0
- package/src/component/public/enterprise/secrets.ts +1 -0
- package/src/component/public/enterprise/webhooks.ts +1 -0
- package/src/component/public/factors/devices.ts +1 -0
- package/src/component/public/factors/passkeys.ts +1 -0
- package/src/component/public/factors/totp.ts +1 -0
- package/src/component/public/groups/core.ts +1 -1
- package/src/component/public/groups/invites.ts +7 -1
- package/src/component/public/groups/members.ts +1 -0
- package/src/component/public/identity/accounts.ts +1 -0
- package/src/component/public/identity/codes.ts +1 -0
- package/src/component/public/identity/sessions.ts +1 -0
- package/src/component/public/identity/tokens.ts +1 -0
- package/src/component/public/identity/users.ts +1 -0
- package/src/component/public/identity/verifiers.ts +1 -0
- package/src/component/public/security/keys.ts +1 -0
- package/src/component/public/security/limits.ts +1 -0
- package/src/providers/password.ts +89 -110
- package/src/server/auth.ts +92 -70
- package/src/server/core.ts +197 -233
- package/src/server/crypto.ts +31 -29
- package/src/server/device.ts +65 -32
- package/src/server/enterprise/domain.ts +158 -170
- package/src/server/enterprise/http.ts +46 -39
- package/src/server/http.ts +36 -30
- package/src/server/identity.ts +5 -5
- package/src/server/index.ts +1 -1
- package/src/server/limits.ts +53 -80
- package/src/server/mounts.ts +47 -74
- package/src/server/mutations/account.ts +22 -36
- package/src/server/mutations/code.ts +6 -6
- package/src/server/mutations/invalidate.ts +1 -1
- package/src/server/mutations/oauth.ts +14 -8
- package/src/server/mutations/refresh.ts +5 -4
- package/src/server/mutations/register.ts +87 -132
- package/src/server/mutations/retrieve.ts +44 -44
- package/src/server/mutations/signature.ts +13 -6
- package/src/server/mutations/signout.ts +1 -1
- package/src/server/mutations/store.ts +16 -31
- package/src/server/mutations/verifier.ts +1 -1
- package/src/server/mutations/verify.ts +3 -5
- package/src/server/oauth.ts +60 -69
- package/src/server/passkey.ts +567 -517
- package/src/server/redirects.ts +10 -6
- package/src/server/refresh.ts +14 -18
- package/src/server/runtime.ts +70 -55
- package/src/server/signin.ts +44 -37
- package/src/server/ssr.ts +390 -407
- package/src/server/totp.ts +85 -35
- package/src/server/types.ts +19 -22
- package/src/server/users.ts +7 -6
- package/src/server/utils.ts +10 -12
- package/dist/component/server/authError.js +0 -34
- package/dist/component/server/authError.js.map +0 -1
- package/dist/component/server/errors.d.ts +0 -1
- package/dist/component/server/errors.js +0 -137
- package/dist/component/server/errors.js.map +0 -1
- package/dist/server/authError.d.ts +0 -46
- package/dist/server/authError.d.ts.map +0 -1
- package/dist/server/authError.js +0 -34
- package/dist/server/authError.js.map +0 -1
- package/dist/server/errors.d.ts +0 -177
- package/dist/server/errors.d.ts.map +0 -1
- package/dist/server/errors.js +0 -212
- package/dist/server/errors.js.map +0 -1
- package/src/server/authError.ts +0 -44
- package/src/server/errors.ts +0 -290
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.js","names":[],"sources":["../../../../src/component/public/enterprise/core.ts"],"sourcesContent":["import { ConvexError, v } from \"convex/values\";\nimport { mutation, query } from \"../../functions\";\nimport {\n vEnterpriseDoc,\n vEnterpriseDomainDoc,\n vEnterprisePolicy,\n vEnterpriseStatus,\n vPaginated,\n} from \"../../model\";\n\n/**\n * Create a new enterprise record attached to a root group.\n *\n * Each group may only have one enterprise record. If an enterprise already\n * exists for the given group, a `ENTERPRISE_ALREADY_EXISTS` error is thrown.\n * The enterprise status defaults to `\"draft\"` when not explicitly provided.\n *\n * @param args.groupId - The ID of the root group that owns this enterprise.\n * @param args.slug - An optional URL-friendly identifier for the enterprise.\n * @param args.name - An optional human-readable display name for the enterprise.\n * @param args.status - The lifecycle status (`\"draft\"`, `\"active\"`, or `\"disabled\"`). Defaults to `\"draft\"`.\n * @param args.policy - An optional enterprise policy object controlling identity linking, provisioning, and deprovisioning behavior.\n * @param args.config - An optional arbitrary configuration blob for enterprise-specific settings.\n * @param args.extend - An optional arbitrary extension object for custom fields.\n * @returns The ID of the newly created `Enterprise` document.\n *\n * @example\n * ```ts\n * const enterpriseId = await ctx.runMutation(\n * components.auth.enterprise.enterpriseCreate,\n * {\n * groupId: orgGroupId,\n * slug: \"acme-corp\",\n * name: \"Acme Corporation\",\n * status: \"active\",\n * },\n * );\n * ```\n */\nexport const enterpriseCreate = mutation({\n args: {\n groupId: v.id(\"Group\"),\n slug: v.optional(v.string()),\n name: v.optional(v.string()),\n status: v.optional(vEnterpriseStatus),\n policy: v.optional(vEnterprisePolicy),\n config: v.optional(v.any()),\n extend: v.optional(v.any()),\n },\n returns: v.id(\"Enterprise\"),\n handler: async (ctx, args) => {\n const existing = await ctx.db\n .query(\"Enterprise\")\n .withIndex(\"group_id\", (idx) => idx.eq(\"groupId\", args.groupId))\n .first();\n if (existing) {\n throw new ConvexError({\n code: \"ENTERPRISE_ALREADY_EXISTS\",\n message: \"An enterprise record already exists for this group.\",\n });\n }\n return await ctx.db.insert(\"Enterprise\", {\n ...args,\n status: args.status ?? \"draft\",\n });\n },\n});\n\n/**\n * Retrieve a single enterprise record by its document ID.\n *\n * Returns the full enterprise document if it exists, or `null` if no\n * enterprise is found with the given ID.\n *\n * @param args.enterpriseId - The document ID of the enterprise to retrieve.\n * @returns The enterprise document, or `null` if not found.\n *\n * @example\n * ```ts\n * const enterprise = await ctx.runQuery(\n * components.auth.enterprise.enterpriseGet,\n * { enterpriseId },\n * );\n * if (enterprise) {\n * console.log(enterprise.name, enterprise.status);\n * }\n * ```\n */\nexport const enterpriseGet = query({\n args: { enterpriseId: v.id(\"Enterprise\") },\n returns: v.union(vEnterpriseDoc, v.null()),\n handler: async (ctx, { enterpriseId }) => {\n return await ctx.db.get(\"Enterprise\", enterpriseId);\n },\n});\n\n/**\n * Retrieve an enterprise record by the ID of its owning group.\n *\n * Looks up the enterprise that is linked to the specified group using the\n * `group_id` index. Returns `null` if no enterprise is associated with the group.\n *\n * @param args.groupId - The ID of the root group whose enterprise record to look up.\n * @returns The enterprise document, or `null` if the group has no enterprise.\n *\n * @example\n * ```ts\n * const enterprise = await ctx.runQuery(\n * components.auth.enterprise.enterpriseGetByGroup,\n * { groupId: orgGroupId },\n * );\n * ```\n */\nexport const enterpriseGetByGroup = query({\n args: { groupId: v.id(\"Group\") },\n returns: v.union(vEnterpriseDoc, v.null()),\n handler: async (ctx, { groupId }) => {\n return await ctx.db\n .query(\"Enterprise\")\n .withIndex(\"group_id\", (idx) => idx.eq(\"groupId\", groupId))\n .first();\n },\n});\n\n/**\n * Retrieve an enterprise record by one of its linked domain names.\n *\n * Looks up an `EnterpriseDomain` row matching the given domain string, then\n * resolves the parent enterprise. Returns both the enterprise and the matched\n * domain document, or `null` if the domain is not registered or its enterprise\n * no longer exists.\n *\n * @param args.domain - The domain name to search for (e.g. `\"acme.com\"`).\n * @returns An object containing the `enterprise` and `domain` documents, or `null` if not found.\n *\n * @example\n * ```ts\n * const result = await ctx.runQuery(\n * components.auth.enterprise.enterpriseGetByDomain,\n * { domain: \"acme.com\" },\n * );\n * if (result) {\n * console.log(result.enterprise.name, result.domain.verifiedAt);\n * }\n * ```\n */\nexport const enterpriseGetByDomain = query({\n args: { domain: v.string() },\n returns: v.union(\n v.object({\n enterprise: vEnterpriseDoc,\n domain: vEnterpriseDomainDoc,\n }),\n v.null(),\n ),\n handler: async (ctx, { domain }) => {\n const domainRow = await ctx.db\n .query(\"EnterpriseDomain\")\n .withIndex(\"domain\", (idx) => idx.eq(\"domain\", domain))\n .first();\n if (!domainRow) {\n return null;\n }\n const enterprise = await ctx.db.get(\"Enterprise\", domainRow.enterpriseId);\n if (!enterprise) {\n return null;\n }\n return { enterprise, domain: domainRow };\n },\n});\n\n/**\n * List enterprise records with optional filtering and cursor-based pagination.\n *\n * Supports filtering by `groupId`, `slug`, and/or `status`. The query selects\n * the most specific index available for the primary filter, then applies\n * remaining predicates as post-filters. Results are ordered by creation time\n * (or the specified field) and paginated using an opaque cursor.\n *\n * @param args.where - Optional filter criteria: `groupId`, `slug`, and/or `status`.\n * @param args.limit - Maximum number of items per page (clamped between 1 and 100, defaults to 50).\n * @param args.cursor - An opaque cursor string returned from a previous call to fetch the next page, or `null` / omitted for the first page.\n * @param args.orderBy - The field to sort results by: `\"_creationTime\"`, `\"name\"`, `\"slug\"`, or `\"status\"`.\n * @param args.order - Sort direction: `\"asc\"` or `\"desc\"` (defaults to `\"desc\"`).\n * @returns A paginated result containing `items` (array of enterprise documents) and `nextCursor` (`string | null`).\n *\n * @example\n * ```ts\n * const page = await ctx.runQuery(\n * components.auth.enterprise.enterpriseList,\n * {\n * where: { status: \"active\" },\n * limit: 25,\n * order: \"asc\",\n * },\n * );\n * for (const ent of page.items) {\n * console.log(ent.name);\n * }\n * // Fetch next page:\n * const nextPage = await ctx.runQuery(\n * components.auth.enterprise.enterpriseList,\n * { where: { status: \"active\" }, cursor: page.nextCursor },\n * );\n * ```\n */\nexport const enterpriseList = query({\n args: {\n where: v.optional(\n v.object({\n groupId: v.optional(v.id(\"Group\")),\n slug: v.optional(v.string()),\n status: v.optional(vEnterpriseStatus),\n }),\n ),\n limit: v.optional(v.number()),\n cursor: v.optional(v.union(v.string(), v.null())),\n orderBy: v.optional(\n v.union(\n v.literal(\"_creationTime\"),\n v.literal(\"name\"),\n v.literal(\"slug\"),\n v.literal(\"status\"),\n ),\n ),\n order: v.optional(v.union(v.literal(\"asc\"), v.literal(\"desc\"))),\n },\n returns: vPaginated(vEnterpriseDoc),\n handler: async (ctx, args) => {\n const where = args.where ?? {};\n const limit = Math.min(Math.max(args.limit ?? 50, 1), 100);\n const order = args.order ?? \"desc\";\n\n let q;\n if (where.groupId !== undefined) {\n q = ctx.db\n .query(\"Enterprise\")\n .withIndex(\"group_id\", (idx) => idx.eq(\"groupId\", where.groupId!));\n } else if (where.slug !== undefined) {\n q = ctx.db\n .query(\"Enterprise\")\n .withIndex(\"slug\", (idx) => idx.eq(\"slug\", where.slug!));\n } else if (where.status !== undefined) {\n q = ctx.db\n .query(\"Enterprise\")\n .withIndex(\"status\", (idx) => idx.eq(\"status\", where.status!));\n } else {\n q = ctx.db.query(\"Enterprise\");\n }\n\n if (where.groupId !== undefined && where.slug !== undefined) {\n q = q.filter((f) => f.eq(f.field(\"slug\"), where.slug!));\n }\n if (where.status !== undefined && where.groupId === undefined) {\n // already handled by index in the dedicated branch\n } else if (where.status !== undefined) {\n q = q.filter((f) => f.eq(f.field(\"status\"), where.status!));\n }\n\n q = q.order(order);\n const all = await q.collect();\n let startIdx = 0;\n if (args.cursor) {\n const cursorIdx = all.findIndex((doc) => doc._id === args.cursor);\n if (cursorIdx !== -1) {\n startIdx = cursorIdx + 1;\n }\n }\n const page = all.slice(startIdx, startIdx + limit + 1);\n const hasMore = page.length > limit;\n const items = hasMore ? page.slice(0, limit) : page;\n const nextCursor = hasMore ? items[items.length - 1]._id : null;\n return { items, nextCursor };\n },\n});\n\n/**\n * Partially update (patch) an existing enterprise record.\n *\n * Merges the provided `data` fields into the existing enterprise document.\n * Only the fields present in `data` are changed; all other fields are preserved.\n *\n * @param args.enterpriseId - The document ID of the enterprise to update.\n * @param args.data - An object containing the fields to update (e.g. `{ name, status, policy }`).\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * await ctx.runMutation(\n * components.auth.enterprise.enterpriseUpdate,\n * {\n * enterpriseId,\n * data: { status: \"active\", name: \"Acme Corp (Renamed)\" },\n * },\n * );\n * ```\n */\nexport const enterpriseUpdate = mutation({\n args: { enterpriseId: v.id(\"Enterprise\"), data: v.any() },\n returns: v.null(),\n handler: async (ctx, { enterpriseId, data }) => {\n await ctx.db.patch(enterpriseId, data);\n return null;\n },\n});\n\n/**\n * Delete an enterprise record and all of its associated child data.\n *\n * This cascading delete removes the enterprise document along with all linked\n * domain records, domain verification records, and enterprise secrets. Callers\n * should ensure that higher-level cleanup (e.g. SCIM identities, webhook\n * endpoints) is handled separately if needed.\n *\n * @param args.enterpriseId - The document ID of the enterprise to delete.\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * await ctx.runMutation(\n * components.auth.enterprise.enterpriseDelete,\n * { enterpriseId },\n * );\n * ```\n */\nexport const enterpriseDelete = mutation({\n args: { enterpriseId: v.id(\"Enterprise\") },\n returns: v.null(),\n handler: async (ctx, { enterpriseId }) => {\n const domains = await ctx.db\n .query(\"EnterpriseDomain\")\n .withIndex(\"enterprise_id\", (idx) => idx.eq(\"enterpriseId\", enterpriseId))\n .collect();\n for (const domain of domains) {\n const verification = await ctx.db\n .query(\"EnterpriseDomainVerification\")\n .withIndex(\"domain_id\", (idx) => idx.eq(\"domainId\", domain._id))\n .first();\n if (verification) {\n await ctx.db.delete(verification._id);\n }\n await ctx.db.delete(domain._id);\n }\n const secrets = await ctx.db\n .query(\"EnterpriseSecret\")\n .withIndex(\"enterprise_id\", (idx) => idx.eq(\"enterpriseId\", enterpriseId))\n .collect();\n for (const secret of secrets) {\n await ctx.db.delete(secret._id);\n }\n await ctx.db.delete(enterpriseId);\n return null;\n },\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"core.js","names":[],"sources":["../../../../src/component/public/enterprise/core.ts"],"sourcesContent":["import { ConvexError, v } from \"convex/values\";\n\nimport { mutation, query } from \"../../functions\";\nimport {\n vEnterpriseDoc,\n vEnterpriseDomainDoc,\n vEnterprisePolicy,\n vEnterpriseStatus,\n vPaginated,\n} from \"../../model\";\n\n/**\n * Create a new enterprise record attached to a root group.\n *\n * Each group may only have one enterprise record. If an enterprise already\n * exists for the given group, a `ENTERPRISE_ALREADY_EXISTS` error is thrown.\n * The enterprise status defaults to `\"draft\"` when not explicitly provided.\n *\n * @param args.groupId - The ID of the root group that owns this enterprise.\n * @param args.slug - An optional URL-friendly identifier for the enterprise.\n * @param args.name - An optional human-readable display name for the enterprise.\n * @param args.status - The lifecycle status (`\"draft\"`, `\"active\"`, or `\"disabled\"`). Defaults to `\"draft\"`.\n * @param args.policy - An optional enterprise policy object controlling identity linking, provisioning, and deprovisioning behavior.\n * @param args.config - An optional arbitrary configuration blob for enterprise-specific settings.\n * @param args.extend - An optional arbitrary extension object for custom fields.\n * @returns The ID of the newly created `Enterprise` document.\n *\n * @example\n * ```ts\n * const enterpriseId = await ctx.runMutation(\n * components.auth.enterprise.enterpriseCreate,\n * {\n * groupId: orgGroupId,\n * slug: \"acme-corp\",\n * name: \"Acme Corporation\",\n * status: \"active\",\n * },\n * );\n * ```\n */\nexport const enterpriseCreate = mutation({\n args: {\n groupId: v.id(\"Group\"),\n slug: v.optional(v.string()),\n name: v.optional(v.string()),\n status: v.optional(vEnterpriseStatus),\n policy: v.optional(vEnterprisePolicy),\n config: v.optional(v.any()),\n extend: v.optional(v.any()),\n },\n returns: v.id(\"Enterprise\"),\n handler: async (ctx, args) => {\n const existing = await ctx.db\n .query(\"Enterprise\")\n .withIndex(\"group_id\", (idx) => idx.eq(\"groupId\", args.groupId))\n .first();\n if (existing) {\n throw new ConvexError({\n code: \"ENTERPRISE_ALREADY_EXISTS\",\n message: \"An enterprise record already exists for this group.\",\n });\n }\n return await ctx.db.insert(\"Enterprise\", {\n ...args,\n status: args.status ?? \"draft\",\n });\n },\n});\n\n/**\n * Retrieve a single enterprise record by its document ID.\n *\n * Returns the full enterprise document if it exists, or `null` if no\n * enterprise is found with the given ID.\n *\n * @param args.enterpriseId - The document ID of the enterprise to retrieve.\n * @returns The enterprise document, or `null` if not found.\n *\n * @example\n * ```ts\n * const enterprise = await ctx.runQuery(\n * components.auth.enterprise.enterpriseGet,\n * { enterpriseId },\n * );\n * if (enterprise) {\n * console.log(enterprise.name, enterprise.status);\n * }\n * ```\n */\nexport const enterpriseGet = query({\n args: { enterpriseId: v.id(\"Enterprise\") },\n returns: v.union(vEnterpriseDoc, v.null()),\n handler: async (ctx, { enterpriseId }) => {\n return await ctx.db.get(\"Enterprise\", enterpriseId);\n },\n});\n\n/**\n * Retrieve an enterprise record by the ID of its owning group.\n *\n * Looks up the enterprise that is linked to the specified group using the\n * `group_id` index. Returns `null` if no enterprise is associated with the group.\n *\n * @param args.groupId - The ID of the root group whose enterprise record to look up.\n * @returns The enterprise document, or `null` if the group has no enterprise.\n *\n * @example\n * ```ts\n * const enterprise = await ctx.runQuery(\n * components.auth.enterprise.enterpriseGetByGroup,\n * { groupId: orgGroupId },\n * );\n * ```\n */\nexport const enterpriseGetByGroup = query({\n args: { groupId: v.id(\"Group\") },\n returns: v.union(vEnterpriseDoc, v.null()),\n handler: async (ctx, { groupId }) => {\n return await ctx.db\n .query(\"Enterprise\")\n .withIndex(\"group_id\", (idx) => idx.eq(\"groupId\", groupId))\n .first();\n },\n});\n\n/**\n * Retrieve an enterprise record by one of its linked domain names.\n *\n * Looks up an `EnterpriseDomain` row matching the given domain string, then\n * resolves the parent enterprise. Returns both the enterprise and the matched\n * domain document, or `null` if the domain is not registered or its enterprise\n * no longer exists.\n *\n * @param args.domain - The domain name to search for (e.g. `\"acme.com\"`).\n * @returns An object containing the `enterprise` and `domain` documents, or `null` if not found.\n *\n * @example\n * ```ts\n * const result = await ctx.runQuery(\n * components.auth.enterprise.enterpriseGetByDomain,\n * { domain: \"acme.com\" },\n * );\n * if (result) {\n * console.log(result.enterprise.name, result.domain.verifiedAt);\n * }\n * ```\n */\nexport const enterpriseGetByDomain = query({\n args: { domain: v.string() },\n returns: v.union(\n v.object({\n enterprise: vEnterpriseDoc,\n domain: vEnterpriseDomainDoc,\n }),\n v.null(),\n ),\n handler: async (ctx, { domain }) => {\n const domainRow = await ctx.db\n .query(\"EnterpriseDomain\")\n .withIndex(\"domain\", (idx) => idx.eq(\"domain\", domain))\n .first();\n if (!domainRow) {\n return null;\n }\n const enterprise = await ctx.db.get(\"Enterprise\", domainRow.enterpriseId);\n if (!enterprise) {\n return null;\n }\n return { enterprise, domain: domainRow };\n },\n});\n\n/**\n * List enterprise records with optional filtering and cursor-based pagination.\n *\n * Supports filtering by `groupId`, `slug`, and/or `status`. The query selects\n * the most specific index available for the primary filter, then applies\n * remaining predicates as post-filters. Results are ordered by creation time\n * (or the specified field) and paginated using an opaque cursor.\n *\n * @param args.where - Optional filter criteria: `groupId`, `slug`, and/or `status`.\n * @param args.limit - Maximum number of items per page (clamped between 1 and 100, defaults to 50).\n * @param args.cursor - An opaque cursor string returned from a previous call to fetch the next page, or `null` / omitted for the first page.\n * @param args.orderBy - The field to sort results by: `\"_creationTime\"`, `\"name\"`, `\"slug\"`, or `\"status\"`.\n * @param args.order - Sort direction: `\"asc\"` or `\"desc\"` (defaults to `\"desc\"`).\n * @returns A paginated result containing `items` (array of enterprise documents) and `nextCursor` (`string | null`).\n *\n * @example\n * ```ts\n * const page = await ctx.runQuery(\n * components.auth.enterprise.enterpriseList,\n * {\n * where: { status: \"active\" },\n * limit: 25,\n * order: \"asc\",\n * },\n * );\n * for (const ent of page.items) {\n * console.log(ent.name);\n * }\n * // Fetch next page:\n * const nextPage = await ctx.runQuery(\n * components.auth.enterprise.enterpriseList,\n * { where: { status: \"active\" }, cursor: page.nextCursor },\n * );\n * ```\n */\nexport const enterpriseList = query({\n args: {\n where: v.optional(\n v.object({\n groupId: v.optional(v.id(\"Group\")),\n slug: v.optional(v.string()),\n status: v.optional(vEnterpriseStatus),\n }),\n ),\n limit: v.optional(v.number()),\n cursor: v.optional(v.union(v.string(), v.null())),\n orderBy: v.optional(\n v.union(\n v.literal(\"_creationTime\"),\n v.literal(\"name\"),\n v.literal(\"slug\"),\n v.literal(\"status\"),\n ),\n ),\n order: v.optional(v.union(v.literal(\"asc\"), v.literal(\"desc\"))),\n },\n returns: vPaginated(vEnterpriseDoc),\n handler: async (ctx, args) => {\n const where = args.where ?? {};\n const limit = Math.min(Math.max(args.limit ?? 50, 1), 100);\n const order = args.order ?? \"desc\";\n\n let q;\n if (where.groupId !== undefined) {\n q = ctx.db\n .query(\"Enterprise\")\n .withIndex(\"group_id\", (idx) => idx.eq(\"groupId\", where.groupId!));\n } else if (where.slug !== undefined) {\n q = ctx.db\n .query(\"Enterprise\")\n .withIndex(\"slug\", (idx) => idx.eq(\"slug\", where.slug!));\n } else if (where.status !== undefined) {\n q = ctx.db\n .query(\"Enterprise\")\n .withIndex(\"status\", (idx) => idx.eq(\"status\", where.status!));\n } else {\n q = ctx.db.query(\"Enterprise\");\n }\n\n if (where.groupId !== undefined && where.slug !== undefined) {\n q = q.filter((f) => f.eq(f.field(\"slug\"), where.slug!));\n }\n if (where.status !== undefined && where.groupId === undefined) {\n // already handled by index in the dedicated branch\n } else if (where.status !== undefined) {\n q = q.filter((f) => f.eq(f.field(\"status\"), where.status!));\n }\n\n q = q.order(order);\n const all = await q.collect();\n let startIdx = 0;\n if (args.cursor) {\n const cursorIdx = all.findIndex((doc) => doc._id === args.cursor);\n if (cursorIdx !== -1) {\n startIdx = cursorIdx + 1;\n }\n }\n const page = all.slice(startIdx, startIdx + limit + 1);\n const hasMore = page.length > limit;\n const items = hasMore ? page.slice(0, limit) : page;\n const nextCursor = hasMore ? items[items.length - 1]._id : null;\n return { items, nextCursor };\n },\n});\n\n/**\n * Partially update (patch) an existing enterprise record.\n *\n * Merges the provided `data` fields into the existing enterprise document.\n * Only the fields present in `data` are changed; all other fields are preserved.\n *\n * @param args.enterpriseId - The document ID of the enterprise to update.\n * @param args.data - An object containing the fields to update (e.g. `{ name, status, policy }`).\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * await ctx.runMutation(\n * components.auth.enterprise.enterpriseUpdate,\n * {\n * enterpriseId,\n * data: { status: \"active\", name: \"Acme Corp (Renamed)\" },\n * },\n * );\n * ```\n */\nexport const enterpriseUpdate = mutation({\n args: { enterpriseId: v.id(\"Enterprise\"), data: v.any() },\n returns: v.null(),\n handler: async (ctx, { enterpriseId, data }) => {\n await ctx.db.patch(enterpriseId, data);\n return null;\n },\n});\n\n/**\n * Delete an enterprise record and all of its associated child data.\n *\n * This cascading delete removes the enterprise document along with all linked\n * domain records, domain verification records, and enterprise secrets. Callers\n * should ensure that higher-level cleanup (e.g. SCIM identities, webhook\n * endpoints) is handled separately if needed.\n *\n * @param args.enterpriseId - The document ID of the enterprise to delete.\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * await ctx.runMutation(\n * components.auth.enterprise.enterpriseDelete,\n * { enterpriseId },\n * );\n * ```\n */\nexport const enterpriseDelete = mutation({\n args: { enterpriseId: v.id(\"Enterprise\") },\n returns: v.null(),\n handler: async (ctx, { enterpriseId }) => {\n const domains = await ctx.db\n .query(\"EnterpriseDomain\")\n .withIndex(\"enterprise_id\", (idx) => idx.eq(\"enterpriseId\", enterpriseId))\n .collect();\n for (const domain of domains) {\n const verification = await ctx.db\n .query(\"EnterpriseDomainVerification\")\n .withIndex(\"domain_id\", (idx) => idx.eq(\"domainId\", domain._id))\n .first();\n if (verification) {\n await ctx.db.delete(verification._id);\n }\n await ctx.db.delete(domain._id);\n }\n const secrets = await ctx.db\n .query(\"EnterpriseSecret\")\n .withIndex(\"enterprise_id\", (idx) => idx.eq(\"enterpriseId\", enterpriseId))\n .collect();\n for (const secret of secrets) {\n await ctx.db.delete(secret._id);\n }\n await ctx.db.delete(enterpriseId);\n return null;\n },\n});\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domains.d.ts","names":[],"sources":["../../../../src/component/public/enterprise/domains.ts"],"mappings":";;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"domains.d.ts","names":[],"sources":["../../../../src/component/public/enterprise/domains.ts"],"mappings":";;;;;;;;;;;;;;;AAoCA;;;;;AA2EA;;;;;AA4BA;;;;;AAoCA;cA3Ia,mBAAA;;;;AA2Lb;;;;;AA4CA;;;;;AAmCA;;;;;;;;cA/La,oBAAA;;;;;;;;;;;;;;;;;;cA4BA,sBAAA;;;;;;;;;;;;;;;;;;;;;cAoCA,+BAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAgDA,kCAAA;;;;;;;;;;;;;;;;;;;cA4CA,kCAAA;;;;;;;;;;;;;;;;;;;;;cAmCA,sBAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domains.js","names":[],"sources":["../../../../src/component/public/enterprise/domains.ts"],"sourcesContent":["import { ConvexError, v } from \"convex/values\";\nimport { mutation, query } from \"../../functions\";\nimport { vEnterpriseDomainDoc, vEnterpriseDomainVerificationDoc } from \"../../model\";\n\n/**\n * Link a domain to an enterprise record, or update an existing link.\n *\n * If the domain is already attached to a different enterprise, an\n * `ENTERPRISE_DOMAIN_TAKEN` error is thrown. If the domain already exists for\n * this enterprise, it is updated in place (e.g. toggling `isPrimary`). When\n * `isPrimary` is `true`, any previously primary domain on the same enterprise\n * is demoted. The first domain added to an enterprise becomes primary by default.\n *\n * @param args.enterpriseId - The ID of the enterprise to attach the domain to.\n * @param args.groupId - The ID of the root group that owns the enterprise.\n * @param args.domain - The domain name to link (e.g. `\"acme.com\"`).\n * @param args.isPrimary - Whether this domain should be set as the primary domain for the enterprise. Defaults to `true` for the first domain.\n * @returns The ID of the created or updated `EnterpriseDomain` document.\n *\n * @example\n * ```ts\n * const domainId = await ctx.runMutation(\n * components.auth.enterprise.enterpriseDomainAdd,\n * {\n * enterpriseId,\n * groupId: orgGroupId,\n * domain: \"acme.com\",\n * isPrimary: true,\n * },\n * );\n * ```\n */\nexport const enterpriseDomainAdd = mutation({\n args: {\n enterpriseId: v.id(\"Enterprise\"),\n groupId: v.id(\"Group\"),\n domain: v.string(),\n isPrimary: v.optional(v.boolean()),\n },\n returns: v.id(\"EnterpriseDomain\"),\n handler: async (ctx, args) => {\n const existingByDomain = await ctx.db\n .query(\"EnterpriseDomain\")\n .withIndex(\"domain\", (idx) => idx.eq(\"domain\", args.domain))\n .first();\n if (\n existingByDomain &&\n existingByDomain.enterpriseId !== args.enterpriseId\n ) {\n throw new ConvexError({\n code: \"ENTERPRISE_DOMAIN_TAKEN\",\n message: \"That domain is already attached to another enterprise.\",\n });\n }\n\n const existingForEnterprise = await ctx.db\n .query(\"EnterpriseDomain\")\n .withIndex(\"enterprise_id\", (idx) =>\n idx.eq(\"enterpriseId\", args.enterpriseId),\n )\n .collect();\n\n for (const row of existingForEnterprise) {\n if (row.domain === args.domain) {\n await ctx.db.patch(row._id, {\n isPrimary: args.isPrimary ?? row.isPrimary,\n });\n return row._id;\n }\n }\n\n if (args.isPrimary === true) {\n for (const row of existingForEnterprise) {\n if (row.isPrimary) {\n await ctx.db.patch(row._id, { isPrimary: false });\n }\n }\n }\n\n return await ctx.db.insert(\"EnterpriseDomain\", {\n ...args,\n isPrimary: args.isPrimary ?? existingForEnterprise.length === 0,\n });\n },\n});\n\n/**\n * List all domains linked to a specific enterprise.\n *\n * Returns all `EnterpriseDomain` documents associated with the given enterprise,\n * queried via the `enterprise_id` index. The result includes both verified and\n * unverified domains.\n *\n * @param args.enterpriseId - The ID of the enterprise whose domains to list.\n * @returns An array of enterprise domain documents.\n *\n * @example\n * ```ts\n * const domains = await ctx.runQuery(\n * components.auth.enterprise.enterpriseDomainList,\n * { enterpriseId },\n * );\n * for (const d of domains) {\n * console.log(d.domain, d.isPrimary, d.verifiedAt);\n * }\n * ```\n */\nexport const enterpriseDomainList = query({\n args: { enterpriseId: v.id(\"Enterprise\") },\n returns: v.array(vEnterpriseDomainDoc),\n handler: async (ctx, { enterpriseId }) => {\n return await ctx.db\n .query(\"EnterpriseDomain\")\n .withIndex(\"enterprise_id\", (idx) => idx.eq(\"enterpriseId\", enterpriseId))\n .collect();\n },\n});\n\n/**\n * Remove a linked enterprise domain and its associated verification record.\n *\n * Deletes the `EnterpriseDomain` document and, if one exists, the related\n * `EnterpriseDomainVerification` record. This is a permanent deletion.\n *\n * @param args.domainId - The document ID of the enterprise domain to remove.\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * await ctx.runMutation(\n * components.auth.enterprise.enterpriseDomainDelete,\n * { domainId },\n * );\n * ```\n */\nexport const enterpriseDomainDelete = mutation({\n args: { domainId: v.id(\"EnterpriseDomain\") },\n returns: v.null(),\n handler: async (ctx, { domainId }) => {\n const verification = await ctx.db\n .query(\"EnterpriseDomainVerification\")\n .withIndex(\"domain_id\", (idx) => idx.eq(\"domainId\", domainId))\n .first();\n if (verification) {\n await ctx.db.delete(verification._id);\n }\n await ctx.db.delete(domainId);\n return null;\n },\n});\n\n/**\n * Retrieve the pending domain verification record for a given enterprise domain.\n *\n * Returns the `EnterpriseDomainVerification` document associated with the\n * specified domain, or `null` if no verification has been initiated.\n *\n * @param args.domainId - The document ID of the enterprise domain whose verification to retrieve.\n * @returns The domain verification document, or `null` if none exists.\n *\n * @example\n * ```ts\n * const verification = await ctx.runQuery(\n * components.auth.enterprise.enterpriseDomainVerificationGet,\n * { domainId },\n * );\n * if (verification) {\n * console.log(verification.recordName, verification.expiresAt);\n * }\n * ```\n */\nexport const enterpriseDomainVerificationGet = query({\n args: { domainId: v.id(\"EnterpriseDomain\") },\n returns: v.union(vEnterpriseDomainVerificationDoc, v.null()),\n handler: async (ctx, { domainId }) => {\n return await ctx.db\n .query(\"EnterpriseDomainVerification\")\n .withIndex(\"domain_id\", (idx) => idx.eq(\"domainId\", domainId))\n .first();\n },\n});\n\n/**\n * Create or update a domain verification challenge for an enterprise domain.\n *\n * If a verification record already exists for the domain, all fields are\n * updated in place (e.g. to rotate the token). Otherwise a new record is\n * created. The caller is responsible for generating the DNS record name,\n * token, and token hash.\n *\n * @param args.enterpriseId - The ID of the enterprise that owns the domain.\n * @param args.groupId - The ID of the root group that owns the enterprise.\n * @param args.domainId - The document ID of the enterprise domain to verify.\n * @param args.domain - The domain name string (e.g. `\"acme.com\"`).\n * @param args.recordName - The DNS TXT record name to be published (e.g. `\"_convex-verify.acme.com\"`).\n * @param args.token - The plaintext verification token value.\n * @param args.tokenHash - A hash of the verification token for secure storage.\n * @param args.requestedAt - Epoch timestamp (ms) when the verification was requested.\n * @param args.expiresAt - Epoch timestamp (ms) after which the challenge expires.\n * @returns The ID of the created or updated `EnterpriseDomainVerification` document.\n *\n * @example\n * ```ts\n * const verificationId = await ctx.runMutation(\n * components.auth.enterprise.enterpriseDomainVerificationUpsert,\n * {\n * enterpriseId,\n * groupId: orgGroupId,\n * domainId,\n * domain: \"acme.com\",\n * recordName: \"_convex-verify.acme.com\",\n * token: \"abc123\",\n * tokenHash: \"sha256:...\",\n * requestedAt: Date.now(),\n * expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000,\n * },\n * );\n * ```\n */\nexport const enterpriseDomainVerificationUpsert = mutation({\n args: {\n enterpriseId: v.id(\"Enterprise\"),\n groupId: v.id(\"Group\"),\n domainId: v.id(\"EnterpriseDomain\"),\n domain: v.string(),\n recordName: v.string(),\n token: v.string(),\n tokenHash: v.string(),\n requestedAt: v.number(),\n expiresAt: v.number(),\n },\n returns: v.id(\"EnterpriseDomainVerification\"),\n handler: async (ctx, args) => {\n const existing = await ctx.db\n .query(\"EnterpriseDomainVerification\")\n .withIndex(\"domain_id\", (idx) => idx.eq(\"domainId\", args.domainId))\n .first();\n if (existing) {\n await ctx.db.patch(existing._id, args);\n return existing._id;\n }\n return await ctx.db.insert(\"EnterpriseDomainVerification\", args);\n },\n});\n\n/**\n * Delete the pending domain verification record for an enterprise domain.\n *\n * Removes the `EnterpriseDomainVerification` document associated with the\n * given domain, effectively cancelling the verification challenge. If no\n * verification record exists, this is a no-op.\n *\n * @param args.domainId - The document ID of the enterprise domain whose verification to delete.\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * await ctx.runMutation(\n * components.auth.enterprise.enterpriseDomainVerificationDelete,\n * { domainId },\n * );\n * ```\n */\nexport const enterpriseDomainVerificationDelete = mutation({\n args: { domainId: v.id(\"EnterpriseDomain\") },\n returns: v.null(),\n handler: async (ctx, { domainId }) => {\n const existing = await ctx.db\n .query(\"EnterpriseDomainVerification\")\n .withIndex(\"domain_id\", (idx) => idx.eq(\"domainId\", domainId))\n .first();\n if (existing) {\n await ctx.db.delete(existing._id);\n }\n return null;\n },\n});\n\n/**\n * Mark an enterprise domain as verified and clean up the verification record.\n *\n * Sets the `verifiedAt` timestamp on the domain document and deletes the\n * associated `EnterpriseDomainVerification` record (if any). Throws an\n * `INVALID_PARAMETERS` error if the domain document does not exist.\n *\n * @param args.domainId - The document ID of the enterprise domain to mark as verified.\n * @param args.verifiedAt - Epoch timestamp (ms) at which the domain was verified.\n * @returns The updated enterprise domain document with the `verifiedAt` field set.\n *\n * @example\n * ```ts\n * const verifiedDomain = await ctx.runMutation(\n * components.auth.enterprise.enterpriseDomainVerify,\n * { domainId, verifiedAt: Date.now() },\n * );\n * console.log(\"Domain verified:\", verifiedDomain.domain);\n * ```\n */\nexport const enterpriseDomainVerify = mutation({\n args: {\n domainId: v.id(\"EnterpriseDomain\"),\n verifiedAt: v.number(),\n },\n returns: vEnterpriseDomainDoc,\n handler: async (ctx, { domainId, verifiedAt }) => {\n await ctx.db.patch(domainId, { verifiedAt });\n const domain = await ctx.db.get(\"EnterpriseDomain\", domainId);\n if (!domain) {\n throw new ConvexError({\n code: \"INVALID_PARAMETERS\",\n message: \"Enterprise domain not found.\",\n });\n }\n const verification = await ctx.db\n .query(\"EnterpriseDomainVerification\")\n .withIndex(\"domain_id\", (idx) => idx.eq(\"domainId\", domainId))\n .first();\n if (verification) {\n await ctx.db.delete(verification._id);\n }\n return domain;\n },\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"domains.js","names":[],"sources":["../../../../src/component/public/enterprise/domains.ts"],"sourcesContent":["import { ConvexError, v } from \"convex/values\";\n\nimport { mutation, query } from \"../../functions\";\nimport {\n vEnterpriseDomainDoc,\n vEnterpriseDomainVerificationDoc,\n} from \"../../model\";\n\n/**\n * Link a domain to an enterprise record, or update an existing link.\n *\n * If the domain is already attached to a different enterprise, an\n * `ENTERPRISE_DOMAIN_TAKEN` error is thrown. If the domain already exists for\n * this enterprise, it is updated in place (e.g. toggling `isPrimary`). When\n * `isPrimary` is `true`, any previously primary domain on the same enterprise\n * is demoted. The first domain added to an enterprise becomes primary by default.\n *\n * @param args.enterpriseId - The ID of the enterprise to attach the domain to.\n * @param args.groupId - The ID of the root group that owns the enterprise.\n * @param args.domain - The domain name to link (e.g. `\"acme.com\"`).\n * @param args.isPrimary - Whether this domain should be set as the primary domain for the enterprise. Defaults to `true` for the first domain.\n * @returns The ID of the created or updated `EnterpriseDomain` document.\n *\n * @example\n * ```ts\n * const domainId = await ctx.runMutation(\n * components.auth.enterprise.enterpriseDomainAdd,\n * {\n * enterpriseId,\n * groupId: orgGroupId,\n * domain: \"acme.com\",\n * isPrimary: true,\n * },\n * );\n * ```\n */\nexport const enterpriseDomainAdd = mutation({\n args: {\n enterpriseId: v.id(\"Enterprise\"),\n groupId: v.id(\"Group\"),\n domain: v.string(),\n isPrimary: v.optional(v.boolean()),\n },\n returns: v.id(\"EnterpriseDomain\"),\n handler: async (ctx, args) => {\n const existingByDomain = await ctx.db\n .query(\"EnterpriseDomain\")\n .withIndex(\"domain\", (idx) => idx.eq(\"domain\", args.domain))\n .first();\n if (\n existingByDomain &&\n existingByDomain.enterpriseId !== args.enterpriseId\n ) {\n throw new ConvexError({\n code: \"ENTERPRISE_DOMAIN_TAKEN\",\n message: \"That domain is already attached to another enterprise.\",\n });\n }\n\n const existingForEnterprise = await ctx.db\n .query(\"EnterpriseDomain\")\n .withIndex(\"enterprise_id\", (idx) =>\n idx.eq(\"enterpriseId\", args.enterpriseId),\n )\n .collect();\n\n for (const row of existingForEnterprise) {\n if (row.domain === args.domain) {\n await ctx.db.patch(row._id, {\n isPrimary: args.isPrimary ?? row.isPrimary,\n });\n return row._id;\n }\n }\n\n if (args.isPrimary === true) {\n for (const row of existingForEnterprise) {\n if (row.isPrimary) {\n await ctx.db.patch(row._id, { isPrimary: false });\n }\n }\n }\n\n return await ctx.db.insert(\"EnterpriseDomain\", {\n ...args,\n isPrimary: args.isPrimary ?? existingForEnterprise.length === 0,\n });\n },\n});\n\n/**\n * List all domains linked to a specific enterprise.\n *\n * Returns all `EnterpriseDomain` documents associated with the given enterprise,\n * queried via the `enterprise_id` index. The result includes both verified and\n * unverified domains.\n *\n * @param args.enterpriseId - The ID of the enterprise whose domains to list.\n * @returns An array of enterprise domain documents.\n *\n * @example\n * ```ts\n * const domains = await ctx.runQuery(\n * components.auth.enterprise.enterpriseDomainList,\n * { enterpriseId },\n * );\n * for (const d of domains) {\n * console.log(d.domain, d.isPrimary, d.verifiedAt);\n * }\n * ```\n */\nexport const enterpriseDomainList = query({\n args: { enterpriseId: v.id(\"Enterprise\") },\n returns: v.array(vEnterpriseDomainDoc),\n handler: async (ctx, { enterpriseId }) => {\n return await ctx.db\n .query(\"EnterpriseDomain\")\n .withIndex(\"enterprise_id\", (idx) => idx.eq(\"enterpriseId\", enterpriseId))\n .collect();\n },\n});\n\n/**\n * Remove a linked enterprise domain and its associated verification record.\n *\n * Deletes the `EnterpriseDomain` document and, if one exists, the related\n * `EnterpriseDomainVerification` record. This is a permanent deletion.\n *\n * @param args.domainId - The document ID of the enterprise domain to remove.\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * await ctx.runMutation(\n * components.auth.enterprise.enterpriseDomainDelete,\n * { domainId },\n * );\n * ```\n */\nexport const enterpriseDomainDelete = mutation({\n args: { domainId: v.id(\"EnterpriseDomain\") },\n returns: v.null(),\n handler: async (ctx, { domainId }) => {\n const verification = await ctx.db\n .query(\"EnterpriseDomainVerification\")\n .withIndex(\"domain_id\", (idx) => idx.eq(\"domainId\", domainId))\n .first();\n if (verification) {\n await ctx.db.delete(verification._id);\n }\n await ctx.db.delete(domainId);\n return null;\n },\n});\n\n/**\n * Retrieve the pending domain verification record for a given enterprise domain.\n *\n * Returns the `EnterpriseDomainVerification` document associated with the\n * specified domain, or `null` if no verification has been initiated.\n *\n * @param args.domainId - The document ID of the enterprise domain whose verification to retrieve.\n * @returns The domain verification document, or `null` if none exists.\n *\n * @example\n * ```ts\n * const verification = await ctx.runQuery(\n * components.auth.enterprise.enterpriseDomainVerificationGet,\n * { domainId },\n * );\n * if (verification) {\n * console.log(verification.recordName, verification.expiresAt);\n * }\n * ```\n */\nexport const enterpriseDomainVerificationGet = query({\n args: { domainId: v.id(\"EnterpriseDomain\") },\n returns: v.union(vEnterpriseDomainVerificationDoc, v.null()),\n handler: async (ctx, { domainId }) => {\n return await ctx.db\n .query(\"EnterpriseDomainVerification\")\n .withIndex(\"domain_id\", (idx) => idx.eq(\"domainId\", domainId))\n .first();\n },\n});\n\n/**\n * Create or update a domain verification challenge for an enterprise domain.\n *\n * If a verification record already exists for the domain, all fields are\n * updated in place (e.g. to rotate the token). Otherwise a new record is\n * created. The caller is responsible for generating the DNS record name,\n * token, and token hash.\n *\n * @param args.enterpriseId - The ID of the enterprise that owns the domain.\n * @param args.groupId - The ID of the root group that owns the enterprise.\n * @param args.domainId - The document ID of the enterprise domain to verify.\n * @param args.domain - The domain name string (e.g. `\"acme.com\"`).\n * @param args.recordName - The DNS TXT record name to be published (e.g. `\"_convex-verify.acme.com\"`).\n * @param args.token - The plaintext verification token value.\n * @param args.tokenHash - A hash of the verification token for secure storage.\n * @param args.requestedAt - Epoch timestamp (ms) when the verification was requested.\n * @param args.expiresAt - Epoch timestamp (ms) after which the challenge expires.\n * @returns The ID of the created or updated `EnterpriseDomainVerification` document.\n *\n * @example\n * ```ts\n * const verificationId = await ctx.runMutation(\n * components.auth.enterprise.enterpriseDomainVerificationUpsert,\n * {\n * enterpriseId,\n * groupId: orgGroupId,\n * domainId,\n * domain: \"acme.com\",\n * recordName: \"_convex-verify.acme.com\",\n * token: \"abc123\",\n * tokenHash: \"sha256:...\",\n * requestedAt: Date.now(),\n * expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000,\n * },\n * );\n * ```\n */\nexport const enterpriseDomainVerificationUpsert = mutation({\n args: {\n enterpriseId: v.id(\"Enterprise\"),\n groupId: v.id(\"Group\"),\n domainId: v.id(\"EnterpriseDomain\"),\n domain: v.string(),\n recordName: v.string(),\n token: v.string(),\n tokenHash: v.string(),\n requestedAt: v.number(),\n expiresAt: v.number(),\n },\n returns: v.id(\"EnterpriseDomainVerification\"),\n handler: async (ctx, args) => {\n const existing = await ctx.db\n .query(\"EnterpriseDomainVerification\")\n .withIndex(\"domain_id\", (idx) => idx.eq(\"domainId\", args.domainId))\n .first();\n if (existing) {\n await ctx.db.patch(existing._id, args);\n return existing._id;\n }\n return await ctx.db.insert(\"EnterpriseDomainVerification\", args);\n },\n});\n\n/**\n * Delete the pending domain verification record for an enterprise domain.\n *\n * Removes the `EnterpriseDomainVerification` document associated with the\n * given domain, effectively cancelling the verification challenge. If no\n * verification record exists, this is a no-op.\n *\n * @param args.domainId - The document ID of the enterprise domain whose verification to delete.\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * await ctx.runMutation(\n * components.auth.enterprise.enterpriseDomainVerificationDelete,\n * { domainId },\n * );\n * ```\n */\nexport const enterpriseDomainVerificationDelete = mutation({\n args: { domainId: v.id(\"EnterpriseDomain\") },\n returns: v.null(),\n handler: async (ctx, { domainId }) => {\n const existing = await ctx.db\n .query(\"EnterpriseDomainVerification\")\n .withIndex(\"domain_id\", (idx) => idx.eq(\"domainId\", domainId))\n .first();\n if (existing) {\n await ctx.db.delete(existing._id);\n }\n return null;\n },\n});\n\n/**\n * Mark an enterprise domain as verified and clean up the verification record.\n *\n * Sets the `verifiedAt` timestamp on the domain document and deletes the\n * associated `EnterpriseDomainVerification` record (if any). Throws an\n * `INVALID_PARAMETERS` error if the domain document does not exist.\n *\n * @param args.domainId - The document ID of the enterprise domain to mark as verified.\n * @param args.verifiedAt - Epoch timestamp (ms) at which the domain was verified.\n * @returns The updated enterprise domain document with the `verifiedAt` field set.\n *\n * @example\n * ```ts\n * const verifiedDomain = await ctx.runMutation(\n * components.auth.enterprise.enterpriseDomainVerify,\n * { domainId, verifiedAt: Date.now() },\n * );\n * console.log(\"Domain verified:\", verifiedDomain.domain);\n * ```\n */\nexport const enterpriseDomainVerify = mutation({\n args: {\n domainId: v.id(\"EnterpriseDomain\"),\n verifiedAt: v.number(),\n },\n returns: vEnterpriseDomainDoc,\n handler: async (ctx, { domainId, verifiedAt }) => {\n await ctx.db.patch(domainId, { verifiedAt });\n const domain = await ctx.db.get(\"EnterpriseDomain\", domainId);\n if (!domain) {\n throw new ConvexError({\n code: \"INVALID_PARAMETERS\",\n message: \"Enterprise domain not found.\",\n });\n }\n const verification = await ctx.db\n .query(\"EnterpriseDomainVerification\")\n .withIndex(\"domain_id\", (idx) => idx.eq(\"domainId\", domainId))\n .first();\n if (verification) {\n await ctx.db.delete(verification._id);\n }\n return domain;\n },\n});\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scim.d.ts","names":[],"sources":["../../../../src/component/public/enterprise/scim.ts"],"mappings":";;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"scim.d.ts","names":[],"sources":["../../../../src/component/public/enterprise/scim.ts"],"mappings":";;;;;;;;;;;;;;;;;;AAyCA;;;;;AA8CA;;;;;AA+BA;;;;;AAmCA;cAhHa,0BAAA;;;;AAyJb;;;;;AA8BA;;;;;AAqCA;;;;;AAiCA;;cA/Ma,mCAAA;;;AA8Pb;;;;;AAgDA;;;;;;;;;;;;;cA/Qa,kCAAA;;;;;;;;;;;;;;;;;;;;;;;;;cAmCA,yBAAA;;;;;;;;;;;;;;;;;;;;;;cAyCA,+BAAA;;;;;;;;;;;;;;;;;;;;cA8BA,4CAAA;;;;;;;;;;;;;;;;;;;;;;cAqCA,sCAAA;;;;;;;;;;;;;;;;;;;;;cAiCA,sCAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA+CA,4BAAA;;;;;;;;;;;;;;;;;;cAgDA,4BAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scim.js","names":[],"sources":["../../../../src/component/public/enterprise/scim.ts"],"sourcesContent":["import { v } from \"convex/values\";\nimport { mutation, query } from \"../../functions\";\nimport {\n vEnterpriseScimConfigDoc,\n vEnterpriseScimIdentityDoc,\n vScimResourceType,\n vScimStatus,\n} from \"../../model\";\n\n/**\n * Create or update the SCIM provisioning configuration for an enterprise.\n *\n * If a SCIM config already exists for the given enterprise, all fields are\n * patched in place (useful for rotating the bearer token). Otherwise a new\n * config document is created. Only one SCIM config is allowed per enterprise.\n *\n * @param args.enterpriseId - The ID of the enterprise to configure SCIM for.\n * @param args.groupId - The ID of the root group that owns the enterprise.\n * @param args.status - The SCIM config lifecycle status: `\"draft\"`, `\"active\"`, or `\"disabled\"`.\n * @param args.basePath - The base URL path for the SCIM endpoint (e.g. `\"/scim/v2\"`).\n * @param args.tokenHash - A hash of the bearer token used to authenticate SCIM requests.\n * @param args.lastRotatedAt - An optional epoch timestamp (ms) recording when the token was last rotated.\n * @param args.extend - An optional arbitrary extension object for custom SCIM settings.\n * @returns The ID of the created or updated `EnterpriseScimConfig` document.\n *\n * @example\n * ```ts\n * const configId = await ctx.runMutation(\n * components.auth.enterprise.enterpriseScimConfigUpsert,\n * {\n * enterpriseId,\n * groupId: orgGroupId,\n * status: \"active\",\n * basePath: \"/scim/v2\",\n * tokenHash: \"sha256:abc123...\",\n * lastRotatedAt: Date.now(),\n * },\n * );\n * ```\n */\nexport const enterpriseScimConfigUpsert = mutation({\n args: {\n enterpriseId: v.id(\"Enterprise\"),\n groupId: v.id(\"Group\"),\n status: vScimStatus,\n basePath: v.string(),\n tokenHash: v.string(),\n lastRotatedAt: v.optional(v.number()),\n extend: v.optional(v.any()),\n },\n returns: v.id(\"EnterpriseScimConfig\"),\n handler: async (ctx, args) => {\n const existing = await ctx.db\n .query(\"EnterpriseScimConfig\")\n .withIndex(\"enterprise_id\", (idx) =>\n idx.eq(\"enterpriseId\", args.enterpriseId),\n )\n .first();\n if (existing) {\n await ctx.db.patch(existing._id, args);\n return existing._id;\n }\n return await ctx.db.insert(\"EnterpriseScimConfig\", args);\n },\n});\n\n/**\n * Retrieve the SCIM configuration for a specific enterprise.\n *\n * Looks up the SCIM config document by enterprise ID using the\n * `enterprise_id` index. Returns `null` if SCIM has not been configured.\n *\n * @param args.enterpriseId - The ID of the enterprise whose SCIM config to retrieve.\n * @returns The SCIM configuration document, or `null` if not configured.\n *\n * @example\n * ```ts\n * const config = await ctx.runQuery(\n * components.auth.enterprise.enterpriseScimConfigGetByEnterprise,\n * { enterpriseId },\n * );\n * if (config) {\n * console.log(config.status, config.basePath);\n * }\n * ```\n */\nexport const enterpriseScimConfigGetByEnterprise = query({\n args: { enterpriseId: v.id(\"Enterprise\") },\n returns: v.union(vEnterpriseScimConfigDoc, v.null()),\n handler: async (ctx, { enterpriseId }) => {\n return await ctx.db\n .query(\"EnterpriseScimConfig\")\n .withIndex(\"enterprise_id\", (idx) => idx.eq(\"enterpriseId\", enterpriseId))\n .first();\n },\n});\n\n/**\n * Look up a SCIM configuration by its bearer token hash.\n *\n * Used during SCIM request authentication to resolve which enterprise a\n * given bearer token belongs to. Returns `null` if no config matches.\n *\n * @param args.tokenHash - The hash of the bearer token from the incoming SCIM request.\n * @returns The matching SCIM configuration document, or `null` if not found.\n *\n * @example\n * ```ts\n * const config = await ctx.runQuery(\n * components.auth.enterprise.enterpriseScimConfigGetByTokenHash,\n * { tokenHash: \"sha256:abc123...\" },\n * );\n * if (config) {\n * console.log(\"Authenticated enterprise:\", config.enterpriseId);\n * }\n * ```\n */\nexport const enterpriseScimConfigGetByTokenHash = query({\n args: { tokenHash: v.string() },\n returns: v.union(vEnterpriseScimConfigDoc, v.null()),\n handler: async (ctx, { tokenHash }) => {\n return await ctx.db\n .query(\"EnterpriseScimConfig\")\n .withIndex(\"token_hash\", (idx) => idx.eq(\"tokenHash\", tokenHash))\n .first();\n },\n});\n\n/**\n * Retrieve a SCIM identity by enterprise, resource type, and external ID.\n *\n * Looks up a SCIM-provisioned identity using the composite index on\n * `(enterpriseId, resourceType, externalId)`. This is the primary lookup\n * used when processing incoming SCIM user or group operations.\n *\n * @param args.enterpriseId - The ID of the enterprise that owns the SCIM identity.\n * @param args.resourceType - The SCIM resource type: `\"user\"` or `\"group\"`.\n * @param args.externalId - The external identifier assigned by the identity provider.\n * @returns The SCIM identity document, or `null` if not found.\n *\n * @example\n * ```ts\n * const identity = await ctx.runQuery(\n * components.auth.enterprise.enterpriseScimIdentityGet,\n * {\n * enterpriseId,\n * resourceType: \"user\",\n * externalId: \"okta-user-abc123\",\n * },\n * );\n * ```\n */\nexport const enterpriseScimIdentityGet = query({\n args: {\n enterpriseId: v.id(\"Enterprise\"),\n resourceType: vScimResourceType,\n externalId: v.string(),\n },\n returns: v.union(vEnterpriseScimIdentityDoc, v.null()),\n handler: async (ctx, args) => {\n return await ctx.db\n .query(\"EnterpriseScimIdentity\")\n .withIndex(\"enterprise_id_resource_type_external_id\", (idx) =>\n idx\n .eq(\"enterpriseId\", args.enterpriseId)\n .eq(\"resourceType\", args.resourceType)\n .eq(\"externalId\", args.externalId),\n )\n .first();\n },\n});\n\n/**\n * Retrieve the SCIM identity linked to a specific user.\n *\n * Looks up the first SCIM identity document associated with the given user ID\n * via the `user_id` index. Useful for checking whether a user was provisioned\n * through SCIM.\n *\n * @param args.userId - The document ID of the user whose SCIM identity to retrieve.\n * @returns The SCIM identity document, or `null` if the user has no SCIM identity.\n *\n * @example\n * ```ts\n * const scimIdentity = await ctx.runQuery(\n * components.auth.enterprise.enterpriseScimIdentityGetByUser,\n * { userId },\n * );\n * if (scimIdentity) {\n * console.log(\"User provisioned via SCIM:\", scimIdentity.externalId);\n * }\n * ```\n */\nexport const enterpriseScimIdentityGetByUser = query({\n args: { userId: v.id(\"User\") },\n returns: v.union(vEnterpriseScimIdentityDoc, v.null()),\n handler: async (ctx, { userId }) => {\n return await ctx.db\n .query(\"EnterpriseScimIdentity\")\n .withIndex(\"user_id\", (idx) => idx.eq(\"userId\", userId))\n .first();\n },\n});\n\n/**\n * Retrieve the SCIM identity for a specific user within a specific enterprise.\n *\n * Uses the composite `(enterpriseId, userId)` index to find the SCIM identity\n * that links a user to a particular enterprise. This is useful when a user may\n * belong to multiple enterprises.\n *\n * @param args.enterpriseId - The ID of the enterprise to scope the lookup to.\n * @param args.userId - The document ID of the user.\n * @returns The SCIM identity document, or `null` if not found.\n *\n * @example\n * ```ts\n * const identity = await ctx.runQuery(\n * components.auth.enterprise.enterpriseScimIdentityGetByEnterpriseAndUser,\n * { enterpriseId, userId },\n * );\n * ```\n */\nexport const enterpriseScimIdentityGetByEnterpriseAndUser = query({\n args: {\n enterpriseId: v.id(\"Enterprise\"),\n userId: v.id(\"User\"),\n },\n returns: v.union(vEnterpriseScimIdentityDoc, v.null()),\n handler: async (ctx, { enterpriseId, userId }) => {\n return await ctx.db\n .query(\"EnterpriseScimIdentity\")\n .withIndex(\"enterprise_id_user_id\", (idx) =>\n idx.eq(\"enterpriseId\", enterpriseId).eq(\"userId\", userId),\n )\n .first();\n },\n});\n\n/**\n * Retrieve the SCIM identity that is mapped to a specific group.\n *\n * Looks up a SCIM identity by its `mappedGroupId` field. This is used when\n * a SCIM group resource has been mapped to an internal group, and you need\n * to find the corresponding SCIM identity record.\n *\n * @param args.mappedGroupId - The document ID of the internal group that a SCIM group is mapped to.\n * @returns The SCIM identity document, or `null` if no mapping exists.\n *\n * @example\n * ```ts\n * const scimGroup = await ctx.runQuery(\n * components.auth.enterprise.enterpriseScimIdentityGetByMappedGroup,\n * { mappedGroupId: teamGroupId },\n * );\n * if (scimGroup) {\n * console.log(\"SCIM external group ID:\", scimGroup.externalId);\n * }\n * ```\n */\nexport const enterpriseScimIdentityGetByMappedGroup = query({\n args: { mappedGroupId: v.id(\"Group\") },\n returns: v.union(vEnterpriseScimIdentityDoc, v.null()),\n handler: async (ctx, { mappedGroupId }) => {\n return await ctx.db\n .query(\"EnterpriseScimIdentity\")\n .withIndex(\"mapped_group_id\", (idx) =>\n idx.eq(\"mappedGroupId\", mappedGroupId),\n )\n .first();\n },\n});\n\n/**\n * List all SCIM identities belonging to a specific enterprise.\n *\n * Returns all `EnterpriseScimIdentity` documents for the given enterprise,\n * including both user and group resource types. Useful for displaying all\n * SCIM-provisioned resources or for bulk operations.\n *\n * @param args.enterpriseId - The ID of the enterprise whose SCIM identities to list.\n * @returns An array of SCIM identity documents.\n *\n * @example\n * ```ts\n * const identities = await ctx.runQuery(\n * components.auth.enterprise.enterpriseScimIdentityListByEnterprise,\n * { enterpriseId },\n * );\n * const users = identities.filter((i) => i.resourceType === \"user\");\n * const groups = identities.filter((i) => i.resourceType === \"group\");\n * ```\n */\nexport const enterpriseScimIdentityListByEnterprise = query({\n args: { enterpriseId: v.id(\"Enterprise\") },\n returns: v.array(vEnterpriseScimIdentityDoc),\n handler: async (ctx, { enterpriseId }) => {\n return await ctx.db\n .query(\"EnterpriseScimIdentity\")\n .withIndex(\"enterprise_id\", (idx) => idx.eq(\"enterpriseId\", enterpriseId))\n .collect();\n },\n});\n\n/**\n * Create or update a SCIM-provisioned identity record.\n *\n * If a SCIM identity with the same `(enterpriseId, resourceType, externalId)`\n * already exists, its fields are patched in place. Otherwise a new record is\n * created. This is the core upsert used by the SCIM provisioning handler to\n * sync users and groups from external identity providers.\n *\n * @param args.enterpriseId - The ID of the enterprise the identity belongs to.\n * @param args.groupId - The ID of the root group that owns the enterprise.\n * @param args.resourceType - The SCIM resource type: `\"user\"` or `\"group\"`.\n * @param args.externalId - The external identifier assigned by the identity provider.\n * @param args.userId - An optional link to the internal user document (for user resources).\n * @param args.mappedGroupId - An optional link to an internal group document (for group resources).\n * @param args.lastProvisionedAt - An optional epoch timestamp (ms) of the last sync.\n * @param args.active - An optional flag indicating whether the identity is active.\n * @param args.raw - An optional raw SCIM payload stored for debugging or re-processing.\n * @returns The ID of the created or updated `EnterpriseScimIdentity` document.\n *\n * @example\n * ```ts\n * const identityId = await ctx.runMutation(\n * components.auth.enterprise.enterpriseScimIdentityUpsert,\n * {\n * enterpriseId,\n * groupId: orgGroupId,\n * resourceType: \"user\",\n * externalId: \"okta-user-abc123\",\n * userId,\n * active: true,\n * lastProvisionedAt: Date.now(),\n * raw: { schemas: [\"urn:ietf:params:scim:schemas:core:2.0:User\"], userName: \"jane@acme.com\" },\n * },\n * );\n * ```\n */\nexport const enterpriseScimIdentityUpsert = mutation({\n args: {\n enterpriseId: v.id(\"Enterprise\"),\n groupId: v.id(\"Group\"),\n resourceType: vScimResourceType,\n externalId: v.string(),\n userId: v.optional(v.id(\"User\")),\n mappedGroupId: v.optional(v.id(\"Group\")),\n lastProvisionedAt: v.optional(v.number()),\n active: v.optional(v.boolean()),\n raw: v.optional(v.any()),\n },\n returns: v.id(\"EnterpriseScimIdentity\"),\n handler: async (ctx, args) => {\n const existing = await ctx.db\n .query(\"EnterpriseScimIdentity\")\n .withIndex(\"enterprise_id_resource_type_external_id\", (idx) =>\n idx\n .eq(\"enterpriseId\", args.enterpriseId)\n .eq(\"resourceType\", args.resourceType)\n .eq(\"externalId\", args.externalId),\n )\n .first();\n if (existing) {\n await ctx.db.patch(existing._id, args);\n return existing._id;\n }\n return await ctx.db.insert(\"EnterpriseScimIdentity\", args);\n },\n});\n\n/**\n * Permanently delete a SCIM identity record.\n *\n * Removes the `EnterpriseScimIdentity` document. This is typically called\n * when a SCIM DELETE request is received for a user or group resource.\n *\n * @param args.identityId - The document ID of the SCIM identity to delete.\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * await ctx.runMutation(\n * components.auth.enterprise.enterpriseScimIdentityDelete,\n * { identityId: scimIdentity._id },\n * );\n * ```\n */\nexport const enterpriseScimIdentityDelete = mutation({\n args: { identityId: v.id(\"EnterpriseScimIdentity\") },\n returns: v.null(),\n handler: async (ctx, { identityId }) => {\n await ctx.db.delete(identityId);\n return null;\n },\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"scim.js","names":[],"sources":["../../../../src/component/public/enterprise/scim.ts"],"sourcesContent":["import { v } from \"convex/values\";\n\nimport { mutation, query } from \"../../functions\";\nimport {\n vEnterpriseScimConfigDoc,\n vEnterpriseScimIdentityDoc,\n vScimResourceType,\n vScimStatus,\n} from \"../../model\";\n\n/**\n * Create or update the SCIM provisioning configuration for an enterprise.\n *\n * If a SCIM config already exists for the given enterprise, all fields are\n * patched in place (useful for rotating the bearer token). Otherwise a new\n * config document is created. Only one SCIM config is allowed per enterprise.\n *\n * @param args.enterpriseId - The ID of the enterprise to configure SCIM for.\n * @param args.groupId - The ID of the root group that owns the enterprise.\n * @param args.status - The SCIM config lifecycle status: `\"draft\"`, `\"active\"`, or `\"disabled\"`.\n * @param args.basePath - The base URL path for the SCIM endpoint (e.g. `\"/scim/v2\"`).\n * @param args.tokenHash - A hash of the bearer token used to authenticate SCIM requests.\n * @param args.lastRotatedAt - An optional epoch timestamp (ms) recording when the token was last rotated.\n * @param args.extend - An optional arbitrary extension object for custom SCIM settings.\n * @returns The ID of the created or updated `EnterpriseScimConfig` document.\n *\n * @example\n * ```ts\n * const configId = await ctx.runMutation(\n * components.auth.enterprise.enterpriseScimConfigUpsert,\n * {\n * enterpriseId,\n * groupId: orgGroupId,\n * status: \"active\",\n * basePath: \"/scim/v2\",\n * tokenHash: \"sha256:abc123...\",\n * lastRotatedAt: Date.now(),\n * },\n * );\n * ```\n */\nexport const enterpriseScimConfigUpsert = mutation({\n args: {\n enterpriseId: v.id(\"Enterprise\"),\n groupId: v.id(\"Group\"),\n status: vScimStatus,\n basePath: v.string(),\n tokenHash: v.string(),\n lastRotatedAt: v.optional(v.number()),\n extend: v.optional(v.any()),\n },\n returns: v.id(\"EnterpriseScimConfig\"),\n handler: async (ctx, args) => {\n const existing = await ctx.db\n .query(\"EnterpriseScimConfig\")\n .withIndex(\"enterprise_id\", (idx) =>\n idx.eq(\"enterpriseId\", args.enterpriseId),\n )\n .first();\n if (existing) {\n await ctx.db.patch(existing._id, args);\n return existing._id;\n }\n return await ctx.db.insert(\"EnterpriseScimConfig\", args);\n },\n});\n\n/**\n * Retrieve the SCIM configuration for a specific enterprise.\n *\n * Looks up the SCIM config document by enterprise ID using the\n * `enterprise_id` index. Returns `null` if SCIM has not been configured.\n *\n * @param args.enterpriseId - The ID of the enterprise whose SCIM config to retrieve.\n * @returns The SCIM configuration document, or `null` if not configured.\n *\n * @example\n * ```ts\n * const config = await ctx.runQuery(\n * components.auth.enterprise.enterpriseScimConfigGetByEnterprise,\n * { enterpriseId },\n * );\n * if (config) {\n * console.log(config.status, config.basePath);\n * }\n * ```\n */\nexport const enterpriseScimConfigGetByEnterprise = query({\n args: { enterpriseId: v.id(\"Enterprise\") },\n returns: v.union(vEnterpriseScimConfigDoc, v.null()),\n handler: async (ctx, { enterpriseId }) => {\n return await ctx.db\n .query(\"EnterpriseScimConfig\")\n .withIndex(\"enterprise_id\", (idx) => idx.eq(\"enterpriseId\", enterpriseId))\n .first();\n },\n});\n\n/**\n * Look up a SCIM configuration by its bearer token hash.\n *\n * Used during SCIM request authentication to resolve which enterprise a\n * given bearer token belongs to. Returns `null` if no config matches.\n *\n * @param args.tokenHash - The hash of the bearer token from the incoming SCIM request.\n * @returns The matching SCIM configuration document, or `null` if not found.\n *\n * @example\n * ```ts\n * const config = await ctx.runQuery(\n * components.auth.enterprise.enterpriseScimConfigGetByTokenHash,\n * { tokenHash: \"sha256:abc123...\" },\n * );\n * if (config) {\n * console.log(\"Authenticated enterprise:\", config.enterpriseId);\n * }\n * ```\n */\nexport const enterpriseScimConfigGetByTokenHash = query({\n args: { tokenHash: v.string() },\n returns: v.union(vEnterpriseScimConfigDoc, v.null()),\n handler: async (ctx, { tokenHash }) => {\n return await ctx.db\n .query(\"EnterpriseScimConfig\")\n .withIndex(\"token_hash\", (idx) => idx.eq(\"tokenHash\", tokenHash))\n .first();\n },\n});\n\n/**\n * Retrieve a SCIM identity by enterprise, resource type, and external ID.\n *\n * Looks up a SCIM-provisioned identity using the composite index on\n * `(enterpriseId, resourceType, externalId)`. This is the primary lookup\n * used when processing incoming SCIM user or group operations.\n *\n * @param args.enterpriseId - The ID of the enterprise that owns the SCIM identity.\n * @param args.resourceType - The SCIM resource type: `\"user\"` or `\"group\"`.\n * @param args.externalId - The external identifier assigned by the identity provider.\n * @returns The SCIM identity document, or `null` if not found.\n *\n * @example\n * ```ts\n * const identity = await ctx.runQuery(\n * components.auth.enterprise.enterpriseScimIdentityGet,\n * {\n * enterpriseId,\n * resourceType: \"user\",\n * externalId: \"okta-user-abc123\",\n * },\n * );\n * ```\n */\nexport const enterpriseScimIdentityGet = query({\n args: {\n enterpriseId: v.id(\"Enterprise\"),\n resourceType: vScimResourceType,\n externalId: v.string(),\n },\n returns: v.union(vEnterpriseScimIdentityDoc, v.null()),\n handler: async (ctx, args) => {\n return await ctx.db\n .query(\"EnterpriseScimIdentity\")\n .withIndex(\"enterprise_id_resource_type_external_id\", (idx) =>\n idx\n .eq(\"enterpriseId\", args.enterpriseId)\n .eq(\"resourceType\", args.resourceType)\n .eq(\"externalId\", args.externalId),\n )\n .first();\n },\n});\n\n/**\n * Retrieve the SCIM identity linked to a specific user.\n *\n * Looks up the first SCIM identity document associated with the given user ID\n * via the `user_id` index. Useful for checking whether a user was provisioned\n * through SCIM.\n *\n * @param args.userId - The document ID of the user whose SCIM identity to retrieve.\n * @returns The SCIM identity document, or `null` if the user has no SCIM identity.\n *\n * @example\n * ```ts\n * const scimIdentity = await ctx.runQuery(\n * components.auth.enterprise.enterpriseScimIdentityGetByUser,\n * { userId },\n * );\n * if (scimIdentity) {\n * console.log(\"User provisioned via SCIM:\", scimIdentity.externalId);\n * }\n * ```\n */\nexport const enterpriseScimIdentityGetByUser = query({\n args: { userId: v.id(\"User\") },\n returns: v.union(vEnterpriseScimIdentityDoc, v.null()),\n handler: async (ctx, { userId }) => {\n return await ctx.db\n .query(\"EnterpriseScimIdentity\")\n .withIndex(\"user_id\", (idx) => idx.eq(\"userId\", userId))\n .first();\n },\n});\n\n/**\n * Retrieve the SCIM identity for a specific user within a specific enterprise.\n *\n * Uses the composite `(enterpriseId, userId)` index to find the SCIM identity\n * that links a user to a particular enterprise. This is useful when a user may\n * belong to multiple enterprises.\n *\n * @param args.enterpriseId - The ID of the enterprise to scope the lookup to.\n * @param args.userId - The document ID of the user.\n * @returns The SCIM identity document, or `null` if not found.\n *\n * @example\n * ```ts\n * const identity = await ctx.runQuery(\n * components.auth.enterprise.enterpriseScimIdentityGetByEnterpriseAndUser,\n * { enterpriseId, userId },\n * );\n * ```\n */\nexport const enterpriseScimIdentityGetByEnterpriseAndUser = query({\n args: {\n enterpriseId: v.id(\"Enterprise\"),\n userId: v.id(\"User\"),\n },\n returns: v.union(vEnterpriseScimIdentityDoc, v.null()),\n handler: async (ctx, { enterpriseId, userId }) => {\n return await ctx.db\n .query(\"EnterpriseScimIdentity\")\n .withIndex(\"enterprise_id_user_id\", (idx) =>\n idx.eq(\"enterpriseId\", enterpriseId).eq(\"userId\", userId),\n )\n .first();\n },\n});\n\n/**\n * Retrieve the SCIM identity that is mapped to a specific group.\n *\n * Looks up a SCIM identity by its `mappedGroupId` field. This is used when\n * a SCIM group resource has been mapped to an internal group, and you need\n * to find the corresponding SCIM identity record.\n *\n * @param args.mappedGroupId - The document ID of the internal group that a SCIM group is mapped to.\n * @returns The SCIM identity document, or `null` if no mapping exists.\n *\n * @example\n * ```ts\n * const scimGroup = await ctx.runQuery(\n * components.auth.enterprise.enterpriseScimIdentityGetByMappedGroup,\n * { mappedGroupId: teamGroupId },\n * );\n * if (scimGroup) {\n * console.log(\"SCIM external group ID:\", scimGroup.externalId);\n * }\n * ```\n */\nexport const enterpriseScimIdentityGetByMappedGroup = query({\n args: { mappedGroupId: v.id(\"Group\") },\n returns: v.union(vEnterpriseScimIdentityDoc, v.null()),\n handler: async (ctx, { mappedGroupId }) => {\n return await ctx.db\n .query(\"EnterpriseScimIdentity\")\n .withIndex(\"mapped_group_id\", (idx) =>\n idx.eq(\"mappedGroupId\", mappedGroupId),\n )\n .first();\n },\n});\n\n/**\n * List all SCIM identities belonging to a specific enterprise.\n *\n * Returns all `EnterpriseScimIdentity` documents for the given enterprise,\n * including both user and group resource types. Useful for displaying all\n * SCIM-provisioned resources or for bulk operations.\n *\n * @param args.enterpriseId - The ID of the enterprise whose SCIM identities to list.\n * @returns An array of SCIM identity documents.\n *\n * @example\n * ```ts\n * const identities = await ctx.runQuery(\n * components.auth.enterprise.enterpriseScimIdentityListByEnterprise,\n * { enterpriseId },\n * );\n * const users = identities.filter((i) => i.resourceType === \"user\");\n * const groups = identities.filter((i) => i.resourceType === \"group\");\n * ```\n */\nexport const enterpriseScimIdentityListByEnterprise = query({\n args: { enterpriseId: v.id(\"Enterprise\") },\n returns: v.array(vEnterpriseScimIdentityDoc),\n handler: async (ctx, { enterpriseId }) => {\n return await ctx.db\n .query(\"EnterpriseScimIdentity\")\n .withIndex(\"enterprise_id\", (idx) => idx.eq(\"enterpriseId\", enterpriseId))\n .collect();\n },\n});\n\n/**\n * Create or update a SCIM-provisioned identity record.\n *\n * If a SCIM identity with the same `(enterpriseId, resourceType, externalId)`\n * already exists, its fields are patched in place. Otherwise a new record is\n * created. This is the core upsert used by the SCIM provisioning handler to\n * sync users and groups from external identity providers.\n *\n * @param args.enterpriseId - The ID of the enterprise the identity belongs to.\n * @param args.groupId - The ID of the root group that owns the enterprise.\n * @param args.resourceType - The SCIM resource type: `\"user\"` or `\"group\"`.\n * @param args.externalId - The external identifier assigned by the identity provider.\n * @param args.userId - An optional link to the internal user document (for user resources).\n * @param args.mappedGroupId - An optional link to an internal group document (for group resources).\n * @param args.lastProvisionedAt - An optional epoch timestamp (ms) of the last sync.\n * @param args.active - An optional flag indicating whether the identity is active.\n * @param args.raw - An optional raw SCIM payload stored for debugging or re-processing.\n * @returns The ID of the created or updated `EnterpriseScimIdentity` document.\n *\n * @example\n * ```ts\n * const identityId = await ctx.runMutation(\n * components.auth.enterprise.enterpriseScimIdentityUpsert,\n * {\n * enterpriseId,\n * groupId: orgGroupId,\n * resourceType: \"user\",\n * externalId: \"okta-user-abc123\",\n * userId,\n * active: true,\n * lastProvisionedAt: Date.now(),\n * raw: { schemas: [\"urn:ietf:params:scim:schemas:core:2.0:User\"], userName: \"jane@acme.com\" },\n * },\n * );\n * ```\n */\nexport const enterpriseScimIdentityUpsert = mutation({\n args: {\n enterpriseId: v.id(\"Enterprise\"),\n groupId: v.id(\"Group\"),\n resourceType: vScimResourceType,\n externalId: v.string(),\n userId: v.optional(v.id(\"User\")),\n mappedGroupId: v.optional(v.id(\"Group\")),\n lastProvisionedAt: v.optional(v.number()),\n active: v.optional(v.boolean()),\n raw: v.optional(v.any()),\n },\n returns: v.id(\"EnterpriseScimIdentity\"),\n handler: async (ctx, args) => {\n const existing = await ctx.db\n .query(\"EnterpriseScimIdentity\")\n .withIndex(\"enterprise_id_resource_type_external_id\", (idx) =>\n idx\n .eq(\"enterpriseId\", args.enterpriseId)\n .eq(\"resourceType\", args.resourceType)\n .eq(\"externalId\", args.externalId),\n )\n .first();\n if (existing) {\n await ctx.db.patch(existing._id, args);\n return existing._id;\n }\n return await ctx.db.insert(\"EnterpriseScimIdentity\", args);\n },\n});\n\n/**\n * Permanently delete a SCIM identity record.\n *\n * Removes the `EnterpriseScimIdentity` document. This is typically called\n * when a SCIM DELETE request is received for a user or group resource.\n *\n * @param args.identityId - The document ID of the SCIM identity to delete.\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * await ctx.runMutation(\n * components.auth.enterprise.enterpriseScimIdentityDelete,\n * { identityId: scimIdentity._id },\n * );\n * ```\n */\nexport const enterpriseScimIdentityDelete = mutation({\n args: { identityId: v.id(\"EnterpriseScimIdentity\") },\n returns: v.null(),\n handler: async (ctx, { identityId }) => {\n await ctx.db.delete(identityId);\n return null;\n },\n});\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secrets.d.ts","names":[],"sources":["../../../../src/component/public/enterprise/secrets.ts"],"mappings":";;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"secrets.d.ts","names":[],"sources":["../../../../src/component/public/enterprise/secrets.ts"],"mappings":";;;;;;;;;;;AAkCA;;;;;AA8CA;;;;;AAkCA;;;;;;;;;;;cAhFa,sBAAA;;;;;;;;;;;;;;;;;;;;;;;cA8CA,mBAAA;;;;;;;;;;;;;;;;;;;cAkCA,sBAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secrets.js","names":[],"sources":["../../../../src/component/public/enterprise/secrets.ts"],"sourcesContent":["import { v } from \"convex/values\";\nimport { mutation, query } from \"../../functions\";\nimport { vEnterpriseSecretDoc, vEnterpriseSecretKind } from \"../../model\";\n\n/**\n * Create or update an encrypted secret for an enterprise.\n *\n * Stores a secret identified by the combination of `(enterpriseId, kind)`.\n * If a secret of the same kind already exists for the enterprise, it is\n * updated with the new ciphertext and timestamp. Otherwise a new secret\n * document is created. Only one secret per kind is allowed per enterprise.\n *\n * @param args.enterpriseId - The ID of the enterprise the secret belongs to.\n * @param args.groupId - The ID of the root group that owns the enterprise.\n * @param args.kind - The type of secret being stored (e.g. `\"oidc_client_secret\"`).\n * @param args.ciphertext - The encrypted secret value.\n * @param args.updatedAt - Epoch timestamp (ms) when the secret was last updated.\n * @returns The ID of the created or updated `EnterpriseSecret` document.\n *\n * @example\n * ```ts\n * const secretId = await ctx.runMutation(\n * components.auth.enterprise.enterpriseSecretUpsert,\n * {\n * enterpriseId,\n * groupId: orgGroupId,\n * kind: \"oidc_client_secret\",\n * ciphertext: \"encrypted:aes256:...\",\n * updatedAt: Date.now(),\n * },\n * );\n * ```\n */\nexport const enterpriseSecretUpsert = mutation({\n args: {\n enterpriseId: v.id(\"Enterprise\"),\n groupId: v.id(\"Group\"),\n kind: vEnterpriseSecretKind,\n ciphertext: v.string(),\n updatedAt: v.number(),\n },\n returns: v.id(\"EnterpriseSecret\"),\n handler: async (ctx, args) => {\n const existing = await ctx.db\n .query(\"EnterpriseSecret\")\n .withIndex(\"enterprise_id_kind\", (idx) =>\n idx.eq(\"enterpriseId\", args.enterpriseId).eq(\"kind\", args.kind),\n )\n .first();\n if (existing) {\n await ctx.db.patch(existing._id, args);\n return existing._id;\n }\n return await ctx.db.insert(\"EnterpriseSecret\", args);\n },\n});\n\n/**\n * Retrieve an encrypted secret for an enterprise by kind.\n *\n * Looks up the secret using the composite `(enterpriseId, kind)` index.\n * Returns the full document including the ciphertext, or `null` if no secret\n * of that kind has been stored for the enterprise.\n *\n * @param args.enterpriseId - The ID of the enterprise whose secret to retrieve.\n * @param args.kind - The type of secret to look up (e.g. `\"oidc_client_secret\"`).\n * @returns The enterprise secret document, or `null` if not found.\n *\n * @example\n * ```ts\n * const secret = await ctx.runQuery(\n * components.auth.enterprise.enterpriseSecretGet,\n * { enterpriseId, kind: \"oidc_client_secret\" },\n * );\n * if (secret) {\n * const plaintext = decrypt(secret.ciphertext);\n * }\n * ```\n */\nexport const enterpriseSecretGet = query({\n args: {\n enterpriseId: v.id(\"Enterprise\"),\n kind: vEnterpriseSecretKind,\n },\n returns: v.union(vEnterpriseSecretDoc, v.null()),\n handler: async (ctx, { enterpriseId, kind }) => {\n return await ctx.db\n .query(\"EnterpriseSecret\")\n .withIndex(\"enterprise_id_kind\", (idx) =>\n idx.eq(\"enterpriseId\", enterpriseId).eq(\"kind\", kind),\n )\n .first();\n },\n});\n\n/**\n * Delete an encrypted secret for an enterprise by kind.\n *\n * Removes the secret document matching the `(enterpriseId, kind)` pair.\n * If no such secret exists, this is a no-op.\n *\n * @param args.enterpriseId - The ID of the enterprise whose secret to delete.\n * @param args.kind - The type of secret to remove (e.g. `\"oidc_client_secret\"`).\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * await ctx.runMutation(\n * components.auth.enterprise.enterpriseSecretDelete,\n * { enterpriseId, kind: \"oidc_client_secret\" },\n * );\n * ```\n */\nexport const enterpriseSecretDelete = mutation({\n args: {\n enterpriseId: v.id(\"Enterprise\"),\n kind: vEnterpriseSecretKind,\n },\n returns: v.null(),\n handler: async (ctx, { enterpriseId, kind }) => {\n const existing = await ctx.db\n .query(\"EnterpriseSecret\")\n .withIndex(\"enterprise_id_kind\", (idx) =>\n idx.eq(\"enterpriseId\", enterpriseId).eq(\"kind\", kind),\n )\n .first();\n if (existing) {\n await ctx.db.delete(existing._id);\n }\n return null;\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"secrets.js","names":[],"sources":["../../../../src/component/public/enterprise/secrets.ts"],"sourcesContent":["import { v } from \"convex/values\";\n\nimport { mutation, query } from \"../../functions\";\nimport { vEnterpriseSecretDoc, vEnterpriseSecretKind } from \"../../model\";\n\n/**\n * Create or update an encrypted secret for an enterprise.\n *\n * Stores a secret identified by the combination of `(enterpriseId, kind)`.\n * If a secret of the same kind already exists for the enterprise, it is\n * updated with the new ciphertext and timestamp. Otherwise a new secret\n * document is created. Only one secret per kind is allowed per enterprise.\n *\n * @param args.enterpriseId - The ID of the enterprise the secret belongs to.\n * @param args.groupId - The ID of the root group that owns the enterprise.\n * @param args.kind - The type of secret being stored (e.g. `\"oidc_client_secret\"`).\n * @param args.ciphertext - The encrypted secret value.\n * @param args.updatedAt - Epoch timestamp (ms) when the secret was last updated.\n * @returns The ID of the created or updated `EnterpriseSecret` document.\n *\n * @example\n * ```ts\n * const secretId = await ctx.runMutation(\n * components.auth.enterprise.enterpriseSecretUpsert,\n * {\n * enterpriseId,\n * groupId: orgGroupId,\n * kind: \"oidc_client_secret\",\n * ciphertext: \"encrypted:aes256:...\",\n * updatedAt: Date.now(),\n * },\n * );\n * ```\n */\nexport const enterpriseSecretUpsert = mutation({\n args: {\n enterpriseId: v.id(\"Enterprise\"),\n groupId: v.id(\"Group\"),\n kind: vEnterpriseSecretKind,\n ciphertext: v.string(),\n updatedAt: v.number(),\n },\n returns: v.id(\"EnterpriseSecret\"),\n handler: async (ctx, args) => {\n const existing = await ctx.db\n .query(\"EnterpriseSecret\")\n .withIndex(\"enterprise_id_kind\", (idx) =>\n idx.eq(\"enterpriseId\", args.enterpriseId).eq(\"kind\", args.kind),\n )\n .first();\n if (existing) {\n await ctx.db.patch(existing._id, args);\n return existing._id;\n }\n return await ctx.db.insert(\"EnterpriseSecret\", args);\n },\n});\n\n/**\n * Retrieve an encrypted secret for an enterprise by kind.\n *\n * Looks up the secret using the composite `(enterpriseId, kind)` index.\n * Returns the full document including the ciphertext, or `null` if no secret\n * of that kind has been stored for the enterprise.\n *\n * @param args.enterpriseId - The ID of the enterprise whose secret to retrieve.\n * @param args.kind - The type of secret to look up (e.g. `\"oidc_client_secret\"`).\n * @returns The enterprise secret document, or `null` if not found.\n *\n * @example\n * ```ts\n * const secret = await ctx.runQuery(\n * components.auth.enterprise.enterpriseSecretGet,\n * { enterpriseId, kind: \"oidc_client_secret\" },\n * );\n * if (secret) {\n * const plaintext = decrypt(secret.ciphertext);\n * }\n * ```\n */\nexport const enterpriseSecretGet = query({\n args: {\n enterpriseId: v.id(\"Enterprise\"),\n kind: vEnterpriseSecretKind,\n },\n returns: v.union(vEnterpriseSecretDoc, v.null()),\n handler: async (ctx, { enterpriseId, kind }) => {\n return await ctx.db\n .query(\"EnterpriseSecret\")\n .withIndex(\"enterprise_id_kind\", (idx) =>\n idx.eq(\"enterpriseId\", enterpriseId).eq(\"kind\", kind),\n )\n .first();\n },\n});\n\n/**\n * Delete an encrypted secret for an enterprise by kind.\n *\n * Removes the secret document matching the `(enterpriseId, kind)` pair.\n * If no such secret exists, this is a no-op.\n *\n * @param args.enterpriseId - The ID of the enterprise whose secret to delete.\n * @param args.kind - The type of secret to remove (e.g. `\"oidc_client_secret\"`).\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * await ctx.runMutation(\n * components.auth.enterprise.enterpriseSecretDelete,\n * { enterpriseId, kind: \"oidc_client_secret\" },\n * );\n * ```\n */\nexport const enterpriseSecretDelete = mutation({\n args: {\n enterpriseId: v.id(\"Enterprise\"),\n kind: vEnterpriseSecretKind,\n },\n returns: v.null(),\n handler: async (ctx, { enterpriseId, kind }) => {\n const existing = await ctx.db\n .query(\"EnterpriseSecret\")\n .withIndex(\"enterprise_id_kind\", (idx) =>\n idx.eq(\"enterpriseId\", enterpriseId).eq(\"kind\", kind),\n )\n .first();\n if (existing) {\n await ctx.db.delete(existing._id);\n }\n return null;\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,MAAa,yBAAyB,SAAS;CAC7C,MAAM;EACJ,cAAc,EAAE,GAAG,aAAa;EAChC,SAAS,EAAE,GAAG,QAAQ;EACtB,MAAM;EACN,YAAY,EAAE,QAAQ;EACtB,WAAW,EAAE,QAAQ;EACtB;CACD,SAAS,EAAE,GAAG,mBAAmB;CACjC,SAAS,OAAO,KAAK,SAAS;EAC5B,MAAM,WAAW,MAAM,IAAI,GACxB,MAAM,mBAAmB,CACzB,UAAU,uBAAuB,QAChC,IAAI,GAAG,gBAAgB,KAAK,aAAa,CAAC,GAAG,QAAQ,KAAK,KAAK,CAChE,CACA,OAAO;AACV,MAAI,UAAU;AACZ,SAAM,IAAI,GAAG,MAAM,SAAS,KAAK,KAAK;AACtC,UAAO,SAAS;;AAElB,SAAO,MAAM,IAAI,GAAG,OAAO,oBAAoB,KAAK;;CAEvD,CAAC;;;;;;;;;;;;;;;;;;;;;;;AAwBF,MAAa,sBAAsB,MAAM;CACvC,MAAM;EACJ,cAAc,EAAE,GAAG,aAAa;EAChC,MAAM;EACP;CACD,SAAS,EAAE,MAAM,sBAAsB,EAAE,MAAM,CAAC;CAChD,SAAS,OAAO,KAAK,EAAE,cAAc,WAAW;AAC9C,SAAO,MAAM,IAAI,GACd,MAAM,mBAAmB,CACzB,UAAU,uBAAuB,QAChC,IAAI,GAAG,gBAAgB,aAAa,CAAC,GAAG,QAAQ,KAAK,CACtD,CACA,OAAO;;CAEb,CAAC;;;;;;;;;;;;;;;;;;;AAoBF,MAAa,yBAAyB,SAAS;CAC7C,MAAM;EACJ,cAAc,EAAE,GAAG,aAAa;EAChC,MAAM;EACP;CACD,SAAS,EAAE,MAAM;CACjB,SAAS,OAAO,KAAK,EAAE,cAAc,WAAW;EAC9C,MAAM,WAAW,MAAM,IAAI,GACxB,MAAM,mBAAmB,CACzB,UAAU,uBAAuB,QAChC,IAAI,GAAG,gBAAgB,aAAa,CAAC,GAAG,QAAQ,KAAK,CACtD,CACA,OAAO;AACV,MAAI,SACF,OAAM,IAAI,GAAG,OAAO,SAAS,IAAI;AAEnC,SAAO;;CAEV,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhooks.d.ts","names":[],"sources":["../../../../src/component/public/enterprise/webhooks.ts"],"mappings":";;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"webhooks.d.ts","names":[],"sources":["../../../../src/component/public/enterprise/webhooks.ts"],"mappings":";;;;;;;;;;;;;;;;AAyCA;;;;;AAyCA;;;;;AA+BA;;;;;AAkCA;;;;cA1Ga,+BAAA;AAiJb;;;;;AAyCA;;;;;AAkCA;;;;;AAsCA;;;;;AAjHA,cAxGa,6BAAA;;;;;;;;;;;;;;;;;;;;;cA+BA,4BAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;cAkCA,+BAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAuCA,gCAAA;;;;;;;;;;;;;;;;;;;;;;;cAyCA,kCAAA;;;;;;;;;;;;;;;;;;;;;;cAkCA,6BAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;cAsCA,8BAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhooks.js","names":[],"sources":["../../../../src/component/public/enterprise/webhooks.ts"],"sourcesContent":["import { v } from \"convex/values\";\nimport { mutation, query } from \"../../functions\";\nimport {\n vEnterpriseWebhookDeliveryDoc,\n vEnterpriseWebhookEndpointDoc,\n vWebhookEndpointStatus,\n} from \"../../model\";\n\n/**\n * Register a new webhook endpoint for an enterprise.\n *\n * Creates an `EnterpriseWebhookEndpoint` document with an initial failure\n * count of `0`. The endpoint status defaults to `\"active\"` when not\n * explicitly provided. Each endpoint subscribes to a set of event types\n * that determine which deliveries are sent to it.\n *\n * @param args.enterpriseId - The ID of the enterprise this endpoint belongs to.\n * @param args.groupId - The ID of the root group that owns the enterprise.\n * @param args.url - The HTTPS URL where webhook payloads will be delivered.\n * @param args.status - An optional lifecycle status (`\"active\"`, `\"paused\"`, or `\"disabled\"`). Defaults to `\"active\"`.\n * @param args.secretHash - A hash of the signing secret used to verify delivery payloads.\n * @param args.subscriptions - An array of event type strings this endpoint subscribes to (e.g. `[\"user.login\", \"scim.provision\"]`).\n * @param args.createdByUserId - An optional ID of the user who created this endpoint.\n * @param args.extend - An optional arbitrary extension object for custom endpoint metadata.\n * @returns The ID of the newly created `EnterpriseWebhookEndpoint` document.\n *\n * @example\n * ```ts\n * const endpointId = await ctx.runMutation(\n * components.auth.enterprise.enterpriseWebhookEndpointCreate,\n * {\n * enterpriseId,\n * groupId: orgGroupId,\n * url: \"https://acme.com/webhooks/auth\",\n * secretHash: \"sha256:whsec_...\",\n * subscriptions: [\"user.login\", \"user.created\", \"scim.provision\"],\n * },\n * );\n * ```\n */\nexport const enterpriseWebhookEndpointCreate = mutation({\n args: {\n enterpriseId: v.id(\"Enterprise\"),\n groupId: v.id(\"Group\"),\n url: v.string(),\n status: v.optional(vWebhookEndpointStatus),\n secretHash: v.string(),\n subscriptions: v.array(v.string()),\n createdByUserId: v.optional(v.id(\"User\")),\n extend: v.optional(v.any()),\n },\n returns: v.id(\"EnterpriseWebhookEndpoint\"),\n handler: async (ctx, args) => {\n return await ctx.db.insert(\"EnterpriseWebhookEndpoint\", {\n ...args,\n status: args.status ?? \"active\",\n failureCount: 0,\n });\n },\n});\n\n/**\n * List all webhook endpoints registered for an enterprise.\n *\n * Returns all `EnterpriseWebhookEndpoint` documents associated with the\n * given enterprise, regardless of status.\n *\n * @param args.enterpriseId - The ID of the enterprise whose webhook endpoints to list.\n * @returns An array of webhook endpoint documents.\n *\n * @example\n * ```ts\n * const endpoints = await ctx.runQuery(\n * components.auth.enterprise.enterpriseWebhookEndpointList,\n * { enterpriseId },\n * );\n * for (const ep of endpoints) {\n * console.log(ep.url, ep.status, ep.subscriptions);\n * }\n * ```\n */\nexport const enterpriseWebhookEndpointList = query({\n args: { enterpriseId: v.id(\"Enterprise\") },\n returns: v.array(vEnterpriseWebhookEndpointDoc),\n handler: async (ctx, { enterpriseId }) => {\n return await ctx.db\n .query(\"EnterpriseWebhookEndpoint\")\n .withIndex(\"enterprise_id\", (idx) => idx.eq(\"enterpriseId\", enterpriseId))\n .collect();\n },\n});\n\n/**\n * Retrieve a single webhook endpoint by its document ID.\n *\n * Returns the full endpoint document if it exists, or `null` if no\n * endpoint is found with the given ID.\n *\n * @param args.endpointId - The document ID of the webhook endpoint to retrieve.\n * @returns The webhook endpoint document, or `null` if not found.\n *\n * @example\n * ```ts\n * const endpoint = await ctx.runQuery(\n * components.auth.enterprise.enterpriseWebhookEndpointGet,\n * { endpointId },\n * );\n * if (endpoint) {\n * console.log(endpoint.url, endpoint.failureCount);\n * }\n * ```\n */\nexport const enterpriseWebhookEndpointGet = query({\n args: { endpointId: v.id(\"EnterpriseWebhookEndpoint\") },\n returns: v.union(vEnterpriseWebhookEndpointDoc, v.null()),\n handler: async (ctx, { endpointId }) => {\n return await ctx.db.get(endpointId);\n },\n});\n\n/**\n * Partially update (patch) an existing webhook endpoint.\n *\n * Merges the provided `data` fields into the endpoint document. Only the\n * fields present in `data` are changed; all other fields are preserved.\n * Common updates include changing the URL, rotating the secret, updating\n * subscriptions, or changing the status.\n *\n * @param args.endpointId - The document ID of the webhook endpoint to update.\n * @param args.data - An object containing the fields to update (e.g. `{ url, status, subscriptions }`).\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * await ctx.runMutation(\n * components.auth.enterprise.enterpriseWebhookEndpointUpdate,\n * {\n * endpointId,\n * data: {\n * status: \"paused\",\n * subscriptions: [\"user.login\"],\n * },\n * },\n * );\n * ```\n */\nexport const enterpriseWebhookEndpointUpdate = mutation({\n args: { endpointId: v.id(\"EnterpriseWebhookEndpoint\"), data: v.any() },\n returns: v.null(),\n handler: async (ctx, { endpointId, data }) => {\n await ctx.db.patch(endpointId, data);\n return null;\n },\n});\n\n/**\n * Enqueue a webhook delivery for a specific endpoint.\n *\n * Creates a new `EnterpriseWebhookDelivery` document with an initial status\n * of `\"pending\"` and an attempt count of `0`. The delivery will be picked up\n * by the delivery worker once `nextAttemptAt` is reached.\n *\n * @param args.enterpriseId - The ID of the enterprise the delivery belongs to.\n * @param args.endpointId - The ID of the webhook endpoint this delivery targets.\n * @param args.auditEventId - An optional ID of the audit event that triggered this delivery.\n * @param args.eventType - The event type string describing the payload (e.g. `\"user.created\"`).\n * @param args.payload - The arbitrary JSON payload to deliver to the endpoint.\n * @param args.nextAttemptAt - Epoch timestamp (ms) when the delivery should first be attempted.\n * @returns The ID of the newly created `EnterpriseWebhookDelivery` document.\n *\n * @example\n * ```ts\n * const deliveryId = await ctx.runMutation(\n * components.auth.enterprise.enterpriseWebhookDeliveryEnqueue,\n * {\n * enterpriseId,\n * endpointId,\n * auditEventId,\n * eventType: \"user.created\",\n * payload: { userId, email: \"jane@acme.com\" },\n * nextAttemptAt: Date.now(),\n * },\n * );\n * ```\n */\nexport const enterpriseWebhookDeliveryEnqueue = mutation({\n args: {\n enterpriseId: v.id(\"Enterprise\"),\n endpointId: v.id(\"EnterpriseWebhookEndpoint\"),\n auditEventId: v.optional(v.id(\"EnterpriseAuditEvent\")),\n eventType: v.string(),\n payload: v.any(),\n nextAttemptAt: v.number(),\n },\n returns: v.id(\"EnterpriseWebhookDelivery\"),\n handler: async (ctx, args) => {\n return await ctx.db.insert(\"EnterpriseWebhookDelivery\", {\n ...args,\n status: \"pending\",\n attemptCount: 0,\n });\n },\n});\n\n/**\n * List pending webhook deliveries that are ready to be attempted.\n *\n * Queries the `status_next_attempt_at` index for deliveries with status\n * `\"pending\"` whose `nextAttemptAt` is at or before the provided timestamp.\n * This is used by the delivery worker to find deliveries due for processing.\n *\n * @param args.now - The current epoch timestamp (ms) used as the cutoff for `nextAttemptAt`.\n * @param args.limit - Maximum number of deliveries to return (clamped between 1 and 100, defaults to 50).\n * @returns An array of webhook delivery documents ready for dispatch.\n *\n * @example\n * ```ts\n * const ready = await ctx.runQuery(\n * components.auth.enterprise.enterpriseWebhookDeliveryListReady,\n * { now: Date.now(), limit: 10 },\n * );\n * for (const delivery of ready) {\n * await dispatchWebhook(delivery);\n * }\n * ```\n */\nexport const enterpriseWebhookDeliveryListReady = query({\n args: { now: v.number(), limit: v.optional(v.number()) },\n returns: v.array(vEnterpriseWebhookDeliveryDoc),\n handler: async (ctx, { now, limit }) => {\n return await ctx.db\n .query(\"EnterpriseWebhookDelivery\")\n .withIndex(\"status_next_attempt_at\", (idx) =>\n idx.eq(\"status\", \"pending\").lte(\"nextAttemptAt\", now),\n )\n .take(Math.min(Math.max(limit ?? 50, 1), 100));\n },\n});\n\n/**\n * List webhook deliveries for a specific enterprise, ordered by most recent first.\n *\n * Returns deliveries in reverse chronological order, useful for displaying\n * delivery history in an admin dashboard. Includes deliveries of all statuses.\n *\n * @param args.enterpriseId - The ID of the enterprise whose deliveries to list.\n * @param args.limit - Maximum number of deliveries to return (clamped between 1 and 100, defaults to 50).\n * @returns An array of webhook delivery documents, most recent first.\n *\n * @example\n * ```ts\n * const deliveries = await ctx.runQuery(\n * components.auth.enterprise.enterpriseWebhookDeliveryList,\n * { enterpriseId, limit: 25 },\n * );\n * for (const d of deliveries) {\n * console.log(d.eventType, d.status, d.attemptCount);\n * }\n * ```\n */\nexport const enterpriseWebhookDeliveryList = query({\n args: { enterpriseId: v.id(\"Enterprise\"), limit: v.optional(v.number()) },\n returns: v.array(vEnterpriseWebhookDeliveryDoc),\n handler: async (ctx, { enterpriseId, limit }) => {\n return await ctx.db\n .query(\"EnterpriseWebhookDelivery\")\n .withIndex(\"enterprise_id\", (idx) => idx.eq(\"enterpriseId\", enterpriseId))\n .order(\"desc\")\n .take(Math.min(Math.max(limit ?? 50, 1), 100));\n },\n});\n\n/**\n * Partially update (patch) an existing webhook delivery record.\n *\n * Merges the provided `data` fields into the delivery document. This is\n * typically used by the delivery worker to update the delivery status,\n * increment the attempt count, record response codes, or schedule retry\n * timestamps after a delivery attempt.\n *\n * @param args.deliveryId - The document ID of the webhook delivery to update.\n * @param args.data - An object containing the fields to update (e.g. `{ status, attemptCount, nextAttemptAt }`).\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * await ctx.runMutation(\n * components.auth.enterprise.enterpriseWebhookDeliveryPatch,\n * {\n * deliveryId,\n * data: {\n * status: \"delivered\",\n * attemptCount: 1,\n * },\n * },\n * );\n * ```\n */\nexport const enterpriseWebhookDeliveryPatch = mutation({\n args: { deliveryId: v.id(\"EnterpriseWebhookDelivery\"), data: v.any() },\n returns: v.null(),\n handler: async (ctx, { deliveryId, data }) => {\n await ctx.db.patch(deliveryId, data);\n return null;\n },\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"webhooks.js","names":[],"sources":["../../../../src/component/public/enterprise/webhooks.ts"],"sourcesContent":["import { v } from \"convex/values\";\n\nimport { mutation, query } from \"../../functions\";\nimport {\n vEnterpriseWebhookDeliveryDoc,\n vEnterpriseWebhookEndpointDoc,\n vWebhookEndpointStatus,\n} from \"../../model\";\n\n/**\n * Register a new webhook endpoint for an enterprise.\n *\n * Creates an `EnterpriseWebhookEndpoint` document with an initial failure\n * count of `0`. The endpoint status defaults to `\"active\"` when not\n * explicitly provided. Each endpoint subscribes to a set of event types\n * that determine which deliveries are sent to it.\n *\n * @param args.enterpriseId - The ID of the enterprise this endpoint belongs to.\n * @param args.groupId - The ID of the root group that owns the enterprise.\n * @param args.url - The HTTPS URL where webhook payloads will be delivered.\n * @param args.status - An optional lifecycle status (`\"active\"`, `\"paused\"`, or `\"disabled\"`). Defaults to `\"active\"`.\n * @param args.secretHash - A hash of the signing secret used to verify delivery payloads.\n * @param args.subscriptions - An array of event type strings this endpoint subscribes to (e.g. `[\"user.login\", \"scim.provision\"]`).\n * @param args.createdByUserId - An optional ID of the user who created this endpoint.\n * @param args.extend - An optional arbitrary extension object for custom endpoint metadata.\n * @returns The ID of the newly created `EnterpriseWebhookEndpoint` document.\n *\n * @example\n * ```ts\n * const endpointId = await ctx.runMutation(\n * components.auth.enterprise.enterpriseWebhookEndpointCreate,\n * {\n * enterpriseId,\n * groupId: orgGroupId,\n * url: \"https://acme.com/webhooks/auth\",\n * secretHash: \"sha256:whsec_...\",\n * subscriptions: [\"user.login\", \"user.created\", \"scim.provision\"],\n * },\n * );\n * ```\n */\nexport const enterpriseWebhookEndpointCreate = mutation({\n args: {\n enterpriseId: v.id(\"Enterprise\"),\n groupId: v.id(\"Group\"),\n url: v.string(),\n status: v.optional(vWebhookEndpointStatus),\n secretHash: v.string(),\n subscriptions: v.array(v.string()),\n createdByUserId: v.optional(v.id(\"User\")),\n extend: v.optional(v.any()),\n },\n returns: v.id(\"EnterpriseWebhookEndpoint\"),\n handler: async (ctx, args) => {\n return await ctx.db.insert(\"EnterpriseWebhookEndpoint\", {\n ...args,\n status: args.status ?? \"active\",\n failureCount: 0,\n });\n },\n});\n\n/**\n * List all webhook endpoints registered for an enterprise.\n *\n * Returns all `EnterpriseWebhookEndpoint` documents associated with the\n * given enterprise, regardless of status.\n *\n * @param args.enterpriseId - The ID of the enterprise whose webhook endpoints to list.\n * @returns An array of webhook endpoint documents.\n *\n * @example\n * ```ts\n * const endpoints = await ctx.runQuery(\n * components.auth.enterprise.enterpriseWebhookEndpointList,\n * { enterpriseId },\n * );\n * for (const ep of endpoints) {\n * console.log(ep.url, ep.status, ep.subscriptions);\n * }\n * ```\n */\nexport const enterpriseWebhookEndpointList = query({\n args: { enterpriseId: v.id(\"Enterprise\") },\n returns: v.array(vEnterpriseWebhookEndpointDoc),\n handler: async (ctx, { enterpriseId }) => {\n return await ctx.db\n .query(\"EnterpriseWebhookEndpoint\")\n .withIndex(\"enterprise_id\", (idx) => idx.eq(\"enterpriseId\", enterpriseId))\n .collect();\n },\n});\n\n/**\n * Retrieve a single webhook endpoint by its document ID.\n *\n * Returns the full endpoint document if it exists, or `null` if no\n * endpoint is found with the given ID.\n *\n * @param args.endpointId - The document ID of the webhook endpoint to retrieve.\n * @returns The webhook endpoint document, or `null` if not found.\n *\n * @example\n * ```ts\n * const endpoint = await ctx.runQuery(\n * components.auth.enterprise.enterpriseWebhookEndpointGet,\n * { endpointId },\n * );\n * if (endpoint) {\n * console.log(endpoint.url, endpoint.failureCount);\n * }\n * ```\n */\nexport const enterpriseWebhookEndpointGet = query({\n args: { endpointId: v.id(\"EnterpriseWebhookEndpoint\") },\n returns: v.union(vEnterpriseWebhookEndpointDoc, v.null()),\n handler: async (ctx, { endpointId }) => {\n return await ctx.db.get(endpointId);\n },\n});\n\n/**\n * Partially update (patch) an existing webhook endpoint.\n *\n * Merges the provided `data` fields into the endpoint document. Only the\n * fields present in `data` are changed; all other fields are preserved.\n * Common updates include changing the URL, rotating the secret, updating\n * subscriptions, or changing the status.\n *\n * @param args.endpointId - The document ID of the webhook endpoint to update.\n * @param args.data - An object containing the fields to update (e.g. `{ url, status, subscriptions }`).\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * await ctx.runMutation(\n * components.auth.enterprise.enterpriseWebhookEndpointUpdate,\n * {\n * endpointId,\n * data: {\n * status: \"paused\",\n * subscriptions: [\"user.login\"],\n * },\n * },\n * );\n * ```\n */\nexport const enterpriseWebhookEndpointUpdate = mutation({\n args: { endpointId: v.id(\"EnterpriseWebhookEndpoint\"), data: v.any() },\n returns: v.null(),\n handler: async (ctx, { endpointId, data }) => {\n await ctx.db.patch(endpointId, data);\n return null;\n },\n});\n\n/**\n * Enqueue a webhook delivery for a specific endpoint.\n *\n * Creates a new `EnterpriseWebhookDelivery` document with an initial status\n * of `\"pending\"` and an attempt count of `0`. The delivery will be picked up\n * by the delivery worker once `nextAttemptAt` is reached.\n *\n * @param args.enterpriseId - The ID of the enterprise the delivery belongs to.\n * @param args.endpointId - The ID of the webhook endpoint this delivery targets.\n * @param args.auditEventId - An optional ID of the audit event that triggered this delivery.\n * @param args.eventType - The event type string describing the payload (e.g. `\"user.created\"`).\n * @param args.payload - The arbitrary JSON payload to deliver to the endpoint.\n * @param args.nextAttemptAt - Epoch timestamp (ms) when the delivery should first be attempted.\n * @returns The ID of the newly created `EnterpriseWebhookDelivery` document.\n *\n * @example\n * ```ts\n * const deliveryId = await ctx.runMutation(\n * components.auth.enterprise.enterpriseWebhookDeliveryEnqueue,\n * {\n * enterpriseId,\n * endpointId,\n * auditEventId,\n * eventType: \"user.created\",\n * payload: { userId, email: \"jane@acme.com\" },\n * nextAttemptAt: Date.now(),\n * },\n * );\n * ```\n */\nexport const enterpriseWebhookDeliveryEnqueue = mutation({\n args: {\n enterpriseId: v.id(\"Enterprise\"),\n endpointId: v.id(\"EnterpriseWebhookEndpoint\"),\n auditEventId: v.optional(v.id(\"EnterpriseAuditEvent\")),\n eventType: v.string(),\n payload: v.any(),\n nextAttemptAt: v.number(),\n },\n returns: v.id(\"EnterpriseWebhookDelivery\"),\n handler: async (ctx, args) => {\n return await ctx.db.insert(\"EnterpriseWebhookDelivery\", {\n ...args,\n status: \"pending\",\n attemptCount: 0,\n });\n },\n});\n\n/**\n * List pending webhook deliveries that are ready to be attempted.\n *\n * Queries the `status_next_attempt_at` index for deliveries with status\n * `\"pending\"` whose `nextAttemptAt` is at or before the provided timestamp.\n * This is used by the delivery worker to find deliveries due for processing.\n *\n * @param args.now - The current epoch timestamp (ms) used as the cutoff for `nextAttemptAt`.\n * @param args.limit - Maximum number of deliveries to return (clamped between 1 and 100, defaults to 50).\n * @returns An array of webhook delivery documents ready for dispatch.\n *\n * @example\n * ```ts\n * const ready = await ctx.runQuery(\n * components.auth.enterprise.enterpriseWebhookDeliveryListReady,\n * { now: Date.now(), limit: 10 },\n * );\n * for (const delivery of ready) {\n * await dispatchWebhook(delivery);\n * }\n * ```\n */\nexport const enterpriseWebhookDeliveryListReady = query({\n args: { now: v.number(), limit: v.optional(v.number()) },\n returns: v.array(vEnterpriseWebhookDeliveryDoc),\n handler: async (ctx, { now, limit }) => {\n return await ctx.db\n .query(\"EnterpriseWebhookDelivery\")\n .withIndex(\"status_next_attempt_at\", (idx) =>\n idx.eq(\"status\", \"pending\").lte(\"nextAttemptAt\", now),\n )\n .take(Math.min(Math.max(limit ?? 50, 1), 100));\n },\n});\n\n/**\n * List webhook deliveries for a specific enterprise, ordered by most recent first.\n *\n * Returns deliveries in reverse chronological order, useful for displaying\n * delivery history in an admin dashboard. Includes deliveries of all statuses.\n *\n * @param args.enterpriseId - The ID of the enterprise whose deliveries to list.\n * @param args.limit - Maximum number of deliveries to return (clamped between 1 and 100, defaults to 50).\n * @returns An array of webhook delivery documents, most recent first.\n *\n * @example\n * ```ts\n * const deliveries = await ctx.runQuery(\n * components.auth.enterprise.enterpriseWebhookDeliveryList,\n * { enterpriseId, limit: 25 },\n * );\n * for (const d of deliveries) {\n * console.log(d.eventType, d.status, d.attemptCount);\n * }\n * ```\n */\nexport const enterpriseWebhookDeliveryList = query({\n args: { enterpriseId: v.id(\"Enterprise\"), limit: v.optional(v.number()) },\n returns: v.array(vEnterpriseWebhookDeliveryDoc),\n handler: async (ctx, { enterpriseId, limit }) => {\n return await ctx.db\n .query(\"EnterpriseWebhookDelivery\")\n .withIndex(\"enterprise_id\", (idx) => idx.eq(\"enterpriseId\", enterpriseId))\n .order(\"desc\")\n .take(Math.min(Math.max(limit ?? 50, 1), 100));\n },\n});\n\n/**\n * Partially update (patch) an existing webhook delivery record.\n *\n * Merges the provided `data` fields into the delivery document. This is\n * typically used by the delivery worker to update the delivery status,\n * increment the attempt count, record response codes, or schedule retry\n * timestamps after a delivery attempt.\n *\n * @param args.deliveryId - The document ID of the webhook delivery to update.\n * @param args.data - An object containing the fields to update (e.g. `{ status, attemptCount, nextAttemptAt }`).\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * await ctx.runMutation(\n * components.auth.enterprise.enterpriseWebhookDeliveryPatch,\n * {\n * deliveryId,\n * data: {\n * status: \"delivered\",\n * attemptCount: 1,\n * },\n * },\n * );\n * ```\n */\nexport const enterpriseWebhookDeliveryPatch = mutation({\n args: { deliveryId: v.id(\"EnterpriseWebhookDelivery\"), data: v.any() },\n returns: v.null(),\n handler: async (ctx, { deliveryId, data }) => {\n await ctx.db.patch(deliveryId, data);\n return null;\n },\n});\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"devices.d.ts","names":[],"sources":["../../../../src/component/public/factors/devices.ts"],"mappings":";;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"devices.d.ts","names":[],"sources":["../../../../src/component/public/factors/devices.ts"],"mappings":";;;;;;;;;;;;;;AAqCA;;;;;AAoCA;;;;;AAoCA;;;;;AAuCA;;;;;AAwCA;cAvJa,YAAA;;;;AAmLb;;;;;;;;;;;;;;;;;;;cA/Ia,mBAAA;;;;;;;;;;;;;;;;;;;;;;;;cAoCA,mBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;cAuCA,eAAA;;;;;;;;;;;;;;;;;;;;;;;;cAwCA,sBAAA;;;;;;;;;;;;;;;;;;;;cA4BA,YAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"devices.js","names":[],"sources":["../../../../src/component/public/factors/devices.ts"],"sourcesContent":["import { v } from \"convex/values\";\nimport { mutation, query } from \"../../functions\";\nimport { vDeviceCodeDoc, vDeviceStatus } from \"../../model\";\n\n/**\n * Insert a new device authorization record into the `DeviceCode` table.\n *\n * Creates a pending device authorization entry as part of the OAuth 2.0\n * Device Authorization Grant (RFC 8628). The record tracks the hashed device\n * code, the human-readable user code, expiry, and polling interval.\n *\n * @param deviceCodeHash - SHA-256 hash of the device code issued to the client.\n * Only the hash is stored; the raw code is never persisted.\n * @param userCode - Short, human-readable code displayed to the end-user\n * so they can authorize the device on a separate screen.\n * @param expiresAt - Unix timestamp (in milliseconds) after which the device\n * authorization request is no longer valid.\n * @param interval - Minimum polling interval in seconds that the device client\n * must wait between token requests.\n * @param status - Initial status of the device authorization (e.g. `\"pending\"`).\n * @returns The `_id` of the newly created `DeviceCode` document.\n *\n * @example\n * ```ts\n * const deviceCodeId = await ctx.runMutation(\n * components.auth.factors.devices.deviceInsert,\n * {\n * deviceCodeHash: \"a1b2c3d4e5f6...\",\n * userCode: \"ABCD-1234\",\n * expiresAt: Date.now() + 10 * 60 * 1000,\n * interval: 5,\n * status: \"pending\",\n * },\n * );\n * ```\n */\nexport const deviceInsert = mutation({\n args: {\n deviceCodeHash: v.string(),\n userCode: v.string(),\n expiresAt: v.number(),\n interval: v.number(),\n status: vDeviceStatus,\n },\n returns: v.id(\"DeviceCode\"),\n handler: async (ctx, args) => {\n return await ctx.db.insert(\"DeviceCode\", args);\n },\n});\n\n/**\n * Look up a device authorization record by its hashed device code.\n *\n * Queries the `DeviceCode` table using the `device_code_hash` index.\n * This is the primary lookup used by the token endpoint when a device\n * client polls for authorization status.\n *\n * @param deviceCodeHash - SHA-256 hash of the device code to look up.\n * @returns The matching `DeviceCode` document, or `null` if no record\n * exists for the given hash.\n *\n * @example\n * ```ts\n * const deviceCode = await ctx.runQuery(\n * components.auth.factors.devices.deviceGetByCodeHash,\n * { deviceCodeHash: \"a1b2c3d4e5f6...\" },\n * );\n * if (deviceCode && deviceCode.status === \"authorized\") {\n * // Exchange for tokens\n * }\n * ```\n */\nexport const deviceGetByCodeHash = query({\n args: { deviceCodeHash: v.string() },\n returns: v.union(vDeviceCodeDoc, v.null()),\n handler: async (ctx, { deviceCodeHash }) => {\n return await ctx.db\n .query(\"DeviceCode\")\n .withIndex(\"device_code_hash\", (q) =>\n q.eq(\"deviceCodeHash\", deviceCodeHash),\n )\n .first();\n },\n});\n\n/**\n * Look up a pending device authorization by its user-facing code.\n *\n * Queries the `DeviceCode` table using the `user_code_status` compound index,\n * filtering to only `\"pending\"` records. This is called when an authenticated\n * user enters the code shown on the device to approve the authorization.\n *\n * @param userCode - The short, human-readable code the user typed in\n * (e.g. `\"ABCD-1234\"`).\n * @returns The matching pending `DeviceCode` document, or `null` if no\n * pending authorization exists for the given user code.\n *\n * @example\n * ```ts\n * const pending = await ctx.runQuery(\n * components.auth.factors.devices.deviceGetByUserCode,\n * { userCode: \"ABCD-1234\" },\n * );\n * if (pending === null) {\n * throw new Error(\"Invalid or expired user code\");\n * }\n * ```\n */\nexport const deviceGetByUserCode = query({\n args: { userCode: v.string() },\n returns: v.union(vDeviceCodeDoc, v.null()),\n handler: async (ctx, { userCode }) => {\n return await ctx.db\n .query(\"DeviceCode\")\n .withIndex(\"user_code_status\", (q) =>\n q.eq(\"userCode\", userCode).eq(\"status\", \"pending\"),\n )\n .first();\n },\n});\n\n/**\n * Authorize a device code by linking it to a user and session.\n *\n * Transitions the device authorization status from `\"pending\"` to\n * `\"authorized\"` and associates it with the approving user and their\n * active session. After this mutation, the next poll from the device\n * client will succeed and tokens can be issued.\n *\n * @param deviceId - The `_id` of the `DeviceCode` document to authorize.\n * @param userId - The `_id` of the `User` who approved the device request.\n * @param sessionId - The `_id` of the `Session` associated with the\n * approving user's current login.\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * await ctx.runMutation(\n * components.auth.factors.devices.deviceAuthorize,\n * {\n * deviceId: pending._id,\n * userId: currentUser._id,\n * sessionId: currentSession._id,\n * },\n * );\n * ```\n */\nexport const deviceAuthorize = mutation({\n args: {\n deviceId: v.id(\"DeviceCode\"),\n userId: v.id(\"User\"),\n sessionId: v.id(\"Session\"),\n },\n returns: v.null(),\n handler: async (ctx, { deviceId, userId, sessionId }) => {\n await ctx.db.patch(\"DeviceCode\", deviceId, {\n status: \"authorized\",\n userId,\n sessionId,\n });\n return null;\n },\n});\n\n/**\n * Update the last-polled timestamp on a device authorization record.\n *\n * Called each time the device client polls the token endpoint. The\n * timestamp is used to enforce the minimum polling interval and to\n * detect slow-polling violations per RFC 8628.\n *\n * @param deviceId - The `_id` of the `DeviceCode` document to update.\n * @param lastPolledAt - Unix timestamp (in milliseconds) of the most\n * recent poll request from the device client.\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * await ctx.runMutation(\n * components.auth.factors.devices.deviceUpdateLastPolled,\n * {\n * deviceId: deviceCode._id,\n * lastPolledAt: Date.now(),\n * },\n * );\n * ```\n */\nexport const deviceUpdateLastPolled = mutation({\n args: { deviceId: v.id(\"DeviceCode\"), lastPolledAt: v.number() },\n returns: v.null(),\n handler: async (ctx, { deviceId, lastPolledAt }) => {\n await ctx.db.patch(\"DeviceCode\", deviceId, { lastPolledAt });\n return null;\n },\n});\n\n/**\n * Delete a device authorization record from the `DeviceCode` table.\n *\n * Permanently removes the device code entry. This should be called after\n * the device authorization has been successfully exchanged for tokens, or\n * when the authorization has expired and needs to be cleaned up.\n *\n * @param deviceId - The `_id` of the `DeviceCode` document to delete.\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * // Clean up after successful token exchange\n * await ctx.runMutation(\n * components.auth.factors.devices.deviceDelete,\n * { deviceId: deviceCode._id },\n * );\n * ```\n */\nexport const deviceDelete = mutation({\n args: { deviceId: v.id(\"DeviceCode\") },\n returns: v.null(),\n handler: async (ctx, { deviceId }) => {\n await ctx.db.delete(\"DeviceCode\", deviceId);\n return null;\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"devices.js","names":[],"sources":["../../../../src/component/public/factors/devices.ts"],"sourcesContent":["import { v } from \"convex/values\";\n\nimport { mutation, query } from \"../../functions\";\nimport { vDeviceCodeDoc, vDeviceStatus } from \"../../model\";\n\n/**\n * Insert a new device authorization record into the `DeviceCode` table.\n *\n * Creates a pending device authorization entry as part of the OAuth 2.0\n * Device Authorization Grant (RFC 8628). The record tracks the hashed device\n * code, the human-readable user code, expiry, and polling interval.\n *\n * @param deviceCodeHash - SHA-256 hash of the device code issued to the client.\n * Only the hash is stored; the raw code is never persisted.\n * @param userCode - Short, human-readable code displayed to the end-user\n * so they can authorize the device on a separate screen.\n * @param expiresAt - Unix timestamp (in milliseconds) after which the device\n * authorization request is no longer valid.\n * @param interval - Minimum polling interval in seconds that the device client\n * must wait between token requests.\n * @param status - Initial status of the device authorization (e.g. `\"pending\"`).\n * @returns The `_id` of the newly created `DeviceCode` document.\n *\n * @example\n * ```ts\n * const deviceCodeId = await ctx.runMutation(\n * components.auth.factors.devices.deviceInsert,\n * {\n * deviceCodeHash: \"a1b2c3d4e5f6...\",\n * userCode: \"ABCD-1234\",\n * expiresAt: Date.now() + 10 * 60 * 1000,\n * interval: 5,\n * status: \"pending\",\n * },\n * );\n * ```\n */\nexport const deviceInsert = mutation({\n args: {\n deviceCodeHash: v.string(),\n userCode: v.string(),\n expiresAt: v.number(),\n interval: v.number(),\n status: vDeviceStatus,\n },\n returns: v.id(\"DeviceCode\"),\n handler: async (ctx, args) => {\n return await ctx.db.insert(\"DeviceCode\", args);\n },\n});\n\n/**\n * Look up a device authorization record by its hashed device code.\n *\n * Queries the `DeviceCode` table using the `device_code_hash` index.\n * This is the primary lookup used by the token endpoint when a device\n * client polls for authorization status.\n *\n * @param deviceCodeHash - SHA-256 hash of the device code to look up.\n * @returns The matching `DeviceCode` document, or `null` if no record\n * exists for the given hash.\n *\n * @example\n * ```ts\n * const deviceCode = await ctx.runQuery(\n * components.auth.factors.devices.deviceGetByCodeHash,\n * { deviceCodeHash: \"a1b2c3d4e5f6...\" },\n * );\n * if (deviceCode && deviceCode.status === \"authorized\") {\n * // Exchange for tokens\n * }\n * ```\n */\nexport const deviceGetByCodeHash = query({\n args: { deviceCodeHash: v.string() },\n returns: v.union(vDeviceCodeDoc, v.null()),\n handler: async (ctx, { deviceCodeHash }) => {\n return await ctx.db\n .query(\"DeviceCode\")\n .withIndex(\"device_code_hash\", (q) =>\n q.eq(\"deviceCodeHash\", deviceCodeHash),\n )\n .first();\n },\n});\n\n/**\n * Look up a pending device authorization by its user-facing code.\n *\n * Queries the `DeviceCode` table using the `user_code_status` compound index,\n * filtering to only `\"pending\"` records. This is called when an authenticated\n * user enters the code shown on the device to approve the authorization.\n *\n * @param userCode - The short, human-readable code the user typed in\n * (e.g. `\"ABCD-1234\"`).\n * @returns The matching pending `DeviceCode` document, or `null` if no\n * pending authorization exists for the given user code.\n *\n * @example\n * ```ts\n * const pending = await ctx.runQuery(\n * components.auth.factors.devices.deviceGetByUserCode,\n * { userCode: \"ABCD-1234\" },\n * );\n * if (pending === null) {\n * throw new Error(\"Invalid or expired user code\");\n * }\n * ```\n */\nexport const deviceGetByUserCode = query({\n args: { userCode: v.string() },\n returns: v.union(vDeviceCodeDoc, v.null()),\n handler: async (ctx, { userCode }) => {\n return await ctx.db\n .query(\"DeviceCode\")\n .withIndex(\"user_code_status\", (q) =>\n q.eq(\"userCode\", userCode).eq(\"status\", \"pending\"),\n )\n .first();\n },\n});\n\n/**\n * Authorize a device code by linking it to a user and session.\n *\n * Transitions the device authorization status from `\"pending\"` to\n * `\"authorized\"` and associates it with the approving user and their\n * active session. After this mutation, the next poll from the device\n * client will succeed and tokens can be issued.\n *\n * @param deviceId - The `_id` of the `DeviceCode` document to authorize.\n * @param userId - The `_id` of the `User` who approved the device request.\n * @param sessionId - The `_id` of the `Session` associated with the\n * approving user's current login.\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * await ctx.runMutation(\n * components.auth.factors.devices.deviceAuthorize,\n * {\n * deviceId: pending._id,\n * userId: currentUser._id,\n * sessionId: currentSession._id,\n * },\n * );\n * ```\n */\nexport const deviceAuthorize = mutation({\n args: {\n deviceId: v.id(\"DeviceCode\"),\n userId: v.id(\"User\"),\n sessionId: v.id(\"Session\"),\n },\n returns: v.null(),\n handler: async (ctx, { deviceId, userId, sessionId }) => {\n await ctx.db.patch(\"DeviceCode\", deviceId, {\n status: \"authorized\",\n userId,\n sessionId,\n });\n return null;\n },\n});\n\n/**\n * Update the last-polled timestamp on a device authorization record.\n *\n * Called each time the device client polls the token endpoint. The\n * timestamp is used to enforce the minimum polling interval and to\n * detect slow-polling violations per RFC 8628.\n *\n * @param deviceId - The `_id` of the `DeviceCode` document to update.\n * @param lastPolledAt - Unix timestamp (in milliseconds) of the most\n * recent poll request from the device client.\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * await ctx.runMutation(\n * components.auth.factors.devices.deviceUpdateLastPolled,\n * {\n * deviceId: deviceCode._id,\n * lastPolledAt: Date.now(),\n * },\n * );\n * ```\n */\nexport const deviceUpdateLastPolled = mutation({\n args: { deviceId: v.id(\"DeviceCode\"), lastPolledAt: v.number() },\n returns: v.null(),\n handler: async (ctx, { deviceId, lastPolledAt }) => {\n await ctx.db.patch(\"DeviceCode\", deviceId, { lastPolledAt });\n return null;\n },\n});\n\n/**\n * Delete a device authorization record from the `DeviceCode` table.\n *\n * Permanently removes the device code entry. This should be called after\n * the device authorization has been successfully exchanged for tokens, or\n * when the authorization has expired and needs to be cleaned up.\n *\n * @param deviceId - The `_id` of the `DeviceCode` document to delete.\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * // Clean up after successful token exchange\n * await ctx.runMutation(\n * components.auth.factors.devices.deviceDelete,\n * { deviceId: deviceCode._id },\n * );\n * ```\n */\nexport const deviceDelete = mutation({\n args: { deviceId: v.id(\"DeviceCode\") },\n returns: v.null(),\n handler: async (ctx, { deviceId }) => {\n await ctx.db.delete(\"DeviceCode\", deviceId);\n return null;\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,MAAa,eAAe,SAAS;CACnC,MAAM;EACJ,gBAAgB,EAAE,QAAQ;EAC1B,UAAU,EAAE,QAAQ;EACpB,WAAW,EAAE,QAAQ;EACrB,UAAU,EAAE,QAAQ;EACpB,QAAQ;EACT;CACD,SAAS,EAAE,GAAG,aAAa;CAC3B,SAAS,OAAO,KAAK,SAAS;AAC5B,SAAO,MAAM,IAAI,GAAG,OAAO,cAAc,KAAK;;CAEjD,CAAC;;;;;;;;;;;;;;;;;;;;;;;AAwBF,MAAa,sBAAsB,MAAM;CACvC,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE;CACpC,SAAS,EAAE,MAAM,gBAAgB,EAAE,MAAM,CAAC;CAC1C,SAAS,OAAO,KAAK,EAAE,qBAAqB;AAC1C,SAAO,MAAM,IAAI,GACd,MAAM,aAAa,CACnB,UAAU,qBAAqB,MAC9B,EAAE,GAAG,kBAAkB,eAAe,CACvC,CACA,OAAO;;CAEb,CAAC;;;;;;;;;;;;;;;;;;;;;;;;AAyBF,MAAa,sBAAsB,MAAM;CACvC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE;CAC9B,SAAS,EAAE,MAAM,gBAAgB,EAAE,MAAM,CAAC;CAC1C,SAAS,OAAO,KAAK,EAAE,eAAe;AACpC,SAAO,MAAM,IAAI,GACd,MAAM,aAAa,CACnB,UAAU,qBAAqB,MAC9B,EAAE,GAAG,YAAY,SAAS,CAAC,GAAG,UAAU,UAAU,CACnD,CACA,OAAO;;CAEb,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BF,MAAa,kBAAkB,SAAS;CACtC,MAAM;EACJ,UAAU,EAAE,GAAG,aAAa;EAC5B,QAAQ,EAAE,GAAG,OAAO;EACpB,WAAW,EAAE,GAAG,UAAU;EAC3B;CACD,SAAS,EAAE,MAAM;CACjB,SAAS,OAAO,KAAK,EAAE,UAAU,QAAQ,gBAAgB;AACvD,QAAM,IAAI,GAAG,MAAM,cAAc,UAAU;GACzC,QAAQ;GACR;GACA;GACD,CAAC;AACF,SAAO;;CAEV,CAAC;;;;;;;;;;;;;;;;;;;;;;;;AAyBF,MAAa,yBAAyB,SAAS;CAC7C,MAAM;EAAE,UAAU,EAAE,GAAG,aAAa;EAAE,cAAc,EAAE,QAAQ;EAAE;CAChE,SAAS,EAAE,MAAM;CACjB,SAAS,OAAO,KAAK,EAAE,UAAU,mBAAmB;AAClD,QAAM,IAAI,GAAG,MAAM,cAAc,UAAU,EAAE,cAAc,CAAC;AAC5D,SAAO;;CAEV,CAAC;;;;;;;;;;;;;;;;;;;;AAqBF,MAAa,eAAe,SAAS;CACnC,MAAM,EAAE,UAAU,EAAE,GAAG,aAAa,EAAE;CACtC,SAAS,EAAE,MAAM;CACjB,SAAS,OAAO,KAAK,EAAE,eAAe;AACpC,QAAM,IAAI,GAAG,OAAO,cAAc,SAAS;AAC3C,SAAO;;CAEV,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passkeys.d.ts","names":[],"sources":["../../../../src/component/public/factors/passkeys.ts"],"mappings":";;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"passkeys.d.ts","names":[],"sources":["../../../../src/component/public/factors/passkeys.ts"],"mappings":";;;;;;;;;;;;;;AAqDA;;;;;AA0CA;;;;;AAmCA;;;;;AAsCA;;;;;AAoCA;;;;;AA2BA;;;;;;;;;;;;cAlLa,aAAA;;;;;;;;;;;;;;;;;;;;;;;;cA0CA,wBAAA;;;;;;;;;;;;;;;;;;;;;;;;;cAmCA,mBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAsCA,oBAAA;;;;;;;;;;;;;;;;;;;;;;;;cAoCA,iBAAA;;;;;;;;;;;;;;;;;;;cA2BA,aAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passkeys.js","names":[],"sources":["../../../../src/component/public/factors/passkeys.ts"],"sourcesContent":["import { v } from \"convex/values\";\nimport { mutation, query } from \"../../functions\";\nimport { vPasskeyDoc } from \"../../model\";\n\n/**\n * Store a new WebAuthn passkey credential for a user.\n *\n * Persists the public key material and metadata returned by the browser's\n * `navigator.credentials.create()` call after a successful registration\n * ceremony. Each passkey is tied to a single user.\n *\n * @param userId - The `_id` of the `User` who owns this passkey.\n * @param credentialId - Base64url-encoded credential identifier assigned\n * by the authenticator; used to look up the key during authentication.\n * @param publicKey - Raw public key bytes (COSE format) for signature\n * verification.\n * @param algorithm - COSE algorithm identifier (e.g. `-7` for ES256,\n * `-257` for RS256).\n * @param counter - Signature counter reported by the authenticator at\n * registration time; used to detect cloned credentials.\n * @param transports - Optional list of transport hints (e.g.\n * `[\"usb\", \"ble\", \"nfc\", \"internal\"]`) to help the browser select\n * the correct authenticator.\n * @param deviceType - Authenticator attachment type (e.g.\n * `\"singleDevice\"` or `\"multiDevice\"`).\n * @param backedUp - Whether the credential is backed up (synced) by the\n * authenticator platform.\n * @param name - Optional human-readable label for the passkey\n * (e.g. `\"MacBook Pro Touch ID\"`).\n * @param createdAt - Unix timestamp (in milliseconds) when the passkey\n * was registered.\n * @returns The `_id` of the newly created `Passkey` document.\n *\n * @example\n * ```ts\n * const passkeyId = await ctx.runMutation(\n * components.auth.factors.passkeys.passkeyInsert,\n * {\n * userId: user._id,\n * credentialId: \"dGVzdC1jcmVkZW50aWFs\",\n * publicKey: publicKeyBytes,\n * algorithm: -7,\n * counter: 0,\n * transports: [\"internal\"],\n * deviceType: \"multiDevice\",\n * backedUp: true,\n * name: \"MacBook Pro Touch ID\",\n * createdAt: Date.now(),\n * },\n * );\n * ```\n */\nexport const passkeyInsert = mutation({\n args: {\n userId: v.id(\"User\"),\n credentialId: v.string(),\n publicKey: v.bytes(),\n algorithm: v.number(),\n counter: v.number(),\n transports: v.optional(v.array(v.string())),\n deviceType: v.string(),\n backedUp: v.boolean(),\n name: v.optional(v.string()),\n createdAt: v.number(),\n },\n returns: v.id(\"Passkey\"),\n handler: async (ctx, args) => {\n return await ctx.db.insert(\"Passkey\", args);\n },\n});\n\n/**\n * Look up a passkey by its credential ID.\n *\n * Queries the `Passkey` table using the `credential_id` unique index.\n * This is the primary lookup during a WebAuthn authentication ceremony:\n * the authenticator provides a credential ID, and this function retrieves\n * the corresponding public key and counter for signature verification.\n *\n * @param credentialId - Base64url-encoded credential identifier to search for.\n * @returns The matching `Passkey` document, or `null` if no passkey exists\n * with the given credential ID.\n *\n * @example\n * ```ts\n * const passkey = await ctx.runQuery(\n * components.auth.factors.passkeys.passkeyGetByCredentialId,\n * { credentialId: \"dGVzdC1jcmVkZW50aWFs\" },\n * );\n * if (passkey === null) {\n * throw new Error(\"Unknown credential\");\n * }\n * ```\n */\nexport const passkeyGetByCredentialId = query({\n args: { credentialId: v.string() },\n returns: v.union(vPasskeyDoc, v.null()),\n handler: async (ctx, { credentialId }) => {\n return await ctx.db\n .query(\"Passkey\")\n .withIndex(\"credential_id\", (q) => q.eq(\"credentialId\", credentialId))\n .unique();\n },\n});\n\n/**\n * List all passkeys registered to a user.\n *\n * Retrieves every `Passkey` document associated with the given user via\n * the `user_id` index. Useful for displaying a user's registered\n * authenticators in a settings page, or for building the\n * `allowCredentials` list during a WebAuthn authentication ceremony.\n *\n * @param userId - The `_id` of the `User` whose passkeys to retrieve.\n * @returns An array of `Passkey` documents. Returns an empty array if the\n * user has no registered passkeys.\n *\n * @example\n * ```ts\n * const passkeys = await ctx.runQuery(\n * components.auth.factors.passkeys.passkeyListByUserId,\n * { userId: user._id },\n * );\n * // Display each passkey's name and creation date\n * for (const pk of passkeys) {\n * console.log(pk.name, new Date(pk.createdAt));\n * }\n * ```\n */\nexport const passkeyListByUserId = query({\n args: { userId: v.id(\"User\") },\n returns: v.array(vPasskeyDoc),\n handler: async (ctx, { userId }) => {\n return await ctx.db\n .query(\"Passkey\")\n .withIndex(\"user_id\", (q) => q.eq(\"userId\", userId))\n .collect();\n },\n});\n\n/**\n * Update a passkey's signature counter and last-used timestamp after\n * a successful authentication.\n *\n * After verifying a WebAuthn assertion, the relying party must persist\n * the new counter value reported by the authenticator. A counter that\n * does not increase may indicate a cloned credential.\n *\n * @param passkeyId - The `_id` of the `Passkey` document to update.\n * @param counter - The new signature counter value returned by the\n * authenticator in the assertion response.\n * @param lastUsedAt - Unix timestamp (in milliseconds) recording when\n * this passkey was most recently used to authenticate.\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * await ctx.runMutation(\n * components.auth.factors.passkeys.passkeyUpdateCounter,\n * {\n * passkeyId: passkey._id,\n * counter: assertionResponse.counter,\n * lastUsedAt: Date.now(),\n * },\n * );\n * ```\n */\nexport const passkeyUpdateCounter = mutation({\n args: {\n passkeyId: v.id(\"Passkey\"),\n counter: v.number(),\n lastUsedAt: v.number(),\n },\n returns: v.null(),\n handler: async (ctx, { passkeyId, counter, lastUsedAt }) => {\n await ctx.db.patch(\"Passkey\", passkeyId, { counter, lastUsedAt });\n return null;\n },\n});\n\n/**\n * Update a passkey's metadata fields.\n *\n * Performs a partial patch on the `Passkey` document. Typically used to\n * rename a passkey (e.g. from `\"Security Key\"` to `\"YubiKey 5C\"`), but\n * can update any mutable fields via the `data` argument.\n *\n * @param passkeyId - The `_id` of the `Passkey` document to update.\n * @param data - An object containing the fields to patch. Commonly\n * includes `{ name: \"New Label\" }`, but accepts any valid passkey fields.\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * await ctx.runMutation(\n * components.auth.factors.passkeys.passkeyUpdateMeta,\n * {\n * passkeyId: passkey._id,\n * data: { name: \"YubiKey 5C NFC\" },\n * },\n * );\n * ```\n */\nexport const passkeyUpdateMeta = mutation({\n args: { passkeyId: v.id(\"Passkey\"), data: v.any() },\n returns: v.null(),\n handler: async (ctx, { passkeyId, data }) => {\n await ctx.db.patch(\"Passkey\", passkeyId, data);\n return null;\n },\n});\n\n/**\n * Delete a passkey credential from the `Passkey` table.\n *\n * Permanently removes the passkey record. After deletion the credential\n * can no longer be used for authentication. Typically called from a\n * user's security settings when they want to unregister an authenticator.\n *\n * @param passkeyId - The `_id` of the `Passkey` document to delete.\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * await ctx.runMutation(\n * components.auth.factors.passkeys.passkeyDelete,\n * { passkeyId: passkey._id },\n * );\n * ```\n */\nexport const passkeyDelete = mutation({\n args: { passkeyId: v.id(\"Passkey\") },\n returns: v.null(),\n handler: async (ctx, { passkeyId }) => {\n await ctx.db.delete(\"Passkey\", passkeyId);\n return null;\n },\n});\n\n// ============================================================================\n// TOTP Two-Factor Authentication\n// ============================================================================\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"passkeys.js","names":[],"sources":["../../../../src/component/public/factors/passkeys.ts"],"sourcesContent":["import { v } from \"convex/values\";\n\nimport { mutation, query } from \"../../functions\";\nimport { vPasskeyDoc } from \"../../model\";\n\n/**\n * Store a new WebAuthn passkey credential for a user.\n *\n * Persists the public key material and metadata returned by the browser's\n * `navigator.credentials.create()` call after a successful registration\n * ceremony. Each passkey is tied to a single user.\n *\n * @param userId - The `_id` of the `User` who owns this passkey.\n * @param credentialId - Base64url-encoded credential identifier assigned\n * by the authenticator; used to look up the key during authentication.\n * @param publicKey - Raw public key bytes (COSE format) for signature\n * verification.\n * @param algorithm - COSE algorithm identifier (e.g. `-7` for ES256,\n * `-257` for RS256).\n * @param counter - Signature counter reported by the authenticator at\n * registration time; used to detect cloned credentials.\n * @param transports - Optional list of transport hints (e.g.\n * `[\"usb\", \"ble\", \"nfc\", \"internal\"]`) to help the browser select\n * the correct authenticator.\n * @param deviceType - Authenticator attachment type (e.g.\n * `\"singleDevice\"` or `\"multiDevice\"`).\n * @param backedUp - Whether the credential is backed up (synced) by the\n * authenticator platform.\n * @param name - Optional human-readable label for the passkey\n * (e.g. `\"MacBook Pro Touch ID\"`).\n * @param createdAt - Unix timestamp (in milliseconds) when the passkey\n * was registered.\n * @returns The `_id` of the newly created `Passkey` document.\n *\n * @example\n * ```ts\n * const passkeyId = await ctx.runMutation(\n * components.auth.factors.passkeys.passkeyInsert,\n * {\n * userId: user._id,\n * credentialId: \"dGVzdC1jcmVkZW50aWFs\",\n * publicKey: publicKeyBytes,\n * algorithm: -7,\n * counter: 0,\n * transports: [\"internal\"],\n * deviceType: \"multiDevice\",\n * backedUp: true,\n * name: \"MacBook Pro Touch ID\",\n * createdAt: Date.now(),\n * },\n * );\n * ```\n */\nexport const passkeyInsert = mutation({\n args: {\n userId: v.id(\"User\"),\n credentialId: v.string(),\n publicKey: v.bytes(),\n algorithm: v.number(),\n counter: v.number(),\n transports: v.optional(v.array(v.string())),\n deviceType: v.string(),\n backedUp: v.boolean(),\n name: v.optional(v.string()),\n createdAt: v.number(),\n },\n returns: v.id(\"Passkey\"),\n handler: async (ctx, args) => {\n return await ctx.db.insert(\"Passkey\", args);\n },\n});\n\n/**\n * Look up a passkey by its credential ID.\n *\n * Queries the `Passkey` table using the `credential_id` unique index.\n * This is the primary lookup during a WebAuthn authentication ceremony:\n * the authenticator provides a credential ID, and this function retrieves\n * the corresponding public key and counter for signature verification.\n *\n * @param credentialId - Base64url-encoded credential identifier to search for.\n * @returns The matching `Passkey` document, or `null` if no passkey exists\n * with the given credential ID.\n *\n * @example\n * ```ts\n * const passkey = await ctx.runQuery(\n * components.auth.factors.passkeys.passkeyGetByCredentialId,\n * { credentialId: \"dGVzdC1jcmVkZW50aWFs\" },\n * );\n * if (passkey === null) {\n * throw new Error(\"Unknown credential\");\n * }\n * ```\n */\nexport const passkeyGetByCredentialId = query({\n args: { credentialId: v.string() },\n returns: v.union(vPasskeyDoc, v.null()),\n handler: async (ctx, { credentialId }) => {\n return await ctx.db\n .query(\"Passkey\")\n .withIndex(\"credential_id\", (q) => q.eq(\"credentialId\", credentialId))\n .unique();\n },\n});\n\n/**\n * List all passkeys registered to a user.\n *\n * Retrieves every `Passkey` document associated with the given user via\n * the `user_id` index. Useful for displaying a user's registered\n * authenticators in a settings page, or for building the\n * `allowCredentials` list during a WebAuthn authentication ceremony.\n *\n * @param userId - The `_id` of the `User` whose passkeys to retrieve.\n * @returns An array of `Passkey` documents. Returns an empty array if the\n * user has no registered passkeys.\n *\n * @example\n * ```ts\n * const passkeys = await ctx.runQuery(\n * components.auth.factors.passkeys.passkeyListByUserId,\n * { userId: user._id },\n * );\n * // Display each passkey's name and creation date\n * for (const pk of passkeys) {\n * console.log(pk.name, new Date(pk.createdAt));\n * }\n * ```\n */\nexport const passkeyListByUserId = query({\n args: { userId: v.id(\"User\") },\n returns: v.array(vPasskeyDoc),\n handler: async (ctx, { userId }) => {\n return await ctx.db\n .query(\"Passkey\")\n .withIndex(\"user_id\", (q) => q.eq(\"userId\", userId))\n .collect();\n },\n});\n\n/**\n * Update a passkey's signature counter and last-used timestamp after\n * a successful authentication.\n *\n * After verifying a WebAuthn assertion, the relying party must persist\n * the new counter value reported by the authenticator. A counter that\n * does not increase may indicate a cloned credential.\n *\n * @param passkeyId - The `_id` of the `Passkey` document to update.\n * @param counter - The new signature counter value returned by the\n * authenticator in the assertion response.\n * @param lastUsedAt - Unix timestamp (in milliseconds) recording when\n * this passkey was most recently used to authenticate.\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * await ctx.runMutation(\n * components.auth.factors.passkeys.passkeyUpdateCounter,\n * {\n * passkeyId: passkey._id,\n * counter: assertionResponse.counter,\n * lastUsedAt: Date.now(),\n * },\n * );\n * ```\n */\nexport const passkeyUpdateCounter = mutation({\n args: {\n passkeyId: v.id(\"Passkey\"),\n counter: v.number(),\n lastUsedAt: v.number(),\n },\n returns: v.null(),\n handler: async (ctx, { passkeyId, counter, lastUsedAt }) => {\n await ctx.db.patch(\"Passkey\", passkeyId, { counter, lastUsedAt });\n return null;\n },\n});\n\n/**\n * Update a passkey's metadata fields.\n *\n * Performs a partial patch on the `Passkey` document. Typically used to\n * rename a passkey (e.g. from `\"Security Key\"` to `\"YubiKey 5C\"`), but\n * can update any mutable fields via the `data` argument.\n *\n * @param passkeyId - The `_id` of the `Passkey` document to update.\n * @param data - An object containing the fields to patch. Commonly\n * includes `{ name: \"New Label\" }`, but accepts any valid passkey fields.\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * await ctx.runMutation(\n * components.auth.factors.passkeys.passkeyUpdateMeta,\n * {\n * passkeyId: passkey._id,\n * data: { name: \"YubiKey 5C NFC\" },\n * },\n * );\n * ```\n */\nexport const passkeyUpdateMeta = mutation({\n args: { passkeyId: v.id(\"Passkey\"), data: v.any() },\n returns: v.null(),\n handler: async (ctx, { passkeyId, data }) => {\n await ctx.db.patch(\"Passkey\", passkeyId, data);\n return null;\n },\n});\n\n/**\n * Delete a passkey credential from the `Passkey` table.\n *\n * Permanently removes the passkey record. After deletion the credential\n * can no longer be used for authentication. Typically called from a\n * user's security settings when they want to unregister an authenticator.\n *\n * @param passkeyId - The `_id` of the `Passkey` document to delete.\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * await ctx.runMutation(\n * components.auth.factors.passkeys.passkeyDelete,\n * { passkeyId: passkey._id },\n * );\n * ```\n */\nexport const passkeyDelete = mutation({\n args: { passkeyId: v.id(\"Passkey\") },\n returns: v.null(),\n handler: async (ctx, { passkeyId }) => {\n await ctx.db.delete(\"Passkey\", passkeyId);\n return null;\n },\n});\n\n// ============================================================================\n// TOTP Two-Factor Authentication\n// ============================================================================\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqDA,MAAa,gBAAgB,SAAS;CACpC,MAAM;EACJ,QAAQ,EAAE,GAAG,OAAO;EACpB,cAAc,EAAE,QAAQ;EACxB,WAAW,EAAE,OAAO;EACpB,WAAW,EAAE,QAAQ;EACrB,SAAS,EAAE,QAAQ;EACnB,YAAY,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;EAC3C,YAAY,EAAE,QAAQ;EACtB,UAAU,EAAE,SAAS;EACrB,MAAM,EAAE,SAAS,EAAE,QAAQ,CAAC;EAC5B,WAAW,EAAE,QAAQ;EACtB;CACD,SAAS,EAAE,GAAG,UAAU;CACxB,SAAS,OAAO,KAAK,SAAS;AAC5B,SAAO,MAAM,IAAI,GAAG,OAAO,WAAW,KAAK;;CAE9C,CAAC;;;;;;;;;;;;;;;;;;;;;;;;AAyBF,MAAa,2BAA2B,MAAM;CAC5C,MAAM,EAAE,cAAc,EAAE,QAAQ,EAAE;CAClC,SAAS,EAAE,MAAM,aAAa,EAAE,MAAM,CAAC;CACvC,SAAS,OAAO,KAAK,EAAE,mBAAmB;AACxC,SAAO,MAAM,IAAI,GACd,MAAM,UAAU,CAChB,UAAU,kBAAkB,MAAM,EAAE,GAAG,gBAAgB,aAAa,CAAC,CACrE,QAAQ;;CAEd,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;AA0BF,MAAa,sBAAsB,MAAM;CACvC,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,EAAE;CAC9B,SAAS,EAAE,MAAM,YAAY;CAC7B,SAAS,OAAO,KAAK,EAAE,aAAa;AAClC,SAAO,MAAM,IAAI,GACd,MAAM,UAAU,CAChB,UAAU,YAAY,MAAM,EAAE,GAAG,UAAU,OAAO,CAAC,CACnD,SAAS;;CAEf,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BF,MAAa,uBAAuB,SAAS;CAC3C,MAAM;EACJ,WAAW,EAAE,GAAG,UAAU;EAC1B,SAAS,EAAE,QAAQ;EACnB,YAAY,EAAE,QAAQ;EACvB;CACD,SAAS,EAAE,MAAM;CACjB,SAAS,OAAO,KAAK,EAAE,WAAW,SAAS,iBAAiB;AAC1D,QAAM,IAAI,GAAG,MAAM,WAAW,WAAW;GAAE;GAAS;GAAY,CAAC;AACjE,SAAO;;CAEV,CAAC;;;;;;;;;;;;;;;;;;;;;;;;AAyBF,MAAa,oBAAoB,SAAS;CACxC,MAAM;EAAE,WAAW,EAAE,GAAG,UAAU;EAAE,MAAM,EAAE,KAAK;EAAE;CACnD,SAAS,EAAE,MAAM;CACjB,SAAS,OAAO,KAAK,EAAE,WAAW,WAAW;AAC3C,QAAM,IAAI,GAAG,MAAM,WAAW,WAAW,KAAK;AAC9C,SAAO;;CAEV,CAAC;;;;;;;;;;;;;;;;;;;AAoBF,MAAa,gBAAgB,SAAS;CACpC,MAAM,EAAE,WAAW,EAAE,GAAG,UAAU,EAAE;CACpC,SAAS,EAAE,MAAM;CACjB,SAAS,OAAO,KAAK,EAAE,gBAAgB;AACrC,QAAM,IAAI,GAAG,OAAO,WAAW,UAAU;AACzC,SAAO;;CAEV,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"totp.d.ts","names":[],"sources":["../../../../src/component/public/factors/totp.ts"],"mappings":";;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"totp.d.ts","names":[],"sources":["../../../../src/component/public/factors/totp.ts"],"mappings":";;;;;;;;;;;;;;;AA0CA;;;;;AAwCA;;;;;AAoCA;;;;;AAkCA;;;;;AAkCA;;;;;cAhJa,UAAA;;;;;AA6Mb;;;;;;;;;;;;;;;;;;;;cArKa,uBAAA;;;;;;;;;;;;;;;;;;;;;;;;cAoCA,gBAAA;;;;;;;;;;;;;;;;;;;;;;;;cAkCA,WAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;cAkCA,gBAAA;;;;;;;;;;;;;;;;;;;;;;;;cAgCA,kBAAA;;;;;;;;;;;;;;;;;;;;;cA6BA,UAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"totp.js","names":[],"sources":["../../../../src/component/public/factors/totp.ts"],"sourcesContent":["import { v } from \"convex/values\";\nimport { mutation, query } from \"../../functions\";\nimport { vTotpFactorDoc } from \"../../model\";\n\n/**\n * Store a new TOTP (Time-based One-Time Password) enrollment for a user.\n *\n * Creates a `TotpFactor` record containing the shared secret and OTP\n * parameters. The enrollment starts in an unverified state until the\n * user confirms it by submitting a valid code generated from the secret.\n *\n * @param userId - The `_id` of the `User` enrolling in TOTP-based 2FA.\n * @param secret - The shared secret key as raw bytes, typically 20 bytes\n * of cryptographically random data.\n * @param digits - Number of digits in the generated OTP code (usually `6`).\n * @param period - Time step in seconds for code generation (usually `30`).\n * @param verified - Whether the enrollment has been verified. Set to\n * `false` during initial setup; set to `true` after the user submits\n * a valid code.\n * @param name - Optional human-readable label for the TOTP factor\n * (e.g. `\"Google Authenticator\"`).\n * @param createdAt - Unix timestamp (in milliseconds) when the enrollment\n * was created.\n * @returns The `_id` of the newly created `TotpFactor` document.\n *\n * @example\n * ```ts\n * const totpId = await ctx.runMutation(\n * components.auth.factors.totp.totpInsert,\n * {\n * userId: user._id,\n * secret: crypto.getRandomValues(new Uint8Array(20)),\n * digits: 6,\n * period: 30,\n * verified: false,\n * name: \"Authenticator App\",\n * createdAt: Date.now(),\n * },\n * );\n * ```\n */\nexport const totpInsert = mutation({\n args: {\n userId: v.id(\"User\"),\n secret: v.bytes(),\n digits: v.number(),\n period: v.number(),\n verified: v.boolean(),\n name: v.optional(v.string()),\n createdAt: v.number(),\n },\n returns: v.id(\"TotpFactor\"),\n handler: async (ctx, args) => {\n return await ctx.db.insert(\"TotpFactor\", args);\n },\n});\n\n/**\n * Get a verified TOTP enrollment for a user.\n *\n * Queries the `TotpFactor` table using the `user_id_verified` compound\n * index to find the first enrollment that has been successfully verified.\n * This is the primary lookup during a TOTP authentication challenge --\n * only verified enrollments should be used to validate codes.\n *\n * @param userId - The `_id` of the `User` whose verified TOTP enrollment\n * to retrieve.\n * @returns The first verified `TotpFactor` document for the user, or\n * `null` if the user has no verified TOTP enrollment.\n *\n * @example\n * ```ts\n * const totp = await ctx.runQuery(\n * components.auth.factors.totp.totpGetVerifiedByUserId,\n * { userId: user._id },\n * );\n * if (totp === null) {\n * // User does not have TOTP 2FA enabled\n * }\n * ```\n */\nexport const totpGetVerifiedByUserId = query({\n args: { userId: v.id(\"User\") },\n returns: v.union(vTotpFactorDoc, v.null()),\n handler: async (ctx, { userId }) => {\n return await ctx.db\n .query(\"TotpFactor\")\n .withIndex(\"user_id_verified\", (q) =>\n q.eq(\"userId\", userId).eq(\"verified\", true),\n )\n .first();\n },\n});\n\n/**\n * List all TOTP enrollments for a user, both verified and unverified.\n *\n * Retrieves every `TotpFactor` document associated with the given user\n * via the `user_id` index. Useful for displaying enrolled authenticator\n * apps in a security settings page, including pending (unverified)\n * enrollments that the user has not yet confirmed.\n *\n * @param userId - The `_id` of the `User` whose TOTP enrollments to\n * retrieve.\n * @returns An array of `TotpFactor` documents. Returns an empty array if\n * the user has no TOTP enrollments.\n *\n * @example\n * ```ts\n * const factors = await ctx.runQuery(\n * components.auth.factors.totp.totpListByUserId,\n * { userId: user._id },\n * );\n * const verified = factors.filter((f) => f.verified);\n * const pending = factors.filter((f) => !f.verified);\n * ```\n */\nexport const totpListByUserId = query({\n args: { userId: v.id(\"User\") },\n returns: v.array(vTotpFactorDoc),\n handler: async (ctx, { userId }) => {\n return await ctx.db\n .query(\"TotpFactor\")\n .withIndex(\"user_id\", (q) => q.eq(\"userId\", userId))\n .collect();\n },\n});\n\n/**\n * Get a single TOTP enrollment by its document ID.\n *\n * Performs a direct document lookup on the `TotpFactor` table. This is\n * used when you already have the enrollment's `_id` (e.g. from a\n * previous list query) and need to fetch its full details, including\n * the secret and verification status.\n *\n * @param totpId - The `_id` of the `TotpFactor` document to retrieve.\n * @returns The `TotpFactor` document, or `null` if no enrollment exists\n * with the given ID.\n *\n * @example\n * ```ts\n * const totp = await ctx.runQuery(\n * components.auth.factors.totp.totpGetById,\n * { totpId: enrollmentId },\n * );\n * if (totp !== null && !totp.verified) {\n * // Enrollment is still pending confirmation\n * }\n * ```\n */\nexport const totpGetById = query({\n args: { totpId: v.id(\"TotpFactor\") },\n returns: v.union(vTotpFactorDoc, v.null()),\n handler: async (ctx, { totpId }) => {\n return await ctx.db.get(\"TotpFactor\", totpId);\n },\n});\n\n/**\n * Mark a TOTP enrollment as verified, completing the setup process.\n *\n * Called after the user successfully submits a valid TOTP code during\n * enrollment. This transitions the factor from a pending state to an\n * active, verified state, enabling it for future authentication\n * challenges.\n *\n * @param totpId - The `_id` of the `TotpFactor` document to mark as\n * verified.\n * @param lastUsedAt - Unix timestamp (in milliseconds) recording when\n * the verification code was successfully validated.\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * // After validating the user's TOTP code during setup\n * await ctx.runMutation(\n * components.auth.factors.totp.totpMarkVerified,\n * {\n * totpId: enrollment._id,\n * lastUsedAt: Date.now(),\n * },\n * );\n * ```\n */\nexport const totpMarkVerified = mutation({\n args: { totpId: v.id(\"TotpFactor\"), lastUsedAt: v.number() },\n returns: v.null(),\n handler: async (ctx, { totpId, lastUsedAt }) => {\n await ctx.db.patch(\"TotpFactor\", totpId, { verified: true, lastUsedAt });\n return null;\n },\n});\n\n/**\n * Update a TOTP enrollment's last-used timestamp.\n *\n * Called after each successful TOTP code validation during sign-in.\n * Tracking the last-used time helps detect stale enrollments and can\n * be surfaced in security settings for user awareness.\n *\n * @param totpId - The `_id` of the `TotpFactor` document to update.\n * @param lastUsedAt - Unix timestamp (in milliseconds) recording when\n * the TOTP code was most recently validated.\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * await ctx.runMutation(\n * components.auth.factors.totp.totpUpdateLastUsed,\n * {\n * totpId: totp._id,\n * lastUsedAt: Date.now(),\n * },\n * );\n * ```\n */\nexport const totpUpdateLastUsed = mutation({\n args: { totpId: v.id(\"TotpFactor\"), lastUsedAt: v.number() },\n returns: v.null(),\n handler: async (ctx, { totpId, lastUsedAt }) => {\n await ctx.db.patch(\"TotpFactor\", totpId, { lastUsedAt });\n return null;\n },\n});\n\n/**\n * Delete a TOTP enrollment from the `TotpFactor` table.\n *\n * Permanently removes the TOTP factor record, including its shared\n * secret. After deletion the user can no longer use this factor for\n * two-factor authentication. Typically called when a user disables\n * TOTP 2FA or wants to re-enroll with a new secret.\n *\n * @param totpId - The `_id` of the `TotpFactor` document to delete.\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * // User disables TOTP 2FA\n * await ctx.runMutation(\n * components.auth.factors.totp.totpDelete,\n * { totpId: totp._id },\n * );\n * ```\n */\nexport const totpDelete = mutation({\n args: { totpId: v.id(\"TotpFactor\") },\n returns: v.null(),\n handler: async (ctx, { totpId }) => {\n await ctx.db.delete(\"TotpFactor\", totpId);\n return null;\n },\n});\n\n// ============================================================================\n// Rate Limits\n// ============================================================================\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,MAAa,aAAa,SAAS;CACjC,MAAM;EACJ,QAAQ,EAAE,GAAG,OAAO;EACpB,QAAQ,EAAE,OAAO;EACjB,QAAQ,EAAE,QAAQ;EAClB,QAAQ,EAAE,QAAQ;EAClB,UAAU,EAAE,SAAS;EACrB,MAAM,EAAE,SAAS,EAAE,QAAQ,CAAC;EAC5B,WAAW,EAAE,QAAQ;EACtB;CACD,SAAS,EAAE,GAAG,aAAa;CAC3B,SAAS,OAAO,KAAK,SAAS;AAC5B,SAAO,MAAM,IAAI,GAAG,OAAO,cAAc,KAAK;;CAEjD,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;AA0BF,MAAa,0BAA0B,MAAM;CAC3C,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,EAAE;CAC9B,SAAS,EAAE,MAAM,gBAAgB,EAAE,MAAM,CAAC;CAC1C,SAAS,OAAO,KAAK,EAAE,aAAa;AAClC,SAAO,MAAM,IAAI,GACd,MAAM,aAAa,CACnB,UAAU,qBAAqB,MAC9B,EAAE,GAAG,UAAU,OAAO,CAAC,GAAG,YAAY,KAAK,CAC5C,CACA,OAAO;;CAEb,CAAC;;;;;;;;;;;;;;;;;;;;;;;;AAyBF,MAAa,mBAAmB,MAAM;CACpC,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,EAAE;CAC9B,SAAS,EAAE,MAAM,eAAe;CAChC,SAAS,OAAO,KAAK,EAAE,aAAa;AAClC,SAAO,MAAM,IAAI,GACd,MAAM,aAAa,CACnB,UAAU,YAAY,MAAM,EAAE,GAAG,UAAU,OAAO,CAAC,CACnD,SAAS;;CAEf,CAAC;;;;;;;;;;;;;;;;;;;;;;;;AAyBF,MAAa,cAAc,MAAM;CAC/B,MAAM,EAAE,QAAQ,EAAE,GAAG,aAAa,EAAE;CACpC,SAAS,EAAE,MAAM,gBAAgB,EAAE,MAAM,CAAC;CAC1C,SAAS,OAAO,KAAK,EAAE,aAAa;AAClC,SAAO,MAAM,IAAI,GAAG,IAAI,cAAc,OAAO;;CAEhD,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BF,MAAa,mBAAmB,SAAS;CACvC,MAAM;EAAE,QAAQ,EAAE,GAAG,aAAa;EAAE,YAAY,EAAE,QAAQ;EAAE;CAC5D,SAAS,EAAE,MAAM;CACjB,SAAS,OAAO,KAAK,EAAE,QAAQ,iBAAiB;AAC9C,QAAM,IAAI,GAAG,MAAM,cAAc,QAAQ;GAAE,UAAU;GAAM;GAAY,CAAC;AACxE,SAAO;;CAEV,CAAC;;;;;;;;;;;;;;;;;;;;;;;;AAyBF,MAAa,qBAAqB,SAAS;CACzC,MAAM;EAAE,QAAQ,EAAE,GAAG,aAAa;EAAE,YAAY,EAAE,QAAQ;EAAE;CAC5D,SAAS,EAAE,MAAM;CACjB,SAAS,OAAO,KAAK,EAAE,QAAQ,iBAAiB;AAC9C,QAAM,IAAI,GAAG,MAAM,cAAc,QAAQ,EAAE,YAAY,CAAC;AACxD,SAAO;;CAEV,CAAC;;;;;;;;;;;;;;;;;;;;;AAsBF,MAAa,aAAa,SAAS;CACjC,MAAM,EAAE,QAAQ,EAAE,GAAG,aAAa,EAAE;CACpC,SAAS,EAAE,MAAM;CACjB,SAAS,OAAO,KAAK,EAAE,aAAa;AAClC,QAAM,IAAI,GAAG,OAAO,cAAc,OAAO;AACzC,SAAO;;CAEV,CAAC"}
|
|
1
|
+
{"version":3,"file":"totp.js","names":[],"sources":["../../../../src/component/public/factors/totp.ts"],"sourcesContent":["import { v } from \"convex/values\";\n\nimport { mutation, query } from \"../../functions\";\nimport { vTotpFactorDoc } from \"../../model\";\n\n/**\n * Store a new TOTP (Time-based One-Time Password) enrollment for a user.\n *\n * Creates a `TotpFactor` record containing the shared secret and OTP\n * parameters. The enrollment starts in an unverified state until the\n * user confirms it by submitting a valid code generated from the secret.\n *\n * @param userId - The `_id` of the `User` enrolling in TOTP-based 2FA.\n * @param secret - The shared secret key as raw bytes, typically 20 bytes\n * of cryptographically random data.\n * @param digits - Number of digits in the generated OTP code (usually `6`).\n * @param period - Time step in seconds for code generation (usually `30`).\n * @param verified - Whether the enrollment has been verified. Set to\n * `false` during initial setup; set to `true` after the user submits\n * a valid code.\n * @param name - Optional human-readable label for the TOTP factor\n * (e.g. `\"Google Authenticator\"`).\n * @param createdAt - Unix timestamp (in milliseconds) when the enrollment\n * was created.\n * @returns The `_id` of the newly created `TotpFactor` document.\n *\n * @example\n * ```ts\n * const totpId = await ctx.runMutation(\n * components.auth.factors.totp.totpInsert,\n * {\n * userId: user._id,\n * secret: crypto.getRandomValues(new Uint8Array(20)),\n * digits: 6,\n * period: 30,\n * verified: false,\n * name: \"Authenticator App\",\n * createdAt: Date.now(),\n * },\n * );\n * ```\n */\nexport const totpInsert = mutation({\n args: {\n userId: v.id(\"User\"),\n secret: v.bytes(),\n digits: v.number(),\n period: v.number(),\n verified: v.boolean(),\n name: v.optional(v.string()),\n createdAt: v.number(),\n },\n returns: v.id(\"TotpFactor\"),\n handler: async (ctx, args) => {\n return await ctx.db.insert(\"TotpFactor\", args);\n },\n});\n\n/**\n * Get a verified TOTP enrollment for a user.\n *\n * Queries the `TotpFactor` table using the `user_id_verified` compound\n * index to find the first enrollment that has been successfully verified.\n * This is the primary lookup during a TOTP authentication challenge --\n * only verified enrollments should be used to validate codes.\n *\n * @param userId - The `_id` of the `User` whose verified TOTP enrollment\n * to retrieve.\n * @returns The first verified `TotpFactor` document for the user, or\n * `null` if the user has no verified TOTP enrollment.\n *\n * @example\n * ```ts\n * const totp = await ctx.runQuery(\n * components.auth.factors.totp.totpGetVerifiedByUserId,\n * { userId: user._id },\n * );\n * if (totp === null) {\n * // User does not have TOTP 2FA enabled\n * }\n * ```\n */\nexport const totpGetVerifiedByUserId = query({\n args: { userId: v.id(\"User\") },\n returns: v.union(vTotpFactorDoc, v.null()),\n handler: async (ctx, { userId }) => {\n return await ctx.db\n .query(\"TotpFactor\")\n .withIndex(\"user_id_verified\", (q) =>\n q.eq(\"userId\", userId).eq(\"verified\", true),\n )\n .first();\n },\n});\n\n/**\n * List all TOTP enrollments for a user, both verified and unverified.\n *\n * Retrieves every `TotpFactor` document associated with the given user\n * via the `user_id` index. Useful for displaying enrolled authenticator\n * apps in a security settings page, including pending (unverified)\n * enrollments that the user has not yet confirmed.\n *\n * @param userId - The `_id` of the `User` whose TOTP enrollments to\n * retrieve.\n * @returns An array of `TotpFactor` documents. Returns an empty array if\n * the user has no TOTP enrollments.\n *\n * @example\n * ```ts\n * const factors = await ctx.runQuery(\n * components.auth.factors.totp.totpListByUserId,\n * { userId: user._id },\n * );\n * const verified = factors.filter((f) => f.verified);\n * const pending = factors.filter((f) => !f.verified);\n * ```\n */\nexport const totpListByUserId = query({\n args: { userId: v.id(\"User\") },\n returns: v.array(vTotpFactorDoc),\n handler: async (ctx, { userId }) => {\n return await ctx.db\n .query(\"TotpFactor\")\n .withIndex(\"user_id\", (q) => q.eq(\"userId\", userId))\n .collect();\n },\n});\n\n/**\n * Get a single TOTP enrollment by its document ID.\n *\n * Performs a direct document lookup on the `TotpFactor` table. This is\n * used when you already have the enrollment's `_id` (e.g. from a\n * previous list query) and need to fetch its full details, including\n * the secret and verification status.\n *\n * @param totpId - The `_id` of the `TotpFactor` document to retrieve.\n * @returns The `TotpFactor` document, or `null` if no enrollment exists\n * with the given ID.\n *\n * @example\n * ```ts\n * const totp = await ctx.runQuery(\n * components.auth.factors.totp.totpGetById,\n * { totpId: enrollmentId },\n * );\n * if (totp !== null && !totp.verified) {\n * // Enrollment is still pending confirmation\n * }\n * ```\n */\nexport const totpGetById = query({\n args: { totpId: v.id(\"TotpFactor\") },\n returns: v.union(vTotpFactorDoc, v.null()),\n handler: async (ctx, { totpId }) => {\n return await ctx.db.get(\"TotpFactor\", totpId);\n },\n});\n\n/**\n * Mark a TOTP enrollment as verified, completing the setup process.\n *\n * Called after the user successfully submits a valid TOTP code during\n * enrollment. This transitions the factor from a pending state to an\n * active, verified state, enabling it for future authentication\n * challenges.\n *\n * @param totpId - The `_id` of the `TotpFactor` document to mark as\n * verified.\n * @param lastUsedAt - Unix timestamp (in milliseconds) recording when\n * the verification code was successfully validated.\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * // After validating the user's TOTP code during setup\n * await ctx.runMutation(\n * components.auth.factors.totp.totpMarkVerified,\n * {\n * totpId: enrollment._id,\n * lastUsedAt: Date.now(),\n * },\n * );\n * ```\n */\nexport const totpMarkVerified = mutation({\n args: { totpId: v.id(\"TotpFactor\"), lastUsedAt: v.number() },\n returns: v.null(),\n handler: async (ctx, { totpId, lastUsedAt }) => {\n await ctx.db.patch(\"TotpFactor\", totpId, { verified: true, lastUsedAt });\n return null;\n },\n});\n\n/**\n * Update a TOTP enrollment's last-used timestamp.\n *\n * Called after each successful TOTP code validation during sign-in.\n * Tracking the last-used time helps detect stale enrollments and can\n * be surfaced in security settings for user awareness.\n *\n * @param totpId - The `_id` of the `TotpFactor` document to update.\n * @param lastUsedAt - Unix timestamp (in milliseconds) recording when\n * the TOTP code was most recently validated.\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * await ctx.runMutation(\n * components.auth.factors.totp.totpUpdateLastUsed,\n * {\n * totpId: totp._id,\n * lastUsedAt: Date.now(),\n * },\n * );\n * ```\n */\nexport const totpUpdateLastUsed = mutation({\n args: { totpId: v.id(\"TotpFactor\"), lastUsedAt: v.number() },\n returns: v.null(),\n handler: async (ctx, { totpId, lastUsedAt }) => {\n await ctx.db.patch(\"TotpFactor\", totpId, { lastUsedAt });\n return null;\n },\n});\n\n/**\n * Delete a TOTP enrollment from the `TotpFactor` table.\n *\n * Permanently removes the TOTP factor record, including its shared\n * secret. After deletion the user can no longer use this factor for\n * two-factor authentication. Typically called when a user disables\n * TOTP 2FA or wants to re-enroll with a new secret.\n *\n * @param totpId - The `_id` of the `TotpFactor` document to delete.\n * @returns `null` on success.\n *\n * @example\n * ```ts\n * // User disables TOTP 2FA\n * await ctx.runMutation(\n * components.auth.factors.totp.totpDelete,\n * { totpId: totp._id },\n * );\n * ```\n */\nexport const totpDelete = mutation({\n args: { totpId: v.id(\"TotpFactor\") },\n returns: v.null(),\n handler: async (ctx, { totpId }) => {\n await ctx.db.delete(\"TotpFactor\", totpId);\n return null;\n },\n});\n\n// ============================================================================\n// Rate Limits\n// ============================================================================\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,MAAa,aAAa,SAAS;CACjC,MAAM;EACJ,QAAQ,EAAE,GAAG,OAAO;EACpB,QAAQ,EAAE,OAAO;EACjB,QAAQ,EAAE,QAAQ;EAClB,QAAQ,EAAE,QAAQ;EAClB,UAAU,EAAE,SAAS;EACrB,MAAM,EAAE,SAAS,EAAE,QAAQ,CAAC;EAC5B,WAAW,EAAE,QAAQ;EACtB;CACD,SAAS,EAAE,GAAG,aAAa;CAC3B,SAAS,OAAO,KAAK,SAAS;AAC5B,SAAO,MAAM,IAAI,GAAG,OAAO,cAAc,KAAK;;CAEjD,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;AA0BF,MAAa,0BAA0B,MAAM;CAC3C,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,EAAE;CAC9B,SAAS,EAAE,MAAM,gBAAgB,EAAE,MAAM,CAAC;CAC1C,SAAS,OAAO,KAAK,EAAE,aAAa;AAClC,SAAO,MAAM,IAAI,GACd,MAAM,aAAa,CACnB,UAAU,qBAAqB,MAC9B,EAAE,GAAG,UAAU,OAAO,CAAC,GAAG,YAAY,KAAK,CAC5C,CACA,OAAO;;CAEb,CAAC;;;;;;;;;;;;;;;;;;;;;;;;AAyBF,MAAa,mBAAmB,MAAM;CACpC,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,EAAE;CAC9B,SAAS,EAAE,MAAM,eAAe;CAChC,SAAS,OAAO,KAAK,EAAE,aAAa;AAClC,SAAO,MAAM,IAAI,GACd,MAAM,aAAa,CACnB,UAAU,YAAY,MAAM,EAAE,GAAG,UAAU,OAAO,CAAC,CACnD,SAAS;;CAEf,CAAC;;;;;;;;;;;;;;;;;;;;;;;;AAyBF,MAAa,cAAc,MAAM;CAC/B,MAAM,EAAE,QAAQ,EAAE,GAAG,aAAa,EAAE;CACpC,SAAS,EAAE,MAAM,gBAAgB,EAAE,MAAM,CAAC;CAC1C,SAAS,OAAO,KAAK,EAAE,aAAa;AAClC,SAAO,MAAM,IAAI,GAAG,IAAI,cAAc,OAAO;;CAEhD,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BF,MAAa,mBAAmB,SAAS;CACvC,MAAM;EAAE,QAAQ,EAAE,GAAG,aAAa;EAAE,YAAY,EAAE,QAAQ;EAAE;CAC5D,SAAS,EAAE,MAAM;CACjB,SAAS,OAAO,KAAK,EAAE,QAAQ,iBAAiB;AAC9C,QAAM,IAAI,GAAG,MAAM,cAAc,QAAQ;GAAE,UAAU;GAAM;GAAY,CAAC;AACxE,SAAO;;CAEV,CAAC;;;;;;;;;;;;;;;;;;;;;;;;AAyBF,MAAa,qBAAqB,SAAS;CACzC,MAAM;EAAE,QAAQ,EAAE,GAAG,aAAa;EAAE,YAAY,EAAE,QAAQ;EAAE;CAC5D,SAAS,EAAE,MAAM;CACjB,SAAS,OAAO,KAAK,EAAE,QAAQ,iBAAiB;AAC9C,QAAM,IAAI,GAAG,MAAM,cAAc,QAAQ,EAAE,YAAY,CAAC;AACxD,SAAO;;CAEV,CAAC;;;;;;;;;;;;;;;;;;;;;AAsBF,MAAa,aAAa,SAAS;CACjC,MAAM,EAAE,QAAQ,EAAE,GAAG,aAAa,EAAE;CACpC,SAAS,EAAE,MAAM;CACjB,SAAS,OAAO,KAAK,EAAE,aAAa;AAClC,QAAM,IAAI,GAAG,OAAO,cAAc,OAAO;AACzC,SAAO;;CAEV,CAAC"}
|