@robelest/convex-auth 0.0.4-preview.13 → 0.0.4-preview.16
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 +140 -9
- package/dist/bin.cjs +5957 -5478
- package/dist/client/index.d.ts +3 -7
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +27 -26
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/api.d.ts +14 -0
- package/dist/component/_generated/api.d.ts.map +1 -1
- package/dist/component/_generated/api.js.map +1 -1
- package/dist/component/_generated/component.d.ts +1672 -24
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/convex.config.d.ts +2 -2
- package/dist/component/convex.config.d.ts.map +1 -1
- package/dist/component/index.d.ts +1 -1
- package/dist/component/index.js +2 -2
- package/dist/component/model.d.ts +153 -0
- package/dist/component/model.d.ts.map +1 -0
- package/dist/component/model.js +343 -0
- package/dist/component/model.js.map +1 -0
- package/dist/component/providers/sso.d.ts +1 -1
- package/dist/component/public/enterprise.d.ts +54 -0
- package/dist/component/public/enterprise.d.ts.map +1 -0
- package/dist/component/public/enterprise.js +515 -0
- package/dist/component/public/enterprise.js.map +1 -0
- package/dist/component/public/factors.d.ts +52 -0
- package/dist/component/public/factors.d.ts.map +1 -0
- package/dist/component/public/factors.js +285 -0
- package/dist/component/public/factors.js.map +1 -0
- package/dist/component/public/groups.d.ts +116 -0
- package/dist/component/public/groups.d.ts.map +1 -0
- package/dist/component/public/groups.js +596 -0
- package/dist/component/public/groups.js.map +1 -0
- package/dist/component/public/identity.d.ts +93 -0
- package/dist/component/public/identity.d.ts.map +1 -0
- package/dist/component/public/identity.js +426 -0
- package/dist/component/public/identity.js.map +1 -0
- package/dist/component/public/keys.d.ts +41 -0
- package/dist/component/public/keys.d.ts.map +1 -0
- package/dist/component/public/keys.js +157 -0
- package/dist/component/public/keys.js.map +1 -0
- package/dist/component/public/shared.d.ts +26 -0
- package/dist/component/public/shared.d.ts.map +1 -0
- package/dist/component/public/shared.js +32 -0
- package/dist/component/public/shared.js.map +1 -0
- package/dist/component/public.d.ts +9 -321
- package/dist/component/public.d.ts.map +1 -1
- package/dist/component/public.js +6 -2145
- package/dist/component/schema.d.ts +406 -260
- package/dist/component/schema.js +37 -32
- package/dist/component/schema.js.map +1 -1
- package/dist/component/server/auth.d.ts +161 -15
- package/dist/component/server/auth.d.ts.map +1 -1
- package/dist/component/server/auth.js +100 -7
- package/dist/component/server/auth.js.map +1 -1
- package/dist/component/server/cookies.js +3 -0
- package/dist/component/server/cookies.js.map +1 -1
- package/dist/component/server/db.js +1 -0
- package/dist/component/server/db.js.map +1 -1
- package/dist/component/server/device.js +3 -1
- package/dist/component/server/device.js.map +1 -1
- package/dist/component/server/domains/core.js +629 -0
- package/dist/component/server/domains/core.js.map +1 -0
- package/dist/component/server/domains/sso.js +884 -0
- package/dist/component/server/domains/sso.js.map +1 -0
- package/dist/component/server/factory.d.ts +136 -0
- package/dist/component/server/factory.d.ts.map +1 -0
- package/dist/component/server/factory.js +1134 -0
- package/dist/component/server/factory.js.map +1 -0
- package/dist/component/server/fx.js +2 -1
- package/dist/component/server/fx.js.map +1 -1
- package/dist/component/server/http.js +287 -0
- package/dist/component/server/http.js.map +1 -0
- package/dist/component/server/identity.js +13 -0
- package/dist/component/server/identity.js.map +1 -0
- package/dist/component/server/keys.js +4 -0
- package/dist/component/server/keys.js.map +1 -1
- package/dist/component/server/mutations/account.js +1 -1
- package/dist/component/server/mutations/index.js +2 -2
- package/dist/component/server/mutations/index.js.map +1 -1
- package/dist/component/server/mutations/invalidate.js +1 -1
- package/dist/component/server/mutations/oauth.js +10 -7
- package/dist/component/server/mutations/oauth.js.map +1 -1
- package/dist/component/server/mutations/refresh.js +1 -1
- package/dist/component/server/mutations/register.js +1 -1
- package/dist/component/server/mutations/retrieve.js +1 -1
- package/dist/component/server/mutations/signature.js +1 -1
- package/dist/component/server/mutations/store.js +6 -3
- package/dist/component/server/mutations/store.js.map +1 -1
- package/dist/component/server/mutations/verify.js +1 -1
- package/dist/component/server/oauth.js +3 -0
- package/dist/component/server/oauth.js.map +1 -1
- package/dist/component/server/passkey.js +3 -2
- package/dist/component/server/passkey.js.map +1 -1
- package/dist/component/server/provider.js +2 -0
- package/dist/component/server/provider.js.map +1 -1
- package/dist/component/server/providers.js +10 -0
- package/dist/component/server/providers.js.map +1 -1
- package/dist/component/server/ratelimit.js +3 -0
- package/dist/component/server/ratelimit.js.map +1 -1
- package/dist/component/server/redirects.js +2 -0
- package/dist/component/server/redirects.js.map +1 -1
- package/dist/component/server/refresh.js +5 -0
- package/dist/component/server/refresh.js.map +1 -1
- package/dist/component/server/sessions.js +5 -0
- package/dist/component/server/sessions.js.map +1 -1
- package/dist/component/server/signin.js +2 -1
- package/dist/component/server/signin.js.map +1 -1
- package/dist/component/server/sso.js +166 -19
- package/dist/component/server/sso.js.map +1 -1
- package/dist/component/server/tokens.js +1 -0
- package/dist/component/server/tokens.js.map +1 -1
- package/dist/component/server/totp.js +4 -2
- package/dist/component/server/totp.js.map +1 -1
- package/dist/component/server/types.d.ts +106 -38
- package/dist/component/server/types.d.ts.map +1 -1
- package/dist/component/server/types.js.map +1 -1
- package/dist/component/server/users.js +1 -0
- package/dist/component/server/users.js.map +1 -1
- package/dist/component/server/utils.js +44 -2
- package/dist/component/server/utils.js.map +1 -1
- package/dist/providers/anonymous.d.ts +1 -1
- package/dist/providers/credentials.d.ts +1 -1
- package/dist/providers/password.d.ts +1 -1
- package/dist/providers/sso.d.ts +1 -1
- package/dist/providers/sso.js.map +1 -1
- package/dist/server/auth.d.ts +163 -17
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +100 -7
- package/dist/server/auth.js.map +1 -1
- package/dist/server/cookies.d.ts +1 -38
- package/dist/server/cookies.js +3 -0
- package/dist/server/cookies.js.map +1 -1
- package/dist/server/db.d.ts +1 -125
- package/dist/server/db.js +1 -0
- package/dist/server/db.js.map +1 -1
- package/dist/server/device.d.ts +1 -24
- package/dist/server/device.js +3 -1
- package/dist/server/device.js.map +1 -1
- package/dist/server/domains/core.d.ts +434 -0
- package/dist/server/domains/core.d.ts.map +1 -0
- package/dist/server/domains/core.js +629 -0
- package/dist/server/domains/core.js.map +1 -0
- package/dist/server/domains/sso.d.ts +409 -0
- package/dist/server/domains/sso.d.ts.map +1 -0
- package/dist/server/domains/sso.js +884 -0
- package/dist/server/domains/sso.js.map +1 -0
- package/dist/server/enterpriseValidators.d.ts +1 -0
- package/dist/server/enterpriseValidators.js +60 -0
- package/dist/server/enterpriseValidators.js.map +1 -0
- package/dist/server/factory.d.ts +136 -0
- package/dist/server/factory.d.ts.map +1 -0
- package/dist/server/factory.js +1134 -0
- package/dist/server/factory.js.map +1 -0
- package/dist/server/fx.d.ts +1 -16
- package/dist/server/fx.d.ts.map +1 -1
- package/dist/server/fx.js +1 -0
- package/dist/server/fx.js.map +1 -1
- package/dist/server/http.d.ts +59 -0
- package/dist/server/http.d.ts.map +1 -0
- package/dist/server/http.js +287 -0
- package/dist/server/http.js.map +1 -0
- package/dist/server/identity.d.ts +1 -0
- package/dist/server/identity.js +13 -0
- package/dist/server/identity.js.map +1 -0
- package/dist/server/index.d.ts +468 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +530 -36
- package/dist/server/index.js.map +1 -1
- package/dist/server/keys.d.ts +1 -57
- package/dist/server/keys.js +4 -0
- package/dist/server/keys.js.map +1 -1
- package/dist/server/mutations/account.d.ts +7 -7
- package/dist/server/mutations/account.d.ts.map +1 -1
- package/dist/server/mutations/code.d.ts +13 -13
- package/dist/server/mutations/code.d.ts.map +1 -1
- package/dist/server/mutations/index.d.ts +107 -107
- package/dist/server/mutations/index.d.ts.map +1 -1
- package/dist/server/mutations/index.js +1 -1
- package/dist/server/mutations/index.js.map +1 -1
- package/dist/server/mutations/invalidate.d.ts +5 -5
- package/dist/server/mutations/invalidate.d.ts.map +1 -1
- package/dist/server/mutations/oauth.d.ts +10 -10
- package/dist/server/mutations/oauth.d.ts.map +1 -1
- package/dist/server/mutations/oauth.js +9 -6
- package/dist/server/mutations/oauth.js.map +1 -1
- package/dist/server/mutations/refresh.d.ts +4 -4
- package/dist/server/mutations/register.d.ts +12 -12
- package/dist/server/mutations/register.d.ts.map +1 -1
- package/dist/server/mutations/retrieve.d.ts +7 -7
- package/dist/server/mutations/signature.d.ts +5 -5
- package/dist/server/mutations/signin.d.ts +6 -6
- package/dist/server/mutations/signin.d.ts.map +1 -1
- package/dist/server/mutations/signout.d.ts +1 -1
- package/dist/server/mutations/store.d.ts +3 -2
- package/dist/server/mutations/store.d.ts.map +1 -1
- package/dist/server/mutations/store.js +6 -3
- package/dist/server/mutations/store.js.map +1 -1
- package/dist/server/mutations/verifier.d.ts +1 -1
- package/dist/server/mutations/verify.d.ts +11 -11
- package/dist/server/mutations/verify.d.ts.map +1 -1
- package/dist/server/oauth.d.ts +1 -59
- package/dist/server/oauth.js +3 -0
- package/dist/server/oauth.js.map +1 -1
- package/dist/server/passkey.d.ts.map +1 -1
- package/dist/server/passkey.js +3 -2
- package/dist/server/passkey.js.map +1 -1
- package/dist/server/provider.d.ts +1 -14
- package/dist/server/provider.d.ts.map +1 -1
- package/dist/server/provider.js +2 -0
- package/dist/server/provider.js.map +1 -1
- package/dist/server/providers.js +10 -0
- package/dist/server/providers.js.map +1 -1
- package/dist/server/ratelimit.d.ts +1 -22
- package/dist/server/ratelimit.js +3 -0
- package/dist/server/ratelimit.js.map +1 -1
- package/dist/server/redirects.d.ts +1 -10
- package/dist/server/redirects.js +2 -0
- package/dist/server/redirects.js.map +1 -1
- package/dist/server/refresh.d.ts +1 -37
- package/dist/server/refresh.js +5 -0
- package/dist/server/refresh.js.map +1 -1
- package/dist/server/sessions.d.ts +1 -28
- package/dist/server/sessions.js +5 -0
- package/dist/server/sessions.js.map +1 -1
- package/dist/server/signin.d.ts +1 -55
- package/dist/server/signin.js +2 -1
- package/dist/server/signin.js.map +1 -1
- package/dist/server/sso.d.ts +1 -348
- package/dist/server/sso.js +165 -18
- package/dist/server/sso.js.map +1 -1
- package/dist/server/templates.d.ts +1 -21
- package/dist/server/templates.js +1 -0
- package/dist/server/templates.js.map +1 -1
- package/dist/server/tokens.d.ts +1 -11
- package/dist/server/tokens.js +1 -0
- package/dist/server/tokens.js.map +1 -1
- package/dist/server/totp.d.ts +1 -23
- package/dist/server/totp.js +4 -2
- package/dist/server/totp.js.map +1 -1
- package/dist/server/types.d.ts +114 -77
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/types.js.map +1 -1
- package/dist/server/users.d.ts +1 -31
- package/dist/server/users.js +1 -0
- package/dist/server/users.js.map +1 -1
- package/dist/server/utils.d.ts +1 -27
- package/dist/server/utils.js +44 -2
- package/dist/server/utils.js.map +1 -1
- package/dist/server/version.d.ts +1 -1
- package/dist/server/version.js +1 -1
- package/dist/server/version.js.map +1 -1
- package/package.json +4 -5
- package/src/cli/bin.ts +5 -0
- package/src/cli/index.ts +22 -9
- package/src/cli/keys.ts +3 -0
- package/src/client/index.ts +36 -37
- package/src/component/_generated/api.ts +14 -0
- package/src/component/_generated/component.ts +2106 -9
- package/src/component/index.ts +3 -1
- package/src/component/model.ts +441 -0
- package/src/component/public/enterprise.ts +753 -0
- package/src/component/public/factors.ts +332 -0
- package/src/component/public/groups.ts +932 -0
- package/src/component/public/identity.ts +566 -0
- package/src/component/public/keys.ts +209 -0
- package/src/component/public/shared.ts +119 -0
- package/src/component/public.ts +5 -2965
- package/src/component/schema.ts +68 -63
- package/src/providers/sso.ts +1 -1
- package/src/server/auth.ts +413 -18
- package/src/server/cookies.ts +3 -0
- package/src/server/db.ts +3 -0
- package/src/server/device.ts +3 -1
- package/src/server/domains/core.ts +1071 -0
- package/src/server/domains/sso.ts +1749 -0
- package/src/server/enterpriseValidators.ts +93 -0
- package/src/server/factory.ts +2181 -0
- package/src/server/fx.ts +1 -0
- package/src/server/http.ts +529 -0
- package/src/server/identity.ts +18 -0
- package/src/server/index.ts +806 -40
- package/src/server/keys.ts +4 -0
- package/src/server/mutations/index.ts +1 -1
- package/src/server/mutations/oauth.ts +36 -8
- package/src/server/mutations/store.ts +6 -3
- package/src/server/oauth.ts +6 -0
- package/src/server/passkey.ts +3 -2
- package/src/server/provider.ts +2 -0
- package/src/server/providers.ts +20 -0
- package/src/server/ratelimit.ts +3 -0
- package/src/server/redirects.ts +2 -0
- package/src/server/refresh.ts +5 -0
- package/src/server/sessions.ts +5 -0
- package/src/server/signin.ts +1 -0
- package/src/server/sso.ts +259 -17
- package/src/server/templates.ts +1 -0
- package/src/server/tokens.ts +1 -0
- package/src/server/totp.ts +4 -2
- package/src/server/types.ts +178 -83
- package/src/server/users.ts +1 -0
- package/src/server/utils.ts +71 -1
- package/src/server/version.ts +1 -1
- package/dist/component/public.js.map +0 -1
- package/dist/component/server/implementation.d.ts +0 -1264
- package/dist/component/server/implementation.d.ts.map +0 -1
- package/dist/component/server/implementation.js +0 -2365
- package/dist/component/server/implementation.js.map +0 -1
- package/dist/server/cookies.d.ts.map +0 -1
- package/dist/server/db.d.ts.map +0 -1
- package/dist/server/device.d.ts.map +0 -1
- package/dist/server/implementation.d.ts +0 -1264
- package/dist/server/implementation.d.ts.map +0 -1
- package/dist/server/implementation.js +0 -2365
- package/dist/server/implementation.js.map +0 -1
- package/dist/server/keys.d.ts.map +0 -1
- package/dist/server/oauth.d.ts.map +0 -1
- package/dist/server/ratelimit.d.ts.map +0 -1
- package/dist/server/redirects.d.ts.map +0 -1
- package/dist/server/refresh.d.ts.map +0 -1
- package/dist/server/sessions.d.ts.map +0 -1
- package/dist/server/signin.d.ts.map +0 -1
- package/dist/server/sso.d.ts.map +0 -1
- package/dist/server/templates.d.ts.map +0 -1
- package/dist/server/tokens.d.ts.map +0 -1
- package/dist/server/totp.d.ts.map +0 -1
- package/dist/server/users.d.ts.map +0 -1
- package/dist/server/utils.d.ts.map +0 -1
- package/src/server/implementation.ts +0 -5336
package/dist/server/sso.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sso.js","names":[],"sources":["../../src/server/sso.ts"],"sourcesContent":["import { sha256 } from \"@oslojs/crypto/sha2\";\nimport {\n decodeBase64urlIgnorePadding,\n encodeBase64urlNoPadding,\n} from \"@oslojs/encoding\";\nimport { Fx } from \"@robelest/fx\";\nimport {\n Constants,\n IdentityProvider,\n ServiceProvider,\n setSchemaValidator,\n} from \"@robelest/samlify\";\n\n// Samlify requires a schema validator to be registered before parsing any SAML\n// response. We use a permissive validator that always resolves because Convex's\n// edge runtime has no file-system access for XML schema files, and structural\n// correctness is already ensured by the XML parser. This is called directly\n// before each parse operation since Convex can restart the V8 isolate between\n// requests, resetting module-level state.\nconst _samlifyPermissiveValidator = {\n validate: (_xml: string) => Promise.resolve(\"OK\"),\n};\nfunction ensureSamlifyValidator() {\n setSchemaValidator(_samlifyPermissiveValidator);\n}\nimport { decodeIdToken } from \"arctic\";\nimport { createRemoteJWKSet, decodeProtectedHeader, jwtVerify } from \"jose\";\n\nimport type {\n OAuthMaterializedConfig,\n OAuthProfile,\n SAMLAttributeMapping,\n} from \"./types\";\n\nexport type ParsedSamlMetadata = {\n issuer: string;\n sso: {\n redirect?: string;\n post?: string;\n };\n slo: {\n redirect?: string;\n post?: string;\n };\n signingCert: string | string[] | null;\n encryptionCert: string | string[] | null;\n nameIdFormats: string[];\n wantsSignedAuthnRequests: boolean;\n};\n\nexport type EnterpriseSamlSource = { kind: \"enterprise\"; id: string };\n\nexport type EnterpriseSamlRelayState = {\n source: EnterpriseSamlSource;\n signature: string;\n requestId: string;\n state: string;\n redirectTo?: string;\n};\n\nexport type EnterpriseSamlUrls = {\n metadataUrl: string;\n acsUrl: string;\n sloUrl?: string;\n};\n\nexport type EnterpriseSamlLoadedSource = {\n source: EnterpriseSamlSource;\n config: unknown;\n status?: string;\n};\n\nexport type EnterpriseSamlHttpRequest = {\n url: URL;\n body: Record<string, string>;\n query: Record<string, string>;\n binding: \"redirect\" | \"post\";\n relayState?: string;\n hasSamlRequest: boolean;\n hasSamlResponse: boolean;\n};\n\nexport type ScimListRequest = {\n startIndex: number;\n count: number;\n filter?: { attribute: string; value: string };\n};\n\nexport const SCIM_USER_SCHEMA_ID = \"urn:ietf:params:scim:schemas:core:2.0:User\";\nexport const SCIM_GROUP_SCHEMA_ID =\n \"urn:ietf:params:scim:schemas:core:2.0:Group\";\n\nexport const ENTERPRISE_OIDC_PROVIDER_PREFIX = \"enterprise:oidc:\";\nexport const ENTERPRISE_SAML_PROVIDER_PREFIX = \"enterprise:saml:\";\nconst OIDC_JWKS_CACHE = new Map<\n string,\n ReturnType<typeof createRemoteJWKSet>\n>();\n\nexport function normalizeDomain(domain: string): string {\n return domain.trim().toLowerCase().replace(/^@+/, \"\");\n}\n\nexport function enterpriseOidcProviderId(enterpriseId: string): string {\n return `${ENTERPRISE_OIDC_PROVIDER_PREFIX}${enterpriseId}`;\n}\n\nexport function enterpriseSamlProviderId(enterpriseId: string): string {\n return `${ENTERPRISE_SAML_PROVIDER_PREFIX}${enterpriseId}`;\n}\n\nexport function getEnterpriseSamlUrls(opts: {\n rootUrl: string;\n source: EnterpriseSamlSource;\n}): EnterpriseSamlUrls {\n const root = opts.rootUrl.replace(/\\/$/, \"\");\n const metadataBase = `${root}/api/auth/sso/${opts.source.id}/saml/metadata`;\n const acsBase = `${root}/api/auth/sso/${opts.source.id}/saml/acs`;\n const sloBase = `${root}/api/auth/sso/${opts.source.id}/saml/slo`;\n return {\n metadataUrl: metadataBase,\n acsUrl: acsBase,\n sloUrl: sloBase,\n };\n}\n\nexport function getEnterpriseOidcUrls(opts: {\n rootUrl: string;\n enterpriseId: string;\n}) {\n const root = opts.rootUrl.replace(/\\/$/, \"\");\n return {\n signInUrl: `${root}/api/auth/sso/${opts.enterpriseId}/oidc/signin`,\n callbackUrl: `${root}/api/auth/sso/${opts.enterpriseId}/oidc/callback`,\n };\n}\n\nexport function isEnterpriseSamlSourceActive(\n source: EnterpriseSamlLoadedSource,\n) {\n return source.status === \"active\";\n}\n\nexport function isEnterpriseProviderId(providerId: string): boolean {\n return (\n providerId.startsWith(ENTERPRISE_OIDC_PROVIDER_PREFIX) ||\n providerId.startsWith(ENTERPRISE_SAML_PROVIDER_PREFIX)\n );\n}\n\nconst asRecord = (value: unknown) =>\n typeof value === \"object\" && value !== null\n ? (value as Record<string, any>)\n : null;\n\nconst getProtocolConfig = (config: unknown, protocol: \"oidc\" | \"saml\") => {\n const base = asRecord(config);\n const direct = base?.[protocol];\n const viaProtocols = asRecord(base?.protocols)?.[protocol];\n return asRecord(direct) ?? asRecord(viaProtocols) ?? {};\n};\n\nexport function getOidcConfig(config: unknown): Record<string, any> {\n return getProtocolConfig(config, \"oidc\");\n}\n\nexport function getSamlConfig(config: unknown): Record<string, any> {\n return getProtocolConfig(config, \"saml\");\n}\n\nexport function upsertProtocolConfig(\n config: unknown,\n protocol: \"oidc\" | \"saml\",\n protocolConfig: Record<string, unknown>,\n) {\n const base = asRecord(config) ?? {};\n const protocols = asRecord(base.protocols) ?? {};\n protocols[protocol] = {\n ...asRecord(protocols[protocol]),\n ...protocolConfig,\n };\n return { ...base, protocols };\n}\n\nexport function createSamlPostBindingResponse(opts: {\n endpoint: string;\n parameter: \"SAMLRequest\" | \"SAMLResponse\";\n value: string;\n relayState?: string;\n}) {\n const fields = [\n `<input type=\"hidden\" name=\"${opts.parameter}\" value=\"${opts.value.replace(/\"/g, \""\")}\" />`,\n opts.relayState\n ? `<input type=\"hidden\" name=\"RelayState\" value=\"${opts.relayState.replace(/\"/g, \""\")}\" />`\n : \"\",\n ].join(\"\");\n return new Response(\n `<!doctype html><html><body><form method=\"POST\" action=\"${opts.endpoint}\">${fields}</form><script>document.forms[0].submit();</script></body></html>`,\n { status: 200, headers: { \"Content-Type\": \"text/html; charset=utf-8\" } },\n );\n}\n\nexport function decodeRelayState(\n value: string | null,\n): Record<string, unknown> {\n if (!value) {\n return {};\n }\n try {\n return JSON.parse(\n new TextDecoder().decode(decodeBase64urlIgnorePadding(value)),\n );\n } catch {\n return {};\n }\n}\n\nexport function encodeEnterpriseSamlRelayState(\n value: EnterpriseSamlRelayState,\n) {\n return encodeBase64urlNoPadding(\n new TextEncoder().encode(\n JSON.stringify({\n source: `${value.source.kind}:${value.source.id}`,\n signature: value.signature,\n requestId: value.requestId,\n state: value.state,\n redirectTo: value.redirectTo,\n }),\n ),\n );\n}\n\nexport function decodeEnterpriseSamlRelayStateOrThrow(\n value: string | null,\n): EnterpriseSamlRelayState {\n if (!value) {\n throw new Error(\"Missing SAML RelayState.\");\n }\n const decoded = decodeRelayState(value);\n if (\n typeof decoded.source !== \"string\" ||\n typeof decoded.signature !== \"string\" ||\n typeof decoded.requestId !== \"string\" ||\n typeof decoded.state !== \"string\"\n ) {\n throw new Error(\"Invalid SAML RelayState.\");\n }\n const [kind, ...rest] = decoded.source.split(\":\");\n const id = rest.join(\":\");\n if (kind !== \"enterprise\" || id.length === 0) {\n throw new Error(\"Invalid enterprise SAML source.\");\n }\n return {\n source: { kind, id } as EnterpriseSamlSource,\n signature: decoded.signature,\n requestId: decoded.requestId,\n state: decoded.state,\n redirectTo:\n typeof decoded.redirectTo === \"string\" ? decoded.redirectTo : undefined,\n };\n}\n\nexport async function readRequestBody(\n request: Request,\n): Promise<Record<string, string>> {\n const contentType = request.headers.get(\"Content-Type\") ?? \"\";\n if (\n contentType.includes(\"application/x-www-form-urlencoded\") ||\n contentType.includes(\"multipart/form-data\")\n ) {\n const form = await request.formData();\n const body: Record<string, string> = {};\n form.forEach((value, key) => {\n body[key] = typeof value === \"string\" ? value : value.name;\n });\n return body;\n }\n return {};\n}\n\nexport async function readEnterpriseSamlHttpRequest(\n request: Request,\n): Promise<EnterpriseSamlHttpRequest> {\n const url = new URL(request.url);\n const body = await readRequestBody(request);\n const query = Object.fromEntries(url.searchParams);\n const binding =\n request.method === \"GET\"\n ? \"redirect\"\n : body.SAMLResponse || body.SAMLRequest\n ? \"post\"\n : \"redirect\";\n return {\n url,\n body,\n query,\n binding,\n relayState:\n body.RelayState ?? url.searchParams.get(\"RelayState\") ?? undefined,\n hasSamlRequest: Boolean(\n body.SAMLRequest ?? url.searchParams.get(\"SAMLRequest\"),\n ),\n hasSamlResponse: Boolean(\n body.SAMLResponse ?? url.searchParams.get(\"SAMLResponse\"),\n ),\n };\n}\n\nasync function discoverOidcConfiguration(config: Record<string, any>) {\n const discoveryUrl =\n typeof config.discoveryUrl === \"string\"\n ? config.discoveryUrl\n : typeof config.issuer === \"string\"\n ? `${config.issuer.replace(/\\/$/, \"\")}/.well-known/openid-configuration`\n : null;\n\n if (!discoveryUrl) {\n throw new Error(\"Enterprise OIDC requires an issuer or discoveryUrl.\");\n }\n\n return await Fx.run(\n Fx.defer(() =>\n Fx.from({\n ok: async () => {\n const response = await fetch(discoveryUrl);\n if (!response.ok) {\n throw new Error(\n `Failed to discover OIDC configuration: ${response.status}`,\n );\n }\n const discovery = (await response.json()) as Record<string, any>;\n if (\n typeof discovery.issuer !== \"string\" ||\n typeof discovery.authorization_endpoint !== \"string\" ||\n typeof discovery.token_endpoint !== \"string\" ||\n typeof discovery.jwks_uri !== \"string\"\n ) {\n throw new Error(\n \"OIDC discovery document is missing required fields.\",\n );\n }\n return discovery;\n },\n err: (error) =>\n error instanceof Error ? error : new Error(String(error)),\n }),\n ).pipe(\n Fx.timeout(10_000),\n Fx.retry(\n Fx.retry.compose(\n Fx.retry.jittered(Fx.retry.exponential(200)),\n Fx.retry.recurs(2),\n ),\n ),\n Fx.recover((error) =>\n Fx.fail(error instanceof Error ? error : new Error(String(error))),\n ),\n ),\n );\n}\n\nfunction getOidcJwks(url: string) {\n let jwks = OIDC_JWKS_CACHE.get(url);\n if (!jwks) {\n jwks = createRemoteJWKSet(new URL(url));\n OIDC_JWKS_CACHE.set(url, jwks);\n }\n return jwks;\n}\n\ntype UserInfoFetchFailure =\n | { kind: \"transport\"; error: unknown }\n | { kind: \"subject-mismatch\" };\n\nfunction userInfoProfileFx(opts: {\n endpoint: string;\n accessToken: string;\n verifiedClaims: Record<string, unknown>;\n verifiedProfile: OAuthProfile & { emailVerified?: boolean };\n}) {\n return Fx.from({\n ok: async () => {\n const response = await fetch(opts.endpoint, {\n headers: { Authorization: `Bearer ${opts.accessToken}` },\n });\n if (!response.ok) {\n throw new Error(`OIDC userinfo request failed: ${response.status}`);\n }\n return (await response.json()) as Record<string, unknown>;\n },\n err: (error): UserInfoFetchFailure => ({ kind: \"transport\", error }),\n }).pipe(\n Fx.chain((userInfo) => {\n const userInfoSubject =\n typeof userInfo.sub === \"string\" ? userInfo.sub : undefined;\n const tokenSubject =\n typeof opts.verifiedClaims.sub === \"string\"\n ? opts.verifiedClaims.sub\n : undefined;\n return userInfoSubject !== undefined &&\n tokenSubject !== undefined &&\n userInfoSubject !== tokenSubject\n ? Fx.fail({ kind: \"subject-mismatch\" } as const)\n : Fx.succeed({\n id:\n userInfoSubject ??\n (typeof opts.verifiedClaims.sub === \"string\"\n ? opts.verifiedClaims.sub\n : undefined) ??\n crypto.randomUUID(),\n email:\n typeof userInfo.email === \"string\"\n ? userInfo.email\n : opts.verifiedProfile.email,\n emailVerified:\n typeof userInfo.email_verified === \"boolean\"\n ? userInfo.email_verified\n : opts.verifiedProfile.emailVerified,\n name:\n typeof userInfo.name === \"string\"\n ? userInfo.name\n : opts.verifiedProfile.name,\n image:\n typeof userInfo.picture === \"string\"\n ? userInfo.picture\n : opts.verifiedProfile.image,\n } as OAuthProfile & { emailVerified?: boolean });\n }),\n Fx.recover((failure) => {\n if (failure.kind === \"transport\") {\n return Fx.succeed(null);\n }\n return Fx.fail(\n new Error(\"OIDC userinfo subject does not match ID token subject.\"),\n );\n }),\n );\n}\n\nexport async function createEnterpriseOidcProvider(\n config: Record<string, any>,\n redirectUri: string,\n) {\n const discovery = await discoverOidcConfiguration(config);\n const expectedIssuer = String(config.issuer ?? discovery.issuer).replace(\n /\\/$/,\n \"\",\n );\n const discoveredIssuer = String(discovery.issuer).replace(/\\/$/, \"\");\n const strictIssuer = config.strictIssuer === true;\n if (\n typeof config.issuer === \"string\" &&\n expectedIssuer !== discoveredIssuer\n ) {\n if (strictIssuer) {\n throw new Error(\n `Configured OIDC issuer mismatch. configured=${expectedIssuer} discovery=${discoveredIssuer}`,\n );\n }\n console.warn(\n \"Configured OIDC issuer differs from discovery issuer; accepting both for token verification.\",\n {\n configuredIssuer: expectedIssuer,\n discoveryIssuer: discoveredIssuer,\n },\n );\n }\n const authorizationEndpoint = discovery.authorization_endpoint as string;\n const tokenEndpoint = discovery.token_endpoint as string;\n const jwksUri = String(config.jwksUri ?? discovery.jwks_uri);\n const supportedIdTokenSigningAlgs = Array.isArray(\n discovery.id_token_signing_alg_values_supported,\n )\n ? discovery.id_token_signing_alg_values_supported.filter(\n (value: unknown): value is string => typeof value === \"string\",\n )\n : [];\n const userinfoEndpoint =\n (discovery.userinfo_endpoint as string | undefined) ?? undefined;\n const scopes = Array.isArray(config.scopes)\n ? config.scopes.filter(\n (value: unknown): value is string => typeof value === \"string\",\n )\n : [\"openid\", \"profile\", \"email\"];\n const expectedAudience = config.audience ?? String(config.clientId);\n const getIssuerCandidates = (issuer: string) => {\n const candidates = [issuer];\n if (issuer.startsWith(\"https://\")) {\n candidates.push(`http://${issuer.slice(\"https://\".length)}`);\n } else if (issuer.startsWith(\"http://\")) {\n candidates.push(`https://${issuer.slice(\"http://\".length)}`);\n }\n return candidates;\n };\n const expectedIssuers = strictIssuer\n ? [expectedIssuer]\n : Array.from(\n new Set([\n ...getIssuerCandidates(expectedIssuer),\n ...getIssuerCandidates(discoveredIssuer),\n ]),\n );\n const jwks = getOidcJwks(jwksUri);\n let verifiedClaims: Record<string, unknown> | null = null;\n let verifiedProfile: (OAuthProfile & { emailVerified?: boolean }) | null =\n null;\n const normalizeProfile = (claims: Record<string, unknown>) => ({\n id: typeof claims.sub === \"string\" ? claims.sub : crypto.randomUUID(),\n email: typeof claims.email === \"string\" ? claims.email : undefined,\n emailVerified:\n typeof claims.email_verified === \"boolean\"\n ? claims.email_verified\n : undefined,\n name: typeof claims.name === \"string\" ? claims.name : undefined,\n image: typeof claims.picture === \"string\" ? claims.picture : undefined,\n });\n\n const provider = {\n createAuthorizationURL(\n state: string,\n codeVerifier: string,\n requestedScopes: string[],\n ) {\n const url = new URL(authorizationEndpoint);\n url.searchParams.set(\"response_type\", \"code\");\n url.searchParams.set(\"client_id\", String(config.clientId));\n url.searchParams.set(\"redirect_uri\", redirectUri);\n url.searchParams.set(\n \"scope\",\n (requestedScopes.length > 0 ? requestedScopes : scopes).join(\" \"),\n );\n url.searchParams.set(\"state\", state);\n url.searchParams.set(\"code_challenge_method\", \"S256\");\n url.searchParams.set(\n \"code_challenge\",\n encodeBase64urlNoPadding(\n sha256(new TextEncoder().encode(codeVerifier)),\n ),\n );\n const authorizationParams =\n typeof config.authorizationParams === \"object\" &&\n config.authorizationParams !== null\n ? (config.authorizationParams as Record<string, unknown>)\n : {};\n for (const [key, value] of Object.entries(authorizationParams)) {\n if (typeof value === \"string\") {\n url.searchParams.set(key, value);\n }\n }\n return url;\n },\n async validateAuthorizationCode(code: string, codeVerifier?: string) {\n const body = new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n redirect_uri: redirectUri,\n client_id: String(config.clientId),\n });\n if (typeof config.clientSecret === \"string\") {\n body.set(\"client_secret\", config.clientSecret);\n }\n if (codeVerifier) {\n body.set(\"code_verifier\", codeVerifier);\n }\n const response = await fetch(tokenEndpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body,\n });\n if (!response.ok) {\n throw new Error(`OIDC token exchange failed: ${response.status}`);\n }\n const data = (await response.json()) as Record<string, any>;\n return {\n data,\n idToken() {\n if (typeof data.id_token !== \"string\") {\n throw new Error(\"OIDC response is missing id_token.\");\n }\n return data.id_token;\n },\n accessToken() {\n if (typeof data.access_token !== \"string\") {\n throw new Error(\"OIDC response is missing access_token.\");\n }\n return data.access_token;\n },\n };\n },\n };\n\n const oauthConfig = {\n scopes,\n nonce: true,\n validateTokens: async (tokens: any, ctx: { nonce?: string }) => {\n const verified = await Fx.run(\n Fx.gen(function* () {\n yield* Fx.guard(\n ctx.nonce === undefined,\n Fx.fail(new Error(\"OIDC nonce is required.\")),\n );\n\n const idToken = tokens.idToken();\n const protectedHeader = decodeProtectedHeader(idToken);\n const tokenAlg = protectedHeader.alg;\n const useSymmetricValidation =\n typeof tokenAlg === \"string\" &&\n (tokenAlg === \"HS256\" ||\n tokenAlg === \"HS384\" ||\n tokenAlg === \"HS512\") &&\n supportedIdTokenSigningAlgs.includes(tokenAlg);\n\n const verificationOptions = {\n audience: expectedAudience,\n requiredClaims: [\"iss\", \"sub\", \"aud\", \"exp\", \"iat\"],\n clockTolerance: config.clockToleranceSeconds ?? 10,\n } as const;\n\n const verification = yield* Fx.from({\n ok: () =>\n useSymmetricValidation\n ? jwtVerify(\n idToken,\n (() => {\n if (typeof config.clientSecret !== \"string\") {\n throw new Error(\n \"OIDC provider uses symmetric ID token signatures but clientSecret is missing.\",\n );\n }\n return new TextEncoder().encode(config.clientSecret);\n })(),\n verificationOptions as any,\n )\n : jwtVerify(idToken, jwks as any, verificationOptions as any),\n err: (error) =>\n error instanceof Error ? error : new Error(String(error)),\n });\n\n const payload = verification.payload as Record<string, unknown>;\n const tokenIssuerRaw =\n typeof payload.iss === \"string\" ? payload.iss : undefined;\n const tokenIssuer =\n typeof tokenIssuerRaw === \"string\"\n ? tokenIssuerRaw.replace(/\\/$/, \"\")\n : undefined;\n\n yield* Fx.guard(\n !tokenIssuer || !expectedIssuers.includes(tokenIssuer),\n Fx.fail(\n new Error(\n `OIDC token issuer mismatch. Received: ${tokenIssuer ?? \"<missing>\"}. Expected one of: ${expectedIssuers.join(\", \")}`,\n ),\n ),\n );\n\n yield* Fx.guard(\n payload.nonce !== ctx.nonce,\n Fx.fail(new Error(\"OIDC nonce mismatch.\")),\n );\n\n yield* Fx.guard(\n Array.isArray(payload.aud) &&\n payload.aud.length > 1 &&\n payload.azp !== String(config.clientId),\n Fx.fail(\n new Error(\"OIDC authorized party does not match client ID.\"),\n ),\n );\n\n return payload;\n }),\n );\n\n verifiedClaims = verified;\n verifiedProfile = normalizeProfile(verified);\n },\n accountLinking: config.accountLinking,\n profile: async (tokens: any): Promise<OAuthProfile> => {\n if (verifiedProfile === null || verifiedClaims === null) {\n const claims = decodeIdToken(tokens.idToken()) as Record<\n string,\n unknown\n >;\n verifiedClaims = claims;\n verifiedProfile = normalizeProfile(claims);\n }\n if (userinfoEndpoint && typeof tokens.accessToken === \"function\") {\n const userInfoProfile = await Fx.run(\n userInfoProfileFx({\n endpoint: userinfoEndpoint,\n accessToken: tokens.accessToken(),\n verifiedClaims,\n verifiedProfile,\n }),\n );\n if (userInfoProfile !== null) {\n return userInfoProfile;\n }\n }\n return verifiedProfile;\n },\n } as const;\n\n return { provider, oauthConfig };\n}\n\nexport function createSyntheticOAuthMaterializedConfig(\n providerId: string,\n): OAuthMaterializedConfig {\n return {\n id: providerId,\n type: \"oauth\",\n provider: null,\n scopes: [],\n accountLinking: \"verifiedEmail\",\n };\n}\n\nexport function parseSamlIdpMetadata(metadata: string): ParsedSamlMetadata {\n const idp = IdentityProvider({ metadata });\n const entityMeta = idp.entityMeta;\n\n const normalizeService = (value: unknown): string | undefined => {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n };\n\n return {\n issuer: entityMeta.getEntityID(),\n sso: {\n redirect: normalizeService(entityMeta.getSingleSignOnService(\"redirect\")),\n post: normalizeService(entityMeta.getSingleSignOnService(\"post\")),\n },\n slo: {\n redirect: normalizeService(entityMeta.getSingleLogoutService(\"redirect\")),\n post: normalizeService(entityMeta.getSingleLogoutService(\"post\")),\n },\n signingCert: entityMeta.getX509Certificate(\"signing\"),\n encryptionCert: entityMeta.getX509Certificate(\"encrypt\"),\n nameIdFormats: (() => {\n const nameIdFormat = entityMeta.getNameIDFormat();\n return Array.isArray(nameIdFormat) ? nameIdFormat : [];\n })(),\n wantsSignedAuthnRequests: entityMeta.isWantAuthnRequestsSigned(),\n };\n}\n\nexport function createServiceProviderMetadata(opts: {\n entityId: string;\n acsUrl: string;\n sloUrl?: string;\n authnRequestsSigned?: boolean;\n signingCert?: string | string[];\n encryptCert?: string | string[];\n privateKey?: string;\n privateKeyPass?: string;\n encPrivateKey?: string;\n encPrivateKeyPass?: string;\n}) {\n const binding = Constants.namespace.binding;\n const sp = ServiceProvider({\n entityID: opts.entityId,\n authnRequestsSigned: opts.authnRequestsSigned ?? false,\n privateKey: opts.privateKey,\n privateKeyPass: opts.privateKeyPass,\n signingCert: opts.signingCert,\n encryptCert: opts.encryptCert,\n encPrivateKey: opts.encPrivateKey,\n encPrivateKeyPass: opts.encPrivateKeyPass,\n assertionConsumerService: [\n {\n Binding: binding.post,\n Location: opts.acsUrl,\n },\n ],\n singleLogoutService: opts.sloUrl\n ? [\n {\n Binding: binding.redirect,\n Location: opts.sloUrl,\n },\n {\n Binding: binding.post,\n Location: opts.sloUrl,\n },\n ]\n : undefined,\n });\n return sp.getMetadata();\n}\n\nexport function createEnterpriseSamlMetadataXml(opts: {\n rootUrl: string;\n source: EnterpriseSamlSource;\n config: unknown;\n}) {\n return createServiceProviderMetadata(\n getSamlServiceProviderOptions({\n rootUrl: opts.rootUrl,\n source: opts.source,\n config: opts.config,\n }),\n );\n}\n\nexport function getSamlServiceProviderOptions(opts: {\n rootUrl: string;\n source: EnterpriseSamlSource;\n config: unknown;\n overrides?: {\n entityId?: string;\n acsUrl?: string;\n sloUrl?: string;\n };\n relayState?: string;\n}) {\n const saml = getSamlConfig(opts.config);\n const sp = asRecord(saml.sp) ?? {};\n const urls = getEnterpriseSamlUrls({\n rootUrl: opts.rootUrl,\n source: opts.source,\n });\n return {\n entityId: opts.overrides?.entityId ?? sp.entityId ?? urls.metadataUrl,\n acsUrl: opts.overrides?.acsUrl ?? sp.acsUrl ?? urls.acsUrl,\n sloUrl: opts.overrides?.sloUrl ?? sp.sloUrl ?? urls.sloUrl,\n relayState: opts.relayState,\n authnRequestsSigned: saml.signAuthnRequests,\n signingCert: sp.signingCert,\n encryptCert: sp.encryptCert,\n privateKey: sp.privateKey,\n privateKeyPass: sp.privateKeyPass,\n encPrivateKey: sp.encPrivateKey,\n encPrivateKeyPass: sp.encPrivateKeyPass,\n };\n}\n\nexport function createSamlServiceProvider(opts: {\n entityId: string;\n acsUrl: string;\n sloUrl?: string;\n relayState?: string;\n authnRequestsSigned?: boolean;\n signingCert?: string | string[];\n encryptCert?: string | string[];\n privateKey?: string;\n privateKeyPass?: string;\n encPrivateKey?: string;\n encPrivateKeyPass?: string;\n}) {\n const binding = Constants.namespace.binding;\n return ServiceProvider({\n entityID: opts.entityId,\n relayState: opts.relayState ?? \"\",\n authnRequestsSigned: opts.authnRequestsSigned ?? false,\n privateKey: opts.privateKey,\n privateKeyPass: opts.privateKeyPass,\n signingCert: opts.signingCert,\n encryptCert: opts.encryptCert,\n encPrivateKey: opts.encPrivateKey,\n encPrivateKeyPass: opts.encPrivateKeyPass,\n assertionConsumerService: [\n {\n Binding: binding.post,\n Location: opts.acsUrl,\n },\n ],\n singleLogoutService: opts.sloUrl\n ? [\n { Binding: binding.redirect, Location: opts.sloUrl },\n { Binding: binding.post, Location: opts.sloUrl },\n ]\n : undefined,\n });\n}\n\nexport function createEnterpriseSamlRuntime(opts: {\n rootUrl: string;\n source: EnterpriseSamlSource;\n config: unknown;\n relayState?: string;\n overrides?: {\n entityId?: string;\n acsUrl?: string;\n sloUrl?: string;\n };\n}) {\n const saml = getSamlConfig(opts.config);\n const spOptions = getSamlServiceProviderOptions({\n rootUrl: opts.rootUrl,\n source: opts.source,\n config: opts.config,\n relayState: opts.relayState,\n overrides: opts.overrides,\n });\n if (typeof saml.idp?.metadataXml !== \"string\") {\n throw new Error(\"SAML IdP metadata is missing.\");\n }\n return {\n saml,\n sp: createSamlServiceProvider(spOptions),\n idp: IdentityProvider({ metadata: saml.idp.metadataXml }),\n urls: getEnterpriseSamlUrls({ rootUrl: opts.rootUrl, source: opts.source }),\n };\n}\n\nexport function createEnterpriseSamlSignInRequest(opts: {\n rootUrl: string;\n source: EnterpriseSamlSource;\n config: unknown;\n state: string;\n signature: string;\n redirectTo?: string;\n}) {\n const runtime = createEnterpriseSamlRuntime({\n rootUrl: opts.rootUrl,\n source: opts.source,\n config: opts.config,\n });\n const binding = runtime.saml.idp.sso?.redirect ? \"redirect\" : \"post\";\n const loginRequest = runtime.sp.createLoginRequest(\n runtime.idp,\n binding as any,\n ) as any;\n const relayState = encodeEnterpriseSamlRelayState({\n source: opts.source,\n signature: opts.signature,\n requestId: loginRequest.id,\n state: opts.state,\n redirectTo: opts.redirectTo,\n });\n return {\n requestId: loginRequest.id as string,\n binding,\n relayState,\n redirectUrl:\n binding === \"redirect\"\n ? (() => {\n const redirectUrl = new URL(loginRequest.context);\n redirectUrl.searchParams.set(\"RelayState\", relayState);\n return redirectUrl.toString();\n })()\n : undefined,\n post:\n binding === \"post\"\n ? {\n endpoint: loginRequest.entityEndpoint as string,\n value: loginRequest.context as string,\n }\n : undefined,\n };\n}\n\nexport async function parseEnterpriseSamlLoginResponse(opts: {\n request: Request;\n rootUrl: string;\n source: EnterpriseSamlSource;\n config: unknown;\n}) {\n ensureSamlifyValidator();\n const httpRequest = await readEnterpriseSamlHttpRequest(opts.request);\n const runtime = createEnterpriseSamlRuntime({\n rootUrl: opts.rootUrl,\n source: opts.source,\n config: opts.config,\n });\n const parsed = (await runtime.sp.parseLoginResponse(\n runtime.idp as any,\n httpRequest.binding as any,\n {\n query: httpRequest.query,\n body: httpRequest.body,\n },\n )) as any;\n // Check for deprecated SAML algorithms and warn\n warnDeprecatedSamlAlgorithms(parsed);\n\n return {\n ...httpRequest,\n runtime,\n parsed,\n relayState: decodeEnterpriseSamlRelayStateOrThrow(\n httpRequest.relayState ?? null,\n ),\n };\n}\n\nconst DEPRECATED_SAML_ALGORITHMS = new Set([\n // Signature algorithms\n \"http://www.w3.org/2000/09/xmldsig#rsa-sha1\",\n \"http://www.w3.org/2000/09/xmldsig#dsa-sha1\",\n // Digest algorithms\n \"http://www.w3.org/2000/09/xmldsig#sha1\",\n // Key encryption\n \"http://www.w3.org/2001/04/xmlenc#rsa-1_5\",\n // Data encryption\n \"http://www.w3.org/2001/04/xmlenc#tripledes-cbc\",\n]);\n\n/**\n * Warn when the SAML response uses deprecated cryptographic algorithms\n * (SHA-1, RSA 1.5, 3DES). These are still accepted for compatibility\n * but should be flagged.\n */\nfunction warnDeprecatedSamlAlgorithms(parsed: any) {\n try {\n const sigAlg =\n parsed?.extract?.signature?.signatureAlgorithm ??\n parsed?.extract?.response?.signatureAlgorithm;\n const digestAlg = parsed?.extract?.signature?.digestAlgorithm;\n\n if (sigAlg && DEPRECATED_SAML_ALGORITHMS.has(sigAlg)) {\n console.warn(\n `[convex-auth] SAML response uses deprecated signature algorithm: ${sigAlg}. ` +\n `Consider upgrading your IdP to use RSA-SHA256 or stronger.`,\n );\n }\n if (digestAlg && DEPRECATED_SAML_ALGORITHMS.has(digestAlg)) {\n console.warn(\n `[convex-auth] SAML response uses deprecated digest algorithm: ${digestAlg}. ` +\n `Consider upgrading your IdP to use SHA-256 or stronger.`,\n );\n }\n } catch {\n // Non-critical — don't break auth flow for algorithm check failures\n }\n}\n\nexport function validateEnterpriseSamlLoginRelayState(opts: {\n relayState: EnterpriseSamlRelayState;\n source: EnterpriseSamlSource;\n inResponseTo?: string;\n}) {\n if (\n opts.relayState.source.kind !== opts.source.kind ||\n opts.relayState.source.id !== opts.source.id ||\n opts.relayState.requestId !== opts.inResponseTo\n ) {\n throw new Error(\"SAML RelayState did not match the pending login request.\");\n }\n}\n\nexport async function parseEnterpriseSamlLogoutMessage(opts: {\n request: Request;\n rootUrl: string;\n source: EnterpriseSamlSource;\n config: unknown;\n}) {\n ensureSamlifyValidator();\n const httpRequest = await readEnterpriseSamlHttpRequest(opts.request);\n const runtime = createEnterpriseSamlRuntime({\n rootUrl: opts.rootUrl,\n source: opts.source,\n config: opts.config,\n relayState: httpRequest.relayState,\n });\n const parsedRequest = httpRequest.hasSamlRequest\n ? ((await runtime.sp.parseLogoutRequest(\n runtime.idp as any,\n httpRequest.binding as any,\n {\n query: httpRequest.query,\n body: httpRequest.body,\n },\n )) as any)\n : undefined;\n return {\n ...httpRequest,\n runtime,\n parsedRequest,\n };\n}\n\nexport async function createEnterpriseOidcRuntime(opts: {\n rootUrl: string;\n enterpriseId: string;\n config: unknown;\n}) {\n const oidc = getOidcConfig(opts.config);\n const providerId = enterpriseOidcProviderId(opts.enterpriseId);\n const urls = getEnterpriseOidcUrls({\n rootUrl: opts.rootUrl,\n enterpriseId: opts.enterpriseId,\n });\n const { provider, oauthConfig } = await createEnterpriseOidcProvider(\n oidc,\n urls.callbackUrl,\n );\n return {\n oidc,\n providerId,\n provider,\n oauthConfig,\n ...urls,\n };\n}\n\nexport function profileFromSamlExtract(\n extract: any,\n mapping?: SAMLAttributeMapping,\n) {\n const attributes =\n typeof extract?.attributes === \"object\" && extract.attributes !== null\n ? (extract.attributes as Record<string, unknown>)\n : {};\n const resolveFirst = (...keys: Array<string | undefined>) => {\n for (const key of keys) {\n if (!key) {\n continue;\n }\n const attribute = attributes[key];\n const value = Array.isArray(attribute) ? attribute[0] : attribute;\n if (value !== undefined) {\n return value;\n }\n }\n return undefined;\n };\n const fieldResolvers = {\n email: () => resolveFirst(mapping?.email),\n name: () =>\n resolveFirst(mapping?.name) ??\n ([resolveFirst(mapping?.firstName), resolveFirst(mapping?.lastName)]\n .filter(Boolean)\n .join(\" \") ||\n undefined),\n subject: () =>\n resolveFirst(mapping?.subject) ?? (extract?.nameID as string | undefined),\n } as const;\n const subject = fieldResolvers.subject() as string | undefined;\n if (subject === undefined) {\n throw new Error(\n \"SAML profile is missing a subject. Configure `attributeMapping.subject` or ensure the assertion includes a NameID.\",\n );\n }\n const email = fieldResolvers.email() as string | undefined;\n const name = fieldResolvers.name() as string | undefined;\n return {\n id: subject,\n email,\n emailVerified: typeof email === \"string\" ? true : undefined,\n name,\n samlAttributes: attributes,\n samlSessionIndex: extract?.sessionIndex?.SessionIndex as string | undefined,\n };\n}\n\nexport function parseScimPath(pathname: string) {\n const parts = pathname.split(\"/\").filter(Boolean);\n const [api, auth, enterprise, enterpriseId, protocol, version, ...rest] =\n parts;\n\n if (\n api !== \"api\" ||\n auth !== \"auth\" ||\n enterprise !== \"enterprise\" ||\n !enterpriseId ||\n enterpriseId === \"setup\" ||\n protocol !== \"scim\" ||\n version !== \"v2\"\n ) {\n return {\n enterpriseId: \"\",\n resource: \"\",\n resourceId: undefined,\n };\n }\n\n return {\n enterpriseId,\n resource: rest[0] ?? \"\",\n resourceId: rest[1],\n };\n}\n\nexport function parseScimListRequest(url: URL): ScimListRequest {\n const startIndex = Math.max(\n 1,\n Number(url.searchParams.get(\"startIndex\") ?? \"1\"),\n );\n const count = Math.min(\n 100,\n Math.max(1, Number(url.searchParams.get(\"count\") ?? \"100\")),\n );\n const filterParam = url.searchParams.get(\"filter\");\n const filter = filterParam\n ? (() => {\n const match = filterParam.match(/^([A-Za-z0-9_.]+)\\s+eq\\s+\"([^\"]+)\"$/);\n if (!match) {\n throw new Error(\"Unsupported SCIM filter.\");\n }\n return { attribute: match[1]!, value: match[2]! };\n })()\n : undefined;\n return { startIndex, count, filter };\n}\n\nexport function scimJson(data: unknown, status = 200, headers?: HeadersInit) {\n const responseHeaders = new Headers({\n \"Content-Type\": \"application/scim+json\",\n });\n if (headers) {\n new Headers(headers).forEach((value, key) => {\n responseHeaders.set(key, value);\n });\n }\n return new Response(JSON.stringify(data), {\n status,\n headers: responseHeaders,\n });\n}\n\nexport function scimError(status: number, scimType: string, detail: string) {\n return scimJson(\n {\n schemas: [\"urn:ietf:params:scim:api:messages:2.0:Error\"],\n status: String(status),\n scimType,\n detail,\n },\n status,\n );\n}\n\nexport function serializeScimUser(args: {\n id: string;\n user: Record<string, any>;\n externalId?: string;\n active?: boolean;\n location?: string;\n}) {\n return {\n schemas: [SCIM_USER_SCHEMA_ID],\n id: args.id,\n externalId: args.externalId,\n meta: {\n resourceType: \"User\",\n location: args.location,\n },\n userName: args.user.email ?? args.user.phone ?? args.user.name ?? args.id,\n active: args.active ?? true,\n name:\n args.user.name !== undefined ? { formatted: args.user.name } : undefined,\n emails:\n typeof args.user.email === \"string\"\n ? [{ value: args.user.email, primary: true }]\n : undefined,\n phoneNumbers:\n typeof args.user.phone === \"string\"\n ? [{ value: args.user.phone, primary: true }]\n : undefined,\n displayName: args.user.name,\n };\n}\n\nexport function serializeScimGroup(args: {\n id: string;\n group: Record<string, any>;\n externalId?: string;\n members?: Array<{ value: string; display?: string }>;\n location?: string;\n}) {\n return {\n schemas: [SCIM_GROUP_SCHEMA_ID],\n id: args.id,\n externalId: args.externalId,\n meta: {\n resourceType: \"Group\",\n location: args.location,\n },\n displayName: args.group.name ?? args.id,\n members: args.members ?? [],\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sso.js","names":[],"sources":["../../src/server/sso.ts"],"sourcesContent":["import { sha256 } from \"@oslojs/crypto/sha2\";\nimport {\n decodeBase64urlIgnorePadding,\n encodeBase64urlNoPadding,\n} from \"@oslojs/encoding\";\nimport { Fx } from \"@robelest/fx\";\nimport {\n Constants,\n IdentityProvider,\n ServiceProvider,\n setSchemaValidator,\n} from \"@robelest/samlify\";\n\n// Samlify requires a schema validator to be registered before parsing any SAML\n// response. We use a permissive validator that always resolves because Convex's\n// edge runtime has no file-system access for XML schema files, and structural\n// correctness is already ensured by the XML parser. This is called directly\n// before each parse operation since Convex can restart the V8 isolate between\n// requests, resetting module-level state.\nconst _samlifyPermissiveValidator = {\n validate: (_xml: string) => Promise.resolve(\"OK\"),\n};\nfunction ensureSamlifyValidator() {\n setSchemaValidator(_samlifyPermissiveValidator);\n}\nimport { decodeIdToken } from \"arctic\";\nimport {\n createRemoteJWKSet,\n customFetch,\n decodeProtectedHeader,\n jwtVerify,\n} from \"jose\";\n\nimport type {\n EnterprisePolicy,\n EnterprisePolicyPatch,\n OAuthMaterializedConfig,\n OAuthProfile,\n SAMLAttributeMapping,\n} from \"./types\";\n\n/** @internal */\nexport type ParsedSamlMetadata = {\n issuer: string;\n sso: {\n redirect?: string;\n post?: string;\n };\n slo: {\n redirect?: string;\n post?: string;\n };\n signingCert: string | string[] | null;\n encryptionCert: string | string[] | null;\n nameIdFormats: string[];\n wantsSignedAuthnRequests: boolean;\n};\n\n/** @internal */\nexport type EnterpriseSamlSource = { kind: \"enterprise\"; id: string };\n\n/** @internal */\nexport type EnterpriseSamlRelayState = {\n source: EnterpriseSamlSource;\n signature: string;\n requestId: string;\n state: string;\n redirectTo?: string;\n};\n\n/** @internal */\nexport type EnterpriseSamlUrls = {\n metadataUrl: string;\n acsUrl: string;\n sloUrl?: string;\n};\n\n/** @internal */\nexport type EnterpriseSamlLoadedSource = {\n source: EnterpriseSamlSource;\n config: unknown;\n status?: string;\n};\n\n/** @internal */\nexport type EnterpriseSamlHttpRequest = {\n url: URL;\n body: Record<string, string>;\n query: Record<string, string>;\n binding: \"redirect\" | \"post\";\n relayState?: string;\n hasSamlRequest: boolean;\n hasSamlResponse: boolean;\n};\n\n/** @internal */\nexport type ScimListRequest = {\n startIndex: number;\n count: number;\n filter?: { attribute: string; value: string };\n};\n\n/** @internal */\nexport const SCIM_USER_SCHEMA_ID = \"urn:ietf:params:scim:schemas:core:2.0:User\";\n/** @internal */\nexport const SCIM_GROUP_SCHEMA_ID =\n \"urn:ietf:params:scim:schemas:core:2.0:Group\";\n\n/** @internal */\nexport const ENTERPRISE_OIDC_PROVIDER_PREFIX = \"enterprise:oidc:\";\n/** @internal */\nexport const ENTERPRISE_SAML_PROVIDER_PREFIX = \"enterprise:saml:\";\nconst OIDC_JWKS_CACHE = new Map<\n string,\n ReturnType<typeof createRemoteJWKSet>\n>();\n\n/** @internal */\nexport function normalizeDomain(domain: string): string {\n return domain.trim().toLowerCase().replace(/^@+/, \"\");\n}\n\n/** @internal */\nexport function enterpriseOidcProviderId(enterpriseId: string): string {\n return `${ENTERPRISE_OIDC_PROVIDER_PREFIX}${enterpriseId}`;\n}\n\n/** @internal */\nexport function enterpriseSamlProviderId(enterpriseId: string): string {\n return `${ENTERPRISE_SAML_PROVIDER_PREFIX}${enterpriseId}`;\n}\n\n/** @internal */\nexport function getEnterpriseSamlUrls(opts: {\n rootUrl: string;\n source: EnterpriseSamlSource;\n}): EnterpriseSamlUrls {\n const root = opts.rootUrl.replace(/\\/$/, \"\");\n const metadataBase = `${root}/api/auth/sso/${opts.source.id}/saml/metadata`;\n const acsBase = `${root}/api/auth/sso/${opts.source.id}/saml/acs`;\n const sloBase = `${root}/api/auth/sso/${opts.source.id}/saml/slo`;\n return {\n metadataUrl: metadataBase,\n acsUrl: acsBase,\n sloUrl: sloBase,\n };\n}\n\n/** @internal */\nexport function getEnterpriseOidcUrls(opts: {\n rootUrl: string;\n enterpriseId: string;\n}) {\n const root = opts.rootUrl.replace(/\\/$/, \"\");\n return {\n signInUrl: `${root}/api/auth/sso/${opts.enterpriseId}/oidc/signin`,\n callbackUrl: `${root}/api/auth/sso/${opts.enterpriseId}/oidc/callback`,\n };\n}\n\n/** @internal */\nexport function isEnterpriseSamlSourceActive(\n source: EnterpriseSamlLoadedSource,\n) {\n return source.status === \"active\";\n}\n\n/** @internal */\nexport function isEnterpriseProviderId(providerId: string): boolean {\n return (\n providerId.startsWith(ENTERPRISE_OIDC_PROVIDER_PREFIX) ||\n providerId.startsWith(ENTERPRISE_SAML_PROVIDER_PREFIX)\n );\n}\n\nconst asRecord = (value: unknown) =>\n typeof value === \"object\" && value !== null\n ? (value as Record<string, any>)\n : null;\n\n/** @internal */\nexport const DEFAULT_ENTERPRISE_POLICY: EnterprisePolicy = {\n version: 1,\n identity: {\n accountLinking: {\n oidc: \"verifiedEmail\",\n saml: \"verifiedEmail\",\n },\n },\n provisioning: {\n scimReuse: {\n user: \"externalId\",\n },\n jit: {\n mode: \"createUserAndMembership\",\n defaultRoleIds: [],\n },\n deprovision: {\n mode: \"soft\",\n },\n },\n};\n\n/** @internal */\nexport function normalizeEnterprisePolicy(policy: unknown): EnterprisePolicy {\n const input = asRecord(policy) ?? {};\n const identity = asRecord(input.identity) ?? {};\n const accountLinking = asRecord(identity.accountLinking) ?? {};\n const provisioning = asRecord(input.provisioning) ?? {};\n const scimReuse = asRecord(provisioning.scimReuse) ?? {};\n const jit = asRecord(provisioning.jit) ?? {};\n const deprovision = asRecord(provisioning.deprovision) ?? {};\n const extend = asRecord(input.extend) ?? undefined;\n\n return {\n version: 1,\n identity: {\n accountLinking: {\n oidc:\n accountLinking.oidc === \"none\"\n ? \"none\"\n : DEFAULT_ENTERPRISE_POLICY.identity.accountLinking.oidc,\n saml:\n accountLinking.saml === \"none\"\n ? \"none\"\n : DEFAULT_ENTERPRISE_POLICY.identity.accountLinking.saml,\n },\n },\n provisioning: {\n scimReuse: {\n user:\n scimReuse.user === \"none\"\n ? \"none\"\n : DEFAULT_ENTERPRISE_POLICY.provisioning.scimReuse.user,\n },\n jit: {\n mode:\n jit.mode === \"off\" ||\n jit.mode === \"createUser\" ||\n jit.mode === \"createUserAndMembership\"\n ? jit.mode\n : DEFAULT_ENTERPRISE_POLICY.provisioning.jit.mode,\n defaultRoleIds: Array.isArray(jit.defaultRoleIds)\n ? Array.from(\n new Set(\n jit.defaultRoleIds.filter(\n (value): value is string =>\n typeof value === \"string\" && value.length > 0,\n ),\n ),\n )\n : typeof jit.defaultRole === \"string\" && jit.defaultRole.length > 0\n ? [jit.defaultRole]\n : DEFAULT_ENTERPRISE_POLICY.provisioning.jit.defaultRoleIds,\n },\n deprovision: {\n mode:\n deprovision.mode === \"hard\"\n ? \"hard\"\n : DEFAULT_ENTERPRISE_POLICY.provisioning.deprovision.mode,\n },\n },\n ...(extend ? { extend } : {}),\n };\n}\n\n/** @internal */\nexport function patchEnterprisePolicy(\n current: unknown,\n patch: EnterprisePolicyPatch,\n): EnterprisePolicy {\n const base = normalizeEnterprisePolicy(current);\n return normalizeEnterprisePolicy({\n ...base,\n ...patch,\n identity: {\n ...base.identity,\n ...patch.identity,\n accountLinking: {\n ...base.identity.accountLinking,\n ...patch.identity?.accountLinking,\n },\n },\n provisioning: {\n ...base.provisioning,\n ...patch.provisioning,\n scimReuse: {\n ...base.provisioning.scimReuse,\n ...patch.provisioning?.scimReuse,\n },\n jit: {\n ...base.provisioning.jit,\n ...patch.provisioning?.jit,\n },\n deprovision: {\n ...base.provisioning.deprovision,\n ...patch.provisioning?.deprovision,\n },\n },\n extend:\n patch.extend === undefined\n ? base.extend\n : { ...base.extend, ...patch.extend },\n });\n}\n\nconst getProtocolConfig = (config: unknown, protocol: \"oidc\" | \"saml\") => {\n const base = asRecord(config);\n const direct = base?.[protocol];\n const viaProtocols = asRecord(base?.protocols)?.[protocol];\n return asRecord(direct) ?? asRecord(viaProtocols) ?? {};\n};\n\n/** @internal */\nexport function getOidcConfig(config: unknown): Record<string, any> {\n return getProtocolConfig(config, \"oidc\");\n}\n\n/** @internal */\nexport function getPublicOidcConfig(config: unknown): Record<string, any> {\n const oidc = getOidcConfig(config);\n const { clientSecret: _clientSecret, ...publicOidc } = oidc;\n return publicOidc;\n}\n\n/** @internal */\nexport function withOidcSecretState(\n config: Record<string, any>,\n hasClientSecret: boolean,\n) {\n return {\n ...config,\n hasClientSecret,\n };\n}\n\n/** @internal */\nexport function getSamlConfig(config: unknown): Record<string, any> {\n return getProtocolConfig(config, \"saml\");\n}\n\n/** @internal */\nexport function upsertProtocolConfig(\n config: unknown,\n protocol: \"oidc\" | \"saml\",\n protocolConfig: Record<string, unknown>,\n) {\n const base = asRecord(config) ?? {};\n const protocols = asRecord(base.protocols) ?? {};\n protocols[protocol] = {\n ...asRecord(protocols[protocol]),\n ...protocolConfig,\n };\n return { ...base, protocols };\n}\n\n/** @internal */\nexport function createSamlPostBindingResponse(opts: {\n endpoint: string;\n parameter: \"SAMLRequest\" | \"SAMLResponse\";\n value: string;\n relayState?: string;\n}) {\n const fields = [\n `<input type=\"hidden\" name=\"${opts.parameter}\" value=\"${opts.value.replace(/\"/g, \""\")}\" />`,\n opts.relayState\n ? `<input type=\"hidden\" name=\"RelayState\" value=\"${opts.relayState.replace(/\"/g, \""\")}\" />`\n : \"\",\n ].join(\"\");\n return new Response(\n `<!doctype html><html><body><form method=\"POST\" action=\"${opts.endpoint}\">${fields}</form><script>document.forms[0].submit();</script></body></html>`,\n { status: 200, headers: { \"Content-Type\": \"text/html; charset=utf-8\" } },\n );\n}\n\n/** @internal */\nexport function decodeRelayState(\n value: string | null,\n): Record<string, unknown> {\n if (!value) {\n return {};\n }\n try {\n return JSON.parse(\n new TextDecoder().decode(decodeBase64urlIgnorePadding(value)),\n );\n } catch {\n return {};\n }\n}\n\n/** @internal */\nexport function encodeEnterpriseSamlRelayState(\n value: EnterpriseSamlRelayState,\n) {\n return encodeBase64urlNoPadding(\n new TextEncoder().encode(\n JSON.stringify({\n source: `${value.source.kind}:${value.source.id}`,\n signature: value.signature,\n requestId: value.requestId,\n state: value.state,\n redirectTo: value.redirectTo,\n }),\n ),\n );\n}\n\n/** @internal */\nexport function decodeEnterpriseSamlRelayStateOrThrow(\n value: string | null,\n): EnterpriseSamlRelayState {\n if (!value) {\n throw new Error(\"Missing SAML RelayState.\");\n }\n const decoded = decodeRelayState(value);\n if (\n typeof decoded.source !== \"string\" ||\n typeof decoded.signature !== \"string\" ||\n typeof decoded.requestId !== \"string\" ||\n typeof decoded.state !== \"string\"\n ) {\n throw new Error(\"Invalid SAML RelayState.\");\n }\n const [kind, ...rest] = decoded.source.split(\":\");\n const id = rest.join(\":\");\n if (kind !== \"enterprise\" || id.length === 0) {\n throw new Error(\"Invalid enterprise SAML source.\");\n }\n return {\n source: { kind, id } as EnterpriseSamlSource,\n signature: decoded.signature,\n requestId: decoded.requestId,\n state: decoded.state,\n redirectTo:\n typeof decoded.redirectTo === \"string\" ? decoded.redirectTo : undefined,\n };\n}\n\n/** @internal */\nexport async function readRequestBody(\n request: Request,\n): Promise<Record<string, string>> {\n const contentType = request.headers.get(\"Content-Type\") ?? \"\";\n if (\n contentType.includes(\"application/x-www-form-urlencoded\") ||\n contentType.includes(\"multipart/form-data\")\n ) {\n const form = await request.formData();\n const body: Record<string, string> = {};\n form.forEach((value, key) => {\n body[key] = typeof value === \"string\" ? value : value.name;\n });\n return body;\n }\n return {};\n}\n\n/** @internal */\nexport async function readEnterpriseSamlHttpRequest(\n request: Request,\n): Promise<EnterpriseSamlHttpRequest> {\n const url = new URL(request.url);\n const body = await readRequestBody(request);\n const query = Object.fromEntries(url.searchParams);\n const binding =\n request.method === \"GET\"\n ? \"redirect\"\n : body.SAMLResponse || body.SAMLRequest\n ? \"post\"\n : \"redirect\";\n return {\n url,\n body,\n query,\n binding,\n relayState:\n body.RelayState ?? url.searchParams.get(\"RelayState\") ?? undefined,\n hasSamlRequest: Boolean(\n body.SAMLRequest ?? url.searchParams.get(\"SAMLRequest\"),\n ),\n hasSamlResponse: Boolean(\n body.SAMLResponse ?? url.searchParams.get(\"SAMLResponse\"),\n ),\n };\n}\n\nasync function discoverOidcConfiguration(config: Record<string, any>) {\n const discoveryUrl =\n typeof config.discoveryUrl === \"string\"\n ? config.discoveryUrl\n : typeof config.issuer === \"string\"\n ? `${config.issuer.replace(/\\/$/, \"\")}/.well-known/openid-configuration`\n : null;\n\n if (!discoveryUrl) {\n throw new Error(\"Enterprise OIDC requires an issuer or discoveryUrl.\");\n }\n\n const oidcFetch = createEnterpriseOidcFetch(config, config.issuer);\n\n return await Fx.run(\n Fx.defer(() =>\n Fx.from({\n ok: async () => {\n const response = await oidcFetch(discoveryUrl);\n if (!response.ok) {\n throw new Error(\n `Failed to discover OIDC configuration: ${response.status}`,\n );\n }\n const discovery = (await response.json()) as Record<string, any>;\n if (\n typeof discovery.issuer !== \"string\" ||\n typeof discovery.authorization_endpoint !== \"string\" ||\n typeof discovery.token_endpoint !== \"string\" ||\n typeof discovery.jwks_uri !== \"string\"\n ) {\n throw new Error(\n \"OIDC discovery document is missing required fields.\",\n );\n }\n return discovery;\n },\n err: (error) =>\n error instanceof Error ? error : new Error(String(error)),\n }),\n ).pipe(\n Fx.timeout(10_000),\n Fx.retry(\n Fx.retry.compose(\n Fx.retry.jittered(Fx.retry.exponential(200)),\n Fx.retry.recurs(2),\n ),\n ),\n Fx.recover((error) =>\n Fx.fail(error instanceof Error ? error : new Error(String(error))),\n ),\n ),\n );\n}\n\nfunction createEnterpriseOidcFetch(\n config: Record<string, any>,\n discoveredIssuer?: string,\n) {\n const runtimeOrigin =\n typeof config.discoveryUrl === \"string\"\n ? new URL(config.discoveryUrl).origin\n : undefined;\n const externalHost =\n typeof config.issuer === \"string\"\n ? new URL(config.issuer).host\n : typeof discoveredIssuer === \"string\"\n ? new URL(discoveredIssuer).host\n : undefined;\n\n return async (input: string | URL, init?: RequestInit) => {\n const url = new URL(typeof input === \"string\" ? input : input.toString());\n const rewrittenUrl =\n runtimeOrigin !== undefined && url.origin !== runtimeOrigin\n ? new URL(`${runtimeOrigin}${url.pathname}${url.search}`)\n : url;\n const headers = new Headers(init?.headers);\n if (runtimeOrigin !== undefined && externalHost !== undefined) {\n headers.set(\"host\", externalHost);\n }\n return await fetch(rewrittenUrl, { ...init, headers });\n };\n}\n\nfunction getOidcJwks(\n url: string,\n fetchImpl?: ReturnType<typeof createEnterpriseOidcFetch>,\n) {\n const cacheKey = fetchImpl ? `${url}::custom` : url;\n let jwks = OIDC_JWKS_CACHE.get(cacheKey);\n if (!jwks) {\n jwks = fetchImpl\n ? createRemoteJWKSet(new URL(url), { [customFetch]: fetchImpl })\n : createRemoteJWKSet(new URL(url));\n OIDC_JWKS_CACHE.set(cacheKey, jwks);\n }\n return jwks;\n}\n\ntype UserInfoFetchFailure =\n | { kind: \"transport\"; error: unknown }\n | { kind: \"subject-mismatch\" };\n\nfunction userInfoProfileFx(opts: {\n endpoint: string;\n accessToken: string;\n verifiedClaims: Record<string, unknown>;\n verifiedProfile: OAuthProfile & { emailVerified?: boolean };\n fetchImpl?: ReturnType<typeof createEnterpriseOidcFetch>;\n}) {\n return Fx.from({\n ok: async () => {\n const response = await (opts.fetchImpl ?? fetch)(opts.endpoint, {\n headers: { Authorization: `Bearer ${opts.accessToken}` },\n });\n if (!response.ok) {\n throw new Error(`OIDC userinfo request failed: ${response.status}`);\n }\n return (await response.json()) as Record<string, unknown>;\n },\n err: (error): UserInfoFetchFailure => ({ kind: \"transport\", error }),\n }).pipe(\n Fx.chain((userInfo) => {\n const userInfoSubject =\n typeof userInfo.sub === \"string\" ? userInfo.sub : undefined;\n const tokenSubject =\n typeof opts.verifiedClaims.sub === \"string\"\n ? opts.verifiedClaims.sub\n : undefined;\n return userInfoSubject !== undefined &&\n tokenSubject !== undefined &&\n userInfoSubject !== tokenSubject\n ? Fx.fail({ kind: \"subject-mismatch\" } as const)\n : Fx.succeed({\n id:\n userInfoSubject ??\n (typeof opts.verifiedClaims.sub === \"string\"\n ? opts.verifiedClaims.sub\n : undefined) ??\n crypto.randomUUID(),\n email:\n typeof userInfo.email === \"string\"\n ? userInfo.email\n : opts.verifiedProfile.email,\n emailVerified:\n typeof userInfo.email_verified === \"boolean\"\n ? userInfo.email_verified\n : opts.verifiedProfile.emailVerified,\n name:\n typeof userInfo.name === \"string\"\n ? userInfo.name\n : opts.verifiedProfile.name,\n image:\n typeof userInfo.picture === \"string\"\n ? userInfo.picture\n : opts.verifiedProfile.image,\n } as OAuthProfile & { emailVerified?: boolean });\n }),\n Fx.recover((failure) => {\n if (failure.kind === \"transport\") {\n return Fx.succeed(null);\n }\n return Fx.fail(\n new Error(\"OIDC userinfo subject does not match ID token subject.\"),\n );\n }),\n );\n}\n\n/** @internal */\nexport async function createEnterpriseOidcProvider(\n config: Record<string, any>,\n redirectUri: string,\n) {\n const discovery = await discoverOidcConfiguration(config);\n const expectedIssuer = String(config.issuer ?? discovery.issuer).replace(\n /\\/$/,\n \"\",\n );\n const discoveredIssuer = String(discovery.issuer).replace(/\\/$/, \"\");\n const strictIssuer = config.strictIssuer === true;\n if (\n typeof config.issuer === \"string\" &&\n expectedIssuer !== discoveredIssuer\n ) {\n if (strictIssuer) {\n throw new Error(\n `Configured OIDC issuer mismatch. configured=${expectedIssuer} discovery=${discoveredIssuer}`,\n );\n }\n console.warn(\n \"Configured OIDC issuer differs from discovery issuer; accepting both for token verification.\",\n {\n configuredIssuer: expectedIssuer,\n discoveryIssuer: discoveredIssuer,\n },\n );\n }\n const authorizationEndpoint = discovery.authorization_endpoint as string;\n const tokenEndpoint = discovery.token_endpoint as string;\n const jwksUri = String(config.jwksUri ?? discovery.jwks_uri);\n const supportedIdTokenSigningAlgs = Array.isArray(\n discovery.id_token_signing_alg_values_supported,\n )\n ? discovery.id_token_signing_alg_values_supported.filter(\n (value: unknown): value is string => typeof value === \"string\",\n )\n : [];\n const userinfoEndpoint =\n (discovery.userinfo_endpoint as string | undefined) ?? undefined;\n const oidcFetch = createEnterpriseOidcFetch(\n config,\n discovery.issuer as string,\n );\n const scopes = Array.isArray(config.scopes)\n ? config.scopes.filter(\n (value: unknown): value is string => typeof value === \"string\",\n )\n : [\"openid\", \"profile\", \"email\"];\n const expectedAudience = config.audience ?? String(config.clientId);\n const getIssuerCandidates = (issuer: string) => {\n const candidates = [issuer];\n if (issuer.startsWith(\"https://\")) {\n candidates.push(`http://${issuer.slice(\"https://\".length)}`);\n } else if (issuer.startsWith(\"http://\")) {\n candidates.push(`https://${issuer.slice(\"http://\".length)}`);\n }\n return candidates;\n };\n const expectedIssuers = strictIssuer\n ? [expectedIssuer]\n : Array.from(\n new Set([\n ...getIssuerCandidates(expectedIssuer),\n ...getIssuerCandidates(discoveredIssuer),\n ]),\n );\n const jwks = getOidcJwks(jwksUri, oidcFetch);\n let verifiedClaims: Record<string, unknown> | null = null;\n let verifiedProfile: (OAuthProfile & { emailVerified?: boolean }) | null =\n null;\n const normalizeProfile = (claims: Record<string, unknown>) => ({\n id: typeof claims.sub === \"string\" ? claims.sub : crypto.randomUUID(),\n email: typeof claims.email === \"string\" ? claims.email : undefined,\n emailVerified:\n typeof claims.email_verified === \"boolean\"\n ? claims.email_verified\n : undefined,\n name: typeof claims.name === \"string\" ? claims.name : undefined,\n image: typeof claims.picture === \"string\" ? claims.picture : undefined,\n });\n\n const provider = {\n createAuthorizationURL(\n state: string,\n codeVerifier: string,\n requestedScopes: string[],\n ) {\n const url = new URL(authorizationEndpoint);\n url.searchParams.set(\"response_type\", \"code\");\n url.searchParams.set(\"client_id\", String(config.clientId));\n url.searchParams.set(\"redirect_uri\", redirectUri);\n url.searchParams.set(\n \"scope\",\n (requestedScopes.length > 0 ? requestedScopes : scopes).join(\" \"),\n );\n url.searchParams.set(\"state\", state);\n url.searchParams.set(\"code_challenge_method\", \"S256\");\n url.searchParams.set(\n \"code_challenge\",\n encodeBase64urlNoPadding(\n sha256(new TextEncoder().encode(codeVerifier)),\n ),\n );\n const authorizationParams =\n typeof config.authorizationParams === \"object\" &&\n config.authorizationParams !== null\n ? (config.authorizationParams as Record<string, unknown>)\n : {};\n for (const [key, value] of Object.entries(authorizationParams)) {\n if (typeof value === \"string\") {\n url.searchParams.set(key, value);\n }\n }\n return url;\n },\n async validateAuthorizationCode(code: string, codeVerifier?: string) {\n const body = new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n redirect_uri: redirectUri,\n client_id: String(config.clientId),\n });\n if (typeof config.clientSecret === \"string\") {\n body.set(\"client_secret\", config.clientSecret);\n }\n if (codeVerifier) {\n body.set(\"code_verifier\", codeVerifier);\n }\n const response = await oidcFetch(tokenEndpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body,\n });\n if (!response.ok) {\n throw new Error(`OIDC token exchange failed: ${response.status}`);\n }\n const data = (await response.json()) as Record<string, any>;\n return {\n data,\n idToken() {\n if (typeof data.id_token !== \"string\") {\n throw new Error(\"OIDC response is missing id_token.\");\n }\n return data.id_token;\n },\n accessToken() {\n if (typeof data.access_token !== \"string\") {\n throw new Error(\"OIDC response is missing access_token.\");\n }\n return data.access_token;\n },\n };\n },\n };\n\n const oauthConfig = {\n scopes,\n nonce: true,\n validateTokens: async (tokens: any, ctx: { nonce?: string }) => {\n const verified = await Fx.run(\n Fx.gen(function* () {\n yield* Fx.guard(\n ctx.nonce === undefined,\n Fx.fail(new Error(\"OIDC nonce is required.\")),\n );\n\n const idToken = tokens.idToken();\n const protectedHeader = decodeProtectedHeader(idToken);\n const tokenAlg = protectedHeader.alg;\n const useSymmetricValidation =\n typeof tokenAlg === \"string\" &&\n (tokenAlg === \"HS256\" ||\n tokenAlg === \"HS384\" ||\n tokenAlg === \"HS512\") &&\n supportedIdTokenSigningAlgs.includes(tokenAlg);\n\n const verificationOptions = {\n audience: expectedAudience,\n requiredClaims: [\"iss\", \"sub\", \"aud\", \"exp\", \"iat\"],\n clockTolerance: config.clockToleranceSeconds ?? 10,\n } as const;\n\n const verification = yield* Fx.from({\n ok: () =>\n useSymmetricValidation\n ? jwtVerify(\n idToken,\n (() => {\n if (typeof config.clientSecret !== \"string\") {\n throw new Error(\n \"OIDC provider uses symmetric ID token signatures but clientSecret is missing.\",\n );\n }\n return new TextEncoder().encode(config.clientSecret);\n })(),\n verificationOptions as any,\n )\n : jwtVerify(idToken, jwks as any, verificationOptions as any),\n err: (error) =>\n error instanceof Error ? error : new Error(String(error)),\n });\n\n const payload = verification.payload as Record<string, unknown>;\n const tokenIssuerRaw =\n typeof payload.iss === \"string\" ? payload.iss : undefined;\n const tokenIssuer =\n typeof tokenIssuerRaw === \"string\"\n ? tokenIssuerRaw.replace(/\\/$/, \"\")\n : undefined;\n\n yield* Fx.guard(\n !tokenIssuer || !expectedIssuers.includes(tokenIssuer),\n Fx.fail(\n new Error(\n `OIDC token issuer mismatch. Received: ${tokenIssuer ?? \"<missing>\"}. Expected one of: ${expectedIssuers.join(\", \")}`,\n ),\n ),\n );\n\n yield* Fx.guard(\n payload.nonce !== ctx.nonce,\n Fx.fail(new Error(\"OIDC nonce mismatch.\")),\n );\n\n yield* Fx.guard(\n Array.isArray(payload.aud) &&\n payload.aud.length > 1 &&\n payload.azp !== String(config.clientId),\n Fx.fail(\n new Error(\"OIDC authorized party does not match client ID.\"),\n ),\n );\n\n return payload;\n }),\n );\n\n verifiedClaims = verified;\n verifiedProfile = normalizeProfile(verified);\n },\n accountLinking: config.accountLinking,\n profile: async (tokens: any): Promise<OAuthProfile> => {\n if (verifiedProfile === null || verifiedClaims === null) {\n const claims = decodeIdToken(tokens.idToken()) as Record<\n string,\n unknown\n >;\n verifiedClaims = claims;\n verifiedProfile = normalizeProfile(claims);\n }\n if (userinfoEndpoint && typeof tokens.accessToken === \"function\") {\n const userInfoProfile = await Fx.run(\n userInfoProfileFx({\n endpoint: userinfoEndpoint,\n accessToken: tokens.accessToken(),\n verifiedClaims,\n verifiedProfile,\n fetchImpl: oidcFetch,\n }),\n );\n if (userInfoProfile !== null) {\n return userInfoProfile;\n }\n }\n return verifiedProfile;\n },\n } as const;\n\n return { provider, oauthConfig };\n}\n\n/** @internal */\nexport function createSyntheticOAuthMaterializedConfig(\n providerId: string,\n options?: {\n accountLinking?: OAuthMaterializedConfig[\"accountLinking\"];\n },\n): OAuthMaterializedConfig {\n return {\n id: providerId,\n type: \"oauth\",\n provider: null,\n scopes: [],\n accountLinking: options?.accountLinking ?? \"verifiedEmail\",\n };\n}\n\n/** @internal */\nexport function parseSamlIdpMetadata(metadata: string): ParsedSamlMetadata {\n const idp = IdentityProvider({ metadata });\n const entityMeta = idp.entityMeta;\n\n const normalizeService = (value: unknown): string | undefined => {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n };\n\n return {\n issuer: entityMeta.getEntityID(),\n sso: {\n redirect: normalizeService(entityMeta.getSingleSignOnService(\"redirect\")),\n post: normalizeService(entityMeta.getSingleSignOnService(\"post\")),\n },\n slo: {\n redirect: normalizeService(entityMeta.getSingleLogoutService(\"redirect\")),\n post: normalizeService(entityMeta.getSingleLogoutService(\"post\")),\n },\n signingCert: entityMeta.getX509Certificate(\"signing\"),\n encryptionCert: entityMeta.getX509Certificate(\"encrypt\"),\n nameIdFormats: (() => {\n const nameIdFormat = entityMeta.getNameIDFormat();\n return Array.isArray(nameIdFormat) ? nameIdFormat : [];\n })(),\n wantsSignedAuthnRequests: entityMeta.isWantAuthnRequestsSigned(),\n };\n}\n\n/** @internal */\nexport function createServiceProviderMetadata(opts: {\n entityId: string;\n acsUrl: string;\n sloUrl?: string;\n authnRequestsSigned?: boolean;\n signingCert?: string | string[];\n encryptCert?: string | string[];\n privateKey?: string;\n privateKeyPass?: string;\n encPrivateKey?: string;\n encPrivateKeyPass?: string;\n}) {\n const binding = Constants.namespace.binding;\n const sp = ServiceProvider({\n entityID: opts.entityId,\n authnRequestsSigned: opts.authnRequestsSigned ?? false,\n privateKey: opts.privateKey,\n privateKeyPass: opts.privateKeyPass,\n signingCert: opts.signingCert,\n encryptCert: opts.encryptCert,\n encPrivateKey: opts.encPrivateKey,\n encPrivateKeyPass: opts.encPrivateKeyPass,\n assertionConsumerService: [\n {\n Binding: binding.post,\n Location: opts.acsUrl,\n },\n ],\n singleLogoutService: opts.sloUrl\n ? [\n {\n Binding: binding.redirect,\n Location: opts.sloUrl,\n },\n {\n Binding: binding.post,\n Location: opts.sloUrl,\n },\n ]\n : undefined,\n });\n return sp.getMetadata();\n}\n\n/** @internal */\nexport function createEnterpriseSamlMetadataXml(opts: {\n rootUrl: string;\n source: EnterpriseSamlSource;\n config: unknown;\n}) {\n return createServiceProviderMetadata(\n getSamlServiceProviderOptions({\n rootUrl: opts.rootUrl,\n source: opts.source,\n config: opts.config,\n }),\n );\n}\n\n/** @internal */\nexport function getSamlServiceProviderOptions(opts: {\n rootUrl: string;\n source: EnterpriseSamlSource;\n config: unknown;\n overrides?: {\n entityId?: string;\n acsUrl?: string;\n sloUrl?: string;\n };\n relayState?: string;\n}) {\n const saml = getSamlConfig(opts.config);\n const sp = asRecord(saml.sp) ?? {};\n const urls = getEnterpriseSamlUrls({\n rootUrl: opts.rootUrl,\n source: opts.source,\n });\n return {\n entityId: opts.overrides?.entityId ?? sp.entityId ?? urls.metadataUrl,\n acsUrl: opts.overrides?.acsUrl ?? sp.acsUrl ?? urls.acsUrl,\n sloUrl: opts.overrides?.sloUrl ?? sp.sloUrl ?? urls.sloUrl,\n relayState: opts.relayState,\n authnRequestsSigned: saml.signAuthnRequests,\n signingCert: sp.signingCert,\n encryptCert: sp.encryptCert,\n privateKey: sp.privateKey,\n privateKeyPass: sp.privateKeyPass,\n encPrivateKey: sp.encPrivateKey,\n encPrivateKeyPass: sp.encPrivateKeyPass,\n };\n}\n\n/** @internal */\nexport function createSamlServiceProvider(opts: {\n entityId: string;\n acsUrl: string;\n sloUrl?: string;\n relayState?: string;\n authnRequestsSigned?: boolean;\n signingCert?: string | string[];\n encryptCert?: string | string[];\n privateKey?: string;\n privateKeyPass?: string;\n encPrivateKey?: string;\n encPrivateKeyPass?: string;\n}) {\n const binding = Constants.namespace.binding;\n return ServiceProvider({\n entityID: opts.entityId,\n relayState: opts.relayState ?? \"\",\n authnRequestsSigned: opts.authnRequestsSigned ?? false,\n privateKey: opts.privateKey,\n privateKeyPass: opts.privateKeyPass,\n signingCert: opts.signingCert,\n encryptCert: opts.encryptCert,\n encPrivateKey: opts.encPrivateKey,\n encPrivateKeyPass: opts.encPrivateKeyPass,\n assertionConsumerService: [\n {\n Binding: binding.post,\n Location: opts.acsUrl,\n },\n ],\n singleLogoutService: opts.sloUrl\n ? [\n { Binding: binding.redirect, Location: opts.sloUrl },\n { Binding: binding.post, Location: opts.sloUrl },\n ]\n : undefined,\n });\n}\n\n/** @internal */\nexport function createEnterpriseSamlRuntime(opts: {\n rootUrl: string;\n source: EnterpriseSamlSource;\n config: unknown;\n relayState?: string;\n overrides?: {\n entityId?: string;\n acsUrl?: string;\n sloUrl?: string;\n };\n}) {\n const saml = getSamlConfig(opts.config);\n const spOptions = getSamlServiceProviderOptions({\n rootUrl: opts.rootUrl,\n source: opts.source,\n config: opts.config,\n relayState: opts.relayState,\n overrides: opts.overrides,\n });\n if (typeof saml.idp?.metadataXml !== \"string\") {\n throw new Error(\"SAML IdP metadata is missing.\");\n }\n return {\n saml,\n sp: createSamlServiceProvider(spOptions),\n idp: IdentityProvider({ metadata: saml.idp.metadataXml }),\n urls: getEnterpriseSamlUrls({ rootUrl: opts.rootUrl, source: opts.source }),\n };\n}\n\n/** @internal */\nexport function createEnterpriseSamlSignInRequest(opts: {\n rootUrl: string;\n source: EnterpriseSamlSource;\n config: unknown;\n state: string;\n signature: string;\n redirectTo?: string;\n}) {\n const runtime = createEnterpriseSamlRuntime({\n rootUrl: opts.rootUrl,\n source: opts.source,\n config: opts.config,\n });\n const binding = runtime.saml.idp.sso?.redirect ? \"redirect\" : \"post\";\n const loginRequest = runtime.sp.createLoginRequest(\n runtime.idp,\n binding as any,\n ) as any;\n const relayState = encodeEnterpriseSamlRelayState({\n source: opts.source,\n signature: opts.signature,\n requestId: loginRequest.id,\n state: opts.state,\n redirectTo: opts.redirectTo,\n });\n return {\n requestId: loginRequest.id as string,\n binding,\n relayState,\n redirectUrl:\n binding === \"redirect\"\n ? (() => {\n const redirectUrl = new URL(loginRequest.context);\n redirectUrl.searchParams.set(\"RelayState\", relayState);\n return redirectUrl.toString();\n })()\n : undefined,\n post:\n binding === \"post\"\n ? {\n endpoint: loginRequest.entityEndpoint as string,\n value: loginRequest.context as string,\n }\n : undefined,\n };\n}\n\n/** @internal */\nexport async function parseEnterpriseSamlLoginResponse(opts: {\n request: Request;\n rootUrl: string;\n source: EnterpriseSamlSource;\n config: unknown;\n}) {\n ensureSamlifyValidator();\n const httpRequest = await readEnterpriseSamlHttpRequest(opts.request);\n const runtime = createEnterpriseSamlRuntime({\n rootUrl: opts.rootUrl,\n source: opts.source,\n config: opts.config,\n });\n const parsed = (await runtime.sp.parseLoginResponse(\n runtime.idp as any,\n httpRequest.binding as any,\n {\n query: httpRequest.query,\n body: httpRequest.body,\n },\n )) as any;\n // Check for deprecated SAML algorithms and warn\n warnDeprecatedSamlAlgorithms(parsed);\n\n return {\n ...httpRequest,\n runtime,\n parsed,\n relayState: decodeEnterpriseSamlRelayStateOrThrow(\n httpRequest.relayState ?? null,\n ),\n };\n}\n\nconst DEPRECATED_SAML_ALGORITHMS = new Set([\n // Signature algorithms\n \"http://www.w3.org/2000/09/xmldsig#rsa-sha1\",\n \"http://www.w3.org/2000/09/xmldsig#dsa-sha1\",\n // Digest algorithms\n \"http://www.w3.org/2000/09/xmldsig#sha1\",\n // Key encryption\n \"http://www.w3.org/2001/04/xmlenc#rsa-1_5\",\n // Data encryption\n \"http://www.w3.org/2001/04/xmlenc#tripledes-cbc\",\n]);\n\n/**\n * Warn when the SAML response uses deprecated cryptographic algorithms\n * (SHA-1, RSA 1.5, 3DES). These are still accepted for compatibility\n * but should be flagged.\n */\nfunction warnDeprecatedSamlAlgorithms(parsed: any) {\n try {\n const sigAlg =\n parsed?.extract?.signature?.signatureAlgorithm ??\n parsed?.extract?.response?.signatureAlgorithm;\n const digestAlg = parsed?.extract?.signature?.digestAlgorithm;\n\n if (sigAlg && DEPRECATED_SAML_ALGORITHMS.has(sigAlg)) {\n console.warn(\n `[convex-auth] SAML response uses deprecated signature algorithm: ${sigAlg}. ` +\n `Consider upgrading your IdP to use RSA-SHA256 or stronger.`,\n );\n }\n if (digestAlg && DEPRECATED_SAML_ALGORITHMS.has(digestAlg)) {\n console.warn(\n `[convex-auth] SAML response uses deprecated digest algorithm: ${digestAlg}. ` +\n `Consider upgrading your IdP to use SHA-256 or stronger.`,\n );\n }\n } catch {\n // Non-critical — don't break auth flow for algorithm check failures\n }\n}\n\n/** @internal */\nexport function validateEnterpriseSamlLoginRelayState(opts: {\n relayState: EnterpriseSamlRelayState;\n source: EnterpriseSamlSource;\n inResponseTo?: string;\n}) {\n if (\n opts.relayState.source.kind !== opts.source.kind ||\n opts.relayState.source.id !== opts.source.id ||\n opts.relayState.requestId !== opts.inResponseTo\n ) {\n throw new Error(\"SAML RelayState did not match the pending login request.\");\n }\n}\n\n/** @internal */\nexport async function parseEnterpriseSamlLogoutMessage(opts: {\n request: Request;\n rootUrl: string;\n source: EnterpriseSamlSource;\n config: unknown;\n}) {\n ensureSamlifyValidator();\n const httpRequest = await readEnterpriseSamlHttpRequest(opts.request);\n const runtime = createEnterpriseSamlRuntime({\n rootUrl: opts.rootUrl,\n source: opts.source,\n config: opts.config,\n relayState: httpRequest.relayState,\n });\n const parsedRequest = httpRequest.hasSamlRequest\n ? ((await runtime.sp.parseLogoutRequest(\n runtime.idp as any,\n httpRequest.binding as any,\n {\n query: httpRequest.query,\n body: httpRequest.body,\n },\n )) as any)\n : undefined;\n return {\n ...httpRequest,\n runtime,\n parsedRequest,\n };\n}\n\n/** @internal */\nexport async function createEnterpriseOidcRuntime(opts: {\n rootUrl: string;\n enterpriseId: string;\n oidc: Record<string, any>;\n}) {\n const providerId = enterpriseOidcProviderId(opts.enterpriseId);\n const urls = getEnterpriseOidcUrls({\n rootUrl: opts.rootUrl,\n enterpriseId: opts.enterpriseId,\n });\n const { provider, oauthConfig } = await createEnterpriseOidcProvider(\n opts.oidc,\n urls.callbackUrl,\n );\n return {\n oidc: opts.oidc,\n providerId,\n provider,\n oauthConfig,\n ...urls,\n };\n}\n\n/** @internal */\nexport function profileFromSamlExtract(\n extract: any,\n mapping?: SAMLAttributeMapping,\n) {\n const attributes =\n typeof extract?.attributes === \"object\" && extract.attributes !== null\n ? (extract.attributes as Record<string, unknown>)\n : {};\n const resolveFirst = (...keys: Array<string | undefined>) => {\n for (const key of keys) {\n if (!key) {\n continue;\n }\n const attribute = attributes[key];\n const value = Array.isArray(attribute) ? attribute[0] : attribute;\n if (value !== undefined) {\n return value;\n }\n }\n return undefined;\n };\n const fieldResolvers = {\n email: () => resolveFirst(mapping?.email),\n name: () =>\n resolveFirst(mapping?.name) ??\n ([resolveFirst(mapping?.firstName), resolveFirst(mapping?.lastName)]\n .filter(Boolean)\n .join(\" \") ||\n undefined),\n subject: () =>\n resolveFirst(mapping?.subject) ?? (extract?.nameID as string | undefined),\n } as const;\n const subject = fieldResolvers.subject() as string | undefined;\n if (subject === undefined) {\n throw new Error(\n \"SAML profile is missing a subject. Configure `attributeMapping.subject` or ensure the assertion includes a NameID.\",\n );\n }\n const email = fieldResolvers.email() as string | undefined;\n const name = fieldResolvers.name() as string | undefined;\n return {\n id: subject,\n email,\n emailVerified: typeof email === \"string\" ? true : undefined,\n name,\n samlAttributes: attributes,\n samlSessionIndex: extract?.sessionIndex?.SessionIndex as string | undefined,\n };\n}\n\n/** @internal */\nexport function parseScimPath(pathname: string) {\n const parts = pathname.split(\"/\").filter(Boolean);\n const [api, auth, sso, enterpriseId, protocol, version, ...rest] = parts;\n\n if (\n api !== \"api\" ||\n auth !== \"auth\" ||\n sso !== \"sso\" ||\n !enterpriseId ||\n enterpriseId === \"setup\" ||\n protocol !== \"scim\" ||\n version !== \"v2\"\n ) {\n return {\n enterpriseId: \"\",\n resource: \"\",\n resourceId: undefined,\n };\n }\n\n return {\n enterpriseId,\n resource: rest[0] ?? \"\",\n resourceId: rest[1],\n };\n}\n\n/** @internal */\nexport function parseScimListRequest(url: URL): ScimListRequest {\n const startIndex = Math.max(\n 1,\n Number(url.searchParams.get(\"startIndex\") ?? \"1\"),\n );\n const count = Math.min(\n 100,\n Math.max(1, Number(url.searchParams.get(\"count\") ?? \"100\")),\n );\n const filterParam = url.searchParams.get(\"filter\");\n const filter = filterParam\n ? (() => {\n const match = filterParam.match(/^([A-Za-z0-9_.]+)\\s+eq\\s+\"([^\"]+)\"$/);\n if (!match) {\n throw new Error(\"Unsupported SCIM filter.\");\n }\n return { attribute: match[1]!, value: match[2]! };\n })()\n : undefined;\n return { startIndex, count, filter };\n}\n\n/** @internal */\nexport function scimJson(data: unknown, status = 200, headers?: HeadersInit) {\n const responseHeaders = new Headers({\n \"Content-Type\": \"application/scim+json\",\n });\n if (headers) {\n new Headers(headers).forEach((value, key) => {\n responseHeaders.set(key, value);\n });\n }\n return new Response(JSON.stringify(data), {\n status,\n headers: responseHeaders,\n });\n}\n\n/** @internal */\nexport function scimError(status: number, scimType: string, detail: string) {\n return scimJson(\n {\n schemas: [\"urn:ietf:params:scim:api:messages:2.0:Error\"],\n status: String(status),\n scimType,\n detail,\n },\n status,\n );\n}\n\n/** @internal */\nexport function serializeScimUser(args: {\n id: string;\n user: Record<string, any>;\n externalId?: string;\n active?: boolean;\n location?: string;\n}) {\n return {\n schemas: [SCIM_USER_SCHEMA_ID],\n id: args.id,\n externalId: args.externalId,\n meta: {\n resourceType: \"User\",\n location: args.location,\n },\n userName: args.user.email ?? args.user.phone ?? args.user.name ?? args.id,\n active: args.active ?? true,\n name:\n args.user.name !== undefined ? { formatted: args.user.name } : undefined,\n emails:\n typeof args.user.email === \"string\"\n ? [{ value: args.user.email, primary: true }]\n : undefined,\n phoneNumbers:\n typeof args.user.phone === \"string\"\n ? [{ value: args.user.phone, primary: true }]\n : undefined,\n displayName: args.user.name,\n };\n}\n\n/** @internal */\nexport function serializeScimGroup(args: {\n id: string;\n group: Record<string, any>;\n externalId?: string;\n members?: Array<{ value: string; display?: string }>;\n location?: string;\n}) {\n return {\n schemas: [SCIM_GROUP_SCHEMA_ID],\n id: args.id,\n externalId: args.externalId,\n meta: {\n resourceType: \"Group\",\n location: args.location,\n },\n displayName: args.group.name ?? args.id,\n members: args.members ?? [],\n };\n}\n"],"mappings":";;;;;;;;AAmBA,MAAM,8BAA8B,EAClC,WAAW,SAAiB,QAAQ,QAAQ,KAAK,EAClD;AACD,SAAS,yBAAyB;AAChC,oBAAmB,4BAA4B;;;AAgFjD,MAAa,sBAAsB;;AAEnC,MAAa,uBACX;;AAGF,MAAa,kCAAkC;;AAE/C,MAAa,kCAAkC;AAC/C,MAAM,kCAAkB,IAAI,KAGzB;;AAGH,SAAgB,gBAAgB,QAAwB;AACtD,QAAO,OAAO,MAAM,CAAC,aAAa,CAAC,QAAQ,OAAO,GAAG;;;AAIvD,SAAgB,yBAAyB,cAA8B;AACrE,QAAO,GAAG,kCAAkC;;;AAI9C,SAAgB,yBAAyB,cAA8B;AACrE,QAAO,GAAG,kCAAkC;;;AAI9C,SAAgB,sBAAsB,MAGf;CACrB,MAAM,OAAO,KAAK,QAAQ,QAAQ,OAAO,GAAG;AAI5C,QAAO;EACL,aAJmB,GAAG,KAAK,gBAAgB,KAAK,OAAO,GAAG;EAK1D,QAJc,GAAG,KAAK,gBAAgB,KAAK,OAAO,GAAG;EAKrD,QAJc,GAAG,KAAK,gBAAgB,KAAK,OAAO,GAAG;EAKtD;;;AAIH,SAAgB,sBAAsB,MAGnC;CACD,MAAM,OAAO,KAAK,QAAQ,QAAQ,OAAO,GAAG;AAC5C,QAAO;EACL,WAAW,GAAG,KAAK,gBAAgB,KAAK,aAAa;EACrD,aAAa,GAAG,KAAK,gBAAgB,KAAK,aAAa;EACxD;;;AAIH,SAAgB,6BACd,QACA;AACA,QAAO,OAAO,WAAW;;;AAI3B,SAAgB,uBAAuB,YAA6B;AAClE,QACE,WAAW,WAAW,gCAAgC,IACtD,WAAW,WAAW,gCAAgC;;AAI1D,MAAM,YAAY,UAChB,OAAO,UAAU,YAAY,UAAU,OAClC,QACD;;AAGN,MAAa,4BAA8C;CACzD,SAAS;CACT,UAAU,EACR,gBAAgB;EACd,MAAM;EACN,MAAM;EACP,EACF;CACD,cAAc;EACZ,WAAW,EACT,MAAM,cACP;EACD,KAAK;GACH,MAAM;GACN,gBAAgB,EAAE;GACnB;EACD,aAAa,EACX,MAAM,QACP;EACF;CACF;;AAGD,SAAgB,0BAA0B,QAAmC;CAC3E,MAAM,QAAQ,SAAS,OAAO,IAAI,EAAE;CAEpC,MAAM,iBAAiB,UADN,SAAS,MAAM,SAAS,IAAI,EAAE,EACN,eAAe,IAAI,EAAE;CAC9D,MAAM,eAAe,SAAS,MAAM,aAAa,IAAI,EAAE;CACvD,MAAM,YAAY,SAAS,aAAa,UAAU,IAAI,EAAE;CACxD,MAAM,MAAM,SAAS,aAAa,IAAI,IAAI,EAAE;CAC5C,MAAM,cAAc,SAAS,aAAa,YAAY,IAAI,EAAE;CAC5D,MAAM,SAAS,SAAS,MAAM,OAAO,IAAI;AAEzC,QAAO;EACL,SAAS;EACT,UAAU,EACR,gBAAgB;GACd,MACE,eAAe,SAAS,SACpB,SACA,0BAA0B,SAAS,eAAe;GACxD,MACE,eAAe,SAAS,SACpB,SACA,0BAA0B,SAAS,eAAe;GACzD,EACF;EACD,cAAc;GACZ,WAAW,EACT,MACE,UAAU,SAAS,SACf,SACA,0BAA0B,aAAa,UAAU,MACxD;GACD,KAAK;IACH,MACE,IAAI,SAAS,SACb,IAAI,SAAS,gBACb,IAAI,SAAS,4BACT,IAAI,OACJ,0BAA0B,aAAa,IAAI;IACjD,gBAAgB,MAAM,QAAQ,IAAI,eAAe,GAC7C,MAAM,KACJ,IAAI,IACF,IAAI,eAAe,QAChB,UACC,OAAO,UAAU,YAAY,MAAM,SAAS,EAC/C,CACF,CACF,GACD,OAAO,IAAI,gBAAgB,YAAY,IAAI,YAAY,SAAS,IAC9D,CAAC,IAAI,YAAY,GACjB,0BAA0B,aAAa,IAAI;IAClD;GACD,aAAa,EACX,MACE,YAAY,SAAS,SACjB,SACA,0BAA0B,aAAa,YAAY,MAC1D;GACF;EACD,GAAI,SAAS,EAAE,QAAQ,GAAG,EAAE;EAC7B;;;AAIH,SAAgB,sBACd,SACA,OACkB;CAClB,MAAM,OAAO,0BAA0B,QAAQ;AAC/C,QAAO,0BAA0B;EAC/B,GAAG;EACH,GAAG;EACH,UAAU;GACR,GAAG,KAAK;GACR,GAAG,MAAM;GACT,gBAAgB;IACd,GAAG,KAAK,SAAS;IACjB,GAAG,MAAM,UAAU;IACpB;GACF;EACD,cAAc;GACZ,GAAG,KAAK;GACR,GAAG,MAAM;GACT,WAAW;IACT,GAAG,KAAK,aAAa;IACrB,GAAG,MAAM,cAAc;IACxB;GACD,KAAK;IACH,GAAG,KAAK,aAAa;IACrB,GAAG,MAAM,cAAc;IACxB;GACD,aAAa;IACX,GAAG,KAAK,aAAa;IACrB,GAAG,MAAM,cAAc;IACxB;GACF;EACD,QACE,MAAM,WAAW,SACb,KAAK,SACL;GAAE,GAAG,KAAK;GAAQ,GAAG,MAAM;GAAQ;EAC1C,CAAC;;AAGJ,MAAM,qBAAqB,QAAiB,aAA8B;CACxE,MAAM,OAAO,SAAS,OAAO;CAC7B,MAAM,SAAS,OAAO;CACtB,MAAM,eAAe,SAAS,MAAM,UAAU,GAAG;AACjD,QAAO,SAAS,OAAO,IAAI,SAAS,aAAa,IAAI,EAAE;;;AAIzD,SAAgB,cAAc,QAAsC;AAClE,QAAO,kBAAkB,QAAQ,OAAO;;;AAI1C,SAAgB,oBAAoB,QAAsC;CAExE,MAAM,EAAE,cAAc,eAAe,GAAG,eAD3B,cAAc,OAAO;AAElC,QAAO;;;AAIT,SAAgB,oBACd,QACA,iBACA;AACA,QAAO;EACL,GAAG;EACH;EACD;;;AAIH,SAAgB,cAAc,QAAsC;AAClE,QAAO,kBAAkB,QAAQ,OAAO;;;AAI1C,SAAgB,qBACd,QACA,UACA,gBACA;CACA,MAAM,OAAO,SAAS,OAAO,IAAI,EAAE;CACnC,MAAM,YAAY,SAAS,KAAK,UAAU,IAAI,EAAE;AAChD,WAAU,YAAY;EACpB,GAAG,SAAS,UAAU,UAAU;EAChC,GAAG;EACJ;AACD,QAAO;EAAE,GAAG;EAAM;EAAW;;;AAI/B,SAAgB,8BAA8B,MAK3C;CACD,MAAM,SAAS,CACb,8BAA8B,KAAK,UAAU,WAAW,KAAK,MAAM,QAAQ,MAAM,SAAS,CAAC,OAC3F,KAAK,aACD,iDAAiD,KAAK,WAAW,QAAQ,MAAM,SAAS,CAAC,QACzF,GACL,CAAC,KAAK,GAAG;AACV,QAAO,IAAI,SACT,0DAA0D,KAAK,SAAS,IAAI,OAAO,qEACnF;EAAE,QAAQ;EAAK,SAAS,EAAE,gBAAgB,4BAA4B;EAAE,CACzE;;;AAIH,SAAgB,iBACd,OACyB;AACzB,KAAI,CAAC,MACH,QAAO,EAAE;AAEX,KAAI;AACF,SAAO,KAAK,MACV,IAAI,aAAa,CAAC,OAAO,6BAA6B,MAAM,CAAC,CAC9D;SACK;AACN,SAAO,EAAE;;;;AAKb,SAAgB,+BACd,OACA;AACA,QAAO,yBACL,IAAI,aAAa,CAAC,OAChB,KAAK,UAAU;EACb,QAAQ,GAAG,MAAM,OAAO,KAAK,GAAG,MAAM,OAAO;EAC7C,WAAW,MAAM;EACjB,WAAW,MAAM;EACjB,OAAO,MAAM;EACb,YAAY,MAAM;EACnB,CAAC,CACH,CACF;;;AAIH,SAAgB,sCACd,OAC0B;AAC1B,KAAI,CAAC,MACH,OAAM,IAAI,MAAM,2BAA2B;CAE7C,MAAM,UAAU,iBAAiB,MAAM;AACvC,KACE,OAAO,QAAQ,WAAW,YAC1B,OAAO,QAAQ,cAAc,YAC7B,OAAO,QAAQ,cAAc,YAC7B,OAAO,QAAQ,UAAU,SAEzB,OAAM,IAAI,MAAM,2BAA2B;CAE7C,MAAM,CAAC,MAAM,GAAG,QAAQ,QAAQ,OAAO,MAAM,IAAI;CACjD,MAAM,KAAK,KAAK,KAAK,IAAI;AACzB,KAAI,SAAS,gBAAgB,GAAG,WAAW,EACzC,OAAM,IAAI,MAAM,kCAAkC;AAEpD,QAAO;EACL,QAAQ;GAAE;GAAM;GAAI;EACpB,WAAW,QAAQ;EACnB,WAAW,QAAQ;EACnB,OAAO,QAAQ;EACf,YACE,OAAO,QAAQ,eAAe,WAAW,QAAQ,aAAa;EACjE;;;AAIH,eAAsB,gBACpB,SACiC;CACjC,MAAM,cAAc,QAAQ,QAAQ,IAAI,eAAe,IAAI;AAC3D,KACE,YAAY,SAAS,oCAAoC,IACzD,YAAY,SAAS,sBAAsB,EAC3C;EACA,MAAM,OAAO,MAAM,QAAQ,UAAU;EACrC,MAAM,OAA+B,EAAE;AACvC,OAAK,SAAS,OAAO,QAAQ;AAC3B,QAAK,OAAO,OAAO,UAAU,WAAW,QAAQ,MAAM;IACtD;AACF,SAAO;;AAET,QAAO,EAAE;;;AAIX,eAAsB,8BACpB,SACoC;CACpC,MAAM,MAAM,IAAI,IAAI,QAAQ,IAAI;CAChC,MAAM,OAAO,MAAM,gBAAgB,QAAQ;AAQ3C,QAAO;EACL;EACA;EACA,OAVY,OAAO,YAAY,IAAI,aAAa;EAWhD,SATA,QAAQ,WAAW,QACf,aACA,KAAK,gBAAgB,KAAK,cACxB,SACA;EAMN,YACE,KAAK,cAAc,IAAI,aAAa,IAAI,aAAa,IAAI;EAC3D,gBAAgB,QACd,KAAK,eAAe,IAAI,aAAa,IAAI,cAAc,CACxD;EACD,iBAAiB,QACf,KAAK,gBAAgB,IAAI,aAAa,IAAI,eAAe,CAC1D;EACF;;AAGH,eAAe,0BAA0B,QAA6B;CACpE,MAAM,eACJ,OAAO,OAAO,iBAAiB,WAC3B,OAAO,eACP,OAAO,OAAO,WAAW,WACvB,GAAG,OAAO,OAAO,QAAQ,OAAO,GAAG,CAAC,qCACpC;AAER,KAAI,CAAC,aACH,OAAM,IAAI,MAAM,sDAAsD;CAGxE,MAAM,YAAY,0BAA0B,QAAQ,OAAO,OAAO;AAElE,QAAO,MAAM,GAAG,IACd,GAAG,YACD,GAAG,KAAK;EACN,IAAI,YAAY;GACd,MAAM,WAAW,MAAM,UAAU,aAAa;AAC9C,OAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MACR,0CAA0C,SAAS,SACpD;GAEH,MAAM,YAAa,MAAM,SAAS,MAAM;AACxC,OACE,OAAO,UAAU,WAAW,YAC5B,OAAO,UAAU,2BAA2B,YAC5C,OAAO,UAAU,mBAAmB,YACpC,OAAO,UAAU,aAAa,SAE9B,OAAM,IAAI,MACR,sDACD;AAEH,UAAO;;EAET,MAAM,UACJ,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC;EAC5D,CAAC,CACH,CAAC,KACA,GAAG,QAAQ,IAAO,EAClB,GAAG,MACD,GAAG,MAAM,QACP,GAAG,MAAM,SAAS,GAAG,MAAM,YAAY,IAAI,CAAC,EAC5C,GAAG,MAAM,OAAO,EAAE,CACnB,CACF,EACD,GAAG,SAAS,UACV,GAAG,KAAK,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC,CAAC,CACnE,CACF,CACF;;AAGH,SAAS,0BACP,QACA,kBACA;CACA,MAAM,gBACJ,OAAO,OAAO,iBAAiB,WAC3B,IAAI,IAAI,OAAO,aAAa,CAAC,SAC7B;CACN,MAAM,eACJ,OAAO,OAAO,WAAW,WACrB,IAAI,IAAI,OAAO,OAAO,CAAC,OACvB,OAAO,qBAAqB,WAC1B,IAAI,IAAI,iBAAiB,CAAC,OAC1B;AAER,QAAO,OAAO,OAAqB,SAAuB;EACxD,MAAM,MAAM,IAAI,IAAI,OAAO,UAAU,WAAW,QAAQ,MAAM,UAAU,CAAC;EACzE,MAAM,eACJ,kBAAkB,UAAa,IAAI,WAAW,gBAC1C,IAAI,IAAI,GAAG,gBAAgB,IAAI,WAAW,IAAI,SAAS,GACvD;EACN,MAAM,UAAU,IAAI,QAAQ,MAAM,QAAQ;AAC1C,MAAI,kBAAkB,UAAa,iBAAiB,OAClD,SAAQ,IAAI,QAAQ,aAAa;AAEnC,SAAO,MAAM,MAAM,cAAc;GAAE,GAAG;GAAM;GAAS,CAAC;;;AAI1D,SAAS,YACP,KACA,WACA;CACA,MAAM,WAAW,YAAY,GAAG,IAAI,YAAY;CAChD,IAAI,OAAO,gBAAgB,IAAI,SAAS;AACxC,KAAI,CAAC,MAAM;AACT,SAAO,YACH,mBAAmB,IAAI,IAAI,IAAI,EAAE,GAAG,cAAc,WAAW,CAAC,GAC9D,mBAAmB,IAAI,IAAI,IAAI,CAAC;AACpC,kBAAgB,IAAI,UAAU,KAAK;;AAErC,QAAO;;AAOT,SAAS,kBAAkB,MAMxB;AACD,QAAO,GAAG,KAAK;EACb,IAAI,YAAY;GACd,MAAM,WAAW,OAAO,KAAK,aAAa,OAAO,KAAK,UAAU,EAC9D,SAAS,EAAE,eAAe,UAAU,KAAK,eAAe,EACzD,CAAC;AACF,OAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MAAM,iCAAiC,SAAS,SAAS;AAErE,UAAQ,MAAM,SAAS,MAAM;;EAE/B,MAAM,WAAiC;GAAE,MAAM;GAAa;GAAO;EACpE,CAAC,CAAC,KACD,GAAG,OAAO,aAAa;EACrB,MAAM,kBACJ,OAAO,SAAS,QAAQ,WAAW,SAAS,MAAM;EACpD,MAAM,eACJ,OAAO,KAAK,eAAe,QAAQ,WAC/B,KAAK,eAAe,MACpB;AACN,SAAO,oBAAoB,UACzB,iBAAiB,UACjB,oBAAoB,eAClB,GAAG,KAAK,EAAE,MAAM,oBAAoB,CAAU,GAC9C,GAAG,QAAQ;GACT,IACE,oBACC,OAAO,KAAK,eAAe,QAAQ,WAChC,KAAK,eAAe,MACpB,WACJ,OAAO,YAAY;GACrB,OACE,OAAO,SAAS,UAAU,WACtB,SAAS,QACT,KAAK,gBAAgB;GAC3B,eACE,OAAO,SAAS,mBAAmB,YAC/B,SAAS,iBACT,KAAK,gBAAgB;GAC3B,MACE,OAAO,SAAS,SAAS,WACrB,SAAS,OACT,KAAK,gBAAgB;GAC3B,OACE,OAAO,SAAS,YAAY,WACxB,SAAS,UACT,KAAK,gBAAgB;GAC5B,CAA+C;GACpD,EACF,GAAG,SAAS,YAAY;AACtB,MAAI,QAAQ,SAAS,YACnB,QAAO,GAAG,QAAQ,KAAK;AAEzB,SAAO,GAAG,qBACR,IAAI,MAAM,yDAAyD,CACpE;GACD,CACH;;;AAIH,eAAsB,6BACpB,QACA,aACA;CACA,MAAM,YAAY,MAAM,0BAA0B,OAAO;CACzD,MAAM,iBAAiB,OAAO,OAAO,UAAU,UAAU,OAAO,CAAC,QAC/D,OACA,GACD;CACD,MAAM,mBAAmB,OAAO,UAAU,OAAO,CAAC,QAAQ,OAAO,GAAG;CACpE,MAAM,eAAe,OAAO,iBAAiB;AAC7C,KACE,OAAO,OAAO,WAAW,YACzB,mBAAmB,kBACnB;AACA,MAAI,aACF,OAAM,IAAI,MACR,+CAA+C,eAAe,aAAa,mBAC5E;AAEH,UAAQ,KACN,gGACA;GACE,kBAAkB;GAClB,iBAAiB;GAClB,CACF;;CAEH,MAAM,wBAAwB,UAAU;CACxC,MAAM,gBAAgB,UAAU;CAChC,MAAM,UAAU,OAAO,OAAO,WAAW,UAAU,SAAS;CAC5D,MAAM,8BAA8B,MAAM,QACxC,UAAU,sCACX,GACG,UAAU,sCAAsC,QAC7C,UAAoC,OAAO,UAAU,SACvD,GACD,EAAE;CACN,MAAM,mBACH,UAAU,qBAA4C;CACzD,MAAM,YAAY,0BAChB,QACA,UAAU,OACX;CACD,MAAM,SAAS,MAAM,QAAQ,OAAO,OAAO,GACvC,OAAO,OAAO,QACX,UAAoC,OAAO,UAAU,SACvD,GACD;EAAC;EAAU;EAAW;EAAQ;CAClC,MAAM,mBAAmB,OAAO,YAAY,OAAO,OAAO,SAAS;CACnE,MAAM,uBAAuB,WAAmB;EAC9C,MAAM,aAAa,CAAC,OAAO;AAC3B,MAAI,OAAO,WAAW,WAAW,CAC/B,YAAW,KAAK,UAAU,OAAO,MAAM,EAAkB,GAAG;WACnD,OAAO,WAAW,UAAU,CACrC,YAAW,KAAK,WAAW,OAAO,MAAM,EAAiB,GAAG;AAE9D,SAAO;;CAET,MAAM,kBAAkB,eACpB,CAAC,eAAe,GAChB,MAAM,KACJ,IAAI,IAAI,CACN,GAAG,oBAAoB,eAAe,EACtC,GAAG,oBAAoB,iBAAiB,CACzC,CAAC,CACH;CACL,MAAM,OAAO,YAAY,SAAS,UAAU;CAC5C,IAAI,iBAAiD;CACrD,IAAI,kBACF;CACF,MAAM,oBAAoB,YAAqC;EAC7D,IAAI,OAAO,OAAO,QAAQ,WAAW,OAAO,MAAM,OAAO,YAAY;EACrE,OAAO,OAAO,OAAO,UAAU,WAAW,OAAO,QAAQ;EACzD,eACE,OAAO,OAAO,mBAAmB,YAC7B,OAAO,iBACP;EACN,MAAM,OAAO,OAAO,SAAS,WAAW,OAAO,OAAO;EACtD,OAAO,OAAO,OAAO,YAAY,WAAW,OAAO,UAAU;EAC9D;AA6LD,QAAO;EAAE,UA3LQ;GACf,uBACE,OACA,cACA,iBACA;IACA,MAAM,MAAM,IAAI,IAAI,sBAAsB;AAC1C,QAAI,aAAa,IAAI,iBAAiB,OAAO;AAC7C,QAAI,aAAa,IAAI,aAAa,OAAO,OAAO,SAAS,CAAC;AAC1D,QAAI,aAAa,IAAI,gBAAgB,YAAY;AACjD,QAAI,aAAa,IACf,UACC,gBAAgB,SAAS,IAAI,kBAAkB,QAAQ,KAAK,IAAI,CAClE;AACD,QAAI,aAAa,IAAI,SAAS,MAAM;AACpC,QAAI,aAAa,IAAI,yBAAyB,OAAO;AACrD,QAAI,aAAa,IACf,kBACA,yBACE,OAAO,IAAI,aAAa,CAAC,OAAO,aAAa,CAAC,CAC/C,CACF;IACD,MAAM,sBACJ,OAAO,OAAO,wBAAwB,YACtC,OAAO,wBAAwB,OAC1B,OAAO,sBACR,EAAE;AACR,SAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,oBAAoB,CAC5D,KAAI,OAAO,UAAU,SACnB,KAAI,aAAa,IAAI,KAAK,MAAM;AAGpC,WAAO;;GAET,MAAM,0BAA0B,MAAc,cAAuB;IACnE,MAAM,OAAO,IAAI,gBAAgB;KAC/B,YAAY;KACZ;KACA,cAAc;KACd,WAAW,OAAO,OAAO,SAAS;KACnC,CAAC;AACF,QAAI,OAAO,OAAO,iBAAiB,SACjC,MAAK,IAAI,iBAAiB,OAAO,aAAa;AAEhD,QAAI,aACF,MAAK,IAAI,iBAAiB,aAAa;IAEzC,MAAM,WAAW,MAAM,UAAU,eAAe;KAC9C,QAAQ;KACR,SAAS,EAAE,gBAAgB,qCAAqC;KAChE;KACD,CAAC;AACF,QAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MAAM,+BAA+B,SAAS,SAAS;IAEnE,MAAM,OAAQ,MAAM,SAAS,MAAM;AACnC,WAAO;KACL;KACA,UAAU;AACR,UAAI,OAAO,KAAK,aAAa,SAC3B,OAAM,IAAI,MAAM,qCAAqC;AAEvD,aAAO,KAAK;;KAEd,cAAc;AACZ,UAAI,OAAO,KAAK,iBAAiB,SAC/B,OAAM,IAAI,MAAM,yCAAyC;AAE3D,aAAO,KAAK;;KAEf;;GAEJ;EAmHkB,aAjHC;GAClB;GACA,OAAO;GACP,gBAAgB,OAAO,QAAa,QAA4B;IAC9D,MAAM,WAAW,MAAM,GAAG,IACxB,GAAG,IAAI,aAAa;AAClB,YAAO,GAAG,MACR,IAAI,UAAU,QACd,GAAG,qBAAK,IAAI,MAAM,0BAA0B,CAAC,CAC9C;KAED,MAAM,UAAU,OAAO,SAAS;KAEhC,MAAM,WADkB,sBAAsB,QAAQ,CACrB;KACjC,MAAM,yBACJ,OAAO,aAAa,aACnB,aAAa,WACZ,aAAa,WACb,aAAa,YACf,4BAA4B,SAAS,SAAS;KAEhD,MAAM,sBAAsB;MAC1B,UAAU;MACV,gBAAgB;OAAC;OAAO;OAAO;OAAO;OAAO;OAAM;MACnD,gBAAgB,OAAO,yBAAyB;MACjD;KAsBD,MAAM,WApBe,OAAO,GAAG,KAAK;MAClC,UACE,yBACI,UACE,gBACO;AACL,WAAI,OAAO,OAAO,iBAAiB,SACjC,OAAM,IAAI,MACR,gFACD;AAEH,cAAO,IAAI,aAAa,CAAC,OAAO,OAAO,aAAa;UAClD,EACJ,oBACD,GACD,UAAU,SAAS,MAAa,oBAA2B;MACjE,MAAM,UACJ,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC;MAC5D,CAAC,EAE2B;KAC7B,MAAM,iBACJ,OAAO,QAAQ,QAAQ,WAAW,QAAQ,MAAM;KAClD,MAAM,cACJ,OAAO,mBAAmB,WACtB,eAAe,QAAQ,OAAO,GAAG,GACjC;AAEN,YAAO,GAAG,MACR,CAAC,eAAe,CAAC,gBAAgB,SAAS,YAAY,EACtD,GAAG,qBACD,IAAI,MACF,yCAAyC,eAAe,YAAY,qBAAqB,gBAAgB,KAAK,KAAK,GACpH,CACF,CACF;AAED,YAAO,GAAG,MACR,QAAQ,UAAU,IAAI,OACtB,GAAG,qBAAK,IAAI,MAAM,uBAAuB,CAAC,CAC3C;AAED,YAAO,GAAG,MACR,MAAM,QAAQ,QAAQ,IAAI,IACxB,QAAQ,IAAI,SAAS,KACrB,QAAQ,QAAQ,OAAO,OAAO,SAAS,EACzC,GAAG,qBACD,IAAI,MAAM,kDAAkD,CAC7D,CACF;AAED,YAAO;MACP,CACH;AAED,qBAAiB;AACjB,sBAAkB,iBAAiB,SAAS;;GAE9C,gBAAgB,OAAO;GACvB,SAAS,OAAO,WAAuC;AACrD,QAAI,oBAAoB,QAAQ,mBAAmB,MAAM;KACvD,MAAM,SAAS,cAAc,OAAO,SAAS,CAAC;AAI9C,sBAAiB;AACjB,uBAAkB,iBAAiB,OAAO;;AAE5C,QAAI,oBAAoB,OAAO,OAAO,gBAAgB,YAAY;KAChE,MAAM,kBAAkB,MAAM,GAAG,IAC/B,kBAAkB;MAChB,UAAU;MACV,aAAa,OAAO,aAAa;MACjC;MACA;MACA,WAAW;MACZ,CAAC,CACH;AACD,SAAI,oBAAoB,KACtB,QAAO;;AAGX,WAAO;;GAEV;EAE+B;;;AAIlC,SAAgB,uCACd,YACA,SAGyB;AACzB,QAAO;EACL,IAAI;EACJ,MAAM;EACN,UAAU;EACV,QAAQ,EAAE;EACV,gBAAgB,SAAS,kBAAkB;EAC5C;;;AAIH,SAAgB,qBAAqB,UAAsC;CAEzE,MAAM,aADM,iBAAiB,EAAE,UAAU,CAAC,CACnB;CAEvB,MAAM,oBAAoB,UAAuC;AAC/D,SAAO,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ;;AAGjE,QAAO;EACL,QAAQ,WAAW,aAAa;EAChC,KAAK;GACH,UAAU,iBAAiB,WAAW,uBAAuB,WAAW,CAAC;GACzE,MAAM,iBAAiB,WAAW,uBAAuB,OAAO,CAAC;GAClE;EACD,KAAK;GACH,UAAU,iBAAiB,WAAW,uBAAuB,WAAW,CAAC;GACzE,MAAM,iBAAiB,WAAW,uBAAuB,OAAO,CAAC;GAClE;EACD,aAAa,WAAW,mBAAmB,UAAU;EACrD,gBAAgB,WAAW,mBAAmB,UAAU;EACxD,sBAAsB;GACpB,MAAM,eAAe,WAAW,iBAAiB;AACjD,UAAO,MAAM,QAAQ,aAAa,GAAG,eAAe,EAAE;MACpD;EACJ,0BAA0B,WAAW,2BAA2B;EACjE;;;AAIH,SAAgB,8BAA8B,MAW3C;CACD,MAAM,UAAU,UAAU,UAAU;AA6BpC,QA5BW,gBAAgB;EACzB,UAAU,KAAK;EACf,qBAAqB,KAAK,uBAAuB;EACjD,YAAY,KAAK;EACjB,gBAAgB,KAAK;EACrB,aAAa,KAAK;EAClB,aAAa,KAAK;EAClB,eAAe,KAAK;EACpB,mBAAmB,KAAK;EACxB,0BAA0B,CACxB;GACE,SAAS,QAAQ;GACjB,UAAU,KAAK;GAChB,CACF;EACD,qBAAqB,KAAK,SACtB,CACE;GACE,SAAS,QAAQ;GACjB,UAAU,KAAK;GAChB,EACD;GACE,SAAS,QAAQ;GACjB,UAAU,KAAK;GAChB,CACF,GACD;EACL,CAAC,CACQ,aAAa;;;AAIzB,SAAgB,gCAAgC,MAI7C;AACD,QAAO,8BACL,8BAA8B;EAC5B,SAAS,KAAK;EACd,QAAQ,KAAK;EACb,QAAQ,KAAK;EACd,CAAC,CACH;;;AAIH,SAAgB,8BAA8B,MAU3C;CACD,MAAM,OAAO,cAAc,KAAK,OAAO;CACvC,MAAM,KAAK,SAAS,KAAK,GAAG,IAAI,EAAE;CAClC,MAAM,OAAO,sBAAsB;EACjC,SAAS,KAAK;EACd,QAAQ,KAAK;EACd,CAAC;AACF,QAAO;EACL,UAAU,KAAK,WAAW,YAAY,GAAG,YAAY,KAAK;EAC1D,QAAQ,KAAK,WAAW,UAAU,GAAG,UAAU,KAAK;EACpD,QAAQ,KAAK,WAAW,UAAU,GAAG,UAAU,KAAK;EACpD,YAAY,KAAK;EACjB,qBAAqB,KAAK;EAC1B,aAAa,GAAG;EAChB,aAAa,GAAG;EAChB,YAAY,GAAG;EACf,gBAAgB,GAAG;EACnB,eAAe,GAAG;EAClB,mBAAmB,GAAG;EACvB;;;AAIH,SAAgB,0BAA0B,MAYvC;CACD,MAAM,UAAU,UAAU,UAAU;AACpC,QAAO,gBAAgB;EACrB,UAAU,KAAK;EACf,YAAY,KAAK,cAAc;EAC/B,qBAAqB,KAAK,uBAAuB;EACjD,YAAY,KAAK;EACjB,gBAAgB,KAAK;EACrB,aAAa,KAAK;EAClB,aAAa,KAAK;EAClB,eAAe,KAAK;EACpB,mBAAmB,KAAK;EACxB,0BAA0B,CACxB;GACE,SAAS,QAAQ;GACjB,UAAU,KAAK;GAChB,CACF;EACD,qBAAqB,KAAK,SACtB,CACE;GAAE,SAAS,QAAQ;GAAU,UAAU,KAAK;GAAQ,EACpD;GAAE,SAAS,QAAQ;GAAM,UAAU,KAAK;GAAQ,CACjD,GACD;EACL,CAAC;;;AAIJ,SAAgB,4BAA4B,MAUzC;CACD,MAAM,OAAO,cAAc,KAAK,OAAO;CACvC,MAAM,YAAY,8BAA8B;EAC9C,SAAS,KAAK;EACd,QAAQ,KAAK;EACb,QAAQ,KAAK;EACb,YAAY,KAAK;EACjB,WAAW,KAAK;EACjB,CAAC;AACF,KAAI,OAAO,KAAK,KAAK,gBAAgB,SACnC,OAAM,IAAI,MAAM,gCAAgC;AAElD,QAAO;EACL;EACA,IAAI,0BAA0B,UAAU;EACxC,KAAK,iBAAiB,EAAE,UAAU,KAAK,IAAI,aAAa,CAAC;EACzD,MAAM,sBAAsB;GAAE,SAAS,KAAK;GAAS,QAAQ,KAAK;GAAQ,CAAC;EAC5E;;;AAIH,SAAgB,kCAAkC,MAO/C;CACD,MAAM,UAAU,4BAA4B;EAC1C,SAAS,KAAK;EACd,QAAQ,KAAK;EACb,QAAQ,KAAK;EACd,CAAC;CACF,MAAM,UAAU,QAAQ,KAAK,IAAI,KAAK,WAAW,aAAa;CAC9D,MAAM,eAAe,QAAQ,GAAG,mBAC9B,QAAQ,KACR,QACD;CACD,MAAM,aAAa,+BAA+B;EAChD,QAAQ,KAAK;EACb,WAAW,KAAK;EAChB,WAAW,aAAa;EACxB,OAAO,KAAK;EACZ,YAAY,KAAK;EAClB,CAAC;AACF,QAAO;EACL,WAAW,aAAa;EACxB;EACA;EACA,aACE,YAAY,oBACD;GACL,MAAM,cAAc,IAAI,IAAI,aAAa,QAAQ;AACjD,eAAY,aAAa,IAAI,cAAc,WAAW;AACtD,UAAO,YAAY,UAAU;MAC3B,GACJ;EACN,MACE,YAAY,SACR;GACE,UAAU,aAAa;GACvB,OAAO,aAAa;GACrB,GACD;EACP;;;AAIH,eAAsB,iCAAiC,MAKpD;AACD,yBAAwB;CACxB,MAAM,cAAc,MAAM,8BAA8B,KAAK,QAAQ;CACrE,MAAM,UAAU,4BAA4B;EAC1C,SAAS,KAAK;EACd,QAAQ,KAAK;EACb,QAAQ,KAAK;EACd,CAAC;CACF,MAAM,SAAU,MAAM,QAAQ,GAAG,mBAC/B,QAAQ,KACR,YAAY,SACZ;EACE,OAAO,YAAY;EACnB,MAAM,YAAY;EACnB,CACF;AAED,8BAA6B,OAAO;AAEpC,QAAO;EACL,GAAG;EACH;EACA;EACA,YAAY,sCACV,YAAY,cAAc,KAC3B;EACF;;AAGH,MAAM,6BAA6B,IAAI,IAAI;CAEzC;CACA;CAEA;CAEA;CAEA;CACD,CAAC;;;;;;AAOF,SAAS,6BAA6B,QAAa;AACjD,KAAI;EACF,MAAM,SACJ,QAAQ,SAAS,WAAW,sBAC5B,QAAQ,SAAS,UAAU;EAC7B,MAAM,YAAY,QAAQ,SAAS,WAAW;AAE9C,MAAI,UAAU,2BAA2B,IAAI,OAAO,CAClD,SAAQ,KACN,oEAAoE,OAAO,8DAE5E;AAEH,MAAI,aAAa,2BAA2B,IAAI,UAAU,CACxD,SAAQ,KACN,iEAAiE,UAAU,2DAE5E;SAEG;;;AAMV,SAAgB,sCAAsC,MAInD;AACD,KACE,KAAK,WAAW,OAAO,SAAS,KAAK,OAAO,QAC5C,KAAK,WAAW,OAAO,OAAO,KAAK,OAAO,MAC1C,KAAK,WAAW,cAAc,KAAK,aAEnC,OAAM,IAAI,MAAM,2DAA2D;;;AAK/E,eAAsB,iCAAiC,MAKpD;AACD,yBAAwB;CACxB,MAAM,cAAc,MAAM,8BAA8B,KAAK,QAAQ;CACrE,MAAM,UAAU,4BAA4B;EAC1C,SAAS,KAAK;EACd,QAAQ,KAAK;EACb,QAAQ,KAAK;EACb,YAAY,YAAY;EACzB,CAAC;CACF,MAAM,gBAAgB,YAAY,iBAC5B,MAAM,QAAQ,GAAG,mBACjB,QAAQ,KACR,YAAY,SACZ;EACE,OAAO,YAAY;EACnB,MAAM,YAAY;EACnB,CACF,GACD;AACJ,QAAO;EACL,GAAG;EACH;EACA;EACD;;;AAIH,eAAsB,4BAA4B,MAI/C;CACD,MAAM,aAAa,yBAAyB,KAAK,aAAa;CAC9D,MAAM,OAAO,sBAAsB;EACjC,SAAS,KAAK;EACd,cAAc,KAAK;EACpB,CAAC;CACF,MAAM,EAAE,UAAU,gBAAgB,MAAM,6BACtC,KAAK,MACL,KAAK,YACN;AACD,QAAO;EACL,MAAM,KAAK;EACX;EACA;EACA;EACA,GAAG;EACJ;;;AAIH,SAAgB,uBACd,SACA,SACA;CACA,MAAM,aACJ,OAAO,SAAS,eAAe,YAAY,QAAQ,eAAe,OAC7D,QAAQ,aACT,EAAE;CACR,MAAM,gBAAgB,GAAG,SAAoC;AAC3D,OAAK,MAAM,OAAO,MAAM;AACtB,OAAI,CAAC,IACH;GAEF,MAAM,YAAY,WAAW;GAC7B,MAAM,QAAQ,MAAM,QAAQ,UAAU,GAAG,UAAU,KAAK;AACxD,OAAI,UAAU,OACZ,QAAO;;;CAKb,MAAM,iBAAiB;EACrB,aAAa,aAAa,SAAS,MAAM;EACzC,YACE,aAAa,SAAS,KAAK,KAC1B,CAAC,aAAa,SAAS,UAAU,EAAE,aAAa,SAAS,SAAS,CAAC,CACjE,OAAO,QAAQ,CACf,KAAK,IAAI,IACV;EACJ,eACE,aAAa,SAAS,QAAQ,IAAK,SAAS;EAC/C;CACD,MAAM,UAAU,eAAe,SAAS;AACxC,KAAI,YAAY,OACd,OAAM,IAAI,MACR,qHACD;CAEH,MAAM,QAAQ,eAAe,OAAO;CACpC,MAAM,OAAO,eAAe,MAAM;AAClC,QAAO;EACL,IAAI;EACJ;EACA,eAAe,OAAO,UAAU,WAAW,OAAO;EAClD;EACA,gBAAgB;EAChB,kBAAkB,SAAS,cAAc;EAC1C;;;AAIH,SAAgB,cAAc,UAAkB;CAE9C,MAAM,CAAC,KAAK,MAAM,KAAK,cAAc,UAAU,SAAS,GAAG,QAD7C,SAAS,MAAM,IAAI,CAAC,OAAO,QAAQ;AAGjD,KACE,QAAQ,SACR,SAAS,UACT,QAAQ,SACR,CAAC,gBACD,iBAAiB,WACjB,aAAa,UACb,YAAY,KAEZ,QAAO;EACL,cAAc;EACd,UAAU;EACV,YAAY;EACb;AAGH,QAAO;EACL;EACA,UAAU,KAAK,MAAM;EACrB,YAAY,KAAK;EAClB;;;AAIH,SAAgB,qBAAqB,KAA2B;CAC9D,MAAM,aAAa,KAAK,IACtB,GACA,OAAO,IAAI,aAAa,IAAI,aAAa,IAAI,IAAI,CAClD;CACD,MAAM,QAAQ,KAAK,IACjB,KACA,KAAK,IAAI,GAAG,OAAO,IAAI,aAAa,IAAI,QAAQ,IAAI,MAAM,CAAC,CAC5D;CACD,MAAM,cAAc,IAAI,aAAa,IAAI,SAAS;AAUlD,QAAO;EAAE;EAAY;EAAO,QATb,qBACJ;GACL,MAAM,QAAQ,YAAY,MAAM,sCAAsC;AACtE,OAAI,CAAC,MACH,OAAM,IAAI,MAAM,2BAA2B;AAE7C,UAAO;IAAE,WAAW,MAAM;IAAK,OAAO,MAAM;IAAK;MAC/C,GACJ;EACgC;;;AAItC,SAAgB,SAAS,MAAe,SAAS,KAAK,SAAuB;CAC3E,MAAM,kBAAkB,IAAI,QAAQ,EAClC,gBAAgB,yBACjB,CAAC;AACF,KAAI,QACF,KAAI,QAAQ,QAAQ,CAAC,SAAS,OAAO,QAAQ;AAC3C,kBAAgB,IAAI,KAAK,MAAM;GAC/B;AAEJ,QAAO,IAAI,SAAS,KAAK,UAAU,KAAK,EAAE;EACxC;EACA,SAAS;EACV,CAAC;;;AAIJ,SAAgB,UAAU,QAAgB,UAAkB,QAAgB;AAC1E,QAAO,SACL;EACE,SAAS,CAAC,8CAA8C;EACxD,QAAQ,OAAO,OAAO;EACtB;EACA;EACD,EACD,OACD;;;AAIH,SAAgB,kBAAkB,MAM/B;AACD,QAAO;EACL,SAAS,CAAC,oBAAoB;EAC9B,IAAI,KAAK;EACT,YAAY,KAAK;EACjB,MAAM;GACJ,cAAc;GACd,UAAU,KAAK;GAChB;EACD,UAAU,KAAK,KAAK,SAAS,KAAK,KAAK,SAAS,KAAK,KAAK,QAAQ,KAAK;EACvE,QAAQ,KAAK,UAAU;EACvB,MACE,KAAK,KAAK,SAAS,SAAY,EAAE,WAAW,KAAK,KAAK,MAAM,GAAG;EACjE,QACE,OAAO,KAAK,KAAK,UAAU,WACvB,CAAC;GAAE,OAAO,KAAK,KAAK;GAAO,SAAS;GAAM,CAAC,GAC3C;EACN,cACE,OAAO,KAAK,KAAK,UAAU,WACvB,CAAC;GAAE,OAAO,KAAK,KAAK;GAAO,SAAS;GAAM,CAAC,GAC3C;EACN,aAAa,KAAK,KAAK;EACxB;;;AAIH,SAAgB,mBAAmB,MAMhC;AACD,QAAO;EACL,SAAS,CAAC,qBAAqB;EAC/B,IAAI,KAAK;EACT,YAAY,KAAK;EACjB,MAAM;GACJ,cAAc;GACd,UAAU,KAAK;GAChB;EACD,aAAa,KAAK,MAAM,QAAQ,KAAK;EACrC,SAAS,KAAK,WAAW,EAAE;EAC5B"}
|
|
@@ -1,21 +1 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* Default email templates generated by the Auth library.
|
|
4
|
-
*
|
|
5
|
-
* These are used when the library sends emails on behalf of the developer
|
|
6
|
-
* (for example magic links). The developer provides the transport via
|
|
7
|
-
* `email.send`; the library provides the content.
|
|
8
|
-
*
|
|
9
|
-
* @module
|
|
10
|
-
*/
|
|
11
|
-
/**
|
|
12
|
-
* Default magic link email template.
|
|
13
|
-
*
|
|
14
|
-
* Clean, minimal design that works across email clients.
|
|
15
|
-
* Used by the auto-registered `email` provider when `email` is
|
|
16
|
-
* configured in `createAuth(...)`.
|
|
17
|
-
*/
|
|
18
|
-
declare function defaultMagicLinkEmail(url: string, host: string): string;
|
|
19
|
-
//#endregion
|
|
20
|
-
export { defaultMagicLinkEmail };
|
|
21
|
-
//# sourceMappingURL=templates.d.ts.map
|
|
1
|
+
export { };
|
package/dist/server/templates.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templates.js","names":[],"sources":["../../src/server/templates.ts"],"sourcesContent":["/**\n * Default email templates generated by the Auth library.\n *\n * These are used when the library sends emails on behalf of the developer\n * (for example magic links). The developer provides the transport via\n * `email.send`; the library provides the content.\n *\n * @module\n */\n\n/**\n * Default magic link email template.\n *\n * Clean, minimal design that works across email clients.\n * Used by the auto-registered `email` provider when `email` is\n * configured in `createAuth(...)`.\n */\nexport function defaultMagicLinkEmail(url: string, host: string): string {\n const escapedHost = host.replace(\n /[&<>\"']/g,\n (c) =>\n ({ \"&\": \"&\", \"<\": \"<\", \">\": \">\", '\"': \""\", \"'\": \"'\" })[\n c\n ]!,\n );\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Sign in to ${escapedHost}</title>\n</head>\n<body style=\"margin:0;padding:0;background-color:#f9fafb;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;\">\n <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background-color:#f9fafb;padding:40px 16px;\">\n <tr>\n <td align=\"center\">\n <table role=\"presentation\" width=\"480\" cellpadding=\"0\" cellspacing=\"0\" style=\"background-color:#ffffff;border:1px solid #e5e7eb;border-radius:8px;overflow:hidden;\">\n <tr>\n <td style=\"padding:32px 32px 0 32px;text-align:center;\">\n <h1 style=\"margin:0 0 8px 0;font-size:20px;font-weight:600;color:#111827;line-height:1.3;\">\n Sign in to ${escapedHost}\n </h1>\n </td>\n </tr>\n <tr>\n <td style=\"padding:24px 32px;\">\n <p style=\"margin:0 0 24px 0;font-size:15px;line-height:1.6;color:#4b5563;text-align:center;\">\n Click the button below to sign in. This link will expire shortly.\n </p>\n <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\">\n <tr>\n <td align=\"center\" style=\"padding:0 0 24px 0;\">\n <a href=\"${url}\" target=\"_blank\" style=\"display:inline-block;background-color:#111827;color:#ffffff;font-size:15px;font-weight:600;text-decoration:none;padding:12px 32px;border-radius:6px;line-height:1;\">\n Sign in\n </a>\n </td>\n </tr>\n </table>\n <p style=\"margin:0 0 12px 0;font-size:13px;line-height:1.6;color:#9ca3af;\">\n If the button doesn't work, copy and paste this URL into your browser:\n </p>\n <p style=\"margin:0;font-size:13px;line-height:1.5;color:#6b7280;word-break:break-all;\">\n ${url}\n </p>\n </td>\n </tr>\n <tr>\n <td style=\"padding:20px 32px;border-top:1px solid #e5e7eb;\">\n <p style=\"margin:0;font-size:12px;line-height:1.5;color:#9ca3af;text-align:center;\">\n If you didn't request this email, you can safely ignore it.\n </p>\n </td>\n </tr>\n </table>\n </td>\n </tr>\n </table>\n</body>\n</html>`;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"templates.js","names":[],"sources":["../../src/server/templates.ts"],"sourcesContent":["/**\n * Default email templates generated by the Auth library.\n *\n * These are used when the library sends emails on behalf of the developer\n * (for example magic links). The developer provides the transport via\n * `email.send`; the library provides the content.\n *\n * @module\n */\n\n/**\n * Default magic link email template.\n *\n * Clean, minimal design that works across email clients.\n * Used by the auto-registered `email` provider when `email` is\n * configured in `createAuth(...)`.\n */\n/** @internal */\nexport function defaultMagicLinkEmail(url: string, host: string): string {\n const escapedHost = host.replace(\n /[&<>\"']/g,\n (c) =>\n ({ \"&\": \"&\", \"<\": \"<\", \">\": \">\", '\"': \""\", \"'\": \"'\" })[\n c\n ]!,\n );\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Sign in to ${escapedHost}</title>\n</head>\n<body style=\"margin:0;padding:0;background-color:#f9fafb;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;\">\n <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background-color:#f9fafb;padding:40px 16px;\">\n <tr>\n <td align=\"center\">\n <table role=\"presentation\" width=\"480\" cellpadding=\"0\" cellspacing=\"0\" style=\"background-color:#ffffff;border:1px solid #e5e7eb;border-radius:8px;overflow:hidden;\">\n <tr>\n <td style=\"padding:32px 32px 0 32px;text-align:center;\">\n <h1 style=\"margin:0 0 8px 0;font-size:20px;font-weight:600;color:#111827;line-height:1.3;\">\n Sign in to ${escapedHost}\n </h1>\n </td>\n </tr>\n <tr>\n <td style=\"padding:24px 32px;\">\n <p style=\"margin:0 0 24px 0;font-size:15px;line-height:1.6;color:#4b5563;text-align:center;\">\n Click the button below to sign in. This link will expire shortly.\n </p>\n <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\">\n <tr>\n <td align=\"center\" style=\"padding:0 0 24px 0;\">\n <a href=\"${url}\" target=\"_blank\" style=\"display:inline-block;background-color:#111827;color:#ffffff;font-size:15px;font-weight:600;text-decoration:none;padding:12px 32px;border-radius:6px;line-height:1;\">\n Sign in\n </a>\n </td>\n </tr>\n </table>\n <p style=\"margin:0 0 12px 0;font-size:13px;line-height:1.6;color:#9ca3af;\">\n If the button doesn't work, copy and paste this URL into your browser:\n </p>\n <p style=\"margin:0;font-size:13px;line-height:1.5;color:#6b7280;word-break:break-all;\">\n ${url}\n </p>\n </td>\n </tr>\n <tr>\n <td style=\"padding:20px 32px;border-top:1px solid #e5e7eb;\">\n <p style=\"margin:0;font-size:12px;line-height:1.5;color:#9ca3af;text-align:center;\">\n If you didn't request this email, you can safely ignore it.\n </p>\n </td>\n </tr>\n </table>\n </td>\n </tr>\n </table>\n</body>\n</html>`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,SAAgB,sBAAsB,KAAa,MAAsB;CACvE,MAAM,cAAc,KAAK,QACvB,aACC,OACE;EAAE,KAAK;EAAS,KAAK;EAAQ,KAAK;EAAQ,MAAK;EAAU,KAAK;EAAS,EACtE,GAEL;AAED,QAAO;;;;;sBAKa,YAAY;;;;;;;;;;6BAUL,YAAY;;;;;;;;;;;;+BAYV,IAAI;;;;;;;;;;kBAUjB,IAAI"}
|
package/dist/server/tokens.d.ts
CHANGED
|
@@ -1,11 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { GenericId } from "convex/values";
|
|
3
|
-
|
|
4
|
-
//#region src/server/tokens.d.ts
|
|
5
|
-
declare function generateToken(args: {
|
|
6
|
-
userId: GenericId<"User">;
|
|
7
|
-
sessionId: GenericId<"Session">;
|
|
8
|
-
}, config: ConvexAuthConfig): Promise<string>;
|
|
9
|
-
//#endregion
|
|
10
|
-
export { generateToken };
|
|
11
|
-
//# sourceMappingURL=tokens.d.ts.map
|
|
1
|
+
export { };
|
package/dist/server/tokens.js
CHANGED
|
@@ -5,6 +5,7 @@ import { SignJWT, importPKCS8 } from "jose";
|
|
|
5
5
|
const DEFAULT_JWT_DURATION_MS = 1e3 * 60 * 60;
|
|
6
6
|
const TOKEN_JTI_LENGTH = 24;
|
|
7
7
|
const TOKEN_JTI_ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
8
|
+
/** @internal */
|
|
8
9
|
async function generateToken(args, config) {
|
|
9
10
|
const privateKey = await importPKCS8(requireEnv("JWT_PRIVATE_KEY"), "RS256");
|
|
10
11
|
const expirationTime = new Date(Date.now() + (config.jwt?.durationMs ?? DEFAULT_JWT_DURATION_MS));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokens.js","names":[],"sources":["../../src/server/tokens.ts"],"sourcesContent":["import { GenericId } from \"convex/values\";\nimport { SignJWT, importPKCS8 } from \"jose\";\n\nimport { ConvexAuthConfig } from \"./types\";\nimport { generateRandomString, TOKEN_SUB_CLAIM_DIVIDER } from \"./utils\";\nimport { requireEnv } from \"./utils\";\n\nconst DEFAULT_JWT_DURATION_MS = 1000 * 60 * 60; // 1 hour\nconst TOKEN_JTI_LENGTH = 24;\nconst TOKEN_JTI_ALPHABET =\n \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\";\n\nexport async function generateToken(\n args: {\n userId: GenericId<\"User\">;\n sessionId: GenericId<\"Session\">;\n },\n config: ConvexAuthConfig,\n) {\n const privateKey = await importPKCS8(requireEnv(\"JWT_PRIVATE_KEY\"), \"RS256\");\n const expirationTime = new Date(\n Date.now() + (config.jwt?.durationMs ?? DEFAULT_JWT_DURATION_MS),\n );\n return await new SignJWT({\n sub: args.userId + TOKEN_SUB_CLAIM_DIVIDER + args.sessionId,\n })\n .setProtectedHeader({ alg: \"RS256\" })\n .setIssuedAt()\n .setJti(generateRandomString(TOKEN_JTI_LENGTH, TOKEN_JTI_ALPHABET))\n .setIssuer(requireEnv(\"CONVEX_SITE_URL\"))\n .setAudience(\"convex\")\n .setExpirationTime(expirationTime)\n .sign(privateKey);\n}\n"],"mappings":";;;;AAOA,MAAM,0BAA0B,MAAO,KAAK;AAC5C,MAAM,mBAAmB;AACzB,MAAM,qBACJ
|
|
1
|
+
{"version":3,"file":"tokens.js","names":[],"sources":["../../src/server/tokens.ts"],"sourcesContent":["import { GenericId } from \"convex/values\";\nimport { SignJWT, importPKCS8 } from \"jose\";\n\nimport { ConvexAuthConfig } from \"./types\";\nimport { generateRandomString, TOKEN_SUB_CLAIM_DIVIDER } from \"./utils\";\nimport { requireEnv } from \"./utils\";\n\nconst DEFAULT_JWT_DURATION_MS = 1000 * 60 * 60; // 1 hour\nconst TOKEN_JTI_LENGTH = 24;\nconst TOKEN_JTI_ALPHABET =\n \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\";\n\n/** @internal */\nexport async function generateToken(\n args: {\n userId: GenericId<\"User\">;\n sessionId: GenericId<\"Session\">;\n },\n config: ConvexAuthConfig,\n) {\n const privateKey = await importPKCS8(requireEnv(\"JWT_PRIVATE_KEY\"), \"RS256\");\n const expirationTime = new Date(\n Date.now() + (config.jwt?.durationMs ?? DEFAULT_JWT_DURATION_MS),\n );\n return await new SignJWT({\n sub: args.userId + TOKEN_SUB_CLAIM_DIVIDER + args.sessionId,\n })\n .setProtectedHeader({ alg: \"RS256\" })\n .setIssuedAt()\n .setJti(generateRandomString(TOKEN_JTI_LENGTH, TOKEN_JTI_ALPHABET))\n .setIssuer(requireEnv(\"CONVEX_SITE_URL\"))\n .setAudience(\"convex\")\n .setExpirationTime(expirationTime)\n .sign(privateKey);\n}\n"],"mappings":";;;;AAOA,MAAM,0BAA0B,MAAO,KAAK;AAC5C,MAAM,mBAAmB;AACzB,MAAM,qBACJ;;AAGF,eAAsB,cACpB,MAIA,QACA;CACA,MAAM,aAAa,MAAM,YAAY,WAAW,kBAAkB,EAAE,QAAQ;CAC5E,MAAM,iBAAiB,IAAI,KACzB,KAAK,KAAK,IAAI,OAAO,KAAK,cAAc,yBACzC;AACD,QAAO,MAAM,IAAI,QAAQ,EACvB,KAAK,KAAK,SAAS,0BAA0B,KAAK,WACnD,CAAC,CACC,mBAAmB,EAAE,KAAK,SAAS,CAAC,CACpC,aAAa,CACb,OAAO,qBAAqB,kBAAkB,mBAAmB,CAAC,CAClE,UAAU,WAAW,kBAAkB,CAAC,CACxC,YAAY,SAAS,CACrB,kBAAkB,eAAe,CACjC,KAAK,WAAW"}
|
package/dist/server/totp.d.ts
CHANGED
|
@@ -1,23 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { AuthError } from "./fx.js";
|
|
3
|
-
import { Fx } from "@robelest/fx";
|
|
4
|
-
|
|
5
|
-
//#region src/server/totp.d.ts
|
|
6
|
-
type EnrichedActionCtx = GenericActionCtxWithAuthConfig<AuthDataModel>;
|
|
7
|
-
type TotpResult = {
|
|
8
|
-
kind: "signedIn";
|
|
9
|
-
signedIn: SessionInfo | null;
|
|
10
|
-
} | {
|
|
11
|
-
kind: "totpSetup";
|
|
12
|
-
uri: string;
|
|
13
|
-
secret: string;
|
|
14
|
-
verifier: string;
|
|
15
|
-
totpId: string;
|
|
16
|
-
};
|
|
17
|
-
declare const handleTotp: (ctx: EnrichedActionCtx, provider: TotpProviderConfig, args: {
|
|
18
|
-
params?: Record<string, any>;
|
|
19
|
-
verifier?: string;
|
|
20
|
-
}) => Fx<TotpResult, AuthError>;
|
|
21
|
-
//#endregion
|
|
22
|
-
export { handleTotp };
|
|
23
|
-
//# sourceMappingURL=totp.d.ts.map
|
|
1
|
+
export { };
|
package/dist/server/totp.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AuthError, Fx } from "./fx.js";
|
|
2
|
+
import { userIdFromIdentitySubject } from "./identity.js";
|
|
2
3
|
import { callVerifierSignature } from "./mutations/signature.js";
|
|
3
4
|
import { callSignIn } from "./mutations/signin.js";
|
|
4
5
|
import { callVerifier } from "./mutations/verifier.js";
|
|
@@ -50,12 +51,13 @@ const resolveTotpDispatchFx = (params, verifier) => resolveTotpFlowFx(params).pi
|
|
|
50
51
|
};
|
|
51
52
|
})
|
|
52
53
|
})));
|
|
54
|
+
/** @internal */
|
|
53
55
|
const handleTotp = (ctx, provider, args) => {
|
|
54
56
|
return resolveTotpDispatchFx(args.params ?? {}, args.verifier).pipe(Fx.chain((dispatch) => Fx.match(dispatch).on("flow", {
|
|
55
57
|
setup: ({ params }) => Fx.from({
|
|
56
58
|
ok: () => ctx.auth.getUserIdentity(),
|
|
57
59
|
err: (e) => new AuthError("INTERNAL_ERROR", String(e))
|
|
58
|
-
}).pipe(Fx.chain((identity) => identity === null ? Fx.fail(new AuthError("TOTP_AUTH_REQUIRED")) : Fx.succeed(identity.subject
|
|
60
|
+
}).pipe(Fx.chain((identity) => identity === null ? Fx.fail(new AuthError("TOTP_AUTH_REQUIRED")) : Fx.succeed(userIdFromIdentitySubject(identity.subject))), Fx.chain((userId) => Fx.from({
|
|
59
61
|
ok: async () => {
|
|
60
62
|
const secret = new Uint8Array(20);
|
|
61
63
|
crypto.getRandomValues(secret);
|
|
@@ -94,7 +96,7 @@ const handleTotp = (ctx, provider, args) => {
|
|
|
94
96
|
confirm: ({ code, totpId, verifier }) => Fx.from({
|
|
95
97
|
ok: () => ctx.auth.getUserIdentity(),
|
|
96
98
|
err: (e) => new AuthError("INTERNAL_ERROR", String(e))
|
|
97
|
-
}).pipe(Fx.chain((identity) => identity === null ? Fx.fail(new AuthError("TOTP_AUTH_REQUIRED")) : Fx.succeed(identity.subject
|
|
99
|
+
}).pipe(Fx.chain((identity) => identity === null ? Fx.fail(new AuthError("TOTP_AUTH_REQUIRED")) : Fx.succeed(userIdFromIdentitySubject(identity.subject))), Fx.chain((userId) => Fx.from({
|
|
98
100
|
ok: () => queryTotpById(ctx, totpId),
|
|
99
101
|
err: () => new AuthError("TOTP_NOT_FOUND")
|
|
100
102
|
}).pipe(Fx.chain((doc) => doc === null ? Fx.fail(new AuthError("TOTP_NOT_FOUND")) : Fx.succeed(doc)), Fx.chain((totpDoc) => totpDoc.verified ? Fx.fail(new AuthError("TOTP_ALREADY_VERIFIED")) : Fx.succeed(totpDoc))).pipe(Fx.chain((totpDoc) => verifyTOTPWithGracePeriod(new Uint8Array(totpDoc.secret), provider.options.period, provider.options.digits, code, 30) ? Fx.succeed(totpDoc) : Fx.fail(new AuthError("TOTP_INVALID_CODE")))).pipe(Fx.chain((_totpDoc) => Fx.from({
|
package/dist/server/totp.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"totp.js","names":["code","verifier"],"sources":["../../src/server/totp.ts"],"sourcesContent":["/**\n * Server-side TOTP ceremony logic for two-factor authentication.\n *\n * Handles the three phases of the TOTP flow:\n * 1. setup — generate a TOTP secret and `otpauth://` URI for enrollment\n * 2. confirm — verify the first code from the authenticator app\n * 3. verify — verify a TOTP code during sign-in (2FA challenge)\n */\n\nimport { encodeBase32LowerCaseNoPadding } from \"@oslojs/encoding\";\nimport { verifyTOTPWithGracePeriod, createTOTPKeyURI } from \"@oslojs/otp\";\nimport type { Fx as FxType } from \"@robelest/fx\";\n\nimport { AuthError, Fx } from \"./fx\";\nimport { callSignIn, callVerifier } from \"./mutations/index\";\nimport { callVerifierSignature } from \"./mutations/signature\";\nimport { TotpProviderConfig, GenericActionCtxWithAuthConfig } from \"./types\";\nimport {\n AuthDataModel,\n SessionInfo,\n queryUserById,\n queryTotpById,\n queryTotpVerifiedByUserId,\n queryVerifierById,\n mutateTotpInsert,\n mutateTotpMarkVerified,\n mutateTotpUpdateLastUsed,\n mutateVerifierDelete,\n} from \"./types\";\n\ntype EnrichedActionCtx = GenericActionCtxWithAuthConfig<AuthDataModel>;\n\n// ============================================================================\n// Setup flow\n// ============================================================================\n\n// ============================================================================\n// Confirm flow\n// ============================================================================\n\n// ============================================================================\n// Verify flow (2FA during sign-in)\n// ============================================================================\n\n// ============================================================================\n// Main dispatch\n// ============================================================================\n\ntype TotpResult =\n | { kind: \"signedIn\"; signedIn: SessionInfo | null }\n | {\n kind: \"totpSetup\";\n uri: string;\n secret: string;\n verifier: string;\n totpId: string;\n };\n\nconst TOTP_FLOWS = [\"setup\", \"confirm\", \"verify\"] as const;\n\ntype TotpFlow = (typeof TOTP_FLOWS)[number];\n\ntype TotpDispatch =\n | { flow: \"setup\"; params: Record<string, unknown> }\n | { flow: \"confirm\"; code: string; totpId: string; verifier: string }\n | { flow: \"verify\"; code: string; verifier: string };\n\nconst resolveTotpFlowFx = (\n params: Record<string, unknown>,\n): FxType<TotpFlow, AuthError> => {\n const flow = params.flow;\n return typeof flow === \"string\" && TOTP_FLOWS.includes(flow as never)\n ? Fx.succeed(flow as TotpFlow)\n : Fx.fail(\n new AuthError(\n \"TOTP_MISSING_FLOW\",\n \"Missing `flow` parameter. Expected one of: setup, confirm, verify\",\n ),\n );\n};\n\nconst requireTotpVerifierFx = (\n verifier: string | undefined,\n): FxType<string, AuthError> =>\n verifier != null\n ? Fx.succeed(verifier)\n : Fx.fail(new AuthError(\"TOTP_MISSING_VERIFIER\"));\n\nconst requireTotpCodeFx = (\n params: Record<string, unknown>,\n): FxType<string, AuthError> =>\n typeof params.code === \"string\"\n ? Fx.succeed(params.code)\n : Fx.fail(new AuthError(\"TOTP_MISSING_CODE\"));\n\nconst requireTotpIdFx = (\n params: Record<string, unknown>,\n): FxType<string, AuthError> =>\n typeof params.totpId === \"string\"\n ? Fx.succeed(params.totpId)\n : Fx.fail(new AuthError(\"TOTP_MISSING_ID\"));\n\nconst resolveTotpDispatchFx = (\n params: Record<string, unknown>,\n verifier: string | undefined,\n): FxType<TotpDispatch, AuthError> =>\n resolveTotpFlowFx(params).pipe(\n Fx.chain((flow) =>\n Fx.match({ flow }).on(\"flow\", {\n setup: () => Fx.succeed({ flow: \"setup\" as const, params }),\n confirm: () =>\n Fx.gen(function* () {\n const resolvedVerifier = yield* requireTotpVerifierFx(verifier);\n const code = yield* requireTotpCodeFx(params);\n const totpId = yield* requireTotpIdFx(params);\n return {\n flow: \"confirm\" as const,\n code,\n totpId,\n verifier: resolvedVerifier,\n };\n }),\n verify: () =>\n Fx.gen(function* () {\n const resolvedVerifier = yield* requireTotpVerifierFx(verifier);\n const code = yield* requireTotpCodeFx(params);\n return {\n flow: \"verify\" as const,\n code,\n verifier: resolvedVerifier,\n };\n }),\n }),\n ),\n );\n\nexport const handleTotp = (\n ctx: EnrichedActionCtx,\n provider: TotpProviderConfig,\n args: { params?: Record<string, any>; verifier?: string },\n): FxType<TotpResult, AuthError> => {\n const params = (args.params ?? {}) as Record<string, unknown>;\n\n return resolveTotpDispatchFx(params, args.verifier).pipe(\n Fx.chain((dispatch) =>\n Fx.match(dispatch).on(\"flow\", {\n setup: ({ params }) =>\n Fx.from({\n ok: () => ctx.auth.getUserIdentity(),\n err: (e) => new AuthError(\"INTERNAL_ERROR\", String(e)),\n }).pipe(\n Fx.chain((identity) =>\n identity === null\n ? Fx.fail(new AuthError(\"TOTP_AUTH_REQUIRED\"))\n : Fx.succeed(identity.subject.split(\"|\")[0]!),\n ),\n Fx.chain((userId) =>\n Fx.from({\n ok: async () => {\n const secret = new Uint8Array(20);\n crypto.getRandomValues(secret);\n\n let accountName: string = params.accountName as string;\n if (!accountName) {\n const user = await queryUserById(ctx, userId);\n accountName = user?.email ?? \"user\";\n }\n\n const uri = createTOTPKeyURI(\n provider.options.issuer,\n accountName,\n secret,\n provider.options.period,\n provider.options.digits,\n );\n const base32Secret = encodeBase32LowerCaseNoPadding(secret);\n\n const verifier = await callVerifier(ctx);\n await callVerifierSignature(ctx, {\n verifier,\n signature: JSON.stringify({\n secret: Array.from(secret),\n userId,\n digits: provider.options.digits,\n period: provider.options.period,\n }),\n });\n\n const totpId = await mutateTotpInsert(ctx, {\n userId,\n secret: secret.buffer.slice(\n secret.byteOffset,\n secret.byteOffset + secret.byteLength,\n ),\n digits: provider.options.digits,\n period: provider.options.period,\n verified: false,\n name:\n typeof params.name === \"string\" ? params.name : undefined,\n createdAt: Date.now(),\n });\n\n return {\n kind: \"totpSetup\" as const,\n uri,\n secret: base32Secret,\n verifier,\n totpId,\n };\n },\n err: (e) =>\n new AuthError(\n \"INTERNAL_ERROR\",\n `TOTP setup failed: ${String(e)}`,\n ),\n }),\n ),\n ),\n confirm: ({ code, totpId, verifier }) =>\n Fx.from({\n ok: () => ctx.auth.getUserIdentity(),\n err: (e) => new AuthError(\"INTERNAL_ERROR\", String(e)),\n }).pipe(\n Fx.chain((identity) =>\n identity === null\n ? Fx.fail(new AuthError(\"TOTP_AUTH_REQUIRED\"))\n : Fx.succeed(identity.subject.split(\"|\")[0]!),\n ),\n Fx.chain((userId) =>\n Fx.from({\n ok: () => queryTotpById(ctx, totpId),\n err: () => new AuthError(\"TOTP_NOT_FOUND\"),\n })\n .pipe(\n Fx.chain((doc) =>\n doc === null\n ? Fx.fail(new AuthError(\"TOTP_NOT_FOUND\"))\n : Fx.succeed(doc),\n ),\n Fx.chain((totpDoc) =>\n totpDoc.verified\n ? Fx.fail(new AuthError(\"TOTP_ALREADY_VERIFIED\"))\n : Fx.succeed(totpDoc),\n ),\n )\n .pipe(\n Fx.chain((totpDoc) =>\n verifyTOTPWithGracePeriod(\n new Uint8Array(totpDoc.secret),\n provider.options.period,\n provider.options.digits,\n code,\n 30,\n )\n ? Fx.succeed(totpDoc)\n : Fx.fail(new AuthError(\"TOTP_INVALID_CODE\")),\n ),\n )\n .pipe(\n Fx.chain((_totpDoc) =>\n Fx.from({\n ok: async () => {\n await mutateTotpMarkVerified(ctx, totpId, Date.now());\n await mutateVerifierDelete(ctx, verifier);\n return callSignIn(ctx, {\n userId,\n generateTokens: true,\n });\n },\n err: (e) => new AuthError(\"INTERNAL_ERROR\", String(e)),\n }),\n ),\n )\n .pipe(\n Fx.map((signInResult) => ({\n kind: \"signedIn\" as const,\n signedIn: signInResult,\n })),\n ),\n ),\n ),\n verify: ({ code, verifier }) =>\n Fx.from({\n ok: () => queryVerifierById(ctx, verifier),\n err: () => new AuthError(\"TOTP_INVALID_VERIFIER\"),\n }).pipe(\n Fx.chain((doc) =>\n doc === null\n ? Fx.fail(new AuthError(\"TOTP_INVALID_VERIFIER\"))\n : Fx.succeed(doc),\n ),\n Fx.map((doc) => {\n const data = JSON.parse(doc.signature!);\n return { userId: data.userId as string, code, verifier };\n }),\n Fx.chain(({ userId, code, verifier }) =>\n Fx.from({\n ok: () => queryTotpVerifiedByUserId(ctx, userId),\n err: () => new AuthError(\"TOTP_NO_ENROLLMENT\"),\n }).pipe(\n Fx.chain((totpDoc) =>\n totpDoc === null\n ? Fx.fail(new AuthError(\"TOTP_NO_ENROLLMENT\"))\n : Fx.succeed(totpDoc),\n ),\n Fx.chain((totpDoc) =>\n verifyTOTPWithGracePeriod(\n new Uint8Array(totpDoc.secret),\n totpDoc.period,\n totpDoc.digits,\n code,\n 30,\n )\n ? Fx.succeed(totpDoc)\n : Fx.fail(new AuthError(\"TOTP_INVALID_CODE\")),\n ),\n Fx.chain((totpDoc) =>\n Fx.from({\n ok: async () => {\n await mutateTotpUpdateLastUsed(\n ctx,\n totpDoc._id,\n Date.now(),\n );\n await mutateVerifierDelete(ctx, verifier);\n return callSignIn(ctx, { userId, generateTokens: true });\n },\n err: (e) => new AuthError(\"INTERNAL_ERROR\", String(e)),\n }),\n ),\n Fx.map((signInResult) => ({\n kind: \"signedIn\" as const,\n signedIn: signInResult,\n })),\n ),\n ),\n ),\n }),\n ),\n );\n};\n\n// ============================================================================\n// Helpers\n// ============================================================================\n"],"mappings":";;;;;;;;;;;;;;;;;AA0DA,MAAM,aAAa;CAAC;CAAS;CAAW;CAAS;AASjD,MAAM,qBACJ,WACgC;CAChC,MAAM,OAAO,OAAO;AACpB,QAAO,OAAO,SAAS,YAAY,WAAW,SAAS,KAAc,GACjE,GAAG,QAAQ,KAAiB,GAC5B,GAAG,KACD,IAAI,UACF,qBACA,oEACD,CACF;;AAGP,MAAM,yBACJ,aAEA,YAAY,OACR,GAAG,QAAQ,SAAS,GACpB,GAAG,KAAK,IAAI,UAAU,wBAAwB,CAAC;AAErD,MAAM,qBACJ,WAEA,OAAO,OAAO,SAAS,WACnB,GAAG,QAAQ,OAAO,KAAK,GACvB,GAAG,KAAK,IAAI,UAAU,oBAAoB,CAAC;AAEjD,MAAM,mBACJ,WAEA,OAAO,OAAO,WAAW,WACrB,GAAG,QAAQ,OAAO,OAAO,GACzB,GAAG,KAAK,IAAI,UAAU,kBAAkB,CAAC;AAE/C,MAAM,yBACJ,QACA,aAEA,kBAAkB,OAAO,CAAC,KACxB,GAAG,OAAO,SACR,GAAG,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,QAAQ;CAC5B,aAAa,GAAG,QAAQ;EAAE,MAAM;EAAkB;EAAQ,CAAC;CAC3D,eACE,GAAG,IAAI,aAAa;EAClB,MAAM,mBAAmB,OAAO,sBAAsB,SAAS;AAG/D,SAAO;GACL,MAAM;GACN,MAJW,OAAO,kBAAkB,OAAO;GAK3C,QAJa,OAAO,gBAAgB,OAAO;GAK3C,UAAU;GACX;GACD;CACJ,cACE,GAAG,IAAI,aAAa;EAClB,MAAM,mBAAmB,OAAO,sBAAsB,SAAS;AAE/D,SAAO;GACL,MAAM;GACN,MAHW,OAAO,kBAAkB,OAAO;GAI3C,UAAU;GACX;GACD;CACL,CAAC,CACH,CACF;AAEH,MAAa,cACX,KACA,UACA,SACkC;AAGlC,QAAO,sBAFS,KAAK,UAAU,EAAE,EAEI,KAAK,SAAS,CAAC,KAClD,GAAG,OAAO,aACR,GAAG,MAAM,SAAS,CAAC,GAAG,QAAQ;EAC5B,QAAQ,EAAE,aACR,GAAG,KAAK;GACN,UAAU,IAAI,KAAK,iBAAiB;GACpC,MAAM,MAAM,IAAI,UAAU,kBAAkB,OAAO,EAAE,CAAC;GACvD,CAAC,CAAC,KACD,GAAG,OAAO,aACR,aAAa,OACT,GAAG,KAAK,IAAI,UAAU,qBAAqB,CAAC,GAC5C,GAAG,QAAQ,SAAS,QAAQ,MAAM,IAAI,CAAC,GAAI,CAChD,EACD,GAAG,OAAO,WACR,GAAG,KAAK;GACN,IAAI,YAAY;IACd,MAAM,SAAS,IAAI,WAAW,GAAG;AACjC,WAAO,gBAAgB,OAAO;IAE9B,IAAI,cAAsB,OAAO;AACjC,QAAI,CAAC,YAEH,gBADa,MAAM,cAAc,KAAK,OAAO,GACzB,SAAS;IAG/B,MAAM,MAAM,iBACV,SAAS,QAAQ,QACjB,aACA,QACA,SAAS,QAAQ,QACjB,SAAS,QAAQ,OAClB;IACD,MAAM,eAAe,+BAA+B,OAAO;IAE3D,MAAM,WAAW,MAAM,aAAa,IAAI;AACxC,UAAM,sBAAsB,KAAK;KAC/B;KACA,WAAW,KAAK,UAAU;MACxB,QAAQ,MAAM,KAAK,OAAO;MAC1B;MACA,QAAQ,SAAS,QAAQ;MACzB,QAAQ,SAAS,QAAQ;MAC1B,CAAC;KACH,CAAC;AAgBF,WAAO;KACL,MAAM;KACN;KACA,QAAQ;KACR;KACA,QAnBa,MAAM,iBAAiB,KAAK;MACzC;MACA,QAAQ,OAAO,OAAO,MACpB,OAAO,YACP,OAAO,aAAa,OAAO,WAC5B;MACD,QAAQ,SAAS,QAAQ;MACzB,QAAQ,SAAS,QAAQ;MACzB,UAAU;MACV,MACE,OAAO,OAAO,SAAS,WAAW,OAAO,OAAO;MAClD,WAAW,KAAK,KAAK;MACtB,CAAC;KAQD;;GAEH,MAAM,MACJ,IAAI,UACF,kBACA,sBAAsB,OAAO,EAAE,GAChC;GACJ,CAAC,CACH,CACF;EACH,UAAU,EAAE,MAAM,QAAQ,eACxB,GAAG,KAAK;GACN,UAAU,IAAI,KAAK,iBAAiB;GACpC,MAAM,MAAM,IAAI,UAAU,kBAAkB,OAAO,EAAE,CAAC;GACvD,CAAC,CAAC,KACD,GAAG,OAAO,aACR,aAAa,OACT,GAAG,KAAK,IAAI,UAAU,qBAAqB,CAAC,GAC5C,GAAG,QAAQ,SAAS,QAAQ,MAAM,IAAI,CAAC,GAAI,CAChD,EACD,GAAG,OAAO,WACR,GAAG,KAAK;GACN,UAAU,cAAc,KAAK,OAAO;GACpC,WAAW,IAAI,UAAU,iBAAiB;GAC3C,CAAC,CACC,KACC,GAAG,OAAO,QACR,QAAQ,OACJ,GAAG,KAAK,IAAI,UAAU,iBAAiB,CAAC,GACxC,GAAG,QAAQ,IAAI,CACpB,EACD,GAAG,OAAO,YACR,QAAQ,WACJ,GAAG,KAAK,IAAI,UAAU,wBAAwB,CAAC,GAC/C,GAAG,QAAQ,QAAQ,CACxB,CACF,CACA,KACC,GAAG,OAAO,YACR,0BACE,IAAI,WAAW,QAAQ,OAAO,EAC9B,SAAS,QAAQ,QACjB,SAAS,QAAQ,QACjB,MACA,GACD,GACG,GAAG,QAAQ,QAAQ,GACnB,GAAG,KAAK,IAAI,UAAU,oBAAoB,CAAC,CAChD,CACF,CACA,KACC,GAAG,OAAO,aACR,GAAG,KAAK;GACN,IAAI,YAAY;AACd,UAAM,uBAAuB,KAAK,QAAQ,KAAK,KAAK,CAAC;AACrD,UAAM,qBAAqB,KAAK,SAAS;AACzC,WAAO,WAAW,KAAK;KACrB;KACA,gBAAgB;KACjB,CAAC;;GAEJ,MAAM,MAAM,IAAI,UAAU,kBAAkB,OAAO,EAAE,CAAC;GACvD,CAAC,CACH,CACF,CACA,KACC,GAAG,KAAK,kBAAkB;GACxB,MAAM;GACN,UAAU;GACX,EAAE,CACJ,CACJ,CACF;EACH,SAAS,EAAE,MAAM,eACf,GAAG,KAAK;GACN,UAAU,kBAAkB,KAAK,SAAS;GAC1C,WAAW,IAAI,UAAU,wBAAwB;GAClD,CAAC,CAAC,KACD,GAAG,OAAO,QACR,QAAQ,OACJ,GAAG,KAAK,IAAI,UAAU,wBAAwB,CAAC,GAC/C,GAAG,QAAQ,IAAI,CACpB,EACD,GAAG,KAAK,QAAQ;AAEd,UAAO;IAAE,QADI,KAAK,MAAM,IAAI,UAAW,CACjB;IAAkB;IAAM;IAAU;IACxD,EACF,GAAG,OAAO,EAAE,QAAQ,cAAM,2BACxB,GAAG,KAAK;GACN,UAAU,0BAA0B,KAAK,OAAO;GAChD,WAAW,IAAI,UAAU,qBAAqB;GAC/C,CAAC,CAAC,KACD,GAAG,OAAO,YACR,YAAY,OACR,GAAG,KAAK,IAAI,UAAU,qBAAqB,CAAC,GAC5C,GAAG,QAAQ,QAAQ,CACxB,EACD,GAAG,OAAO,YACR,0BACE,IAAI,WAAW,QAAQ,OAAO,EAC9B,QAAQ,QACR,QAAQ,QACRA,QACA,GACD,GACG,GAAG,QAAQ,QAAQ,GACnB,GAAG,KAAK,IAAI,UAAU,oBAAoB,CAAC,CAChD,EACD,GAAG,OAAO,YACR,GAAG,KAAK;GACN,IAAI,YAAY;AACd,UAAM,yBACJ,KACA,QAAQ,KACR,KAAK,KAAK,CACX;AACD,UAAM,qBAAqB,KAAKC,WAAS;AACzC,WAAO,WAAW,KAAK;KAAE;KAAQ,gBAAgB;KAAM,CAAC;;GAE1D,MAAM,MAAM,IAAI,UAAU,kBAAkB,OAAO,EAAE,CAAC;GACvD,CAAC,CACH,EACD,GAAG,KAAK,kBAAkB;GACxB,MAAM;GACN,UAAU;GACX,EAAE,CACJ,CACF,CACF;EACJ,CAAC,CACH,CACF"}
|
|
1
|
+
{"version":3,"file":"totp.js","names":["code","verifier"],"sources":["../../src/server/totp.ts"],"sourcesContent":["/**\n * Server-side TOTP ceremony logic for two-factor authentication.\n *\n * Handles the three phases of the TOTP flow:\n * 1. setup — generate a TOTP secret and `otpauth://` URI for enrollment\n * 2. confirm — verify the first code from the authenticator app\n * 3. verify — verify a TOTP code during sign-in (2FA challenge)\n */\n\nimport { encodeBase32LowerCaseNoPadding } from \"@oslojs/encoding\";\nimport { verifyTOTPWithGracePeriod, createTOTPKeyURI } from \"@oslojs/otp\";\nimport type { Fx as FxType } from \"@robelest/fx\";\n\nimport { AuthError, Fx } from \"./fx\";\nimport { userIdFromIdentitySubject } from \"./identity\";\nimport { callSignIn, callVerifier } from \"./mutations/index\";\nimport { callVerifierSignature } from \"./mutations/signature\";\nimport { TotpProviderConfig, GenericActionCtxWithAuthConfig } from \"./types\";\nimport {\n AuthDataModel,\n SessionInfo,\n queryUserById,\n queryTotpById,\n queryTotpVerifiedByUserId,\n queryVerifierById,\n mutateTotpInsert,\n mutateTotpMarkVerified,\n mutateTotpUpdateLastUsed,\n mutateVerifierDelete,\n} from \"./types\";\n\ntype EnrichedActionCtx = GenericActionCtxWithAuthConfig<AuthDataModel>;\n\n// ============================================================================\n// Setup flow\n// ============================================================================\n\n// ============================================================================\n// Confirm flow\n// ============================================================================\n\n// ============================================================================\n// Verify flow (2FA during sign-in)\n// ============================================================================\n\n// ============================================================================\n// Main dispatch\n// ============================================================================\n\ntype TotpResult =\n | { kind: \"signedIn\"; signedIn: SessionInfo | null }\n | {\n kind: \"totpSetup\";\n uri: string;\n secret: string;\n verifier: string;\n totpId: string;\n };\n\nconst TOTP_FLOWS = [\"setup\", \"confirm\", \"verify\"] as const;\n\ntype TotpFlow = (typeof TOTP_FLOWS)[number];\n\ntype TotpDispatch =\n | { flow: \"setup\"; params: Record<string, unknown> }\n | { flow: \"confirm\"; code: string; totpId: string; verifier: string }\n | { flow: \"verify\"; code: string; verifier: string };\n\nconst resolveTotpFlowFx = (\n params: Record<string, unknown>,\n): FxType<TotpFlow, AuthError> => {\n const flow = params.flow;\n return typeof flow === \"string\" && TOTP_FLOWS.includes(flow as never)\n ? Fx.succeed(flow as TotpFlow)\n : Fx.fail(\n new AuthError(\n \"TOTP_MISSING_FLOW\",\n \"Missing `flow` parameter. Expected one of: setup, confirm, verify\",\n ),\n );\n};\n\nconst requireTotpVerifierFx = (\n verifier: string | undefined,\n): FxType<string, AuthError> =>\n verifier != null\n ? Fx.succeed(verifier)\n : Fx.fail(new AuthError(\"TOTP_MISSING_VERIFIER\"));\n\nconst requireTotpCodeFx = (\n params: Record<string, unknown>,\n): FxType<string, AuthError> =>\n typeof params.code === \"string\"\n ? Fx.succeed(params.code)\n : Fx.fail(new AuthError(\"TOTP_MISSING_CODE\"));\n\nconst requireTotpIdFx = (\n params: Record<string, unknown>,\n): FxType<string, AuthError> =>\n typeof params.totpId === \"string\"\n ? Fx.succeed(params.totpId)\n : Fx.fail(new AuthError(\"TOTP_MISSING_ID\"));\n\nconst resolveTotpDispatchFx = (\n params: Record<string, unknown>,\n verifier: string | undefined,\n): FxType<TotpDispatch, AuthError> =>\n resolveTotpFlowFx(params).pipe(\n Fx.chain((flow) =>\n Fx.match({ flow }).on(\"flow\", {\n setup: () => Fx.succeed({ flow: \"setup\" as const, params }),\n confirm: () =>\n Fx.gen(function* () {\n const resolvedVerifier = yield* requireTotpVerifierFx(verifier);\n const code = yield* requireTotpCodeFx(params);\n const totpId = yield* requireTotpIdFx(params);\n return {\n flow: \"confirm\" as const,\n code,\n totpId,\n verifier: resolvedVerifier,\n };\n }),\n verify: () =>\n Fx.gen(function* () {\n const resolvedVerifier = yield* requireTotpVerifierFx(verifier);\n const code = yield* requireTotpCodeFx(params);\n return {\n flow: \"verify\" as const,\n code,\n verifier: resolvedVerifier,\n };\n }),\n }),\n ),\n );\n\n/** @internal */\nexport const handleTotp = (\n ctx: EnrichedActionCtx,\n provider: TotpProviderConfig,\n args: { params?: Record<string, any>; verifier?: string },\n): FxType<TotpResult, AuthError> => {\n const params = (args.params ?? {}) as Record<string, unknown>;\n\n return resolveTotpDispatchFx(params, args.verifier).pipe(\n Fx.chain((dispatch) =>\n Fx.match(dispatch).on(\"flow\", {\n setup: ({ params }) =>\n Fx.from({\n ok: () => ctx.auth.getUserIdentity(),\n err: (e) => new AuthError(\"INTERNAL_ERROR\", String(e)),\n }).pipe(\n Fx.chain((identity) =>\n identity === null\n ? Fx.fail(new AuthError(\"TOTP_AUTH_REQUIRED\"))\n : Fx.succeed(userIdFromIdentitySubject(identity.subject)),\n ),\n Fx.chain((userId) =>\n Fx.from({\n ok: async () => {\n const secret = new Uint8Array(20);\n crypto.getRandomValues(secret);\n\n let accountName: string = params.accountName as string;\n if (!accountName) {\n const user = await queryUserById(ctx, userId);\n accountName = user?.email ?? \"user\";\n }\n\n const uri = createTOTPKeyURI(\n provider.options.issuer,\n accountName,\n secret,\n provider.options.period,\n provider.options.digits,\n );\n const base32Secret = encodeBase32LowerCaseNoPadding(secret);\n\n const verifier = await callVerifier(ctx);\n await callVerifierSignature(ctx, {\n verifier,\n signature: JSON.stringify({\n secret: Array.from(secret),\n userId,\n digits: provider.options.digits,\n period: provider.options.period,\n }),\n });\n\n const totpId = await mutateTotpInsert(ctx, {\n userId,\n secret: secret.buffer.slice(\n secret.byteOffset,\n secret.byteOffset + secret.byteLength,\n ),\n digits: provider.options.digits,\n period: provider.options.period,\n verified: false,\n name:\n typeof params.name === \"string\" ? params.name : undefined,\n createdAt: Date.now(),\n });\n\n return {\n kind: \"totpSetup\" as const,\n uri,\n secret: base32Secret,\n verifier,\n totpId,\n };\n },\n err: (e) =>\n new AuthError(\n \"INTERNAL_ERROR\",\n `TOTP setup failed: ${String(e)}`,\n ),\n }),\n ),\n ),\n confirm: ({ code, totpId, verifier }) =>\n Fx.from({\n ok: () => ctx.auth.getUserIdentity(),\n err: (e) => new AuthError(\"INTERNAL_ERROR\", String(e)),\n }).pipe(\n Fx.chain((identity) =>\n identity === null\n ? Fx.fail(new AuthError(\"TOTP_AUTH_REQUIRED\"))\n : Fx.succeed(userIdFromIdentitySubject(identity.subject)),\n ),\n Fx.chain((userId) =>\n Fx.from({\n ok: () => queryTotpById(ctx, totpId),\n err: () => new AuthError(\"TOTP_NOT_FOUND\"),\n })\n .pipe(\n Fx.chain((doc) =>\n doc === null\n ? Fx.fail(new AuthError(\"TOTP_NOT_FOUND\"))\n : Fx.succeed(doc),\n ),\n Fx.chain((totpDoc) =>\n totpDoc.verified\n ? Fx.fail(new AuthError(\"TOTP_ALREADY_VERIFIED\"))\n : Fx.succeed(totpDoc),\n ),\n )\n .pipe(\n Fx.chain((totpDoc) =>\n verifyTOTPWithGracePeriod(\n new Uint8Array(totpDoc.secret),\n provider.options.period,\n provider.options.digits,\n code,\n 30,\n )\n ? Fx.succeed(totpDoc)\n : Fx.fail(new AuthError(\"TOTP_INVALID_CODE\")),\n ),\n )\n .pipe(\n Fx.chain((_totpDoc) =>\n Fx.from({\n ok: async () => {\n await mutateTotpMarkVerified(ctx, totpId, Date.now());\n await mutateVerifierDelete(ctx, verifier);\n return callSignIn(ctx, {\n userId,\n generateTokens: true,\n });\n },\n err: (e) => new AuthError(\"INTERNAL_ERROR\", String(e)),\n }),\n ),\n )\n .pipe(\n Fx.map((signInResult) => ({\n kind: \"signedIn\" as const,\n signedIn: signInResult,\n })),\n ),\n ),\n ),\n verify: ({ code, verifier }) =>\n Fx.from({\n ok: () => queryVerifierById(ctx, verifier),\n err: () => new AuthError(\"TOTP_INVALID_VERIFIER\"),\n }).pipe(\n Fx.chain((doc) =>\n doc === null\n ? Fx.fail(new AuthError(\"TOTP_INVALID_VERIFIER\"))\n : Fx.succeed(doc),\n ),\n Fx.map((doc) => {\n const data = JSON.parse(doc.signature!);\n return { userId: data.userId as string, code, verifier };\n }),\n Fx.chain(({ userId, code, verifier }) =>\n Fx.from({\n ok: () => queryTotpVerifiedByUserId(ctx, userId),\n err: () => new AuthError(\"TOTP_NO_ENROLLMENT\"),\n }).pipe(\n Fx.chain((totpDoc) =>\n totpDoc === null\n ? Fx.fail(new AuthError(\"TOTP_NO_ENROLLMENT\"))\n : Fx.succeed(totpDoc),\n ),\n Fx.chain((totpDoc) =>\n verifyTOTPWithGracePeriod(\n new Uint8Array(totpDoc.secret),\n totpDoc.period,\n totpDoc.digits,\n code,\n 30,\n )\n ? Fx.succeed(totpDoc)\n : Fx.fail(new AuthError(\"TOTP_INVALID_CODE\")),\n ),\n Fx.chain((totpDoc) =>\n Fx.from({\n ok: async () => {\n await mutateTotpUpdateLastUsed(\n ctx,\n totpDoc._id,\n Date.now(),\n );\n await mutateVerifierDelete(ctx, verifier);\n return callSignIn(ctx, { userId, generateTokens: true });\n },\n err: (e) => new AuthError(\"INTERNAL_ERROR\", String(e)),\n }),\n ),\n Fx.map((signInResult) => ({\n kind: \"signedIn\" as const,\n signedIn: signInResult,\n })),\n ),\n ),\n ),\n }),\n ),\n );\n};\n\n// ============================================================================\n// Helpers\n// ============================================================================\n"],"mappings":"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"}
|