@robelest/convex-auth 0.0.4-preview.2 → 0.0.4-preview.21
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 +67 -26
- package/dist/authorization/index.d.ts +63 -0
- package/dist/authorization/index.d.ts.map +1 -0
- package/dist/authorization/index.js +63 -0
- package/dist/authorization/index.js.map +1 -0
- package/dist/bin.js +6185 -0
- 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 -299
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +407 -534
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/api.d.ts +42 -0
- 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 +2546 -90
- 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 -0
- package/dist/component/convex.config.d.ts +2 -2
- package/dist/component/functions.d.ts +11 -9
- package/dist/component/functions.d.ts.map +1 -1
- package/dist/component/functions.js.map +1 -1
- package/dist/component/index.d.ts +7 -11
- package/dist/component/index.js +2 -3
- package/dist/component/model.d.ts +153 -0
- package/dist/component/model.d.ts.map +1 -0
- package/dist/component/model.js +349 -0
- package/dist/component/model.js.map +1 -0
- package/dist/component/providers/anonymous.d.ts +54 -0
- package/dist/component/providers/anonymous.d.ts.map +1 -0
- package/dist/component/providers/credentials.d.ts +5 -5
- package/dist/component/providers/credentials.d.ts.map +1 -1
- package/dist/component/providers/device.d.ts +67 -0
- package/dist/component/providers/device.d.ts.map +1 -0
- package/dist/component/providers/email.d.ts +62 -0
- package/dist/component/providers/email.d.ts.map +1 -0
- package/dist/component/providers/oauth.d.ts.map +1 -1
- package/dist/component/providers/oauth.js.map +1 -1
- package/dist/component/providers/passkey.d.ts +57 -0
- package/dist/component/providers/passkey.d.ts.map +1 -0
- package/dist/component/providers/password.d.ts +88 -0
- package/dist/component/providers/password.d.ts.map +1 -0
- package/dist/component/providers/phone.d.ts +48 -0
- package/dist/component/providers/phone.d.ts.map +1 -0
- package/dist/component/providers/sso.d.ts +50 -0
- package/dist/component/providers/sso.d.ts.map +1 -0
- package/dist/component/providers/totp.d.ts +45 -0
- package/dist/component/providers/totp.d.ts.map +1 -0
- 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 +24 -271
- package/dist/component/public.d.ts.map +1 -1
- package/dist/component/public.js +21 -1229
- package/dist/component/schema.d.ts +473 -110
- package/dist/component/schema.js +162 -73
- package/dist/component/schema.js.map +1 -1
- package/dist/component/server/auth.d.ts +318 -373
- package/dist/component/server/auth.d.ts.map +1 -1
- package/dist/component/server/auth.js +204 -123
- 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} +43 -12
- package/dist/component/server/config.js.map +1 -0
- package/dist/component/server/cookies.js +3 -0
- package/dist/component/server/cookies.js.map +1 -1
- package/dist/component/server/core.js +713 -0
- package/dist/component/server/core.js.map +1 -0
- package/dist/component/server/crypto.js +38 -0
- package/dist/component/server/crypto.js.map +1 -0
- package/dist/component/server/{implementation/db.js → db.js} +2 -1
- package/dist/component/server/db.js.map +1 -0
- package/dist/component/server/device.js +109 -0
- package/dist/component/server/device.js.map +1 -0
- package/dist/component/server/enterprise/config.js +46 -0
- package/dist/component/server/enterprise/config.js.map +1 -0
- package/dist/component/server/enterprise/domain.js +885 -0
- 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.d.ts +1 -0
- package/dist/component/server/errors.js +24 -16
- package/dist/component/server/errors.js.map +1 -1
- package/dist/component/server/http.js +288 -0
- package/dist/component/server/http.js.map +1 -0
- package/dist/component/server/identity.js +13 -0
- package/dist/component/server/identity.js.map +1 -0
- package/dist/{server/implementation → component/server}/keys.js +9 -31
- package/dist/component/server/keys.js.map +1 -0
- package/dist/component/server/limits.js +61 -0
- package/dist/component/server/limits.js.map +1 -0
- package/dist/component/server/mutations/account.js +44 -0
- package/dist/component/server/mutations/account.js.map +1 -0
- package/dist/component/server/{implementation/mutations → mutations}/code.js +7 -4
- package/dist/component/server/mutations/code.js.map +1 -0
- package/dist/component/server/mutations/invalidate.js +32 -0
- package/dist/component/server/mutations/invalidate.js.map +1 -0
- package/dist/component/server/mutations/oauth.js +110 -0
- package/dist/component/server/mutations/oauth.js.map +1 -0
- package/dist/component/server/mutations/refresh.js +119 -0
- package/dist/component/server/mutations/refresh.js.map +1 -0
- package/dist/component/server/mutations/register.js +83 -0
- package/dist/component/server/mutations/register.js.map +1 -0
- package/dist/component/server/mutations/retrieve.js +65 -0
- package/dist/component/server/mutations/retrieve.js.map +1 -0
- package/dist/component/server/mutations/signature.js +32 -0
- package/dist/component/server/mutations/signature.js.map +1 -0
- package/dist/component/server/{implementation/mutations → mutations}/signin.js +2 -2
- package/dist/component/server/mutations/signin.js.map +1 -0
- package/dist/component/server/mutations/signout.js +27 -0
- package/dist/component/server/mutations/signout.js.map +1 -0
- 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 +85 -0
- package/dist/component/server/mutations/store.js.map +1 -0
- package/dist/component/server/mutations/verifier.js +18 -0
- package/dist/component/server/mutations/verifier.js.map +1 -0
- package/dist/component/server/mutations/verify.js +98 -0
- package/dist/component/server/mutations/verify.js.map +1 -0
- package/dist/component/server/oauth.js +106 -60
- package/dist/component/server/oauth.js.map +1 -1
- package/dist/component/server/passkey.js +328 -0
- package/dist/component/server/passkey.js.map +1 -0
- package/dist/{server/implementation → component/server}/redirects.js +13 -11
- package/dist/component/server/redirects.js.map +1 -0
- package/dist/component/server/refresh.js +96 -0
- package/dist/component/server/refresh.js.map +1 -0
- package/dist/component/server/runtime.d.ts +136 -0
- 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/{server/implementation → component/server}/sessions.js +14 -8
- package/dist/component/server/sessions.js.map +1 -0
- package/dist/component/server/signin.js +201 -0
- package/dist/component/server/signin.js.map +1 -0
- package/dist/component/server/tokens.js +17 -0
- package/dist/component/server/tokens.js.map +1 -0
- package/dist/component/server/totp.js +148 -0
- package/dist/component/server/totp.js.map +1 -0
- package/dist/component/server/types.d.ts +387 -298
- package/dist/component/server/types.d.ts.map +1 -1
- package/dist/component/server/{implementation/types.js → types.js} +1 -1
- package/dist/component/server/types.js.map +1 -0
- package/dist/component/server/{implementation/users.js → users.js} +54 -35
- package/dist/component/server/users.js.map +1 -0
- package/dist/component/server/utils.js +110 -4
- 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/anonymous.d.ts +3 -9
- package/dist/providers/anonymous.d.ts.map +1 -1
- package/dist/providers/anonymous.js +1 -18
- package/dist/providers/anonymous.js.map +1 -1
- package/dist/providers/credentials.d.ts +8 -10
- package/dist/providers/credentials.d.ts.map +1 -1
- package/dist/providers/credentials.js +3 -5
- package/dist/providers/credentials.js.map +1 -1
- package/dist/providers/device.d.ts +18 -10
- package/dist/providers/device.d.ts.map +1 -1
- package/dist/providers/device.js +4 -8
- package/dist/providers/device.js.map +1 -1
- package/dist/providers/email.d.ts +50 -23
- package/dist/providers/email.d.ts.map +1 -1
- package/dist/providers/email.js +58 -34
- package/dist/providers/email.js.map +1 -1
- package/dist/providers/index.d.ts +7 -3
- package/dist/providers/index.js +4 -1
- package/dist/providers/oauth.d.ts.map +1 -1
- package/dist/providers/oauth.js.map +1 -1
- package/dist/providers/passkey.d.ts +12 -9
- package/dist/providers/passkey.d.ts.map +1 -1
- package/dist/providers/passkey.js +1 -7
- package/dist/providers/passkey.js.map +1 -1
- package/dist/providers/password.d.ts +6 -12
- package/dist/providers/password.d.ts.map +1 -1
- package/dist/providers/password.js +189 -89
- package/dist/providers/password.js.map +1 -1
- package/dist/providers/phone.d.ts +40 -11
- package/dist/providers/phone.d.ts.map +1 -1
- package/dist/providers/phone.js +52 -21
- package/dist/providers/phone.js.map +1 -1
- package/dist/providers/sso.d.ts +50 -0
- package/dist/providers/sso.d.ts.map +1 -0
- package/dist/providers/sso.js +34 -0
- package/dist/providers/sso.js.map +1 -0
- package/dist/providers/totp.d.ts +12 -9
- package/dist/providers/totp.d.ts.map +1 -1
- package/dist/providers/totp.js +1 -7
- package/dist/providers/totp.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 +335 -370
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +204 -123
- package/dist/server/auth.js.map +1 -1
- package/dist/server/authError.d.ts +46 -0
- 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/config.d.ts +1 -0
- package/dist/server/{providers.js → config.js} +43 -12
- package/dist/server/config.js.map +1 -0
- package/dist/server/cookies.d.ts +1 -38
- package/dist/server/cookies.js +3 -0
- package/dist/server/cookies.js.map +1 -1
- package/dist/server/core.d.ts +1436 -0
- package/dist/server/core.d.ts.map +1 -0
- package/dist/server/core.js +713 -0
- package/dist/server/core.js.map +1 -0
- package/dist/server/crypto.d.ts +8 -0
- package/dist/server/crypto.d.ts.map +1 -0
- package/dist/server/crypto.js +38 -0
- package/dist/server/crypto.js.map +1 -0
- package/dist/server/db.d.ts +1 -0
- package/dist/server/{implementation/db.js → db.js} +2 -1
- package/dist/server/db.js.map +1 -0
- package/dist/server/device.d.ts +1 -0
- package/dist/server/device.js +109 -0
- package/dist/server/device.js.map +1 -0
- package/dist/server/enterprise/config.d.ts +1 -0
- package/dist/server/enterprise/config.js +46 -0
- package/dist/server/enterprise/config.js.map +1 -0
- package/dist/server/enterprise/domain.d.ts +409 -0
- package/dist/server/enterprise/domain.d.ts.map +1 -0
- package/dist/server/enterprise/domain.js +885 -0
- 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.d.ts +1 -0
- package/dist/server/enterprise/oidc.js +248 -0
- package/dist/server/enterprise/oidc.js.map +1 -0
- package/dist/server/enterprise/policy.d.ts +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/enterprise/validators.js +60 -0
- package/dist/server/enterprise/validators.js.map +1 -0
- package/dist/server/errors.d.ts +33 -1
- package/dist/server/errors.d.ts.map +1 -1
- package/dist/server/errors.js +44 -1
- package/dist/server/errors.js.map +1 -1
- package/dist/server/http.d.ts +59 -0
- package/dist/server/http.d.ts.map +1 -0
- package/dist/server/http.js +288 -0
- package/dist/server/http.js.map +1 -0
- package/dist/server/identity.d.ts +1 -0
- package/dist/server/identity.js +13 -0
- package/dist/server/identity.js.map +1 -0
- package/dist/server/index.d.ts +4 -182
- package/dist/server/index.js +4 -376
- package/dist/server/keys.d.ts +1 -0
- package/dist/{component/server/implementation → server}/keys.js +9 -31
- package/dist/server/keys.js.map +1 -0
- package/dist/server/limits.d.ts +1 -0
- package/dist/server/limits.js +61 -0
- 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 +30 -0
- package/dist/server/mutations/account.d.ts.map +1 -0
- package/dist/server/mutations/account.js +44 -0
- package/dist/server/mutations/account.js.map +1 -0
- package/dist/server/mutations/code.d.ts +30 -0
- package/dist/server/mutations/code.d.ts.map +1 -0
- package/dist/server/{implementation/mutations → mutations}/code.js +7 -4
- package/dist/server/mutations/code.js.map +1 -0
- package/dist/server/mutations/index.d.ts +14 -0
- package/dist/server/mutations/index.js +15 -0
- package/dist/server/mutations/invalidate.d.ts +20 -0
- package/dist/server/mutations/invalidate.d.ts.map +1 -0
- package/dist/server/mutations/invalidate.js +32 -0
- package/dist/server/mutations/invalidate.js.map +1 -0
- package/dist/server/mutations/oauth.d.ts +28 -0
- package/dist/server/mutations/oauth.d.ts.map +1 -0
- package/dist/server/mutations/oauth.js +110 -0
- package/dist/server/mutations/oauth.js.map +1 -0
- package/dist/server/mutations/refresh.d.ts +21 -0
- package/dist/server/mutations/refresh.d.ts.map +1 -0
- package/dist/server/mutations/refresh.js +119 -0
- package/dist/server/mutations/refresh.js.map +1 -0
- package/dist/server/mutations/register.d.ts +38 -0
- package/dist/server/mutations/register.d.ts.map +1 -0
- package/dist/server/mutations/register.js +83 -0
- package/dist/server/mutations/register.js.map +1 -0
- package/dist/server/mutations/retrieve.d.ts +33 -0
- package/dist/server/mutations/retrieve.d.ts.map +1 -0
- package/dist/server/mutations/retrieve.js +65 -0
- package/dist/server/mutations/retrieve.js.map +1 -0
- package/dist/server/mutations/signature.d.ts +22 -0
- package/dist/server/mutations/signature.d.ts.map +1 -0
- package/dist/server/mutations/signature.js +32 -0
- package/dist/server/mutations/signature.js.map +1 -0
- package/dist/server/mutations/signin.d.ts +22 -0
- package/dist/server/mutations/signin.d.ts.map +1 -0
- package/dist/server/{implementation/mutations → mutations}/signin.js +2 -2
- package/dist/server/mutations/signin.js.map +1 -0
- package/dist/server/mutations/signout.d.ts +16 -0
- package/dist/server/mutations/signout.d.ts.map +1 -0
- package/dist/server/mutations/signout.js +27 -0
- package/dist/server/mutations/signout.js.map +1 -0
- 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 +306 -0
- package/dist/server/mutations/store.d.ts.map +1 -0
- package/dist/server/mutations/store.js +85 -0
- package/dist/server/mutations/store.js.map +1 -0
- package/dist/server/mutations/verifier.d.ts +13 -0
- package/dist/server/mutations/verifier.d.ts.map +1 -0
- package/dist/server/mutations/verifier.js +18 -0
- package/dist/server/mutations/verifier.js.map +1 -0
- package/dist/server/mutations/verify.d.ts +26 -0
- package/dist/server/mutations/verify.d.ts.map +1 -0
- package/dist/server/mutations/verify.js +98 -0
- package/dist/server/mutations/verify.js.map +1 -0
- package/dist/server/oauth.d.ts +1 -48
- package/dist/server/oauth.js +107 -64
- package/dist/server/oauth.js.map +1 -1
- package/dist/server/passkey.d.ts +27 -0
- package/dist/server/passkey.d.ts.map +1 -0
- package/dist/server/passkey.js +328 -0
- package/dist/server/passkey.js.map +1 -0
- package/dist/server/redirects.d.ts +1 -0
- package/dist/{component/server/implementation → server}/redirects.js +13 -11
- package/dist/server/redirects.js.map +1 -0
- package/dist/server/refresh.d.ts +1 -0
- package/dist/server/refresh.js +96 -0
- package/dist/server/refresh.js.map +1 -0
- package/dist/server/runtime.d.ts +136 -0
- 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/sessions.d.ts +1 -0
- package/dist/{component/server/implementation → server}/sessions.js +14 -8
- package/dist/server/sessions.js.map +1 -0
- package/dist/server/signin.d.ts +1 -0
- package/dist/server/signin.js +201 -0
- package/dist/server/signin.js.map +1 -0
- 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/templates.d.ts +1 -21
- package/dist/server/templates.js +2 -1
- package/dist/server/templates.js.map +1 -1
- package/dist/server/tokens.d.ts +1 -0
- package/dist/server/tokens.js +17 -0
- package/dist/server/tokens.js.map +1 -0
- package/dist/server/totp.d.ts +1 -0
- package/dist/server/totp.js +148 -0
- package/dist/server/totp.js.map +1 -0
- package/dist/server/types.d.ts +498 -306
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/types.js +108 -1
- package/dist/server/types.js.map +1 -0
- package/dist/server/users.d.ts +1 -0
- package/dist/server/{implementation/users.js → users.js} +54 -35
- package/dist/server/users.js.map +1 -0
- package/dist/server/utils.d.ts +1 -6
- package/dist/server/utils.js +110 -4
- package/dist/server/utils.js.map +1 -1
- package/package.json +49 -46
- package/src/authorization/index.ts +83 -0
- package/src/cli/bin.ts +5 -0
- package/src/cli/command.ts +6 -5
- package/src/cli/index.ts +456 -248
- package/src/cli/keys.ts +3 -0
- 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 +745 -989
- 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 +42 -0
- package/src/component/_generated/component.ts +3123 -102
- package/src/component/functions.ts +38 -22
- package/src/component/index.ts +10 -20
- package/src/component/model.ts +449 -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 +26 -1766
- package/src/component/schema.ts +273 -100
- package/src/providers/anonymous.ts +10 -20
- package/src/providers/credentials.ts +14 -22
- package/src/providers/device.ts +3 -14
- package/src/providers/email.ts +83 -47
- package/src/providers/index.ts +7 -0
- package/src/providers/oauth.ts +5 -3
- package/src/providers/passkey.ts +0 -13
- package/src/providers/password.ts +307 -130
- package/src/providers/phone.ts +81 -37
- package/src/providers/sso.ts +54 -0
- package/src/providers/totp.ts +0 -13
- package/src/samlify.d.ts +53 -0
- package/src/server/auth.ts +701 -247
- package/src/server/authError.ts +44 -0
- package/src/server/{providers.ts → config.ts} +84 -15
- package/src/server/cookies.ts +8 -1
- package/src/server/core.ts +2095 -0
- package/src/server/crypto.ts +88 -0
- package/src/server/{implementation/db.ts → db.ts} +90 -15
- package/src/server/device.ts +221 -0
- package/src/server/enterprise/config.ts +51 -0
- package/src/server/enterprise/domain.ts +1751 -0
- 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/enterprise/validators.ts +93 -0
- package/src/server/errors.ts +130 -119
- package/src/server/http.ts +531 -0
- package/src/server/identity.ts +18 -0
- package/src/server/index.ts +32 -650
- package/src/server/{implementation/keys.ts → keys.ts} +16 -44
- package/src/server/limits.ts +134 -0
- package/src/server/mounts.ts +948 -0
- package/src/server/mutations/account.ts +76 -0
- package/src/server/{implementation/mutations → mutations}/code.ts +22 -11
- package/src/server/mutations/index.ts +13 -0
- package/src/server/mutations/invalidate.ts +50 -0
- package/src/server/mutations/oauth.ts +237 -0
- package/src/server/mutations/refresh.ts +298 -0
- package/src/server/mutations/register.ts +200 -0
- package/src/server/mutations/retrieve.ts +109 -0
- package/src/server/mutations/signature.ts +50 -0
- package/src/server/{implementation/mutations → mutations}/signin.ts +9 -7
- package/src/server/mutations/signout.ts +43 -0
- package/src/server/mutations/store/refs.ts +10 -0
- package/src/server/mutations/store.ts +138 -0
- package/src/server/mutations/verifier.ts +34 -0
- package/src/server/mutations/verify.ts +202 -0
- package/src/server/oauth.ts +243 -131
- package/src/server/passkey.ts +784 -0
- package/src/server/{implementation/redirects.ts → redirects.ts} +21 -16
- package/src/server/refresh.ts +222 -0
- package/src/server/runtime.ts +880 -0
- package/src/server/{implementation/sessions.ts → sessions.ts} +33 -25
- package/src/server/signin.ts +438 -0
- package/src/server/ssr.ts +1764 -0
- package/src/server/templates.ts +8 -3
- package/src/server/{implementation/tokens.ts → tokens.ts} +11 -5
- package/src/server/totp.ts +349 -0
- package/src/server/types.ts +972 -207
- package/src/server/{implementation/users.ts → users.ts} +129 -75
- package/src/server/utils.ts +192 -5
- package/src/test.ts +28 -4
- package/dist/bin.cjs +0 -27757
- package/dist/component/providers/email.js +0 -47
- package/dist/component/providers/email.js.map +0 -1
- package/dist/component/public.js.map +0 -1
- package/dist/component/server/implementation/db.js.map +0 -1
- package/dist/component/server/implementation/device.js +0 -135
- package/dist/component/server/implementation/device.js.map +0 -1
- package/dist/component/server/implementation/index.d.ts +0 -870
- package/dist/component/server/implementation/index.d.ts.map +0 -1
- package/dist/component/server/implementation/index.js +0 -610
- package/dist/component/server/implementation/index.js.map +0 -1
- package/dist/component/server/implementation/keys.js.map +0 -1
- package/dist/component/server/implementation/mutations/account.js +0 -39
- package/dist/component/server/implementation/mutations/account.js.map +0 -1
- package/dist/component/server/implementation/mutations/code.js.map +0 -1
- package/dist/component/server/implementation/mutations/index.js +0 -70
- package/dist/component/server/implementation/mutations/index.js.map +0 -1
- package/dist/component/server/implementation/mutations/invalidate.js +0 -29
- package/dist/component/server/implementation/mutations/invalidate.js.map +0 -1
- package/dist/component/server/implementation/mutations/oauth.js +0 -51
- package/dist/component/server/implementation/mutations/oauth.js.map +0 -1
- package/dist/component/server/implementation/mutations/refresh.js +0 -85
- package/dist/component/server/implementation/mutations/refresh.js.map +0 -1
- package/dist/component/server/implementation/mutations/register.js +0 -65
- package/dist/component/server/implementation/mutations/register.js.map +0 -1
- package/dist/component/server/implementation/mutations/retrieve.js +0 -50
- package/dist/component/server/implementation/mutations/retrieve.js.map +0 -1
- package/dist/component/server/implementation/mutations/signature.js +0 -27
- package/dist/component/server/implementation/mutations/signature.js.map +0 -1
- package/dist/component/server/implementation/mutations/signin.js.map +0 -1
- package/dist/component/server/implementation/mutations/signout.js +0 -27
- package/dist/component/server/implementation/mutations/signout.js.map +0 -1
- package/dist/component/server/implementation/mutations/store.js +0 -12
- package/dist/component/server/implementation/mutations/store.js.map +0 -1
- package/dist/component/server/implementation/mutations/verifier.js +0 -16
- package/dist/component/server/implementation/mutations/verifier.js.map +0 -1
- package/dist/component/server/implementation/mutations/verify.js +0 -105
- package/dist/component/server/implementation/mutations/verify.js.map +0 -1
- package/dist/component/server/implementation/passkey.js +0 -307
- package/dist/component/server/implementation/passkey.js.map +0 -1
- package/dist/component/server/implementation/provider.js +0 -19
- package/dist/component/server/implementation/provider.js.map +0 -1
- package/dist/component/server/implementation/ratelimit.js +0 -48
- package/dist/component/server/implementation/ratelimit.js.map +0 -1
- package/dist/component/server/implementation/redirects.js.map +0 -1
- package/dist/component/server/implementation/refresh.js +0 -109
- package/dist/component/server/implementation/refresh.js.map +0 -1
- package/dist/component/server/implementation/sessions.js.map +0 -1
- package/dist/component/server/implementation/signin.js +0 -148
- package/dist/component/server/implementation/signin.js.map +0 -1
- package/dist/component/server/implementation/tokens.js +0 -15
- package/dist/component/server/implementation/tokens.js.map +0 -1
- package/dist/component/server/implementation/totp.js +0 -142
- package/dist/component/server/implementation/totp.js.map +0 -1
- package/dist/component/server/implementation/types.d.ts +0 -42
- package/dist/component/server/implementation/types.d.ts.map +0 -1
- package/dist/component/server/implementation/types.js.map +0 -1
- package/dist/component/server/implementation/users.js.map +0 -1
- package/dist/component/server/implementation/utils.js +0 -56
- package/dist/component/server/implementation/utils.js.map +0 -1
- package/dist/component/server/providers.js.map +0 -1
- package/dist/component/server/templates.js +0 -84
- package/dist/component/server/templates.js.map +0 -1
- package/dist/server/cookies.d.ts.map +0 -1
- package/dist/server/implementation/db.d.ts +0 -86
- package/dist/server/implementation/db.d.ts.map +0 -1
- package/dist/server/implementation/db.js.map +0 -1
- package/dist/server/implementation/device.d.ts +0 -30
- package/dist/server/implementation/device.d.ts.map +0 -1
- package/dist/server/implementation/device.js +0 -135
- package/dist/server/implementation/device.js.map +0 -1
- package/dist/server/implementation/index.d.ts +0 -870
- package/dist/server/implementation/index.d.ts.map +0 -1
- package/dist/server/implementation/index.js +0 -610
- package/dist/server/implementation/index.js.map +0 -1
- package/dist/server/implementation/keys.d.ts +0 -66
- package/dist/server/implementation/keys.d.ts.map +0 -1
- package/dist/server/implementation/keys.js.map +0 -1
- package/dist/server/implementation/mutations/account.d.ts +0 -27
- package/dist/server/implementation/mutations/account.d.ts.map +0 -1
- package/dist/server/implementation/mutations/account.js +0 -39
- package/dist/server/implementation/mutations/account.js.map +0 -1
- package/dist/server/implementation/mutations/code.d.ts +0 -29
- package/dist/server/implementation/mutations/code.d.ts.map +0 -1
- package/dist/server/implementation/mutations/code.js.map +0 -1
- package/dist/server/implementation/mutations/index.d.ts +0 -310
- package/dist/server/implementation/mutations/index.d.ts.map +0 -1
- package/dist/server/implementation/mutations/index.js +0 -70
- package/dist/server/implementation/mutations/index.js.map +0 -1
- package/dist/server/implementation/mutations/invalidate.d.ts +0 -18
- package/dist/server/implementation/mutations/invalidate.d.ts.map +0 -1
- package/dist/server/implementation/mutations/invalidate.js +0 -29
- package/dist/server/implementation/mutations/invalidate.js.map +0 -1
- package/dist/server/implementation/mutations/oauth.d.ts +0 -23
- package/dist/server/implementation/mutations/oauth.d.ts.map +0 -1
- package/dist/server/implementation/mutations/oauth.js +0 -51
- package/dist/server/implementation/mutations/oauth.js.map +0 -1
- package/dist/server/implementation/mutations/refresh.d.ts +0 -20
- package/dist/server/implementation/mutations/refresh.d.ts.map +0 -1
- package/dist/server/implementation/mutations/refresh.js +0 -85
- package/dist/server/implementation/mutations/refresh.js.map +0 -1
- package/dist/server/implementation/mutations/register.d.ts +0 -37
- package/dist/server/implementation/mutations/register.d.ts.map +0 -1
- package/dist/server/implementation/mutations/register.js +0 -65
- package/dist/server/implementation/mutations/register.js.map +0 -1
- package/dist/server/implementation/mutations/retrieve.d.ts +0 -31
- package/dist/server/implementation/mutations/retrieve.d.ts.map +0 -1
- package/dist/server/implementation/mutations/retrieve.js +0 -50
- package/dist/server/implementation/mutations/retrieve.js.map +0 -1
- package/dist/server/implementation/mutations/signature.d.ts +0 -19
- package/dist/server/implementation/mutations/signature.d.ts.map +0 -1
- package/dist/server/implementation/mutations/signature.js +0 -27
- package/dist/server/implementation/mutations/signature.js.map +0 -1
- package/dist/server/implementation/mutations/signin.d.ts +0 -21
- package/dist/server/implementation/mutations/signin.d.ts.map +0 -1
- package/dist/server/implementation/mutations/signin.js.map +0 -1
- package/dist/server/implementation/mutations/signout.d.ts +0 -14
- package/dist/server/implementation/mutations/signout.d.ts.map +0 -1
- package/dist/server/implementation/mutations/signout.js +0 -27
- package/dist/server/implementation/mutations/signout.js.map +0 -1
- package/dist/server/implementation/mutations/store.d.ts +0 -11
- package/dist/server/implementation/mutations/store.d.ts.map +0 -1
- package/dist/server/implementation/mutations/store.js +0 -12
- package/dist/server/implementation/mutations/store.js.map +0 -1
- package/dist/server/implementation/mutations/verifier.d.ts +0 -11
- package/dist/server/implementation/mutations/verifier.d.ts.map +0 -1
- package/dist/server/implementation/mutations/verifier.js +0 -16
- package/dist/server/implementation/mutations/verifier.js.map +0 -1
- package/dist/server/implementation/mutations/verify.d.ts +0 -25
- package/dist/server/implementation/mutations/verify.d.ts.map +0 -1
- package/dist/server/implementation/mutations/verify.js +0 -105
- package/dist/server/implementation/mutations/verify.js.map +0 -1
- package/dist/server/implementation/passkey.d.ts +0 -24
- package/dist/server/implementation/passkey.d.ts.map +0 -1
- package/dist/server/implementation/passkey.js +0 -307
- package/dist/server/implementation/passkey.js.map +0 -1
- package/dist/server/implementation/provider.d.ts +0 -10
- package/dist/server/implementation/provider.d.ts.map +0 -1
- package/dist/server/implementation/provider.js +0 -19
- package/dist/server/implementation/provider.js.map +0 -1
- package/dist/server/implementation/ratelimit.d.ts +0 -10
- package/dist/server/implementation/ratelimit.d.ts.map +0 -1
- package/dist/server/implementation/ratelimit.js +0 -48
- package/dist/server/implementation/ratelimit.js.map +0 -1
- package/dist/server/implementation/redirects.d.ts +0 -10
- package/dist/server/implementation/redirects.d.ts.map +0 -1
- package/dist/server/implementation/redirects.js.map +0 -1
- package/dist/server/implementation/refresh.d.ts +0 -37
- package/dist/server/implementation/refresh.d.ts.map +0 -1
- package/dist/server/implementation/refresh.js +0 -109
- package/dist/server/implementation/refresh.js.map +0 -1
- package/dist/server/implementation/sessions.d.ts +0 -29
- package/dist/server/implementation/sessions.d.ts.map +0 -1
- package/dist/server/implementation/sessions.js.map +0 -1
- package/dist/server/implementation/signin.d.ts +0 -55
- package/dist/server/implementation/signin.d.ts.map +0 -1
- package/dist/server/implementation/signin.js +0 -148
- package/dist/server/implementation/signin.js.map +0 -1
- package/dist/server/implementation/tokens.d.ts +0 -11
- package/dist/server/implementation/tokens.d.ts.map +0 -1
- package/dist/server/implementation/tokens.js +0 -15
- package/dist/server/implementation/tokens.js.map +0 -1
- package/dist/server/implementation/totp.d.ts +0 -31
- package/dist/server/implementation/totp.d.ts.map +0 -1
- package/dist/server/implementation/totp.js +0 -142
- package/dist/server/implementation/totp.js.map +0 -1
- package/dist/server/implementation/types.d.ts +0 -189
- package/dist/server/implementation/types.d.ts.map +0 -1
- package/dist/server/implementation/types.js +0 -97
- package/dist/server/implementation/types.js.map +0 -1
- package/dist/server/implementation/users.d.ts +0 -30
- package/dist/server/implementation/users.d.ts.map +0 -1
- package/dist/server/implementation/users.js.map +0 -1
- package/dist/server/implementation/utils.d.ts +0 -19
- package/dist/server/implementation/utils.d.ts.map +0 -1
- package/dist/server/implementation/utils.js +0 -56
- package/dist/server/implementation/utils.js.map +0 -1
- package/dist/server/index.d.ts.map +0 -1
- package/dist/server/index.js.map +0 -1
- package/dist/server/oauth.d.ts.map +0 -1
- package/dist/server/providers.d.ts +0 -72
- package/dist/server/providers.d.ts.map +0 -1
- package/dist/server/providers.js.map +0 -1
- package/dist/server/templates.d.ts.map +0 -1
- package/dist/server/utils.d.ts.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/cli/utils.ts +0 -248
- package/src/server/implementation/device.ts +0 -307
- package/src/server/implementation/index.ts +0 -1583
- package/src/server/implementation/mutations/account.ts +0 -50
- package/src/server/implementation/mutations/index.ts +0 -157
- package/src/server/implementation/mutations/invalidate.ts +0 -42
- package/src/server/implementation/mutations/oauth.ts +0 -73
- package/src/server/implementation/mutations/refresh.ts +0 -175
- package/src/server/implementation/mutations/register.ts +0 -100
- package/src/server/implementation/mutations/retrieve.ts +0 -79
- package/src/server/implementation/mutations/signature.ts +0 -39
- package/src/server/implementation/mutations/signout.ts +0 -35
- package/src/server/implementation/mutations/store.ts +0 -7
- package/src/server/implementation/mutations/verifier.ts +0 -24
- package/src/server/implementation/mutations/verify.ts +0 -194
- package/src/server/implementation/passkey.ts +0 -620
- package/src/server/implementation/provider.ts +0 -36
- package/src/server/implementation/ratelimit.ts +0 -79
- package/src/server/implementation/refresh.ts +0 -172
- package/src/server/implementation/signin.ts +0 -296
- package/src/server/implementation/totp.ts +0 -342
- package/src/server/implementation/types.ts +0 -444
- package/src/server/implementation/utils.ts +0 -91
- package/src/server/version.ts +0 -2
package/src/client/index.ts
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
|
+
import { Fx } from "@robelest/fx";
|
|
1
2
|
import { ConvexHttpClient } from "convex/browser";
|
|
2
3
|
import { ConvexError, Value } from "convex/values";
|
|
3
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
|
+
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";
|
|
36
|
+
|
|
4
37
|
// Re-export error utilities so consumers can import from `@robelest/convex-auth/client`.
|
|
5
38
|
export {
|
|
6
39
|
isAuthError,
|
|
@@ -8,139 +41,29 @@ export {
|
|
|
8
41
|
AUTH_ERRORS,
|
|
9
42
|
type AuthErrorCode,
|
|
10
43
|
} from "../server/errors";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
fetchToken: (args: {
|
|
25
|
-
forceRefreshToken: boolean;
|
|
26
|
-
}) => Promise<string | null | undefined>,
|
|
27
|
-
onChange?: (isAuthenticated: boolean) => void,
|
|
28
|
-
): void;
|
|
29
|
-
clearAuth?(): void;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** Pluggable key-value storage (defaults to `localStorage`). */
|
|
33
|
-
export interface Storage {
|
|
34
|
-
getItem(
|
|
35
|
-
key: string,
|
|
36
|
-
): string | null | undefined | Promise<string | null | undefined>;
|
|
37
|
-
setItem(key: string, value: string): void | Promise<void>;
|
|
38
|
-
removeItem(key: string): void | Promise<void>;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
type AuthSession = {
|
|
42
|
-
token: string;
|
|
43
|
-
refreshToken: string;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Device code response returned when signing in with the `"device"` provider.
|
|
48
|
-
*
|
|
49
|
-
* The device displays the `userCode` (or `verificationUriComplete`) and
|
|
50
|
-
* polls via `auth.device.poll()` until the user authorizes.
|
|
51
|
-
*/
|
|
52
|
-
export type DeviceCodeResult = {
|
|
53
|
-
/** High-entropy device code used for polling (keep secret). */
|
|
54
|
-
deviceCode: string;
|
|
55
|
-
/** Short human-readable code the user enters (e.g. "WDJB-MJHT"). */
|
|
56
|
-
userCode: string;
|
|
57
|
-
/** Base verification URL (e.g. "https://myapp.com/device"). */
|
|
58
|
-
verificationUri: string;
|
|
59
|
-
/** Verification URL with user code pre-filled as `?code=XXXX-XXXX`. */
|
|
60
|
-
verificationUriComplete: string;
|
|
61
|
-
/** Lifetime of the codes in seconds. */
|
|
62
|
-
expiresIn: number;
|
|
63
|
-
/** Minimum polling interval in seconds. */
|
|
64
|
-
interval: number;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Result of a `signIn` call.
|
|
69
|
-
*
|
|
70
|
-
* - `signingIn: true` — credentials were accepted and the user is authenticated.
|
|
71
|
-
* - `redirect` — OAuth flow initiated; redirect the user to `redirect.toString()`.
|
|
72
|
-
* - `totpRequired` — credentials valid but 2FA is needed; call `auth.totp.verify()`.
|
|
73
|
-
* - `deviceCode` — device flow initiated; display the code and poll via `auth.device.poll()`.
|
|
74
|
-
* - `verifier` — opaque string for multi-step flows (TOTP, passkey).
|
|
75
|
-
*/
|
|
76
|
-
export type SignInResult = {
|
|
77
|
-
/** `true` when sign-in completed and the user is authenticated. */
|
|
78
|
-
signingIn: boolean;
|
|
79
|
-
/** OAuth redirect URL. Present when the provider requires a browser redirect. */
|
|
80
|
-
redirect?: URL;
|
|
81
|
-
/** `true` when the account has TOTP enabled and a code is required. */
|
|
82
|
-
totpRequired?: boolean;
|
|
83
|
-
/** Device code response for the device authorization flow (RFC 8628). */
|
|
84
|
-
deviceCode?: DeviceCodeResult;
|
|
85
|
-
/** Opaque verifier for multi-step flows (pass to `totp.verify` or passkey phase 2). */
|
|
86
|
-
verifier?: string;
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
/** Reactive auth state snapshot returned by `auth.state` and `auth.onChange`. */
|
|
90
|
-
export type AuthState = {
|
|
91
|
-
/** `true` during initial hydration before the first token is resolved. */
|
|
92
|
-
isLoading: boolean;
|
|
93
|
-
/** `true` when a valid JWT exists (user is signed in). */
|
|
94
|
-
isAuthenticated: boolean;
|
|
95
|
-
/** The raw JWT string, or `null` when not authenticated. */
|
|
96
|
-
token: string | null;
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
/** Options for {@link client}. */
|
|
100
|
-
export type ClientOptions = {
|
|
101
|
-
/** Any Convex client (`ConvexClient` or `ConvexReactClient`). */
|
|
102
|
-
convex: ConvexTransport;
|
|
103
|
-
/**
|
|
104
|
-
* Convex deployment URL. Derived automatically from the client internals
|
|
105
|
-
* when omitted — pass explicitly only if auto-detection fails.
|
|
106
|
-
*/
|
|
107
|
-
url?: string;
|
|
108
|
-
/**
|
|
109
|
-
* Key-value storage for persisting tokens.
|
|
110
|
-
*
|
|
111
|
-
* - Defaults to `localStorage` in SPA mode.
|
|
112
|
-
* - Defaults to `null` (in-memory only) when `proxy` is set,
|
|
113
|
-
* since httpOnly cookies handle persistence.
|
|
114
|
-
*/
|
|
115
|
-
storage?: Storage | null;
|
|
116
|
-
/** Override how the URL bar is updated after OAuth code exchange. */
|
|
117
|
-
replaceURL?: (relativeUrl: string) => void | Promise<void>;
|
|
118
|
-
/**
|
|
119
|
-
* SSR proxy endpoint (e.g. `"/api/auth"`).
|
|
120
|
-
*
|
|
121
|
-
* When set, `signIn`/`signOut`/token refresh POST to this URL
|
|
122
|
-
* (with `credentials: "include"`) instead of calling Convex directly.
|
|
123
|
-
* The server handles httpOnly cookies for token persistence.
|
|
124
|
-
*
|
|
125
|
-
* Pair with {@link ClientOptions.token} for flash-free SSR hydration.
|
|
126
|
-
*/
|
|
127
|
-
proxy?: string;
|
|
128
|
-
/**
|
|
129
|
-
* JWT from server-side hydration.
|
|
130
|
-
*
|
|
131
|
-
* In proxy mode the server reads the JWT from an httpOnly cookie
|
|
132
|
-
* and passes it to the client during SSR. This avoids a loading
|
|
133
|
-
* flash on first render — the client is immediately authenticated.
|
|
134
|
-
*/
|
|
135
|
-
token?: string | null;
|
|
136
|
-
};
|
|
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";
|
|
137
57
|
|
|
138
58
|
const VERIFIER_STORAGE_KEY = "__convexAuthOAuthVerifier";
|
|
139
59
|
const JWT_STORAGE_KEY = "__convexAuthJWT";
|
|
140
60
|
const REFRESH_TOKEN_STORAGE_KEY = "__convexAuthRefreshToken";
|
|
61
|
+
const INVITE_TOKEN_KEY = "__convexAuthPendingInvite";
|
|
62
|
+
const INVITE_EMAIL_KEY = "__convexAuthPendingInviteEmail";
|
|
141
63
|
|
|
142
|
-
const
|
|
143
|
-
const
|
|
64
|
+
const RETRY_BASE_MS = 500;
|
|
65
|
+
const RETRY_MAX_RETRIES = 2;
|
|
66
|
+
const AUTH_HANDSHAKE_TIMEOUT_MS = 5000;
|
|
144
67
|
|
|
145
68
|
/**
|
|
146
69
|
* Resolve the Convex deployment URL from the client.
|
|
@@ -169,9 +92,10 @@ function resolveUrl(convex: ConvexTransport, explicit?: string): string {
|
|
|
169
92
|
* ```ts
|
|
170
93
|
* import { ConvexClient } from 'convex/browser';
|
|
171
94
|
* import { client } from '@robelest/convex-auth/client';
|
|
95
|
+
* import { api } from '../convex/_generated/api';
|
|
172
96
|
*
|
|
173
97
|
* const convex = new ConvexClient(CONVEX_URL);
|
|
174
|
-
* const auth = client({ convex });
|
|
98
|
+
* const auth = client({ convex, api: api.auth });
|
|
175
99
|
* ```
|
|
176
100
|
*
|
|
177
101
|
* ### SSR / proxy mode
|
|
@@ -179,8 +103,8 @@ function resolveUrl(convex: ConvexTransport, explicit?: string): string {
|
|
|
179
103
|
* ```ts
|
|
180
104
|
* const auth = client({
|
|
181
105
|
* convex,
|
|
182
|
-
*
|
|
183
|
-
*
|
|
106
|
+
* proxyPath: '/api/auth',
|
|
107
|
+
* tokenSeed: tokenFromServer, // JWT read from httpOnly cookie during SSR
|
|
184
108
|
* });
|
|
185
109
|
* ```
|
|
186
110
|
*
|
|
@@ -189,10 +113,26 @@ function resolveUrl(convex: ConvexTransport, explicit?: string): string {
|
|
|
189
113
|
* holds the JWT in memory only.
|
|
190
114
|
*
|
|
191
115
|
* @param options - Client configuration. See {@link ClientOptions}.
|
|
192
|
-
* @
|
|
116
|
+
* @typeParam Api - An AuthApiRefs type determining which factor helpers are available.
|
|
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.
|
|
193
120
|
*/
|
|
194
|
-
export function client
|
|
195
|
-
|
|
121
|
+
export function client<
|
|
122
|
+
Api extends AuthApiRefs<boolean, boolean, boolean> = AuthApiRefs,
|
|
123
|
+
>(options: ClientOptions<Api>): AuthClient<Api> {
|
|
124
|
+
const { convex, proxyPath, api: apiRefs } = options;
|
|
125
|
+
const proxy = proxyPath;
|
|
126
|
+
|
|
127
|
+
function requireApiRefs() {
|
|
128
|
+
if (!apiRefs) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
"The `api` option is required when `proxyPath` is not set. " +
|
|
131
|
+
"Pass { api: api.auth }.",
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
return apiRefs;
|
|
135
|
+
}
|
|
196
136
|
|
|
197
137
|
// In proxy mode, default storage to null (cookies handle persistence).
|
|
198
138
|
const storage =
|
|
@@ -204,20 +144,79 @@ export function client(options: ClientOptions) {
|
|
|
204
144
|
? null
|
|
205
145
|
: window.localStorage;
|
|
206
146
|
|
|
207
|
-
const
|
|
208
|
-
options.
|
|
147
|
+
const replaceUrl =
|
|
148
|
+
options.replaceUrl ??
|
|
209
149
|
((url: string) => {
|
|
210
150
|
if (typeof window !== "undefined") {
|
|
211
151
|
window.history.replaceState({}, "", url);
|
|
212
152
|
}
|
|
213
153
|
});
|
|
214
154
|
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
// Location — SSR-safe URL reading
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
|
|
159
|
+
function getLocation(): URL | null {
|
|
160
|
+
if (typeof options.location === "function") return options.location();
|
|
161
|
+
if (options.location instanceof URL) return options.location;
|
|
162
|
+
if (typeof window !== "undefined") return new URL(window.location.href);
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
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
|
+
*/
|
|
181
|
+
function param(name: string): string | null {
|
|
182
|
+
const loc = getLocation();
|
|
183
|
+
return loc?.searchParams.get(name) ?? null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function cleanUrlParams(params: string[]) {
|
|
187
|
+
const loc = getLocation();
|
|
188
|
+
if (!loc) return;
|
|
189
|
+
const searchParams = new URLSearchParams(loc.search);
|
|
190
|
+
let changed = false;
|
|
191
|
+
for (const p of params) {
|
|
192
|
+
if (searchParams.has(p)) {
|
|
193
|
+
searchParams.delete(p);
|
|
194
|
+
changed = true;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (changed) {
|
|
198
|
+
const next = searchParams.toString()
|
|
199
|
+
? `${loc.pathname}?${searchParams}`
|
|
200
|
+
: loc.pathname;
|
|
201
|
+
void replaceUrl(next);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
215
205
|
const url = proxy ? undefined : resolveUrl(convex, options.url);
|
|
216
206
|
const escapedNamespace = proxy
|
|
217
207
|
? proxy.replace(/[^a-zA-Z0-9]/g, "")
|
|
218
208
|
: url!.replace(/[^a-zA-Z0-9]/g, "");
|
|
219
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
|
+
});
|
|
220
218
|
const subscribers = new Set<() => void>();
|
|
219
|
+
let disposeStorageListener: (() => void) | null = null;
|
|
221
220
|
|
|
222
221
|
// Unauthenticated HTTP client for code verification & OAuth exchange.
|
|
223
222
|
// Only needed in SPA mode — proxy mode routes everything through the proxy.
|
|
@@ -227,30 +226,173 @@ export function client(options: ClientOptions) {
|
|
|
227
226
|
// State
|
|
228
227
|
// ---------------------------------------------------------------------------
|
|
229
228
|
|
|
230
|
-
// If a server-provided token was supplied (SSR hydration),
|
|
231
|
-
|
|
229
|
+
// If a server-provided token was supplied (SSR hydration), treat it as
|
|
230
|
+
// immediately authenticated to avoid a handshake-only loading screen.
|
|
231
|
+
const serverToken =
|
|
232
|
+
typeof options.tokenSeed === "string" && options.tokenSeed.trim().length > 0
|
|
233
|
+
? options.tokenSeed
|
|
234
|
+
: null;
|
|
232
235
|
const hasServerToken = serverToken !== null;
|
|
233
236
|
|
|
234
237
|
let token: string | null = serverToken;
|
|
235
238
|
let isLoading = !hasServerToken;
|
|
239
|
+
let authConfirmed = hasServerToken;
|
|
240
|
+
let handshakePending = false;
|
|
241
|
+
let authEpoch = 0;
|
|
242
|
+
let destroyed = false;
|
|
243
|
+
const handshakeWaiters = new Set<HandshakeWaiter>();
|
|
236
244
|
let snapshot: AuthState = {
|
|
245
|
+
phase: hasServerToken
|
|
246
|
+
? "authenticated"
|
|
247
|
+
: isLoading
|
|
248
|
+
? "loading"
|
|
249
|
+
: "unauthenticated",
|
|
237
250
|
isLoading,
|
|
238
251
|
isAuthenticated: hasServerToken,
|
|
239
252
|
token,
|
|
240
253
|
};
|
|
241
254
|
let handlingCodeFlow = false;
|
|
242
255
|
|
|
256
|
+
const createHandshakeError = (
|
|
257
|
+
code: AuthHandshakeErrorCode,
|
|
258
|
+
context: Record<string, unknown>,
|
|
259
|
+
) => {
|
|
260
|
+
return new ConvexError({
|
|
261
|
+
code,
|
|
262
|
+
message: AUTH_ERRORS[code],
|
|
263
|
+
...context,
|
|
264
|
+
} as Value);
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const settleHandshakeWaiters = (
|
|
268
|
+
epoch: number,
|
|
269
|
+
outcome:
|
|
270
|
+
| { type: "resolve" }
|
|
271
|
+
| { type: "reject"; error: ConvexError<Value> },
|
|
272
|
+
) => {
|
|
273
|
+
for (const waiter of Array.from(handshakeWaiters)) {
|
|
274
|
+
if (waiter.epoch !== epoch) {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
clearTimeout(waiter.timeoutId);
|
|
278
|
+
handshakeWaiters.delete(waiter);
|
|
279
|
+
if (outcome.type === "resolve") {
|
|
280
|
+
waiter.resolve();
|
|
281
|
+
} else {
|
|
282
|
+
waiter.reject(outcome.error);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
const rejectObsoleteHandshakeWaiters = (activeEpoch: number) => {
|
|
288
|
+
for (const waiter of Array.from(handshakeWaiters)) {
|
|
289
|
+
if (waiter.epoch >= activeEpoch) {
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
clearTimeout(waiter.timeoutId);
|
|
293
|
+
handshakeWaiters.delete(waiter);
|
|
294
|
+
waiter.reject(
|
|
295
|
+
createHandshakeError("AUTH_HANDSHAKE_REJECTED", {
|
|
296
|
+
...waiter.context,
|
|
297
|
+
reason: "token_changed",
|
|
298
|
+
}),
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const waitForAuthHandshake = async (context: AuthFlowContext) => {
|
|
304
|
+
if (token === null) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (authConfirmed && !handshakePending) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (!handshakePending) {
|
|
311
|
+
throw createHandshakeError("AUTH_HANDSHAKE_REJECTED", {
|
|
312
|
+
...context,
|
|
313
|
+
reason: "auth_rejected",
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const epoch = authEpoch;
|
|
318
|
+
await new Promise<void>((resolve, reject) => {
|
|
319
|
+
const waiterRef: { current: HandshakeWaiter | null } = { current: null };
|
|
320
|
+
const timeoutId = setTimeout(() => {
|
|
321
|
+
if (waiterRef.current !== null) {
|
|
322
|
+
handshakeWaiters.delete(waiterRef.current);
|
|
323
|
+
}
|
|
324
|
+
reject(
|
|
325
|
+
createHandshakeError("AUTH_HANDSHAKE_TIMEOUT", {
|
|
326
|
+
...context,
|
|
327
|
+
timeoutMs: AUTH_HANDSHAKE_TIMEOUT_MS,
|
|
328
|
+
}),
|
|
329
|
+
);
|
|
330
|
+
}, AUTH_HANDSHAKE_TIMEOUT_MS);
|
|
331
|
+
|
|
332
|
+
const waiter: HandshakeWaiter = {
|
|
333
|
+
epoch,
|
|
334
|
+
context,
|
|
335
|
+
resolve,
|
|
336
|
+
reject,
|
|
337
|
+
timeoutId,
|
|
338
|
+
};
|
|
339
|
+
waiterRef.current = waiter;
|
|
340
|
+
handshakeWaiters.add(waiter);
|
|
341
|
+
});
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
const handleConvexAuthChange = (isAuthenticated: boolean) => {
|
|
345
|
+
if (destroyed) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (isAuthenticated) {
|
|
350
|
+
authConfirmed = true;
|
|
351
|
+
handshakePending = false;
|
|
352
|
+
settleHandshakeWaiters(authEpoch, { type: "resolve" });
|
|
353
|
+
} else {
|
|
354
|
+
authConfirmed = false;
|
|
355
|
+
// Do not reject immediately while a handshake is pending.
|
|
356
|
+
// Convex can transiently emit `false` while reauth is still in flight,
|
|
357
|
+
// and a subsequent `true` confirms the same session.
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (updateSnapshot()) {
|
|
361
|
+
notify();
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
|
|
243
365
|
const notify = () => {
|
|
244
366
|
for (const cb of subscribers) cb();
|
|
245
367
|
};
|
|
246
368
|
|
|
247
369
|
const updateSnapshot = () => {
|
|
370
|
+
const phaseDispatch = {
|
|
371
|
+
tag:
|
|
372
|
+
token !== null && handshakePending
|
|
373
|
+
? "handshake"
|
|
374
|
+
: isLoading
|
|
375
|
+
? "loading"
|
|
376
|
+
: token !== null && authConfirmed
|
|
377
|
+
? "authenticated"
|
|
378
|
+
: "unauthenticated",
|
|
379
|
+
} as const;
|
|
380
|
+
|
|
381
|
+
const phase = {
|
|
382
|
+
handshake: "handshake",
|
|
383
|
+
loading: "loading",
|
|
384
|
+
authenticated: "authenticated",
|
|
385
|
+
unauthenticated: "unauthenticated",
|
|
386
|
+
}[phaseDispatch.tag] as AuthState["phase"];
|
|
387
|
+
|
|
248
388
|
const next: AuthState = {
|
|
249
|
-
|
|
250
|
-
|
|
389
|
+
phase,
|
|
390
|
+
isLoading: phase === "loading" || phase === "handshake",
|
|
391
|
+
isAuthenticated: phase === "authenticated",
|
|
251
392
|
token,
|
|
252
393
|
};
|
|
253
394
|
if (
|
|
395
|
+
snapshot.phase === next.phase &&
|
|
254
396
|
snapshot.isLoading === next.isLoading &&
|
|
255
397
|
snapshot.isAuthenticated === next.isAuthenticated &&
|
|
256
398
|
snapshot.token === next.token
|
|
@@ -261,28 +403,54 @@ export function client(options: ClientOptions) {
|
|
|
261
403
|
return true;
|
|
262
404
|
};
|
|
263
405
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
};
|
|
273
|
-
const storageRemove = async (name: string) => {
|
|
274
|
-
if (storage) await storage.removeItem(key(name));
|
|
406
|
+
const finalizeLoadingState = () => {
|
|
407
|
+
if (!isLoading) {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
isLoading = false;
|
|
411
|
+
if (updateSnapshot()) {
|
|
412
|
+
notify();
|
|
413
|
+
}
|
|
275
414
|
};
|
|
276
415
|
|
|
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();
|
|
428
|
+
|
|
277
429
|
// ---------------------------------------------------------------------------
|
|
278
430
|
// Token management
|
|
279
431
|
// ---------------------------------------------------------------------------
|
|
280
432
|
|
|
433
|
+
const bindConvexAuth = () => {
|
|
434
|
+
convex.setAuth(fetchAccessToken, handleConvexAuthChange);
|
|
435
|
+
};
|
|
436
|
+
|
|
281
437
|
const setToken = async (
|
|
282
438
|
args:
|
|
283
|
-
| {
|
|
284
|
-
|
|
439
|
+
| {
|
|
440
|
+
shouldStore: true;
|
|
441
|
+
tokens: AuthSession | null;
|
|
442
|
+
requireHandshake?: boolean;
|
|
443
|
+
resyncConvexAuth?: boolean;
|
|
444
|
+
}
|
|
445
|
+
| {
|
|
446
|
+
shouldStore: false;
|
|
447
|
+
tokens: { token: string } | null;
|
|
448
|
+
requireHandshake?: boolean;
|
|
449
|
+
resyncConvexAuth?: boolean;
|
|
450
|
+
},
|
|
285
451
|
) => {
|
|
452
|
+
const previousToken = token;
|
|
453
|
+
|
|
286
454
|
if (args.tokens === null) {
|
|
287
455
|
token = null;
|
|
288
456
|
if (args.shouldStore) {
|
|
@@ -296,49 +464,72 @@ export function client(options: ClientOptions) {
|
|
|
296
464
|
await storageSet(REFRESH_TOKEN_STORAGE_KEY, args.tokens.refreshToken);
|
|
297
465
|
}
|
|
298
466
|
}
|
|
467
|
+
|
|
468
|
+
if (token !== previousToken) {
|
|
469
|
+
authEpoch += 1;
|
|
470
|
+
rejectObsoleteHandshakeWaiters(authEpoch);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (token === null) {
|
|
474
|
+
authConfirmed = false;
|
|
475
|
+
handshakePending = false;
|
|
476
|
+
settleHandshakeWaiters(authEpoch, {
|
|
477
|
+
type: "reject",
|
|
478
|
+
error: createHandshakeError("AUTH_HANDSHAKE_REJECTED", {
|
|
479
|
+
reason: "token_cleared",
|
|
480
|
+
}),
|
|
481
|
+
});
|
|
482
|
+
} else {
|
|
483
|
+
const shouldEnterHandshake =
|
|
484
|
+
args.requireHandshake === true || !authConfirmed;
|
|
485
|
+
if (shouldEnterHandshake) {
|
|
486
|
+
authConfirmed = false;
|
|
487
|
+
handshakePending = true;
|
|
488
|
+
} else {
|
|
489
|
+
handshakePending = false;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
299
493
|
const hadPendingLoad = isLoading;
|
|
300
494
|
isLoading = false;
|
|
301
495
|
const changed = updateSnapshot();
|
|
496
|
+
if (args.resyncConvexAuth !== false) {
|
|
497
|
+
bindConvexAuth();
|
|
498
|
+
}
|
|
302
499
|
if (hadPendingLoad || changed) {
|
|
303
|
-
// Re-sync the Convex client so it picks up the new token immediately.
|
|
304
|
-
// Without this, the initial convex.setAuth(fetchAccessToken) from
|
|
305
|
-
// initialization never re-polls and queries run unauthenticated after
|
|
306
|
-
// magic link code exchange.
|
|
307
|
-
if (!proxy) {
|
|
308
|
-
convex.setAuth(fetchAccessToken);
|
|
309
|
-
}
|
|
310
500
|
notify();
|
|
311
501
|
}
|
|
312
502
|
};
|
|
313
503
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
504
|
+
const setTokenAndMaybeWait = async (
|
|
505
|
+
args:
|
|
506
|
+
| {
|
|
507
|
+
shouldStore: true;
|
|
508
|
+
tokens: AuthSession | null;
|
|
509
|
+
waitForHandshake: boolean;
|
|
510
|
+
context: AuthFlowContext;
|
|
511
|
+
}
|
|
512
|
+
| {
|
|
513
|
+
shouldStore: false;
|
|
514
|
+
tokens: { token: string } | null;
|
|
515
|
+
waitForHandshake: boolean;
|
|
516
|
+
context: AuthFlowContext;
|
|
517
|
+
},
|
|
518
|
+
): Promise<boolean> => {
|
|
519
|
+
const { waitForHandshake, context, ...tokenArgs } = args;
|
|
520
|
+
await setToken({
|
|
521
|
+
...(tokenArgs as
|
|
522
|
+
| { shouldStore: true; tokens: AuthSession | null }
|
|
523
|
+
| { shouldStore: false; tokens: { token: string } | null }),
|
|
524
|
+
requireHandshake: waitForHandshake,
|
|
324
525
|
});
|
|
325
|
-
if (
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
errorBody !== null &&
|
|
331
|
-
"authError" in errorBody &&
|
|
332
|
-
typeof (errorBody as Record<string, unknown>).authError === "object"
|
|
333
|
-
) {
|
|
334
|
-
throw new ConvexError((errorBody as Record<string, unknown>).authError as Value);
|
|
335
|
-
}
|
|
336
|
-
throw new Error(
|
|
337
|
-
(errorBody as Record<string, unknown>).error as string ??
|
|
338
|
-
`Proxy request failed: ${response.status}`,
|
|
339
|
-
);
|
|
526
|
+
if (tokenArgs.tokens === null) {
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
if (waitForHandshake) {
|
|
530
|
+
await waitForAuthHandshake(context);
|
|
340
531
|
}
|
|
341
|
-
return
|
|
532
|
+
return true;
|
|
342
533
|
};
|
|
343
534
|
|
|
344
535
|
// ---------------------------------------------------------------------------
|
|
@@ -348,40 +539,58 @@ export function client(options: ClientOptions) {
|
|
|
348
539
|
const verifyCode = async (
|
|
349
540
|
args: { code: string; verifier?: string } | { refreshToken: string },
|
|
350
541
|
) => {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
|
|
542
|
+
const verifyCodeRetryPolicy = Fx.retry.while(
|
|
543
|
+
Fx.retry.compose(
|
|
544
|
+
Fx.retry.jittered(Fx.retry.exponential(RETRY_BASE_MS)),
|
|
545
|
+
Fx.retry.recurs(RETRY_MAX_RETRIES),
|
|
546
|
+
),
|
|
547
|
+
(meta) => isTransientNetworkError(meta.input),
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
return Fx.run(
|
|
551
|
+
Fx.from({
|
|
552
|
+
ok: () =>
|
|
553
|
+
httpClient!.action(
|
|
554
|
+
requireApiRefs().signIn,
|
|
555
|
+
"code" in args
|
|
556
|
+
? { params: { code: args.code }, verifier: args.verifier }
|
|
557
|
+
: args,
|
|
558
|
+
),
|
|
559
|
+
err: (e) => e,
|
|
560
|
+
}).pipe(
|
|
561
|
+
Fx.retry(verifyCodeRetryPolicy),
|
|
562
|
+
Fx.recover((e) => Fx.fatal(e)),
|
|
563
|
+
),
|
|
564
|
+
);
|
|
372
565
|
};
|
|
373
566
|
|
|
374
567
|
const verifyCodeAndSetToken = async (
|
|
375
568
|
args: { code: string; verifier?: string } | { refreshToken: string },
|
|
569
|
+
opts?: { resyncConvexAuth?: boolean },
|
|
376
570
|
) => {
|
|
377
571
|
const { tokens } = await verifyCode(args);
|
|
378
572
|
await setToken({
|
|
379
573
|
shouldStore: true,
|
|
380
574
|
tokens: (tokens as AuthSession | null) ?? null,
|
|
575
|
+
resyncConvexAuth: opts?.resyncConvexAuth,
|
|
381
576
|
});
|
|
382
577
|
return tokens !== null;
|
|
383
578
|
};
|
|
384
579
|
|
|
580
|
+
const normalizeDeviceCodeResult = (device_code: any): DeviceCodeResult => {
|
|
581
|
+
return {
|
|
582
|
+
deviceCode: device_code.deviceCode,
|
|
583
|
+
userCode: device_code.userCode,
|
|
584
|
+
verificationUri:
|
|
585
|
+
device_code.verification_uri ?? device_code.verificationUri,
|
|
586
|
+
verificationUriComplete:
|
|
587
|
+
device_code.verification_uri_complete ??
|
|
588
|
+
device_code.verificationUriComplete,
|
|
589
|
+
expiresIn: device_code.expiresIn,
|
|
590
|
+
interval: device_code.interval,
|
|
591
|
+
};
|
|
592
|
+
};
|
|
593
|
+
|
|
385
594
|
// ---------------------------------------------------------------------------
|
|
386
595
|
// signIn
|
|
387
596
|
// ---------------------------------------------------------------------------
|
|
@@ -394,6 +603,7 @@ export function client(options: ClientOptions) {
|
|
|
394
603
|
* @param args - Provider-specific arguments. Pass a `Record<string, Value>`
|
|
395
604
|
* or `FormData`. Common fields: `email`, `password`, `code`, `redirectTo`.
|
|
396
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).
|
|
397
607
|
*
|
|
398
608
|
* @example Email magic link
|
|
399
609
|
* ```ts
|
|
@@ -403,8 +613,8 @@ export function client(options: ClientOptions) {
|
|
|
403
613
|
* @example Password
|
|
404
614
|
* ```ts
|
|
405
615
|
* const result = await auth.signIn('password', { email, password, flow: 'signIn' });
|
|
406
|
-
* if (result.totpRequired) {
|
|
407
|
-
* await auth.totp.verify({ code: totpCode, verifier: result.verifier
|
|
616
|
+
* if (result.kind === 'totpRequired') {
|
|
617
|
+
* await auth.totp.verify({ code: totpCode, verifier: result.verifier });
|
|
408
618
|
* }
|
|
409
619
|
* ```
|
|
410
620
|
*
|
|
@@ -417,80 +627,121 @@ export function client(options: ClientOptions) {
|
|
|
417
627
|
provider?: string,
|
|
418
628
|
args?: FormData | Record<string, Value>,
|
|
419
629
|
): Promise<SignInResult> => {
|
|
630
|
+
// Persist invite before potential OAuth redirect
|
|
631
|
+
await persistInvite();
|
|
632
|
+
|
|
420
633
|
const params =
|
|
421
634
|
args instanceof FormData
|
|
422
|
-
?
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
)
|
|
429
|
-
: args ?? {};
|
|
635
|
+
? (() => {
|
|
636
|
+
const formParams: Record<string, Value> = {};
|
|
637
|
+
args.forEach((value, key) => {
|
|
638
|
+
formParams[key] = typeof value === "string" ? value : value.name;
|
|
639
|
+
});
|
|
640
|
+
return formParams;
|
|
641
|
+
})()
|
|
642
|
+
: (args ?? {});
|
|
643
|
+
const flow =
|
|
644
|
+
typeof params.flow === "string" && params.flow.length > 0
|
|
645
|
+
? params.flow
|
|
646
|
+
: "signIn";
|
|
647
|
+
|
|
648
|
+
const handleSignInActionResult = async (
|
|
649
|
+
result: SignInActionResult,
|
|
650
|
+
options: { shouldStore: boolean; persistVerifier: boolean },
|
|
651
|
+
): Promise<SignInResult> =>
|
|
652
|
+
Fx.run(
|
|
653
|
+
Fx.match(result, result.kind, {
|
|
654
|
+
redirect: (redirectResult) =>
|
|
655
|
+
Fx.from({
|
|
656
|
+
ok: async () => {
|
|
657
|
+
const redirectUrl = new URL(redirectResult.redirect);
|
|
658
|
+
if (options.persistVerifier) {
|
|
659
|
+
await storageSet(
|
|
660
|
+
VERIFIER_STORAGE_KEY,
|
|
661
|
+
redirectResult.verifier,
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
if (typeof window !== "undefined") {
|
|
665
|
+
window.location.href = redirectUrl.toString();
|
|
666
|
+
}
|
|
667
|
+
return {
|
|
668
|
+
kind: "redirect" as const,
|
|
669
|
+
redirect: redirectUrl,
|
|
670
|
+
verifier: redirectResult.verifier,
|
|
671
|
+
};
|
|
672
|
+
},
|
|
673
|
+
err: (e) => e as never,
|
|
674
|
+
}),
|
|
675
|
+
totpRequired: (totpRequiredResult) =>
|
|
676
|
+
Fx.succeed({
|
|
677
|
+
kind: "totpRequired" as const,
|
|
678
|
+
verifier: totpRequiredResult.verifier,
|
|
679
|
+
}),
|
|
680
|
+
deviceCode: (deviceCodeResult) =>
|
|
681
|
+
Fx.succeed({
|
|
682
|
+
kind: "deviceCode" as const,
|
|
683
|
+
deviceCode: normalizeDeviceCodeResult(
|
|
684
|
+
deviceCodeResult.deviceCode,
|
|
685
|
+
),
|
|
686
|
+
}),
|
|
687
|
+
signedIn: (signedInResult) =>
|
|
688
|
+
Fx.from({
|
|
689
|
+
ok: async () => {
|
|
690
|
+
const signingIn = await setTokenAndMaybeWait(
|
|
691
|
+
options.shouldStore
|
|
692
|
+
? {
|
|
693
|
+
shouldStore: true as const,
|
|
694
|
+
tokens: signedInResult.tokens,
|
|
695
|
+
waitForHandshake: true,
|
|
696
|
+
context: { provider, flow },
|
|
697
|
+
}
|
|
698
|
+
: {
|
|
699
|
+
shouldStore: false as const,
|
|
700
|
+
tokens:
|
|
701
|
+
signedInResult.tokens === null
|
|
702
|
+
? null
|
|
703
|
+
: { token: signedInResult.tokens.token },
|
|
704
|
+
waitForHandshake: true,
|
|
705
|
+
context: { provider, flow },
|
|
706
|
+
},
|
|
707
|
+
);
|
|
708
|
+
return signingIn
|
|
709
|
+
? ({ kind: "signedIn" as const } as SignInResult)
|
|
710
|
+
: ({ kind: "started" as const } as SignInResult);
|
|
711
|
+
},
|
|
712
|
+
err: (e) => e as never,
|
|
713
|
+
}),
|
|
714
|
+
started: (_startedResult) => Fx.succeed({ kind: "started" as const }),
|
|
715
|
+
passkeyOptions: (_passkeyOptionsResult) =>
|
|
716
|
+
Fx.succeed({ kind: "started" as const }),
|
|
717
|
+
totpSetup: (_totpSetupResult) =>
|
|
718
|
+
Fx.succeed({ kind: "started" as const }),
|
|
719
|
+
}),
|
|
720
|
+
);
|
|
430
721
|
|
|
431
722
|
if (proxy) {
|
|
432
|
-
|
|
433
|
-
const result = await proxyFetch({
|
|
723
|
+
const result = (await proxyFetch({
|
|
434
724
|
action: "auth:signIn",
|
|
435
725
|
args: { provider, params },
|
|
726
|
+
})) as SignInActionResult;
|
|
727
|
+
return handleSignInActionResult(result, {
|
|
728
|
+
shouldStore: false,
|
|
729
|
+
persistVerifier: false,
|
|
436
730
|
});
|
|
437
|
-
if (result.redirect !== undefined) {
|
|
438
|
-
const redirectUrl = new URL(result.redirect);
|
|
439
|
-
// Verifier is stored server-side in an httpOnly cookie.
|
|
440
|
-
if (typeof window !== "undefined") {
|
|
441
|
-
window.location.href = redirectUrl.toString();
|
|
442
|
-
}
|
|
443
|
-
return { signingIn: false, redirect: redirectUrl };
|
|
444
|
-
}
|
|
445
|
-
if (result.totpRequired) {
|
|
446
|
-
return { signingIn: false, totpRequired: true, verifier: result.verifier };
|
|
447
|
-
}
|
|
448
|
-
if (result.deviceCode !== undefined) {
|
|
449
|
-
return { signingIn: false, deviceCode: result.deviceCode as DeviceCodeResult };
|
|
450
|
-
}
|
|
451
|
-
if (result.tokens !== undefined) {
|
|
452
|
-
// Proxy returns { token, refreshToken: "dummy" }.
|
|
453
|
-
// Store JWT in memory only — real refresh token is in httpOnly cookie.
|
|
454
|
-
await setToken({
|
|
455
|
-
shouldStore: false,
|
|
456
|
-
tokens:
|
|
457
|
-
result.tokens === null ? null : { token: result.tokens.token },
|
|
458
|
-
});
|
|
459
|
-
return { signingIn: result.tokens !== null };
|
|
460
|
-
}
|
|
461
|
-
return { signingIn: false };
|
|
462
731
|
}
|
|
463
732
|
|
|
464
733
|
// SPA mode: call Convex directly.
|
|
465
734
|
const verifier = (await storageGet(VERIFIER_STORAGE_KEY)) ?? undefined;
|
|
466
735
|
await storageRemove(VERIFIER_STORAGE_KEY);
|
|
467
|
-
const result = await convex.action(
|
|
736
|
+
const result = (await convex.action(requireApiRefs().signIn, {
|
|
468
737
|
provider,
|
|
469
738
|
params,
|
|
470
739
|
verifier,
|
|
740
|
+
})) as SignInActionResult;
|
|
741
|
+
return handleSignInActionResult(result, {
|
|
742
|
+
shouldStore: true,
|
|
743
|
+
persistVerifier: true,
|
|
471
744
|
});
|
|
472
|
-
if (result.redirect !== undefined) {
|
|
473
|
-
const redirectUrl = new URL(result.redirect);
|
|
474
|
-
await storageSet(VERIFIER_STORAGE_KEY, result.verifier!);
|
|
475
|
-
if (typeof window !== "undefined") {
|
|
476
|
-
window.location.href = redirectUrl.toString();
|
|
477
|
-
}
|
|
478
|
-
return { signingIn: false, redirect: redirectUrl };
|
|
479
|
-
}
|
|
480
|
-
if (result.totpRequired) {
|
|
481
|
-
return { signingIn: false, totpRequired: true, verifier: result.verifier };
|
|
482
|
-
}
|
|
483
|
-
if (result.deviceCode !== undefined) {
|
|
484
|
-
return { signingIn: false, deviceCode: result.deviceCode as DeviceCodeResult };
|
|
485
|
-
}
|
|
486
|
-
if (result.tokens !== undefined) {
|
|
487
|
-
await setToken({
|
|
488
|
-
shouldStore: true,
|
|
489
|
-
tokens: (result.tokens as AuthSession | null) ?? null,
|
|
490
|
-
});
|
|
491
|
-
return { signingIn: result.tokens !== null };
|
|
492
|
-
}
|
|
493
|
-
return { signingIn: false };
|
|
494
745
|
};
|
|
495
746
|
|
|
496
747
|
// ---------------------------------------------------------------------------
|
|
@@ -506,22 +757,24 @@ export function client(options: ClientOptions) {
|
|
|
506
757
|
*/
|
|
507
758
|
const signOut = async () => {
|
|
508
759
|
if (proxy) {
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
760
|
+
await Fx.run(
|
|
761
|
+
Fx.from({
|
|
762
|
+
ok: () => proxyFetch({ action: "auth:signOut", args: {} }),
|
|
763
|
+
err: () => undefined,
|
|
764
|
+
}).pipe(Fx.recover(() => Fx.succeed(undefined))),
|
|
765
|
+
);
|
|
514
766
|
await setToken({ shouldStore: false, tokens: null });
|
|
515
767
|
if (convex.clearAuth) convex.clearAuth();
|
|
516
768
|
return;
|
|
517
769
|
}
|
|
518
770
|
|
|
519
771
|
// SPA mode.
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
772
|
+
await Fx.run(
|
|
773
|
+
Fx.from({
|
|
774
|
+
ok: () => convex.action(requireApiRefs().signOut, {}),
|
|
775
|
+
err: () => undefined,
|
|
776
|
+
}).pipe(Fx.recover(() => Fx.succeed(undefined))),
|
|
777
|
+
);
|
|
525
778
|
await setToken({ shouldStore: true, tokens: null });
|
|
526
779
|
if (convex.clearAuth) convex.clearAuth();
|
|
527
780
|
};
|
|
@@ -540,26 +793,71 @@ export function client(options: ClientOptions) {
|
|
|
540
793
|
if (proxy) {
|
|
541
794
|
// Proxy mode: POST to the proxy to refresh.
|
|
542
795
|
// The proxy reads the real refresh token from the httpOnly cookie.
|
|
796
|
+
const resolvedProxyUrl = await resolveProxyUrl();
|
|
797
|
+
if (
|
|
798
|
+
typeof window === "undefined" &&
|
|
799
|
+
!(await isAbsoluteUrl(resolvedProxyUrl))
|
|
800
|
+
) {
|
|
801
|
+
finalizeLoadingState();
|
|
802
|
+
return token;
|
|
803
|
+
}
|
|
804
|
+
|
|
543
805
|
const tokenBeforeRefresh = token;
|
|
544
806
|
return await browserMutex("__convexAuthProxyRefresh", async () => {
|
|
545
807
|
// Another tab/call may have already refreshed.
|
|
546
808
|
if (token !== tokenBeforeRefresh) return token;
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
809
|
+
|
|
810
|
+
const proxyRefreshRetryPolicy = Fx.retry.while(
|
|
811
|
+
Fx.retry.compose(
|
|
812
|
+
Fx.retry.jittered(Fx.retry.exponential(RETRY_BASE_MS)),
|
|
813
|
+
Fx.retry.recurs(RETRY_MAX_RETRIES),
|
|
814
|
+
),
|
|
815
|
+
(meta) => isRetriableProxyRefreshError(meta.input),
|
|
816
|
+
);
|
|
817
|
+
|
|
818
|
+
await Fx.run(
|
|
819
|
+
Fx.from({
|
|
820
|
+
ok: () =>
|
|
821
|
+
proxyFetch({
|
|
822
|
+
action: "auth:signIn",
|
|
823
|
+
args: { refreshToken: true },
|
|
824
|
+
}),
|
|
825
|
+
err: (e) => e,
|
|
826
|
+
}).pipe(
|
|
827
|
+
Fx.retry(proxyRefreshRetryPolicy),
|
|
828
|
+
Fx.chain((result: any) =>
|
|
829
|
+
Fx.from({
|
|
830
|
+
ok: async () => {
|
|
831
|
+
if (result.tokens) {
|
|
832
|
+
await setToken({
|
|
833
|
+
shouldStore: false,
|
|
834
|
+
tokens: { token: result.tokens.token },
|
|
835
|
+
resyncConvexAuth: false,
|
|
836
|
+
});
|
|
837
|
+
} else {
|
|
838
|
+
await setToken({
|
|
839
|
+
shouldStore: false,
|
|
840
|
+
tokens: null,
|
|
841
|
+
resyncConvexAuth: false,
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
},
|
|
845
|
+
err: (e) => e,
|
|
846
|
+
}),
|
|
847
|
+
),
|
|
848
|
+
Fx.inspect((error) =>
|
|
849
|
+
Fx.sync(() =>
|
|
850
|
+
console.error("[convex-auth] Proxy refresh failed:", error),
|
|
851
|
+
),
|
|
852
|
+
),
|
|
853
|
+
Fx.recover(() => {
|
|
854
|
+
if (token === null) {
|
|
855
|
+
finalizeLoadingState();
|
|
856
|
+
}
|
|
857
|
+
return Fx.succeed(undefined);
|
|
858
|
+
}),
|
|
859
|
+
),
|
|
860
|
+
);
|
|
563
861
|
return token;
|
|
564
862
|
});
|
|
565
863
|
}
|
|
@@ -574,9 +872,13 @@ export function client(options: ClientOptions) {
|
|
|
574
872
|
const refreshToken =
|
|
575
873
|
(await storageGet(REFRESH_TOKEN_STORAGE_KEY)) ?? null;
|
|
576
874
|
if (!refreshToken) {
|
|
875
|
+
finalizeLoadingState();
|
|
577
876
|
return null;
|
|
578
877
|
}
|
|
579
|
-
await verifyCodeAndSetToken(
|
|
878
|
+
await verifyCodeAndSetToken(
|
|
879
|
+
{ refreshToken },
|
|
880
|
+
{ resyncConvexAuth: false },
|
|
881
|
+
);
|
|
580
882
|
return token;
|
|
581
883
|
});
|
|
582
884
|
};
|
|
@@ -591,14 +893,32 @@ export function client(options: ClientOptions) {
|
|
|
591
893
|
const code = new URLSearchParams(window.location.search).get("code");
|
|
592
894
|
if (!code) return;
|
|
593
895
|
handlingCodeFlow = true;
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
896
|
+
await Fx.run(
|
|
897
|
+
Fx.from({
|
|
898
|
+
ok: async () => {
|
|
899
|
+
await signIn(undefined, { code });
|
|
900
|
+
const codeUrl = new URL(window.location.href);
|
|
901
|
+
codeUrl.searchParams.delete("code");
|
|
902
|
+
await replaceUrl(codeUrl.pathname + codeUrl.search + codeUrl.hash);
|
|
903
|
+
},
|
|
904
|
+
err: (e) => e,
|
|
905
|
+
}).pipe(
|
|
906
|
+
Fx.recover(() => Fx.succeed(undefined)),
|
|
907
|
+
Fx.tap(() =>
|
|
908
|
+
Fx.sync(() => {
|
|
909
|
+
handlingCodeFlow = false;
|
|
910
|
+
}),
|
|
911
|
+
),
|
|
912
|
+
Fx.inspect(() =>
|
|
913
|
+
Fx.sync(() => {
|
|
914
|
+
handlingCodeFlow = false;
|
|
915
|
+
}),
|
|
916
|
+
),
|
|
917
|
+
),
|
|
918
|
+
);
|
|
919
|
+
// The flag is always reset — Fx.recover above ensures success path,
|
|
920
|
+
// but reset defensively here too.
|
|
921
|
+
handlingCodeFlow = false;
|
|
602
922
|
};
|
|
603
923
|
|
|
604
924
|
// ---------------------------------------------------------------------------
|
|
@@ -643,636 +963,124 @@ export function client(options: ClientOptions) {
|
|
|
643
963
|
|
|
644
964
|
// Cross-tab sync via storage events (SPA mode only).
|
|
645
965
|
if (!proxy && typeof window !== "undefined") {
|
|
966
|
+
const registryKey = key(JWT_STORAGE_KEY);
|
|
967
|
+
const registry = getStorageListenerRegistry();
|
|
968
|
+
const existingListener = registry[registryKey];
|
|
969
|
+
if (existingListener !== undefined) {
|
|
970
|
+
window.removeEventListener("storage", existingListener);
|
|
971
|
+
}
|
|
972
|
+
|
|
646
973
|
const onStorage = (event: StorageEvent) => {
|
|
647
|
-
|
|
974
|
+
Fx.detach(async () => {
|
|
648
975
|
if (event.key !== key(JWT_STORAGE_KEY)) return;
|
|
649
976
|
await setToken({
|
|
650
977
|
shouldStore: false,
|
|
651
|
-
tokens:
|
|
652
|
-
event.newValue === null ? null : { token: event.newValue },
|
|
978
|
+
tokens: event.newValue === null ? null : { token: event.newValue },
|
|
653
979
|
});
|
|
654
|
-
})
|
|
980
|
+
}, "[convex-auth] Storage event handler failed:");
|
|
655
981
|
};
|
|
656
982
|
window.addEventListener("storage", onStorage);
|
|
983
|
+
registry[registryKey] = onStorage;
|
|
984
|
+
disposeStorageListener = () => {
|
|
985
|
+
if (registry[registryKey] === onStorage) {
|
|
986
|
+
delete registry[registryKey];
|
|
987
|
+
}
|
|
988
|
+
window.removeEventListener("storage", onStorage);
|
|
989
|
+
};
|
|
657
990
|
}
|
|
658
991
|
|
|
659
992
|
// Auto-wire: feed our tokens into the Convex client so
|
|
660
993
|
// queries and mutations are automatically authenticated.
|
|
661
|
-
|
|
994
|
+
bindConvexAuth();
|
|
662
995
|
|
|
663
996
|
// Auto-hydrate and handle code flow.
|
|
664
997
|
if (typeof window !== "undefined") {
|
|
665
998
|
if (proxy) {
|
|
666
|
-
// Proxy mode:
|
|
667
|
-
//
|
|
999
|
+
// Proxy mode: eagerly resolve auth once on startup so routes that only
|
|
1000
|
+
// read auth state (and do not issue Convex queries yet) don't stay in
|
|
1001
|
+
// the initial loading phase.
|
|
668
1002
|
if (!hasServerToken) {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
updateSnapshot();
|
|
1003
|
+
Fx.detach(
|
|
1004
|
+
() => fetchAccessToken({ forceRefreshToken: true }),
|
|
1005
|
+
"[convex-auth] Proxy token refresh failed:",
|
|
1006
|
+
);
|
|
674
1007
|
}
|
|
675
1008
|
} else {
|
|
676
1009
|
// SPA mode: hydrate from localStorage, then handle OAuth code flow.
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
1010
|
+
Fx.detach(async () => {
|
|
1011
|
+
await Fx.run(
|
|
1012
|
+
Fx.from({
|
|
1013
|
+
ok: async () => {
|
|
1014
|
+
await hydrateFromStorage();
|
|
1015
|
+
await handleCodeFlow();
|
|
1016
|
+
},
|
|
1017
|
+
err: (e) => e,
|
|
1018
|
+
}).pipe(
|
|
1019
|
+
Fx.inspect((error) =>
|
|
1020
|
+
Fx.sync(() =>
|
|
1021
|
+
console.error(
|
|
1022
|
+
"[convex-auth] Client initialization failed:",
|
|
1023
|
+
error,
|
|
1024
|
+
),
|
|
1025
|
+
),
|
|
1026
|
+
),
|
|
1027
|
+
Fx.recover((_error) =>
|
|
1028
|
+
Fx.from({
|
|
1029
|
+
ok: () => setToken({ shouldStore: false, tokens: null }),
|
|
1030
|
+
err: (e) => e,
|
|
1031
|
+
}).pipe(Fx.recover(() => Fx.succeed(undefined))),
|
|
1032
|
+
),
|
|
1033
|
+
),
|
|
1034
|
+
);
|
|
1035
|
+
}, "[convex-auth] SPA initialization failed:");
|
|
682
1036
|
}
|
|
683
1037
|
}
|
|
684
1038
|
|
|
685
1039
|
// ---------------------------------------------------------------------------
|
|
686
|
-
//
|
|
1040
|
+
// Auth factor helpers
|
|
687
1041
|
// ---------------------------------------------------------------------------
|
|
688
1042
|
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
for (let i = 0; i < bytes.byteLength; i++) {
|
|
697
|
-
binary += String.fromCharCode(bytes[i]!);
|
|
698
|
-
}
|
|
699
|
-
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
700
|
-
};
|
|
701
|
-
|
|
702
|
-
const base64urlDecode = (str: string): Uint8Array => {
|
|
703
|
-
const padded = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
704
|
-
const binary = atob(padded);
|
|
705
|
-
const bytes = new Uint8Array(binary.length);
|
|
706
|
-
for (let i = 0; i < binary.length; i++) {
|
|
707
|
-
bytes[i] = binary.charCodeAt(i);
|
|
708
|
-
}
|
|
709
|
-
return bytes;
|
|
710
|
-
};
|
|
711
|
-
|
|
712
|
-
const passkey = {
|
|
713
|
-
/**
|
|
714
|
-
* Check if WebAuthn passkeys are supported in the current environment.
|
|
715
|
-
*/
|
|
716
|
-
isSupported: (): boolean => {
|
|
717
|
-
return (
|
|
718
|
-
typeof window !== "undefined" &&
|
|
719
|
-
typeof window.PublicKeyCredential !== "undefined"
|
|
720
|
-
);
|
|
721
|
-
},
|
|
722
|
-
|
|
723
|
-
/**
|
|
724
|
-
* Check if conditional UI (autofill-assisted passkey sign-in) is supported.
|
|
725
|
-
*
|
|
726
|
-
* ```ts
|
|
727
|
-
* if (await auth.passkey.isAutofillSupported()) {
|
|
728
|
-
* auth.passkey.authenticate({ autofill: true });
|
|
729
|
-
* }
|
|
730
|
-
* ```
|
|
731
|
-
*/
|
|
732
|
-
isAutofillSupported: async (): Promise<boolean> => {
|
|
733
|
-
if (typeof window === "undefined") return false;
|
|
734
|
-
if (typeof window.PublicKeyCredential === "undefined") return false;
|
|
735
|
-
if (
|
|
736
|
-
typeof (
|
|
737
|
-
window.PublicKeyCredential as any
|
|
738
|
-
).isConditionalMediationAvailable !== "function"
|
|
739
|
-
) {
|
|
740
|
-
return false;
|
|
741
|
-
}
|
|
742
|
-
return (
|
|
743
|
-
window.PublicKeyCredential as any
|
|
744
|
-
).isConditionalMediationAvailable();
|
|
745
|
-
},
|
|
746
|
-
|
|
747
|
-
/**
|
|
748
|
-
* Register a new passkey for the current or new user.
|
|
749
|
-
*
|
|
750
|
-
* Performs the full two-round-trip WebAuthn registration ceremony:
|
|
751
|
-
* 1. Requests creation options from the server (challenge, RP info)
|
|
752
|
-
* 2. Calls `navigator.credentials.create()` with the options
|
|
753
|
-
* 3. Sends the attestation back to the server for verification
|
|
754
|
-
* 4. Server creates user + account + passkey records and returns tokens
|
|
755
|
-
*
|
|
756
|
-
* Works in both SPA and proxy (SSR) modes.
|
|
757
|
-
*
|
|
758
|
-
* ```ts
|
|
759
|
-
* await auth.passkey.register({ name: "MacBook Touch ID" });
|
|
760
|
-
* ```
|
|
761
|
-
*
|
|
762
|
-
* @param opts.name - Friendly name for this passkey
|
|
763
|
-
* @param opts.email - Email to associate with the new account
|
|
764
|
-
* @param opts.userName - Username for the credential (defaults to email)
|
|
765
|
-
* @param opts.userDisplayName - Display name for the credential
|
|
766
|
-
* @returns `{ signingIn: true }` on success
|
|
767
|
-
*/
|
|
768
|
-
register: async (
|
|
769
|
-
opts?: {
|
|
770
|
-
name?: string;
|
|
771
|
-
email?: string;
|
|
772
|
-
userName?: string;
|
|
773
|
-
userDisplayName?: string;
|
|
774
|
-
},
|
|
775
|
-
): Promise<SignInResult> => {
|
|
776
|
-
const phase1Params = {
|
|
777
|
-
flow: "register-options",
|
|
778
|
-
email: opts?.email,
|
|
779
|
-
userName: opts?.userName,
|
|
780
|
-
userDisplayName: opts?.userDisplayName,
|
|
781
|
-
};
|
|
782
|
-
|
|
783
|
-
// Phase 1: Get registration options from server
|
|
784
|
-
let phase1Result: any;
|
|
785
|
-
if (proxy) {
|
|
786
|
-
phase1Result = await proxyFetch({
|
|
787
|
-
action: "auth:signIn",
|
|
788
|
-
args: { provider: "passkey", params: phase1Params },
|
|
789
|
-
});
|
|
790
|
-
} else {
|
|
791
|
-
phase1Result = await convex.action("auth:signIn" as any, {
|
|
792
|
-
provider: "passkey",
|
|
793
|
-
params: phase1Params,
|
|
794
|
-
});
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
if (!phase1Result.options) {
|
|
798
|
-
throw new Error("Server did not return passkey registration options");
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
const options = phase1Result.options;
|
|
802
|
-
|
|
803
|
-
// Convert base64url strings to ArrayBuffers for the credential API
|
|
804
|
-
const createOptions: CredentialCreationOptions = {
|
|
805
|
-
publicKey: {
|
|
806
|
-
rp: options.rp,
|
|
807
|
-
user: {
|
|
808
|
-
id: base64urlDecode(options.user.id).buffer as ArrayBuffer,
|
|
809
|
-
name: options.user.name,
|
|
810
|
-
displayName: options.user.displayName,
|
|
811
|
-
},
|
|
812
|
-
challenge: base64urlDecode(options.challenge).buffer as ArrayBuffer,
|
|
813
|
-
pubKeyCredParams: options.pubKeyCredParams,
|
|
814
|
-
timeout: options.timeout,
|
|
815
|
-
attestation: options.attestation,
|
|
816
|
-
authenticatorSelection: options.authenticatorSelection,
|
|
817
|
-
excludeCredentials: (options.excludeCredentials ?? []).map(
|
|
818
|
-
(cred: any) => ({
|
|
819
|
-
type: cred.type ?? "public-key",
|
|
820
|
-
id: base64urlDecode(cred.id).buffer as ArrayBuffer,
|
|
821
|
-
transports: cred.transports,
|
|
822
|
-
}),
|
|
823
|
-
),
|
|
824
|
-
},
|
|
825
|
-
};
|
|
826
|
-
|
|
827
|
-
// Phase 2: Create credential via browser API
|
|
828
|
-
const credential = (await navigator.credentials.create(
|
|
829
|
-
createOptions,
|
|
830
|
-
)) as PublicKeyCredential | null;
|
|
831
|
-
if (!credential) {
|
|
832
|
-
throw new Error("Passkey registration was cancelled");
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
const response =
|
|
836
|
-
credential.response as AuthenticatorAttestationResponse;
|
|
837
|
-
|
|
838
|
-
// Extract transports if available
|
|
839
|
-
const transports =
|
|
840
|
-
typeof response.getTransports === "function"
|
|
841
|
-
? response.getTransports()
|
|
842
|
-
: undefined;
|
|
843
|
-
|
|
844
|
-
const phase2Params = {
|
|
845
|
-
flow: "register-verify",
|
|
846
|
-
clientDataJSON: base64urlEncode(response.clientDataJSON),
|
|
847
|
-
attestationObject: base64urlEncode(response.attestationObject),
|
|
848
|
-
transports,
|
|
849
|
-
passkeyName: opts?.name,
|
|
850
|
-
email: opts?.email,
|
|
851
|
-
};
|
|
852
|
-
|
|
853
|
-
// Phase 3: Send attestation to server for verification
|
|
854
|
-
let phase2Result: any;
|
|
855
|
-
if (proxy) {
|
|
856
|
-
// In proxy mode the verifier is stored in an httpOnly cookie by the proxy.
|
|
857
|
-
// We pass it back explicitly so the proxy can forward it to Convex.
|
|
858
|
-
phase2Result = await proxyFetch({
|
|
859
|
-
action: "auth:signIn",
|
|
860
|
-
args: {
|
|
861
|
-
provider: "passkey",
|
|
862
|
-
params: phase2Params,
|
|
863
|
-
verifier: phase1Result.verifier,
|
|
864
|
-
},
|
|
865
|
-
});
|
|
866
|
-
} else {
|
|
867
|
-
phase2Result = await convex.action("auth:signIn" as any, {
|
|
868
|
-
provider: "passkey",
|
|
869
|
-
params: phase2Params,
|
|
870
|
-
verifier: phase1Result.verifier,
|
|
871
|
-
});
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
if (phase2Result.tokens) {
|
|
875
|
-
if (proxy) {
|
|
876
|
-
await setToken({
|
|
877
|
-
shouldStore: false,
|
|
878
|
-
tokens:
|
|
879
|
-
phase2Result.tokens === null
|
|
880
|
-
? null
|
|
881
|
-
: { token: phase2Result.tokens.token },
|
|
882
|
-
});
|
|
883
|
-
} else {
|
|
884
|
-
await setToken({
|
|
885
|
-
shouldStore: true,
|
|
886
|
-
tokens: phase2Result.tokens as AuthSession,
|
|
887
|
-
});
|
|
888
|
-
}
|
|
889
|
-
return { signingIn: true };
|
|
890
|
-
}
|
|
891
|
-
return { signingIn: false };
|
|
892
|
-
},
|
|
893
|
-
|
|
894
|
-
/**
|
|
895
|
-
* Authenticate with an existing passkey.
|
|
896
|
-
*
|
|
897
|
-
* Performs the full two-round-trip WebAuthn authentication ceremony:
|
|
898
|
-
* 1. Requests assertion options from the server (challenge, allowed credentials)
|
|
899
|
-
* 2. Calls `navigator.credentials.get()` with the options
|
|
900
|
-
* 3. Sends the assertion back to the server for signature verification
|
|
901
|
-
* 4. Server verifies signature, updates counter, creates session, returns tokens
|
|
902
|
-
*
|
|
903
|
-
* Works in both SPA and proxy (SSR) modes.
|
|
904
|
-
*
|
|
905
|
-
* ```ts
|
|
906
|
-
* // Discoverable credential (no email needed)
|
|
907
|
-
* await auth.passkey.authenticate();
|
|
908
|
-
*
|
|
909
|
-
* // Scoped to a specific user's credentials
|
|
910
|
-
* await auth.passkey.authenticate({ email: "user@example.com" });
|
|
911
|
-
*
|
|
912
|
-
* // Autofill-assisted (conditional UI)
|
|
913
|
-
* await auth.passkey.authenticate({ autofill: true });
|
|
914
|
-
* ```
|
|
915
|
-
*
|
|
916
|
-
* @param opts.email - Scope to credentials for this email's user
|
|
917
|
-
* @param opts.autofill - Use conditional mediation (autofill UI)
|
|
918
|
-
* @returns `{ signingIn: true }` on success
|
|
919
|
-
*/
|
|
920
|
-
authenticate: async (
|
|
921
|
-
opts?: { email?: string; autofill?: boolean },
|
|
922
|
-
): Promise<SignInResult> => {
|
|
923
|
-
const phase1Params = {
|
|
924
|
-
flow: "auth-options",
|
|
925
|
-
email: opts?.email,
|
|
926
|
-
};
|
|
927
|
-
|
|
928
|
-
// Phase 1: Get assertion options from server
|
|
929
|
-
let phase1Result: any;
|
|
930
|
-
if (proxy) {
|
|
931
|
-
phase1Result = await proxyFetch({
|
|
932
|
-
action: "auth:signIn",
|
|
933
|
-
args: { provider: "passkey", params: phase1Params },
|
|
934
|
-
});
|
|
935
|
-
} else {
|
|
936
|
-
phase1Result = await convex.action("auth:signIn" as any, {
|
|
937
|
-
provider: "passkey",
|
|
938
|
-
params: phase1Params,
|
|
939
|
-
});
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
if (!phase1Result.options) {
|
|
943
|
-
throw new Error("Server did not return passkey authentication options");
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
const options = phase1Result.options;
|
|
947
|
-
|
|
948
|
-
// Convert base64url strings to ArrayBuffers for the credential API
|
|
949
|
-
const getOptions: CredentialRequestOptions = {
|
|
950
|
-
publicKey: {
|
|
951
|
-
challenge: base64urlDecode(options.challenge).buffer as ArrayBuffer,
|
|
952
|
-
timeout: options.timeout,
|
|
953
|
-
rpId: options.rpId,
|
|
954
|
-
userVerification: options.userVerification,
|
|
955
|
-
allowCredentials: (options.allowCredentials ?? []).map(
|
|
956
|
-
(cred: any) => ({
|
|
957
|
-
type: cred.type ?? "public-key",
|
|
958
|
-
id: base64urlDecode(cred.id).buffer as ArrayBuffer,
|
|
959
|
-
transports: cred.transports,
|
|
960
|
-
}),
|
|
961
|
-
),
|
|
962
|
-
},
|
|
963
|
-
...(opts?.autofill ? { mediation: "conditional" as any } : {}),
|
|
964
|
-
};
|
|
965
|
-
|
|
966
|
-
// Phase 2: Get credential via browser API
|
|
967
|
-
const credential = (await navigator.credentials.get(
|
|
968
|
-
getOptions,
|
|
969
|
-
)) as PublicKeyCredential | null;
|
|
970
|
-
if (!credential) {
|
|
971
|
-
throw new Error("Passkey authentication was cancelled");
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
const response =
|
|
975
|
-
credential.response as AuthenticatorAssertionResponse;
|
|
976
|
-
|
|
977
|
-
const phase2Params = {
|
|
978
|
-
flow: "auth-verify",
|
|
979
|
-
credentialId: base64urlEncode(credential.rawId),
|
|
980
|
-
clientDataJSON: base64urlEncode(response.clientDataJSON),
|
|
981
|
-
authenticatorData: base64urlEncode(response.authenticatorData),
|
|
982
|
-
signature: base64urlEncode(response.signature),
|
|
983
|
-
};
|
|
984
|
-
|
|
985
|
-
// Phase 3: Send assertion to server for verification
|
|
986
|
-
let phase2Result: any;
|
|
987
|
-
if (proxy) {
|
|
988
|
-
phase2Result = await proxyFetch({
|
|
989
|
-
action: "auth:signIn",
|
|
990
|
-
args: {
|
|
991
|
-
provider: "passkey",
|
|
992
|
-
params: phase2Params,
|
|
993
|
-
verifier: phase1Result.verifier,
|
|
994
|
-
},
|
|
995
|
-
});
|
|
996
|
-
} else {
|
|
997
|
-
phase2Result = await convex.action("auth:signIn" as any, {
|
|
998
|
-
provider: "passkey",
|
|
999
|
-
params: phase2Params,
|
|
1000
|
-
verifier: phase1Result.verifier,
|
|
1001
|
-
});
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
if (phase2Result.tokens) {
|
|
1005
|
-
if (proxy) {
|
|
1006
|
-
await setToken({
|
|
1007
|
-
shouldStore: false,
|
|
1008
|
-
tokens:
|
|
1009
|
-
phase2Result.tokens === null
|
|
1010
|
-
? null
|
|
1011
|
-
: { token: phase2Result.tokens.token },
|
|
1012
|
-
});
|
|
1013
|
-
} else {
|
|
1014
|
-
await setToken({
|
|
1015
|
-
shouldStore: true,
|
|
1016
|
-
tokens: phase2Result.tokens as AuthSession,
|
|
1017
|
-
});
|
|
1018
|
-
}
|
|
1019
|
-
return { signingIn: true };
|
|
1020
|
-
}
|
|
1021
|
-
return { signingIn: false };
|
|
1022
|
-
},
|
|
1023
|
-
};
|
|
1024
|
-
|
|
1025
|
-
const totp = {
|
|
1026
|
-
/**
|
|
1027
|
-
* Start TOTP enrollment. Must be authenticated.
|
|
1028
|
-
*
|
|
1029
|
-
* Returns a URI for QR code display and a base32 secret for manual entry.
|
|
1030
|
-
*
|
|
1031
|
-
* ```ts
|
|
1032
|
-
* const setup = await auth.totp.setup();
|
|
1033
|
-
* // Display QR code from setup.uri
|
|
1034
|
-
* // Or show setup.secret for manual entry
|
|
1035
|
-
* ```
|
|
1036
|
-
*/
|
|
1037
|
-
setup: async (
|
|
1038
|
-
opts?: { name?: string; accountName?: string },
|
|
1039
|
-
): Promise<{ uri: string; secret: string; verifier: string; totpId: string }> => {
|
|
1040
|
-
const params: Record<string, any> = { flow: "setup" };
|
|
1041
|
-
if (opts?.name) params.name = opts.name;
|
|
1042
|
-
if (opts?.accountName) params.accountName = opts.accountName;
|
|
1043
|
-
|
|
1044
|
-
if (proxy) {
|
|
1045
|
-
const result = await proxyFetch({
|
|
1046
|
-
action: "auth:signIn",
|
|
1047
|
-
args: { provider: "totp", params },
|
|
1048
|
-
});
|
|
1049
|
-
return { uri: result.totpSetup.uri, secret: result.totpSetup.secret, verifier: result.verifier, totpId: result.totpSetup.totpId };
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
const result = await convex.action("auth:signIn" as any, {
|
|
1053
|
-
provider: "totp",
|
|
1054
|
-
params,
|
|
1055
|
-
});
|
|
1056
|
-
return { uri: result.totpSetup.uri, secret: result.totpSetup.secret, verifier: result.verifier, totpId: result.totpSetup.totpId };
|
|
1057
|
-
},
|
|
1058
|
-
|
|
1059
|
-
/**
|
|
1060
|
-
* Complete TOTP enrollment by verifying the first code from the authenticator app.
|
|
1061
|
-
*
|
|
1062
|
-
* ```ts
|
|
1063
|
-
* await auth.totp.confirm({ code: "123456", verifier: setup.verifier, totpId: setup.totpId });
|
|
1064
|
-
* ```
|
|
1065
|
-
*/
|
|
1066
|
-
confirm: async (opts: {
|
|
1067
|
-
code: string;
|
|
1068
|
-
verifier: string;
|
|
1069
|
-
totpId: string;
|
|
1070
|
-
}): Promise<void> => {
|
|
1071
|
-
const params: Record<string, any> = {
|
|
1072
|
-
flow: "confirm",
|
|
1073
|
-
code: opts.code,
|
|
1074
|
-
totpId: opts.totpId,
|
|
1075
|
-
};
|
|
1076
|
-
|
|
1077
|
-
if (proxy) {
|
|
1078
|
-
const result = await proxyFetch({
|
|
1079
|
-
action: "auth:signIn",
|
|
1080
|
-
args: { provider: "totp", params, verifier: opts.verifier },
|
|
1081
|
-
});
|
|
1082
|
-
if (result.tokens) {
|
|
1083
|
-
await setToken({
|
|
1084
|
-
shouldStore: false,
|
|
1085
|
-
tokens: result.tokens === null ? null : { token: result.tokens.token },
|
|
1086
|
-
});
|
|
1087
|
-
}
|
|
1088
|
-
return;
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
const result = await convex.action("auth:signIn" as any, {
|
|
1092
|
-
provider: "totp",
|
|
1093
|
-
params,
|
|
1094
|
-
verifier: opts.verifier,
|
|
1095
|
-
});
|
|
1096
|
-
if (result.tokens) {
|
|
1097
|
-
await setToken({
|
|
1098
|
-
shouldStore: true,
|
|
1099
|
-
tokens: (result.tokens as AuthSession | null) ?? null,
|
|
1100
|
-
});
|
|
1101
|
-
}
|
|
1102
|
-
},
|
|
1103
|
-
|
|
1104
|
-
/**
|
|
1105
|
-
* Complete 2FA verification during sign-in.
|
|
1106
|
-
*
|
|
1107
|
-
* Called after a credentials sign-in returns `totpRequired: true`.
|
|
1108
|
-
*
|
|
1109
|
-
* ```ts
|
|
1110
|
-
* const result = await auth.signIn("password", { email, password });
|
|
1111
|
-
* if (result.totpRequired) {
|
|
1112
|
-
* await auth.totp.verify({ code: "123456", verifier: result.verifier! });
|
|
1113
|
-
* }
|
|
1114
|
-
* ```
|
|
1115
|
-
*/
|
|
1116
|
-
verify: async (opts: { code: string; verifier: string }): Promise<void> => {
|
|
1117
|
-
const params: Record<string, any> = {
|
|
1118
|
-
flow: "verify",
|
|
1119
|
-
code: opts.code,
|
|
1120
|
-
};
|
|
1121
|
-
|
|
1122
|
-
if (proxy) {
|
|
1123
|
-
const result = await proxyFetch({
|
|
1124
|
-
action: "auth:signIn",
|
|
1125
|
-
args: { provider: "totp", params, verifier: opts.verifier },
|
|
1126
|
-
});
|
|
1127
|
-
if (result.tokens) {
|
|
1128
|
-
await setToken({
|
|
1129
|
-
shouldStore: false,
|
|
1130
|
-
tokens: result.tokens === null ? null : { token: result.tokens.token },
|
|
1131
|
-
});
|
|
1132
|
-
}
|
|
1133
|
-
return;
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
const result = await convex.action("auth:signIn" as any, {
|
|
1137
|
-
provider: "totp",
|
|
1138
|
-
params,
|
|
1139
|
-
verifier: opts.verifier,
|
|
1140
|
-
});
|
|
1141
|
-
if (result.tokens) {
|
|
1142
|
-
await setToken({
|
|
1143
|
-
shouldStore: true,
|
|
1144
|
-
tokens: (result.tokens as AuthSession | null) ?? null,
|
|
1145
|
-
});
|
|
1146
|
-
}
|
|
1147
|
-
},
|
|
1148
|
-
};
|
|
1149
|
-
|
|
1150
|
-
const device = {
|
|
1151
|
-
/**
|
|
1152
|
-
* Poll for device authorization status.
|
|
1153
|
-
*
|
|
1154
|
-
* The device calls this repeatedly (respecting `interval`) after
|
|
1155
|
-
* initiating a device flow via `signIn("device")`. Returns when
|
|
1156
|
-
* the user authorizes, or throws on timeout/denial.
|
|
1157
|
-
*
|
|
1158
|
-
* ```ts
|
|
1159
|
-
* const result = await auth.signIn("device");
|
|
1160
|
-
* const { deviceCode } = result;
|
|
1161
|
-
* // Display deviceCode.userCode to the user, then poll:
|
|
1162
|
-
* await auth.device.poll(deviceCode);
|
|
1163
|
-
* // User is now signed in
|
|
1164
|
-
* ```
|
|
1165
|
-
*
|
|
1166
|
-
* @param code - The {@link DeviceCodeResult} from `signIn("device")`.
|
|
1167
|
-
* @returns Resolves when the device is authorized and tokens are stored.
|
|
1168
|
-
* @throws When the code expires, is denied, or polling encounters an error.
|
|
1169
|
-
*/
|
|
1170
|
-
poll: async (code: DeviceCodeResult): Promise<void> => {
|
|
1171
|
-
const intervalMs = code.interval * 1000;
|
|
1172
|
-
const expiresAt = Date.now() + code.expiresIn * 1000;
|
|
1173
|
-
|
|
1174
|
-
while (Date.now() < expiresAt) {
|
|
1175
|
-
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
1176
|
-
|
|
1177
|
-
try {
|
|
1178
|
-
let result: any;
|
|
1179
|
-
const params: Record<string, any> = {
|
|
1180
|
-
flow: "poll",
|
|
1181
|
-
deviceCode: code.deviceCode,
|
|
1182
|
-
};
|
|
1183
|
-
|
|
1184
|
-
if (proxy) {
|
|
1185
|
-
result = await proxyFetch({
|
|
1186
|
-
action: "auth:signIn",
|
|
1187
|
-
args: { provider: "device", params },
|
|
1188
|
-
});
|
|
1189
|
-
} else {
|
|
1190
|
-
result = await convex.action("auth:signIn" as any, {
|
|
1191
|
-
provider: "device",
|
|
1192
|
-
params,
|
|
1193
|
-
});
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
// Authorized — tokens received
|
|
1197
|
-
if (result.tokens) {
|
|
1198
|
-
if (proxy) {
|
|
1199
|
-
await setToken({
|
|
1200
|
-
shouldStore: false,
|
|
1201
|
-
tokens:
|
|
1202
|
-
result.tokens === null
|
|
1203
|
-
? null
|
|
1204
|
-
: { token: result.tokens.token },
|
|
1205
|
-
});
|
|
1206
|
-
} else {
|
|
1207
|
-
await setToken({
|
|
1208
|
-
shouldStore: true,
|
|
1209
|
-
tokens: (result.tokens as AuthSession | null) ?? null,
|
|
1210
|
-
});
|
|
1211
|
-
}
|
|
1212
|
-
return;
|
|
1213
|
-
}
|
|
1214
|
-
} catch (e: unknown) {
|
|
1215
|
-
// Handle expected polling errors
|
|
1216
|
-
if (e instanceof ConvexError) {
|
|
1217
|
-
const data = e.data as Record<string, unknown>;
|
|
1218
|
-
const code_ = data?.code as string | undefined;
|
|
1219
|
-
if (code_ === "DEVICE_AUTHORIZATION_PENDING") {
|
|
1220
|
-
continue; // Keep polling
|
|
1221
|
-
}
|
|
1222
|
-
if (code_ === "DEVICE_SLOW_DOWN") {
|
|
1223
|
-
// Back off by adding one interval
|
|
1224
|
-
await new Promise((resolve) =>
|
|
1225
|
-
setTimeout(resolve, intervalMs),
|
|
1226
|
-
);
|
|
1227
|
-
continue;
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
// Non-recoverable error — rethrow
|
|
1231
|
-
throw e;
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
|
-
throw new Error("Device authorization timed out.");
|
|
1236
|
-
},
|
|
1043
|
+
const passkey = createPasskeyClient({
|
|
1044
|
+
proxy,
|
|
1045
|
+
convex,
|
|
1046
|
+
requireApiRefs,
|
|
1047
|
+
proxyFetch,
|
|
1048
|
+
setTokenAndMaybeWait,
|
|
1049
|
+
});
|
|
1237
1050
|
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
* // On the /device verification page:
|
|
1246
|
-
* await auth.device.verify(userCode);
|
|
1247
|
-
* ```
|
|
1248
|
-
*
|
|
1249
|
-
* @param userCode - The user code entered by the user (e.g. "WDJB-MJHT").
|
|
1250
|
-
*/
|
|
1251
|
-
verify: async (userCode: string): Promise<void> => {
|
|
1252
|
-
const params: Record<string, any> = {
|
|
1253
|
-
flow: "verify",
|
|
1254
|
-
userCode,
|
|
1255
|
-
};
|
|
1051
|
+
const totp = createTotpClient({
|
|
1052
|
+
proxy,
|
|
1053
|
+
convex,
|
|
1054
|
+
requireApiRefs,
|
|
1055
|
+
proxyFetch,
|
|
1056
|
+
setTokenAndMaybeWait,
|
|
1057
|
+
});
|
|
1256
1058
|
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
provider: "device",
|
|
1265
|
-
params,
|
|
1266
|
-
});
|
|
1267
|
-
}
|
|
1268
|
-
},
|
|
1269
|
-
};
|
|
1059
|
+
const device = createDeviceClient({
|
|
1060
|
+
proxy,
|
|
1061
|
+
convex,
|
|
1062
|
+
requireApiRefs,
|
|
1063
|
+
proxyFetch,
|
|
1064
|
+
setTokenAndMaybeWait,
|
|
1065
|
+
});
|
|
1270
1066
|
|
|
1271
1067
|
return {
|
|
1272
1068
|
/** Current auth state snapshot. */
|
|
1273
1069
|
get state(): AuthState {
|
|
1274
1070
|
return snapshot;
|
|
1275
1071
|
},
|
|
1072
|
+
/** SSR-safe URL param reader. */
|
|
1073
|
+
param,
|
|
1074
|
+
/** Pending invite from URL or recovered from storage. Null if none. */
|
|
1075
|
+
get invite(): PendingInvite | null {
|
|
1076
|
+
const pendingInvite = getPendingInvite();
|
|
1077
|
+
if (!pendingInvite) return null;
|
|
1078
|
+
return {
|
|
1079
|
+
token: pendingInvite.token,
|
|
1080
|
+
email: pendingInvite.email,
|
|
1081
|
+
accept: acceptInvite,
|
|
1082
|
+
};
|
|
1083
|
+
},
|
|
1276
1084
|
/** Sign in with a provider. See {@link SignInResult} for return shape. */
|
|
1277
1085
|
signIn,
|
|
1278
1086
|
/** Sign out and clear all token state. */
|
|
@@ -1285,94 +1093,42 @@ export function client(options: ClientOptions) {
|
|
|
1285
1093
|
totp,
|
|
1286
1094
|
/** Device authorization (RFC 8628) helpers. */
|
|
1287
1095
|
device,
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
return mutex;
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
|
-
function setMutexValue(
|
|
1330
|
-
key: string,
|
|
1331
|
-
value: {
|
|
1332
|
-
currentlyRunning: Promise<void> | null;
|
|
1333
|
-
waiting: Array<() => Promise<void>>;
|
|
1334
|
-
},
|
|
1335
|
-
) {
|
|
1336
|
-
(globalThis as any).__convexAuthMutexes[key] = value;
|
|
1337
|
-
}
|
|
1338
|
-
|
|
1339
|
-
async function enqueueCallbackForMutex(
|
|
1340
|
-
key: string,
|
|
1341
|
-
callback: () => Promise<void>,
|
|
1342
|
-
) {
|
|
1343
|
-
const mutex = getMutexValue(key);
|
|
1344
|
-
if (mutex.currentlyRunning === null) {
|
|
1345
|
-
setMutexValue(key, {
|
|
1346
|
-
currentlyRunning: callback().finally(() => {
|
|
1347
|
-
const nextCb = getMutexValue(key).waiting.shift();
|
|
1348
|
-
getMutexValue(key).currentlyRunning = null;
|
|
1349
|
-
setMutexValue(key, {
|
|
1350
|
-
...getMutexValue(key),
|
|
1351
|
-
currentlyRunning:
|
|
1352
|
-
nextCb === undefined ? null : enqueueCallbackForMutex(key, nextCb),
|
|
1353
|
-
});
|
|
1354
|
-
}),
|
|
1355
|
-
waiting: [],
|
|
1356
|
-
});
|
|
1357
|
-
} else {
|
|
1358
|
-
setMutexValue(key, {
|
|
1359
|
-
...mutex,
|
|
1360
|
-
waiting: [...mutex.waiting, callback],
|
|
1361
|
-
});
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
|
|
1365
|
-
async function manualMutex<T>(
|
|
1366
|
-
key: string,
|
|
1367
|
-
callback: () => Promise<T>,
|
|
1368
|
-
): Promise<T> {
|
|
1369
|
-
const outerPromise = new Promise<T>((resolve, reject) => {
|
|
1370
|
-
const wrappedCallback: () => Promise<void> = () => {
|
|
1371
|
-
return callback()
|
|
1372
|
-
.then((v) => resolve(v))
|
|
1373
|
-
.catch((e) => reject(e));
|
|
1374
|
-
};
|
|
1375
|
-
void enqueueCallbackForMutex(key, wrappedCallback);
|
|
1376
|
-
});
|
|
1377
|
-
return outerPromise;
|
|
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
|
+
*/
|
|
1122
|
+
destroy: () => {
|
|
1123
|
+
destroyed = true;
|
|
1124
|
+
settleHandshakeWaiters(authEpoch, {
|
|
1125
|
+
type: "reject",
|
|
1126
|
+
error: createHandshakeError("AUTH_HANDSHAKE_REJECTED", {
|
|
1127
|
+
reason: "destroyed",
|
|
1128
|
+
}),
|
|
1129
|
+
});
|
|
1130
|
+
disposeStorageListener?.();
|
|
1131
|
+
subscribers.clear();
|
|
1132
|
+
},
|
|
1133
|
+
} as AuthClient<Api>;
|
|
1378
1134
|
}
|