@robelest/convex-auth 0.0.4-preview.21 → 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/convex.config.d.ts +2 -2
- package/dist/component/convex.config.d.ts.map +1 -1
- package/dist/component/model.d.ts +5 -5
- 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 +39 -39
- package/dist/component/server/auth.d.ts +95 -52
- package/dist/component/server/auth.d.ts.map +1 -1
- package/dist/component/server/auth.js +63 -43
- 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 +3 -3
- 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 +95 -52
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +63 -43
- 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 +26 -64
- 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 +13 -13
- 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 +12 -10
- 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 +97 -97
- package/dist/server/mutations/store.d.ts.map +1 -1
- 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 +10 -10
- 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 +14 -14
- 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 +2 -6
- package/src/authorization/index.ts +1 -1
- package/src/cli/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 -0
- 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 +177 -111
- 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 +2 -0
- 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
package/dist/server/users.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"users.js","names":[],"sources":["../../src/server/users.ts"],"sourcesContent":["import { Fx } from \"@robelest/fx\";\nimport { GenericId } from \"convex/values\";\n\nimport { authDb } from \"./db\";\nimport { AuthError } from \"./authError\";\nimport { Doc, MutationCtx } from \"./types\";\nimport { AuthProviderMaterializedConfig, ConvexAuthConfig } from \"./types\";\nimport { LOG_LEVELS, logWithLevel } from \"./utils\";\n\ntype CreateOrUpdateUserArgs = {\n type: \"oauth\" | \"credentials\" | \"email\" | \"phone\" | \"verification\";\n provider: AuthProviderMaterializedConfig;\n profile: Record<string, unknown> & {\n email?: string;\n phone?: string;\n emailVerified?: boolean;\n phoneVerified?: boolean;\n };\n accountExtend?: Record<string, unknown>;\n shouldLinkViaEmail?: boolean;\n shouldLinkViaPhone?: boolean;\n};\n\nfunction mergeExtend(\n existing: unknown,\n incoming: Record<string, unknown> | undefined,\n) {\n if (!incoming) {\n return undefined;\n }\n const existingRecord =\n typeof existing === \"object\" &&\n existing !== null &&\n !Array.isArray(existing)\n ? (existing as Record<string, unknown>)\n : undefined;\n return existingRecord ? { ...existingRecord, ...incoming } : incoming;\n}\n\n/** @internal */\nexport async function upsertUserAndAccount(\n ctx: MutationCtx,\n sessionId: GenericId<\"Session\"> | null,\n account:\n | { existingAccount: Doc<\"Account\"> }\n | {\n providerAccountId: string;\n secret?: string;\n },\n args: CreateOrUpdateUserArgs,\n config: ConvexAuthConfig,\n opts?: { existingUserId?: GenericId<\"User\"> },\n): Promise<{\n userId: GenericId<\"User\">;\n accountId: GenericId<\"Account\">;\n}> {\n const userId = await defaultCreateOrUpdateUser(\n ctx,\n sessionId,\n \"existingAccount\" in account ? account.existingAccount : null,\n args,\n config,\n opts?.existingUserId ?? null,\n );\n const accountId = await createOrUpdateAccount(\n ctx,\n userId,\n account,\n args,\n config,\n );\n return { userId, accountId };\n}\n\nasync function defaultCreateOrUpdateUser(\n ctx: MutationCtx,\n existingSessionId: GenericId<\"Session\"> | null,\n existingAccount: Doc<\"Account\"> | null,\n args: CreateOrUpdateUserArgs,\n config: ConvexAuthConfig,\n existingUserIdOverride: GenericId<\"User\"> | null,\n) {\n logWithLevel(LOG_LEVELS.DEBUG, \"defaultCreateOrUpdateUser args:\", {\n existingAccountId: existingAccount?._id,\n existingSessionId,\n args,\n });\n const existingUserId = existingAccount?.userId ?? null;\n const db = authDb(ctx, config);\n if (config.callbacks?.createOrUpdateUser !== undefined) {\n logWithLevel(LOG_LEVELS.DEBUG, \"Using custom createOrUpdateUser callback\");\n return await config.callbacks.createOrUpdateUser(ctx, {\n existingUserId,\n ...args,\n });\n }\n\n const {\n provider,\n profile: {\n id: _profileId,\n emailVerified: profileEmailVerified,\n phoneVerified: profilePhoneVerified,\n ...profile\n },\n } = args;\n const emailVerified =\n profileEmailVerified ??\n (provider.type === \"oauth\" && provider.accountLinking !== \"none\");\n const phoneVerified = profilePhoneVerified ?? false;\n const shouldLinkViaEmail =\n args.shouldLinkViaEmail || emailVerified || provider.type === \"email\";\n const shouldLinkViaPhone =\n args.shouldLinkViaPhone || phoneVerified || provider.type === \"phone\";\n\n let userId = existingUserId ?? existingUserIdOverride;\n if (existingUserId === null) {\n const existingUserWithVerifiedEmailId =\n typeof profile.email === \"string\" && shouldLinkViaEmail\n ? ((await uniqueUserWithVerifiedEmail(ctx, profile.email, config))\n ?._id ?? null)\n : null;\n\n const existingUserWithVerifiedPhoneId =\n typeof profile.phone === \"string\" && shouldLinkViaPhone\n ? ((await uniqueUserWithVerifiedPhone(ctx, profile.phone, config))\n ?._id ?? null)\n : null;\n const linkDispatch = {\n tag:\n existingUserWithVerifiedEmailId !== null &&\n existingUserWithVerifiedPhoneId !== null\n ? \"both\"\n : existingUserWithVerifiedEmailId !== null\n ? \"email\"\n : existingUserWithVerifiedPhoneId !== null\n ? \"phone\"\n : \"none\",\n existingUserWithVerifiedEmailId,\n existingUserWithVerifiedPhoneId,\n } as const;\n\n const linkHandlers = {\n both: () =>\n Fx.sync(() => {\n logWithLevel(\n LOG_LEVELS.DEBUG,\n `Found existing email and phone verified users, so not linking: email: ${linkDispatch.existingUserWithVerifiedEmailId}, phone: ${linkDispatch.existingUserWithVerifiedPhoneId}`,\n );\n return null;\n }),\n email: () =>\n Fx.sync(() => {\n logWithLevel(\n LOG_LEVELS.DEBUG,\n `Found existing email verified user, linking: ${linkDispatch.existingUserWithVerifiedEmailId}`,\n );\n return linkDispatch.existingUserWithVerifiedEmailId;\n }),\n phone: () =>\n Fx.sync(() => {\n logWithLevel(\n LOG_LEVELS.DEBUG,\n `Found existing phone verified user, linking: ${linkDispatch.existingUserWithVerifiedPhoneId}`,\n );\n return linkDispatch.existingUserWithVerifiedPhoneId;\n }),\n none: () =>\n Fx.sync(() => {\n logWithLevel(\n LOG_LEVELS.DEBUG,\n \"No existing verified users found, creating new user\",\n );\n return null;\n }),\n } as const;\n\n userId = await Fx.run(linkHandlers[linkDispatch.tag]());\n }\n const userData = {\n ...(emailVerified ? { emailVerificationTime: Date.now() } : null),\n ...(phoneVerified ? { phoneVerificationTime: Date.now() } : null),\n ...profile,\n };\n const existingOrLinkedUserId = userId;\n if (userId !== null) {\n await Fx.run(\n Fx.from({\n ok: () => db.users.patch(userId!, userData),\n err: (error) =>\n new AuthError(\n \"USER_UPDATE_FAILED\",\n `Could not update user document with ID \\`${userId}\\`, ` +\n `either the user has been deleted but their account has not, ` +\n `or the profile data doesn't match the \\`users\\` table schema: ` +\n `${(error as Error).message}`,\n ),\n }).pipe(Fx.recover((e) => Fx.fatal(e.toConvexError()))),\n );\n } else {\n userId = (await db.users.insert(userData)) as GenericId<\"User\">;\n }\n const afterUserCreatedOrUpdated = config.callbacks?.afterUserCreatedOrUpdated;\n if (afterUserCreatedOrUpdated !== undefined) {\n logWithLevel(\n LOG_LEVELS.DEBUG,\n \"Calling custom afterUserCreatedOrUpdated callback\",\n );\n await afterUserCreatedOrUpdated(ctx, {\n userId,\n existingUserId: existingOrLinkedUserId,\n ...args,\n });\n } else {\n logWithLevel(\n LOG_LEVELS.DEBUG,\n \"No custom afterUserCreatedOrUpdated callback, skipping\",\n );\n }\n return userId;\n}\n\nasync function uniqueUserWithVerifiedEmail(\n ctx: MutationCtx,\n email: string,\n config: ConvexAuthConfig,\n) {\n const db = authDb(ctx, config);\n return (await db.users.findByVerifiedEmail(email)) as Doc<\"User\"> | null;\n}\n\nasync function uniqueUserWithVerifiedPhone(\n ctx: MutationCtx,\n phone: string,\n config: ConvexAuthConfig,\n) {\n const db = authDb(ctx, config);\n return (await db.users.findByVerifiedPhone(phone)) as Doc<\"User\"> | null;\n}\n\nasync function createOrUpdateAccount(\n ctx: MutationCtx,\n userId: GenericId<\"User\">,\n account:\n | { existingAccount: Doc<\"Account\"> }\n | {\n providerAccountId: string;\n secret?: string;\n },\n args: CreateOrUpdateUserArgs,\n config: ConvexAuthConfig,\n) {\n const db = authDb(ctx, config);\n const mergedExtend =\n \"existingAccount\" in account\n ? mergeExtend(account.existingAccount.extend, args.accountExtend)\n : args.accountExtend;\n const accountId =\n \"existingAccount\" in account\n ? account.existingAccount._id\n : ((await db.accounts.create({\n userId,\n provider: args.provider.id,\n providerAccountId: account.providerAccountId,\n secret: account.secret,\n extend: mergedExtend,\n })) as GenericId<\"Account\">);\n // This is never used with the default `createOrUpdateUser` implementation,\n // but it is used for manual linking via custom `createOrUpdateUser`:\n if (\n \"existingAccount\" in account &&\n account.existingAccount.userId !== userId\n ) {\n await db.accounts.patch(accountId, { userId });\n }\n const accountPatchData: Record<string, unknown> = {};\n if (mergedExtend) {\n accountPatchData.extend = mergedExtend;\n }\n if (args.profile.emailVerified) {\n accountPatchData.emailVerified = args.profile.email;\n }\n if (args.profile.phoneVerified) {\n accountPatchData.phoneVerified = args.profile.phone;\n }\n if (Object.keys(accountPatchData).length > 0) {\n await db.accounts.patch(accountId, accountPatchData);\n }\n return accountId;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"users.js","names":[],"sources":["../../src/server/users.ts"],"sourcesContent":["import { Fx } from \"@robelest/fx\";\nimport { Cv } from \"@robelest/fx/convex\";\nimport { GenericId } from \"convex/values\";\n\nimport { authDb } from \"./db\";\nimport { Doc, MutationCtx } from \"./types\";\nimport { AuthProviderMaterializedConfig, ConvexAuthConfig } from \"./types\";\nimport { LOG_LEVELS, logWithLevel } from \"./utils\";\n\ntype CreateOrUpdateUserArgs = {\n type: \"oauth\" | \"credentials\" | \"email\" | \"phone\" | \"verification\";\n provider: AuthProviderMaterializedConfig;\n profile: Record<string, unknown> & {\n email?: string;\n phone?: string;\n emailVerified?: boolean;\n phoneVerified?: boolean;\n };\n accountExtend?: Record<string, unknown>;\n shouldLinkViaEmail?: boolean;\n shouldLinkViaPhone?: boolean;\n};\n\nfunction mergeExtend(\n existing: unknown,\n incoming: Record<string, unknown> | undefined,\n) {\n if (!incoming) {\n return undefined;\n }\n const existingRecord =\n typeof existing === \"object\" &&\n existing !== null &&\n !Array.isArray(existing)\n ? (existing as Record<string, unknown>)\n : undefined;\n return existingRecord ? { ...existingRecord, ...incoming } : incoming;\n}\n\n/** @internal */\nexport async function upsertUserAndAccount(\n ctx: MutationCtx,\n sessionId: GenericId<\"Session\"> | null,\n account:\n | { existingAccount: Doc<\"Account\"> }\n | {\n providerAccountId: string;\n secret?: string;\n },\n args: CreateOrUpdateUserArgs,\n config: ConvexAuthConfig,\n opts?: { existingUserId?: GenericId<\"User\"> },\n): Promise<{\n userId: GenericId<\"User\">;\n accountId: GenericId<\"Account\">;\n}> {\n const userId = await defaultCreateOrUpdateUser(\n ctx,\n sessionId,\n \"existingAccount\" in account ? account.existingAccount : null,\n args,\n config,\n opts?.existingUserId ?? null,\n );\n const accountId = await createOrUpdateAccount(\n ctx,\n userId,\n account,\n args,\n config,\n );\n return { userId, accountId };\n}\n\nasync function defaultCreateOrUpdateUser(\n ctx: MutationCtx,\n existingSessionId: GenericId<\"Session\"> | null,\n existingAccount: Doc<\"Account\"> | null,\n args: CreateOrUpdateUserArgs,\n config: ConvexAuthConfig,\n existingUserIdOverride: GenericId<\"User\"> | null,\n) {\n logWithLevel(LOG_LEVELS.DEBUG, \"defaultCreateOrUpdateUser args:\", {\n existingAccountId: existingAccount?._id,\n existingSessionId,\n args,\n });\n const existingUserId = existingAccount?.userId ?? null;\n const db = authDb(ctx, config);\n if (config.callbacks?.createOrUpdateUser !== undefined) {\n logWithLevel(LOG_LEVELS.DEBUG, \"Using custom createOrUpdateUser callback\");\n return await config.callbacks.createOrUpdateUser(ctx, {\n existingUserId,\n ...args,\n });\n }\n\n const {\n provider,\n profile: {\n id: _profileId,\n emailVerified: profileEmailVerified,\n phoneVerified: profilePhoneVerified,\n ...profile\n },\n } = args;\n const emailVerified =\n profileEmailVerified ??\n (provider.type === \"oauth\" && provider.accountLinking !== \"none\");\n const phoneVerified = profilePhoneVerified ?? false;\n const shouldLinkViaEmail =\n args.shouldLinkViaEmail || emailVerified || provider.type === \"email\";\n const shouldLinkViaPhone =\n args.shouldLinkViaPhone || phoneVerified || provider.type === \"phone\";\n\n let userId = existingUserId ?? existingUserIdOverride;\n if (existingUserId === null) {\n const existingUserWithVerifiedEmailId =\n typeof profile.email === \"string\" && shouldLinkViaEmail\n ? ((await uniqueUserWithVerifiedEmail(ctx, profile.email, config))\n ?._id ?? null)\n : null;\n\n const existingUserWithVerifiedPhoneId =\n typeof profile.phone === \"string\" && shouldLinkViaPhone\n ? ((await uniqueUserWithVerifiedPhone(ctx, profile.phone, config))\n ?._id ?? null)\n : null;\n const linkDispatch = {\n tag:\n existingUserWithVerifiedEmailId !== null &&\n existingUserWithVerifiedPhoneId !== null\n ? \"both\"\n : existingUserWithVerifiedEmailId !== null\n ? \"email\"\n : existingUserWithVerifiedPhoneId !== null\n ? \"phone\"\n : \"none\",\n existingUserWithVerifiedEmailId,\n existingUserWithVerifiedPhoneId,\n } as const;\n\n const linkHandlers = {\n both: () =>\n Fx.sync(() => {\n logWithLevel(\n LOG_LEVELS.DEBUG,\n `Found existing email and phone verified users, so not linking: email: ${linkDispatch.existingUserWithVerifiedEmailId}, phone: ${linkDispatch.existingUserWithVerifiedPhoneId}`,\n );\n return null;\n }),\n email: () =>\n Fx.sync(() => {\n logWithLevel(\n LOG_LEVELS.DEBUG,\n `Found existing email verified user, linking: ${linkDispatch.existingUserWithVerifiedEmailId}`,\n );\n return linkDispatch.existingUserWithVerifiedEmailId;\n }),\n phone: () =>\n Fx.sync(() => {\n logWithLevel(\n LOG_LEVELS.DEBUG,\n `Found existing phone verified user, linking: ${linkDispatch.existingUserWithVerifiedPhoneId}`,\n );\n return linkDispatch.existingUserWithVerifiedPhoneId;\n }),\n none: () =>\n Fx.sync(() => {\n logWithLevel(\n LOG_LEVELS.DEBUG,\n \"No existing verified users found, creating new user\",\n );\n return null;\n }),\n } as const;\n\n userId = await Fx.run(linkHandlers[linkDispatch.tag]());\n }\n const userData = {\n ...(emailVerified ? { emailVerificationTime: Date.now() } : null),\n ...(phoneVerified ? { phoneVerificationTime: Date.now() } : null),\n ...profile,\n };\n const existingOrLinkedUserId = userId;\n if (userId !== null) {\n await Fx.run(\n Fx.from({\n ok: () => db.users.patch(userId!, userData),\n err: (error) =>\n Cv.error({\n code: \"USER_UPDATE_FAILED\",\n message:\n `Could not update user document with ID \\`${userId}\\`, ` +\n `either the user has been deleted but their account has not, ` +\n `or the profile data doesn't match the \\`users\\` table schema: ` +\n `${(error as Error).message}`,\n }),\n }).pipe(Fx.recover((e) => Fx.fatal(e))),\n );\n } else {\n userId = (await db.users.insert(userData)) as GenericId<\"User\">;\n }\n const afterUserCreatedOrUpdated = config.callbacks?.afterUserCreatedOrUpdated;\n if (afterUserCreatedOrUpdated !== undefined) {\n logWithLevel(\n LOG_LEVELS.DEBUG,\n \"Calling custom afterUserCreatedOrUpdated callback\",\n );\n await afterUserCreatedOrUpdated(ctx, {\n userId,\n existingUserId: existingOrLinkedUserId,\n ...args,\n });\n } else {\n logWithLevel(\n LOG_LEVELS.DEBUG,\n \"No custom afterUserCreatedOrUpdated callback, skipping\",\n );\n }\n return userId;\n}\n\nasync function uniqueUserWithVerifiedEmail(\n ctx: MutationCtx,\n email: string,\n config: ConvexAuthConfig,\n) {\n const db = authDb(ctx, config);\n return (await db.users.findByVerifiedEmail(email)) as Doc<\"User\"> | null;\n}\n\nasync function uniqueUserWithVerifiedPhone(\n ctx: MutationCtx,\n phone: string,\n config: ConvexAuthConfig,\n) {\n const db = authDb(ctx, config);\n return (await db.users.findByVerifiedPhone(phone)) as Doc<\"User\"> | null;\n}\n\nasync function createOrUpdateAccount(\n ctx: MutationCtx,\n userId: GenericId<\"User\">,\n account:\n | { existingAccount: Doc<\"Account\"> }\n | {\n providerAccountId: string;\n secret?: string;\n },\n args: CreateOrUpdateUserArgs,\n config: ConvexAuthConfig,\n) {\n const db = authDb(ctx, config);\n const mergedExtend =\n \"existingAccount\" in account\n ? mergeExtend(account.existingAccount.extend, args.accountExtend)\n : args.accountExtend;\n const accountId =\n \"existingAccount\" in account\n ? account.existingAccount._id\n : ((await db.accounts.create({\n userId,\n provider: args.provider.id,\n providerAccountId: account.providerAccountId,\n secret: account.secret,\n extend: mergedExtend,\n })) as GenericId<\"Account\">);\n // This is never used with the default `createOrUpdateUser` implementation,\n // but it is used for manual linking via custom `createOrUpdateUser`:\n if (\n \"existingAccount\" in account &&\n account.existingAccount.userId !== userId\n ) {\n await db.accounts.patch(accountId, { userId });\n }\n const accountPatchData: Record<string, unknown> = {};\n if (mergedExtend) {\n accountPatchData.extend = mergedExtend;\n }\n if (args.profile.emailVerified) {\n accountPatchData.emailVerified = args.profile.email;\n }\n if (args.profile.phoneVerified) {\n accountPatchData.phoneVerified = args.profile.phone;\n }\n if (Object.keys(accountPatchData).length > 0) {\n await db.accounts.patch(accountId, accountPatchData);\n }\n return accountId;\n}\n"],"mappings":"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"}
|
package/dist/server/utils.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Cv } from "@robelest/fx/convex";
|
|
2
2
|
import { generateRandomString as generateRandomString$1 } from "@oslojs/crypto/random";
|
|
3
3
|
import { sha256 as sha256$1 } from "@oslojs/crypto/sha2";
|
|
4
4
|
import { decodeBase64urlIgnorePadding, encodeBase64urlNoPadding, encodeHexLowerCase } from "@oslojs/encoding";
|
|
@@ -7,13 +7,16 @@ import { decodeBase64urlIgnorePadding, encodeBase64urlNoPadding, encodeHexLowerC
|
|
|
7
7
|
/**
|
|
8
8
|
* Require an environment variable to be set, throwing at config time if missing.
|
|
9
9
|
*
|
|
10
|
-
* Uses `
|
|
10
|
+
* Uses `Cv.error()` directly since this is a synchronous guard
|
|
11
11
|
* called inline in many expressions — not suitable for Fx pipeline wrapping.
|
|
12
12
|
*/
|
|
13
13
|
/** @internal */
|
|
14
14
|
function requireEnv(name) {
|
|
15
15
|
const value = process.env[name];
|
|
16
|
-
if (value === void 0) throw
|
|
16
|
+
if (value === void 0) throw Cv.error({
|
|
17
|
+
code: "MISSING_ENV_VAR",
|
|
18
|
+
message: `Missing environment variable \`${name}\``
|
|
19
|
+
});
|
|
17
20
|
return value;
|
|
18
21
|
}
|
|
19
22
|
/** @internal */
|
|
@@ -107,7 +110,10 @@ async function encryptSecret(value) {
|
|
|
107
110
|
/** @internal */
|
|
108
111
|
async function decryptSecret(ciphertext) {
|
|
109
112
|
const [ivEncoded, payloadEncoded] = ciphertext.split(".");
|
|
110
|
-
if (!ivEncoded || !payloadEncoded) throw
|
|
113
|
+
if (!ivEncoded || !payloadEncoded) throw Cv.error({
|
|
114
|
+
code: "INVALID_PARAMETERS",
|
|
115
|
+
message: "Stored enterprise secret is malformed."
|
|
116
|
+
});
|
|
111
117
|
const key = await getSecretCryptoKey();
|
|
112
118
|
const decrypted = await crypto.subtle.decrypt({
|
|
113
119
|
name: "AES-GCM",
|
package/dist/server/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","names":["rawSha256","osloGenerateRandomString"],"sources":["../../src/server/utils.ts"],"sourcesContent":["import {\n RandomReader,\n generateRandomString as osloGenerateRandomString,\n} from \"@oslojs/crypto/random\";\nimport { sha256 as rawSha256 } from \"@oslojs/crypto/sha2\";\nimport {\n decodeBase64urlIgnorePadding,\n encodeBase64urlNoPadding,\n encodeHexLowerCase,\n} from \"@oslojs/encoding\";\
|
|
1
|
+
{"version":3,"file":"utils.js","names":["rawSha256","osloGenerateRandomString"],"sources":["../../src/server/utils.ts"],"sourcesContent":["import {\n RandomReader,\n generateRandomString as osloGenerateRandomString,\n} from \"@oslojs/crypto/random\";\nimport { sha256 as rawSha256 } from \"@oslojs/crypto/sha2\";\nimport {\n decodeBase64urlIgnorePadding,\n encodeBase64urlNoPadding,\n encodeHexLowerCase,\n} from \"@oslojs/encoding\";\nimport { Cv } from \"@robelest/fx/convex\";\n\n/**\n * Require an environment variable to be set, throwing at config time if missing.\n *\n * Uses `Cv.error()` directly since this is a synchronous guard\n * called inline in many expressions — not suitable for Fx pipeline wrapping.\n */\n/** @internal */\nexport function requireEnv(name: string) {\n const value = process.env[name];\n if (value === undefined) {\n throw Cv.error({\n code: \"MISSING_ENV_VAR\",\n message: `Missing environment variable \\`${name}\\``,\n });\n }\n return value;\n}\n\n/** @internal */\nexport function isLocalHost(host?: string) {\n if (host === undefined) {\n return false;\n }\n const raw = host.includes(\"://\") ? host : `http://${host}`;\n let url: URL;\n try {\n url = new URL(raw);\n } catch {\n return false;\n }\n return (\n url.hostname === \"localhost\" ||\n url.hostname === \"127.0.0.1\" ||\n url.hostname === \"::1\"\n );\n}\n\n// Internal server utilities (merged from former internalUtils.ts)\n\n/** @internal */\nexport const TOKEN_SUB_CLAIM_DIVIDER = \"|\";\n/** @internal */\nexport const REFRESH_TOKEN_DIVIDER = \"|\";\n\n/** @internal */\nexport async function sha256(input: string) {\n return encodeHexLowerCase(rawSha256(new TextEncoder().encode(input)));\n}\n\n/** @internal */\nexport function generateRandomString(length: number, alphabet: string) {\n const random: RandomReader = {\n read(bytes) {\n crypto.getRandomValues(bytes as Uint8Array<ArrayBuffer>);\n },\n };\n\n return osloGenerateRandomString(random, alphabet, length);\n}\n\n/** @internal */\nexport function errorMessage(error: unknown) {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** @internal */\nexport function logError(error: unknown) {\n logWithLevel(\n LOG_LEVELS.ERROR,\n error instanceof Error\n ? error.message + \"\\n\" + error.stack?.replace(\"\\\\n\", \"\\n\")\n : error,\n );\n}\n\n/** @internal */\nexport const LOG_LEVELS = {\n ERROR: \"ERROR\",\n WARN: \"WARN\",\n INFO: \"INFO\",\n DEBUG: \"DEBUG\",\n} as const;\ntype LogLevel = keyof typeof LOG_LEVELS;\n\n/** @internal */\nexport function logWithLevel(level: LogLevel, ...args: unknown[]) {\n const configuredLogLevel =\n LOG_LEVELS[\n (process.env.AUTH_LOG_LEVEL as LogLevel | undefined) ?? \"INFO\"\n ] ?? \"INFO\";\n switch (level) {\n case \"ERROR\":\n console.error(...args);\n break;\n case \"WARN\":\n if (configuredLogLevel !== \"ERROR\") {\n console.warn(...args);\n }\n break;\n case \"INFO\":\n if (configuredLogLevel === \"INFO\" || configuredLogLevel === \"DEBUG\") {\n console.info(...args);\n }\n break;\n case \"DEBUG\":\n if (configuredLogLevel === \"DEBUG\") {\n console.debug(...args);\n }\n break;\n }\n}\n\nconst UNREDACTED_LENGTH = 5;\n/** @internal */\nexport function maybeRedact(value: string) {\n if (value === \"\") {\n return \"\";\n }\n const shouldRedact = process.env.AUTH_LOG_SECRETS !== \"true\";\n if (shouldRedact) {\n if (value.length < UNREDACTED_LENGTH * 2) {\n return \"<redacted>\";\n }\n return (\n value.substring(0, UNREDACTED_LENGTH) +\n \"<redacted>\" +\n value.substring(value.length - UNREDACTED_LENGTH)\n );\n } else {\n return value;\n }\n}\n\nconst SECRET_KEY_ENV = \"AUTH_SECRET_ENCRYPTION_KEY\";\nconst SECRET_IV_LENGTH = 12;\n\nfunction toArrayBuffer(bytes: Uint8Array) {\n return bytes.buffer.slice(\n bytes.byteOffset,\n bytes.byteOffset + bytes.byteLength,\n ) as ArrayBuffer;\n}\n\nasync function getSecretCryptoKey() {\n const material = requireEnv(SECRET_KEY_ENV);\n const rawKey = rawSha256(new TextEncoder().encode(material));\n return await crypto.subtle.importKey(\n \"raw\",\n toArrayBuffer(rawKey),\n { name: \"AES-GCM\" },\n false,\n [\"encrypt\", \"decrypt\"],\n );\n}\n\n/** @internal */\nexport async function encryptSecret(value: string) {\n const key = await getSecretCryptoKey();\n const iv = crypto.getRandomValues(new Uint8Array(SECRET_IV_LENGTH));\n const encrypted = await crypto.subtle.encrypt(\n { name: \"AES-GCM\", iv: toArrayBuffer(iv) },\n key,\n toArrayBuffer(new TextEncoder().encode(value)),\n );\n return `${encodeBase64urlNoPadding(iv)}.${encodeBase64urlNoPadding(new Uint8Array(encrypted))}`;\n}\n\n/** @internal */\nexport async function decryptSecret(ciphertext: string) {\n const [ivEncoded, payloadEncoded] = ciphertext.split(\".\");\n if (!ivEncoded || !payloadEncoded) {\n throw Cv.error({\n code: \"INVALID_PARAMETERS\",\n message: \"Stored enterprise secret is malformed.\",\n });\n }\n const key = await getSecretCryptoKey();\n const decrypted = await crypto.subtle.decrypt(\n {\n name: \"AES-GCM\",\n iv: toArrayBuffer(decodeBase64urlIgnorePadding(ivEncoded)),\n },\n key,\n toArrayBuffer(decodeBase64urlIgnorePadding(payloadEncoded)),\n );\n return new TextDecoder().decode(decrypted);\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@robelest/convex-auth",
|
|
3
|
-
"version": "0.0.4-preview.
|
|
3
|
+
"version": "0.0.4-preview.23",
|
|
4
4
|
"description": "Authentication for Convex",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"auth",
|
|
@@ -54,10 +54,6 @@
|
|
|
54
54
|
"types": "./dist/component/convex.config.d.ts",
|
|
55
55
|
"default": "./dist/component/convex.config.js"
|
|
56
56
|
},
|
|
57
|
-
"./errors": {
|
|
58
|
-
"types": "./dist/server/errors.d.ts",
|
|
59
|
-
"import": "./dist/server/errors.js"
|
|
60
|
-
},
|
|
61
57
|
"./providers": {
|
|
62
58
|
"types": "./dist/providers/index.d.ts",
|
|
63
59
|
"import": "./dist/providers/index.js"
|
|
@@ -103,7 +99,7 @@
|
|
|
103
99
|
},
|
|
104
100
|
"@comment devDependencies": [
|
|
105
101
|
"The CLI deps (@clack/prompts, @commander-js/extra-typings, dotenv,",
|
|
106
|
-
"valibot) are devDependencies because they are bundled into dist/bin.
|
|
102
|
+
"valibot) are devDependencies because they are bundled into dist/bin.js",
|
|
107
103
|
"via esbuild."
|
|
108
104
|
],
|
|
109
105
|
"scripts": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* map where each role has an `id` (matching its key), an optional `label`,
|
|
12
12
|
* and a typed `grants` array. The returned object is fully type-safe:
|
|
13
13
|
* role IDs and grant strings are narrowed to their literal types, so
|
|
14
|
-
* `member.
|
|
14
|
+
* `member.inspect()` / `member.require()` and `member.create()` calls are checked at compile time.
|
|
15
15
|
* This is the canonical way to define role metadata for the auth library.
|
|
16
16
|
*
|
|
17
17
|
* @param roles - An object mapping role IDs to their configuration.
|
package/src/cli/index.ts
CHANGED
|
@@ -22,7 +22,7 @@ import { generateKeys } from "./keys";
|
|
|
22
22
|
// ---------------------------------------------------------------------------
|
|
23
23
|
|
|
24
24
|
function getPackageVersion(): string {
|
|
25
|
-
// When bundled to dist/bin.
|
|
25
|
+
// When bundled to dist/bin.js the package.json is one level up
|
|
26
26
|
for (const relative of ["..", "../.."]) {
|
|
27
27
|
try {
|
|
28
28
|
const pkgPath = path.resolve(__dirname, relative, "package.json");
|
package/src/client/core/types.ts
CHANGED
|
@@ -286,21 +286,19 @@ export interface DeviceClient {
|
|
|
286
286
|
*
|
|
287
287
|
* @param opts - Poll options.
|
|
288
288
|
* @param opts.code - The {@link DeviceCodeResult} returned from `signIn("device")`.
|
|
289
|
-
* @
|
|
289
|
+
* @throws `ConvexError({ code: "DEVICE_CODE_EXPIRED" })` when the code expires before authorization.
|
|
290
290
|
*
|
|
291
291
|
* @example
|
|
292
292
|
* ```ts
|
|
293
293
|
* const result = await auth.signIn("device");
|
|
294
294
|
* if (result.kind === "deviceCode") {
|
|
295
295
|
* // Display result.deviceCode.userCode to the user
|
|
296
|
-
*
|
|
297
|
-
*
|
|
296
|
+
* await auth.device.poll({ code: result.deviceCode });
|
|
297
|
+
* console.log("Device authorized!");
|
|
298
298
|
* }
|
|
299
299
|
* ```
|
|
300
300
|
*/
|
|
301
|
-
poll(opts: {
|
|
302
|
-
code: DeviceCodeResult;
|
|
303
|
-
}): Promise<{ ok: true } | { ok: false; expired: boolean }>;
|
|
301
|
+
poll(opts: { code: DeviceCodeResult }): Promise<void>;
|
|
304
302
|
|
|
305
303
|
/**
|
|
306
304
|
* Approve a device flow from the verification page using the displayed user code.
|
|
@@ -310,17 +308,14 @@ export interface DeviceClient {
|
|
|
310
308
|
*
|
|
311
309
|
* @param opts - Verification options.
|
|
312
310
|
* @param opts.code - The user code string (e.g. `"WDJB-MJHT"`).
|
|
313
|
-
* @
|
|
311
|
+
* @throws `ConvexError({ code: "DEVICE_AUTHORIZATION_FAILED" })` when verification fails.
|
|
314
312
|
*
|
|
315
313
|
* @example
|
|
316
314
|
* ```ts
|
|
317
|
-
*
|
|
318
|
-
* if (!result.ok) console.error(result.message);
|
|
315
|
+
* await auth.device.verify({ code: "WDJB-MJHT" });
|
|
319
316
|
* ```
|
|
320
317
|
*/
|
|
321
|
-
verify(opts: {
|
|
322
|
-
code: string;
|
|
323
|
-
}): Promise<{ ok: true } | { ok: false; message: string }>;
|
|
318
|
+
verify(opts: { code: string }): Promise<void>;
|
|
324
319
|
}
|
|
325
320
|
|
|
326
321
|
/**
|
|
@@ -345,8 +340,13 @@ export interface PendingInvite {
|
|
|
345
340
|
* @readonly
|
|
346
341
|
*/
|
|
347
342
|
readonly email: string | null;
|
|
348
|
-
/**
|
|
349
|
-
|
|
343
|
+
/**
|
|
344
|
+
* Consume the invite: clears storage/URL params and returns the token.
|
|
345
|
+
*
|
|
346
|
+
* @returns The invite token.
|
|
347
|
+
* @throws When there is no pending invite to accept.
|
|
348
|
+
*/
|
|
349
|
+
accept(): Promise<{ token: string }>;
|
|
350
350
|
}
|
|
351
351
|
|
|
352
352
|
/** Base auth client — always present. */
|
|
@@ -36,9 +36,7 @@ export function createDeviceClient(deps: DeviceDeps): DeviceClient {
|
|
|
36
36
|
deps;
|
|
37
37
|
|
|
38
38
|
return {
|
|
39
|
-
poll: async (opts: {
|
|
40
|
-
code: DeviceCodeResult;
|
|
41
|
-
}): Promise<{ ok: true } | { ok: false; expired: boolean }> => {
|
|
39
|
+
poll: async (opts: { code: DeviceCodeResult }): Promise<void> => {
|
|
42
40
|
const { code } = opts;
|
|
43
41
|
const intervalMs = code.interval * 1000;
|
|
44
42
|
const expiresAt = Date.now() + code.expiresIn * 1000;
|
|
@@ -121,16 +119,17 @@ export function createDeviceClient(deps: DeviceDeps): DeviceClient {
|
|
|
121
119
|
context: { provider: "device", flow: "poll" },
|
|
122
120
|
});
|
|
123
121
|
}
|
|
124
|
-
return
|
|
122
|
+
return;
|
|
125
123
|
}
|
|
126
124
|
}
|
|
127
125
|
|
|
128
|
-
|
|
126
|
+
throw new ConvexError({
|
|
127
|
+
code: "DEVICE_CODE_EXPIRED",
|
|
128
|
+
message: "Device code expired before authorization was completed.",
|
|
129
|
+
});
|
|
129
130
|
},
|
|
130
131
|
|
|
131
|
-
verify: async (opts: {
|
|
132
|
-
code: string;
|
|
133
|
-
}): Promise<{ ok: true } | { ok: false; message: string }> => {
|
|
132
|
+
verify: async (opts: { code: string }): Promise<void> => {
|
|
134
133
|
const params: Record<string, any> = {
|
|
135
134
|
flow: "verify",
|
|
136
135
|
userCode: opts.code,
|
|
@@ -148,12 +147,11 @@ export function createDeviceClient(deps: DeviceDeps): DeviceClient {
|
|
|
148
147
|
params,
|
|
149
148
|
});
|
|
150
149
|
}
|
|
151
|
-
return { ok: true as const };
|
|
152
150
|
} catch (e: unknown) {
|
|
153
|
-
|
|
154
|
-
|
|
151
|
+
throw new ConvexError({
|
|
152
|
+
code: "DEVICE_AUTHORIZATION_FAILED",
|
|
155
153
|
message: e instanceof Error ? e.message : "Invalid or expired code.",
|
|
156
|
-
};
|
|
154
|
+
});
|
|
157
155
|
}
|
|
158
156
|
},
|
|
159
157
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Fx } from "@robelest/fx";
|
|
2
2
|
|
|
3
|
-
import { base64urlDecode, base64urlEncode } from "../runtime/browser";
|
|
4
3
|
import type {
|
|
5
4
|
AuthSession,
|
|
6
5
|
ConvexTransport,
|
|
@@ -8,6 +7,7 @@ import type {
|
|
|
8
7
|
SignInActionResult,
|
|
9
8
|
SignInResult,
|
|
10
9
|
} from "../core/types";
|
|
10
|
+
import { base64urlDecode, base64urlEncode } from "../runtime/browser";
|
|
11
11
|
|
|
12
12
|
type PasskeyDeps = {
|
|
13
13
|
proxy: string | undefined;
|
|
@@ -43,31 +43,28 @@ export function createPasskeyClient(deps: PasskeyDeps): PasskeyClient {
|
|
|
43
43
|
return Fx.run(
|
|
44
44
|
Fx.match(result, result.kind, {
|
|
45
45
|
signedIn: (signedInResult) =>
|
|
46
|
-
Fx.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
tokens
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
: ({ kind: "started" as const } as SignInResult);
|
|
69
|
-
},
|
|
70
|
-
err: (e) => e as never,
|
|
46
|
+
Fx.promise(async () => {
|
|
47
|
+
const signingIn = await setTokenAndMaybeWait(
|
|
48
|
+
proxy
|
|
49
|
+
? {
|
|
50
|
+
shouldStore: false as const,
|
|
51
|
+
tokens:
|
|
52
|
+
signedInResult.tokens === null
|
|
53
|
+
? null
|
|
54
|
+
: { token: signedInResult.tokens.token },
|
|
55
|
+
waitForHandshake: true,
|
|
56
|
+
context: { provider: "passkey", flow },
|
|
57
|
+
}
|
|
58
|
+
: {
|
|
59
|
+
shouldStore: true as const,
|
|
60
|
+
tokens: signedInResult.tokens,
|
|
61
|
+
waitForHandshake: true,
|
|
62
|
+
context: { provider: "passkey", flow },
|
|
63
|
+
},
|
|
64
|
+
);
|
|
65
|
+
return signingIn
|
|
66
|
+
? ({ kind: "signedIn" as const } as SignInResult)
|
|
67
|
+
: ({ kind: "started" as const } as SignInResult);
|
|
71
68
|
}),
|
|
72
69
|
redirect: () => Fx.succeed({ kind: "started" as const }),
|
|
73
70
|
started: () => Fx.succeed({ kind: "started" as const }),
|
package/src/client/index.ts
CHANGED
|
@@ -2,18 +2,6 @@ import { Fx } from "@robelest/fx";
|
|
|
2
2
|
import { ConvexHttpClient } from "convex/browser";
|
|
3
3
|
import { ConvexError, Value } from "convex/values";
|
|
4
4
|
|
|
5
|
-
import { AUTH_ERRORS } from "../server/errors";
|
|
6
|
-
import { browserMutex, getStorageListenerRegistry } from "./runtime/browser";
|
|
7
|
-
import { createDeviceClient } from "./factors/device";
|
|
8
|
-
import { createInviteManager } from "./runtime/invite";
|
|
9
|
-
import { createPasskeyClient } from "./factors/passkey";
|
|
10
|
-
import {
|
|
11
|
-
createProxyHelpers,
|
|
12
|
-
isRetriableProxyRefreshError,
|
|
13
|
-
isTransientNetworkError,
|
|
14
|
-
} from "./runtime/proxy";
|
|
15
|
-
import { createStorageHelpers } from "./runtime/storage";
|
|
16
|
-
import { createTotpClient } from "./factors/totp";
|
|
17
5
|
import type {
|
|
18
6
|
AuthApiRefs,
|
|
19
7
|
AuthClient,
|
|
@@ -33,14 +21,18 @@ import type {
|
|
|
33
21
|
Storage,
|
|
34
22
|
TotpClient,
|
|
35
23
|
} from "./core/types";
|
|
24
|
+
import { createDeviceClient } from "./factors/device";
|
|
25
|
+
import { createPasskeyClient } from "./factors/passkey";
|
|
26
|
+
import { createTotpClient } from "./factors/totp";
|
|
27
|
+
import { browserMutex, getStorageListenerRegistry } from "./runtime/browser";
|
|
28
|
+
import { createInviteManager } from "./runtime/invite";
|
|
29
|
+
import {
|
|
30
|
+
createProxyHelpers,
|
|
31
|
+
isRetriableProxyRefreshError,
|
|
32
|
+
isTransientNetworkError,
|
|
33
|
+
} from "./runtime/proxy";
|
|
34
|
+
import { createStorageHelpers } from "./runtime/storage";
|
|
36
35
|
|
|
37
|
-
// Re-export error utilities so consumers can import from `@robelest/convex-auth/client`.
|
|
38
|
-
export {
|
|
39
|
-
isAuthError,
|
|
40
|
-
parseAuthError,
|
|
41
|
-
AUTH_ERRORS,
|
|
42
|
-
type AuthErrorCode,
|
|
43
|
-
} from "../server/errors";
|
|
44
36
|
export type {
|
|
45
37
|
AuthApiRefs,
|
|
46
38
|
AuthClient,
|
|
@@ -253,13 +245,20 @@ export function client<
|
|
|
253
245
|
};
|
|
254
246
|
let handlingCodeFlow = false;
|
|
255
247
|
|
|
248
|
+
const HANDSHAKE_ERROR_MESSAGES: Record<AuthHandshakeErrorCode, string> = {
|
|
249
|
+
AUTH_HANDSHAKE_TIMEOUT:
|
|
250
|
+
"Sign-in succeeded but authentication confirmation timed out.",
|
|
251
|
+
AUTH_HANDSHAKE_REJECTED:
|
|
252
|
+
"Authentication was rejected while confirming the session.",
|
|
253
|
+
};
|
|
254
|
+
|
|
256
255
|
const createHandshakeError = (
|
|
257
256
|
code: AuthHandshakeErrorCode,
|
|
258
257
|
context: Record<string, unknown>,
|
|
259
258
|
) => {
|
|
260
259
|
return new ConvexError({
|
|
261
260
|
code,
|
|
262
|
-
message:
|
|
261
|
+
message: HANDSHAKE_ERROR_MESSAGES[code],
|
|
263
262
|
...context,
|
|
264
263
|
} as Value);
|
|
265
264
|
};
|
|
@@ -652,25 +651,19 @@ export function client<
|
|
|
652
651
|
Fx.run(
|
|
653
652
|
Fx.match(result, result.kind, {
|
|
654
653
|
redirect: (redirectResult) =>
|
|
655
|
-
Fx.
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
kind: "redirect" as const,
|
|
669
|
-
redirect: redirectUrl,
|
|
670
|
-
verifier: redirectResult.verifier,
|
|
671
|
-
};
|
|
672
|
-
},
|
|
673
|
-
err: (e) => e as never,
|
|
654
|
+
Fx.promise(async () => {
|
|
655
|
+
const redirectUrl = new URL(redirectResult.redirect);
|
|
656
|
+
if (options.persistVerifier) {
|
|
657
|
+
await storageSet(VERIFIER_STORAGE_KEY, redirectResult.verifier);
|
|
658
|
+
}
|
|
659
|
+
if (typeof window !== "undefined") {
|
|
660
|
+
window.location.href = redirectUrl.toString();
|
|
661
|
+
}
|
|
662
|
+
return {
|
|
663
|
+
kind: "redirect" as const,
|
|
664
|
+
redirect: redirectUrl,
|
|
665
|
+
verifier: redirectResult.verifier,
|
|
666
|
+
};
|
|
674
667
|
}),
|
|
675
668
|
totpRequired: (totpRequiredResult) =>
|
|
676
669
|
Fx.succeed({
|
|
@@ -685,31 +678,28 @@ export function client<
|
|
|
685
678
|
),
|
|
686
679
|
}),
|
|
687
680
|
signedIn: (signedInResult) =>
|
|
688
|
-
Fx.
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
tokens
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
: ({ kind: "started" as const } as SignInResult);
|
|
711
|
-
},
|
|
712
|
-
err: (e) => e as never,
|
|
681
|
+
Fx.promise(async () => {
|
|
682
|
+
const signingIn = await setTokenAndMaybeWait(
|
|
683
|
+
options.shouldStore
|
|
684
|
+
? {
|
|
685
|
+
shouldStore: true as const,
|
|
686
|
+
tokens: signedInResult.tokens,
|
|
687
|
+
waitForHandshake: true,
|
|
688
|
+
context: { provider, flow },
|
|
689
|
+
}
|
|
690
|
+
: {
|
|
691
|
+
shouldStore: false as const,
|
|
692
|
+
tokens:
|
|
693
|
+
signedInResult.tokens === null
|
|
694
|
+
? null
|
|
695
|
+
: { token: signedInResult.tokens.token },
|
|
696
|
+
waitForHandshake: true,
|
|
697
|
+
context: { provider, flow },
|
|
698
|
+
},
|
|
699
|
+
);
|
|
700
|
+
return signingIn
|
|
701
|
+
? ({ kind: "signedIn" as const } as SignInResult)
|
|
702
|
+
: ({ kind: "started" as const } as SignInResult);
|
|
713
703
|
}),
|
|
714
704
|
started: (_startedResult) => Fx.succeed({ kind: "started" as const }),
|
|
715
705
|
passkeyOptions: (_passkeyOptionsResult) =>
|
|
@@ -48,18 +48,16 @@ export function createInviteManager(args: {
|
|
|
48
48
|
await storageSet(emailKey, pendingInvite.email);
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
|
-
async acceptInvite(): Promise<{
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}> {
|
|
56
|
-
if (!pendingInvite) return { ok: false, message: "No pending invite" };
|
|
51
|
+
async acceptInvite(): Promise<{ token: string }> {
|
|
52
|
+
if (!pendingInvite) {
|
|
53
|
+
throw new Error("No pending invite to accept.");
|
|
54
|
+
}
|
|
57
55
|
const { token } = pendingInvite;
|
|
58
56
|
pendingInvite = null;
|
|
59
57
|
void storageRemove(tokenKey);
|
|
60
58
|
void storageRemove(emailKey);
|
|
61
59
|
cleanUrlParams(["invite", "email"]);
|
|
62
|
-
return {
|
|
60
|
+
return { token };
|
|
63
61
|
},
|
|
64
62
|
};
|
|
65
63
|
}
|
package/src/component/index.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { v } from "convex/values";
|
|
2
|
+
|
|
2
3
|
import { mutation, query } from "../../functions";
|
|
3
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
vAuditActorType,
|
|
6
|
+
vAuditStatus,
|
|
7
|
+
vEnterpriseAuditEventDoc,
|
|
8
|
+
} from "../../model";
|
|
4
9
|
|
|
5
10
|
/**
|
|
6
11
|
* Record a new audit event for an enterprise.
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { ConvexError, v } from "convex/values";
|
|
2
|
+
|
|
2
3
|
import { mutation, query } from "../../functions";
|
|
3
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
vEnterpriseDomainDoc,
|
|
6
|
+
vEnterpriseDomainVerificationDoc,
|
|
7
|
+
} from "../../model";
|
|
4
8
|
|
|
5
9
|
/**
|
|
6
10
|
* Link a domain to an enterprise record, or update an existing link.
|