@estifanos-sh/convex-auth 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -6
- package/dist/bin.js +2 -34
- package/dist/browser/index.d.ts +2 -2
- package/dist/browser/index.d.ts.map +1 -1
- package/dist/browser/index.js +15 -9
- package/dist/browser/index.js.map +1 -1
- package/dist/client/core/types.d.ts +68 -97
- package/dist/client/core/types.d.ts.map +1 -1
- package/dist/client/core/types.js.map +1 -1
- package/dist/client/errors.d.ts +15 -0
- package/dist/client/errors.d.ts.map +1 -0
- package/dist/client/errors.js +8 -1
- package/dist/client/errors.js.map +1 -1
- package/dist/client/factors/device.js +4 -4
- package/dist/client/factors/device.js.map +1 -1
- package/dist/client/factors/totp.js +20 -12
- package/dist/client/factors/totp.js.map +1 -1
- package/dist/client/factors/webauthn.js +6 -2
- package/dist/client/factors/webauthn.js.map +1 -1
- package/dist/client/index.d.ts +6 -3
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +13 -10
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/api.d.ts +13 -3
- 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 +365 -74
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/_generated/dataModel.d.ts +2 -2
- package/dist/component/_generated/dataModel.d.ts.map +1 -1
- package/dist/component/_generated/server.d.ts +13 -2
- package/dist/component/_generated/server.d.ts.map +1 -1
- package/dist/component/_generated/server.js +2 -1
- package/dist/component/_generated/server.js.map +1 -1
- package/dist/component/account.d.ts +52 -38
- package/dist/component/account.d.ts.map +1 -1
- package/dist/component/account.js +42 -23
- package/dist/component/account.js.map +1 -1
- package/dist/component/batch.d.ts +3 -0
- package/dist/component/batch.d.ts.map +1 -0
- package/dist/component/batch.js +17 -0
- package/dist/component/batch.js.map +1 -0
- package/dist/component/connection/audit.d.ts +30 -27
- package/dist/component/connection/audit.d.ts.map +1 -1
- package/dist/component/connection/audit.js +16 -23
- package/dist/component/connection/audit.js.map +1 -1
- package/dist/component/connection/cache.d.ts +6 -6
- package/dist/component/connection/cache.js +0 -1
- package/dist/component/connection/cache.js.map +1 -1
- package/dist/component/connection/domain/verification.d.ts +19 -19
- package/dist/component/connection/domain/verification.js +1 -2
- package/dist/component/connection/domain/verification.js.map +1 -1
- package/dist/component/connection/domain.d.ts +20 -20
- package/dist/component/connection/domain.js +1 -2
- package/dist/component/connection/domain.js.map +1 -1
- package/dist/component/connection/saml/assertion.d.ts +4 -4
- package/dist/component/connection/saml/assertion.js +0 -1
- package/dist/component/connection/saml/assertion.js.map +1 -1
- package/dist/component/connection/saml/request.d.ts +8 -9
- package/dist/component/connection/saml/request.d.ts.map +1 -1
- package/dist/component/connection/saml/request.js +0 -2
- package/dist/component/connection/saml/request.js.map +1 -1
- package/dist/component/connection/scim/config.d.ts +13 -13
- package/dist/component/connection/scim/config.d.ts.map +1 -1
- package/dist/component/connection/scim/config.js +2 -2
- package/dist/component/connection/scim/config.js.map +1 -1
- package/dist/component/connection/scim/identity.d.ts +122 -61
- package/dist/component/connection/scim/identity.d.ts.map +1 -1
- package/dist/component/connection/scim/identity.js +408 -60
- package/dist/component/connection/scim/identity.js.map +1 -1
- package/dist/component/connection/secret.d.ts +15 -15
- package/dist/component/connection/secret.d.ts.map +1 -1
- package/dist/component/connection/secret.js +2 -2
- package/dist/component/connection/secret.js.map +1 -1
- package/dist/component/connection/webhook/delivery.d.ts +69 -53
- package/dist/component/connection/webhook/delivery.d.ts.map +1 -1
- package/dist/component/connection/webhook/delivery.js +129 -172
- package/dist/component/connection/webhook/delivery.js.map +1 -1
- package/dist/component/connection/webhook/endpoint.d.ts +21 -21
- package/dist/component/connection/webhook/endpoint.d.ts.map +1 -1
- package/dist/component/connection/webhook/endpoint.js +2 -2
- package/dist/component/connection/webhook/endpoint.js.map +1 -1
- package/dist/component/connection/webhook/events.d.ts +26 -0
- package/dist/component/connection/webhook/events.d.ts.map +1 -0
- package/dist/component/connection/webhook/events.js +39 -0
- package/dist/component/connection/webhook/events.js.map +1 -0
- package/dist/component/connection.d.ts +39 -39
- package/dist/component/connection.d.ts.map +1 -1
- package/dist/component/connection.js +9 -9
- package/dist/component/connection.js.map +1 -1
- package/dist/component/context.d.ts +115 -0
- package/dist/component/context.d.ts.map +1 -0
- package/dist/component/context.js +49 -0
- package/dist/component/context.js.map +1 -0
- package/dist/component/convex.config.d.ts +2 -2
- package/dist/component/crons.d.ts +2 -2
- package/dist/component/documents.d.ts +1244 -0
- package/dist/component/documents.d.ts.map +1 -0
- package/dist/component/documents.js +44 -0
- package/dist/component/documents.js.map +1 -0
- package/dist/component/event.d.ts +142 -141
- package/dist/component/event.d.ts.map +1 -1
- package/dist/component/event.js +90 -119
- package/dist/component/event.js.map +1 -1
- package/dist/component/eventstream.d.ts +30 -0
- package/dist/component/eventstream.d.ts.map +1 -0
- package/dist/component/eventstream.js +60 -0
- package/dist/component/eventstream.js.map +1 -0
- package/dist/component/factor/device.d.ts +25 -24
- package/dist/component/factor/device.d.ts.map +1 -1
- package/dist/component/factor/device.js +7 -4
- package/dist/component/factor/device.js.map +1 -1
- package/dist/component/factor/passkey.d.ts +173 -85
- package/dist/component/factor/passkey.d.ts.map +1 -1
- package/dist/component/factor/passkey.js +138 -27
- package/dist/component/factor/passkey.js.map +1 -1
- package/dist/component/factor/totp.d.ts +56 -50
- package/dist/component/factor/totp.d.ts.map +1 -1
- package/dist/component/factor/totp.js +6 -5
- package/dist/component/factor/totp.js.map +1 -1
- package/dist/component/group/active.d.ts +326 -22
- package/dist/component/group/active.d.ts.map +1 -1
- package/dist/component/group/active.js +22 -13
- package/dist/component/group/active.js.map +1 -1
- package/dist/component/group/invite.d.ts +30 -30
- package/dist/component/group/invite.d.ts.map +1 -1
- package/dist/component/group/invite.js +2 -2
- package/dist/component/group/invite.js.map +1 -1
- package/dist/component/group/member.d.ts +36 -36
- package/dist/component/group/member.d.ts.map +1 -1
- package/dist/component/group/member.js +5 -3
- package/dist/component/group/member.js.map +1 -1
- package/dist/component/group.d.ts +49 -64
- package/dist/component/group.d.ts.map +1 -1
- package/dist/component/group.js +5 -3
- package/dist/component/group.js.map +1 -1
- package/dist/component/http.d.ts +2 -2
- package/dist/component/index.d.ts +0 -10
- package/dist/component/index.js +1 -3
- package/dist/component/limits.d.ts +5 -4
- package/dist/component/limits.d.ts.map +1 -1
- package/dist/component/limits.js +7 -4
- package/dist/component/limits.js.map +1 -1
- package/dist/component/maintenance.d.ts +6 -3
- package/dist/component/maintenance.d.ts.map +1 -1
- package/dist/component/maintenance.js +45 -6
- package/dist/component/maintenance.js.map +1 -1
- package/dist/component/model.d.ts +847 -1723
- package/dist/component/model.d.ts.map +1 -1
- package/dist/component/model.js +28 -317
- package/dist/component/model.js.map +1 -1
- package/dist/component/modules.d.ts +11 -5
- package/dist/component/modules.d.ts.map +1 -1
- package/dist/component/modules.js +4 -1
- package/dist/component/modules.js.map +1 -1
- package/dist/component/oauth/client.d.ts +20 -20
- package/dist/component/oauth/client.d.ts.map +1 -1
- package/dist/component/oauth/client.js +2 -2
- package/dist/component/oauth/client.js.map +1 -1
- package/dist/component/oauth/code.d.ts +14 -14
- package/dist/component/oauth/code.js +1 -2
- package/dist/component/oauth/code.js.map +1 -1
- package/dist/component/oauth/refresh.d.ts +20 -20
- package/dist/component/oauth/refresh.js +1 -2
- package/dist/component/oauth/refresh.js.map +1 -1
- package/dist/component/schema.d.ts +1148 -607
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +43 -15
- package/dist/component/schema.js.map +1 -1
- package/dist/component/session.d.ts +68 -32
- package/dist/component/session.d.ts.map +1 -1
- package/dist/component/session.js +118 -34
- package/dist/component/session.js.map +1 -1
- package/dist/component/token/continuation.d.ts +47 -18
- package/dist/component/token/continuation.d.ts.map +1 -1
- package/dist/component/token/continuation.js +76 -18
- package/dist/component/token/continuation.js.map +1 -1
- package/dist/component/token/enrollment.d.ts +42 -0
- package/dist/component/token/enrollment.d.ts.map +1 -0
- package/dist/component/token/enrollment.js +57 -0
- package/dist/component/token/enrollment.js.map +1 -0
- package/dist/component/token/pkce.d.ts +31 -24
- package/dist/component/token/pkce.d.ts.map +1 -1
- package/dist/component/token/pkce.js +15 -20
- package/dist/component/token/pkce.js.map +1 -1
- package/dist/component/token/refresh.d.ts +42 -35
- package/dist/component/token/refresh.d.ts.map +1 -1
- package/dist/component/token/refresh.js +16 -8
- package/dist/component/token/refresh.js.map +1 -1
- package/dist/component/token/verification.d.ts +34 -30
- package/dist/component/token/verification.d.ts.map +1 -1
- package/dist/component/token/verification.js +7 -6
- package/dist/component/token/verification.js.map +1 -1
- package/dist/component/user/email.d.ts +21 -21
- package/dist/component/user/email.d.ts.map +1 -1
- package/dist/component/user/email.js +2 -2
- package/dist/component/user/email.js.map +1 -1
- package/dist/component/user/key.d.ts +36 -36
- package/dist/component/user/key.d.ts.map +1 -1
- package/dist/component/user/key.js +2 -2
- package/dist/component/user/key.js.map +1 -1
- package/dist/component/user.d.ts +52 -48
- package/dist/component/user.d.ts.map +1 -1
- package/dist/component/user.js +157 -36
- package/dist/component/user.js.map +1 -1
- package/dist/expo/index.d.ts +4 -4
- package/dist/expo/index.d.ts.map +1 -1
- package/dist/expo/index.js +20 -13
- package/dist/expo/index.js.map +1 -1
- package/dist/node_modules/.pnpm/@opentelemetry_sdk-logs@0.214.0_@opentelemetry_api@1.9.1/node_modules/@opentelemetry/sdk-logs/build/src/export/ReadableLogRecord.d.ts +2 -2
- package/dist/node_modules/.pnpm/@opentelemetry_sdk-logs@0.214.0_@opentelemetry_api@1.9.1/node_modules/@opentelemetry/sdk-logs/build/src/export/SdkLogRecord.d.ts +1 -1
- package/dist/node_modules/.pnpm/@opentelemetry_sdk-metrics@2.6.1_@opentelemetry_api@1.9.1/node_modules/@opentelemetry/sdk-metrics/build/src/MeterProvider.d.ts +2 -2
- package/dist/node_modules/.pnpm/@opentelemetry_sdk-metrics@2.6.1_@opentelemetry_api@1.9.1/node_modules/@opentelemetry/sdk-metrics/build/src/export/MetricData.d.ts +1 -1
- package/dist/node_modules/.pnpm/@opentelemetry_sdk-trace-base@2.6.1_@opentelemetry_api@1.9.1/node_modules/@opentelemetry/sdk-trace-base/build/src/Span.d.ts +1 -1
- package/dist/node_modules/.pnpm/@opentelemetry_sdk-trace-base@2.6.1_@opentelemetry_api@1.9.1/node_modules/@opentelemetry/sdk-trace-base/build/src/export/ReadableSpan.d.ts +1 -1
- package/dist/node_modules/.pnpm/@opentelemetry_sdk-trace-base@2.6.1_@opentelemetry_api@1.9.1/node_modules/@opentelemetry/sdk-trace-base/build/src/types.d.ts +1 -1
- package/dist/otel.d.ts +1 -1
- package/dist/providers/anonymous.d.ts +11 -5
- package/dist/providers/anonymous.d.ts.map +1 -1
- package/dist/providers/anonymous.js +4 -1
- package/dist/providers/anonymous.js.map +1 -1
- package/dist/providers/apple.d.ts +1 -1
- package/dist/providers/apple.js +19 -4
- package/dist/providers/apple.js.map +1 -1
- package/dist/providers/credentials.d.ts +22 -14
- package/dist/providers/credentials.d.ts.map +1 -1
- package/dist/providers/credentials.js +2 -0
- package/dist/providers/credentials.js.map +1 -1
- package/dist/providers/custom.d.ts +3 -3
- package/dist/providers/custom.d.ts.map +1 -1
- package/dist/providers/custom.js.map +1 -1
- package/dist/providers/email.d.ts +3 -3
- package/dist/providers/email.d.ts.map +1 -1
- package/dist/providers/email.js.map +1 -1
- package/dist/providers/github.d.ts +1 -1
- package/dist/providers/github.d.ts.map +1 -1
- package/dist/providers/github.js +9 -4
- package/dist/providers/github.js.map +1 -1
- package/dist/providers/google.d.ts +1 -1
- package/dist/providers/google.d.ts.map +1 -1
- package/dist/providers/google.js +9 -4
- package/dist/providers/google.js.map +1 -1
- package/dist/providers/microsoft.d.ts +1 -1
- package/dist/providers/microsoft.d.ts.map +1 -1
- package/dist/providers/microsoft.js +12 -4
- package/dist/providers/microsoft.js.map +1 -1
- package/dist/providers/password.d.ts +117 -15
- package/dist/providers/password.d.ts.map +1 -1
- package/dist/providers/password.js +71 -26
- package/dist/providers/password.js.map +1 -1
- package/dist/providers/phone.d.ts +4 -3
- package/dist/providers/phone.d.ts.map +1 -1
- package/dist/providers/phone.js.map +1 -1
- package/dist/providers/webauthn.d.ts.map +1 -1
- package/dist/providers/webauthn.js +6 -0
- package/dist/providers/webauthn.js.map +1 -1
- package/dist/react/index.d.ts +40 -41
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +27 -52
- package/dist/react/index.js.map +1 -1
- package/dist/server/auth.d.ts +224 -126
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +44 -25
- package/dist/server/auth.js.map +1 -1
- package/dist/server/component/factor/db.js +13 -6
- package/dist/server/component/factor/db.js.map +1 -1
- package/dist/server/config.d.ts +2 -1
- package/dist/server/connection/domain.d.ts +4 -65
- package/dist/server/connection/domain.d.ts.map +1 -1
- package/dist/server/connection/domain.js +29 -5
- package/dist/server/connection/domain.js.map +1 -1
- package/dist/server/connection/group/service.js +7 -5
- package/dist/server/connection/group/service.js.map +1 -1
- package/dist/server/connection/http.js +136 -291
- package/dist/server/connection/http.js.map +1 -1
- package/dist/server/connection/oidc.js +8 -17
- package/dist/server/connection/oidc.js.map +1 -1
- package/dist/server/connection/policy/domain.js +4 -1
- package/dist/server/connection/policy/domain.js.map +1 -1
- package/dist/server/connection/policy.js +14 -13
- package/dist/server/connection/policy.js.map +1 -1
- package/dist/server/connection/provision.js +5 -19
- package/dist/server/connection/provision.js.map +1 -1
- package/dist/server/connection/saml/encoding.js +2 -6
- package/dist/server/connection/saml/encoding.js.map +1 -1
- package/dist/server/connection/saml/signature.js +2 -2
- package/dist/server/connection/saml/signature.js.map +1 -1
- package/dist/server/connection/saml/xmldsig.js +1 -1
- package/dist/server/connection/saml/xmldsig.js.map +1 -1
- package/dist/server/connection/webhook.js +4 -36
- package/dist/server/connection/webhook.js.map +1 -1
- package/dist/server/context.d.ts +2 -2
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/context.js +35 -24
- package/dist/server/context.js.map +1 -1
- package/dist/server/contract.d.ts.map +1 -1
- package/dist/server/contract.js +6 -14
- package/dist/server/contract.js.map +1 -1
- package/dist/server/core.js +46 -1
- package/dist/server/core.js.map +1 -1
- package/dist/server/cors.d.ts +2 -2
- package/dist/server/cors.d.ts.map +1 -1
- package/dist/server/crypto.js.map +1 -1
- package/dist/server/db.js +6 -2
- package/dist/server/db.js.map +1 -1
- package/dist/server/device.js +1 -1
- package/dist/server/domains/account.d.ts +9 -1
- package/dist/server/domains/account.d.ts.map +1 -1
- package/dist/server/domains/account.js +21 -9
- package/dist/server/domains/account.js.map +1 -1
- package/dist/server/domains/context.js +34 -0
- package/dist/server/domains/context.js.map +1 -0
- package/dist/server/domains/factor.d.ts +3 -2
- package/dist/server/domains/factor.d.ts.map +1 -1
- package/dist/server/domains/factor.js.map +1 -1
- package/dist/server/domains/group.js.map +1 -1
- package/dist/server/domains/invite.js.map +1 -1
- package/dist/server/domains/key.js.map +1 -1
- package/dist/server/domains/member.js.map +1 -1
- package/dist/server/domains/session.js.map +1 -1
- package/dist/server/domains/user.js.map +1 -1
- package/dist/server/env.js +2 -22
- package/dist/server/env.js.map +1 -1
- package/dist/server/events.d.ts +3 -1
- package/dist/server/events.d.ts.map +1 -1
- package/dist/server/events.js +6 -6
- package/dist/server/events.js.map +1 -1
- package/dist/server/facade.d.ts +1 -33
- package/dist/server/facade.d.ts.map +1 -1
- package/dist/server/facade.js +1 -2
- package/dist/server/facade.js.map +1 -1
- package/dist/server/http.d.ts +1 -0
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.js +4 -4
- package/dist/server/http.js.map +1 -1
- package/dist/server/identity/claims.js +1 -1
- package/dist/server/identity/convex.d.ts +2 -0
- package/dist/server/identity/convex.d.ts.map +1 -1
- package/dist/server/index.d.ts +6 -9
- package/dist/server/index.js +2 -4
- package/dist/server/limits.js +23 -2
- package/dist/server/limits.js.map +1 -1
- package/dist/server/mutations/calls.js +3 -2
- package/dist/server/mutations/credentials/signin.js +7 -3
- package/dist/server/mutations/credentials/signin.js.map +1 -1
- package/dist/server/mutations/enrollment.js +87 -0
- package/dist/server/mutations/enrollment.js.map +1 -0
- package/dist/server/mutations/invalidate.js +5 -10
- package/dist/server/mutations/invalidate.js.map +1 -1
- package/dist/server/mutations/oauth.js +3 -3
- package/dist/server/mutations/oauth.js.map +1 -1
- package/dist/server/mutations/refresh.js +1 -0
- package/dist/server/mutations/refresh.js.map +1 -1
- package/dist/server/mutations/register.js +52 -21
- package/dist/server/mutations/register.js.map +1 -1
- package/dist/server/mutations/retrieve.js +4 -1
- package/dist/server/mutations/retrieve.js.map +1 -1
- package/dist/server/mutations/signin.js +2 -2
- package/dist/server/mutations/signin.js.map +1 -1
- package/dist/server/mutations/store.js +6 -1
- package/dist/server/mutations/store.js.map +1 -1
- package/dist/server/mutations/verify.js +6 -4
- package/dist/server/mutations/verify.js.map +1 -1
- package/dist/server/oauth/client.d.ts +1 -1
- package/dist/server/oauth/client.js.map +1 -1
- package/dist/server/oauth/code.d.ts +1 -1
- package/dist/server/oauth/code.js.map +1 -1
- package/dist/server/oauth/factory.js +89 -13
- package/dist/server/oauth/factory.js.map +1 -1
- package/dist/server/oauth/handlers.js +2 -2
- package/dist/server/oauth/runtime.js +24 -9
- package/dist/server/oauth/runtime.js.map +1 -1
- package/dist/server/preload.d.ts.map +1 -1
- package/dist/server/preload.js +14 -11
- package/dist/server/preload.js.map +1 -1
- package/dist/server/runtime.d.ts +236 -148
- package/dist/server/runtime.d.ts.map +1 -1
- package/dist/server/runtime.js +92 -44
- package/dist/server/runtime.js.map +1 -1
- package/dist/server/session/lifecycle.d.ts +2 -1
- package/dist/server/session/lifecycle.d.ts.map +1 -1
- package/dist/server/session/lifecycle.js +29 -10
- package/dist/server/session/lifecycle.js.map +1 -1
- package/dist/server/signin/flow.js +2 -2
- package/dist/server/signin/flow.js.map +1 -1
- package/dist/server/telemetry.js +1 -1
- package/dist/server/tokens.js +6 -3
- package/dist/server/tokens.js.map +1 -1
- package/dist/server/totp.js +4 -3
- package/dist/server/totp.js.map +1 -1
- package/dist/server/types.d.ts +131 -45
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/user/account.js +0 -1
- package/dist/server/user/account.js.map +1 -1
- package/dist/server/validators.d.ts +930 -944
- package/dist/server/validators.d.ts.map +1 -1
- package/dist/server/validators.js +66 -39
- package/dist/server/validators.js.map +1 -1
- package/dist/server/webauthn.js +60 -29
- package/dist/server/webauthn.js.map +1 -1
- package/dist/shared/brand.d.ts +3 -7
- package/dist/shared/brand.d.ts.map +1 -1
- package/dist/shared/params.d.ts +78 -0
- package/dist/shared/params.d.ts.map +1 -0
- package/dist/shared/results.d.ts +13 -1
- package/dist/shared/results.d.ts.map +1 -1
- package/dist/svelte/context.svelte.d.ts +23 -12
- package/dist/svelte/context.svelte.d.ts.map +1 -1
- package/dist/svelte/context.svelte.js +20 -13
- package/dist/svelte/index.d.ts +1 -4
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +1 -4
- package/dist/test.d.ts +1204 -611
- package/dist/test.d.ts.map +1 -1
- package/dist/test.js +50 -1
- package/dist/test.js.map +1 -1
- package/package.json +5 -14
- package/dist/_generated/component.d.ts +0 -3074
- package/dist/_generated/component.d.ts.map +0 -1
- package/dist/component/functions.d.ts +0 -9
- package/dist/component/functions.d.ts.map +0 -1
- package/dist/component/functions.js +0 -3
- package/dist/core/index.d.ts +0 -533
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/index.js +0 -86
- package/dist/core/index.js.map +0 -1
- package/dist/model.js.map +0 -1
- package/dist/server/env.d.ts +0 -28
- package/dist/server/env.d.ts.map +0 -1
- package/dist/svelte/AuthLoading.svelte +0 -13
- package/dist/svelte/AuthLoading.svelte.d.ts +0 -8
- package/dist/svelte/AuthLoading.svelte.d.ts.map +0 -1
- package/dist/svelte/SignedIn.svelte +0 -14
- package/dist/svelte/SignedIn.svelte.d.ts +0 -8
- package/dist/svelte/SignedIn.svelte.d.ts.map +0 -1
- package/dist/svelte/SignedOut.svelte +0 -13
- package/dist/svelte/SignedOut.svelte.d.ts +0 -8
- package/dist/svelte/SignedOut.svelte.d.ts.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flow.js","names":[],"sources":["../../../src/server/signin/flow.ts"],"sourcesContent":["import { GenericId, ConvexError } from \"convex/values\";\nimport type { CredentialsAuthorizeResult } from \"../../providers/credentials\";\n\nimport { assertNever } from \"../../shared/brand\";\nimport { authFlowError } from \"../../shared/errors\";\nimport type {\n AuthTokens,\n SignInFlowResult,\n SignInRedirectResult,\n SignInSessionResult,\n SignInStartResult,\n} from \"../../shared/results\";\nimport { handleDevice } from \"../device\";\nimport type { AuthErrorData } from \"../errors\";\nimport { toConvexError } from \"../errors\";\nimport { log } from \"../log\";\nimport {\n callCreateVerificationCode,\n callRefreshSession,\n callSignIn,\n callVerifier,\n callVerifyCodeAndSignIn,\n} from \"../mutations/calls\";\nimport { handleWebAuthn } from \"../webauthn\";\nimport type { SignInParams } from \"../payloads\";\nimport { generateRandomString } from \"../random\";\nimport { redirectAbsoluteUrl, setURLSearchParam } from \"../redirects\";\nimport { finalizeSessionIssuance } from \"../session/lifecycle\";\nimport { handleTotp } from \"../totp\";\nimport {\n AuthProviderMaterializedConfig,\n ConvexCredentialsConfig,\n EmailConfig,\n GenericActionCtxWithAuthConfig,\n PhoneConfig,\n} from \"../types\";\nimport { queryTotpVerifiedByUserId } from \"../component/factor/db\";\nimport { AuthDataModel, SessionInfo } from \"../types\";\nimport type { OAuthMaterializedConfig } from \"../types\";\nimport { authUrlFromEnv } from \"../url\";\nimport { withSpan } from \"../utils/span\";\n\nconst DEFAULT_EMAIL_VERIFICATION_CODE_DURATION_S = 60 * 60 * 24;\n\ntype EnrichedActionCtx = GenericActionCtxWithAuthConfig<AuthDataModel>;\n\ntype SignInResult = SignInFlowResult<SessionInfo<AuthTokens | null> | null>;\n\ntype VerificationParams = {\n email?: string;\n phone?: string;\n redirectTo?: unknown;\n connectionId?: unknown;\n loginHint?: unknown;\n protocol?: unknown;\n code?: unknown;\n};\n\nconst normalizeVerificationParams = (params: SignInParams | undefined) => {\n const value = (params ?? {}) as VerificationParams;\n return {\n email: typeof value.email === \"string\" ? value.email : undefined,\n phone: typeof value.phone === \"string\" ? value.phone : undefined,\n redirectTo: value.redirectTo,\n connectionId: value.connectionId,\n loginHint: value.loginHint,\n protocol: value.protocol,\n code: value.code,\n };\n};\n\nconst describeUnknown = (value: unknown) => {\n if (typeof value === \"string\") {\n return JSON.stringify(value);\n }\n if (\n typeof value === \"number\" ||\n typeof value === \"boolean\" ||\n typeof value === \"bigint\" ||\n value === null\n ) {\n return String(value);\n }\n const json = JSON.stringify(value);\n return json ?? Object.prototype.toString.call(value);\n};\n\nconst asConvexError = (\n error: unknown,\n code: string,\n message: string,\n): ConvexError<AuthErrorData> => {\n if (error instanceof ConvexError) {\n return error;\n }\n if (error instanceof Error) {\n return toConvexError(authFlowError(code, error.message || message));\n }\n return toConvexError(authFlowError(code, `${message} ${describeUnknown(error)}`.trim()));\n};\n\nconst asCredentialsError = (error: unknown): ConvexError<AuthErrorData> => {\n if (error instanceof ConvexError) {\n return error as ConvexError<AuthErrorData>;\n }\n if (error instanceof Error) {\n return new ConvexError({\n code: error.message.startsWith(\"Missing `\") ? \"INVALID_PARAMETERS\" : \"INVALID_CREDENTIALS\",\n message: error.message,\n });\n }\n return toConvexError(authFlowError(\"INTERNAL_ERROR\", \"Failed to authorize credentials.\"));\n};\n\n/**\n * Entry point for all sign-in flows.\n *\n * Refreshes a session (when only `refreshToken` is given), verifies a sign-in\n * code (when only `params.code` is given), or dispatches to the matching\n * provider handler based on `provider.type`.\n *\n * @param provider - The resolved provider, or `null` for refresh/code-only calls.\n * @returns The flow result: `signedIn`, `started`, `redirect`, `totpRequired`, etc.\n */\nexport async function signInImpl(\n ctx: EnrichedActionCtx,\n provider: AuthProviderMaterializedConfig | null,\n args: {\n accountId?: GenericId<\"Account\">;\n params?: SignInParams;\n verifier?: string;\n continuation?: string;\n refreshToken?: string;\n calledBy?: string;\n },\n options: {\n generateTokens: boolean;\n allowExtraProviders: boolean;\n authSiteUrl?: string;\n resolveConnectionProtocol?: (\n ctx: EnrichedActionCtx,\n connectionId: string,\n ) => Promise<\"oidc\" | \"saml\">;\n },\n): Promise<SignInResult> {\n return withSpan(\n \"convex-auth.signin\",\n {\n hasProvider: provider !== null,\n hasCode: args.params?.code !== undefined,\n hasRefreshToken: args.refreshToken !== undefined,\n },\n async () => {\n if (provider === null && args.refreshToken) {\n try {\n const session = await callRefreshSession(ctx, { refreshToken: args.refreshToken! });\n if (session === null) {\n return { kind: \"signedIn\" as const, session: null };\n }\n return { kind: \"signedIn\" as const, session };\n } catch (error) {\n throw asConvexError(error, \"INTERNAL_ERROR\", \"Failed to refresh session.\");\n }\n }\n\n if (provider === null && args.params?.code !== undefined) {\n try {\n const result = await callVerifyCodeAndSignIn(ctx, {\n params: args.params as SignInParams,\n verifier: args.verifier,\n createSession: true,\n generateTokens: true,\n allowExtraProviders: options.allowExtraProviders,\n });\n return { kind: \"signedIn\" as const, session: result };\n } catch (error) {\n throw asConvexError(error, \"INTERNAL_ERROR\", \"Failed to verify sign-in code.\");\n }\n }\n\n const resolvedProvider = provider;\n if (resolvedProvider === null) {\n throw toConvexError(\n authFlowError(\n \"SIGN_IN_MISSING_PARAMS\",\n \"Cannot sign in: missing provider, code, or refresh token.\",\n ),\n );\n }\n\n /** Exhaustive narrowing dispatch: each case narrows `resolvedProvider` to its concrete config so the handler receives the typed variant without a cast. */\n switch (resolvedProvider.type) {\n case \"email\":\n case \"phone\":\n return handleEmailAndPhoneProvider(ctx, resolvedProvider, args, options);\n case \"credentials\":\n return handleCredentials(ctx, resolvedProvider, args, options);\n case \"oauth\":\n return handleOAuthProvider(ctx, resolvedProvider, args, options);\n case \"webauthn\":\n return handleWebAuthn(ctx, resolvedProvider, args);\n case \"totp\":\n return handleTotp(ctx, resolvedProvider, args);\n case \"device\":\n return handleDevice(ctx, resolvedProvider, args);\n case \"connection\":\n return handleConnectionProvider(ctx, args, options);\n default:\n return assertNever(resolvedProvider, \"Unknown provider type\");\n }\n },\n );\n}\n\nasync function handleEmailAndPhoneProvider(\n ctx: EnrichedActionCtx,\n provider: EmailConfig | PhoneConfig,\n args: {\n params?: SignInParams;\n accountId?: GenericId<\"Account\">;\n },\n options: {\n generateTokens: boolean;\n allowExtraProviders: boolean;\n },\n): Promise<SignInStartResult | SignInSessionResult<SessionInfo<AuthTokens | null>>> {\n return withSpan(`convex-auth.signin.${provider.type}`, {}, async () => {\n const normalizedParams = normalizeVerificationParams(args.params);\n if (args.params?.code !== undefined) {\n let result;\n try {\n result = await callVerifyCodeAndSignIn(ctx, {\n params: args.params as SignInParams,\n provider: provider.id,\n createSession: true,\n generateTokens: options.generateTokens,\n allowExtraProviders: options.allowExtraProviders,\n });\n } catch (error) {\n throw asConvexError(error, \"INTERNAL_ERROR\", \"Failed to verify email or phone code.\");\n }\n if (result === null) {\n throw toConvexError(\n authFlowError(\"INVALID_VERIFICATION_CODE\", \"Invalid or expired verification code.\"),\n );\n }\n return {\n kind: \"signedIn\" as const,\n session: result,\n };\n }\n\n const alphabet = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\";\n let code: string;\n if (provider.generateVerificationToken) {\n try {\n code = await provider.generateVerificationToken();\n } catch {\n throw toConvexError(\n authFlowError(\"INTERNAL_ERROR\", \"Failed to generate verification token\"),\n );\n }\n } else {\n code = generateRandomString(32, alphabet);\n }\n\n const expirationTime =\n Date.now() + (provider.maxAge ?? DEFAULT_EMAIL_VERIFICATION_CODE_DURATION_S) * 1000;\n\n let identifier: string;\n try {\n identifier = await callCreateVerificationCode(ctx, {\n provider: provider.id,\n accountId: args.accountId,\n email: normalizedParams.email,\n phone: normalizedParams.phone,\n code,\n expirationTime,\n allowExtraProviders: options.allowExtraProviders,\n });\n } catch (error) {\n throw asConvexError(error, \"INTERNAL_ERROR\", \"Failed to create verification code.\");\n }\n\n let destination: string;\n try {\n destination = await redirectAbsoluteUrl(ctx, ctx.auth.config, {\n redirectTo: args.params?.redirectTo,\n });\n } catch (error) {\n throw asConvexError(error, \"INVALID_REDIRECT\", \"Failed to resolve redirect URL.\");\n }\n\n const verificationArgs = {\n identifier,\n url: setURLSearchParam(destination, \"code\", code),\n token: code,\n expires: new Date(expirationTime),\n };\n\n if (provider.type === \"email\") {\n try {\n await provider.sendVerificationRequest(\n {\n ...verificationArgs,\n provider,\n request: new Request(\"http://localhost\"),\n },\n ctx,\n );\n } catch {\n throw toConvexError(authFlowError(\"INTERNAL_ERROR\", \"Failed to send email code\"));\n }\n } else {\n try {\n await provider.sendVerificationRequest({ ...verificationArgs, provider }, ctx);\n } catch {\n throw toConvexError(authFlowError(\"INTERNAL_ERROR\", \"Failed to send phone code\"));\n }\n }\n\n return { kind: \"started\" as const };\n });\n}\n\nasync function handleCredentials(\n ctx: EnrichedActionCtx,\n provider: ConvexCredentialsConfig,\n args: {\n params?: SignInParams;\n },\n options: {\n generateTokens: boolean;\n },\n): Promise<SignInFlowResult<SessionInfo<AuthTokens | null> | null>> {\n return withSpan(\"convex-auth.signin.credentials\", {}, async () => {\n let result: CredentialsAuthorizeResult;\n try {\n result = await withSpan(\n \"convex-auth.signin.credentials.authorize\",\n { providerId: provider.id },\n () => provider.authorize(args.params ?? {}, ctx),\n );\n } catch (error) {\n throw asCredentialsError(error);\n }\n if (result === null) {\n return { kind: \"signedIn\" as const, session: null };\n }\n if (\"kind\" in result) {\n return result;\n }\n\n if (\"provision\" in result) {\n const { provision, hasTotp } = result;\n try {\n const provisioned = await withSpan(\n \"convex-auth.signin.credentials.provision\",\n { providerId: provider.id },\n () =>\n ctx.auth.account.create(ctx, {\n provider: provider.id,\n account: provision.account,\n profile: provision.profile,\n shouldLinkViaEmail: provision.match?.includes(\"email\"),\n shouldLinkViaPhone: provision.match?.includes(\"phone\"),\n }),\n );\n result = {\n userId: provisioned.account.userId,\n hasTotp,\n };\n } catch (error) {\n throw asConvexError(error, \"INTERNAL_ERROR\", \"Failed to provision credentials account.\");\n }\n }\n\n const hintedHasTotp = result.hasTotp;\n const preIssuedIssuance = result.issuance;\n\n let hasTotpEnrolled: boolean;\n if (hintedHasTotp === false) {\n hasTotpEnrolled = false;\n } else if (hintedHasTotp === true) {\n hasTotpEnrolled = true;\n } else if (preIssuedIssuance !== undefined && preIssuedIssuance.refreshToken === null) {\n hasTotpEnrolled = true;\n } else {\n try {\n const totpDoc = await withSpan(\n \"convex-auth.signin.credentials.totp-check\",\n { hinted: hintedHasTotp ?? \"unknown\" },\n () => queryTotpVerifiedByUserId(ctx, result.userId),\n );\n hasTotpEnrolled = totpDoc !== null;\n } catch (error) {\n throw asConvexError(error, \"INTERNAL_ERROR\", \"Failed to load TOTP enrollment.\");\n }\n }\n\n if (hasTotpEnrolled) {\n let verifier: string;\n try {\n verifier = await callVerifier(ctx, JSON.stringify({ userId: result.userId }));\n } catch (error) {\n throw asConvexError(error, \"INTERNAL_ERROR\", \"Failed to create verifier.\");\n }\n return { kind: \"totpRequired\" as const, verifier };\n }\n\n if (preIssuedIssuance !== undefined) {\n try {\n const idsAndTokens = await withSpan(\n \"convex-auth.signin.credentials.finalize\",\n { generateTokens: options.generateTokens, fromAuthorize: true },\n () =>\n finalizeSessionIssuance(ctx.auth.config, {\n ...preIssuedIssuance,\n refreshToken: options.generateTokens ? preIssuedIssuance.refreshToken : null,\n }),\n );\n return { kind: \"signedIn\" as const, session: idsAndTokens };\n } catch (error) {\n throw asConvexError(error, \"INTERNAL_ERROR\", \"Failed to finalize sign-in.\");\n }\n }\n\n let idsAndTokens;\n try {\n idsAndTokens = await withSpan(\n \"convex-auth.signin.credentials.issue-session\",\n { generateTokens: options.generateTokens },\n () =>\n callSignIn(ctx, {\n userId: result.userId,\n sessionId: result.sessionId,\n provider: provider.id,\n generateTokens: options.generateTokens,\n }),\n );\n } catch (error) {\n throw asConvexError(error, \"INTERNAL_ERROR\", \"Failed to complete sign-in.\");\n }\n return { kind: \"signedIn\" as const, session: idsAndTokens };\n });\n}\n\nasync function handleOAuthProvider(\n ctx: EnrichedActionCtx,\n provider: OAuthMaterializedConfig,\n args: {\n params?: SignInParams;\n verifier?: string;\n },\n options: {\n allowExtraProviders: boolean;\n authSiteUrl?: string;\n },\n): Promise<SignInSessionResult<SessionInfo<AuthTokens> | null> | SignInRedirectResult> {\n return withSpan(`convex-auth.signin.oauth`, { provider: provider.id }, async () => {\n if (args.params?.code !== undefined) {\n let result;\n try {\n result = await callVerifyCodeAndSignIn(ctx, {\n params: args.params as SignInParams,\n verifier: args.verifier,\n createSession: true,\n generateTokens: true,\n allowExtraProviders: options.allowExtraProviders,\n });\n } catch (error) {\n throw asConvexError(error, \"INTERNAL_ERROR\", \"Failed to verify OAuth sign-in.\");\n }\n return {\n kind: \"signedIn\" as const,\n session: result,\n };\n }\n\n const authSiteUrl = options.authSiteUrl ?? authUrlFromEnv();\n const redirect = new URL(`${authSiteUrl.replace(/\\/$/, \"\")}/signin/${provider.id}`);\n let verifier: string;\n try {\n verifier = await callVerifier(ctx);\n } catch (error) {\n throw asConvexError(error, \"INTERNAL_ERROR\", \"Failed to create verifier.\");\n }\n redirect.searchParams.set(\"code\", verifier);\n\n if (args.params?.redirectTo !== undefined) {\n if (typeof args.params.redirectTo !== \"string\") {\n throw toConvexError(\n authFlowError(\n \"INVALID_REDIRECT\",\n `Expected \\`redirectTo\\` to be a string, got ${describeUnknown(args.params.redirectTo)}`,\n ),\n );\n }\n redirect.searchParams.set(\"redirectTo\", args.params.redirectTo);\n }\n\n return {\n kind: \"redirect\" as const,\n redirect: redirect.toString(),\n verifier,\n };\n });\n}\n\nasync function handleConnectionProvider(\n ctx: EnrichedActionCtx,\n args: {\n params?: SignInParams;\n },\n options: {\n resolveConnectionProtocol?: (\n ctx: EnrichedActionCtx,\n connectionId: string,\n ) => Promise<\"oidc\" | \"saml\">;\n authSiteUrl?: string;\n },\n): Promise<{ kind: \"redirect\"; redirect: string; verifier: string }> {\n return withSpan(\"convex-auth.signin.connection\", {}, async () => {\n const normalizedParams = normalizeVerificationParams(args.params);\n const connectionId = normalizedParams.connectionId;\n if (!connectionId || typeof connectionId !== \"string\") {\n throw toConvexError(\n authFlowError(\"SIGN_IN_MISSING_PARAMS\", \"connectionId is required for Connection sign-in.\"),\n );\n }\n\n let protocol: \"oidc\" | \"saml\" =\n (normalizedParams.protocol === \"oidc\" || normalizedParams.protocol === \"saml\"\n ? normalizedParams.protocol\n : undefined) ??\n (options.resolveConnectionProtocol\n ? await (async () => {\n try {\n return await options.resolveConnectionProtocol!(ctx, connectionId);\n } catch (error) {\n throw asConvexError(\n error,\n \"INTERNAL_ERROR\",\n \"Failed to resolve Connection protocol.\",\n );\n }\n })()\n : \"oidc\");\n\n log(\"DEBUG\", \"[group-connection] signin:resolved\", {\n connectionId,\n protocol,\n redirectTo: typeof args.params?.redirectTo === \"string\" ? args.params.redirectTo : undefined,\n });\n\n if (protocol !== \"oidc\" && protocol !== \"saml\") {\n throw toConvexError(\n authFlowError(\n \"SIGN_IN_MISSING_PARAMS\",\n `Invalid Connection protocol: ${protocol as string}. Expected \"oidc\" or \"saml\".`,\n ),\n );\n }\n\n let verifier: string;\n try {\n verifier = await callVerifier(ctx);\n } catch (error) {\n throw asConvexError(error, \"INTERNAL_ERROR\", \"Failed to create verifier.\");\n }\n const siteUrl = options.authSiteUrl ?? authUrlFromEnv();\n const redirect = new URL(\n `${siteUrl.replace(/\\/$/, \"\")}/connections/${connectionId}/${protocol}/signin`,\n );\n redirect.searchParams.set(\"code\", verifier);\n\n if (typeof args.params?.redirectTo === \"string\") {\n redirect.searchParams.set(\"redirectTo\", args.params.redirectTo);\n }\n if (typeof normalizedParams.loginHint === \"string\") {\n redirect.searchParams.set(\"loginHint\", normalizedParams.loginHint);\n }\n log(\"DEBUG\", \"[group-connection] signin:redirect\", {\n connectionId,\n protocol,\n redirect: redirect.toString(),\n });\n\n return {\n kind: \"redirect\" as const,\n redirect: redirect.toString(),\n verifier,\n };\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"flow.js","names":[],"sources":["../../../src/server/signin/flow.ts"],"sourcesContent":["import type { GenericDataModel } from \"convex/server\";\nimport { GenericId, ConvexError } from \"convex/values\";\nimport type { GenericValidator } from \"convex/values\";\nimport type { CredentialsAuthorizeResult } from \"../../providers/credentials\";\n\nimport { assertNever } from \"../../shared/brand\";\nimport { authFlowError } from \"../../shared/errors\";\nimport type {\n AuthTokens,\n SignInFlowResult,\n SignInRedirectResult,\n SignInSessionResult,\n SignInStartResult,\n} from \"../../shared/results\";\nimport { handleDevice } from \"../device\";\nimport type { AuthErrorData } from \"../errors\";\nimport { toConvexError } from \"../errors\";\nimport { log } from \"../log\";\nimport {\n callCreateVerificationCode,\n callRefreshSession,\n callSignIn,\n callVerifier,\n callVerifyCodeAndSignIn,\n} from \"../mutations/calls\";\nimport { handleWebAuthn } from \"../webauthn\";\nimport type { SignInParams } from \"../payloads\";\nimport { generateRandomString } from \"../random\";\nimport { redirectAbsoluteUrl, setURLSearchParam } from \"../redirects\";\nimport { finalizeSessionIssuance } from \"../session/lifecycle\";\nimport { handleTotp } from \"../totp\";\nimport {\n AuthProviderMaterializedConfig,\n ConvexCredentialsConfig,\n EmailConfig,\n GenericActionCtxWithAuthConfig,\n PhoneConfig,\n} from \"../types\";\nimport { queryTotpVerifiedByUserId } from \"../component/factor/db\";\nimport { AuthDataModel, SessionInfo } from \"../types\";\nimport type { OAuthMaterializedConfig } from \"../types\";\nimport { authUrlFromEnv } from \"../url\";\nimport { withSpan } from \"../utils/span\";\n\nconst DEFAULT_EMAIL_VERIFICATION_CODE_DURATION_S = 60 * 60 * 24;\n\ntype EnrichedActionCtx = GenericActionCtxWithAuthConfig<AuthDataModel>;\n\ntype SignInResult = SignInFlowResult<SessionInfo<AuthTokens | null> | null>;\n\ntype VerificationParams = {\n email?: string;\n phone?: string;\n redirectTo?: unknown;\n connectionId?: unknown;\n loginHint?: unknown;\n protocol?: unknown;\n code?: unknown;\n};\n\nconst normalizeVerificationParams = (params: SignInParams | undefined) => {\n const value = (params ?? {}) as VerificationParams;\n return {\n email: typeof value.email === \"string\" ? value.email : undefined,\n phone: typeof value.phone === \"string\" ? value.phone : undefined,\n redirectTo: value.redirectTo,\n connectionId: value.connectionId,\n loginHint: value.loginHint,\n protocol: value.protocol,\n code: value.code,\n };\n};\n\nconst describeUnknown = (value: unknown) => {\n if (typeof value === \"string\") {\n return JSON.stringify(value);\n }\n if (\n typeof value === \"number\" ||\n typeof value === \"boolean\" ||\n typeof value === \"bigint\" ||\n value === null\n ) {\n return String(value);\n }\n const json = JSON.stringify(value);\n return json ?? Object.prototype.toString.call(value);\n};\n\nconst asConvexError = (\n error: unknown,\n code: string,\n message: string,\n): ConvexError<AuthErrorData> => {\n if (error instanceof ConvexError) {\n return error;\n }\n if (error instanceof Error) {\n return toConvexError(authFlowError(code, error.message || message));\n }\n return toConvexError(authFlowError(code, `${message} ${describeUnknown(error)}`.trim()));\n};\n\nconst asCredentialsError = (error: unknown): ConvexError<AuthErrorData> => {\n if (error instanceof ConvexError) {\n return error as ConvexError<AuthErrorData>;\n }\n if (error instanceof Error) {\n return new ConvexError({\n code: error.message.startsWith(\"Missing `\") ? \"INVALID_PARAMETERS\" : \"INVALID_CREDENTIALS\",\n message: error.message,\n });\n }\n return toConvexError(authFlowError(\"INTERNAL_ERROR\", \"Failed to authorize credentials.\"));\n};\n\n/**\n * Entry point for all sign-in flows.\n *\n * Refreshes a session (when only `refreshToken` is given), verifies a sign-in\n * code (when only `params.code` is given), or dispatches to the matching\n * provider handler based on `provider.type`.\n *\n * @param provider - The resolved provider, or `null` for refresh/code-only calls.\n * @returns The flow result: `signedIn`, `started`, `redirect`, `totpRequired`, etc.\n */\nexport async function signInImpl(\n ctx: EnrichedActionCtx,\n provider: AuthProviderMaterializedConfig | null,\n args: {\n accountId?: GenericId<\"Account\">;\n params?: SignInParams;\n verifier?: string;\n continuation?: string;\n refreshToken?: string;\n calledBy?: string;\n },\n options: {\n generateTokens: boolean;\n allowExtraProviders: boolean;\n authSiteUrl?: string;\n resolveConnectionProtocol?: (\n ctx: EnrichedActionCtx,\n connectionId: string,\n ) => Promise<\"oidc\" | \"saml\">;\n },\n): Promise<SignInResult> {\n return withSpan(\n \"convex-auth.signin\",\n {\n hasProvider: provider !== null,\n hasCode: args.params?.code !== undefined,\n hasRefreshToken: args.refreshToken !== undefined,\n },\n async () => {\n if (provider === null && args.refreshToken) {\n try {\n const session = await callRefreshSession(ctx, { refreshToken: args.refreshToken! });\n if (session === null) {\n return { kind: \"signedIn\" as const, session: null };\n }\n return { kind: \"signedIn\" as const, session };\n } catch (error) {\n throw asConvexError(error, \"INTERNAL_ERROR\", \"Failed to refresh session.\");\n }\n }\n\n if (provider === null && args.params?.code !== undefined) {\n try {\n const result = await callVerifyCodeAndSignIn(ctx, {\n params: args.params as SignInParams,\n verifier: args.verifier,\n createSession: true,\n generateTokens: true,\n allowExtraProviders: options.allowExtraProviders,\n });\n return { kind: \"signedIn\" as const, session: result };\n } catch (error) {\n throw asConvexError(error, \"INTERNAL_ERROR\", \"Failed to verify sign-in code.\");\n }\n }\n\n const resolvedProvider = provider;\n if (resolvedProvider === null) {\n throw toConvexError(\n authFlowError(\n \"SIGN_IN_MISSING_PARAMS\",\n \"Cannot sign in: missing provider, code, or refresh token.\",\n ),\n );\n }\n\n /** Exhaustive narrowing dispatch: each case narrows `resolvedProvider` to its concrete config so the handler receives the typed variant without a cast. */\n switch (resolvedProvider.type) {\n case \"email\":\n case \"phone\":\n return handleEmailAndPhoneProvider(ctx, resolvedProvider, args, options);\n case \"credentials\":\n return handleCredentials(ctx, resolvedProvider, args, options);\n case \"oauth\":\n return handleOAuthProvider(ctx, resolvedProvider, args, options);\n case \"webauthn\":\n return handleWebAuthn(ctx, resolvedProvider, args);\n case \"totp\":\n return handleTotp(ctx, resolvedProvider, args);\n case \"device\":\n return handleDevice(ctx, resolvedProvider, args);\n case \"connection\":\n return handleConnectionProvider(ctx, args, options);\n default:\n return assertNever(resolvedProvider, \"Unknown provider type\");\n }\n },\n );\n}\n\nasync function handleEmailAndPhoneProvider(\n ctx: EnrichedActionCtx,\n provider: EmailConfig | PhoneConfig,\n args: {\n params?: SignInParams;\n accountId?: GenericId<\"Account\">;\n },\n options: {\n generateTokens: boolean;\n allowExtraProviders: boolean;\n },\n): Promise<SignInStartResult | SignInSessionResult<SessionInfo<AuthTokens | null>>> {\n return withSpan(`convex-auth.signin.${provider.type}`, {}, async () => {\n const normalizedParams = normalizeVerificationParams(args.params);\n if (args.params?.code !== undefined) {\n let result;\n try {\n result = await callVerifyCodeAndSignIn(ctx, {\n params: args.params as SignInParams,\n provider: provider.id,\n createSession: true,\n generateTokens: options.generateTokens,\n allowExtraProviders: options.allowExtraProviders,\n });\n } catch (error) {\n throw asConvexError(error, \"INTERNAL_ERROR\", \"Failed to verify email or phone code.\");\n }\n if (result === null) {\n throw toConvexError(\n authFlowError(\"INVALID_VERIFICATION_CODE\", \"Invalid or expired verification code.\"),\n );\n }\n return {\n kind: \"signedIn\" as const,\n session: result,\n };\n }\n\n const alphabet = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\";\n let code: string;\n if (provider.generateVerificationToken) {\n try {\n code = await provider.generateVerificationToken();\n } catch {\n throw toConvexError(\n authFlowError(\"INTERNAL_ERROR\", \"Failed to generate verification token\"),\n );\n }\n } else {\n code = generateRandomString(32, alphabet);\n }\n\n const expirationTime =\n Date.now() + (provider.maxAge ?? DEFAULT_EMAIL_VERIFICATION_CODE_DURATION_S) * 1000;\n\n let identifier: string;\n try {\n identifier = await callCreateVerificationCode(ctx, {\n provider: provider.id,\n accountId: args.accountId,\n email: normalizedParams.email,\n phone: normalizedParams.phone,\n code,\n expirationTime,\n allowExtraProviders: options.allowExtraProviders,\n });\n } catch (error) {\n throw asConvexError(error, \"INTERNAL_ERROR\", \"Failed to create verification code.\");\n }\n\n let destination: string;\n try {\n destination = await redirectAbsoluteUrl(ctx, ctx.auth.config, {\n redirectTo: args.params?.redirectTo,\n });\n } catch (error) {\n throw asConvexError(error, \"INVALID_REDIRECT\", \"Failed to resolve redirect URL.\");\n }\n\n const verificationArgs = {\n identifier,\n url: setURLSearchParam(destination, \"code\", code),\n token: code,\n expires: new Date(expirationTime),\n };\n\n if (provider.type === \"email\") {\n try {\n await provider.sendVerificationRequest(\n {\n ...verificationArgs,\n provider,\n request: new Request(\"http://localhost\"),\n },\n ctx,\n );\n } catch {\n throw toConvexError(authFlowError(\"INTERNAL_ERROR\", \"Failed to send email code\"));\n }\n } else {\n try {\n await provider.sendVerificationRequest({ ...verificationArgs, provider }, ctx);\n } catch {\n throw toConvexError(authFlowError(\"INTERNAL_ERROR\", \"Failed to send phone code\"));\n }\n }\n\n return { kind: \"started\" as const };\n });\n}\n\nasync function handleCredentials(\n ctx: EnrichedActionCtx,\n provider: ConvexCredentialsConfig<GenericDataModel, GenericValidator, string>,\n args: {\n params?: SignInParams;\n },\n options: {\n generateTokens: boolean;\n },\n): Promise<SignInFlowResult<SessionInfo<AuthTokens | null> | null>> {\n return withSpan(\"convex-auth.signin.credentials\", {}, async () => {\n let result: CredentialsAuthorizeResult;\n try {\n result = await withSpan(\n \"convex-auth.signin.credentials.authorize\",\n { providerId: provider.id },\n () => provider.authorize(args.params, ctx),\n );\n } catch (error) {\n throw asCredentialsError(error);\n }\n if (result === null) {\n return { kind: \"signedIn\" as const, session: null };\n }\n if (\"kind\" in result) {\n return result;\n }\n\n if (\"provision\" in result) {\n const { provision, hasTotp } = result;\n try {\n const provisioned = await withSpan(\n \"convex-auth.signin.credentials.provision\",\n { providerId: provider.id },\n () =>\n ctx.auth.account.create(ctx, {\n provider: provider.id,\n account: provision.account,\n profile: provision.profile,\n shouldLinkViaEmail: provision.match?.includes(\"email\"),\n shouldLinkViaPhone: provision.match?.includes(\"phone\"),\n }),\n );\n result = {\n userId: provisioned.account.userId,\n hasTotp,\n };\n } catch (error) {\n throw asConvexError(error, \"INTERNAL_ERROR\", \"Failed to provision credentials account.\");\n }\n }\n\n const hintedHasTotp = result.hasTotp;\n const preIssuedIssuance = result.issuance;\n\n let hasTotpEnrolled: boolean;\n if (hintedHasTotp === false) {\n hasTotpEnrolled = false;\n } else if (hintedHasTotp === true) {\n hasTotpEnrolled = true;\n } else if (preIssuedIssuance !== undefined && preIssuedIssuance.refreshToken === null) {\n hasTotpEnrolled = true;\n } else {\n try {\n const totpDoc = await withSpan(\n \"convex-auth.signin.credentials.totp-check\",\n { hinted: hintedHasTotp ?? \"unknown\" },\n () => queryTotpVerifiedByUserId(ctx, result.userId),\n );\n hasTotpEnrolled = totpDoc !== null;\n } catch (error) {\n throw asConvexError(error, \"INTERNAL_ERROR\", \"Failed to load TOTP enrollment.\");\n }\n }\n\n if (hasTotpEnrolled) {\n let verifier: string;\n try {\n verifier = await callVerifier(ctx, JSON.stringify({ userId: result.userId }));\n } catch (error) {\n throw asConvexError(error, \"INTERNAL_ERROR\", \"Failed to create verifier.\");\n }\n return { kind: \"totpRequired\" as const, verifier };\n }\n\n if (preIssuedIssuance !== undefined) {\n try {\n const idsAndTokens = await withSpan(\n \"convex-auth.signin.credentials.finalize\",\n { generateTokens: options.generateTokens, fromAuthorize: true },\n () =>\n finalizeSessionIssuance(ctx.auth.config, {\n ...preIssuedIssuance,\n refreshToken: options.generateTokens ? preIssuedIssuance.refreshToken : null,\n }),\n );\n return { kind: \"signedIn\" as const, session: idsAndTokens };\n } catch (error) {\n throw asConvexError(error, \"INTERNAL_ERROR\", \"Failed to finalize sign-in.\");\n }\n }\n\n let idsAndTokens;\n try {\n idsAndTokens = await withSpan(\n \"convex-auth.signin.credentials.issue-session\",\n { generateTokens: options.generateTokens },\n () =>\n callSignIn(ctx, {\n userId: result.userId,\n sessionId: result.sessionId,\n provider: provider.id,\n generateTokens: options.generateTokens,\n }),\n );\n } catch (error) {\n throw asConvexError(error, \"INTERNAL_ERROR\", \"Failed to complete sign-in.\");\n }\n return { kind: \"signedIn\" as const, session: idsAndTokens };\n });\n}\n\nasync function handleOAuthProvider(\n ctx: EnrichedActionCtx,\n provider: OAuthMaterializedConfig,\n args: {\n params?: SignInParams;\n verifier?: string;\n },\n options: {\n allowExtraProviders: boolean;\n authSiteUrl?: string;\n },\n): Promise<SignInSessionResult<SessionInfo<AuthTokens> | null> | SignInRedirectResult> {\n return withSpan(`convex-auth.signin.oauth`, { provider: provider.id }, async () => {\n if (args.params?.code !== undefined) {\n let result;\n try {\n result = await callVerifyCodeAndSignIn(ctx, {\n params: args.params as SignInParams,\n verifier: args.verifier,\n createSession: true,\n generateTokens: true,\n allowExtraProviders: options.allowExtraProviders,\n });\n } catch (error) {\n throw asConvexError(error, \"INTERNAL_ERROR\", \"Failed to verify OAuth sign-in.\");\n }\n return {\n kind: \"signedIn\" as const,\n session: result,\n };\n }\n\n const authSiteUrl = options.authSiteUrl ?? authUrlFromEnv();\n const redirect = new URL(`${authSiteUrl.replace(/\\/$/, \"\")}/signin/${provider.id}`);\n let verifier: string;\n try {\n verifier = await callVerifier(ctx);\n } catch (error) {\n throw asConvexError(error, \"INTERNAL_ERROR\", \"Failed to create verifier.\");\n }\n redirect.searchParams.set(\"code\", verifier);\n\n if (args.params?.redirectTo !== undefined) {\n if (typeof args.params.redirectTo !== \"string\") {\n throw toConvexError(\n authFlowError(\n \"INVALID_REDIRECT\",\n `Expected \\`redirectTo\\` to be a string, got ${describeUnknown(args.params.redirectTo)}`,\n ),\n );\n }\n redirect.searchParams.set(\"redirectTo\", args.params.redirectTo);\n }\n\n return {\n kind: \"redirect\" as const,\n redirect: redirect.toString(),\n verifier,\n };\n });\n}\n\nasync function handleConnectionProvider(\n ctx: EnrichedActionCtx,\n args: {\n params?: SignInParams;\n },\n options: {\n resolveConnectionProtocol?: (\n ctx: EnrichedActionCtx,\n connectionId: string,\n ) => Promise<\"oidc\" | \"saml\">;\n authSiteUrl?: string;\n },\n): Promise<{ kind: \"redirect\"; redirect: string; verifier: string }> {\n return withSpan(\"convex-auth.signin.connection\", {}, async () => {\n const normalizedParams = normalizeVerificationParams(args.params);\n const connectionId = normalizedParams.connectionId;\n if (!connectionId || typeof connectionId !== \"string\") {\n throw toConvexError(\n authFlowError(\"SIGN_IN_MISSING_PARAMS\", \"connectionId is required for Connection sign-in.\"),\n );\n }\n\n let protocol: \"oidc\" | \"saml\" =\n (normalizedParams.protocol === \"oidc\" || normalizedParams.protocol === \"saml\"\n ? normalizedParams.protocol\n : undefined) ??\n (options.resolveConnectionProtocol\n ? await (async () => {\n try {\n return await options.resolveConnectionProtocol!(ctx, connectionId);\n } catch (error) {\n throw asConvexError(\n error,\n \"INTERNAL_ERROR\",\n \"Failed to resolve Connection protocol.\",\n );\n }\n })()\n : \"oidc\");\n\n log(\"DEBUG\", \"[group-connection] signin:resolved\", {\n connectionId,\n protocol,\n redirectTo: typeof args.params?.redirectTo === \"string\" ? args.params.redirectTo : undefined,\n });\n\n if (protocol !== \"oidc\" && protocol !== \"saml\") {\n throw toConvexError(\n authFlowError(\n \"SIGN_IN_MISSING_PARAMS\",\n `Invalid Connection protocol: ${protocol as string}. Expected \"oidc\" or \"saml\".`,\n ),\n );\n }\n\n let verifier: string;\n try {\n verifier = await callVerifier(ctx);\n } catch (error) {\n throw asConvexError(error, \"INTERNAL_ERROR\", \"Failed to create verifier.\");\n }\n const siteUrl = options.authSiteUrl ?? authUrlFromEnv();\n const redirect = new URL(\n `${siteUrl.replace(/\\/$/, \"\")}/connections/${connectionId}/${protocol}/signin`,\n );\n redirect.searchParams.set(\"code\", verifier);\n\n if (typeof args.params?.redirectTo === \"string\") {\n redirect.searchParams.set(\"redirectTo\", args.params.redirectTo);\n }\n if (typeof normalizedParams.loginHint === \"string\") {\n redirect.searchParams.set(\"loginHint\", normalizedParams.loginHint);\n }\n log(\"DEBUG\", \"[group-connection] signin:redirect\", {\n connectionId,\n protocol,\n redirect: redirect.toString(),\n });\n\n return {\n kind: \"redirect\" as const,\n redirect: redirect.toString(),\n verifier,\n };\n });\n}\n"],"mappings":"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"}
|
package/dist/server/telemetry.js
CHANGED
package/dist/server/tokens.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { generateRandomString } from "./random.js";
|
|
1
2
|
import { envOptionalString, requireAuthKey } from "./env.js";
|
|
2
3
|
import { authUrlFromEnv } from "./url.js";
|
|
3
|
-
import { generateRandomString } from "./random.js";
|
|
4
4
|
import { withSpan } from "./utils/span.js";
|
|
5
5
|
import { SignJWT, createLocalJWKSet, importPKCS8, jwtVerify } from "jose";
|
|
6
6
|
|
|
@@ -139,16 +139,19 @@ async function verifyOAuthToken(token, opts) {
|
|
|
139
139
|
/**
|
|
140
140
|
* Sign a session access token (EdDSA) carrying the identity claims, audienced to Convex.
|
|
141
141
|
*
|
|
142
|
-
* Expiry is `config.jwt.durationMs` from now
|
|
142
|
+
* Expiry is the earlier of `config.jwt.durationMs` from now (defaulting to one
|
|
143
|
+
* hour) and the durable session's absolute expiration.
|
|
143
144
|
*
|
|
144
145
|
* @internal
|
|
145
146
|
*/
|
|
146
147
|
async function generateToken(args, config) {
|
|
147
148
|
const privateKey = await withSpan("convex-auth.tokens.import-key", { alg: JWT_ALG }, () => getPrivateKey());
|
|
148
|
-
const
|
|
149
|
+
const jwtExpirationTime = Date.now() + (config.jwt?.durationMs ?? DEFAULT_JWT_DURATION_MS);
|
|
150
|
+
const expirationTime = new Date(Math.min(jwtExpirationTime, args.sessionExpirationTime));
|
|
149
151
|
const claims = {
|
|
150
152
|
sub: args.identity.subject,
|
|
151
153
|
sid: args.identity.sessionId,
|
|
154
|
+
session_epoch: args.identity.sessionEpoch,
|
|
152
155
|
...args.identity.name !== void 0 ? { name: args.identity.name } : null,
|
|
153
156
|
...args.identity.email !== void 0 ? { email: args.identity.email } : null,
|
|
154
157
|
...args.identity.emailVerified !== void 0 ? { email_verified: args.identity.emailVerified } : null,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokens.js","names":[],"sources":["../../src/server/tokens.ts"],"sourcesContent":["import { SignJWT, createLocalJWKSet, importPKCS8, jwtVerify } from \"jose\";\n\nimport type { AccessToken } from \"../shared/brand\";\nimport { envOptionalString, requireAuthKey } from \"./env\";\nimport { generateRandomString } from \"./random\";\nimport { ConvexAuthConfig } from \"./types\";\nimport type { SessionTokenIdentityClaims } from \"./types\";\nimport { authUrlFromEnv } from \"./url\";\nimport { withSpan } from \"./utils/span\";\n\nconst DEFAULT_JWT_DURATION_MS = 1000 * 60 * 60;\nconst TOKEN_JTI_LENGTH = 24;\nconst TOKEN_JTI_ALPHABET = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\";\n\nlet cachedPrivateKeyPromise: Promise<Awaited<ReturnType<typeof importPKCS8>>> | null = null;\nconst cachedIssuers = new Map<string, string>();\n\nconst JWT_ALG = \"EdDSA\" as const;\n\nfunction normalizePkcs8Pem(value: string) {\n const trimmed = value.trim();\n if (!trimmed.includes(\"-----BEGIN PRIVATE KEY-----\")) {\n return trimmed;\n }\n\n const withEscapedNewlines = trimmed.replace(/\\\\n/g, \"\\n\");\n if (withEscapedNewlines.includes(\"\\n\")) {\n return withEscapedNewlines;\n }\n\n const beginMarker = \"-----BEGIN PRIVATE KEY-----\";\n const endMarker = \"-----END PRIVATE KEY-----\";\n const body = trimmed.replace(beginMarker, \"\").replace(endMarker, \"\").trim().replace(/\\s+/g, \"\");\n\n if (body.length === 0) {\n return trimmed;\n }\n\n const wrappedBody = body.match(/.{1,64}/g)?.join(\"\\n\") ?? body;\n return `${beginMarker}\\n${wrappedBody}\\n${endMarker}`;\n}\n\nconst getPrivateKey = () => {\n if (cachedPrivateKeyPromise === null) {\n try {\n const pem = normalizePkcs8Pem(requireAuthKey(\"jwtPrivateKey\"));\n cachedPrivateKeyPromise = importPKCS8(pem, JWT_ALG).catch((error) => {\n cachedPrivateKeyPromise = null;\n throw error;\n });\n } catch (error) {\n cachedPrivateKeyPromise = null;\n throw error;\n }\n }\n return cachedPrivateKeyPromise;\n};\n\nconst getIssuer = (path?: string) => {\n const key = path ?? \"\";\n let issuer = cachedIssuers.get(key);\n if (issuer === undefined) {\n issuer = authUrlFromEnv(path);\n cachedIssuers.set(key, issuer);\n }\n return issuer;\n};\n\nif (envOptionalString(\"AUTH_KEYS\")) {\n try {\n void getPrivateKey().catch((err) => {\n console.error(\"[auth] JWT private key pre-warm failed\", { err });\n });\n } catch (err) {\n console.error(\"[auth] JWT private key pre-warm threw synchronously\", { err });\n }\n}\nif (envOptionalString(\"CONVEX_SITE_URL\")) {\n try {\n getIssuer();\n } catch (err) {\n console.error(\"[auth] JWT issuer pre-warm threw\", { err });\n }\n}\n\n/** Lifetime of an issued OAuth access token, in seconds. Shared with the token endpoint's `expires_in`. */\nexport const OAUTH_ACCESS_TOKEN_DURATION_S = 900;\n\n/**\n * Sign an OAuth access token (EdDSA) for the given user, client, and scopes.\n *\n * The `aud` claim is `\"convex\"` so the token is a valid Convex identity\n * (`applicationID: \"convex\"`) — load-bearing: MCP tools run the user's queries\n * via `ctx.runQuery`, which requires Convex's identity layer to accept this\n * token. For that reason the access token must look like an ID-token-shaped JWT:\n * it carries NO `typ: \"at+jwt\"` header (Convex's validator rejects that RFC 9068\n * type), and is instead marked as an access token by a positive `token_use:\n * \"access\"` claim, which `verifyOAuthToken` checks (alongside the `client_id`\n * claim) to tell it apart from a session token. We don't issue OIDC ID tokens,\n * so there is no id-vs-access confusion for the `at+jwt` typ to guard against.\n *\n * When an RFC 8707 `resource` is supplied the token carries a `resource` claim\n * binding it to that protected resource; an MCP endpoint rejects tokens whose\n * `resource` does not match its own canonical resource.\n *\n * @internal\n */\nexport async function generateOAuthToken(args: {\n userId: string;\n clientId: string;\n scopes: string[];\n issuer?: string;\n resource?: string;\n}): Promise<string> {\n const privateKey = await withSpan(\"convex-auth.tokens.import-key\", { alg: JWT_ALG }, () =>\n getPrivateKey(),\n );\n const exp = new Date(Date.now() + OAUTH_ACCESS_TOKEN_DURATION_S * 1000);\n return await withSpan(\"convex-auth.tokens.sign-oauth\", { alg: JWT_ALG }, () =>\n new SignJWT({\n sub: args.userId,\n aud: \"convex\",\n token_use: \"access\",\n scope: args.scopes.join(\" \"),\n client_id: args.clientId,\n ...(args.resource !== undefined ? { resource: args.resource } : null),\n })\n .setProtectedHeader({ alg: JWT_ALG })\n .setIssuedAt()\n .setJti(generateRandomString(TOKEN_JTI_LENGTH, TOKEN_JTI_ALPHABET))\n .setIssuer(args.issuer ?? getIssuer())\n .setExpirationTime(exp)\n .sign(privateKey),\n );\n}\n\nlet cachedKeySet: ReturnType<typeof createLocalJWKSet> | null = null;\n\n/**\n * The local JWKS keyset, built once and reused. `AUTH_KEYS` is static per\n * deployment, so caching also preserves jose's internal imported-key cache\n * across requests rather than re-importing keys on every verification.\n */\nfunction getJwkSet(): ReturnType<typeof createLocalJWKSet> {\n if (cachedKeySet === null) {\n cachedKeySet = createLocalJWKSet(requireAuthKey(\"jwks\"));\n }\n return cachedKeySet;\n}\n\n/**\n * Verify an OAuth access token against the local JWKS and issuer.\n *\n * When `opts.resource` is supplied, the token's RFC 8707 `resource` claim must\n * equal it exactly — a token bound to a different (or no) resource is rejected.\n * This is how an MCP endpoint enforces that a token was issued for *it*.\n *\n * @returns The token's user, client, scopes, and bound `resource`, or `null` if\n * verification fails, the token is not an OAuth access token, or the resource\n * assertion does not hold.\n * @internal\n */\nexport async function verifyOAuthToken(\n token: string,\n opts?: { issuer?: string; resource?: string },\n): Promise<{ userId: string; clientId: string; scopes: string[]; resource: string | null } | null> {\n try {\n const { payload } = await jwtVerify(token, getJwkSet(), {\n issuer: opts?.issuer ?? getIssuer(),\n audience: \"convex\",\n // Defense-in-depth: pin the accepted JWS algorithm so a token can only be\n // verified under EdDSA, never coerced to a different `alg` (algorithm\n // substitution / confusion hardening).\n algorithms: [JWT_ALG],\n clockTolerance: 30,\n });\n if ((payload as { token_use?: string }).token_use !== \"access\") return null;\n const userId = payload.sub;\n const clientId = (payload as { client_id?: string }).client_id;\n const scope = (payload as { scope?: string }).scope ?? \"\";\n if (!userId || !clientId) return null;\n const resourceClaim = (payload as { resource?: unknown }).resource;\n const resource = typeof resourceClaim === \"string\" ? resourceClaim : null;\n if (opts?.resource !== undefined && resource !== opts.resource) return null;\n // NOTE: the returned `scopes` are ADVISORY, not a Convex-enforced capability\n // boundary. Because `aud` is \"convex\", Convex's identity layer accepts this\n // token as the user's full identity — any query/mutation the user could run,\n // a client bearing this token can run via `ctx.runQuery`/`ctx.runMutation`,\n // regardless of `scope`. Integrators MUST enforce these scopes app-side\n // (e.g. inside each MCP tool handler) for them to actually restrict access.\n return { userId, clientId, scopes: scope ? scope.split(\" \") : [], resource };\n } catch {\n return null;\n }\n}\n\n/**\n * Sign a session access token (EdDSA) carrying the identity claims, audienced to Convex.\n *\n * Expiry is `config.jwt.durationMs` from now, defaulting to one hour.\n *\n * @internal\n */\nexport async function generateToken(\n args: {\n identity: SessionTokenIdentityClaims;\n },\n config: ConvexAuthConfig,\n): Promise<AccessToken> {\n const privateKey = await withSpan(\"convex-auth.tokens.import-key\", { alg: JWT_ALG }, () =>\n getPrivateKey(),\n );\n const expirationTime = new Date(Date.now() + (config.jwt?.durationMs ?? DEFAULT_JWT_DURATION_MS));\n const claims = {\n sub: args.identity.subject,\n sid: args.identity.sessionId,\n ...(args.identity.name !== undefined ? { name: args.identity.name } : null),\n ...(args.identity.email !== undefined ? { email: args.identity.email } : null),\n ...(args.identity.emailVerified !== undefined\n ? { email_verified: args.identity.emailVerified }\n : null),\n ...(args.identity.picture !== undefined ? { picture: args.identity.picture } : null),\n ...(args.identity.phoneNumber !== undefined\n ? { phone_number: args.identity.phoneNumber }\n : null),\n ...(args.identity.phoneNumberVerified !== undefined\n ? { phone_number_verified: args.identity.phoneNumberVerified }\n : null),\n };\n return (await withSpan(\"convex-auth.tokens.sign\", { alg: JWT_ALG }, () =>\n new SignJWT(claims)\n .setProtectedHeader({ alg: JWT_ALG })\n .setIssuedAt()\n .setJti(generateRandomString(TOKEN_JTI_LENGTH, TOKEN_JTI_ALPHABET))\n .setIssuer(getIssuer(config.path))\n .setAudience(\"convex\")\n .setExpirationTime(expirationTime)\n .sign(privateKey),\n )) as AccessToken;\n}\n"],"mappings":";;;;;;;AAUA,MAAM,0BAA0B,MAAO,KAAK;AAC5C,MAAM,mBAAmB;AACzB,MAAM,qBAAqB;AAE3B,IAAI,0BAAmF;AACvF,MAAM,gCAAgB,IAAI,KAAqB;AAE/C,MAAM,UAAU;AAEhB,SAAS,kBAAkB,OAAe;CACxC,MAAM,UAAU,MAAM,MAAM;AAC5B,KAAI,CAAC,QAAQ,SAAS,8BAA8B,CAClD,QAAO;CAGT,MAAM,sBAAsB,QAAQ,QAAQ,QAAQ,KAAK;AACzD,KAAI,oBAAoB,SAAS,KAAK,CACpC,QAAO;CAGT,MAAM,cAAc;CACpB,MAAM,YAAY;CAClB,MAAM,OAAO,QAAQ,QAAQ,aAAa,GAAG,CAAC,QAAQ,WAAW,GAAG,CAAC,MAAM,CAAC,QAAQ,QAAQ,GAAG;AAE/F,KAAI,KAAK,WAAW,EAClB,QAAO;AAIT,QAAO,GAAG,YAAY,IADF,KAAK,MAAM,WAAW,EAAE,KAAK,KAAK,IAAI,KACpB,IAAI;;AAG5C,MAAM,sBAAsB;AAC1B,KAAI,4BAA4B,KAC9B,KAAI;AAEF,4BAA0B,YADd,kBAAkB,eAAe,gBAAgB,CAAC,EACnB,QAAQ,CAAC,OAAO,UAAU;AACnE,6BAA0B;AAC1B,SAAM;IACN;UACK,OAAO;AACd,4BAA0B;AAC1B,QAAM;;AAGV,QAAO;;AAGT,MAAM,aAAa,SAAkB;CACnC,MAAM,MAAM,QAAQ;CACpB,IAAI,SAAS,cAAc,IAAI,IAAI;AACnC,KAAI,WAAW,QAAW;AACxB,WAAS,eAAe,KAAK;AAC7B,gBAAc,IAAI,KAAK,OAAO;;AAEhC,QAAO;;AAGT,IAAI,kBAAkB,YAAY,CAChC,KAAI;AACF,CAAK,eAAe,CAAC,OAAO,QAAQ;AAClC,UAAQ,MAAM,0CAA0C,EAAE,KAAK,CAAC;GAChE;SACK,KAAK;AACZ,SAAQ,MAAM,uDAAuD,EAAE,KAAK,CAAC;;AAGjF,IAAI,kBAAkB,kBAAkB,CACtC,KAAI;AACF,YAAW;SACJ,KAAK;AACZ,SAAQ,MAAM,oCAAoC,EAAE,KAAK,CAAC;;;AAK9D,MAAa,gCAAgC;;;;;;;;;;;;;;;;;;;;AAqB7C,eAAsB,mBAAmB,MAMrB;CAClB,MAAM,aAAa,MAAM,SAAS,iCAAiC,EAAE,KAAK,SAAS,QACjF,eAAe,CAChB;CACD,MAAM,MAAM,IAAI,KAAK,KAAK,KAAK,GAAG,gCAAgC,IAAK;AACvE,QAAO,MAAM,SAAS,iCAAiC,EAAE,KAAK,SAAS,QACrE,IAAI,QAAQ;EACV,KAAK,KAAK;EACV,KAAK;EACL,WAAW;EACX,OAAO,KAAK,OAAO,KAAK,IAAI;EAC5B,WAAW,KAAK;EAChB,GAAI,KAAK,aAAa,SAAY,EAAE,UAAU,KAAK,UAAU,GAAG;EACjE,CAAC,CACC,mBAAmB,EAAE,KAAK,SAAS,CAAC,CACpC,aAAa,CACb,OAAO,qBAAqB,kBAAkB,mBAAmB,CAAC,CAClE,UAAU,KAAK,UAAU,WAAW,CAAC,CACrC,kBAAkB,IAAI,CACtB,KAAK,WAAW,CACpB;;AAGH,IAAI,eAA4D;;;;;;AAOhE,SAAS,YAAkD;AACzD,KAAI,iBAAiB,KACnB,gBAAe,kBAAkB,eAAe,OAAO,CAAC;AAE1D,QAAO;;;;;;;;;;;;;;AAeT,eAAsB,iBACpB,OACA,MACiG;AACjG,KAAI;EACF,MAAM,EAAE,YAAY,MAAM,UAAU,OAAO,WAAW,EAAE;GACtD,QAAQ,MAAM,UAAU,WAAW;GACnC,UAAU;GAIV,YAAY,CAAC,QAAQ;GACrB,gBAAgB;GACjB,CAAC;AACF,MAAK,QAAmC,cAAc,SAAU,QAAO;EACvE,MAAM,SAAS,QAAQ;EACvB,MAAM,WAAY,QAAmC;EACrD,MAAM,QAAS,QAA+B,SAAS;AACvD,MAAI,CAAC,UAAU,CAAC,SAAU,QAAO;EACjC,MAAM,gBAAiB,QAAmC;EAC1D,MAAM,WAAW,OAAO,kBAAkB,WAAW,gBAAgB;AACrE,MAAI,MAAM,aAAa,UAAa,aAAa,KAAK,SAAU,QAAO;AAOvE,SAAO;GAAE;GAAQ;GAAU,QAAQ,QAAQ,MAAM,MAAM,IAAI,GAAG,EAAE;GAAE;GAAU;SACtE;AACN,SAAO;;;;;;;;;;AAWX,eAAsB,cACpB,MAGA,QACsB;CACtB,MAAM,aAAa,MAAM,SAAS,iCAAiC,EAAE,KAAK,SAAS,QACjF,eAAe,CAChB;CACD,MAAM,iBAAiB,IAAI,KAAK,KAAK,KAAK,IAAI,OAAO,KAAK,cAAc,yBAAyB;CACjG,MAAM,SAAS;EACb,KAAK,KAAK,SAAS;EACnB,KAAK,KAAK,SAAS;EACnB,GAAI,KAAK,SAAS,SAAS,SAAY,EAAE,MAAM,KAAK,SAAS,MAAM,GAAG;EACtE,GAAI,KAAK,SAAS,UAAU,SAAY,EAAE,OAAO,KAAK,SAAS,OAAO,GAAG;EACzE,GAAI,KAAK,SAAS,kBAAkB,SAChC,EAAE,gBAAgB,KAAK,SAAS,eAAe,GAC/C;EACJ,GAAI,KAAK,SAAS,YAAY,SAAY,EAAE,SAAS,KAAK,SAAS,SAAS,GAAG;EAC/E,GAAI,KAAK,SAAS,gBAAgB,SAC9B,EAAE,cAAc,KAAK,SAAS,aAAa,GAC3C;EACJ,GAAI,KAAK,SAAS,wBAAwB,SACtC,EAAE,uBAAuB,KAAK,SAAS,qBAAqB,GAC5D;EACL;AACD,QAAQ,MAAM,SAAS,2BAA2B,EAAE,KAAK,SAAS,QAChE,IAAI,QAAQ,OAAO,CAChB,mBAAmB,EAAE,KAAK,SAAS,CAAC,CACpC,aAAa,CACb,OAAO,qBAAqB,kBAAkB,mBAAmB,CAAC,CAClE,UAAU,UAAU,OAAO,KAAK,CAAC,CACjC,YAAY,SAAS,CACrB,kBAAkB,eAAe,CACjC,KAAK,WAAW,CACpB"}
|
|
1
|
+
{"version":3,"file":"tokens.js","names":[],"sources":["../../src/server/tokens.ts"],"sourcesContent":["import { SignJWT, createLocalJWKSet, importPKCS8, jwtVerify } from \"jose\";\n\nimport type { AccessToken } from \"../shared/brand\";\nimport { envOptionalString, requireAuthKey } from \"./env\";\nimport { generateRandomString } from \"./random\";\nimport { ConvexAuthConfig } from \"./types\";\nimport type { SessionTokenIdentityClaims } from \"./types\";\nimport { authUrlFromEnv } from \"./url\";\nimport { withSpan } from \"./utils/span\";\n\nconst DEFAULT_JWT_DURATION_MS = 1000 * 60 * 60;\nconst TOKEN_JTI_LENGTH = 24;\nconst TOKEN_JTI_ALPHABET = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\";\n\nlet cachedPrivateKeyPromise: Promise<Awaited<ReturnType<typeof importPKCS8>>> | null = null;\nconst cachedIssuers = new Map<string, string>();\n\nconst JWT_ALG = \"EdDSA\" as const;\n\nfunction normalizePkcs8Pem(value: string) {\n const trimmed = value.trim();\n if (!trimmed.includes(\"-----BEGIN PRIVATE KEY-----\")) {\n return trimmed;\n }\n\n const withEscapedNewlines = trimmed.replace(/\\\\n/g, \"\\n\");\n if (withEscapedNewlines.includes(\"\\n\")) {\n return withEscapedNewlines;\n }\n\n const beginMarker = \"-----BEGIN PRIVATE KEY-----\";\n const endMarker = \"-----END PRIVATE KEY-----\";\n const body = trimmed.replace(beginMarker, \"\").replace(endMarker, \"\").trim().replace(/\\s+/g, \"\");\n\n if (body.length === 0) {\n return trimmed;\n }\n\n const wrappedBody = body.match(/.{1,64}/g)?.join(\"\\n\") ?? body;\n return `${beginMarker}\\n${wrappedBody}\\n${endMarker}`;\n}\n\nconst getPrivateKey = () => {\n if (cachedPrivateKeyPromise === null) {\n try {\n const pem = normalizePkcs8Pem(requireAuthKey(\"jwtPrivateKey\"));\n cachedPrivateKeyPromise = importPKCS8(pem, JWT_ALG).catch((error) => {\n cachedPrivateKeyPromise = null;\n throw error;\n });\n } catch (error) {\n cachedPrivateKeyPromise = null;\n throw error;\n }\n }\n return cachedPrivateKeyPromise;\n};\n\nconst getIssuer = (path?: string) => {\n const key = path ?? \"\";\n let issuer = cachedIssuers.get(key);\n if (issuer === undefined) {\n issuer = authUrlFromEnv(path);\n cachedIssuers.set(key, issuer);\n }\n return issuer;\n};\n\nif (envOptionalString(\"AUTH_KEYS\")) {\n try {\n void getPrivateKey().catch((err) => {\n console.error(\"[auth] JWT private key pre-warm failed\", { err });\n });\n } catch (err) {\n console.error(\"[auth] JWT private key pre-warm threw synchronously\", { err });\n }\n}\nif (envOptionalString(\"CONVEX_SITE_URL\")) {\n try {\n getIssuer();\n } catch (err) {\n console.error(\"[auth] JWT issuer pre-warm threw\", { err });\n }\n}\n\n/** Lifetime of an issued OAuth access token, in seconds. Shared with the token endpoint's `expires_in`. */\nexport const OAUTH_ACCESS_TOKEN_DURATION_S = 900;\n\n/**\n * Sign an OAuth access token (EdDSA) for the given user, client, and scopes.\n *\n * The `aud` claim is `\"convex\"` so the token is a valid Convex identity\n * (`applicationID: \"convex\"`) — load-bearing: MCP tools run the user's queries\n * via `ctx.runQuery`, which requires Convex's identity layer to accept this\n * token. For that reason the access token must look like an ID-token-shaped JWT:\n * it carries NO `typ: \"at+jwt\"` header (Convex's validator rejects that RFC 9068\n * type), and is instead marked as an access token by a positive `token_use:\n * \"access\"` claim, which `verifyOAuthToken` checks (alongside the `client_id`\n * claim) to tell it apart from a session token. We don't issue OIDC ID tokens,\n * so there is no id-vs-access confusion for the `at+jwt` typ to guard against.\n *\n * When an RFC 8707 `resource` is supplied the token carries a `resource` claim\n * binding it to that protected resource; an MCP endpoint rejects tokens whose\n * `resource` does not match its own canonical resource.\n *\n * @internal\n */\nexport async function generateOAuthToken(args: {\n userId: string;\n clientId: string;\n scopes: string[];\n issuer?: string;\n resource?: string;\n}): Promise<string> {\n const privateKey = await withSpan(\"convex-auth.tokens.import-key\", { alg: JWT_ALG }, () =>\n getPrivateKey(),\n );\n const exp = new Date(Date.now() + OAUTH_ACCESS_TOKEN_DURATION_S * 1000);\n return await withSpan(\"convex-auth.tokens.sign-oauth\", { alg: JWT_ALG }, () =>\n new SignJWT({\n sub: args.userId,\n aud: \"convex\",\n token_use: \"access\",\n scope: args.scopes.join(\" \"),\n client_id: args.clientId,\n ...(args.resource !== undefined ? { resource: args.resource } : null),\n })\n .setProtectedHeader({ alg: JWT_ALG })\n .setIssuedAt()\n .setJti(generateRandomString(TOKEN_JTI_LENGTH, TOKEN_JTI_ALPHABET))\n .setIssuer(args.issuer ?? getIssuer())\n .setExpirationTime(exp)\n .sign(privateKey),\n );\n}\n\nlet cachedKeySet: ReturnType<typeof createLocalJWKSet> | null = null;\n\n/**\n * The local JWKS keyset, built once and reused. `AUTH_KEYS` is static per\n * deployment, so caching also preserves jose's internal imported-key cache\n * across requests rather than re-importing keys on every verification.\n */\nfunction getJwkSet(): ReturnType<typeof createLocalJWKSet> {\n if (cachedKeySet === null) {\n cachedKeySet = createLocalJWKSet(requireAuthKey(\"jwks\"));\n }\n return cachedKeySet;\n}\n\n/**\n * Verify an OAuth access token against the local JWKS and issuer.\n *\n * When `opts.resource` is supplied, the token's RFC 8707 `resource` claim must\n * equal it exactly — a token bound to a different (or no) resource is rejected.\n * This is how an MCP endpoint enforces that a token was issued for *it*.\n *\n * @returns The token's user, client, scopes, and bound `resource`, or `null` if\n * verification fails, the token is not an OAuth access token, or the resource\n * assertion does not hold.\n * @internal\n */\nexport async function verifyOAuthToken(\n token: string,\n opts?: { issuer?: string; resource?: string },\n): Promise<{ userId: string; clientId: string; scopes: string[]; resource: string | null } | null> {\n try {\n const { payload } = await jwtVerify(token, getJwkSet(), {\n issuer: opts?.issuer ?? getIssuer(),\n audience: \"convex\",\n // Defense-in-depth: pin the accepted JWS algorithm so a token can only be\n // verified under EdDSA, never coerced to a different `alg` (algorithm\n // substitution / confusion hardening).\n algorithms: [JWT_ALG],\n clockTolerance: 30,\n });\n if ((payload as { token_use?: string }).token_use !== \"access\") return null;\n const userId = payload.sub;\n const clientId = (payload as { client_id?: string }).client_id;\n const scope = (payload as { scope?: string }).scope ?? \"\";\n if (!userId || !clientId) return null;\n const resourceClaim = (payload as { resource?: unknown }).resource;\n const resource = typeof resourceClaim === \"string\" ? resourceClaim : null;\n if (opts?.resource !== undefined && resource !== opts.resource) return null;\n // NOTE: the returned `scopes` are ADVISORY, not a Convex-enforced capability\n // boundary. Because `aud` is \"convex\", Convex's identity layer accepts this\n // token as the user's full identity — any query/mutation the user could run,\n // a client bearing this token can run via `ctx.runQuery`/`ctx.runMutation`,\n // regardless of `scope`. Integrators MUST enforce these scopes app-side\n // (e.g. inside each MCP tool handler) for them to actually restrict access.\n return { userId, clientId, scopes: scope ? scope.split(\" \") : [], resource };\n } catch {\n return null;\n }\n}\n\n/**\n * Sign a session access token (EdDSA) carrying the identity claims, audienced to Convex.\n *\n * Expiry is the earlier of `config.jwt.durationMs` from now (defaulting to one\n * hour) and the durable session's absolute expiration.\n *\n * @internal\n */\nexport async function generateToken(\n args: {\n identity: SessionTokenIdentityClaims;\n /** Absolute durable-session expiry in milliseconds since Unix epoch. */\n sessionExpirationTime: number;\n },\n config: ConvexAuthConfig,\n): Promise<AccessToken> {\n const privateKey = await withSpan(\"convex-auth.tokens.import-key\", { alg: JWT_ALG }, () =>\n getPrivateKey(),\n );\n const jwtExpirationTime = Date.now() + (config.jwt?.durationMs ?? DEFAULT_JWT_DURATION_MS);\n const expirationTime = new Date(Math.min(jwtExpirationTime, args.sessionExpirationTime));\n const claims = {\n sub: args.identity.subject,\n sid: args.identity.sessionId,\n session_epoch: args.identity.sessionEpoch,\n ...(args.identity.name !== undefined ? { name: args.identity.name } : null),\n ...(args.identity.email !== undefined ? { email: args.identity.email } : null),\n ...(args.identity.emailVerified !== undefined\n ? { email_verified: args.identity.emailVerified }\n : null),\n ...(args.identity.picture !== undefined ? { picture: args.identity.picture } : null),\n ...(args.identity.phoneNumber !== undefined\n ? { phone_number: args.identity.phoneNumber }\n : null),\n ...(args.identity.phoneNumberVerified !== undefined\n ? { phone_number_verified: args.identity.phoneNumberVerified }\n : null),\n };\n return (await withSpan(\"convex-auth.tokens.sign\", { alg: JWT_ALG }, () =>\n new SignJWT(claims)\n .setProtectedHeader({ alg: JWT_ALG })\n .setIssuedAt()\n .setJti(generateRandomString(TOKEN_JTI_LENGTH, TOKEN_JTI_ALPHABET))\n .setIssuer(getIssuer(config.path))\n .setAudience(\"convex\")\n .setExpirationTime(expirationTime)\n .sign(privateKey),\n )) as AccessToken;\n}\n"],"mappings":"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"}
|
package/dist/server/totp.js
CHANGED
|
@@ -3,7 +3,7 @@ import { getAuthenticatedUserIdOrNull } from "./identity/claims.js";
|
|
|
3
3
|
import { queueAuthEvent } from "./events.js";
|
|
4
4
|
import { encodeRefreshToken, refreshTokenExpirationTime } from "./token/refresh.js";
|
|
5
5
|
import { setActiveSpanAttributes } from "./utils/span.js";
|
|
6
|
-
import { buildSessionIdentity, finalizeSessionIssuance,
|
|
6
|
+
import { buildSessionIdentity, finalizeSessionIssuance, getAuthSessionReplacement, sessionExpirationTime } from "./session/lifecycle.js";
|
|
7
7
|
import { authFlowError } from "../shared/errors.js";
|
|
8
8
|
import { toConvexError } from "./errors.js";
|
|
9
9
|
import { maxSignInAttempts } from "./limits.js";
|
|
@@ -194,13 +194,13 @@ const handleTotp = async (ctx, provider, args) => {
|
|
|
194
194
|
if (begun.status === "not_found") throw convexError(input.intent === "enrollment" ? ErrorCode.TOTP_NOT_FOUND : ErrorCode.TOTP_NO_ENROLLMENT, input.intent === "enrollment" ? "TOTP enrollment not found." : "No verified TOTP enrollment found.");
|
|
195
195
|
if (begun.status !== "ready") throw convexError(ErrorCode.INTERNAL_ERROR, "Unexpected TOTP verification state.");
|
|
196
196
|
if (!verifyTOTPWithGracePeriod(await decryptTotpSecret(begun.factor.secret), begun.factor.period, begun.factor.digits, input.code, 30)) throw convexError(ErrorCode.TOTP_INVALID_CODE, "Invalid TOTP code.");
|
|
197
|
-
const
|
|
197
|
+
const replaceSession = await getAuthSessionReplacement(ctx);
|
|
198
198
|
const completed = await ctx.runMutation(ctx.auth.config.component.factor.totp.completeVerification, {
|
|
199
199
|
verifierId: input.verifier,
|
|
200
200
|
intent: input.intent,
|
|
201
201
|
authenticatedUserId: input.authenticatedUserId,
|
|
202
202
|
totpId: input.totpId,
|
|
203
|
-
|
|
203
|
+
replaceSession,
|
|
204
204
|
sessionExpirationTime: sessionExpirationTime(ctx.auth.config),
|
|
205
205
|
refreshTokenExpirationTime: refreshTokenExpirationTime(ctx.auth.config),
|
|
206
206
|
now: Date.now()
|
|
@@ -266,6 +266,7 @@ const handleTotp = async (ctx, provider, args) => {
|
|
|
266
266
|
session: await finalizeSessionIssuance(ctx.auth.config, {
|
|
267
267
|
userId,
|
|
268
268
|
sessionId,
|
|
269
|
+
sessionExpirationTime: completed.sessionExpirationTime,
|
|
269
270
|
identity: buildSessionIdentity(userId, sessionId, completed.user),
|
|
270
271
|
refreshToken: encodeRefreshToken(completed.refreshTokenId, sessionId)
|
|
271
272
|
})
|
package/dist/server/totp.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"totp.js","names":[],"sources":["../../src/server/totp.ts"],"sourcesContent":["/**\n * Server-side TOTP ceremony logic for two-factor authentication.\n *\n * Two single-word flows:\n *\n * - `setup` — generate a TOTP secret and `otpauth://` URI for enrollment.\n * - `verify` — consume a TOTP code. Auto-detects:\n * - first-time enrollment confirmation (caller passes `totpId`), or\n * - 2FA sign-in challenge (no `totpId`).\n */\n\nimport {\n decodeBase64urlIgnorePadding,\n encodeBase32LowerCaseNoPadding,\n encodeBase64urlNoPadding,\n} from \"@oslojs/encoding\";\nimport { createTOTPKeyURI, verifyTOTPWithGracePeriod } from \"@oslojs/otp\";\nimport { ConvexError, GenericId } from \"convex/values\";\n\nimport { ErrorCode } from \"../shared/codes\";\nimport { authFlowError } from \"../shared/errors\";\nimport type { AuthTokens, SignInSessionResult, SignInTotpSetupResult } from \"../shared/results\";\nimport type { AuthErrorData } from \"./errors\";\nimport { toConvexError } from \"./errors\";\nimport { queueAuthEvent } from \"./events\";\nimport { getAuthenticatedUserIdOrNull } from \"./identity/claims\";\nimport { maxSignInAttempts } from \"./limits\";\nimport { decryptSecret, encryptSecret } from \"./secret\";\nimport {\n buildSessionIdentity,\n finalizeSessionIssuance,\n getAuthSessionId,\n sessionExpirationTime,\n} from \"./session/lifecycle\";\nimport { encodeRefreshToken, refreshTokenExpirationTime } from \"./token/refresh\";\nimport { buildKnownSignInIdentityAttributes } from \"./telemetry\";\nimport {\n AuthDataModel,\n type CrossComponentUserDoc,\n GenericActionCtxWithAuthConfig,\n SessionInfo,\n TotpProviderConfig,\n} from \"./types\";\nimport { setActiveSpanAttributes } from \"./utils/span\";\n\ntype EnrichedActionCtx = GenericActionCtxWithAuthConfig<AuthDataModel>;\n\ntype TotpResult =\n | SignInSessionResult<SessionInfo<AuthTokens | null> | null>\n | SignInTotpSetupResult;\n\nconst TOTP_FLOWS = { setup: true, verify: true } as const;\n\ntype TotpFlow = keyof typeof TOTP_FLOWS;\n\nfunction isTotpFlow(value: unknown): value is TotpFlow {\n return typeof value === \"string\" && Object.hasOwn(TOTP_FLOWS, value);\n}\n\ntype TotpDispatch =\n | { flow: \"setup\"; params: Record<string, unknown> }\n /** Enrollment confirmation — `totpId` distinguishes from a 2FA challenge. */\n | { flow: \"verify\"; code: string; verifier: string; totpId: string; intent: \"enrollment\" }\n /** 2FA challenge during sign-in. */\n | { flow: \"verify\"; code: string; verifier: string; totpId?: undefined; intent: \"challenge\" };\n\nconst convexError = (code: ErrorCode, message: string) =>\n toConvexError(authFlowError(code, message));\n\nconst asConvexError = (\n error: unknown,\n code: ErrorCode,\n message: string,\n): ConvexError<AuthErrorData> =>\n error instanceof ConvexError\n ? error\n : error instanceof Error\n ? toConvexError(authFlowError(code, error.message || message))\n : convexError(code, message);\n\n/**\n * Encrypt a raw TOTP secret for storage. The secret bytes are base64url-encoded\n * and sealed with the server's {@link encryptSecret} (AES-GCM); the resulting\n * ciphertext string is stored UTF-8-encoded in the `TotpFactor.secret` bytes\n * field. Runs server-side because component functions cannot read\n * the secret-encryption key from `AUTH_KEYS`.\n */\nasync function encryptTotpSecret(secret: Uint8Array): Promise<ArrayBuffer> {\n const ciphertext = await encryptSecret(encodeBase64urlNoPadding(secret));\n const encoded = new TextEncoder().encode(ciphertext);\n return encoded.buffer.slice(\n encoded.byteOffset,\n encoded.byteOffset + encoded.byteLength,\n ) as ArrayBuffer;\n}\n\n/**\n * Reverse {@link encryptTotpSecret}: decode the stored ciphertext bytes, decrypt\n * to the base64url secret, and return the raw secret bytes for verification.\n */\nasync function decryptTotpSecret(stored: ArrayBuffer): Promise<Uint8Array> {\n const ciphertext = new TextDecoder().decode(stored);\n const secretB64 = await decryptSecret(ciphertext);\n return decodeBase64urlIgnorePadding(secretB64);\n}\n\nfunction resolveTotpFlow(params: Record<string, unknown>): TotpFlow {\n const flow = params.flow;\n if (isTotpFlow(flow)) {\n return flow;\n }\n throw convexError(\n ErrorCode.TOTP_MISSING_FLOW,\n \"Missing `flow` parameter. Expected one of: \" + Object.keys(TOTP_FLOWS).join(\", \"),\n );\n}\n\nfunction requireTotpVerifier(verifier: string | undefined): string {\n if (verifier != null) {\n return verifier;\n }\n throw convexError(ErrorCode.TOTP_MISSING_VERIFIER, \"Missing verifier for TOTP operation.\");\n}\n\nfunction requireTotpCode(params: Record<string, unknown>): string {\n if (typeof params.code === \"string\") {\n return params.code;\n }\n throw convexError(ErrorCode.TOTP_MISSING_CODE, \"Missing TOTP code.\");\n}\n\nfunction resolveTotpDispatch(\n params: Record<string, unknown>,\n verifier: string | undefined,\n): TotpDispatch {\n const flow = resolveTotpFlow(params);\n if (flow === \"setup\") {\n return { flow, params };\n }\n const resolvedVerifier = requireTotpVerifier(verifier);\n const code = requireTotpCode(params);\n if (typeof params.totpId === \"string\" && params.totpId.length > 0) {\n return {\n flow,\n code,\n totpId: params.totpId,\n verifier: resolvedVerifier,\n intent: \"enrollment\",\n };\n }\n return {\n flow,\n code,\n verifier: resolvedVerifier,\n intent: \"challenge\",\n };\n}\n\nasync function requireAuthenticatedUserId(ctx: EnrichedActionCtx): Promise<string> {\n try {\n const userId = await getAuthenticatedUserIdOrNull(ctx);\n if (userId === null) {\n throw convexError(\n ErrorCode.TOTP_AUTH_REQUIRED,\n \"Sign in first, then set up two-factor authentication.\",\n );\n }\n return userId;\n } catch (error) {\n if (error instanceof ConvexError) {\n throw error;\n }\n throw asConvexError(error, ErrorCode.INTERNAL_ERROR, String(error));\n }\n}\n\n/**\n * Drive the TOTP provider's `setup` / `verify` flow.\n *\n * `setup` issues an enrollment secret and `otpauth://` URI; `verify` either\n * confirms a first-time enrollment (when `params.totpId` is present) or\n * completes a 2FA challenge during sign-in.\n *\n * @internal\n */\nexport const handleTotp = async (\n ctx: EnrichedActionCtx,\n provider: TotpProviderConfig,\n args: { params?: Record<string, unknown>; verifier?: string },\n): Promise<TotpResult> => {\n const params = args.params ?? {};\n const dispatch = resolveTotpDispatch(params, args.verifier);\n\n async function setup(setupParams: Record<string, unknown>): Promise<TotpResult> {\n const userId = await requireAuthenticatedUserId(ctx);\n const secret = new Uint8Array(20);\n crypto.getRandomValues(secret);\n const base32Secret = encodeBase32LowerCaseNoPadding(secret);\n let enrollment: { user: CrossComponentUserDoc; totpId: string; verifierId: string };\n try {\n enrollment = (await ctx.runMutation(ctx.auth.config.component.factor.totp.createEnrollment, {\n userId,\n secret: await encryptTotpSecret(secret),\n digits: provider.options.digits,\n period: provider.options.period,\n name: typeof setupParams.name === \"string\" ? setupParams.name : undefined,\n createdAt: Date.now(),\n })) as typeof enrollment;\n } catch (error) {\n throw asConvexError(error, ErrorCode.INTERNAL_ERROR, `TOTP setup failed: ${String(error)}`);\n }\n const accountName =\n typeof setupParams.accountName === \"string\" && setupParams.accountName.length > 0\n ? setupParams.accountName\n : (enrollment.user.email ?? \"user\");\n const uri = createTOTPKeyURI(\n provider.options.issuer,\n accountName,\n secret,\n provider.options.period,\n provider.options.digits,\n );\n\n return {\n kind: \"totpSetup\" as const,\n totpSetup: { uri, secret: base32Secret, totpId: enrollment.totpId },\n verifier: enrollment.verifierId,\n };\n }\n\n /**\n * `verify` with `totpId`: completes a first-time enrollment after `setup`.\n * Marks the TOTP factor as verified and signs the user in.\n */\n async function confirmEnrollment(\n code: string,\n totpId: string,\n verifier: string,\n ): Promise<TotpResult> {\n const userId = await requireAuthenticatedUserId(ctx);\n const result = await verifyTotp({\n code,\n verifier,\n intent: \"enrollment\",\n authenticatedUserId: userId,\n totpId,\n });\n await queueAuthEvent(ctx, ctx.auth.config, {\n kind: \"totp.enrolled\",\n actor: { type: \"user\", id: userId },\n subject: { type: \"totp\", id: totpId },\n targets: [{ kind: \"user\", id: userId }],\n outcome: \"success\",\n data: { totpId },\n });\n return result;\n }\n\n /**\n * `verify` without `totpId`: completes a 2FA challenge during sign-in.\n * Looks up the user's verified TOTP factor, validates the code, signs in.\n */\n async function verifyChallenge(code: string, verifier: string): Promise<TotpResult> {\n return await verifyTotp({ code, verifier, intent: \"challenge\" });\n }\n\n async function verifyTotp(input: {\n code: string;\n verifier: string;\n intent: \"enrollment\" | \"challenge\";\n authenticatedUserId?: string;\n totpId?: string;\n }): Promise<TotpResult> {\n const begun = (await ctx.runMutation(ctx.auth.config.component.factor.totp.beginVerification, {\n verifierId: input.verifier,\n intent: input.intent,\n authenticatedUserId: input.authenticatedUserId,\n totpId: input.totpId,\n maxAttemptsPerHour: maxSignInAttempts(ctx.auth.config),\n })) as\n | { status: \"invalid_verifier\" | \"limited\" | \"not_found\" | \"already_verified\" }\n | {\n status: \"ready\";\n userId: string;\n factor: {\n _id: string;\n secret: ArrayBuffer;\n period: number;\n digits: number;\n };\n };\n\n if (begun.status === \"invalid_verifier\") {\n throw convexError(ErrorCode.TOTP_INVALID_VERIFIER, \"Invalid or expired TOTP verifier.\");\n }\n if (begun.status === \"limited\") {\n throw convexError(ErrorCode.RATE_LIMITED, \"Too many TOTP attempts. Try again later.\");\n }\n if (begun.status === \"already_verified\") {\n throw convexError(ErrorCode.TOTP_ALREADY_VERIFIED, \"TOTP enrollment is already verified.\");\n }\n if (begun.status === \"not_found\") {\n throw convexError(\n input.intent === \"enrollment\" ? ErrorCode.TOTP_NOT_FOUND : ErrorCode.TOTP_NO_ENROLLMENT,\n input.intent === \"enrollment\"\n ? \"TOTP enrollment not found.\"\n : \"No verified TOTP enrollment found.\",\n );\n }\n if (begun.status !== \"ready\") {\n throw convexError(ErrorCode.INTERNAL_ERROR, \"Unexpected TOTP verification state.\");\n }\n\n const secret = await decryptTotpSecret(begun.factor.secret);\n if (\n !verifyTOTPWithGracePeriod(secret, begun.factor.period, begun.factor.digits, input.code, 30)\n ) {\n throw convexError(ErrorCode.TOTP_INVALID_CODE, \"Invalid TOTP code.\");\n }\n\n const replaceSessionId = (await getAuthSessionId(ctx)) ?? undefined;\n const completed = (await ctx.runMutation(\n ctx.auth.config.component.factor.totp.completeVerification,\n {\n verifierId: input.verifier,\n intent: input.intent,\n authenticatedUserId: input.authenticatedUserId,\n totpId: input.totpId,\n replaceSessionId,\n sessionExpirationTime: sessionExpirationTime(ctx.auth.config),\n refreshTokenExpirationTime: refreshTokenExpirationTime(ctx.auth.config),\n now: Date.now(),\n },\n )) as\n | { status: \"rejected\" }\n | {\n status: \"accepted\";\n user: CrossComponentUserDoc;\n factorId: string;\n sessionId: string;\n refreshTokenId: string;\n replacedSessionId?: string;\n };\n if (completed.status !== \"accepted\") {\n throw convexError(ErrorCode.TOTP_INVALID_VERIFIER, \"Invalid or expired TOTP verifier.\");\n }\n\n const userId = completed.user._id as GenericId<\"User\">;\n const sessionId = completed.sessionId as GenericId<\"Session\">;\n setActiveSpanAttributes({\n \"auth.signin.result\": \"success\",\n ...buildKnownSignInIdentityAttributes(\n ctx.auth.config,\n { userId, sessionId },\n completed.user.email,\n ),\n });\n if (completed.replacedSessionId !== undefined) {\n const replacedSessionId = completed.replacedSessionId as GenericId<\"Session\">;\n await queueAuthEvent(ctx, ctx.auth.config, {\n kind: \"session.invalidated\",\n actor: { type: \"system\" },\n subject: { type: \"session\", id: replacedSessionId },\n targets: [\n { kind: \"user\", id: userId },\n { kind: \"session\", id: replacedSessionId },\n ],\n outcome: \"success\",\n data: { userId, reason: \"replaced\" },\n });\n }\n await queueAuthEvent(ctx, ctx.auth.config, {\n kind: \"session.signed_in\",\n actor: { type: \"user\", id: userId },\n subject: { type: \"session\", id: sessionId },\n targets: [\n { kind: \"user\", id: userId },\n { kind: \"session\", id: sessionId },\n ],\n outcome: \"success\",\n data: { provider: \"session\", method: provider.id },\n });\n const session = await finalizeSessionIssuance(ctx.auth.config, {\n userId,\n sessionId,\n identity: buildSessionIdentity(userId, sessionId, completed.user),\n refreshToken: encodeRefreshToken(\n completed.refreshTokenId as GenericId<\"RefreshToken\">,\n sessionId,\n ),\n });\n return { kind: \"signedIn\" as const, session };\n }\n\n if (dispatch.flow === \"setup\") {\n return await setup(dispatch.params);\n }\n if (dispatch.intent === \"enrollment\") {\n return await confirmEnrollment(dispatch.code, dispatch.totpId, dispatch.verifier);\n }\n return await verifyChallenge(dispatch.code, dispatch.verifier);\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"totp.js","names":[],"sources":["../../src/server/totp.ts"],"sourcesContent":["/**\n * Server-side TOTP ceremony logic for two-factor authentication.\n *\n * Two single-word flows:\n *\n * - `setup` — generate a TOTP secret and `otpauth://` URI for enrollment.\n * - `verify` — consume a TOTP code. Auto-detects:\n * - first-time enrollment confirmation (caller passes `totpId`), or\n * - 2FA sign-in challenge (no `totpId`).\n */\n\nimport {\n decodeBase64urlIgnorePadding,\n encodeBase32LowerCaseNoPadding,\n encodeBase64urlNoPadding,\n} from \"@oslojs/encoding\";\nimport { createTOTPKeyURI, verifyTOTPWithGracePeriod } from \"@oslojs/otp\";\nimport { ConvexError, GenericId } from \"convex/values\";\n\nimport { ErrorCode } from \"../shared/codes\";\nimport { authFlowError } from \"../shared/errors\";\nimport type { AuthTokens, SignInSessionResult, SignInTotpSetupResult } from \"../shared/results\";\nimport type { AuthErrorData } from \"./errors\";\nimport { toConvexError } from \"./errors\";\nimport { queueAuthEvent } from \"./events\";\nimport { getAuthenticatedUserIdOrNull } from \"./identity/claims\";\nimport { maxSignInAttempts } from \"./limits\";\nimport { decryptSecret, encryptSecret } from \"./secret\";\nimport {\n buildSessionIdentity,\n finalizeSessionIssuance,\n getAuthSessionReplacement,\n sessionExpirationTime,\n} from \"./session/lifecycle\";\nimport { encodeRefreshToken, refreshTokenExpirationTime } from \"./token/refresh\";\nimport { buildKnownSignInIdentityAttributes } from \"./telemetry\";\nimport {\n AuthDataModel,\n type CrossComponentUserDoc,\n GenericActionCtxWithAuthConfig,\n SessionInfo,\n TotpProviderConfig,\n} from \"./types\";\nimport { setActiveSpanAttributes } from \"./utils/span\";\n\ntype EnrichedActionCtx = GenericActionCtxWithAuthConfig<AuthDataModel>;\n\ntype TotpResult =\n | SignInSessionResult<SessionInfo<AuthTokens | null> | null>\n | SignInTotpSetupResult;\n\nconst TOTP_FLOWS = { setup: true, verify: true } as const;\n\ntype TotpFlow = keyof typeof TOTP_FLOWS;\n\nfunction isTotpFlow(value: unknown): value is TotpFlow {\n return typeof value === \"string\" && Object.hasOwn(TOTP_FLOWS, value);\n}\n\ntype TotpDispatch =\n | { flow: \"setup\"; params: Record<string, unknown> }\n /** Enrollment confirmation — `totpId` distinguishes from a 2FA challenge. */\n | { flow: \"verify\"; code: string; verifier: string; totpId: string; intent: \"enrollment\" }\n /** 2FA challenge during sign-in. */\n | { flow: \"verify\"; code: string; verifier: string; totpId?: undefined; intent: \"challenge\" };\n\nconst convexError = (code: ErrorCode, message: string) =>\n toConvexError(authFlowError(code, message));\n\nconst asConvexError = (\n error: unknown,\n code: ErrorCode,\n message: string,\n): ConvexError<AuthErrorData> =>\n error instanceof ConvexError\n ? error\n : error instanceof Error\n ? toConvexError(authFlowError(code, error.message || message))\n : convexError(code, message);\n\n/**\n * Encrypt a raw TOTP secret for storage. The secret bytes are base64url-encoded\n * and sealed with the server's {@link encryptSecret} (AES-GCM); the resulting\n * ciphertext string is stored UTF-8-encoded in the `TotpFactor.secret` bytes\n * field. Runs server-side because component functions cannot read\n * the secret-encryption key from `AUTH_KEYS`.\n */\nasync function encryptTotpSecret(secret: Uint8Array): Promise<ArrayBuffer> {\n const ciphertext = await encryptSecret(encodeBase64urlNoPadding(secret));\n const encoded = new TextEncoder().encode(ciphertext);\n return encoded.buffer.slice(\n encoded.byteOffset,\n encoded.byteOffset + encoded.byteLength,\n ) as ArrayBuffer;\n}\n\n/**\n * Reverse {@link encryptTotpSecret}: decode the stored ciphertext bytes, decrypt\n * to the base64url secret, and return the raw secret bytes for verification.\n */\nasync function decryptTotpSecret(stored: ArrayBuffer): Promise<Uint8Array> {\n const ciphertext = new TextDecoder().decode(stored);\n const secretB64 = await decryptSecret(ciphertext);\n return decodeBase64urlIgnorePadding(secretB64);\n}\n\nfunction resolveTotpFlow(params: Record<string, unknown>): TotpFlow {\n const flow = params.flow;\n if (isTotpFlow(flow)) {\n return flow;\n }\n throw convexError(\n ErrorCode.TOTP_MISSING_FLOW,\n \"Missing `flow` parameter. Expected one of: \" + Object.keys(TOTP_FLOWS).join(\", \"),\n );\n}\n\nfunction requireTotpVerifier(verifier: string | undefined): string {\n if (verifier != null) {\n return verifier;\n }\n throw convexError(ErrorCode.TOTP_MISSING_VERIFIER, \"Missing verifier for TOTP operation.\");\n}\n\nfunction requireTotpCode(params: Record<string, unknown>): string {\n if (typeof params.code === \"string\") {\n return params.code;\n }\n throw convexError(ErrorCode.TOTP_MISSING_CODE, \"Missing TOTP code.\");\n}\n\nfunction resolveTotpDispatch(\n params: Record<string, unknown>,\n verifier: string | undefined,\n): TotpDispatch {\n const flow = resolveTotpFlow(params);\n if (flow === \"setup\") {\n return { flow, params };\n }\n const resolvedVerifier = requireTotpVerifier(verifier);\n const code = requireTotpCode(params);\n if (typeof params.totpId === \"string\" && params.totpId.length > 0) {\n return {\n flow,\n code,\n totpId: params.totpId,\n verifier: resolvedVerifier,\n intent: \"enrollment\",\n };\n }\n return {\n flow,\n code,\n verifier: resolvedVerifier,\n intent: \"challenge\",\n };\n}\n\nasync function requireAuthenticatedUserId(ctx: EnrichedActionCtx): Promise<string> {\n try {\n const userId = await getAuthenticatedUserIdOrNull(ctx);\n if (userId === null) {\n throw convexError(\n ErrorCode.TOTP_AUTH_REQUIRED,\n \"Sign in first, then set up two-factor authentication.\",\n );\n }\n return userId;\n } catch (error) {\n if (error instanceof ConvexError) {\n throw error;\n }\n throw asConvexError(error, ErrorCode.INTERNAL_ERROR, String(error));\n }\n}\n\n/**\n * Drive the TOTP provider's `setup` / `verify` flow.\n *\n * `setup` issues an enrollment secret and `otpauth://` URI; `verify` either\n * confirms a first-time enrollment (when `params.totpId` is present) or\n * completes a 2FA challenge during sign-in.\n *\n * @internal\n */\nexport const handleTotp = async (\n ctx: EnrichedActionCtx,\n provider: TotpProviderConfig,\n args: { params?: Record<string, unknown>; verifier?: string },\n): Promise<TotpResult> => {\n const params = args.params ?? {};\n const dispatch = resolveTotpDispatch(params, args.verifier);\n\n async function setup(setupParams: Record<string, unknown>): Promise<TotpResult> {\n const userId = await requireAuthenticatedUserId(ctx);\n const secret = new Uint8Array(20);\n crypto.getRandomValues(secret);\n const base32Secret = encodeBase32LowerCaseNoPadding(secret);\n let enrollment: { user: CrossComponentUserDoc; totpId: string; verifierId: string };\n try {\n enrollment = (await ctx.runMutation(ctx.auth.config.component.factor.totp.createEnrollment, {\n userId,\n secret: await encryptTotpSecret(secret),\n digits: provider.options.digits,\n period: provider.options.period,\n name: typeof setupParams.name === \"string\" ? setupParams.name : undefined,\n createdAt: Date.now(),\n })) as typeof enrollment;\n } catch (error) {\n throw asConvexError(error, ErrorCode.INTERNAL_ERROR, `TOTP setup failed: ${String(error)}`);\n }\n const accountName =\n typeof setupParams.accountName === \"string\" && setupParams.accountName.length > 0\n ? setupParams.accountName\n : (enrollment.user.email ?? \"user\");\n const uri = createTOTPKeyURI(\n provider.options.issuer,\n accountName,\n secret,\n provider.options.period,\n provider.options.digits,\n );\n\n return {\n kind: \"totpSetup\" as const,\n totpSetup: { uri, secret: base32Secret, totpId: enrollment.totpId },\n verifier: enrollment.verifierId,\n };\n }\n\n /**\n * `verify` with `totpId`: completes a first-time enrollment after `setup`.\n * Marks the TOTP factor as verified and signs the user in.\n */\n async function confirmEnrollment(\n code: string,\n totpId: string,\n verifier: string,\n ): Promise<TotpResult> {\n const userId = await requireAuthenticatedUserId(ctx);\n const result = await verifyTotp({\n code,\n verifier,\n intent: \"enrollment\",\n authenticatedUserId: userId,\n totpId,\n });\n await queueAuthEvent(ctx, ctx.auth.config, {\n kind: \"totp.enrolled\",\n actor: { type: \"user\", id: userId },\n subject: { type: \"totp\", id: totpId },\n targets: [{ kind: \"user\", id: userId }],\n outcome: \"success\",\n data: { totpId },\n });\n return result;\n }\n\n /**\n * `verify` without `totpId`: completes a 2FA challenge during sign-in.\n * Looks up the user's verified TOTP factor, validates the code, signs in.\n */\n async function verifyChallenge(code: string, verifier: string): Promise<TotpResult> {\n return await verifyTotp({ code, verifier, intent: \"challenge\" });\n }\n\n async function verifyTotp(input: {\n code: string;\n verifier: string;\n intent: \"enrollment\" | \"challenge\";\n authenticatedUserId?: string;\n totpId?: string;\n }): Promise<TotpResult> {\n const begun = (await ctx.runMutation(ctx.auth.config.component.factor.totp.beginVerification, {\n verifierId: input.verifier,\n intent: input.intent,\n authenticatedUserId: input.authenticatedUserId,\n totpId: input.totpId,\n maxAttemptsPerHour: maxSignInAttempts(ctx.auth.config),\n })) as\n | { status: \"invalid_verifier\" | \"limited\" | \"not_found\" | \"already_verified\" }\n | {\n status: \"ready\";\n userId: string;\n factor: {\n _id: string;\n secret: ArrayBuffer;\n period: number;\n digits: number;\n };\n };\n\n if (begun.status === \"invalid_verifier\") {\n throw convexError(ErrorCode.TOTP_INVALID_VERIFIER, \"Invalid or expired TOTP verifier.\");\n }\n if (begun.status === \"limited\") {\n throw convexError(ErrorCode.RATE_LIMITED, \"Too many TOTP attempts. Try again later.\");\n }\n if (begun.status === \"already_verified\") {\n throw convexError(ErrorCode.TOTP_ALREADY_VERIFIED, \"TOTP enrollment is already verified.\");\n }\n if (begun.status === \"not_found\") {\n throw convexError(\n input.intent === \"enrollment\" ? ErrorCode.TOTP_NOT_FOUND : ErrorCode.TOTP_NO_ENROLLMENT,\n input.intent === \"enrollment\"\n ? \"TOTP enrollment not found.\"\n : \"No verified TOTP enrollment found.\",\n );\n }\n if (begun.status !== \"ready\") {\n throw convexError(ErrorCode.INTERNAL_ERROR, \"Unexpected TOTP verification state.\");\n }\n\n const secret = await decryptTotpSecret(begun.factor.secret);\n if (\n !verifyTOTPWithGracePeriod(secret, begun.factor.period, begun.factor.digits, input.code, 30)\n ) {\n throw convexError(ErrorCode.TOTP_INVALID_CODE, \"Invalid TOTP code.\");\n }\n\n const replaceSession = await getAuthSessionReplacement(ctx);\n const completed = (await ctx.runMutation(\n ctx.auth.config.component.factor.totp.completeVerification,\n {\n verifierId: input.verifier,\n intent: input.intent,\n authenticatedUserId: input.authenticatedUserId,\n totpId: input.totpId,\n replaceSession,\n sessionExpirationTime: sessionExpirationTime(ctx.auth.config),\n refreshTokenExpirationTime: refreshTokenExpirationTime(ctx.auth.config),\n now: Date.now(),\n },\n )) as\n | { status: \"rejected\" }\n | {\n status: \"accepted\";\n user: CrossComponentUserDoc;\n factorId: string;\n sessionId: string;\n sessionExpirationTime: number;\n refreshTokenId: string;\n replacedSessionId?: string;\n };\n if (completed.status !== \"accepted\") {\n throw convexError(ErrorCode.TOTP_INVALID_VERIFIER, \"Invalid or expired TOTP verifier.\");\n }\n\n const userId = completed.user._id as GenericId<\"User\">;\n const sessionId = completed.sessionId as GenericId<\"Session\">;\n setActiveSpanAttributes({\n \"auth.signin.result\": \"success\",\n ...buildKnownSignInIdentityAttributes(\n ctx.auth.config,\n { userId, sessionId },\n completed.user.email,\n ),\n });\n if (completed.replacedSessionId !== undefined) {\n const replacedSessionId = completed.replacedSessionId as GenericId<\"Session\">;\n await queueAuthEvent(ctx, ctx.auth.config, {\n kind: \"session.invalidated\",\n actor: { type: \"system\" },\n subject: { type: \"session\", id: replacedSessionId },\n targets: [\n { kind: \"user\", id: userId },\n { kind: \"session\", id: replacedSessionId },\n ],\n outcome: \"success\",\n data: { userId, reason: \"replaced\" },\n });\n }\n await queueAuthEvent(ctx, ctx.auth.config, {\n kind: \"session.signed_in\",\n actor: { type: \"user\", id: userId },\n subject: { type: \"session\", id: sessionId },\n targets: [\n { kind: \"user\", id: userId },\n { kind: \"session\", id: sessionId },\n ],\n outcome: \"success\",\n data: { provider: \"session\", method: provider.id },\n });\n const session = await finalizeSessionIssuance(ctx.auth.config, {\n userId,\n sessionId,\n sessionExpirationTime: completed.sessionExpirationTime,\n identity: buildSessionIdentity(userId, sessionId, completed.user),\n refreshToken: encodeRefreshToken(\n completed.refreshTokenId as GenericId<\"RefreshToken\">,\n sessionId,\n ),\n });\n return { kind: \"signedIn\" as const, session };\n }\n\n if (dispatch.flow === \"setup\") {\n return await setup(dispatch.params);\n }\n if (dispatch.intent === \"enrollment\") {\n return await confirmEnrollment(dispatch.code, dispatch.totpId, dispatch.verifier);\n }\n return await verifyChallenge(dispatch.code, dispatch.verifier);\n};\n"],"mappings":"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"}
|