@robelest/convex-auth 0.0.4-preview.18 → 0.0.4-preview.19
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/README.md +6 -3
- package/dist/authorization/index.d.ts +46 -0
- package/dist/authorization/index.d.ts.map +1 -1
- package/dist/authorization/index.js +46 -0
- package/dist/authorization/index.js.map +1 -1
- package/dist/bin.cjs +21 -13
- package/dist/client/core/types.d.ts +20 -0
- package/dist/client/core/types.d.ts.map +1 -0
- package/dist/client/index.d.ts +2 -16
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +67 -565
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/api.d.ts +40 -12
- package/dist/component/_generated/api.d.ts.map +1 -1
- package/dist/component/_generated/api.js.map +1 -1
- package/dist/component/_generated/component.d.ts +1306 -1344
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/client/core/types.d.ts +2 -0
- package/dist/component/client/index.d.ts +2 -2
- package/dist/component/model.d.ts +80 -80
- package/dist/component/model.d.ts.map +1 -1
- package/dist/component/model.js +5 -1
- package/dist/component/model.js.map +1 -1
- package/dist/component/providers/credentials.d.ts +3 -2
- package/dist/component/providers/credentials.d.ts.map +1 -1
- package/dist/component/providers/email.d.ts +30 -0
- package/dist/component/providers/email.d.ts.map +1 -1
- package/dist/component/providers/phone.d.ts +28 -0
- package/dist/component/providers/phone.d.ts.map +1 -1
- package/dist/component/public/enterprise/audit.d.ts +73 -0
- package/dist/component/public/enterprise/audit.d.ts.map +1 -0
- package/dist/component/public/enterprise/audit.js +108 -0
- package/dist/component/public/enterprise/audit.js.map +1 -0
- package/dist/component/public/enterprise/core.d.ts +176 -0
- package/dist/component/public/enterprise/core.d.ts.map +1 -0
- package/dist/component/public/enterprise/core.js +292 -0
- package/dist/component/public/enterprise/core.js.map +1 -0
- package/dist/component/public/enterprise/domains.d.ts +174 -0
- package/dist/component/public/enterprise/domains.d.ts.map +1 -0
- package/dist/component/public/enterprise/domains.js +271 -0
- package/dist/component/public/enterprise/domains.js.map +1 -0
- package/dist/component/public/enterprise/scim.d.ts +245 -0
- package/dist/component/public/enterprise/scim.d.ts.map +1 -0
- package/dist/component/public/enterprise/scim.js +344 -0
- package/dist/component/public/enterprise/scim.js.map +1 -0
- package/dist/component/public/enterprise/secrets.d.ts +78 -0
- package/dist/component/public/enterprise/secrets.d.ts.map +1 -0
- package/dist/component/public/enterprise/secrets.js +118 -0
- package/dist/component/public/enterprise/secrets.js.map +1 -0
- package/dist/component/public/enterprise/webhooks.d.ts +211 -0
- package/dist/component/public/enterprise/webhooks.d.ts.map +1 -0
- package/dist/component/public/enterprise/webhooks.js +300 -0
- package/dist/component/public/enterprise/webhooks.js.map +1 -0
- package/dist/component/public/factors/devices.d.ts +157 -0
- package/dist/component/public/factors/devices.d.ts.map +1 -0
- package/dist/component/public/factors/devices.js +216 -0
- package/dist/component/public/factors/devices.js.map +1 -0
- package/dist/component/public/factors/passkeys.d.ts +175 -0
- package/dist/component/public/factors/passkeys.d.ts.map +1 -0
- package/dist/component/public/factors/passkeys.js +238 -0
- package/dist/component/public/factors/passkeys.js.map +1 -0
- package/dist/component/public/factors/totp.d.ts +189 -0
- package/dist/component/public/factors/totp.d.ts.map +1 -0
- package/dist/component/public/factors/totp.js +254 -0
- package/dist/component/public/factors/totp.js.map +1 -0
- package/dist/component/public/groups/core.d.ts +137 -0
- package/dist/component/public/groups/core.d.ts.map +1 -0
- package/dist/component/public/groups/core.js +321 -0
- package/dist/component/public/groups/core.js.map +1 -0
- package/dist/component/public/groups/invites.d.ts +217 -0
- package/dist/component/public/groups/invites.d.ts.map +1 -0
- package/dist/component/public/groups/invites.js +457 -0
- package/dist/component/public/groups/invites.js.map +1 -0
- package/dist/component/public/groups/members.d.ts +204 -0
- package/dist/component/public/groups/members.d.ts.map +1 -0
- package/dist/component/public/groups/members.js +355 -0
- package/dist/component/public/groups/members.js.map +1 -0
- package/dist/component/public/identity/accounts.d.ts +147 -0
- package/dist/component/public/identity/accounts.d.ts.map +1 -0
- package/dist/component/public/identity/accounts.js +200 -0
- package/dist/component/public/identity/accounts.js.map +1 -0
- package/dist/component/public/identity/codes.d.ts +104 -0
- package/dist/component/public/identity/codes.d.ts.map +1 -0
- package/dist/component/public/identity/codes.js +140 -0
- package/dist/component/public/identity/codes.js.map +1 -0
- package/dist/component/public/identity/sessions.d.ts +128 -0
- package/dist/component/public/identity/sessions.d.ts.map +1 -0
- package/dist/component/public/identity/sessions.js +192 -0
- package/dist/component/public/identity/sessions.js.map +1 -0
- package/dist/component/public/identity/tokens.d.ts +169 -0
- package/dist/component/public/identity/tokens.d.ts.map +1 -0
- package/dist/component/public/identity/tokens.js +227 -0
- package/dist/component/public/identity/tokens.js.map +1 -0
- package/dist/component/public/identity/users.d.ts +212 -0
- package/dist/component/public/identity/users.d.ts.map +1 -0
- package/dist/component/public/identity/users.js +311 -0
- package/dist/component/public/identity/users.js.map +1 -0
- package/dist/component/public/identity/verifiers.d.ts +116 -0
- package/dist/component/public/identity/verifiers.d.ts.map +1 -0
- package/dist/component/public/identity/verifiers.js +154 -0
- package/dist/component/public/identity/verifiers.js.map +1 -0
- package/dist/component/public/security/keys.d.ts +209 -0
- package/dist/component/public/security/keys.d.ts.map +1 -0
- package/dist/component/public/security/keys.js +319 -0
- package/dist/component/public/security/keys.js.map +1 -0
- package/dist/component/public/security/limits.d.ts +114 -0
- package/dist/component/public/security/limits.d.ts.map +1 -0
- package/dist/component/public/security/limits.js +169 -0
- package/dist/component/public/security/limits.js.map +1 -0
- package/dist/component/public.d.ts +22 -7
- package/dist/component/public.js +21 -6
- package/dist/component/schema.d.ts +288 -288
- package/dist/component/server/auth.d.ts +212 -25
- package/dist/component/server/auth.d.ts.map +1 -1
- package/dist/component/server/auth.js +68 -4
- package/dist/component/server/auth.js.map +1 -1
- package/dist/component/server/authError.js +34 -0
- package/dist/component/server/authError.js.map +1 -0
- package/dist/component/server/{providers.js → config.js} +2 -2
- package/dist/component/server/config.js.map +1 -0
- package/dist/component/server/{domains/core.js → core.js} +107 -88
- package/dist/component/server/core.js.map +1 -0
- package/dist/component/server/{provider.js → crypto.js} +3 -3
- package/dist/component/server/crypto.js.map +1 -0
- package/dist/component/server/device.js +1 -1
- package/dist/component/server/device.js.map +1 -1
- package/dist/component/server/enterprise/config.js +46 -0
- package/dist/component/server/enterprise/config.js.map +1 -0
- package/dist/component/server/{domains/sso.js → enterprise/domain.js} +6 -5
- package/dist/component/server/enterprise/domain.js.map +1 -0
- package/dist/component/server/enterprise/http.js +766 -0
- package/dist/component/server/enterprise/http.js.map +1 -0
- package/dist/component/server/enterprise/oidc.js +248 -0
- package/dist/component/server/enterprise/oidc.js.map +1 -0
- package/dist/component/server/enterprise/policy.js +85 -0
- package/dist/component/server/enterprise/policy.js.map +1 -0
- package/dist/component/server/enterprise/saml.js +338 -0
- package/dist/component/server/enterprise/saml.js.map +1 -0
- package/dist/component/server/enterprise/scim.js +97 -0
- package/dist/component/server/enterprise/scim.js.map +1 -0
- package/dist/component/server/enterprise/shared.js +51 -0
- package/dist/component/server/enterprise/shared.js.map +1 -0
- package/dist/component/server/errors.js.map +1 -1
- package/dist/component/server/http.js +2 -1
- package/dist/component/server/http.js.map +1 -1
- package/dist/component/server/identity.js +1 -1
- package/dist/component/server/identity.js.map +1 -1
- package/dist/component/server/{ratelimit.js → limits.js} +3 -3
- package/dist/component/server/limits.js.map +1 -0
- package/dist/component/server/mutations/account.js +3 -3
- package/dist/component/server/mutations/account.js.map +1 -1
- package/dist/component/server/mutations/code.js +2 -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 +5 -3
- package/dist/component/server/mutations/oauth.js.map +1 -1
- package/dist/component/server/mutations/refresh.js +1 -1
- package/dist/component/server/mutations/refresh.js.map +1 -1
- package/dist/component/server/mutations/register.js +3 -3
- package/dist/component/server/mutations/register.js.map +1 -1
- package/dist/component/server/mutations/retrieve.js +4 -4
- package/dist/component/server/mutations/retrieve.js.map +1 -1
- package/dist/component/server/mutations/signature.js +2 -2
- package/dist/component/server/mutations/signature.js.map +1 -1
- package/dist/component/server/mutations/signin.js +1 -1
- package/dist/component/server/mutations/signin.js.map +1 -1
- package/dist/component/server/mutations/signout.js +1 -1
- package/dist/component/server/mutations/signout.js.map +1 -1
- package/dist/component/server/mutations/store/refs.js +15 -0
- package/dist/component/server/mutations/store/refs.js.map +1 -0
- package/dist/component/server/mutations/store.js +80 -10
- package/dist/component/server/mutations/store.js.map +1 -1
- package/dist/component/server/mutations/verifier.js +1 -1
- package/dist/component/server/mutations/verifier.js.map +1 -1
- package/dist/component/server/mutations/verify.js +4 -3
- package/dist/component/server/mutations/verify.js.map +1 -1
- package/dist/component/server/oauth.js +1 -1
- package/dist/component/server/oauth.js.map +1 -1
- package/dist/component/server/passkey.js +2 -1
- package/dist/component/server/passkey.js.map +1 -1
- package/dist/component/server/redirects.js +1 -1
- package/dist/component/server/redirects.js.map +1 -1
- package/dist/component/server/refresh.js +1 -1
- package/dist/component/server/refresh.js.map +1 -1
- package/dist/component/server/{factory.d.ts → runtime.d.ts} +2 -2
- package/dist/component/server/runtime.d.ts.map +1 -0
- package/dist/component/server/runtime.js +413 -0
- package/dist/component/server/runtime.js.map +1 -0
- package/dist/component/server/signin.js +2 -1
- package/dist/component/server/signin.js.map +1 -1
- package/dist/component/server/totp.js +2 -1
- package/dist/component/server/totp.js.map +1 -1
- package/dist/component/server/types.d.ts +225 -41
- 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 +1 -1
- package/dist/component/server/users.js.map +1 -1
- package/dist/component/server/utils.js +1 -1
- package/dist/component/server/utils.js.map +1 -1
- package/dist/core/types.d.ts +369 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/factors/device.js +105 -0
- package/dist/factors/device.js.map +1 -0
- package/dist/factors/passkey.js +181 -0
- package/dist/factors/passkey.js.map +1 -0
- package/dist/factors/totp.js +122 -0
- package/dist/factors/totp.js.map +1 -0
- package/dist/providers/credentials.d.ts +5 -2
- package/dist/providers/credentials.d.ts.map +1 -1
- package/dist/providers/credentials.js +2 -0
- package/dist/providers/credentials.js.map +1 -1
- package/dist/providers/email.d.ts +30 -0
- package/dist/providers/email.d.ts.map +1 -1
- package/dist/providers/email.js +24 -0
- package/dist/providers/email.js.map +1 -1
- package/dist/providers/phone.d.ts +28 -0
- package/dist/providers/phone.d.ts.map +1 -1
- package/dist/providers/phone.js +22 -0
- package/dist/providers/phone.js.map +1 -1
- package/dist/runtime/browser.js +68 -0
- package/dist/runtime/browser.js.map +1 -0
- package/dist/runtime/invite.js +51 -0
- package/dist/runtime/invite.js.map +1 -0
- package/dist/runtime/proxy.js +70 -0
- package/dist/runtime/proxy.js.map +1 -0
- package/dist/runtime/storage.js +37 -0
- package/dist/runtime/storage.js.map +1 -0
- package/dist/server/auth.d.ts +218 -28
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +68 -4
- package/dist/server/auth.js.map +1 -1
- package/dist/server/{fx.d.ts → authError.d.ts} +25 -15
- package/dist/server/authError.d.ts.map +1 -0
- package/dist/server/authError.js +34 -0
- package/dist/server/authError.js.map +1 -0
- package/dist/server/{providers.js → config.js} +2 -2
- package/dist/server/config.js.map +1 -0
- package/dist/server/core.d.ts +1436 -0
- package/dist/server/core.d.ts.map +1 -0
- package/dist/server/{domains/core.js → core.js} +107 -88
- package/dist/server/core.js.map +1 -0
- package/dist/server/{provider.d.ts → crypto.d.ts} +2 -2
- package/dist/server/crypto.d.ts.map +1 -0
- package/dist/server/{provider.js → crypto.js} +3 -3
- package/dist/{component/server/provider.js.map → server/crypto.js.map} +1 -1
- package/dist/server/device.js +1 -1
- package/dist/server/device.js.map +1 -1
- package/dist/server/enterprise/config.js +46 -0
- package/dist/server/enterprise/config.js.map +1 -0
- package/dist/server/{domains/sso.d.ts → enterprise/domain.d.ts} +4 -4
- package/dist/server/enterprise/domain.d.ts.map +1 -0
- package/dist/server/{domains/sso.js → enterprise/domain.js} +6 -5
- package/dist/server/enterprise/domain.js.map +1 -0
- package/dist/server/enterprise/http.d.ts +26 -0
- package/dist/server/enterprise/http.d.ts.map +1 -0
- package/dist/server/enterprise/http.js +766 -0
- package/dist/server/enterprise/http.js.map +1 -0
- package/dist/server/enterprise/oidc.js +248 -0
- package/dist/server/enterprise/oidc.js.map +1 -0
- package/dist/server/enterprise/policy.js +85 -0
- package/dist/server/enterprise/policy.js.map +1 -0
- package/dist/server/enterprise/saml.d.ts +1 -0
- package/dist/server/enterprise/saml.js +338 -0
- package/dist/server/enterprise/saml.js.map +1 -0
- package/dist/server/enterprise/scim.d.ts +1 -0
- package/dist/server/enterprise/scim.js +97 -0
- package/dist/server/enterprise/scim.js.map +1 -0
- package/dist/server/enterprise/shared.d.ts +5 -0
- package/dist/server/enterprise/shared.d.ts.map +1 -0
- package/dist/server/enterprise/shared.js +51 -0
- package/dist/server/enterprise/shared.js.map +1 -0
- package/dist/server/enterprise/validators.d.ts +1 -0
- package/dist/server/{enterpriseValidators.js → enterprise/validators.js} +2 -2
- package/dist/server/enterprise/validators.js.map +1 -0
- package/dist/server/errors.d.ts +2 -0
- package/dist/server/errors.d.ts.map +1 -1
- package/dist/server/errors.js +2 -0
- package/dist/server/errors.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 +2 -1
- package/dist/server/http.js.map +1 -1
- package/dist/server/identity.js +1 -1
- package/dist/server/identity.js.map +1 -1
- package/dist/server/index.d.ts +4 -705
- package/dist/server/index.js +4 -1367
- package/dist/server/limits.d.ts +1 -0
- package/dist/server/{ratelimit.js → limits.js} +3 -3
- package/dist/server/limits.js.map +1 -0
- package/dist/server/mounts.d.ts +647 -0
- package/dist/server/mounts.d.ts.map +1 -0
- package/dist/server/mounts.js +643 -0
- package/dist/server/mounts.js.map +1 -0
- package/dist/server/mutations/account.d.ts +8 -8
- package/dist/server/mutations/account.js +3 -3
- package/dist/server/mutations/account.js.map +1 -1
- package/dist/server/mutations/code.d.ts +13 -13
- package/dist/server/mutations/code.js +2 -2
- package/dist/server/mutations/code.js.map +1 -1
- package/dist/server/mutations/index.d.ts +8 -312
- package/dist/server/mutations/index.js +14 -84
- package/dist/server/mutations/invalidate.d.ts +5 -5
- package/dist/server/mutations/invalidate.d.ts.map +1 -1
- package/dist/server/mutations/invalidate.js +1 -1
- package/dist/server/mutations/invalidate.js.map +1 -1
- package/dist/server/mutations/oauth.d.ts +9 -9
- package/dist/server/mutations/oauth.d.ts.map +1 -1
- package/dist/server/mutations/oauth.js +5 -3
- package/dist/server/mutations/oauth.js.map +1 -1
- package/dist/server/mutations/refresh.d.ts +4 -4
- package/dist/server/mutations/refresh.js +1 -1
- package/dist/server/mutations/refresh.js.map +1 -1
- package/dist/server/mutations/register.d.ts +10 -10
- package/dist/server/mutations/register.d.ts.map +1 -1
- package/dist/server/mutations/register.js +3 -3
- package/dist/server/mutations/register.js.map +1 -1
- package/dist/server/mutations/retrieve.d.ts +8 -10
- package/dist/server/mutations/retrieve.d.ts.map +1 -1
- package/dist/server/mutations/retrieve.js +5 -7
- package/dist/server/mutations/retrieve.js.map +1 -1
- package/dist/server/mutations/signature.d.ts +6 -6
- package/dist/server/mutations/signature.d.ts.map +1 -1
- package/dist/server/mutations/signature.js +2 -2
- package/dist/server/mutations/signature.js.map +1 -1
- package/dist/server/mutations/signin.d.ts +1 -1
- package/dist/server/mutations/signin.js +1 -1
- package/dist/server/mutations/signin.js.map +1 -1
- package/dist/server/mutations/signout.d.ts +1 -1
- package/dist/server/mutations/signout.js +1 -1
- package/dist/server/mutations/signout.js.map +1 -1
- package/dist/server/mutations/store/refs.d.ts +12 -0
- package/dist/server/mutations/store/refs.d.ts.map +1 -0
- package/dist/server/mutations/store/refs.js +15 -0
- package/dist/server/mutations/store/refs.js.map +1 -0
- package/dist/server/mutations/store.d.ts +303 -9
- package/dist/server/mutations/store.d.ts.map +1 -1
- package/dist/server/mutations/store.js +80 -10
- package/dist/server/mutations/store.js.map +1 -1
- package/dist/server/mutations/verifier.d.ts +1 -1
- package/dist/server/mutations/verifier.js +1 -1
- package/dist/server/mutations/verifier.js.map +1 -1
- package/dist/server/mutations/verify.d.ts +8 -8
- package/dist/server/mutations/verify.d.ts.map +1 -1
- package/dist/server/mutations/verify.js +4 -3
- package/dist/server/mutations/verify.js.map +1 -1
- package/dist/server/oauth.js +1 -1
- 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 +3 -2
- package/dist/server/passkey.js.map +1 -1
- package/dist/server/redirects.js +1 -1
- package/dist/server/redirects.js.map +1 -1
- package/dist/server/refresh.js +1 -1
- package/dist/server/refresh.js.map +1 -1
- package/dist/server/{factory.d.ts → runtime.d.ts} +16 -16
- package/dist/server/runtime.d.ts.map +1 -0
- package/dist/server/runtime.js +413 -0
- package/dist/server/runtime.js.map +1 -0
- package/dist/server/signin.js +3 -2
- package/dist/server/signin.js.map +1 -1
- package/dist/server/ssr.d.ts +226 -0
- package/dist/server/ssr.d.ts.map +1 -0
- package/dist/server/ssr.js +786 -0
- package/dist/server/ssr.js.map +1 -0
- package/dist/server/totp.js +2 -1
- package/dist/server/totp.js.map +1 -1
- package/dist/server/types.d.ts +242 -42
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/types.js +12 -1
- package/dist/server/types.js.map +1 -1
- package/dist/server/users.js +1 -1
- package/dist/server/users.js.map +1 -1
- package/dist/server/utils.js +1 -1
- package/dist/server/utils.js.map +1 -1
- package/package.json +3 -4
- package/src/authorization/index.ts +46 -0
- package/src/cli/index.ts +38 -20
- package/src/client/core/types.ts +437 -0
- package/src/client/factors/device.ts +160 -0
- package/src/client/factors/passkey.ts +282 -0
- package/src/client/factors/totp.ts +150 -0
- package/src/client/index.ts +133 -1267
- package/src/client/runtime/browser.ts +112 -0
- package/src/client/runtime/invite.ts +65 -0
- package/src/client/runtime/proxy.ts +111 -0
- package/src/client/runtime/storage.ts +79 -0
- package/src/component/_generated/api.ts +40 -12
- package/src/component/_generated/component.ts +1772 -1798
- package/src/component/model.ts +6 -0
- package/src/component/public/enterprise/audit.ts +120 -0
- package/src/component/public/enterprise/core.ts +354 -0
- package/src/component/public/enterprise/domains.ts +323 -0
- package/src/component/public/enterprise/scim.ts +396 -0
- package/src/component/public/enterprise/secrets.ts +132 -0
- package/src/component/public/enterprise/webhooks.ts +306 -0
- package/src/component/public/factors/devices.ts +223 -0
- package/src/component/public/factors/passkeys.ts +242 -0
- package/src/component/public/factors/totp.ts +258 -0
- package/src/component/public/groups/core.ts +481 -0
- package/src/component/public/groups/invites.ts +602 -0
- package/src/component/public/groups/members.ts +409 -0
- package/src/component/public/identity/accounts.ts +206 -0
- package/src/component/public/identity/codes.ts +148 -0
- package/src/component/public/identity/sessions.ts +209 -0
- package/src/component/public/identity/tokens.ts +250 -0
- package/src/component/public/identity/users.ts +354 -0
- package/src/component/public/identity/verifiers.ts +157 -0
- package/src/component/public/security/keys.ts +365 -0
- package/src/component/public/security/limits.ts +173 -0
- package/src/component/public.ts +27 -5
- package/src/providers/credentials.ts +4 -5
- package/src/providers/email.ts +30 -0
- package/src/providers/phone.ts +28 -0
- package/src/server/auth.ts +282 -28
- package/src/server/authError.ts +44 -0
- package/src/server/core.ts +2095 -0
- package/src/server/{provider.ts → crypto.ts} +1 -1
- package/src/server/device.ts +1 -1
- package/src/server/enterprise/config.ts +51 -0
- package/src/server/{domains/sso.ts → enterprise/domain.ts} +4 -2
- package/src/server/enterprise/http.ts +1324 -0
- package/src/server/enterprise/oidc.ts +500 -0
- package/src/server/enterprise/policy.ts +128 -0
- package/src/server/enterprise/saml.ts +578 -0
- package/src/server/enterprise/scim.ts +135 -0
- package/src/server/enterprise/shared.ts +134 -0
- package/src/server/errors.ts +2 -0
- package/src/server/http.ts +3 -1
- package/src/server/identity.ts +1 -1
- package/src/server/index.ts +32 -2605
- package/src/server/{ratelimit.ts → limits.ts} +1 -1
- package/src/server/mounts.ts +948 -0
- package/src/server/mutations/account.ts +4 -4
- package/src/server/mutations/code.ts +3 -3
- package/src/server/mutations/index.ts +8 -148
- package/src/server/mutations/invalidate.ts +2 -2
- package/src/server/mutations/oauth.ts +8 -6
- package/src/server/mutations/refresh.ts +3 -3
- package/src/server/mutations/register.ts +3 -3
- package/src/server/mutations/retrieve.ts +4 -8
- package/src/server/mutations/signature.ts +3 -3
- package/src/server/mutations/signin.ts +2 -2
- package/src/server/mutations/signout.ts +2 -2
- package/src/server/mutations/store/refs.ts +10 -0
- package/src/server/mutations/store.ts +137 -9
- package/src/server/mutations/verifier.ts +2 -2
- package/src/server/mutations/verify.ts +5 -5
- package/src/server/oauth.ts +1 -1
- package/src/server/passkey.ts +3 -4
- package/src/server/redirects.ts +1 -1
- package/src/server/refresh.ts +1 -1
- package/src/server/runtime.ts +880 -0
- package/src/server/signin.ts +3 -1
- package/src/server/ssr.ts +1764 -0
- package/src/server/totp.ts +3 -1
- package/src/server/types.ts +254 -43
- package/src/server/users.ts +1 -1
- package/src/server/utils.ts +1 -1
- package/src/test.ts +25 -0
- package/dist/component/public/enterprise.d.ts +0 -54
- package/dist/component/public/enterprise.d.ts.map +0 -1
- package/dist/component/public/enterprise.js +0 -515
- package/dist/component/public/enterprise.js.map +0 -1
- package/dist/component/public/factors.d.ts +0 -52
- package/dist/component/public/factors.d.ts.map +0 -1
- package/dist/component/public/factors.js +0 -285
- package/dist/component/public/factors.js.map +0 -1
- package/dist/component/public/groups.d.ts +0 -124
- package/dist/component/public/groups.d.ts.map +0 -1
- package/dist/component/public/groups.js +0 -680
- package/dist/component/public/groups.js.map +0 -1
- package/dist/component/public/identity.d.ts +0 -93
- package/dist/component/public/identity.d.ts.map +0 -1
- package/dist/component/public/identity.js +0 -426
- package/dist/component/public/identity.js.map +0 -1
- package/dist/component/public/keys.d.ts +0 -41
- package/dist/component/public/keys.d.ts.map +0 -1
- package/dist/component/public/keys.js +0 -157
- package/dist/component/public/keys.js.map +0 -1
- package/dist/component/public/shared.d.ts +0 -26
- package/dist/component/public/shared.d.ts.map +0 -1
- package/dist/component/public/shared.js +0 -32
- package/dist/component/public/shared.js.map +0 -1
- package/dist/component/server/domains/core.js.map +0 -1
- package/dist/component/server/domains/sso.js.map +0 -1
- package/dist/component/server/factory.d.ts.map +0 -1
- package/dist/component/server/factory.js +0 -1134
- package/dist/component/server/factory.js.map +0 -1
- package/dist/component/server/fx.js +0 -66
- package/dist/component/server/fx.js.map +0 -1
- package/dist/component/server/mutations/index.js +0 -85
- package/dist/component/server/mutations/index.js.map +0 -1
- package/dist/component/server/providers.js.map +0 -1
- package/dist/component/server/ratelimit.js.map +0 -1
- package/dist/component/server/sso.js +0 -830
- package/dist/component/server/sso.js.map +0 -1
- package/dist/server/domains/core.d.ts +0 -449
- package/dist/server/domains/core.d.ts.map +0 -1
- package/dist/server/domains/core.js.map +0 -1
- package/dist/server/domains/sso.d.ts.map +0 -1
- package/dist/server/domains/sso.js.map +0 -1
- package/dist/server/enterpriseValidators.js.map +0 -1
- package/dist/server/factory.d.ts.map +0 -1
- package/dist/server/factory.js +0 -1134
- package/dist/server/factory.js.map +0 -1
- package/dist/server/fx.d.ts.map +0 -1
- package/dist/server/fx.js +0 -66
- package/dist/server/fx.js.map +0 -1
- package/dist/server/index.d.ts.map +0 -1
- package/dist/server/index.js.map +0 -1
- package/dist/server/mutations/index.d.ts.map +0 -1
- package/dist/server/mutations/index.js.map +0 -1
- package/dist/server/provider.d.ts.map +0 -1
- package/dist/server/provider.js.map +0 -1
- package/dist/server/providers.js.map +0 -1
- package/dist/server/ratelimit.js.map +0 -1
- package/dist/server/sso.js +0 -830
- package/dist/server/sso.js.map +0 -1
- package/dist/server/version.d.ts +0 -5
- package/dist/server/version.d.ts.map +0 -1
- package/dist/server/version.js +0 -6
- package/dist/server/version.js.map +0 -1
- package/src/component/public/enterprise.ts +0 -753
- package/src/component/public/factors.ts +0 -333
- package/src/component/public/groups.ts +0 -1066
- package/src/component/public/identity.ts +0 -569
- package/src/component/public/keys.ts +0 -209
- package/src/component/public/shared.ts +0 -119
- package/src/server/domains/core.ts +0 -1096
- package/src/server/factory.ts +0 -2181
- package/src/server/fx.ts +0 -152
- package/src/server/sso.ts +0 -1516
- package/src/server/version.ts +0 -2
- /package/dist/server/{enterpriseValidators.d.ts → config.d.ts} +0 -0
- /package/dist/server/{providers.d.ts → enterprise/config.d.ts} +0 -0
- /package/dist/server/{ratelimit.d.ts → enterprise/oidc.d.ts} +0 -0
- /package/dist/server/{sso.d.ts → enterprise/policy.d.ts} +0 -0
- /package/src/server/{providers.ts → config.ts} +0 -0
- /package/src/server/{enterpriseValidators.ts → enterprise/validators.ts} +0 -0
package/src/client/index.ts
CHANGED
|
@@ -1,9 +1,38 @@
|
|
|
1
1
|
import { Fx } from "@robelest/fx";
|
|
2
2
|
import { ConvexHttpClient } from "convex/browser";
|
|
3
|
-
import type { FunctionReference } from "convex/server";
|
|
4
3
|
import { ConvexError, Value } from "convex/values";
|
|
5
4
|
|
|
6
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
|
+
import type {
|
|
18
|
+
AuthApiRefs,
|
|
19
|
+
AuthClient,
|
|
20
|
+
AuthFlowContext,
|
|
21
|
+
AuthHandshakeErrorCode,
|
|
22
|
+
AuthSession,
|
|
23
|
+
AuthState,
|
|
24
|
+
ClientOptions,
|
|
25
|
+
ConvexTransport,
|
|
26
|
+
DeviceClient,
|
|
27
|
+
DeviceCodeResult,
|
|
28
|
+
HandshakeWaiter,
|
|
29
|
+
PasskeyClient,
|
|
30
|
+
PendingInvite,
|
|
31
|
+
SignInActionResult,
|
|
32
|
+
SignInResult,
|
|
33
|
+
Storage,
|
|
34
|
+
TotpClient,
|
|
35
|
+
} from "./core/types";
|
|
7
36
|
|
|
8
37
|
// Re-export error utilities so consumers can import from `@robelest/convex-auth/client`.
|
|
9
38
|
export {
|
|
@@ -12,257 +41,19 @@ export {
|
|
|
12
41
|
AUTH_ERRORS,
|
|
13
42
|
type AuthErrorCode,
|
|
14
43
|
} from "../server/errors";
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
fetchToken: (args: {
|
|
29
|
-
forceRefreshToken: boolean;
|
|
30
|
-
}) => Promise<string | null | undefined>,
|
|
31
|
-
onChange?: (isAuthenticated: boolean) => void,
|
|
32
|
-
): void;
|
|
33
|
-
clearAuth?(): void;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** Pluggable key-value storage (defaults to `localStorage`). */
|
|
37
|
-
export interface Storage {
|
|
38
|
-
getItem(
|
|
39
|
-
key: string,
|
|
40
|
-
): string | null | undefined | Promise<string | null | undefined>;
|
|
41
|
-
setItem(key: string, value: string): void | Promise<void>;
|
|
42
|
-
removeItem(key: string): void | Promise<void>;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
type AuthSession = {
|
|
46
|
-
token: string;
|
|
47
|
-
refreshToken: string;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
type SignInActionResult =
|
|
51
|
-
| { kind: "signedIn"; tokens: AuthSession | null }
|
|
52
|
-
| { kind: "redirect"; redirect: string; verifier: string }
|
|
53
|
-
| { kind: "started" }
|
|
54
|
-
| { kind: "passkeyOptions"; options: Record<string, any>; verifier: string }
|
|
55
|
-
| { kind: "totpRequired"; verifier: string }
|
|
56
|
-
| {
|
|
57
|
-
kind: "totpSetup";
|
|
58
|
-
totpSetup: { uri: string; secret: string; totpId: string };
|
|
59
|
-
verifier: string;
|
|
60
|
-
}
|
|
61
|
-
| {
|
|
62
|
-
kind: "deviceCode";
|
|
63
|
-
deviceCode: {
|
|
64
|
-
deviceCode: string;
|
|
65
|
-
userCode: string;
|
|
66
|
-
verificationUri: string;
|
|
67
|
-
verificationUriComplete: string;
|
|
68
|
-
expiresIn: number;
|
|
69
|
-
interval: number;
|
|
70
|
-
};
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Device code response returned when signing in with the `"device"` provider.
|
|
75
|
-
*
|
|
76
|
-
* The device displays the `userCode` (or `verification_uri_complete`) and
|
|
77
|
-
* polls via `auth.device.poll()` until the user authorizes.
|
|
78
|
-
*/
|
|
79
|
-
export type DeviceCodeResult = {
|
|
80
|
-
/** High-entropy device code used for polling (keep secret). */
|
|
81
|
-
deviceCode: string;
|
|
82
|
-
/** Short human-readable code the user enters (e.g. "WDJB-MJHT"). */
|
|
83
|
-
userCode: string;
|
|
84
|
-
/** Base verification URL (e.g. "https://myapp.com/device"). */
|
|
85
|
-
verification_uri: string;
|
|
86
|
-
/** Verification URL with user code pre-filled as `?code=XXXX-XXXX`. */
|
|
87
|
-
verification_uri_complete: string;
|
|
88
|
-
/** Lifetime of the codes in seconds. */
|
|
89
|
-
expiresIn: number;
|
|
90
|
-
/** Minimum polling interval in seconds. */
|
|
91
|
-
interval: number;
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Result of a `signIn` call.
|
|
96
|
-
*
|
|
97
|
-
* - `kind: "signedIn"` — credentials were accepted and the user is authenticated.
|
|
98
|
-
* - `kind: "redirect"` — OAuth flow initiated; redirect the user to `redirect.toString()`.
|
|
99
|
-
* - `kind: "totpRequired"` — credentials valid but 2FA is needed; call `auth.totp.verify()`.
|
|
100
|
-
* - `kind: "deviceCode"` — device flow initiated; display the code and poll via `auth.device.poll()`.
|
|
101
|
-
* - `kind: "started"` — a non-immediate flow started (for example email/phone verification).
|
|
102
|
-
*/
|
|
103
|
-
export type SignInResult =
|
|
104
|
-
| { kind: "signedIn" }
|
|
105
|
-
| { kind: "redirect"; redirect: URL; verifier: string }
|
|
106
|
-
| { kind: "totpRequired"; verifier: string }
|
|
107
|
-
| { kind: "deviceCode"; deviceCode: DeviceCodeResult }
|
|
108
|
-
| { kind: "started" };
|
|
109
|
-
|
|
110
|
-
/** Reactive auth state snapshot returned by `auth.state` and `auth.onChange`. */
|
|
111
|
-
export type AuthState = {
|
|
112
|
-
/** High-level auth phase for deterministic UI state handling. */
|
|
113
|
-
phase: "loading" | "handshake" | "authenticated" | "unauthenticated";
|
|
114
|
-
/** `true` during initial hydration before the first token is resolved. */
|
|
115
|
-
isLoading: boolean;
|
|
116
|
-
/** `true` only after Convex confirms authentication with the backend. */
|
|
117
|
-
isAuthenticated: boolean;
|
|
118
|
-
/** The raw JWT string, or `null` when not authenticated. */
|
|
119
|
-
token: string | null;
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Typed Convex API references for the auth functions.
|
|
124
|
-
* Pass these from your generated `api` object.
|
|
125
|
-
*/
|
|
126
|
-
export type AuthApiRefs<
|
|
127
|
-
HasPasskey extends boolean = boolean,
|
|
128
|
-
HasTotp extends boolean = boolean,
|
|
129
|
-
HasDevice extends boolean = boolean,
|
|
130
|
-
> = {
|
|
131
|
-
signIn: FunctionReference<"action", "public", any, any>;
|
|
132
|
-
signOut: FunctionReference<"action", "public", any, any>;
|
|
133
|
-
store: FunctionReference<"mutation", "public", any, any>;
|
|
134
|
-
/** @internal Set automatically by `createAuth` — do not set manually. */
|
|
135
|
-
_capabilities?: {
|
|
136
|
-
passkey: HasPasskey;
|
|
137
|
-
totp: HasTotp;
|
|
138
|
-
device: HasDevice;
|
|
139
|
-
};
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
/** Passkey (WebAuthn) client-side helpers. */
|
|
143
|
-
export interface PasskeyClient {
|
|
144
|
-
isSupported(): boolean;
|
|
145
|
-
isAutofillSupported(): Promise<boolean>;
|
|
146
|
-
register(opts?: Record<string, any>): Promise<SignInResult>;
|
|
147
|
-
authenticate(opts?: Record<string, any>): Promise<SignInResult>;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/** TOTP two-factor authentication client-side helpers. */
|
|
151
|
-
export interface TotpClient {
|
|
152
|
-
setup(opts?: Record<string, any>): Promise<Record<string, any>>;
|
|
153
|
-
confirm(opts: Record<string, any>): Promise<void>;
|
|
154
|
-
verify(opts: Record<string, any>): Promise<void>;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/** Device authorization (RFC 8628) client-side helpers. */
|
|
158
|
-
export interface DeviceClient {
|
|
159
|
-
poll(code: any): Promise<void>;
|
|
160
|
-
verify(userCode: string): Promise<void>;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/** Extract capability flags from an AuthApiRefs type. */
|
|
164
|
-
type InferCaps<Api extends AuthApiRefs<boolean, boolean, boolean>> =
|
|
165
|
-
Api extends AuthApiRefs<infer P, infer T, infer D>
|
|
166
|
-
? { passkey: P; totp: T; device: D }
|
|
167
|
-
: { passkey: boolean; totp: boolean; device: boolean };
|
|
168
|
-
|
|
169
|
-
/** Pending invite detected from URL or recovered from storage after redirect. */
|
|
170
|
-
export interface PendingInvite {
|
|
171
|
-
readonly token: string;
|
|
172
|
-
readonly email: string | null;
|
|
173
|
-
/** Consume the invite: clears storage/URL params and returns the token. */
|
|
174
|
-
accept(): Promise<{ ok: boolean; token?: string; message?: string }>;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/** Base auth client — always present. */
|
|
178
|
-
export interface AuthClientBase {
|
|
179
|
-
readonly state: AuthState;
|
|
180
|
-
/** SSR-safe URL param reader. Uses the `location` option or `window.location`. */
|
|
181
|
-
param: (name: string) => string | null;
|
|
182
|
-
/** Pending invite from URL `?invite=` or recovered from storage. Null if none. */
|
|
183
|
-
readonly invite: PendingInvite | null;
|
|
184
|
-
signIn: (
|
|
185
|
-
provider: string,
|
|
186
|
-
params?: Record<string, any>,
|
|
187
|
-
) => Promise<SignInResult>;
|
|
188
|
-
signOut: () => Promise<void>;
|
|
189
|
-
onChange: (callback: () => void) => () => void;
|
|
190
|
-
destroy: () => void;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Auth client return type — conditionally includes `passkey`, `totp`, and
|
|
195
|
-
* `device` helpers based on the capabilities in the `AuthApiRefs` type.
|
|
196
|
-
*/
|
|
197
|
-
export type AuthClient<
|
|
198
|
-
Api extends AuthApiRefs<boolean, boolean, boolean> = AuthApiRefs,
|
|
199
|
-
> = AuthClientBase &
|
|
200
|
-
(InferCaps<Api>["passkey"] extends true ? { passkey: PasskeyClient } : {}) &
|
|
201
|
-
(InferCaps<Api>["totp"] extends true ? { totp: TotpClient } : {}) &
|
|
202
|
-
(InferCaps<Api>["device"] extends true ? { device: DeviceClient } : {});
|
|
203
|
-
|
|
204
|
-
/** Options for {@link client}. */
|
|
205
|
-
export type ClientOptions<
|
|
206
|
-
Api extends AuthApiRefs<boolean, boolean, boolean> = AuthApiRefs,
|
|
207
|
-
> = {
|
|
208
|
-
/** Any Convex client (`ConvexClient` or `ConvexReactClient`). */
|
|
209
|
-
convex: ConvexTransport;
|
|
210
|
-
/**
|
|
211
|
-
* Typed Convex API references for the auth functions.
|
|
212
|
-
* Required in SPA mode. Optional when `proxyPath` is set (proxy mode
|
|
213
|
-
* routes through the SSR proxy instead of calling Convex directly).
|
|
214
|
-
*
|
|
215
|
-
* ```ts
|
|
216
|
-
* import { api } from "../convex/_generated/api";
|
|
217
|
-
* client({
|
|
218
|
-
* convex,
|
|
219
|
-
* api: api.auth,
|
|
220
|
-
* });
|
|
221
|
-
* ```
|
|
222
|
-
*/
|
|
223
|
-
api?: Api;
|
|
224
|
-
/**
|
|
225
|
-
* Convex deployment URL. Derived automatically from the client internals
|
|
226
|
-
* when omitted — pass explicitly only if auto-detection fails.
|
|
227
|
-
*/
|
|
228
|
-
url?: string;
|
|
229
|
-
/**
|
|
230
|
-
* Key-value storage for persisting tokens.
|
|
231
|
-
*
|
|
232
|
-
* - Defaults to `localStorage` in SPA mode.
|
|
233
|
-
* - Defaults to `null` (in-memory only) when `proxyPath` is set,
|
|
234
|
-
* since httpOnly cookies handle persistence.
|
|
235
|
-
*/
|
|
236
|
-
storage?: Storage | null;
|
|
237
|
-
/** Override how the URL bar is updated after OAuth code exchange. */
|
|
238
|
-
replaceUrl?: (relativeUrl: string) => void | Promise<void>;
|
|
239
|
-
/**
|
|
240
|
-
* SSR proxy endpoint (e.g. `"/api/auth"`).
|
|
241
|
-
*
|
|
242
|
-
* When set, `signIn`/`signOut`/token refresh POST to this URL
|
|
243
|
-
* (with `credentials: "include"`) instead of calling Convex directly.
|
|
244
|
-
* The server handles httpOnly cookies for token persistence.
|
|
245
|
-
*
|
|
246
|
-
* Pair with {@link ClientOptions.tokenSeed} for flash-free SSR hydration.
|
|
247
|
-
*/
|
|
248
|
-
proxyPath?: string;
|
|
249
|
-
/**
|
|
250
|
-
* JWT from server-side hydration.
|
|
251
|
-
*
|
|
252
|
-
* In proxy mode the server reads the JWT from an httpOnly cookie
|
|
253
|
-
* and passes it to the client during SSR. This avoids a loading
|
|
254
|
-
* flash on first render — the client is immediately authenticated.
|
|
255
|
-
*/
|
|
256
|
-
tokenSeed?: string | null;
|
|
257
|
-
/**
|
|
258
|
-
* SSR-safe URL source for reading query parameters.
|
|
259
|
-
*
|
|
260
|
-
* - SvelteKit: `page.url` (from `$app/state`)
|
|
261
|
-
* - Next.js: pass from server props or `useSearchParams()`
|
|
262
|
-
* - SPA: omit (defaults to `window.location` with SSR guard)
|
|
263
|
-
*/
|
|
264
|
-
location?: URL | (() => URL | null);
|
|
265
|
-
};
|
|
44
|
+
export type {
|
|
45
|
+
AuthApiRefs,
|
|
46
|
+
AuthClient,
|
|
47
|
+
AuthState,
|
|
48
|
+
ClientOptions,
|
|
49
|
+
DeviceClient,
|
|
50
|
+
DeviceCodeResult,
|
|
51
|
+
PasskeyClient,
|
|
52
|
+
PendingInvite,
|
|
53
|
+
SignInResult,
|
|
54
|
+
Storage,
|
|
55
|
+
TotpClient,
|
|
56
|
+
} from "./core/types";
|
|
266
57
|
|
|
267
58
|
const VERIFIER_STORAGE_KEY = "__convexAuthOAuthVerifier";
|
|
268
59
|
const JWT_STORAGE_KEY = "__convexAuthJWT";
|
|
@@ -273,24 +64,6 @@ const INVITE_EMAIL_KEY = "__convexAuthPendingInviteEmail";
|
|
|
273
64
|
const RETRY_BASE_MS = 500;
|
|
274
65
|
const RETRY_MAX_RETRIES = 2;
|
|
275
66
|
const AUTH_HANDSHAKE_TIMEOUT_MS = 5000;
|
|
276
|
-
const NETWORK_ERROR_PATTERN = /(network|fetch|load failed|failed to fetch)/i;
|
|
277
|
-
|
|
278
|
-
type AuthHandshakeErrorCode =
|
|
279
|
-
| "AUTH_HANDSHAKE_TIMEOUT"
|
|
280
|
-
| "AUTH_HANDSHAKE_REJECTED";
|
|
281
|
-
|
|
282
|
-
type AuthFlowContext = {
|
|
283
|
-
provider?: string;
|
|
284
|
-
flow: string;
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
type HandshakeWaiter = {
|
|
288
|
-
epoch: number;
|
|
289
|
-
context: AuthFlowContext;
|
|
290
|
-
resolve: () => void;
|
|
291
|
-
reject: (error: ConvexError<Value>) => void;
|
|
292
|
-
timeoutId: ReturnType<typeof setTimeout>;
|
|
293
|
-
};
|
|
294
67
|
|
|
295
68
|
/**
|
|
296
69
|
* Resolve the Convex deployment URL from the client.
|
|
@@ -308,28 +81,6 @@ function resolveUrl(convex: ConvexTransport, explicit?: string): string {
|
|
|
308
81
|
);
|
|
309
82
|
}
|
|
310
83
|
|
|
311
|
-
function isTransientNetworkError(error: unknown): boolean {
|
|
312
|
-
return (
|
|
313
|
-
error instanceof TypeError ||
|
|
314
|
-
(error instanceof Error && NETWORK_ERROR_PATTERN.test(error.message || ""))
|
|
315
|
-
);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
function isRetriableProxyRefreshError(error: unknown): boolean {
|
|
319
|
-
if (isTransientNetworkError(error)) {
|
|
320
|
-
return true;
|
|
321
|
-
}
|
|
322
|
-
if (!(error instanceof Error)) {
|
|
323
|
-
return false;
|
|
324
|
-
}
|
|
325
|
-
const statusMatch = error.message.match(/Proxy request failed:\s*(\d{3})/);
|
|
326
|
-
if (statusMatch === null) {
|
|
327
|
-
return false;
|
|
328
|
-
}
|
|
329
|
-
const statusCode = Number(statusMatch[1]);
|
|
330
|
-
return statusCode >= 500 && statusCode < 600;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
84
|
/**
|
|
334
85
|
* Create a framework-agnostic auth client.
|
|
335
86
|
*
|
|
@@ -362,7 +113,10 @@ function isRetriableProxyRefreshError(error: unknown): boolean {
|
|
|
362
113
|
* holds the JWT in memory only.
|
|
363
114
|
*
|
|
364
115
|
* @param options - Client configuration. See {@link ClientOptions}.
|
|
116
|
+
* @typeParam Api - An AuthApiRefs type determining which factor helpers are available.
|
|
365
117
|
* @returns Auth client with conditional `passkey`, `totp`, and `device` helpers.
|
|
118
|
+
* @throws {Error} When the Convex deployment URL cannot be determined and `url` is not passed explicitly.
|
|
119
|
+
* @throws {Error} When `proxyPath` is not set and the `api` option is missing.
|
|
366
120
|
*/
|
|
367
121
|
export function client<
|
|
368
122
|
Api extends AuthApiRefs<boolean, boolean, boolean> = AuthApiRefs,
|
|
@@ -409,6 +163,21 @@ export function client<
|
|
|
409
163
|
return null;
|
|
410
164
|
}
|
|
411
165
|
|
|
166
|
+
/**
|
|
167
|
+
* SSR-safe URL parameter reader.
|
|
168
|
+
*
|
|
169
|
+
* Uses the `location` option if provided, otherwise falls back to
|
|
170
|
+
* `window.location` (returns `null` during SSR where `window` is unavailable).
|
|
171
|
+
*
|
|
172
|
+
* @param name - The query parameter name.
|
|
173
|
+
* @returns The parameter value, or `null` if not present or in SSR.
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```ts
|
|
177
|
+
* const workspaceId = auth.param("workspace");
|
|
178
|
+
* const tab = auth.param("tab") ?? "issues";
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
412
181
|
function param(name: string): string | null {
|
|
413
182
|
const loc = getLocation();
|
|
414
183
|
return loc?.searchParams.get(name) ?? null;
|
|
@@ -438,6 +207,14 @@ export function client<
|
|
|
438
207
|
? proxy.replace(/[^a-zA-Z0-9]/g, "")
|
|
439
208
|
: url!.replace(/[^a-zA-Z0-9]/g, "");
|
|
440
209
|
const key = (name: string) => `${name}_${escapedNamespace}`;
|
|
210
|
+
const {
|
|
211
|
+
get: storageGet,
|
|
212
|
+
set: storageSet,
|
|
213
|
+
remove: storageRemove,
|
|
214
|
+
} = createStorageHelpers({ storage, key });
|
|
215
|
+
const { isAbsoluteUrl, proxyFetch, resolveProxyUrl } = createProxyHelpers({
|
|
216
|
+
proxy,
|
|
217
|
+
});
|
|
441
218
|
const subscribers = new Set<() => void>();
|
|
442
219
|
let disposeStorageListener: (() => void) | null = null;
|
|
443
220
|
|
|
@@ -636,126 +413,18 @@ export function client<
|
|
|
636
413
|
}
|
|
637
414
|
};
|
|
638
415
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
}).pipe(
|
|
652
|
-
Fx.inspect((error) =>
|
|
653
|
-
Fx.sync(() =>
|
|
654
|
-
console.error(
|
|
655
|
-
`[convex-auth] Failed to read ${name} from storage:`,
|
|
656
|
-
error,
|
|
657
|
-
),
|
|
658
|
-
),
|
|
659
|
-
),
|
|
660
|
-
Fx.recover(() => Fx.succeed(null)),
|
|
661
|
-
),
|
|
662
|
-
);
|
|
663
|
-
};
|
|
664
|
-
|
|
665
|
-
const storageSet = async (name: string, value: string): Promise<void> => {
|
|
666
|
-
if (!storage) {
|
|
667
|
-
return;
|
|
668
|
-
}
|
|
669
|
-
await Fx.run(
|
|
670
|
-
Fx.from({
|
|
671
|
-
ok: () => storage.setItem(key(name), value),
|
|
672
|
-
err: (e) => e,
|
|
673
|
-
}).pipe(
|
|
674
|
-
Fx.inspect((error) =>
|
|
675
|
-
Fx.sync(() =>
|
|
676
|
-
console.error(
|
|
677
|
-
`[convex-auth] Failed to write ${name} to storage:`,
|
|
678
|
-
error,
|
|
679
|
-
),
|
|
680
|
-
),
|
|
681
|
-
),
|
|
682
|
-
Fx.recover(() => Fx.succeed(undefined)),
|
|
683
|
-
),
|
|
684
|
-
);
|
|
685
|
-
};
|
|
686
|
-
|
|
687
|
-
const storageRemove = async (name: string): Promise<void> => {
|
|
688
|
-
if (!storage) {
|
|
689
|
-
return;
|
|
690
|
-
}
|
|
691
|
-
await Fx.run(
|
|
692
|
-
Fx.from({
|
|
693
|
-
ok: () => storage.removeItem(key(name)),
|
|
694
|
-
err: (e) => e,
|
|
695
|
-
}).pipe(
|
|
696
|
-
Fx.inspect((error) =>
|
|
697
|
-
Fx.sync(() =>
|
|
698
|
-
console.error(
|
|
699
|
-
`[convex-auth] Failed to remove ${name} from storage:`,
|
|
700
|
-
error,
|
|
701
|
-
),
|
|
702
|
-
),
|
|
703
|
-
),
|
|
704
|
-
Fx.recover(() => Fx.succeed(undefined)),
|
|
705
|
-
),
|
|
706
|
-
);
|
|
707
|
-
};
|
|
708
|
-
|
|
709
|
-
// ---------------------------------------------------------------------------
|
|
710
|
-
// Invite lifecycle
|
|
711
|
-
// ---------------------------------------------------------------------------
|
|
712
|
-
|
|
713
|
-
let pendingInvite: { token: string; email: string | null } | null = null;
|
|
714
|
-
|
|
715
|
-
// Load invite from URL params (synchronous), then try storage (async, lazy)
|
|
716
|
-
const urlInviteToken = param("invite");
|
|
717
|
-
if (urlInviteToken) {
|
|
718
|
-
pendingInvite = { token: urlInviteToken, email: param("email") };
|
|
719
|
-
} else {
|
|
720
|
-
// Recover invite from storage after OAuth redirect (fire-and-forget init)
|
|
721
|
-
void (async () => {
|
|
722
|
-
const storedToken = await storageGet(INVITE_TOKEN_KEY);
|
|
723
|
-
if (storedToken && !pendingInvite) {
|
|
724
|
-
pendingInvite = {
|
|
725
|
-
token: storedToken,
|
|
726
|
-
email: (await storageGet(INVITE_EMAIL_KEY)) ?? null,
|
|
727
|
-
};
|
|
728
|
-
void storageRemove(INVITE_TOKEN_KEY);
|
|
729
|
-
void storageRemove(INVITE_EMAIL_KEY);
|
|
730
|
-
}
|
|
731
|
-
})();
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
async function persistInvite() {
|
|
735
|
-
if (!pendingInvite) return;
|
|
736
|
-
await storageSet(INVITE_TOKEN_KEY, pendingInvite.token);
|
|
737
|
-
if (pendingInvite.email) {
|
|
738
|
-
await storageSet(INVITE_EMAIL_KEY, pendingInvite.email);
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
/**
|
|
743
|
-
* Consume the pending invite: clears storage/URL and returns the token.
|
|
744
|
-
* The app uses the returned token to call its own accept mutation.
|
|
745
|
-
*/
|
|
746
|
-
async function acceptInvite(): Promise<{
|
|
747
|
-
ok: boolean;
|
|
748
|
-
token?: string;
|
|
749
|
-
message?: string;
|
|
750
|
-
}> {
|
|
751
|
-
if (!pendingInvite) return { ok: false, message: "No pending invite" };
|
|
752
|
-
const { token } = pendingInvite;
|
|
753
|
-
pendingInvite = null;
|
|
754
|
-
void storageRemove(INVITE_TOKEN_KEY);
|
|
755
|
-
void storageRemove(INVITE_EMAIL_KEY);
|
|
756
|
-
cleanUrlParams(["invite", "email"]);
|
|
757
|
-
return { ok: true, token };
|
|
758
|
-
}
|
|
416
|
+
const inviteManager = createInviteManager({
|
|
417
|
+
param,
|
|
418
|
+
storageGet,
|
|
419
|
+
storageSet,
|
|
420
|
+
storageRemove,
|
|
421
|
+
cleanUrlParams,
|
|
422
|
+
tokenKey: INVITE_TOKEN_KEY,
|
|
423
|
+
emailKey: INVITE_EMAIL_KEY,
|
|
424
|
+
});
|
|
425
|
+
const getPendingInvite = () => inviteManager.getPendingInvite();
|
|
426
|
+
const persistInvite = () => inviteManager.persistInvite();
|
|
427
|
+
const acceptInvite = () => inviteManager.acceptInvite();
|
|
759
428
|
|
|
760
429
|
// ---------------------------------------------------------------------------
|
|
761
430
|
// Token management
|
|
@@ -863,87 +532,6 @@ export function client<
|
|
|
863
532
|
return true;
|
|
864
533
|
};
|
|
865
534
|
|
|
866
|
-
// ---------------------------------------------------------------------------
|
|
867
|
-
// Proxy fetch helper
|
|
868
|
-
// ---------------------------------------------------------------------------
|
|
869
|
-
|
|
870
|
-
const resolveProxyUrl = () => {
|
|
871
|
-
const origin =
|
|
872
|
-
typeof window !== "undefined" &&
|
|
873
|
-
typeof window.location?.origin === "string"
|
|
874
|
-
? window.location.origin
|
|
875
|
-
: typeof location !== "undefined" && typeof location.origin === "string"
|
|
876
|
-
? location.origin
|
|
877
|
-
: null;
|
|
878
|
-
if (origin !== null) {
|
|
879
|
-
return new URL(proxy!, origin).toString();
|
|
880
|
-
}
|
|
881
|
-
return Fx.run(
|
|
882
|
-
Fx.from({
|
|
883
|
-
ok: () => new URL(proxy!).toString(),
|
|
884
|
-
err: () => proxy! as string,
|
|
885
|
-
}).pipe(Fx.recover((fallback) => Fx.succeed(fallback))),
|
|
886
|
-
);
|
|
887
|
-
};
|
|
888
|
-
|
|
889
|
-
const isAbsoluteUrl = (value: string) => {
|
|
890
|
-
return Fx.run(
|
|
891
|
-
Fx.from({
|
|
892
|
-
ok: () => {
|
|
893
|
-
new URL(value);
|
|
894
|
-
return true;
|
|
895
|
-
},
|
|
896
|
-
err: () => false as const,
|
|
897
|
-
}).pipe(Fx.recover((v) => Fx.succeed(v))),
|
|
898
|
-
);
|
|
899
|
-
};
|
|
900
|
-
|
|
901
|
-
const proxyFetch = async (body: Record<string, unknown>) => {
|
|
902
|
-
const proxyUrl = await resolveProxyUrl();
|
|
903
|
-
if (typeof window === "undefined" && !(await isAbsoluteUrl(proxyUrl))) {
|
|
904
|
-
throw new Error(
|
|
905
|
-
`Cannot call relative proxy URL \`${proxy!}\` without a browser origin. ` +
|
|
906
|
-
"Pass an absolute proxy URL for server runtimes.",
|
|
907
|
-
);
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
const response = await fetch(proxyUrl, {
|
|
911
|
-
method: "POST",
|
|
912
|
-
headers: { "Content-Type": "application/json" },
|
|
913
|
-
credentials: "include",
|
|
914
|
-
body: JSON.stringify(body),
|
|
915
|
-
});
|
|
916
|
-
if (!response.ok) {
|
|
917
|
-
const errorBody = await Fx.run(
|
|
918
|
-
Fx.from({
|
|
919
|
-
ok: () => response.json() as Promise<Record<string, unknown>>,
|
|
920
|
-
err: () => ({}) as Record<string, unknown>,
|
|
921
|
-
}).pipe(Fx.recover((fallback) => Fx.succeed(fallback))),
|
|
922
|
-
);
|
|
923
|
-
// Reconstruct ConvexError when the proxy forwards structured auth error data.
|
|
924
|
-
if (
|
|
925
|
-
typeof errorBody === "object" &&
|
|
926
|
-
errorBody !== null &&
|
|
927
|
-
"authError" in errorBody &&
|
|
928
|
-
typeof (errorBody as Record<string, unknown>).authError === "object"
|
|
929
|
-
) {
|
|
930
|
-
throw new ConvexError(
|
|
931
|
-
(errorBody as Record<string, unknown>).authError as Value,
|
|
932
|
-
);
|
|
933
|
-
}
|
|
934
|
-
throw new Error(
|
|
935
|
-
((errorBody as Record<string, unknown>).error as string) ??
|
|
936
|
-
`Proxy request failed: ${response.status}`,
|
|
937
|
-
);
|
|
938
|
-
}
|
|
939
|
-
return Fx.run(
|
|
940
|
-
Fx.from({
|
|
941
|
-
ok: () => response.json(),
|
|
942
|
-
err: () => new Error("Proxy response was not valid JSON"),
|
|
943
|
-
}).pipe(Fx.recover((e) => Fx.fatal(e))),
|
|
944
|
-
);
|
|
945
|
-
};
|
|
946
|
-
|
|
947
535
|
// ---------------------------------------------------------------------------
|
|
948
536
|
// Code verification with retries (SPA mode only)
|
|
949
537
|
// ---------------------------------------------------------------------------
|
|
@@ -993,9 +581,9 @@ export function client<
|
|
|
993
581
|
return {
|
|
994
582
|
deviceCode: device_code.deviceCode,
|
|
995
583
|
userCode: device_code.userCode,
|
|
996
|
-
|
|
584
|
+
verificationUri:
|
|
997
585
|
device_code.verification_uri ?? device_code.verificationUri,
|
|
998
|
-
|
|
586
|
+
verificationUriComplete:
|
|
999
587
|
device_code.verification_uri_complete ??
|
|
1000
588
|
device_code.verificationUriComplete,
|
|
1001
589
|
expiresIn: device_code.expiresIn,
|
|
@@ -1015,6 +603,7 @@ export function client<
|
|
|
1015
603
|
* @param args - Provider-specific arguments. Pass a `Record<string, Value>`
|
|
1016
604
|
* or `FormData`. Common fields: `email`, `password`, `code`, `redirectTo`.
|
|
1017
605
|
* @returns A {@link SignInResult} indicating the outcome.
|
|
606
|
+
* @throws {ConvexError} When the server action rejects the sign-in attempt (e.g. invalid credentials, provider error, or rate limiting).
|
|
1018
607
|
*
|
|
1019
608
|
* @example Email magic link
|
|
1020
609
|
* ```ts
|
|
@@ -1448,692 +1037,32 @@ export function client<
|
|
|
1448
1037
|
}
|
|
1449
1038
|
|
|
1450
1039
|
// ---------------------------------------------------------------------------
|
|
1451
|
-
//
|
|
1040
|
+
// Auth factor helpers
|
|
1452
1041
|
// ---------------------------------------------------------------------------
|
|
1453
1042
|
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
for (let i = 0; i < bytes.byteLength; i++) {
|
|
1462
|
-
binary += String.fromCharCode(bytes[i]!);
|
|
1463
|
-
}
|
|
1464
|
-
return btoa(binary)
|
|
1465
|
-
.replace(/\+/g, "-")
|
|
1466
|
-
.replace(/\//g, "_")
|
|
1467
|
-
.replace(/=+$/, "");
|
|
1468
|
-
};
|
|
1469
|
-
|
|
1470
|
-
const base64urlDecode = (str: string): Uint8Array => {
|
|
1471
|
-
const padded = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
1472
|
-
const binary = atob(padded);
|
|
1473
|
-
const bytes = new Uint8Array(binary.length);
|
|
1474
|
-
for (let i = 0; i < binary.length; i++) {
|
|
1475
|
-
bytes[i] = binary.charCodeAt(i);
|
|
1476
|
-
}
|
|
1477
|
-
return bytes;
|
|
1478
|
-
};
|
|
1479
|
-
|
|
1480
|
-
const passkey = {
|
|
1481
|
-
/**
|
|
1482
|
-
* Check if WebAuthn passkeys are supported in the current environment.
|
|
1483
|
-
*/
|
|
1484
|
-
isSupported: (): boolean => {
|
|
1485
|
-
return (
|
|
1486
|
-
typeof window !== "undefined" &&
|
|
1487
|
-
typeof window.PublicKeyCredential !== "undefined"
|
|
1488
|
-
);
|
|
1489
|
-
},
|
|
1490
|
-
|
|
1491
|
-
/**
|
|
1492
|
-
* Check if conditional UI (autofill-assisted passkey sign-in) is supported.
|
|
1493
|
-
*
|
|
1494
|
-
* ```ts
|
|
1495
|
-
* if (await auth.passkey.isAutofillSupported()) {
|
|
1496
|
-
* auth.passkey.authenticate({ autofill: true });
|
|
1497
|
-
* }
|
|
1498
|
-
* ```
|
|
1499
|
-
*/
|
|
1500
|
-
isAutofillSupported: async (): Promise<boolean> => {
|
|
1501
|
-
if (typeof window === "undefined") return false;
|
|
1502
|
-
if (typeof window.PublicKeyCredential === "undefined") return false;
|
|
1503
|
-
if (
|
|
1504
|
-
typeof (window.PublicKeyCredential as any)
|
|
1505
|
-
.isConditionalMediationAvailable !== "function"
|
|
1506
|
-
) {
|
|
1507
|
-
return false;
|
|
1508
|
-
}
|
|
1509
|
-
return (
|
|
1510
|
-
window.PublicKeyCredential as any
|
|
1511
|
-
).isConditionalMediationAvailable();
|
|
1512
|
-
},
|
|
1513
|
-
|
|
1514
|
-
/**
|
|
1515
|
-
* Register a new passkey for the current or new user.
|
|
1516
|
-
*
|
|
1517
|
-
* Performs the full two-round-trip WebAuthn registration ceremony:
|
|
1518
|
-
* 1. Requests creation options from the server (challenge, RP info)
|
|
1519
|
-
* 2. Calls `navigator.credentials.create()` with the options
|
|
1520
|
-
* 3. Sends the attestation back to the server for verification
|
|
1521
|
-
* 4. Server creates user + account + passkey records and returns tokens
|
|
1522
|
-
*
|
|
1523
|
-
* Works in both SPA and proxy (SSR) modes.
|
|
1524
|
-
*
|
|
1525
|
-
* ```ts
|
|
1526
|
-
* await auth.passkey.register({ name: "MacBook Touch ID" });
|
|
1527
|
-
* ```
|
|
1528
|
-
*
|
|
1529
|
-
* @param opts.name - Friendly name for this passkey
|
|
1530
|
-
* @param opts.email - Email to associate with the new account
|
|
1531
|
-
* @param opts.userName - Username for the credential (defaults to email)
|
|
1532
|
-
* @param opts.userDisplayName - Display name for the credential
|
|
1533
|
-
* @returns `{ kind: "signedIn" }` on success
|
|
1534
|
-
*/
|
|
1535
|
-
register: async (opts?: {
|
|
1536
|
-
name?: string;
|
|
1537
|
-
email?: string;
|
|
1538
|
-
userName?: string;
|
|
1539
|
-
userDisplayName?: string;
|
|
1540
|
-
}): Promise<SignInResult> => {
|
|
1541
|
-
const phase1Params = {
|
|
1542
|
-
flow: "registerOptions",
|
|
1543
|
-
email: opts?.email,
|
|
1544
|
-
userName: opts?.userName,
|
|
1545
|
-
userDisplayName: opts?.userDisplayName,
|
|
1546
|
-
};
|
|
1547
|
-
|
|
1548
|
-
// Phase 1: Get registration options from server
|
|
1549
|
-
let phase1Result: SignInActionResult;
|
|
1550
|
-
if (proxy) {
|
|
1551
|
-
phase1Result = (await proxyFetch({
|
|
1552
|
-
action: "auth:signIn",
|
|
1553
|
-
args: { provider: "passkey", params: phase1Params },
|
|
1554
|
-
})) as SignInActionResult;
|
|
1555
|
-
} else {
|
|
1556
|
-
phase1Result = (await convex.action(requireApiRefs().signIn, {
|
|
1557
|
-
provider: "passkey",
|
|
1558
|
-
params: phase1Params,
|
|
1559
|
-
})) as SignInActionResult;
|
|
1560
|
-
}
|
|
1561
|
-
|
|
1562
|
-
if (phase1Result.kind !== "passkeyOptions") {
|
|
1563
|
-
throw new Error("Server did not return passkey registration options");
|
|
1564
|
-
}
|
|
1565
|
-
|
|
1566
|
-
const options = phase1Result.options;
|
|
1567
|
-
|
|
1568
|
-
// Convert base64url strings to ArrayBuffers for the credential API
|
|
1569
|
-
const createOptions: CredentialCreationOptions = {
|
|
1570
|
-
publicKey: {
|
|
1571
|
-
rp: options.rp,
|
|
1572
|
-
user: {
|
|
1573
|
-
id: base64urlDecode(options.user.id).buffer as ArrayBuffer,
|
|
1574
|
-
name: options.user.name,
|
|
1575
|
-
displayName: options.user.displayName,
|
|
1576
|
-
},
|
|
1577
|
-
challenge: base64urlDecode(options.challenge).buffer as ArrayBuffer,
|
|
1578
|
-
pubKeyCredParams: options.pubKeyCredParams,
|
|
1579
|
-
timeout: options.timeout,
|
|
1580
|
-
attestation: options.attestation,
|
|
1581
|
-
authenticatorSelection: options.authenticatorSelection,
|
|
1582
|
-
excludeCredentials: (options.excludeCredentials ?? []).map(
|
|
1583
|
-
(cred: any) => ({
|
|
1584
|
-
type: cred.type ?? "public-key",
|
|
1585
|
-
id: base64urlDecode(cred.id).buffer as ArrayBuffer,
|
|
1586
|
-
transports: cred.transports,
|
|
1587
|
-
}),
|
|
1588
|
-
),
|
|
1589
|
-
},
|
|
1590
|
-
};
|
|
1591
|
-
|
|
1592
|
-
// Phase 2: Create credential via browser API
|
|
1593
|
-
const credential = (await navigator.credentials.create(
|
|
1594
|
-
createOptions,
|
|
1595
|
-
)) as PublicKeyCredential | null;
|
|
1596
|
-
if (!credential) {
|
|
1597
|
-
throw new Error("Passkey registration was cancelled");
|
|
1598
|
-
}
|
|
1599
|
-
|
|
1600
|
-
const response = credential.response as AuthenticatorAttestationResponse;
|
|
1601
|
-
|
|
1602
|
-
// Extract transports if available
|
|
1603
|
-
const transports =
|
|
1604
|
-
typeof response.getTransports === "function"
|
|
1605
|
-
? response.getTransports()
|
|
1606
|
-
: undefined;
|
|
1607
|
-
|
|
1608
|
-
const phase2Params = {
|
|
1609
|
-
flow: "registerVerify",
|
|
1610
|
-
clientDataJSON: base64urlEncode(response.clientDataJSON),
|
|
1611
|
-
attestationObject: base64urlEncode(response.attestationObject),
|
|
1612
|
-
transports,
|
|
1613
|
-
passkeyName: opts?.name,
|
|
1614
|
-
email: opts?.email,
|
|
1615
|
-
};
|
|
1616
|
-
|
|
1617
|
-
// Phase 3: Send attestation to server for verification
|
|
1618
|
-
let phase2Result: SignInActionResult;
|
|
1619
|
-
if (proxy) {
|
|
1620
|
-
// In proxy mode the verifier is stored in an httpOnly cookie by the proxy.
|
|
1621
|
-
// We pass it back explicitly so the proxy can forward it to Convex.
|
|
1622
|
-
phase2Result = (await proxyFetch({
|
|
1623
|
-
action: "auth:signIn",
|
|
1624
|
-
args: {
|
|
1625
|
-
provider: "passkey",
|
|
1626
|
-
params: phase2Params,
|
|
1627
|
-
verifier: phase1Result.verifier,
|
|
1628
|
-
},
|
|
1629
|
-
})) as SignInActionResult;
|
|
1630
|
-
} else {
|
|
1631
|
-
phase2Result = (await convex.action(requireApiRefs().signIn, {
|
|
1632
|
-
provider: "passkey",
|
|
1633
|
-
params: phase2Params,
|
|
1634
|
-
verifier: phase1Result.verifier,
|
|
1635
|
-
})) as SignInActionResult;
|
|
1636
|
-
}
|
|
1637
|
-
|
|
1638
|
-
return Fx.run(
|
|
1639
|
-
Fx.match(phase2Result, phase2Result.kind, {
|
|
1640
|
-
signedIn: (signedInResult) =>
|
|
1641
|
-
Fx.from({
|
|
1642
|
-
ok: async () => {
|
|
1643
|
-
const signingIn = await setTokenAndMaybeWait(
|
|
1644
|
-
proxy
|
|
1645
|
-
? {
|
|
1646
|
-
shouldStore: false as const,
|
|
1647
|
-
tokens:
|
|
1648
|
-
signedInResult.tokens === null
|
|
1649
|
-
? null
|
|
1650
|
-
: { token: signedInResult.tokens.token },
|
|
1651
|
-
waitForHandshake: true,
|
|
1652
|
-
context: {
|
|
1653
|
-
provider: "passkey",
|
|
1654
|
-
flow: "registerVerify",
|
|
1655
|
-
},
|
|
1656
|
-
}
|
|
1657
|
-
: {
|
|
1658
|
-
shouldStore: true as const,
|
|
1659
|
-
tokens: signedInResult.tokens,
|
|
1660
|
-
waitForHandshake: true,
|
|
1661
|
-
context: {
|
|
1662
|
-
provider: "passkey",
|
|
1663
|
-
flow: "registerVerify",
|
|
1664
|
-
},
|
|
1665
|
-
},
|
|
1666
|
-
);
|
|
1667
|
-
return signingIn
|
|
1668
|
-
? ({ kind: "signedIn" as const } as SignInResult)
|
|
1669
|
-
: ({ kind: "started" as const } as SignInResult);
|
|
1670
|
-
},
|
|
1671
|
-
err: (e) => e as never,
|
|
1672
|
-
}),
|
|
1673
|
-
redirect: (_redirectResult) =>
|
|
1674
|
-
Fx.succeed({ kind: "started" as const }),
|
|
1675
|
-
started: (_startedResult) => Fx.succeed({ kind: "started" as const }),
|
|
1676
|
-
passkeyOptions: (_passkeyOptionsResult) =>
|
|
1677
|
-
Fx.succeed({ kind: "started" as const }),
|
|
1678
|
-
totpRequired: (_totpRequiredResult) =>
|
|
1679
|
-
Fx.succeed({ kind: "started" as const }),
|
|
1680
|
-
totpSetup: (_totpSetupResult) =>
|
|
1681
|
-
Fx.succeed({ kind: "started" as const }),
|
|
1682
|
-
deviceCode: (_deviceCodeResult) =>
|
|
1683
|
-
Fx.succeed({ kind: "started" as const }),
|
|
1684
|
-
}),
|
|
1685
|
-
);
|
|
1686
|
-
},
|
|
1687
|
-
|
|
1688
|
-
/**
|
|
1689
|
-
* Authenticate with an existing passkey.
|
|
1690
|
-
*
|
|
1691
|
-
* Performs the full two-round-trip WebAuthn authentication ceremony:
|
|
1692
|
-
* 1. Requests assertion options from the server (challenge, allowed credentials)
|
|
1693
|
-
* 2. Calls `navigator.credentials.get()` with the options
|
|
1694
|
-
* 3. Sends the assertion back to the server for signature verification
|
|
1695
|
-
* 4. Server verifies signature, updates counter, creates session, returns tokens
|
|
1696
|
-
*
|
|
1697
|
-
* Works in both SPA and proxy (SSR) modes.
|
|
1698
|
-
*
|
|
1699
|
-
* ```ts
|
|
1700
|
-
* // Discoverable credential (no email needed)
|
|
1701
|
-
* await auth.passkey.authenticate();
|
|
1702
|
-
*
|
|
1703
|
-
* // Scoped to a specific user's credentials
|
|
1704
|
-
* await auth.passkey.authenticate({ email: "user@example.com" });
|
|
1705
|
-
*
|
|
1706
|
-
* // Autofill-assisted (conditional UI)
|
|
1707
|
-
* await auth.passkey.authenticate({ autofill: true });
|
|
1708
|
-
* ```
|
|
1709
|
-
*
|
|
1710
|
-
* @param opts.email - Scope to credentials for this email's user
|
|
1711
|
-
* @param opts.autofill - Use conditional mediation (autofill UI)
|
|
1712
|
-
* @returns `{ kind: "signedIn" }` on success
|
|
1713
|
-
*/
|
|
1714
|
-
authenticate: async (opts?: {
|
|
1715
|
-
email?: string;
|
|
1716
|
-
autofill?: boolean;
|
|
1717
|
-
}): Promise<SignInResult> => {
|
|
1718
|
-
const phase1Params = {
|
|
1719
|
-
flow: "authOptions",
|
|
1720
|
-
email: opts?.email,
|
|
1721
|
-
};
|
|
1722
|
-
|
|
1723
|
-
// Phase 1: Get assertion options from server
|
|
1724
|
-
let phase1Result: SignInActionResult;
|
|
1725
|
-
if (proxy) {
|
|
1726
|
-
phase1Result = (await proxyFetch({
|
|
1727
|
-
action: "auth:signIn",
|
|
1728
|
-
args: { provider: "passkey", params: phase1Params },
|
|
1729
|
-
})) as SignInActionResult;
|
|
1730
|
-
} else {
|
|
1731
|
-
phase1Result = (await convex.action(requireApiRefs().signIn, {
|
|
1732
|
-
provider: "passkey",
|
|
1733
|
-
params: phase1Params,
|
|
1734
|
-
})) as SignInActionResult;
|
|
1735
|
-
}
|
|
1736
|
-
|
|
1737
|
-
if (phase1Result.kind !== "passkeyOptions") {
|
|
1738
|
-
throw new Error("Server did not return passkey authentication options");
|
|
1739
|
-
}
|
|
1740
|
-
|
|
1741
|
-
const options = phase1Result.options;
|
|
1742
|
-
|
|
1743
|
-
// Convert base64url strings to ArrayBuffers for the credential API
|
|
1744
|
-
const getOptions: CredentialRequestOptions = {
|
|
1745
|
-
publicKey: {
|
|
1746
|
-
challenge: base64urlDecode(options.challenge).buffer as ArrayBuffer,
|
|
1747
|
-
timeout: options.timeout,
|
|
1748
|
-
rpId: options.rpId,
|
|
1749
|
-
userVerification: options.userVerification,
|
|
1750
|
-
allowCredentials: (options.allowCredentials ?? []).map(
|
|
1751
|
-
(cred: any) => ({
|
|
1752
|
-
type: cred.type ?? "public-key",
|
|
1753
|
-
id: base64urlDecode(cred.id).buffer as ArrayBuffer,
|
|
1754
|
-
transports: cred.transports,
|
|
1755
|
-
}),
|
|
1756
|
-
),
|
|
1757
|
-
},
|
|
1758
|
-
...(opts?.autofill ? { mediation: "conditional" as any } : {}),
|
|
1759
|
-
};
|
|
1760
|
-
|
|
1761
|
-
// Phase 2: Get credential via browser API
|
|
1762
|
-
const credential = (await navigator.credentials.get(
|
|
1763
|
-
getOptions,
|
|
1764
|
-
)) as PublicKeyCredential | null;
|
|
1765
|
-
if (!credential) {
|
|
1766
|
-
throw new Error("Passkey authentication was cancelled");
|
|
1767
|
-
}
|
|
1768
|
-
|
|
1769
|
-
const response = credential.response as AuthenticatorAssertionResponse;
|
|
1770
|
-
|
|
1771
|
-
const phase2Params = {
|
|
1772
|
-
flow: "authVerify",
|
|
1773
|
-
credentialId: base64urlEncode(credential.rawId),
|
|
1774
|
-
clientDataJSON: base64urlEncode(response.clientDataJSON),
|
|
1775
|
-
authenticatorData: base64urlEncode(response.authenticatorData),
|
|
1776
|
-
signature: base64urlEncode(response.signature),
|
|
1777
|
-
};
|
|
1778
|
-
|
|
1779
|
-
// Phase 3: Send assertion to server for verification
|
|
1780
|
-
let phase2Result: SignInActionResult;
|
|
1781
|
-
if (proxy) {
|
|
1782
|
-
phase2Result = (await proxyFetch({
|
|
1783
|
-
action: "auth:signIn",
|
|
1784
|
-
args: {
|
|
1785
|
-
provider: "passkey",
|
|
1786
|
-
params: phase2Params,
|
|
1787
|
-
verifier: phase1Result.verifier,
|
|
1788
|
-
},
|
|
1789
|
-
})) as SignInActionResult;
|
|
1790
|
-
} else {
|
|
1791
|
-
phase2Result = (await convex.action(requireApiRefs().signIn, {
|
|
1792
|
-
provider: "passkey",
|
|
1793
|
-
params: phase2Params,
|
|
1794
|
-
verifier: phase1Result.verifier,
|
|
1795
|
-
})) as SignInActionResult;
|
|
1796
|
-
}
|
|
1797
|
-
|
|
1798
|
-
return Fx.run(
|
|
1799
|
-
Fx.match(phase2Result, phase2Result.kind, {
|
|
1800
|
-
signedIn: (signedInResult) =>
|
|
1801
|
-
Fx.from({
|
|
1802
|
-
ok: async () => {
|
|
1803
|
-
const signingIn = await setTokenAndMaybeWait(
|
|
1804
|
-
proxy
|
|
1805
|
-
? {
|
|
1806
|
-
shouldStore: false as const,
|
|
1807
|
-
tokens:
|
|
1808
|
-
signedInResult.tokens === null
|
|
1809
|
-
? null
|
|
1810
|
-
: { token: signedInResult.tokens.token },
|
|
1811
|
-
waitForHandshake: true,
|
|
1812
|
-
context: { provider: "passkey", flow: "authVerify" },
|
|
1813
|
-
}
|
|
1814
|
-
: {
|
|
1815
|
-
shouldStore: true as const,
|
|
1816
|
-
tokens: signedInResult.tokens,
|
|
1817
|
-
waitForHandshake: true,
|
|
1818
|
-
context: { provider: "passkey", flow: "authVerify" },
|
|
1819
|
-
},
|
|
1820
|
-
);
|
|
1821
|
-
return signingIn
|
|
1822
|
-
? ({ kind: "signedIn" as const } as SignInResult)
|
|
1823
|
-
: ({ kind: "started" as const } as SignInResult);
|
|
1824
|
-
},
|
|
1825
|
-
err: (e) => e as never,
|
|
1826
|
-
}),
|
|
1827
|
-
redirect: (_redirectResult) =>
|
|
1828
|
-
Fx.succeed({ kind: "started" as const }),
|
|
1829
|
-
started: (_startedResult) => Fx.succeed({ kind: "started" as const }),
|
|
1830
|
-
passkeyOptions: (_passkeyOptionsResult) =>
|
|
1831
|
-
Fx.succeed({ kind: "started" as const }),
|
|
1832
|
-
totpRequired: (_totpRequiredResult) =>
|
|
1833
|
-
Fx.succeed({ kind: "started" as const }),
|
|
1834
|
-
totpSetup: (_totpSetupResult) =>
|
|
1835
|
-
Fx.succeed({ kind: "started" as const }),
|
|
1836
|
-
deviceCode: (_deviceCodeResult) =>
|
|
1837
|
-
Fx.succeed({ kind: "started" as const }),
|
|
1838
|
-
}),
|
|
1839
|
-
);
|
|
1840
|
-
},
|
|
1841
|
-
};
|
|
1842
|
-
|
|
1843
|
-
const totp = {
|
|
1844
|
-
/**
|
|
1845
|
-
* Start TOTP enrollment. Must be authenticated.
|
|
1846
|
-
*
|
|
1847
|
-
* Returns a URI for QR code display and a base32 secret for manual entry.
|
|
1848
|
-
*
|
|
1849
|
-
* ```ts
|
|
1850
|
-
* const setup = await auth.totp.setup();
|
|
1851
|
-
* // Display QR code from setup.uri
|
|
1852
|
-
* // Or show setup.secret for manual entry
|
|
1853
|
-
* ```
|
|
1854
|
-
*/
|
|
1855
|
-
setup: async (opts?: {
|
|
1856
|
-
name?: string;
|
|
1857
|
-
accountName?: string;
|
|
1858
|
-
}): Promise<{
|
|
1859
|
-
uri: string;
|
|
1860
|
-
secret: string;
|
|
1861
|
-
verifier: string;
|
|
1862
|
-
totpId: string;
|
|
1863
|
-
}> => {
|
|
1864
|
-
const params: Record<string, any> = { flow: "setup" };
|
|
1865
|
-
if (opts?.name) params.name = opts.name;
|
|
1866
|
-
if (opts?.accountName) params.accountName = opts.accountName;
|
|
1867
|
-
|
|
1868
|
-
if (proxy) {
|
|
1869
|
-
const result = await proxyFetch({
|
|
1870
|
-
action: "auth:signIn",
|
|
1871
|
-
args: { provider: "totp", params },
|
|
1872
|
-
});
|
|
1873
|
-
return {
|
|
1874
|
-
uri: result.totpSetup.uri,
|
|
1875
|
-
secret: result.totpSetup.secret,
|
|
1876
|
-
verifier: result.verifier,
|
|
1877
|
-
totpId: result.totpSetup.totpId,
|
|
1878
|
-
};
|
|
1879
|
-
}
|
|
1880
|
-
|
|
1881
|
-
const result = await convex.action(requireApiRefs().signIn, {
|
|
1882
|
-
provider: "totp",
|
|
1883
|
-
params,
|
|
1884
|
-
});
|
|
1885
|
-
return {
|
|
1886
|
-
uri: result.totpSetup.uri,
|
|
1887
|
-
secret: result.totpSetup.secret,
|
|
1888
|
-
verifier: result.verifier,
|
|
1889
|
-
totpId: result.totpSetup.totpId,
|
|
1890
|
-
};
|
|
1891
|
-
},
|
|
1892
|
-
|
|
1893
|
-
/**
|
|
1894
|
-
* Complete TOTP enrollment by verifying the first code from the authenticator app.
|
|
1895
|
-
*
|
|
1896
|
-
* ```ts
|
|
1897
|
-
* await auth.totp.confirm({ code: "123456", verifier: setup.verifier, totpId: setup.totpId });
|
|
1898
|
-
* ```
|
|
1899
|
-
*/
|
|
1900
|
-
confirm: async (opts: {
|
|
1901
|
-
code: string;
|
|
1902
|
-
verifier: string;
|
|
1903
|
-
totpId: string;
|
|
1904
|
-
}): Promise<void> => {
|
|
1905
|
-
const params: Record<string, any> = {
|
|
1906
|
-
flow: "confirm",
|
|
1907
|
-
code: opts.code,
|
|
1908
|
-
totpId: opts.totpId,
|
|
1909
|
-
};
|
|
1910
|
-
|
|
1911
|
-
if (proxy) {
|
|
1912
|
-
const result = await proxyFetch({
|
|
1913
|
-
action: "auth:signIn",
|
|
1914
|
-
args: { provider: "totp", params, verifier: opts.verifier },
|
|
1915
|
-
});
|
|
1916
|
-
if (result.tokens) {
|
|
1917
|
-
await setTokenAndMaybeWait({
|
|
1918
|
-
shouldStore: false,
|
|
1919
|
-
tokens:
|
|
1920
|
-
result.tokens === null ? null : { token: result.tokens.token },
|
|
1921
|
-
waitForHandshake: true,
|
|
1922
|
-
context: { provider: "totp", flow: "confirm" },
|
|
1923
|
-
});
|
|
1924
|
-
}
|
|
1925
|
-
return;
|
|
1926
|
-
}
|
|
1927
|
-
|
|
1928
|
-
const result = await convex.action(requireApiRefs().signIn, {
|
|
1929
|
-
provider: "totp",
|
|
1930
|
-
params,
|
|
1931
|
-
verifier: opts.verifier,
|
|
1932
|
-
});
|
|
1933
|
-
if (result.tokens) {
|
|
1934
|
-
await setTokenAndMaybeWait({
|
|
1935
|
-
shouldStore: true,
|
|
1936
|
-
tokens: (result.tokens as AuthSession | null) ?? null,
|
|
1937
|
-
waitForHandshake: true,
|
|
1938
|
-
context: { provider: "totp", flow: "confirm" },
|
|
1939
|
-
});
|
|
1940
|
-
}
|
|
1941
|
-
},
|
|
1942
|
-
|
|
1943
|
-
/**
|
|
1944
|
-
* Complete 2FA verification during sign-in.
|
|
1945
|
-
*
|
|
1946
|
-
* Called after a credentials sign-in returns `kind: "totpRequired"`.
|
|
1947
|
-
*
|
|
1948
|
-
* ```ts
|
|
1949
|
-
* const result = await auth.signIn("password", { email, password });
|
|
1950
|
-
* if (result.kind === "totpRequired") {
|
|
1951
|
-
* await auth.totp.verify({ code: "123456", verifier: result.verifier });
|
|
1952
|
-
* }
|
|
1953
|
-
* ```
|
|
1954
|
-
*/
|
|
1955
|
-
verify: async (opts: { code: string; verifier: string }): Promise<void> => {
|
|
1956
|
-
const params: Record<string, any> = {
|
|
1957
|
-
flow: "verify",
|
|
1958
|
-
code: opts.code,
|
|
1959
|
-
};
|
|
1960
|
-
|
|
1961
|
-
if (proxy) {
|
|
1962
|
-
const result = await proxyFetch({
|
|
1963
|
-
action: "auth:signIn",
|
|
1964
|
-
args: { provider: "totp", params, verifier: opts.verifier },
|
|
1965
|
-
});
|
|
1966
|
-
if (result.tokens) {
|
|
1967
|
-
await setTokenAndMaybeWait({
|
|
1968
|
-
shouldStore: false,
|
|
1969
|
-
tokens:
|
|
1970
|
-
result.tokens === null ? null : { token: result.tokens.token },
|
|
1971
|
-
waitForHandshake: true,
|
|
1972
|
-
context: { provider: "totp", flow: "verify" },
|
|
1973
|
-
});
|
|
1974
|
-
}
|
|
1975
|
-
return;
|
|
1976
|
-
}
|
|
1977
|
-
|
|
1978
|
-
const result = await convex.action(requireApiRefs().signIn, {
|
|
1979
|
-
provider: "totp",
|
|
1980
|
-
params,
|
|
1981
|
-
verifier: opts.verifier,
|
|
1982
|
-
});
|
|
1983
|
-
if (result.tokens) {
|
|
1984
|
-
await setTokenAndMaybeWait({
|
|
1985
|
-
shouldStore: true,
|
|
1986
|
-
tokens: (result.tokens as AuthSession | null) ?? null,
|
|
1987
|
-
waitForHandshake: true,
|
|
1988
|
-
context: { provider: "totp", flow: "verify" },
|
|
1989
|
-
});
|
|
1990
|
-
}
|
|
1991
|
-
},
|
|
1992
|
-
};
|
|
1993
|
-
|
|
1994
|
-
const device = {
|
|
1995
|
-
/**
|
|
1996
|
-
* Poll for device authorization status.
|
|
1997
|
-
*
|
|
1998
|
-
* The device calls this repeatedly (respecting `interval`) after
|
|
1999
|
-
* initiating a device flow via `signIn("device")`. Returns when
|
|
2000
|
-
* the user authorizes, or throws on timeout/denial.
|
|
2001
|
-
*
|
|
2002
|
-
* ```ts
|
|
2003
|
-
* const result = await auth.signIn("device");
|
|
2004
|
-
* const { deviceCode } = result;
|
|
2005
|
-
* // Display deviceCode.userCode to the user, then poll:
|
|
2006
|
-
* await auth.device.poll(deviceCode);
|
|
2007
|
-
* // User is now signed in
|
|
2008
|
-
* ```
|
|
2009
|
-
*
|
|
2010
|
-
* @param code - The {@link DeviceCodeResult} from `signIn("device")`.
|
|
2011
|
-
* @returns Resolves when the device is authorized and tokens are stored.
|
|
2012
|
-
* @throws When the code expires, is denied, or polling encounters an error.
|
|
2013
|
-
*/
|
|
2014
|
-
poll: async (code: DeviceCodeResult): Promise<void> => {
|
|
2015
|
-
const intervalMs = code.interval * 1000;
|
|
2016
|
-
const expiresAt = Date.now() + code.expiresIn * 1000;
|
|
2017
|
-
|
|
2018
|
-
while (Date.now() < expiresAt) {
|
|
2019
|
-
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
2020
|
-
|
|
2021
|
-
const pollResult = await Fx.run(
|
|
2022
|
-
Fx.from({
|
|
2023
|
-
ok: async () => {
|
|
2024
|
-
let result: any;
|
|
2025
|
-
const params: Record<string, any> = {
|
|
2026
|
-
flow: "poll",
|
|
2027
|
-
deviceCode: code.deviceCode,
|
|
2028
|
-
};
|
|
2029
|
-
|
|
2030
|
-
if (proxy) {
|
|
2031
|
-
result = await proxyFetch({
|
|
2032
|
-
action: "auth:signIn",
|
|
2033
|
-
args: { provider: "device", params },
|
|
2034
|
-
});
|
|
2035
|
-
} else {
|
|
2036
|
-
result = await convex.action(requireApiRefs().signIn, {
|
|
2037
|
-
provider: "device",
|
|
2038
|
-
params,
|
|
2039
|
-
});
|
|
2040
|
-
}
|
|
2041
|
-
|
|
2042
|
-
return result;
|
|
2043
|
-
},
|
|
2044
|
-
err: (e) => e,
|
|
2045
|
-
}).pipe(
|
|
2046
|
-
Fx.recover((e: unknown) => {
|
|
2047
|
-
const dispatch =
|
|
2048
|
-
e instanceof ConvexError
|
|
2049
|
-
? {
|
|
2050
|
-
tag:
|
|
2051
|
-
(e.data as Record<string, unknown> | undefined)
|
|
2052
|
-
?.code === "DEVICE_AUTHORIZATION_PENDING"
|
|
2053
|
-
? "continue"
|
|
2054
|
-
: (e.data as Record<string, unknown> | undefined)
|
|
2055
|
-
?.code === "DEVICE_SLOW_DOWN"
|
|
2056
|
-
? "slowDown"
|
|
2057
|
-
: "fatal",
|
|
2058
|
-
}
|
|
2059
|
-
: ({ tag: "fatal" } as const);
|
|
2060
|
-
|
|
2061
|
-
return Fx.match(dispatch, dispatch.tag, {
|
|
2062
|
-
continue: () => Fx.succeed({ _poll: "continue" as const }),
|
|
2063
|
-
slowDown: () => Fx.succeed({ _poll: "slow_down" as const }),
|
|
2064
|
-
fatal: () => Fx.fatal(e),
|
|
2065
|
-
});
|
|
2066
|
-
}),
|
|
2067
|
-
),
|
|
2068
|
-
);
|
|
2069
|
-
|
|
2070
|
-
if ("_poll" in pollResult) {
|
|
2071
|
-
if (pollResult._poll === "slow_down") {
|
|
2072
|
-
// Back off by adding one interval
|
|
2073
|
-
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
2074
|
-
}
|
|
2075
|
-
continue;
|
|
2076
|
-
}
|
|
2077
|
-
|
|
2078
|
-
// Authorized — tokens received
|
|
2079
|
-
if (pollResult.tokens) {
|
|
2080
|
-
if (proxy) {
|
|
2081
|
-
await setTokenAndMaybeWait({
|
|
2082
|
-
shouldStore: false,
|
|
2083
|
-
tokens:
|
|
2084
|
-
pollResult.tokens === null
|
|
2085
|
-
? null
|
|
2086
|
-
: { token: pollResult.tokens.token },
|
|
2087
|
-
waitForHandshake: true,
|
|
2088
|
-
context: { provider: "device", flow: "poll" },
|
|
2089
|
-
});
|
|
2090
|
-
} else {
|
|
2091
|
-
await setTokenAndMaybeWait({
|
|
2092
|
-
shouldStore: true,
|
|
2093
|
-
tokens: (pollResult.tokens as AuthSession | null) ?? null,
|
|
2094
|
-
waitForHandshake: true,
|
|
2095
|
-
context: { provider: "device", flow: "poll" },
|
|
2096
|
-
});
|
|
2097
|
-
}
|
|
2098
|
-
return;
|
|
2099
|
-
}
|
|
2100
|
-
}
|
|
2101
|
-
|
|
2102
|
-
throw new Error("Device authorization timed out.");
|
|
2103
|
-
},
|
|
1043
|
+
const passkey = createPasskeyClient({
|
|
1044
|
+
proxy,
|
|
1045
|
+
convex,
|
|
1046
|
+
requireApiRefs,
|
|
1047
|
+
proxyFetch,
|
|
1048
|
+
setTokenAndMaybeWait,
|
|
1049
|
+
});
|
|
2104
1050
|
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
* // On the /device verification page:
|
|
2113
|
-
* await auth.device.verify(userCode);
|
|
2114
|
-
* ```
|
|
2115
|
-
*
|
|
2116
|
-
* @param userCode - The user code entered by the user (e.g. "WDJB-MJHT").
|
|
2117
|
-
*/
|
|
2118
|
-
verify: async (userCode: string): Promise<void> => {
|
|
2119
|
-
const params: Record<string, any> = {
|
|
2120
|
-
flow: "verify",
|
|
2121
|
-
userCode,
|
|
2122
|
-
};
|
|
1051
|
+
const totp = createTotpClient({
|
|
1052
|
+
proxy,
|
|
1053
|
+
convex,
|
|
1054
|
+
requireApiRefs,
|
|
1055
|
+
proxyFetch,
|
|
1056
|
+
setTokenAndMaybeWait,
|
|
1057
|
+
});
|
|
2123
1058
|
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
provider: "device",
|
|
2132
|
-
params,
|
|
2133
|
-
});
|
|
2134
|
-
}
|
|
2135
|
-
},
|
|
2136
|
-
};
|
|
1059
|
+
const device = createDeviceClient({
|
|
1060
|
+
proxy,
|
|
1061
|
+
convex,
|
|
1062
|
+
requireApiRefs,
|
|
1063
|
+
proxyFetch,
|
|
1064
|
+
setTokenAndMaybeWait,
|
|
1065
|
+
});
|
|
2137
1066
|
|
|
2138
1067
|
return {
|
|
2139
1068
|
/** Current auth state snapshot. */
|
|
@@ -2144,6 +1073,7 @@ export function client<
|
|
|
2144
1073
|
param,
|
|
2145
1074
|
/** Pending invite from URL or recovered from storage. Null if none. */
|
|
2146
1075
|
get invite(): PendingInvite | null {
|
|
1076
|
+
const pendingInvite = getPendingInvite();
|
|
2147
1077
|
if (!pendingInvite) return null;
|
|
2148
1078
|
return {
|
|
2149
1079
|
token: pendingInvite.token,
|
|
@@ -2163,7 +1093,32 @@ export function client<
|
|
|
2163
1093
|
totp,
|
|
2164
1094
|
/** Device authorization (RFC 8628) helpers. */
|
|
2165
1095
|
device,
|
|
2166
|
-
/**
|
|
1096
|
+
/**
|
|
1097
|
+
* Tear down this auth client instance.
|
|
1098
|
+
*
|
|
1099
|
+
* Removes the cross-tab `storage` event listener, clears all
|
|
1100
|
+
* `onChange` subscribers, and rejects any in-flight handshake
|
|
1101
|
+
* waiters. Call this when the client is no longer needed
|
|
1102
|
+
* (e.g. on SPA unmount or hot-module replacement) to prevent
|
|
1103
|
+
* memory leaks and stale callbacks.
|
|
1104
|
+
*
|
|
1105
|
+
* @example
|
|
1106
|
+
* ```ts
|
|
1107
|
+
* // SvelteKit onDestroy
|
|
1108
|
+
* import { onDestroy } from "svelte";
|
|
1109
|
+
* const auth = client({ convex, api: api.auth });
|
|
1110
|
+
* onDestroy(() => auth.destroy());
|
|
1111
|
+
* ```
|
|
1112
|
+
*
|
|
1113
|
+
* @example
|
|
1114
|
+
* ```ts
|
|
1115
|
+
* const unsubscribe = auth.onChange((state) => console.log(state.phase));
|
|
1116
|
+
*
|
|
1117
|
+
* // Later, during cleanup:
|
|
1118
|
+
* unsubscribe();
|
|
1119
|
+
* auth.destroy();
|
|
1120
|
+
* ```
|
|
1121
|
+
*/
|
|
2167
1122
|
destroy: () => {
|
|
2168
1123
|
destroyed = true;
|
|
2169
1124
|
settleHandshakeWaiters(authEpoch, {
|
|
@@ -2177,92 +1132,3 @@ export function client<
|
|
|
2177
1132
|
},
|
|
2178
1133
|
} as AuthClient<Api>;
|
|
2179
1134
|
}
|
|
2180
|
-
|
|
2181
|
-
// ---------------------------------------------------------------------------
|
|
2182
|
-
// Browser mutex — ensures only one tab refreshes a token at a time.
|
|
2183
|
-
// ---------------------------------------------------------------------------
|
|
2184
|
-
|
|
2185
|
-
async function browserMutex<T>(
|
|
2186
|
-
key: string,
|
|
2187
|
-
callback: () => Promise<T>,
|
|
2188
|
-
): Promise<T> {
|
|
2189
|
-
const lockManager = (globalThis as any)?.navigator?.locks;
|
|
2190
|
-
return lockManager !== undefined
|
|
2191
|
-
? await lockManager.request(key, callback)
|
|
2192
|
-
: await manualMutex(key, callback);
|
|
2193
|
-
}
|
|
2194
|
-
|
|
2195
|
-
function getStorageListenerRegistry(): Record<
|
|
2196
|
-
string,
|
|
2197
|
-
(event: StorageEvent) => void
|
|
2198
|
-
> {
|
|
2199
|
-
const globalAny = globalThis as any;
|
|
2200
|
-
if (globalAny.__convexAuthStorageListeners === undefined) {
|
|
2201
|
-
globalAny.__convexAuthStorageListeners = {} as Record<
|
|
2202
|
-
string,
|
|
2203
|
-
(event: StorageEvent) => void
|
|
2204
|
-
>;
|
|
2205
|
-
}
|
|
2206
|
-
return globalAny.__convexAuthStorageListeners as Record<
|
|
2207
|
-
string,
|
|
2208
|
-
(event: StorageEvent) => void
|
|
2209
|
-
>;
|
|
2210
|
-
}
|
|
2211
|
-
|
|
2212
|
-
function getManualMutexTails(): Record<string, Promise<void>> {
|
|
2213
|
-
const globalAny = globalThis as any;
|
|
2214
|
-
if (globalAny.__convexAuthMutexTails === undefined) {
|
|
2215
|
-
globalAny.__convexAuthMutexTails = {} as Record<string, Promise<void>>;
|
|
2216
|
-
}
|
|
2217
|
-
return globalAny.__convexAuthMutexTails as Record<string, Promise<void>>;
|
|
2218
|
-
}
|
|
2219
|
-
|
|
2220
|
-
async function manualMutex<T>(
|
|
2221
|
-
key: string,
|
|
2222
|
-
callback: () => Promise<T>,
|
|
2223
|
-
): Promise<T> {
|
|
2224
|
-
const mutexTails = getManualMutexTails();
|
|
2225
|
-
const previousTail = mutexTails[key] ?? Promise.resolve();
|
|
2226
|
-
|
|
2227
|
-
let releaseCurrent: (() => void) | undefined;
|
|
2228
|
-
const currentTail = new Promise<void>((resolve) => {
|
|
2229
|
-
releaseCurrent = resolve;
|
|
2230
|
-
});
|
|
2231
|
-
|
|
2232
|
-
mutexTails[key] = previousTail.then(
|
|
2233
|
-
() => currentTail,
|
|
2234
|
-
() => currentTail,
|
|
2235
|
-
);
|
|
2236
|
-
|
|
2237
|
-
await Fx.run(
|
|
2238
|
-
Fx.from({
|
|
2239
|
-
ok: () => previousTail,
|
|
2240
|
-
err: () => undefined,
|
|
2241
|
-
}).pipe(Fx.recover(() => Fx.succeed(undefined))),
|
|
2242
|
-
);
|
|
2243
|
-
let result: T;
|
|
2244
|
-
let threw = false;
|
|
2245
|
-
let thrownError: unknown;
|
|
2246
|
-
await Fx.run(
|
|
2247
|
-
Fx.from({
|
|
2248
|
-
ok: async () => {
|
|
2249
|
-
result = await callback();
|
|
2250
|
-
},
|
|
2251
|
-
err: (e) => e,
|
|
2252
|
-
}).pipe(
|
|
2253
|
-
Fx.recover((e) => {
|
|
2254
|
-
threw = true;
|
|
2255
|
-
thrownError = e;
|
|
2256
|
-
return Fx.succeed(undefined);
|
|
2257
|
-
}),
|
|
2258
|
-
),
|
|
2259
|
-
);
|
|
2260
|
-
releaseCurrent?.();
|
|
2261
|
-
if (mutexTails[key] === currentTail) {
|
|
2262
|
-
delete mutexTails[key];
|
|
2263
|
-
}
|
|
2264
|
-
if (threw) {
|
|
2265
|
-
throw thrownError;
|
|
2266
|
-
}
|
|
2267
|
-
return result!;
|
|
2268
|
-
}
|