@antasphere/chassis-server 1.0.0
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/LICENSE +82 -0
- package/dist/accounts/deletion.d.ts +163 -0
- package/dist/accounts/deletion.d.ts.map +1 -0
- package/dist/accounts/deletion.js +360 -0
- package/dist/accounts/deletion.js.map +1 -0
- package/dist/accounts/erasure-log.d.ts +66 -0
- package/dist/accounts/erasure-log.d.ts.map +1 -0
- package/dist/accounts/erasure-log.js +117 -0
- package/dist/accounts/erasure-log.js.map +1 -0
- package/dist/accounts/index.d.ts +6 -0
- package/dist/accounts/index.d.ts.map +1 -0
- package/dist/accounts/index.js +6 -0
- package/dist/accounts/index.js.map +1 -0
- package/dist/accounts/mint-refusal.d.ts +47 -0
- package/dist/accounts/mint-refusal.d.ts.map +1 -0
- package/dist/accounts/mint-refusal.js +59 -0
- package/dist/accounts/mint-refusal.js.map +1 -0
- package/dist/accounts/signup-duplicate.d.ts +14 -0
- package/dist/accounts/signup-duplicate.d.ts.map +1 -0
- package/dist/accounts/signup-duplicate.js +25 -0
- package/dist/accounts/signup-duplicate.js.map +1 -0
- package/dist/accounts/superadmin.d.ts +27 -0
- package/dist/accounts/superadmin.d.ts.map +1 -0
- package/dist/accounts/superadmin.js +46 -0
- package/dist/accounts/superadmin.js.map +1 -0
- package/dist/api/apikeys.d.ts +14 -0
- package/dist/api/apikeys.d.ts.map +1 -0
- package/dist/api/apikeys.js +124 -0
- package/dist/api/apikeys.js.map +1 -0
- package/dist/api/audit.d.ts +4 -0
- package/dist/api/audit.d.ts.map +1 -0
- package/dist/api/audit.js +75 -0
- package/dist/api/audit.js.map +1 -0
- package/dist/api/break-glass.d.ts +48 -0
- package/dist/api/break-glass.d.ts.map +1 -0
- package/dist/api/break-glass.js +217 -0
- package/dist/api/break-glass.js.map +1 -0
- package/dist/api/cli-auth.d.ts +84 -0
- package/dist/api/cli-auth.d.ts.map +1 -0
- package/dist/api/cli-auth.js +177 -0
- package/dist/api/cli-auth.js.map +1 -0
- package/dist/api/create-api.d.ts +123 -0
- package/dist/api/create-api.d.ts.map +1 -0
- package/dist/api/create-api.js +1165 -0
- package/dist/api/create-api.js.map +1 -0
- package/dist/api/demo-passes.d.ts +45 -0
- package/dist/api/demo-passes.d.ts.map +1 -0
- package/dist/api/demo-passes.js +143 -0
- package/dist/api/demo-passes.js.map +1 -0
- package/dist/api/errors.d.ts +5 -0
- package/dist/api/errors.d.ts.map +1 -0
- package/dist/api/errors.js +5 -0
- package/dist/api/errors.js.map +1 -0
- package/dist/api/export.d.ts +63 -0
- package/dist/api/export.d.ts.map +1 -0
- package/dist/api/export.js +393 -0
- package/dist/api/export.js.map +1 -0
- package/dist/api/files.d.ts +40 -0
- package/dist/api/files.d.ts.map +1 -0
- package/dist/api/files.js +150 -0
- package/dist/api/files.js.map +1 -0
- package/dist/api/index.d.ts +17 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +17 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/invitations.d.ts +50 -0
- package/dist/api/invitations.d.ts.map +1 -0
- package/dist/api/invitations.js +313 -0
- package/dist/api/invitations.js.map +1 -0
- package/dist/api/members.d.ts +25 -0
- package/dist/api/members.d.ts.map +1 -0
- package/dist/api/members.js +522 -0
- package/dist/api/members.js.map +1 -0
- package/dist/api/onboarding.d.ts +26 -0
- package/dist/api/onboarding.d.ts.map +1 -0
- package/dist/api/onboarding.js +23 -0
- package/dist/api/onboarding.js.map +1 -0
- package/dist/api/openapi-doc.d.ts +21 -0
- package/dist/api/openapi-doc.d.ts.map +1 -0
- package/dist/api/openapi-doc.js +25 -0
- package/dist/api/openapi-doc.js.map +1 -0
- package/dist/api/projects.d.ts +24 -0
- package/dist/api/projects.d.ts.map +1 -0
- package/dist/api/projects.js +624 -0
- package/dist/api/projects.js.map +1 -0
- package/dist/api/scope-routes.d.ts +12 -0
- package/dist/api/scope-routes.d.ts.map +1 -0
- package/dist/api/scope-routes.js +2 -0
- package/dist/api/scope-routes.js.map +1 -0
- package/dist/api/sso-connect.d.ts +70 -0
- package/dist/api/sso-connect.d.ts.map +1 -0
- package/dist/api/sso-connect.js +164 -0
- package/dist/api/sso-connect.js.map +1 -0
- package/dist/api/sso-logout.d.ts +41 -0
- package/dist/api/sso-logout.d.ts.map +1 -0
- package/dist/api/sso-logout.js +34 -0
- package/dist/api/sso-logout.js.map +1 -0
- package/dist/api/teams.d.ts +43 -0
- package/dist/api/teams.d.ts.map +1 -0
- package/dist/api/teams.js +447 -0
- package/dist/api/teams.js.map +1 -0
- package/dist/api/workspaces.d.ts +114 -0
- package/dist/api/workspaces.d.ts.map +1 -0
- package/dist/api/workspaces.js +462 -0
- package/dist/api/workspaces.js.map +1 -0
- package/dist/apikeys/index.d.ts +3 -0
- package/dist/apikeys/index.d.ts.map +1 -0
- package/dist/apikeys/index.js +3 -0
- package/dist/apikeys/index.js.map +1 -0
- package/dist/apikeys/peppers.d.ts +53 -0
- package/dist/apikeys/peppers.d.ts.map +1 -0
- package/dist/apikeys/peppers.js +97 -0
- package/dist/apikeys/peppers.js.map +1 -0
- package/dist/apikeys/service.d.ts +56 -0
- package/dist/apikeys/service.d.ts.map +1 -0
- package/dist/apikeys/service.js +196 -0
- package/dist/apikeys/service.js.map +1 -0
- package/dist/app.d.ts +65 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +142 -0
- package/dist/app.js.map +1 -0
- package/dist/audit/filters.d.ts +38 -0
- package/dist/audit/filters.d.ts.map +1 -0
- package/dist/audit/filters.js +97 -0
- package/dist/audit/filters.js.map +1 -0
- package/dist/audit/index.d.ts +3 -0
- package/dist/audit/index.d.ts.map +1 -0
- package/dist/audit/index.js +3 -0
- package/dist/audit/index.js.map +1 -0
- package/dist/audit/service.d.ts +65 -0
- package/dist/audit/service.d.ts.map +1 -0
- package/dist/audit/service.js +103 -0
- package/dist/audit/service.js.map +1 -0
- package/dist/boot.d.ts +8 -0
- package/dist/boot.d.ts.map +1 -0
- package/dist/boot.js +822 -0
- package/dist/boot.js.map +1 -0
- package/dist/constant-time.d.ts +3 -0
- package/dist/constant-time.d.ts.map +1 -0
- package/dist/constant-time.js +10 -0
- package/dist/constant-time.js.map +1 -0
- package/dist/email/driver.d.ts +16 -0
- package/dist/email/driver.d.ts.map +1 -0
- package/dist/email/driver.js +136 -0
- package/dist/email/driver.js.map +1 -0
- package/dist/email/index.d.ts +4 -0
- package/dist/email/index.d.ts.map +1 -0
- package/dist/email/index.js +4 -0
- package/dist/email/index.js.map +1 -0
- package/dist/email/shell.d.ts +72 -0
- package/dist/email/shell.d.ts.map +1 -0
- package/dist/email/shell.js +146 -0
- package/dist/email/shell.js.map +1 -0
- package/dist/email/templates.d.ts +83 -0
- package/dist/email/templates.d.ts.map +1 -0
- package/dist/email/templates.js +96 -0
- package/dist/email/templates.js.map +1 -0
- package/dist/entitlements/check.d.ts +140 -0
- package/dist/entitlements/check.d.ts.map +1 -0
- package/dist/entitlements/check.js +276 -0
- package/dist/entitlements/check.js.map +1 -0
- package/dist/entitlements/clock-skew.d.ts +27 -0
- package/dist/entitlements/clock-skew.d.ts.map +1 -0
- package/dist/entitlements/clock-skew.js +58 -0
- package/dist/entitlements/clock-skew.js.map +1 -0
- package/dist/entitlements/gate.d.ts +113 -0
- package/dist/entitlements/gate.d.ts.map +1 -0
- package/dist/entitlements/gate.js +430 -0
- package/dist/entitlements/gate.js.map +1 -0
- package/dist/entitlements/hub-machine-token.d.ts +83 -0
- package/dist/entitlements/hub-machine-token.d.ts.map +1 -0
- package/dist/entitlements/hub-machine-token.js +175 -0
- package/dist/entitlements/hub-machine-token.js.map +1 -0
- package/dist/entitlements/hub-subject.d.ts +32 -0
- package/dist/entitlements/hub-subject.d.ts.map +1 -0
- package/dist/entitlements/hub-subject.js +68 -0
- package/dist/entitlements/hub-subject.js.map +1 -0
- package/dist/entitlements/index.d.ts +9 -0
- package/dist/entitlements/index.d.ts.map +1 -0
- package/dist/entitlements/index.js +9 -0
- package/dist/entitlements/index.js.map +1 -0
- package/dist/entitlements/poster.d.ts +81 -0
- package/dist/entitlements/poster.d.ts.map +1 -0
- package/dist/entitlements/poster.js +169 -0
- package/dist/entitlements/poster.js.map +1 -0
- package/dist/entitlements/profiles.d.ts +121 -0
- package/dist/entitlements/profiles.d.ts.map +1 -0
- package/dist/entitlements/profiles.js +212 -0
- package/dist/entitlements/profiles.js.map +1 -0
- package/dist/entitlements/slot.d.ts +33 -0
- package/dist/entitlements/slot.d.ts.map +1 -0
- package/dist/entitlements/slot.js +75 -0
- package/dist/entitlements/slot.js.map +1 -0
- package/dist/env.d.ts +293 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +383 -0
- package/dist/env.js.map +1 -0
- package/dist/files/http.d.ts +23 -0
- package/dist/files/http.d.ts.map +1 -0
- package/dist/files/http.js +66 -0
- package/dist/files/http.js.map +1 -0
- package/dist/files/index.d.ts +5 -0
- package/dist/files/index.d.ts.map +1 -0
- package/dist/files/index.js +5 -0
- package/dist/files/index.js.map +1 -0
- package/dist/files/serve.d.ts +43 -0
- package/dist/files/serve.d.ts.map +1 -0
- package/dist/files/serve.js +71 -0
- package/dist/files/serve.js.map +1 -0
- package/dist/files/service.d.ts +69 -0
- package/dist/files/service.d.ts.map +1 -0
- package/dist/files/service.js +188 -0
- package/dist/files/service.js.map +1 -0
- package/dist/files/spool.d.ts +21 -0
- package/dist/files/spool.d.ts.map +1 -0
- package/dist/files/spool.js +47 -0
- package/dist/files/spool.js.map +1 -0
- package/dist/identity/better-auth.d.ts +3927 -0
- package/dist/identity/better-auth.d.ts.map +1 -0
- package/dist/identity/better-auth.js +896 -0
- package/dist/identity/better-auth.js.map +1 -0
- package/dist/identity/demo-pass-plugin.d.ts +29 -0
- package/dist/identity/demo-pass-plugin.d.ts.map +1 -0
- package/dist/identity/demo-pass-plugin.js +88 -0
- package/dist/identity/demo-pass-plugin.js.map +1 -0
- package/dist/identity/demo-pass-rules.d.ts +84 -0
- package/dist/identity/demo-pass-rules.d.ts.map +1 -0
- package/dist/identity/demo-pass-rules.js +223 -0
- package/dist/identity/demo-pass-rules.js.map +1 -0
- package/dist/identity/demo-pass.d.ts +159 -0
- package/dist/identity/demo-pass.d.ts.map +1 -0
- package/dist/identity/demo-pass.js +319 -0
- package/dist/identity/demo-pass.js.map +1 -0
- package/dist/identity/hub-grant.d.ts +182 -0
- package/dist/identity/hub-grant.d.ts.map +1 -0
- package/dist/identity/hub-grant.js +594 -0
- package/dist/identity/hub-grant.js.map +1 -0
- package/dist/identity/hub-jwt.d.ts +22 -0
- package/dist/identity/hub-jwt.d.ts.map +1 -0
- package/dist/identity/hub-jwt.js +77 -0
- package/dist/identity/hub-jwt.js.map +1 -0
- package/dist/identity/hub-live-gate.d.ts +17 -0
- package/dist/identity/hub-live-gate.d.ts.map +1 -0
- package/dist/identity/hub-live-gate.js +146 -0
- package/dist/identity/hub-live-gate.js.map +1 -0
- package/dist/identity/hub-logout.d.ts +88 -0
- package/dist/identity/hub-logout.d.ts.map +1 -0
- package/dist/identity/hub-logout.js +158 -0
- package/dist/identity/hub-logout.js.map +1 -0
- package/dist/identity/hub-projection.d.ts +103 -0
- package/dist/identity/hub-projection.d.ts.map +1 -0
- package/dist/identity/hub-projection.js +130 -0
- package/dist/identity/hub-projection.js.map +1 -0
- package/dist/identity/hub-reconcile.d.ts +151 -0
- package/dist/identity/hub-reconcile.d.ts.map +1 -0
- package/dist/identity/hub-reconcile.js +400 -0
- package/dist/identity/hub-reconcile.js.map +1 -0
- package/dist/identity/hub-sso.d.ts +363 -0
- package/dist/identity/hub-sso.d.ts.map +1 -0
- package/dist/identity/hub-sso.js +536 -0
- package/dist/identity/hub-sso.js.map +1 -0
- package/dist/identity/hub-user-client.d.ts +222 -0
- package/dist/identity/hub-user-client.d.ts.map +1 -0
- package/dist/identity/hub-user-client.js +406 -0
- package/dist/identity/hub-user-client.js.map +1 -0
- package/dist/identity/index.d.ts +13 -0
- package/dist/identity/index.d.ts.map +1 -0
- package/dist/identity/index.js +13 -0
- package/dist/identity/index.js.map +1 -0
- package/dist/identity/oauth-jwt.d.ts +32 -0
- package/dist/identity/oauth-jwt.d.ts.map +1 -0
- package/dist/identity/oauth-jwt.js +122 -0
- package/dist/identity/oauth-jwt.js.map +1 -0
- package/dist/identity/resolve-membership.d.ts +28 -0
- package/dist/identity/resolve-membership.d.ts.map +1 -0
- package/dist/identity/resolve-membership.js +69 -0
- package/dist/identity/resolve-membership.js.map +1 -0
- package/dist/identity/signing-key.d.ts +49 -0
- package/dist/identity/signing-key.d.ts.map +1 -0
- package/dist/identity/signing-key.js +144 -0
- package/dist/identity/signing-key.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/invitations/index.d.ts +2 -0
- package/dist/invitations/index.d.ts.map +1 -0
- package/dist/invitations/index.js +2 -0
- package/dist/invitations/index.js.map +1 -0
- package/dist/invitations/service.d.ts +36 -0
- package/dist/invitations/service.d.ts.map +1 -0
- package/dist/invitations/service.js +122 -0
- package/dist/invitations/service.js.map +1 -0
- package/dist/jobs/index.d.ts +3 -0
- package/dist/jobs/index.d.ts.map +1 -0
- package/dist/jobs/index.js +3 -0
- package/dist/jobs/index.js.map +1 -0
- package/dist/jobs/pgboss.d.ts +119 -0
- package/dist/jobs/pgboss.d.ts.map +1 -0
- package/dist/jobs/pgboss.js +521 -0
- package/dist/jobs/pgboss.js.map +1 -0
- package/dist/jobs/timers.d.ts +127 -0
- package/dist/jobs/timers.d.ts.map +1 -0
- package/dist/jobs/timers.js +228 -0
- package/dist/jobs/timers.js.map +1 -0
- package/dist/logger.d.ts +17 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +64 -0
- package/dist/logger.js.map +1 -0
- package/dist/mcp/errors.d.ts +37 -0
- package/dist/mcp/errors.d.ts.map +1 -0
- package/dist/mcp/errors.js +77 -0
- package/dist/mcp/errors.js.map +1 -0
- package/dist/mcp/http.d.ts +35 -0
- package/dist/mcp/http.d.ts.map +1 -0
- package/dist/mcp/http.js +80 -0
- package/dist/mcp/http.js.map +1 -0
- package/dist/mcp/index.d.ts +8 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +8 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/inputs.d.ts +19 -0
- package/dist/mcp/inputs.d.ts.map +1 -0
- package/dist/mcp/inputs.js +28 -0
- package/dist/mcp/inputs.js.map +1 -0
- package/dist/mcp/projects.d.ts +31 -0
- package/dist/mcp/projects.d.ts.map +1 -0
- package/dist/mcp/projects.js +315 -0
- package/dist/mcp/projects.js.map +1 -0
- package/dist/mcp/server.d.ts +52 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +93 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/teams.d.ts +7 -0
- package/dist/mcp/teams.d.ts.map +1 -0
- package/dist/mcp/teams.js +62 -0
- package/dist/mcp/teams.js.map +1 -0
- package/dist/mcp/tool-kit.d.ts +67 -0
- package/dist/mcp/tool-kit.d.ts.map +1 -0
- package/dist/mcp/tool-kit.js +74 -0
- package/dist/mcp/tool-kit.js.map +1 -0
- package/dist/members/removal.d.ts +95 -0
- package/dist/members/removal.d.ts.map +1 -0
- package/dist/members/removal.js +143 -0
- package/dist/members/removal.js.map +1 -0
- package/dist/middleware/auth-body.d.ts +25 -0
- package/dist/middleware/auth-body.d.ts.map +1 -0
- package/dist/middleware/auth-body.js +55 -0
- package/dist/middleware/auth-body.js.map +1 -0
- package/dist/middleware/auth-context.d.ts +170 -0
- package/dist/middleware/auth-context.d.ts.map +1 -0
- package/dist/middleware/auth-context.js +251 -0
- package/dist/middleware/auth-context.js.map +1 -0
- package/dist/middleware/body-cap.d.ts +17 -0
- package/dist/middleware/body-cap.d.ts.map +1 -0
- package/dist/middleware/body-cap.js +31 -0
- package/dist/middleware/body-cap.js.map +1 -0
- package/dist/middleware/body-refusal.d.ts +20 -0
- package/dist/middleware/body-refusal.d.ts.map +1 -0
- package/dist/middleware/body-refusal.js +5 -0
- package/dist/middleware/body-refusal.js.map +1 -0
- package/dist/middleware/cross-site.d.ts +61 -0
- package/dist/middleware/cross-site.d.ts.map +1 -0
- package/dist/middleware/cross-site.js +59 -0
- package/dist/middleware/cross-site.js.map +1 -0
- package/dist/middleware/hub-managed.d.ts +45 -0
- package/dist/middleware/hub-managed.d.ts.map +1 -0
- package/dist/middleware/hub-managed.js +56 -0
- package/dist/middleware/hub-managed.js.map +1 -0
- package/dist/middleware/idempotency.d.ts +14 -0
- package/dist/middleware/idempotency.d.ts.map +1 -0
- package/dist/middleware/idempotency.js +222 -0
- package/dist/middleware/idempotency.js.map +1 -0
- package/dist/middleware/index.d.ts +12 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +12 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/json-depth.d.ts +32 -0
- package/dist/middleware/json-depth.d.ts.map +1 -0
- package/dist/middleware/json-depth.js +80 -0
- package/dist/middleware/json-depth.js.map +1 -0
- package/dist/middleware/no-store.d.ts +21 -0
- package/dist/middleware/no-store.d.ts.map +1 -0
- package/dist/middleware/no-store.js +32 -0
- package/dist/middleware/no-store.js.map +1 -0
- package/dist/middleware/oauth-public.d.ts +16 -0
- package/dist/middleware/oauth-public.d.ts.map +1 -0
- package/dist/middleware/oauth-public.js +50 -0
- package/dist/middleware/oauth-public.js.map +1 -0
- package/dist/middleware/rate-limit.d.ts +182 -0
- package/dist/middleware/rate-limit.d.ts.map +1 -0
- package/dist/middleware/rate-limit.js +277 -0
- package/dist/middleware/rate-limit.js.map +1 -0
- package/dist/middleware/request-id.d.ts +10 -0
- package/dist/middleware/request-id.d.ts.map +1 -0
- package/dist/middleware/request-id.js +42 -0
- package/dist/middleware/request-id.js.map +1 -0
- package/dist/middleware/scopes.d.ts +33 -0
- package/dist/middleware/scopes.d.ts.map +1 -0
- package/dist/middleware/scopes.js +139 -0
- package/dist/middleware/scopes.js.map +1 -0
- package/dist/middleware/security-headers.d.ts +53 -0
- package/dist/middleware/security-headers.d.ts.map +1 -0
- package/dist/middleware/security-headers.js +96 -0
- package/dist/middleware/security-headers.js.map +1 -0
- package/dist/observability/index.d.ts +3 -0
- package/dist/observability/index.d.ts.map +1 -0
- package/dist/observability/index.js +3 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability/metrics.d.ts +17 -0
- package/dist/observability/metrics.d.ts.map +1 -0
- package/dist/observability/metrics.js +82 -0
- package/dist/observability/metrics.js.map +1 -0
- package/dist/observability/otel.d.ts +20 -0
- package/dist/observability/otel.d.ts.map +1 -0
- package/dist/observability/otel.js +54 -0
- package/dist/observability/otel.js.map +1 -0
- package/dist/pagination.d.ts +57 -0
- package/dist/pagination.d.ts.map +1 -0
- package/dist/pagination.js +90 -0
- package/dist/pagination.js.map +1 -0
- package/dist/platform/edition.d.ts +57 -0
- package/dist/platform/edition.d.ts.map +1 -0
- package/dist/platform/edition.js +90 -0
- package/dist/platform/edition.js.map +1 -0
- package/dist/platform/entitlements.d.ts +26 -0
- package/dist/platform/entitlements.d.ts.map +1 -0
- package/dist/platform/entitlements.js +28 -0
- package/dist/platform/entitlements.js.map +1 -0
- package/dist/platform/events.d.ts +87 -0
- package/dist/platform/events.d.ts.map +1 -0
- package/dist/platform/events.js +32 -0
- package/dist/platform/events.js.map +1 -0
- package/dist/platform/index.d.ts +8 -0
- package/dist/platform/index.d.ts.map +1 -0
- package/dist/platform/index.js +8 -0
- package/dist/platform/index.js.map +1 -0
- package/dist/platform/local-identity.d.ts +39 -0
- package/dist/platform/local-identity.d.ts.map +1 -0
- package/dist/platform/local-identity.js +87 -0
- package/dist/platform/local-identity.js.map +1 -0
- package/dist/platform/registry.d.ts +23 -0
- package/dist/platform/registry.d.ts.map +1 -0
- package/dist/platform/registry.js +5 -0
- package/dist/platform/registry.js.map +1 -0
- package/dist/platform/usage.d.ts +11 -0
- package/dist/platform/usage.d.ts.map +1 -0
- package/dist/platform/usage.js +12 -0
- package/dist/platform/usage.js.map +1 -0
- package/dist/platform/workspaces.d.ts +25 -0
- package/dist/platform/workspaces.d.ts.map +1 -0
- package/dist/platform/workspaces.js +44 -0
- package/dist/platform/workspaces.js.map +1 -0
- package/dist/projects/access.d.ts +40 -0
- package/dist/projects/access.d.ts.map +1 -0
- package/dist/projects/access.js +133 -0
- package/dist/projects/access.js.map +1 -0
- package/dist/projects/index.d.ts +2 -0
- package/dist/projects/index.d.ts.map +1 -0
- package/dist/projects/index.js +2 -0
- package/dist/projects/index.js.map +1 -0
- package/dist/route-label.d.ts +23 -0
- package/dist/route-label.d.ts.map +1 -0
- package/dist/route-label.js +29 -0
- package/dist/route-label.js.map +1 -0
- package/dist/routes/health.d.ts +30 -0
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/health.js +54 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/index.d.ts +3 -0
- package/dist/routes/index.d.ts.map +1 -0
- package/dist/routes/index.js +3 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/wellknown.d.ts +11 -0
- package/dist/routes/wellknown.d.ts.map +1 -0
- package/dist/routes/wellknown.js +45 -0
- package/dist/routes/wellknown.js.map +1 -0
- package/dist/secret.d.ts +30 -0
- package/dist/secret.d.ts.map +1 -0
- package/dist/secret.js +97 -0
- package/dist/secret.js.map +1 -0
- package/dist/setup-transport.d.ts +23 -0
- package/dist/setup-transport.d.ts.map +1 -0
- package/dist/setup-transport.js +52 -0
- package/dist/setup-transport.js.map +1 -0
- package/dist/state.d.ts +21 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +4 -0
- package/dist/state.js.map +1 -0
- package/dist/storage/driver.d.ts +38 -0
- package/dist/storage/driver.d.ts.map +1 -0
- package/dist/storage/driver.js +14 -0
- package/dist/storage/driver.js.map +1 -0
- package/dist/storage/factory.d.ts +4 -0
- package/dist/storage/factory.d.ts.map +1 -0
- package/dist/storage/factory.js +20 -0
- package/dist/storage/factory.js.map +1 -0
- package/dist/storage/index.d.ts +4 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +4 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/local.d.ts +21 -0
- package/dist/storage/local.d.ts.map +1 -0
- package/dist/storage/local.js +88 -0
- package/dist/storage/local.js.map +1 -0
- package/dist/storage/s3.d.ts +29 -0
- package/dist/storage/s3.d.ts.map +1 -0
- package/dist/storage/s3.js +82 -0
- package/dist/storage/s3.js.map +1 -0
- package/dist/testing/fake-hub.d.ts +425 -0
- package/dist/testing/fake-hub.d.ts.map +1 -0
- package/dist/testing/fake-hub.js +1074 -0
- package/dist/testing/fake-hub.js.map +1 -0
- package/dist/testing/helpers.d.ts +124 -0
- package/dist/testing/helpers.d.ts.map +1 -0
- package/dist/testing/helpers.js +174 -0
- package/dist/testing/helpers.js.map +1 -0
- package/dist/testing/index.d.ts +12 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +12 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/setup-origins.d.ts +21 -0
- package/dist/testing/setup-origins.d.ts.map +1 -0
- package/dist/testing/setup-origins.js +57 -0
- package/dist/testing/setup-origins.js.map +1 -0
- package/dist/testing/sso-helpers.d.ts +43 -0
- package/dist/testing/sso-helpers.d.ts.map +1 -0
- package/dist/testing/sso-helpers.js +159 -0
- package/dist/testing/sso-helpers.js.map +1 -0
- package/dist/tool-definition.d.ts +382 -0
- package/dist/tool-definition.d.ts.map +1 -0
- package/dist/tool-definition.js +46 -0
- package/dist/tool-definition.js.map +1 -0
- package/dist/util/index.d.ts +8 -0
- package/dist/util/index.d.ts.map +1 -0
- package/dist/util/index.js +8 -0
- package/dist/util/index.js.map +1 -0
- package/package.json +161 -0
- package/src/accounts/deletion.ts +389 -0
- package/src/accounts/erasure-log.ts +140 -0
- package/src/accounts/index.ts +5 -0
- package/src/accounts/mint-refusal.ts +67 -0
- package/src/accounts/signup-duplicate.ts +21 -0
- package/src/accounts/superadmin.ts +46 -0
- package/src/api/apikeys.ts +147 -0
- package/src/api/audit.ts +83 -0
- package/src/api/break-glass.ts +294 -0
- package/src/api/cli-auth.ts +308 -0
- package/src/api/create-api.ts +1403 -0
- package/src/api/demo-passes.ts +207 -0
- package/src/api/errors.ts +13 -0
- package/src/api/export.ts +482 -0
- package/src/api/files.ts +200 -0
- package/src/api/index.ts +29 -0
- package/src/api/invitations.ts +396 -0
- package/src/api/members.ts +617 -0
- package/src/api/onboarding.ts +50 -0
- package/src/api/openapi-doc.ts +34 -0
- package/src/api/projects.ts +788 -0
- package/src/api/scope-routes.ts +12 -0
- package/src/api/sso-connect.ts +246 -0
- package/src/api/sso-logout.ts +76 -0
- package/src/api/teams.ts +575 -0
- package/src/api/workspaces.ts +649 -0
- package/src/apikeys/index.ts +2 -0
- package/src/apikeys/peppers.ts +118 -0
- package/src/apikeys/service.ts +223 -0
- package/src/app.ts +228 -0
- package/src/audit/filters.ts +101 -0
- package/src/audit/index.ts +9 -0
- package/src/audit/service.ts +154 -0
- package/src/boot.ts +953 -0
- package/src/constant-time.ts +9 -0
- package/src/email/driver.ts +139 -0
- package/src/email/index.ts +26 -0
- package/src/email/shell.ts +201 -0
- package/src/email/templates.ts +184 -0
- package/src/entitlements/check.ts +435 -0
- package/src/entitlements/clock-skew.ts +58 -0
- package/src/entitlements/gate.ts +636 -0
- package/src/entitlements/hub-machine-token.ts +205 -0
- package/src/entitlements/hub-subject.ts +77 -0
- package/src/entitlements/index.ts +37 -0
- package/src/entitlements/poster.ts +265 -0
- package/src/entitlements/profiles.ts +303 -0
- package/src/entitlements/slot.ts +105 -0
- package/src/env.ts +456 -0
- package/src/files/http.ts +77 -0
- package/src/files/index.ts +4 -0
- package/src/files/serve.ts +115 -0
- package/src/files/service.ts +209 -0
- package/src/files/spool.ts +58 -0
- package/src/identity/better-auth.ts +1103 -0
- package/src/identity/demo-pass-plugin.ts +121 -0
- package/src/identity/demo-pass-rules.ts +229 -0
- package/src/identity/demo-pass.ts +390 -0
- package/src/identity/hub-grant.ts +704 -0
- package/src/identity/hub-jwt.ts +78 -0
- package/src/identity/hub-live-gate.ts +167 -0
- package/src/identity/hub-logout.ts +174 -0
- package/src/identity/hub-projection.ts +251 -0
- package/src/identity/hub-reconcile.ts +572 -0
- package/src/identity/hub-sso.ts +696 -0
- package/src/identity/hub-user-client.ts +554 -0
- package/src/identity/index.ts +56 -0
- package/src/identity/oauth-jwt.ts +128 -0
- package/src/identity/resolve-membership.ts +104 -0
- package/src/identity/signing-key.ts +164 -0
- package/src/index.ts +65 -0
- package/src/invitations/index.ts +1 -0
- package/src/invitations/service.ts +171 -0
- package/src/jobs/index.ts +25 -0
- package/src/jobs/pgboss.ts +594 -0
- package/src/jobs/timers.ts +287 -0
- package/src/logger.ts +67 -0
- package/src/mcp/errors.ts +111 -0
- package/src/mcp/http.ts +129 -0
- package/src/mcp/index.ts +28 -0
- package/src/mcp/inputs.ts +33 -0
- package/src/mcp/projects.ts +480 -0
- package/src/mcp/server.ts +174 -0
- package/src/mcp/teams.ts +97 -0
- package/src/mcp/tool-kit.ts +148 -0
- package/src/members/removal.ts +216 -0
- package/src/middleware/auth-body.ts +58 -0
- package/src/middleware/auth-context.ts +384 -0
- package/src/middleware/body-cap.ts +33 -0
- package/src/middleware/body-refusal.ts +23 -0
- package/src/middleware/cross-site.ts +122 -0
- package/src/middleware/hub-managed.ts +61 -0
- package/src/middleware/idempotency.ts +264 -0
- package/src/middleware/index.ts +33 -0
- package/src/middleware/json-depth.ts +74 -0
- package/src/middleware/no-store.ts +30 -0
- package/src/middleware/oauth-public.ts +54 -0
- package/src/middleware/rate-limit.ts +432 -0
- package/src/middleware/request-id.ts +54 -0
- package/src/middleware/scopes.ts +150 -0
- package/src/middleware/security-headers.ts +129 -0
- package/src/observability/index.ts +2 -0
- package/src/observability/metrics.ts +102 -0
- package/src/observability/otel.ts +79 -0
- package/src/pagination.ts +118 -0
- package/src/platform/edition.ts +140 -0
- package/src/platform/entitlements.ts +47 -0
- package/src/platform/events.ts +71 -0
- package/src/platform/index.ts +7 -0
- package/src/platform/local-identity.ts +90 -0
- package/src/platform/registry.ts +28 -0
- package/src/platform/usage.ts +13 -0
- package/src/platform/workspaces.ts +41 -0
- package/src/projects/access.ts +160 -0
- package/src/projects/index.ts +7 -0
- package/src/route-label.ts +29 -0
- package/src/routes/health.ts +79 -0
- package/src/routes/index.ts +2 -0
- package/src/routes/wellknown.ts +67 -0
- package/src/secret.ts +115 -0
- package/src/setup-transport.ts +46 -0
- package/src/state.ts +23 -0
- package/src/storage/driver.ts +48 -0
- package/src/storage/factory.ts +34 -0
- package/src/storage/index.ts +3 -0
- package/src/storage/local.ts +93 -0
- package/src/storage/s3.ts +109 -0
- package/src/testing/fake-hub.ts +1316 -0
- package/src/testing/helpers.ts +254 -0
- package/src/testing/index.ts +11 -0
- package/src/testing/setup-origins.ts +58 -0
- package/src/testing/sso-helpers.ts +178 -0
- package/src/tool-definition.ts +463 -0
- package/src/util/index.ts +17 -0
- package/test/fixtures/onboarding-backfill.sql +13 -0
- package/test/fixtures/operator-user-id-backfill.sql +18 -0
- package/test/host/dr-lib.sh +253 -0
- package/test/host/drizzle/0000_chassis_baseline.sql +438 -0
- package/test/host/drizzle/0001_last_owner_guard.sql +42 -0
- package/test/host/drizzle/meta/0000_snapshot.json +3566 -0
- package/test/host/drizzle/meta/_journal.json +20 -0
- package/test/host/drizzle.config.ts +17 -0
- package/test/host/identity.ts +41 -0
- package/test/host/minimal-host.ts +35 -0
- package/test/host/minimal-tool.ts +123 -0
- package/test/integration/account-oracle-uniformity.test.ts +284 -0
- package/test/integration/account-recovery.test.ts +236 -0
- package/test/integration/account.test.ts +120 -0
- package/test/integration/api-quota.test.ts +208 -0
- package/test/integration/apikey-expiry.test.ts +178 -0
- package/test/integration/apikey-pepper-rotation.test.ts +190 -0
- package/test/integration/boot-posture.test.ts +169 -0
- package/test/integration/break-glass.test.ts +484 -0
- package/test/integration/cloud-lifecycle.test.ts +233 -0
- package/test/integration/credential-boundaries.test.ts +195 -0
- package/test/integration/default-workspace.test.ts +414 -0
- package/test/integration/demo-pass-rules.test.ts +332 -0
- package/test/integration/demo-pass.test.ts +1222 -0
- package/test/integration/dr-verify-real-dump.test.ts +154 -0
- package/test/integration/edition.test.ts +868 -0
- package/test/integration/email-change.test.ts +394 -0
- package/test/integration/empty-tool.test.ts +267 -0
- package/test/integration/entitlements-null-hook.test.ts +187 -0
- package/test/integration/entitlements.test.ts +693 -0
- package/test/integration/export-entries.test.ts +321 -0
- package/test/integration/files.test.ts +320 -0
- package/test/integration/first-boot-claim.test.ts +131 -0
- package/test/integration/gdpr.test.ts +532 -0
- package/test/integration/helpers.ts +29 -0
- package/test/integration/hub-grant.test.ts +505 -0
- package/test/integration/hub-live-gate.test.ts +612 -0
- package/test/integration/hub-managed-membership.test.ts +559 -0
- package/test/integration/hub-org-selector.test.ts +281 -0
- package/test/integration/hub-reconcile.test.ts +646 -0
- package/test/integration/hub-sso.test.ts +704 -0
- package/test/integration/hub-teams.test.ts +771 -0
- package/test/integration/idempotency.test.ts +261 -0
- package/test/integration/invite-verification.test.ts +210 -0
- package/test/integration/last-owner-race.test.ts +263 -0
- package/test/integration/m9-fixes.test.ts +290 -0
- package/test/integration/mcp-projects.test.ts +463 -0
- package/test/integration/member-removal-race.test.ts +466 -0
- package/test/integration/member-removal.test.ts +794 -0
- package/test/integration/oauth-mcp.test.ts +477 -0
- package/test/integration/oauth-workspace.test.ts +371 -0
- package/test/integration/observability.test.ts +183 -0
- package/test/integration/onboarding.test.ts +268 -0
- package/test/integration/orphan-purge.test.ts +451 -0
- package/test/integration/pagination.test.ts +344 -0
- package/test/integration/platform-core.test.ts +305 -0
- package/test/integration/platform.test.ts +246 -0
- package/test/integration/pool-survives-restart.test.ts +80 -0
- package/test/integration/projects-cloud.test.ts +193 -0
- package/test/integration/projects.test.ts +1310 -0
- package/test/integration/security-fixes.test.ts +190 -0
- package/test/integration/setup-transport.test.ts +118 -0
- package/test/integration/signin-wall.test.ts +103 -0
- package/test/integration/signing-key-rotation.test.ts +349 -0
- package/test/integration/sso-helpers.ts +15 -0
- package/test/integration/sso-logout.test.ts +283 -0
- package/test/integration/teams-cloud.test.ts +194 -0
- package/test/integration/teams.test.ts +551 -0
- package/test/integration/timers.test.ts +377 -0
- package/test/integration/two-factor.test.ts +301 -0
- package/test/integration/usage-window.test.ts +161 -0
- package/test/integration/workspace-create-cloud.test.ts +523 -0
- package/test/integration/workspace-create.test.ts +510 -0
- package/test/integration/workspace-scoping.test.ts +530 -0
- package/test/integration/zz-adv-replay-owner.test.ts +205 -0
- package/test/unit/apikey-peppers.test.ts +110 -0
- package/test/unit/apikey-prefix.test.ts +34 -0
- package/test/unit/audit-filters.test.ts +135 -0
- package/test/unit/auth-origins.test.ts +92 -0
- package/test/unit/body-cap.test.ts +84 -0
- package/test/unit/clock-skew.test.ts +68 -0
- package/test/unit/cross-site.test.ts +166 -0
- package/test/unit/declared-credits.test.ts +13 -0
- package/test/unit/demo-pass-rules.test.ts +192 -0
- package/test/unit/email-driver.test.ts +127 -0
- package/test/unit/end-pool.test.ts +181 -0
- package/test/unit/entitlement-check.test.ts +492 -0
- package/test/unit/entitlement-gate.test.ts +1333 -0
- package/test/unit/entitlement-profiles.test.ts +289 -0
- package/test/unit/erasure-log.test.ts +72 -0
- package/test/unit/error-mapping.test.ts +89 -0
- package/test/unit/export-entries.test.ts +85 -0
- package/test/unit/export-sanitize.test.ts +27 -0
- package/test/unit/files-head-route.test.ts +155 -0
- package/test/unit/files-http.test.ts +97 -0
- package/test/unit/head-rewrite.test.ts +72 -0
- package/test/unit/hub-create-org.test.ts +181 -0
- package/test/unit/hub-jwt.test.ts +93 -0
- package/test/unit/hub-sso-params.test.ts +39 -0
- package/test/unit/hub-subject.test.ts +83 -0
- package/test/unit/idempotency-crypto.test.ts +45 -0
- package/test/unit/json-depth.test.ts +88 -0
- package/test/unit/logger-redaction.test.ts +66 -0
- package/test/unit/mcp-errors.test.ts +110 -0
- package/test/unit/mcp-inputs.test.ts +106 -0
- package/test/unit/mcp-project-hints.test.ts +26 -0
- package/test/unit/no-store.test.ts +49 -0
- package/test/unit/no-tool-migration-filename.test.ts +70 -0
- package/test/unit/observability.test.ts +64 -0
- package/test/unit/openapi-doc.test.ts +93 -0
- package/test/unit/rate-limit-consume-on.test.ts +76 -0
- package/test/unit/rate-limit.test.ts +39 -0
- package/test/unit/readyz-storage.test.ts +157 -0
- package/test/unit/request-quota.test.ts +146 -0
- package/test/unit/scopes.test.ts +66 -0
- package/test/unit/security-headers.test.ts +131 -0
- package/test/unit/serve-content-type.test.ts +74 -0
- package/test/unit/setup-transport.test.ts +29 -0
- package/test/unit/storage-local.test.ts +53 -0
- package/test/unit/superadmin.test.ts +32 -0
- package/test/unit/timers.test.ts +287 -0
- package/test/unit/tool-definition-guards.test.ts +166 -0
- package/test/unit/tool-entitlements-guard.test.ts +168 -0
- package/test/unit/usage-poster.test.ts +542 -0
- package/test/unit/usage-window.test.ts +48 -0
- package/test/unit/workspace-create-audit.test.ts +48 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { timingSafeEqual } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
/** Length-safe constant-time string comparison for token checks. */
|
|
4
|
+
export function constantTimeEquals(a: string, b: string): boolean {
|
|
5
|
+
const bufA = Buffer.from(a);
|
|
6
|
+
const bufB = Buffer.from(b);
|
|
7
|
+
if (bufA.length !== bufB.length) return false;
|
|
8
|
+
return timingSafeEqual(bufA, bufB);
|
|
9
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { Logger } from '../logger.js';
|
|
2
|
+
import type { Env } from '../env.js';
|
|
3
|
+
|
|
4
|
+
export interface EmailMessage {
|
|
5
|
+
to: string;
|
|
6
|
+
subject: string;
|
|
7
|
+
html: string;
|
|
8
|
+
text?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface EmailDriver {
|
|
12
|
+
readonly name: 'none' | 'smtp' | 'resend' | 'brevo';
|
|
13
|
+
/** True when this driver actually delivers mail. */
|
|
14
|
+
readonly delivers: boolean;
|
|
15
|
+
send(message: EmailMessage): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Default driver: no SMTP required, ever. Flows that would email (invitations,
|
|
20
|
+
* OTP) must always offer a copyable-link alternative; this driver just logs.
|
|
21
|
+
*/
|
|
22
|
+
class NoneDriver implements EmailDriver {
|
|
23
|
+
readonly name = 'none' as const;
|
|
24
|
+
readonly delivers = false;
|
|
25
|
+
constructor(private readonly logger: Logger) {}
|
|
26
|
+
async send(message: EmailMessage): Promise<void> {
|
|
27
|
+
this.logger.info({ to: message.to, subject: message.subject }, 'email driver=none: not sending');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class SmtpDriver implements EmailDriver {
|
|
32
|
+
readonly name = 'smtp' as const;
|
|
33
|
+
readonly delivers = true;
|
|
34
|
+
// Loaded lazily so the none/resend paths never import nodemailer.
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
|
36
|
+
private transporterPromise: Promise<import('nodemailer').Transporter> | null = null;
|
|
37
|
+
constructor(
|
|
38
|
+
private readonly url: string,
|
|
39
|
+
private readonly from: string
|
|
40
|
+
) {}
|
|
41
|
+
private transporter() {
|
|
42
|
+
this.transporterPromise ??= import('nodemailer').then((m) => m.default.createTransport(this.url));
|
|
43
|
+
return this.transporterPromise;
|
|
44
|
+
}
|
|
45
|
+
async send(message: EmailMessage): Promise<void> {
|
|
46
|
+
const t = await this.transporter();
|
|
47
|
+
await t.sendMail({ from: this.from, ...message });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
class ResendDriver implements EmailDriver {
|
|
52
|
+
readonly name = 'resend' as const;
|
|
53
|
+
readonly delivers = true;
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
|
55
|
+
private clientPromise: Promise<import('resend').Resend> | null = null;
|
|
56
|
+
constructor(
|
|
57
|
+
private readonly apiKey: string,
|
|
58
|
+
private readonly from: string
|
|
59
|
+
) {}
|
|
60
|
+
private client() {
|
|
61
|
+
this.clientPromise ??= import('resend').then((m) => new m.Resend(this.apiKey));
|
|
62
|
+
return this.clientPromise;
|
|
63
|
+
}
|
|
64
|
+
async send(message: EmailMessage): Promise<void> {
|
|
65
|
+
const client = await this.client();
|
|
66
|
+
const { error } = await client.emails.send({ from: this.from, ...message });
|
|
67
|
+
if (error) throw new Error(`resend: ${error.message}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Brevo over its REST API, no SDK: the endpoint takes one JSON body and answers
|
|
73
|
+
* 2xx or an error, and a dependency buys nothing on top of that. Brevo is the
|
|
74
|
+
* house provider across the Antasphere templates (PRDCT-2431); `resend` and
|
|
75
|
+
* `smtp` stay supported for the instances already on them.
|
|
76
|
+
*/
|
|
77
|
+
class BrevoDriver implements EmailDriver {
|
|
78
|
+
readonly name = 'brevo' as const;
|
|
79
|
+
readonly delivers = true;
|
|
80
|
+
constructor(
|
|
81
|
+
private readonly apiKey: string,
|
|
82
|
+
private readonly from: string
|
|
83
|
+
) {}
|
|
84
|
+
async send(message: EmailMessage): Promise<void> {
|
|
85
|
+
const res = await fetch('https://api.brevo.com/v3/smtp/email', {
|
|
86
|
+
method: 'POST',
|
|
87
|
+
headers: {
|
|
88
|
+
'api-key': this.apiKey,
|
|
89
|
+
'content-type': 'application/json',
|
|
90
|
+
accept: 'application/json'
|
|
91
|
+
},
|
|
92
|
+
body: JSON.stringify({
|
|
93
|
+
sender: parseSender(this.from),
|
|
94
|
+
to: [{ email: message.to }],
|
|
95
|
+
subject: message.subject,
|
|
96
|
+
htmlContent: message.html,
|
|
97
|
+
...(message.text ? { textContent: message.text } : {})
|
|
98
|
+
})
|
|
99
|
+
});
|
|
100
|
+
if (res.ok) return;
|
|
101
|
+
// The body names the reason (bad key, unverified sender, malformed
|
|
102
|
+
// recipient) and may echo the request, so it never reaches the thrown
|
|
103
|
+
// message a caller might relay onward.
|
|
104
|
+
if (res.status === 401) throw new Error('brevo: authentication failed');
|
|
105
|
+
if (res.status === 429) throw new Error('brevo: rate limited');
|
|
106
|
+
if (res.status >= 500) throw new Error('brevo: temporarily unavailable');
|
|
107
|
+
throw new Error(`brevo: refused the message (${res.status})`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** `Name <addr>` → Brevo's split `{ name, email }`; a bare address passes through. */
|
|
112
|
+
function parseSender(from: string): { name?: string; email: string } {
|
|
113
|
+
const match = /^\s*(.*?)\s*<\s*([^>]+)\s*>\s*$/.exec(from);
|
|
114
|
+
if (!match) return { email: from.trim() };
|
|
115
|
+
const [, name, email] = match;
|
|
116
|
+
return name ? { name, email: email!.trim() } : { email: email!.trim() };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function createEmailDriver(
|
|
120
|
+
env: Pick<Env, 'EMAIL_DRIVER' | 'SMTP_URL' | 'RESEND_API_KEY' | 'BREVO_API_KEY' | 'EMAIL_FROM'>,
|
|
121
|
+
logger: Logger
|
|
122
|
+
): EmailDriver {
|
|
123
|
+
switch (env.EMAIL_DRIVER) {
|
|
124
|
+
case 'smtp':
|
|
125
|
+
if (!env.SMTP_URL) throw new Error('EMAIL_DRIVER=smtp requires SMTP_URL');
|
|
126
|
+
if (!env.EMAIL_FROM) throw new Error('EMAIL_DRIVER=smtp requires EMAIL_FROM');
|
|
127
|
+
return new SmtpDriver(env.SMTP_URL, env.EMAIL_FROM);
|
|
128
|
+
case 'resend':
|
|
129
|
+
if (!env.RESEND_API_KEY) throw new Error('EMAIL_DRIVER=resend requires RESEND_API_KEY');
|
|
130
|
+
if (!env.EMAIL_FROM) throw new Error('EMAIL_DRIVER=resend requires EMAIL_FROM');
|
|
131
|
+
return new ResendDriver(env.RESEND_API_KEY, env.EMAIL_FROM);
|
|
132
|
+
case 'brevo':
|
|
133
|
+
if (!env.BREVO_API_KEY) throw new Error('EMAIL_DRIVER=brevo requires BREVO_API_KEY');
|
|
134
|
+
if (!env.EMAIL_FROM) throw new Error('EMAIL_DRIVER=brevo requires EMAIL_FROM');
|
|
135
|
+
return new BrevoDriver(env.BREVO_API_KEY, env.EMAIL_FROM);
|
|
136
|
+
default:
|
|
137
|
+
return new NoneDriver(logger);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export { type EmailDriver, type EmailMessage, createEmailDriver } from './driver.js';
|
|
2
|
+
export {
|
|
3
|
+
type EmailAssets,
|
|
4
|
+
type MailBrand,
|
|
5
|
+
type ShellParts,
|
|
6
|
+
button,
|
|
7
|
+
codePlate,
|
|
8
|
+
emailAssetsAt,
|
|
9
|
+
esc,
|
|
10
|
+
facts,
|
|
11
|
+
fine,
|
|
12
|
+
fmtDate,
|
|
13
|
+
makeShell,
|
|
14
|
+
para,
|
|
15
|
+
quietButton,
|
|
16
|
+
quote,
|
|
17
|
+
setEmailAssets,
|
|
18
|
+
spelledLink
|
|
19
|
+
} from './shell.js';
|
|
20
|
+
export {
|
|
21
|
+
buildChangeEmailConfirmEmail,
|
|
22
|
+
buildInviteEmail,
|
|
23
|
+
buildOtpEmail,
|
|
24
|
+
buildPasswordResetEmail,
|
|
25
|
+
buildVerifyEmailEmail
|
|
26
|
+
} from './templates.js';
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The mail shell: the one layout every transactional email is set in, and the
|
|
3
|
+
* few blocks a builder composes a body from. The hub and the chassis carry this
|
|
4
|
+
* file BYTE-IDENTICAL (the twin rule, LESSONS.md): change one, copy it to the
|
|
5
|
+
* other. What differs per product is the `MailBrand` its templates.ts passes.
|
|
6
|
+
*
|
|
7
|
+
* The look is the dashboards': the creme paper, the umber accent, the serif
|
|
8
|
+
* display face over the sans body, mono field-note labels, and the grain over
|
|
9
|
+
* a gradient as a band at the top of the card.
|
|
10
|
+
*
|
|
11
|
+
* Mail clients are not browsers, which shapes three choices here:
|
|
12
|
+
* - The grain band and the mark are hosted IMAGES (`/email/band.jpg`,
|
|
13
|
+
* `/email/mark.png`, served from the dashboard's static folder). Gmail
|
|
14
|
+
* strips SVG and data URIs, so neither can travel inline. Until
|
|
15
|
+
* `setEmailAssets` is called (or where images are blocked) the band falls
|
|
16
|
+
* back to a plain CSS gradient and the header to the name alone.
|
|
17
|
+
* - Layout is tables and inline styles; the webfonts load where the client
|
|
18
|
+
* allows a stylesheet (Apple Mail, iOS) and fall back to Georgia and the
|
|
19
|
+
* system sans everywhere else.
|
|
20
|
+
* - `color-scheme: light` keeps a dark-mode client from inverting the paper.
|
|
21
|
+
*
|
|
22
|
+
* Env-free like the builders: the asset urls are handed in once at boot, so a
|
|
23
|
+
* preview script can hand in its own.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
export interface MailBrand {
|
|
27
|
+
/** The product's name, as the header and the footer say it. */
|
|
28
|
+
name: string;
|
|
29
|
+
/** One line under the footer's name: what this product is. */
|
|
30
|
+
tagline: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface EmailAssets {
|
|
34
|
+
/** The grain-over-gradient band, 1200x320. */
|
|
35
|
+
band: string;
|
|
36
|
+
/** The woven-A mark, transparent, 120x120. */
|
|
37
|
+
mark: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let assets: EmailAssets | null = null;
|
|
41
|
+
|
|
42
|
+
/** Hand the shell its two image urls. Called once at boot; the preview hands in its own. */
|
|
43
|
+
export function setEmailAssets(next: EmailAssets | null): void {
|
|
44
|
+
assets = next;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The two urls as an instance serves them, from the dashboard's static folder. */
|
|
48
|
+
export function emailAssetsAt(publicBaseUrl: string): EmailAssets {
|
|
49
|
+
const base = publicBaseUrl.replace(/\/+$/, '');
|
|
50
|
+
return { band: `${base}/email/band.jpg`, mark: `${base}/email/mark.png` };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Nothing reaches email HTML unescaped: user text, and urls in attributes alike. */
|
|
54
|
+
export const esc = (s: string): string =>
|
|
55
|
+
s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
56
|
+
|
|
57
|
+
const INK = '#1c1915';
|
|
58
|
+
const INK_SOFT = '#35302a';
|
|
59
|
+
const MUTED = '#6e6759';
|
|
60
|
+
const HAIRLINE = '#e0daca';
|
|
61
|
+
const GROUND = '#f1ede1';
|
|
62
|
+
const SHEET = '#fbf9f3';
|
|
63
|
+
const PLATE = '#f4f0e5';
|
|
64
|
+
const ACCENT = '#7a6652';
|
|
65
|
+
const ACCENT_INK = '#f7f4ec';
|
|
66
|
+
|
|
67
|
+
const DISPLAY = "'Sentient',Georgia,'Times New Roman',serif";
|
|
68
|
+
const UI = "'Synonym',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif";
|
|
69
|
+
const MONO = "ui-monospace,'SF Mono',Menlo,Consolas,monospace";
|
|
70
|
+
|
|
71
|
+
/** A date the way a person says it: "Thursday 22 January 2026, 14:00 UTC". */
|
|
72
|
+
export function fmtDate(d: Date): string {
|
|
73
|
+
const day = new Intl.DateTimeFormat('en-GB', {
|
|
74
|
+
weekday: 'long',
|
|
75
|
+
day: 'numeric',
|
|
76
|
+
month: 'long',
|
|
77
|
+
year: 'numeric',
|
|
78
|
+
timeZone: 'UTC'
|
|
79
|
+
}).format(d);
|
|
80
|
+
const time = new Intl.DateTimeFormat('en-GB', {
|
|
81
|
+
hour: '2-digit',
|
|
82
|
+
minute: '2-digit',
|
|
83
|
+
hour12: false,
|
|
84
|
+
timeZone: 'UTC'
|
|
85
|
+
}).format(d);
|
|
86
|
+
return `${day.replace(',', '')}, ${time} UTC`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ─── Blocks ──────────────────────────────────────────────────────────
|
|
90
|
+
// Each takes HTML that is already safe: the builder escapes user text.
|
|
91
|
+
|
|
92
|
+
/** A paragraph of body copy. */
|
|
93
|
+
export const para = (html: string): string =>
|
|
94
|
+
`<p style="margin:0 0 18px;font-family:${UI};font-size:15px;line-height:1.65;color:${INK_SOFT}">${html}</p>`;
|
|
95
|
+
|
|
96
|
+
/** The one thing to do. */
|
|
97
|
+
export const button = (url: string, label: string): string =>
|
|
98
|
+
`<table role="presentation" cellpadding="0" cellspacing="0" style="margin:6px 0 26px"><tr>
|
|
99
|
+
<td style="border-radius:16px;background:${ACCENT}">
|
|
100
|
+
<a href="${esc(url)}" style="display:inline-block;padding:12px 24px;border-radius:16px;font-family:${UI};font-size:15px;font-weight:500;color:${ACCENT_INK};text-decoration:none">${label}</a>
|
|
101
|
+
</td></tr></table>`;
|
|
102
|
+
|
|
103
|
+
/** The other thing to do, when a mail offers two. */
|
|
104
|
+
export const quietButton = (url: string, label: string): string =>
|
|
105
|
+
`<table role="presentation" cellpadding="0" cellspacing="0" style="margin:6px 0 26px"><tr>
|
|
106
|
+
<td style="border-radius:16px;border:1px solid ${HAIRLINE};background:${SHEET}">
|
|
107
|
+
<a href="${esc(url)}" style="display:inline-block;padding:11px 23px;border-radius:16px;font-family:${UI};font-size:15px;font-weight:500;color:${INK};text-decoration:none">${label}</a>
|
|
108
|
+
</td></tr></table>`;
|
|
109
|
+
|
|
110
|
+
/** Someone's own words, set apart from ours. */
|
|
111
|
+
export const quote = (html: string): string =>
|
|
112
|
+
`<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin:0 0 20px"><tr>
|
|
113
|
+
<td style="border-left:2px solid ${ACCENT};padding:4px 0 4px 16px;font-family:${DISPLAY};font-size:17px;line-height:1.55;font-style:italic;color:${INK}">${html}</td>
|
|
114
|
+
</tr></table>`;
|
|
115
|
+
|
|
116
|
+
/** A plate of labelled facts: mono field-note labels over the values. */
|
|
117
|
+
export const facts = (rows: Array<[label: string, valueHtml: string]>): string =>
|
|
118
|
+
`<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin:0 0 24px;background:${PLATE};border:1px solid ${HAIRLINE};border-radius:12px"><tr><td style="padding:6px 18px">
|
|
119
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0">${rows
|
|
120
|
+
.map(
|
|
121
|
+
([label, value], i) => `<tr>
|
|
122
|
+
<td style="padding:11px 16px 11px 0;${i > 0 ? `border-top:1px solid ${HAIRLINE};` : ''}font-family:${MONO};font-size:10px;letter-spacing:0.14em;text-transform:uppercase;color:${MUTED};white-space:nowrap;vertical-align:top;width:1%">${label}</td>
|
|
123
|
+
<td style="padding:9px 0;${i > 0 ? `border-top:1px solid ${HAIRLINE};` : ''}font-family:${UI};font-size:14px;line-height:1.5;color:${INK};vertical-align:top">${value}</td>
|
|
124
|
+
</tr>`
|
|
125
|
+
)
|
|
126
|
+
.join('')}</table>
|
|
127
|
+
</td></tr></table>`;
|
|
128
|
+
|
|
129
|
+
/** A code to type, large enough to read across a desk. */
|
|
130
|
+
export const codePlate = (code: string): string =>
|
|
131
|
+
`<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin:0 0 24px;background:${PLATE};border:1px solid ${HAIRLINE};border-radius:12px"><tr>
|
|
132
|
+
<td align="center" style="padding:22px 12px;font-family:${MONO};font-size:34px;font-weight:600;letter-spacing:0.28em;color:${INK}">${esc(code)}</td>
|
|
133
|
+
</tr></table>`;
|
|
134
|
+
|
|
135
|
+
/** The small print under the action. */
|
|
136
|
+
export const fine = (html: string): string =>
|
|
137
|
+
`<p style="margin:0 0 10px;font-family:${UI};font-size:13px;line-height:1.6;color:${MUTED}">${html}</p>`;
|
|
138
|
+
|
|
139
|
+
/** The link spelled out, for when the button does not work. */
|
|
140
|
+
export const spelledLink = (url: string): string =>
|
|
141
|
+
`<p style="margin:0;font-family:${UI};font-size:12px;line-height:1.6;color:${MUTED}">Button not working? Paste this into your browser:<br>
|
|
142
|
+
<span style="font-family:${MONO};font-size:11px;word-break:break-all;color:${MUTED}">${esc(url)}</span></p>`;
|
|
143
|
+
|
|
144
|
+
// ─── The shell ───────────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
export interface ShellParts {
|
|
147
|
+
/** The inbox preview line, hidden in the body. */
|
|
148
|
+
preheader: string;
|
|
149
|
+
/** The mono label over the title: what kind of mail this is. */
|
|
150
|
+
eyebrow: string;
|
|
151
|
+
/** The title, already escaped. */
|
|
152
|
+
title: string;
|
|
153
|
+
/** The blocks. */
|
|
154
|
+
body: string;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function makeShell(brand: MailBrand): (parts: ShellParts) => string {
|
|
158
|
+
return (parts) => {
|
|
159
|
+
const band = assets
|
|
160
|
+
? `background-color:#e6dccb;background-image:url('${esc(assets.band)}');background-size:cover;background-position:center`
|
|
161
|
+
: 'background-color:#e6dccb;background-image:linear-gradient(115deg,#f6f0e4 0%,#ece4d6 45%,#d9c6ac 80%,#c9b193 100%)';
|
|
162
|
+
const mark = assets
|
|
163
|
+
? `<td style="padding-right:11px;vertical-align:middle"><img src="${esc(assets.mark)}" width="34" height="34" alt="" style="display:block;border:0"></td>`
|
|
164
|
+
: '';
|
|
165
|
+
|
|
166
|
+
return `<!doctype html>
|
|
167
|
+
<html lang="en">
|
|
168
|
+
<head>
|
|
169
|
+
<meta charset="utf-8">
|
|
170
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
171
|
+
<meta name="color-scheme" content="light">
|
|
172
|
+
<meta name="supported-color-schemes" content="light">
|
|
173
|
+
<title>${esc(brand.name)}</title>
|
|
174
|
+
<link rel="stylesheet" href="https://api.fontshare.com/v2/css?f%5B%5D=sentient@300,400&f%5B%5D=synonym@400,500&display=swap">
|
|
175
|
+
</head>
|
|
176
|
+
<body style="margin:0;padding:0;background:${GROUND}">
|
|
177
|
+
<div style="display:none;max-height:0;overflow:hidden;opacity:0;color:${GROUND};font-size:1px;line-height:1px">${esc(parts.preheader)}</div>
|
|
178
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:${GROUND}">
|
|
179
|
+
<tr><td align="center" style="padding:36px 16px 40px">
|
|
180
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="max-width:560px;background:${SHEET};border:1px solid ${HAIRLINE};border-radius:18px;overflow:hidden">
|
|
181
|
+
<tr><td height="124" style="height:124px;padding:0 36px 22px;vertical-align:bottom;border-bottom:1px solid ${HAIRLINE};${band}">
|
|
182
|
+
<table role="presentation" cellpadding="0" cellspacing="0"><tr>
|
|
183
|
+
${mark}
|
|
184
|
+
<td style="vertical-align:middle;font-family:${DISPLAY};font-size:23px;font-weight:300;letter-spacing:-0.01em;color:${INK}">${esc(brand.name)}</td>
|
|
185
|
+
</tr></table>
|
|
186
|
+
</td></tr>
|
|
187
|
+
<tr><td style="padding:34px 36px 30px">
|
|
188
|
+
<p style="margin:0 0 12px;font-family:${MONO};font-size:10px;letter-spacing:0.16em;text-transform:uppercase;color:${ACCENT}">${esc(parts.eyebrow)}</p>
|
|
189
|
+
<h1 style="margin:0 0 20px;font-family:${DISPLAY};font-size:29px;line-height:1.2;font-weight:300;letter-spacing:-0.015em;color:${INK}">${parts.title}</h1>
|
|
190
|
+
${parts.body}
|
|
191
|
+
</td></tr>
|
|
192
|
+
<tr><td style="padding:20px 36px 24px;border-top:1px solid ${HAIRLINE};background:${PLATE}">
|
|
193
|
+
<p style="margin:0 0 3px;font-family:${DISPLAY};font-size:15px;font-weight:400;color:${INK}">${esc(brand.name)}</p>
|
|
194
|
+
<p style="margin:0;font-family:${UI};font-size:12px;line-height:1.55;color:${MUTED}">${esc(brand.tagline)}</p>
|
|
195
|
+
</td></tr>
|
|
196
|
+
</table>
|
|
197
|
+
</td></tr>
|
|
198
|
+
</table>
|
|
199
|
+
</body></html>`;
|
|
200
|
+
};
|
|
201
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The account mails every tool sends: the workspace invitation, the password
|
|
3
|
+
* reset, the two legs of an email change and the sign-in code. Env-free by
|
|
4
|
+
* design (ported convention from a predecessor template): every env-dependent
|
|
5
|
+
* value (urls, names, dates) arrives as a parameter, so a preview script can
|
|
6
|
+
* render these standalone (`pnpm preview:emails` in a tool's server app).
|
|
7
|
+
*
|
|
8
|
+
* The layout and the blocks live in shell.ts; this file holds what the mails
|
|
9
|
+
* SAY. The voice is the product's, not a log's: say what happened, who did it,
|
|
10
|
+
* and the one thing to do next, in the words a person would use.
|
|
11
|
+
*
|
|
12
|
+
* The chassis spells no product. The name and the footer line arrive as the
|
|
13
|
+
* `brand` (`identity.displayName` + `copy.mail.tagline`), and the two phrases
|
|
14
|
+
* of the invitation that say what the tool IS arrive from `copy.mail` too. A
|
|
15
|
+
* tool's own mails (its app's email folder) compose the same shell and blocks.
|
|
16
|
+
*/
|
|
17
|
+
import {
|
|
18
|
+
button,
|
|
19
|
+
codePlate,
|
|
20
|
+
esc,
|
|
21
|
+
fine,
|
|
22
|
+
fmtDate,
|
|
23
|
+
makeShell,
|
|
24
|
+
para,
|
|
25
|
+
spelledLink,
|
|
26
|
+
type MailBrand
|
|
27
|
+
} from './shell.js';
|
|
28
|
+
|
|
29
|
+
export interface InviteEmailParams {
|
|
30
|
+
/** The product's name and footer line (`identity.displayName`, `copy.mail.tagline`). */
|
|
31
|
+
brand: MailBrand;
|
|
32
|
+
/** What joining gives, in the tool's words (`copy.mail.invitePitch`). */
|
|
33
|
+
pitch: string;
|
|
34
|
+
/** The end of the inbox preview line, after "Join <workspace> on <name>: " (`copy.mail.invitePreheader`). */
|
|
35
|
+
preheader: string;
|
|
36
|
+
inviteeEmail: string;
|
|
37
|
+
inviterName: string;
|
|
38
|
+
workspaceName: string;
|
|
39
|
+
acceptUrl: string;
|
|
40
|
+
expiresAt: Date;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function buildInviteEmail(p: InviteEmailParams): { subject: string; html: string; text: string } {
|
|
44
|
+
const name = p.brand.name;
|
|
45
|
+
const subject = `${p.inviterName} invited you to ${p.workspaceName}`;
|
|
46
|
+
const html = makeShell(p.brand)({
|
|
47
|
+
preheader: `Join ${p.workspaceName} on ${name}: ${p.preheader}`,
|
|
48
|
+
eyebrow: 'Invitation',
|
|
49
|
+
title: `${esc(p.inviterName)} wants you in ${esc(p.workspaceName)}`,
|
|
50
|
+
body:
|
|
51
|
+
para(
|
|
52
|
+
`You have been invited to <strong>${esc(p.workspaceName)}</strong>, a workspace on ${esc(name)}.
|
|
53
|
+
${esc(p.pitch)}`
|
|
54
|
+
) +
|
|
55
|
+
button(p.acceptUrl, 'Join the workspace') +
|
|
56
|
+
fine(
|
|
57
|
+
`This invitation was sent to ${esc(p.inviteeEmail)} and stays open until ${fmtDate(p.expiresAt)}.`
|
|
58
|
+
) +
|
|
59
|
+
spelledLink(p.acceptUrl)
|
|
60
|
+
});
|
|
61
|
+
const text = `${p.inviterName} invited you to join ${p.workspaceName} on ${name}.\n\nJoin the workspace: ${p.acceptUrl}\n\nThe invitation stays open until ${fmtDate(p.expiresAt)}.`;
|
|
62
|
+
return { subject, html, text };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface PasswordResetEmailParams {
|
|
66
|
+
/** The product's name and footer line (`identity.displayName`, `copy.mail.tagline`). */
|
|
67
|
+
brand: MailBrand;
|
|
68
|
+
resetUrl: string;
|
|
69
|
+
expiresAt: Date;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function buildPasswordResetEmail(p: PasswordResetEmailParams): {
|
|
73
|
+
subject: string;
|
|
74
|
+
html: string;
|
|
75
|
+
text: string;
|
|
76
|
+
} {
|
|
77
|
+
const name = p.brand.name;
|
|
78
|
+
const subject = `Reset your ${name} password`;
|
|
79
|
+
const html = makeShell(p.brand)({
|
|
80
|
+
preheader: 'Choose a new password. If you did not ask for this, nothing changes.',
|
|
81
|
+
eyebrow: 'Your account',
|
|
82
|
+
title: 'Let’s get you a new password',
|
|
83
|
+
body:
|
|
84
|
+
para(
|
|
85
|
+
`Someone asked to reset the password of your ${esc(name)} account. If that was you, choose a new one here:`
|
|
86
|
+
) +
|
|
87
|
+
button(p.resetUrl, 'Choose a new password') +
|
|
88
|
+
fine(
|
|
89
|
+
`The link works until ${fmtDate(p.expiresAt)}. If you did not ask for this, ignore this email: your password stays as it is.`
|
|
90
|
+
) +
|
|
91
|
+
spelledLink(p.resetUrl)
|
|
92
|
+
});
|
|
93
|
+
const text = `Reset your ${name} password: ${p.resetUrl}\n\nThe link works until ${fmtDate(p.expiresAt)}. If you did not ask for this, ignore this email: your password stays as it is.`;
|
|
94
|
+
return { subject, html, text };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface ChangeEmailConfirmEmailParams {
|
|
98
|
+
/** The product's name and footer line (`identity.displayName`, `copy.mail.tagline`). */
|
|
99
|
+
brand: MailBrand;
|
|
100
|
+
newEmail: string;
|
|
101
|
+
confirmUrl: string;
|
|
102
|
+
expiresAt: Date;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** To the OLD address: a verified user must confirm the change first (two-leg flow). */
|
|
106
|
+
export function buildChangeEmailConfirmEmail(p: ChangeEmailConfirmEmailParams): {
|
|
107
|
+
subject: string;
|
|
108
|
+
html: string;
|
|
109
|
+
text: string;
|
|
110
|
+
} {
|
|
111
|
+
const name = p.brand.name;
|
|
112
|
+
const subject = `Confirm your ${name} email change`;
|
|
113
|
+
const html = makeShell(p.brand)({
|
|
114
|
+
preheader: `You asked to move your account to ${p.newEmail}. Confirm it here.`,
|
|
115
|
+
eyebrow: 'Your account',
|
|
116
|
+
title: 'Moving to a new address?',
|
|
117
|
+
body:
|
|
118
|
+
para(
|
|
119
|
+
`You asked to change the email of your ${esc(name)} account to <strong>${esc(p.newEmail)}</strong>.
|
|
120
|
+
Confirm it here, and we will send one last link to the new address to finish the move.`
|
|
121
|
+
) +
|
|
122
|
+
button(p.confirmUrl, 'Yes, change my email') +
|
|
123
|
+
fine(
|
|
124
|
+
`The link works until ${fmtDate(p.expiresAt)}. If you did not ask for this, ignore this email: your address stays as it is.`
|
|
125
|
+
) +
|
|
126
|
+
spelledLink(p.confirmUrl)
|
|
127
|
+
});
|
|
128
|
+
const text = `Confirm changing your ${name} email to ${p.newEmail}: ${p.confirmUrl}\n\nThe link works until ${fmtDate(p.expiresAt)}. If you did not ask for this, ignore this email: your address stays as it is.`;
|
|
129
|
+
return { subject, html, text };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface VerifyEmailEmailParams {
|
|
133
|
+
/** The product's name and footer line (`identity.displayName`, `copy.mail.tagline`). */
|
|
134
|
+
brand: MailBrand;
|
|
135
|
+
verifyUrl: string;
|
|
136
|
+
expiresAt: Date;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** To the NEW address: the final leg of every email change (and any plain verification). */
|
|
140
|
+
export function buildVerifyEmailEmail(p: VerifyEmailEmailParams): {
|
|
141
|
+
subject: string;
|
|
142
|
+
html: string;
|
|
143
|
+
text: string;
|
|
144
|
+
} {
|
|
145
|
+
const name = p.brand.name;
|
|
146
|
+
const subject = `Verify your ${name} email address`;
|
|
147
|
+
const html = makeShell(p.brand)({
|
|
148
|
+
preheader: 'One click to confirm this address is yours.',
|
|
149
|
+
eyebrow: 'Your account',
|
|
150
|
+
title: 'Is this address yours?',
|
|
151
|
+
body:
|
|
152
|
+
para(
|
|
153
|
+
`One click confirms that this address belongs to you, and it becomes the email of your ${esc(name)} account.`
|
|
154
|
+
) +
|
|
155
|
+
button(p.verifyUrl, 'Yes, verify this address') +
|
|
156
|
+
fine(`The link works until ${fmtDate(p.expiresAt)}. If you did not ask for this, ignore this email.`) +
|
|
157
|
+
spelledLink(p.verifyUrl)
|
|
158
|
+
});
|
|
159
|
+
const text = `Verify your ${name} email address: ${p.verifyUrl}\n\nThe link works until ${fmtDate(p.expiresAt)}. If you did not ask for this, ignore this email.`;
|
|
160
|
+
return { subject, html, text };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export interface OtpEmailParams {
|
|
164
|
+
/** The product's name and footer line (`identity.displayName`, `copy.mail.tagline`). */
|
|
165
|
+
brand: MailBrand;
|
|
166
|
+
otp: string;
|
|
167
|
+
type: string;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function buildOtpEmail(p: OtpEmailParams): { subject: string; html: string; text: string } {
|
|
171
|
+
const name = p.brand.name;
|
|
172
|
+
const subject = `${p.otp} is your ${name} code`;
|
|
173
|
+
const html = makeShell(p.brand)({
|
|
174
|
+
preheader: 'Type it in to continue. It only works once.',
|
|
175
|
+
eyebrow: 'Sign in',
|
|
176
|
+
title: 'Here is your code',
|
|
177
|
+
body:
|
|
178
|
+
para(`Type this into ${esc(name)} to continue:`) +
|
|
179
|
+
codePlate(p.otp) +
|
|
180
|
+
fine('The code works once and only for a few minutes. If you did not ask for it, ignore this email.')
|
|
181
|
+
});
|
|
182
|
+
const text = `Your ${name} code: ${p.otp}\n\nIt works once and only for a few minutes. If you did not ask for it, ignore this email.`;
|
|
183
|
+
return { subject, html, text };
|
|
184
|
+
}
|