@digimuza/saas-core 0.0.2
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/CLAUDE.md +1165 -0
- package/LICENSE +21 -0
- package/README.md +234 -0
- package/dist/api-keys/index.d.ts +9 -0
- package/dist/api-keys/index.d.ts.map +1 -0
- package/dist/api-keys/index.js +37 -0
- package/dist/api-keys/index.js.map +1 -0
- package/dist/api-keys/keys.d.ts +55 -0
- package/dist/api-keys/keys.d.ts.map +1 -0
- package/dist/api-keys/keys.js +92 -0
- package/dist/api-keys/keys.js.map +1 -0
- package/dist/api-keys/service.d.ts +111 -0
- package/dist/api-keys/service.d.ts.map +1 -0
- package/dist/api-keys/service.js +210 -0
- package/dist/api-keys/service.js.map +1 -0
- package/dist/audit/index.d.ts +5 -0
- package/dist/audit/index.d.ts.map +1 -0
- package/dist/audit/index.js +44 -0
- package/dist/audit/index.js.map +1 -0
- package/dist/audit/service.d.ts +88 -0
- package/dist/audit/service.d.ts.map +1 -0
- package/dist/audit/service.js +128 -0
- package/dist/audit/service.js.map +1 -0
- package/dist/auth/client.d.ts +5 -0
- package/dist/auth/client.d.ts.map +1 -0
- package/dist/auth/client.js +26 -0
- package/dist/auth/client.js.map +1 -0
- package/dist/auth/cookies.d.ts +51 -0
- package/dist/auth/cookies.d.ts.map +1 -0
- package/dist/auth/cookies.js +58 -0
- package/dist/auth/cookies.js.map +1 -0
- package/dist/auth/index.d.ts +14 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +46 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/service.d.ts +67 -0
- package/dist/auth/service.d.ts.map +1 -0
- package/dist/auth/service.js +275 -0
- package/dist/auth/service.js.map +1 -0
- package/dist/auth/types.d.ts +56 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +2 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/authorization/index.d.ts +4 -0
- package/dist/authorization/index.d.ts.map +1 -0
- package/dist/authorization/index.js +24 -0
- package/dist/authorization/index.js.map +1 -0
- package/dist/authorization/service.d.ts +92 -0
- package/dist/authorization/service.d.ts.map +1 -0
- package/dist/authorization/service.js +254 -0
- package/dist/authorization/service.js.map +1 -0
- package/dist/billing/index.d.ts +16 -0
- package/dist/billing/index.d.ts.map +1 -0
- package/dist/billing/index.js +47 -0
- package/dist/billing/index.js.map +1 -0
- package/dist/billing/provider.d.ts +114 -0
- package/dist/billing/provider.d.ts.map +1 -0
- package/dist/billing/provider.js +2 -0
- package/dist/billing/provider.js.map +1 -0
- package/dist/billing/revolut.d.ts +68 -0
- package/dist/billing/revolut.d.ts.map +1 -0
- package/dist/billing/revolut.js +181 -0
- package/dist/billing/revolut.js.map +1 -0
- package/dist/billing/service.d.ts +124 -0
- package/dist/billing/service.d.ts.map +1 -0
- package/dist/billing/service.js +470 -0
- package/dist/billing/service.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +43 -0
- package/dist/cli/commands/dev.d.ts.map +1 -0
- package/dist/cli/commands/dev.js +212 -0
- package/dist/cli/commands/dev.js.map +1 -0
- package/dist/cli/commands/project.d.ts +12 -0
- package/dist/cli/commands/project.d.ts.map +1 -0
- package/dist/cli/commands/project.js +49 -0
- package/dist/cli/commands/project.js.map +1 -0
- package/dist/cli/docker.d.ts +50 -0
- package/dist/cli/docker.d.ts.map +1 -0
- package/dist/cli/docker.js +84 -0
- package/dist/cli/docker.js.map +1 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +198 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/output.d.ts +30 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +66 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/templates.d.ts +52 -0
- package/dist/cli/templates.d.ts.map +1 -0
- package/dist/cli/templates.js +225 -0
- package/dist/cli/templates.js.map +1 -0
- package/dist/config/client.d.ts +60 -0
- package/dist/config/client.d.ts.map +1 -0
- package/dist/config/client.js +94 -0
- package/dist/config/client.js.map +1 -0
- package/dist/config/config-loader.d.ts +2 -0
- package/dist/config/config-loader.d.ts.map +1 -0
- package/dist/config/config-loader.js +2 -0
- package/dist/config/config-loader.js.map +1 -0
- package/dist/config/env.d.ts +24 -0
- package/dist/config/env.d.ts.map +1 -0
- package/dist/config/env.js +40 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/errors.d.ts +17 -0
- package/dist/config/errors.d.ts.map +1 -0
- package/dist/config/errors.js +26 -0
- package/dist/config/errors.js.map +1 -0
- package/dist/config/index.d.ts +72 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +106 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/runtime.d.ts +15 -0
- package/dist/config/runtime.d.ts.map +1 -0
- package/dist/config/runtime.js +22 -0
- package/dist/config/runtime.js.map +1 -0
- package/dist/config/sections.d.ts +226 -0
- package/dist/config/sections.d.ts.map +1 -0
- package/dist/config/sections.js +141 -0
- package/dist/config/sections.js.map +1 -0
- package/dist/db/client.d.ts +43 -0
- package/dist/db/client.d.ts.map +1 -0
- package/dist/db/client.js +77 -0
- package/dist/db/client.js.map +1 -0
- package/dist/db/errors.d.ts +39 -0
- package/dist/db/errors.d.ts.map +1 -0
- package/dist/db/errors.js +67 -0
- package/dist/db/errors.js.map +1 -0
- package/dist/db/index.d.ts +10 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +10 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/migrate.d.ts +21 -0
- package/dist/db/migrate.d.ts.map +1 -0
- package/dist/db/migrate.js +28 -0
- package/dist/db/migrate.js.map +1 -0
- package/dist/db/schema/_shared.d.ts +30 -0
- package/dist/db/schema/_shared.d.ts.map +1 -0
- package/dist/db/schema/_shared.js +36 -0
- package/dist/db/schema/_shared.js.map +1 -0
- package/dist/db/schema/apikeys.d.ts +263 -0
- package/dist/db/schema/apikeys.d.ts.map +1 -0
- package/dist/db/schema/apikeys.js +70 -0
- package/dist/db/schema/apikeys.js.map +1 -0
- package/dist/db/schema/authorization.d.ts +465 -0
- package/dist/db/schema/authorization.d.ts.map +1 -0
- package/dist/db/schema/authorization.js +134 -0
- package/dist/db/schema/authorization.js.map +1 -0
- package/dist/db/schema/billing.d.ts +1100 -0
- package/dist/db/schema/billing.d.ts.map +1 -0
- package/dist/db/schema/billing.js +223 -0
- package/dist/db/schema/billing.js.map +1 -0
- package/dist/db/schema/events.d.ts +161 -0
- package/dist/db/schema/events.d.ts.map +1 -0
- package/dist/db/schema/events.js +66 -0
- package/dist/db/schema/events.js.map +1 -0
- package/dist/db/schema/features.d.ts +243 -0
- package/dist/db/schema/features.d.ts.map +1 -0
- package/dist/db/schema/features.js +87 -0
- package/dist/db/schema/features.js.map +1 -0
- package/dist/db/schema/files.d.ts +253 -0
- package/dist/db/schema/files.d.ts.map +1 -0
- package/dist/db/schema/files.js +74 -0
- package/dist/db/schema/files.js.map +1 -0
- package/dist/db/schema/index.d.ts +38 -0
- package/dist/db/schema/index.d.ts.map +1 -0
- package/dist/db/schema/index.js +38 -0
- package/dist/db/schema/index.js.map +1 -0
- package/dist/db/schema/invitations.d.ts +214 -0
- package/dist/db/schema/invitations.d.ts.map +1 -0
- package/dist/db/schema/invitations.js +80 -0
- package/dist/db/schema/invitations.js.map +1 -0
- package/dist/db/schema/messaging.d.ts +442 -0
- package/dist/db/schema/messaging.d.ts.map +1 -0
- package/dist/db/schema/messaging.js +120 -0
- package/dist/db/schema/messaging.js.map +1 -0
- package/dist/db/schema/users.d.ts +246 -0
- package/dist/db/schema/users.d.ts.map +1 -0
- package/dist/db/schema/users.js +60 -0
- package/dist/db/schema/users.js.map +1 -0
- package/dist/db/schema/webhooks.d.ts +693 -0
- package/dist/db/schema/webhooks.d.ts.map +1 -0
- package/dist/db/schema/webhooks.js +147 -0
- package/dist/db/schema/webhooks.js.map +1 -0
- package/dist/db/schema/workspaces.d.ts +196 -0
- package/dist/db/schema/workspaces.d.ts.map +1 -0
- package/dist/db/schema/workspaces.js +57 -0
- package/dist/db/schema/workspaces.js.map +1 -0
- package/dist/db/types.d.ts +18 -0
- package/dist/db/types.d.ts.map +1 -0
- package/dist/db/types.js +2 -0
- package/dist/db/types.js.map +1 -0
- package/dist/email/adapters.d.ts +29 -0
- package/dist/email/adapters.d.ts.map +1 -0
- package/dist/email/adapters.js +60 -0
- package/dist/email/adapters.js.map +1 -0
- package/dist/email/index.d.ts +20 -0
- package/dist/email/index.d.ts.map +1 -0
- package/dist/email/index.js +79 -0
- package/dist/email/index.js.map +1 -0
- package/dist/email/provider.d.ts +47 -0
- package/dist/email/provider.d.ts.map +1 -0
- package/dist/email/provider.js +7 -0
- package/dist/email/provider.js.map +1 -0
- package/dist/email/resend.d.ts +22 -0
- package/dist/email/resend.d.ts.map +1 -0
- package/dist/email/resend.js +51 -0
- package/dist/email/resend.js.map +1 -0
- package/dist/email/service.d.ts +55 -0
- package/dist/email/service.d.ts.map +1 -0
- package/dist/email/service.js +87 -0
- package/dist/email/service.js.map +1 -0
- package/dist/email/templates.d.ts +81 -0
- package/dist/email/templates.d.ts.map +1 -0
- package/dist/email/templates.js +84 -0
- package/dist/email/templates.js.map +1 -0
- package/dist/entitlements/index.d.ts +10 -0
- package/dist/entitlements/index.d.ts.map +1 -0
- package/dist/entitlements/index.js +28 -0
- package/dist/entitlements/index.js.map +1 -0
- package/dist/entitlements/service.d.ts +83 -0
- package/dist/entitlements/service.d.ts.map +1 -0
- package/dist/entitlements/service.js +158 -0
- package/dist/entitlements/service.js.map +1 -0
- package/dist/errors.d.ts +85 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +95 -0
- package/dist/errors.js.map +1 -0
- package/dist/events/core-events.d.ts +120 -0
- package/dist/events/core-events.d.ts.map +1 -0
- package/dist/events/core-events.js +109 -0
- package/dist/events/core-events.js.map +1 -0
- package/dist/events/index.d.ts +15 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/index.js +58 -0
- package/dist/events/index.js.map +1 -0
- package/dist/events/publisher.d.ts +46 -0
- package/dist/events/publisher.d.ts.map +1 -0
- package/dist/events/publisher.js +54 -0
- package/dist/events/publisher.js.map +1 -0
- package/dist/events/registry.d.ts +87 -0
- package/dist/events/registry.d.ts.map +1 -0
- package/dist/events/registry.js +34 -0
- package/dist/events/registry.js.map +1 -0
- package/dist/events/service.d.ts +78 -0
- package/dist/events/service.d.ts.map +1 -0
- package/dist/events/service.js +182 -0
- package/dist/events/service.js.map +1 -0
- package/dist/features/bucket.d.ts +22 -0
- package/dist/features/bucket.d.ts.map +1 -0
- package/dist/features/bucket.js +42 -0
- package/dist/features/bucket.js.map +1 -0
- package/dist/features/index.d.ts +15 -0
- package/dist/features/index.d.ts.map +1 -0
- package/dist/features/index.js +78 -0
- package/dist/features/index.js.map +1 -0
- package/dist/features/registry.d.ts +51 -0
- package/dist/features/registry.d.ts.map +1 -0
- package/dist/features/registry.js +52 -0
- package/dist/features/registry.js.map +1 -0
- package/dist/features/service.d.ts +78 -0
- package/dist/features/service.d.ts.map +1 -0
- package/dist/features/service.js +207 -0
- package/dist/features/service.js.map +1 -0
- package/dist/files/index.d.ts +6 -0
- package/dist/files/index.d.ts.map +1 -0
- package/dist/files/index.js +64 -0
- package/dist/files/index.js.map +1 -0
- package/dist/files/service.d.ts +92 -0
- package/dist/files/service.d.ts.map +1 -0
- package/dist/files/service.js +303 -0
- package/dist/files/service.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/jobs/backend.d.ts +23 -0
- package/dist/jobs/backend.d.ts.map +1 -0
- package/dist/jobs/backend.js +23 -0
- package/dist/jobs/backend.js.map +1 -0
- package/dist/jobs/index.d.ts +29 -0
- package/dist/jobs/index.d.ts.map +1 -0
- package/dist/jobs/index.js +114 -0
- package/dist/jobs/index.js.map +1 -0
- package/dist/jobs/service.d.ts +67 -0
- package/dist/jobs/service.d.ts.map +1 -0
- package/dist/jobs/service.js +139 -0
- package/dist/jobs/service.js.map +1 -0
- package/dist/jobs/types.d.ts +78 -0
- package/dist/jobs/types.d.ts.map +1 -0
- package/dist/jobs/types.js +4 -0
- package/dist/jobs/types.js.map +1 -0
- package/dist/logger/default.d.ts +7 -0
- package/dist/logger/default.d.ts.map +1 -0
- package/dist/logger/default.js +28 -0
- package/dist/logger/default.js.map +1 -0
- package/dist/logger/index.d.ts +24 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/logger/index.js +90 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/logger/redact.d.ts +40 -0
- package/dist/logger/redact.d.ts.map +1 -0
- package/dist/logger/redact.js +178 -0
- package/dist/logger/redact.js.map +1 -0
- package/dist/logger/service.d.ts +14 -0
- package/dist/logger/service.d.ts.map +1 -0
- package/dist/logger/service.js +56 -0
- package/dist/logger/service.js.map +1 -0
- package/dist/logger/sinks.d.ts +37 -0
- package/dist/logger/sinks.d.ts.map +1 -0
- package/dist/logger/sinks.js +135 -0
- package/dist/logger/sinks.js.map +1 -0
- package/dist/logger/types.d.ts +47 -0
- package/dist/logger/types.d.ts.map +1 -0
- package/dist/logger/types.js +8 -0
- package/dist/logger/types.js.map +1 -0
- package/dist/notifications/index.d.ts +8 -0
- package/dist/notifications/index.d.ts.map +1 -0
- package/dist/notifications/index.js +72 -0
- package/dist/notifications/index.js.map +1 -0
- package/dist/notifications/service.d.ts +79 -0
- package/dist/notifications/service.d.ts.map +1 -0
- package/dist/notifications/service.js +164 -0
- package/dist/notifications/service.js.map +1 -0
- package/dist/realtime/channels.d.ts +37 -0
- package/dist/realtime/channels.d.ts.map +1 -0
- package/dist/realtime/channels.js +58 -0
- package/dist/realtime/channels.js.map +1 -0
- package/dist/realtime/index.d.ts +19 -0
- package/dist/realtime/index.d.ts.map +1 -0
- package/dist/realtime/index.js +118 -0
- package/dist/realtime/index.js.map +1 -0
- package/dist/realtime/postgres.d.ts +48 -0
- package/dist/realtime/postgres.d.ts.map +1 -0
- package/dist/realtime/postgres.js +151 -0
- package/dist/realtime/postgres.js.map +1 -0
- package/dist/realtime/service.d.ts +75 -0
- package/dist/realtime/service.d.ts.map +1 -0
- package/dist/realtime/service.js +103 -0
- package/dist/realtime/service.js.map +1 -0
- package/dist/realtime/sse.d.ts +55 -0
- package/dist/realtime/sse.d.ts.map +1 -0
- package/dist/realtime/sse.js +123 -0
- package/dist/realtime/sse.js.map +1 -0
- package/dist/realtime/transport.d.ts +52 -0
- package/dist/realtime/transport.d.ts.map +1 -0
- package/dist/realtime/transport.js +2 -0
- package/dist/realtime/transport.js.map +1 -0
- package/dist/storage/index.d.ts +18 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +89 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/keys.d.ts +50 -0
- package/dist/storage/keys.d.ts.map +1 -0
- package/dist/storage/keys.js +120 -0
- package/dist/storage/keys.js.map +1 -0
- package/dist/storage/memory.d.ts +32 -0
- package/dist/storage/memory.d.ts.map +1 -0
- package/dist/storage/memory.js +84 -0
- package/dist/storage/memory.js.map +1 -0
- package/dist/storage/provider.d.ts +93 -0
- package/dist/storage/provider.d.ts.map +1 -0
- package/dist/storage/provider.js +2 -0
- package/dist/storage/provider.js.map +1 -0
- package/dist/storage/s3.d.ts +34 -0
- package/dist/storage/s3.d.ts.map +1 -0
- package/dist/storage/s3.js +175 -0
- package/dist/storage/s3.js.map +1 -0
- package/dist/storage/service.d.ts +86 -0
- package/dist/storage/service.d.ts.map +1 -0
- package/dist/storage/service.js +107 -0
- package/dist/storage/service.js.map +1 -0
- package/dist/storage/sigv4.d.ts +58 -0
- package/dist/storage/sigv4.d.ts.map +1 -0
- package/dist/storage/sigv4.js +114 -0
- package/dist/storage/sigv4.js.map +1 -0
- package/dist/streams/client.d.ts +91 -0
- package/dist/streams/client.d.ts.map +1 -0
- package/dist/streams/client.js +128 -0
- package/dist/streams/client.js.map +1 -0
- package/dist/streams/events.d.ts +68 -0
- package/dist/streams/events.d.ts.map +1 -0
- package/dist/streams/events.js +81 -0
- package/dist/streams/events.js.map +1 -0
- package/dist/streams/index.d.ts +47 -0
- package/dist/streams/index.d.ts.map +1 -0
- package/dist/streams/index.js +47 -0
- package/dist/streams/index.js.map +1 -0
- package/dist/streams/realtime.d.ts +27 -0
- package/dist/streams/realtime.d.ts.map +1 -0
- package/dist/streams/realtime.js +50 -0
- package/dist/streams/realtime.js.map +1 -0
- package/dist/streams/state.d.ts +26 -0
- package/dist/streams/state.d.ts.map +1 -0
- package/dist/streams/state.js +34 -0
- package/dist/streams/state.js.map +1 -0
- package/dist/users/index.d.ts +9 -0
- package/dist/users/index.d.ts.map +1 -0
- package/dist/users/index.js +23 -0
- package/dist/users/index.js.map +1 -0
- package/dist/users/service.d.ts +43 -0
- package/dist/users/service.d.ts.map +1 -0
- package/dist/users/service.js +113 -0
- package/dist/users/service.js.map +1 -0
- package/dist/webhooks/inbound.d.ts +70 -0
- package/dist/webhooks/inbound.d.ts.map +1 -0
- package/dist/webhooks/inbound.js +146 -0
- package/dist/webhooks/inbound.js.map +1 -0
- package/dist/webhooks/index.d.ts +12 -0
- package/dist/webhooks/index.d.ts.map +1 -0
- package/dist/webhooks/index.js +65 -0
- package/dist/webhooks/index.js.map +1 -0
- package/dist/webhooks/outgoing.d.ts +89 -0
- package/dist/webhooks/outgoing.d.ts.map +1 -0
- package/dist/webhooks/outgoing.js +375 -0
- package/dist/webhooks/outgoing.js.map +1 -0
- package/dist/webhooks/signature.d.ts +40 -0
- package/dist/webhooks/signature.d.ts.map +1 -0
- package/dist/webhooks/signature.js +77 -0
- package/dist/webhooks/signature.js.map +1 -0
- package/dist/workspaces/context.d.ts +30 -0
- package/dist/workspaces/context.d.ts.map +1 -0
- package/dist/workspaces/context.js +12 -0
- package/dist/workspaces/context.js.map +1 -0
- package/dist/workspaces/index.d.ts +5 -0
- package/dist/workspaces/index.d.ts.map +1 -0
- package/dist/workspaces/index.js +21 -0
- package/dist/workspaces/index.js.map +1 -0
- package/dist/workspaces/service.d.ts +126 -0
- package/dist/workspaces/service.d.ts.map +1 -0
- package/dist/workspaces/service.js +495 -0
- package/dist/workspaces/service.js.map +1 -0
- package/docs/README.md +71 -0
- package/docs/api-keys.md +184 -0
- package/docs/audit.md +198 -0
- package/docs/auth.md +196 -0
- package/docs/authorization.md +225 -0
- package/docs/billing.md +293 -0
- package/docs/cli.md +238 -0
- package/docs/config.md +163 -0
- package/docs/db.md +219 -0
- package/docs/email.md +222 -0
- package/docs/entitlements.md +194 -0
- package/docs/errors.md +158 -0
- package/docs/events.md +279 -0
- package/docs/feature-flags.md +237 -0
- package/docs/files.md +229 -0
- package/docs/jobs.md +288 -0
- package/docs/logger.md +269 -0
- package/docs/notifications.md +179 -0
- package/docs/publishing.md +128 -0
- package/docs/realtime.md +360 -0
- package/docs/storage.md +279 -0
- package/docs/streams.md +296 -0
- package/docs/users.md +117 -0
- package/docs/webhooks.md +272 -0
- package/docs/workspaces.md +269 -0
- package/drizzle/0000_init_users.sql +21 -0
- package/drizzle/0001_workspaces_roles_permissions.sql +86 -0
- package/drizzle/0002_billing_entitlements.sql +97 -0
- package/drizzle/0003_events.sql +17 -0
- package/drizzle/0004_notifications_audit.sql +64 -0
- package/drizzle/0005_api_keys_webhooks.sql +77 -0
- package/drizzle/0006_feature_flags.sql +22 -0
- package/drizzle/0007_files.sql +23 -0
- package/drizzle/meta/0000_snapshot.json +160 -0
- package/drizzle/meta/0001_snapshot.json +837 -0
- package/drizzle/meta/0002_snapshot.json +1580 -0
- package/drizzle/meta/0003_snapshot.json +1751 -0
- package/drizzle/meta/0004_snapshot.json +2109 -0
- package/drizzle/meta/0005_snapshot.json +2719 -0
- package/drizzle/meta/0006_snapshot.json +2909 -0
- package/drizzle/meta/0007_snapshot.json +3113 -0
- package/drizzle/meta/_journal.json +62 -0
- package/package.json +180 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import { and, asc, eq, inArray, lte, or, sql } from "drizzle-orm";
|
|
2
|
+
import { webhookDeliveries, webhookEndpoints } from "../db/schema/index.js";
|
|
3
|
+
import { ConflictError, InvalidInputError, NotFoundError } from "../errors.js";
|
|
4
|
+
import { isUniqueViolation } from "../db/errors.js";
|
|
5
|
+
import { generateWebhookSecret, signWebhookPayload, SIGNATURE_HEADER, TIMESTAMP_HEADER, } from "./signature.js";
|
|
6
|
+
const DEFAULT_MAX_ATTEMPTS = 6;
|
|
7
|
+
const DEFAULT_INITIAL_RETRY_MS = 30_000;
|
|
8
|
+
const DEFAULT_MAX_RETRY_MS = 6 * 60 * 60 * 1000;
|
|
9
|
+
const DEFAULT_FAILURE_THRESHOLD = 20;
|
|
10
|
+
const DEFAULT_CLAIM_LIMIT = 50;
|
|
11
|
+
const MAX_CLAIM_LIMIT = 500;
|
|
12
|
+
const MAX_ERROR_LENGTH = 1000;
|
|
13
|
+
/** Reject URLs a tenant should not be able to make the server call. */
|
|
14
|
+
function assertDeliverableUrl(url) {
|
|
15
|
+
let parsed;
|
|
16
|
+
try {
|
|
17
|
+
parsed = new URL(url);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
throw new InvalidInputError("A webhook URL must be absolute.");
|
|
21
|
+
}
|
|
22
|
+
if (parsed.protocol !== "https:") {
|
|
23
|
+
/**
|
|
24
|
+
* HTTPS only. A signed payload over plain HTTP still exposes its contents,
|
|
25
|
+
* and the signature does not stop an interceptor reading it.
|
|
26
|
+
*/
|
|
27
|
+
throw new InvalidInputError("A webhook URL must use https.");
|
|
28
|
+
}
|
|
29
|
+
const host = parsed.hostname.toLowerCase();
|
|
30
|
+
/**
|
|
31
|
+
* First-pass SSRF guard. A tenant registering `http://169.254.169.254/` would
|
|
32
|
+
* otherwise have the server fetch its own cloud metadata and post the result
|
|
33
|
+
* back. DNS can still resolve a public name to a private address, so a
|
|
34
|
+
* production deployment should also route egress through a proxy that enforces
|
|
35
|
+
* this at connect time — which is why {@link WebhookSender} is injectable.
|
|
36
|
+
*/
|
|
37
|
+
const blocked = host === "localhost" ||
|
|
38
|
+
host === "0.0.0.0" ||
|
|
39
|
+
host.endsWith(".localhost") ||
|
|
40
|
+
host.endsWith(".internal") ||
|
|
41
|
+
host.endsWith(".local") ||
|
|
42
|
+
/^127\./.test(host) ||
|
|
43
|
+
/^10\./.test(host) ||
|
|
44
|
+
/^192\.168\./.test(host) ||
|
|
45
|
+
/^169\.254\./.test(host) ||
|
|
46
|
+
/^172\.(1[6-9]|2\d|3[01])\./.test(host) ||
|
|
47
|
+
host === "[::1]" ||
|
|
48
|
+
host === "::1";
|
|
49
|
+
if (blocked) {
|
|
50
|
+
throw new InvalidInputError("A webhook URL may not point at a private or loopback address.");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function truncate(message) {
|
|
54
|
+
return message.length > MAX_ERROR_LENGTH
|
|
55
|
+
? `${message.slice(0, MAX_ERROR_LENGTH - 1)}…`
|
|
56
|
+
: message;
|
|
57
|
+
}
|
|
58
|
+
export function createOutgoingWebhooksService(deps) {
|
|
59
|
+
const now = deps.now ?? (() => new Date());
|
|
60
|
+
const maxAttempts = deps.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
|
|
61
|
+
const initialRetryMs = deps.initialRetryDelayMs ?? DEFAULT_INITIAL_RETRY_MS;
|
|
62
|
+
const maxRetryMs = deps.maxRetryDelayMs ?? DEFAULT_MAX_RETRY_MS;
|
|
63
|
+
const failureThreshold = deps.failureThreshold ?? DEFAULT_FAILURE_THRESHOLD;
|
|
64
|
+
/** Doubling backoff, capped. Attempt 1 has already happened when this runs. */
|
|
65
|
+
function retryDelayMs(attemptCount) {
|
|
66
|
+
return Math.min(initialRetryMs * 2 ** (attemptCount - 1), maxRetryMs);
|
|
67
|
+
}
|
|
68
|
+
async function requireOwnEndpoint(context, endpointId) {
|
|
69
|
+
const rows = await deps.db
|
|
70
|
+
.select()
|
|
71
|
+
.from(webhookEndpoints)
|
|
72
|
+
.where(and(eq(webhookEndpoints.id, endpointId),
|
|
73
|
+
// Scoped, so another tenant's endpoint id is simply not found.
|
|
74
|
+
eq(webhookEndpoints.workspaceId, context.workspaceId)))
|
|
75
|
+
.limit(1);
|
|
76
|
+
const endpoint = rows[0];
|
|
77
|
+
if (endpoint === undefined) {
|
|
78
|
+
throw new NotFoundError("Webhook endpoint not found.");
|
|
79
|
+
}
|
|
80
|
+
return endpoint;
|
|
81
|
+
}
|
|
82
|
+
const endpoints = {
|
|
83
|
+
async create(context, input) {
|
|
84
|
+
assertDeliverableUrl(input.url);
|
|
85
|
+
const secret = generateWebhookSecret();
|
|
86
|
+
try {
|
|
87
|
+
const [endpoint] = await deps.db
|
|
88
|
+
.insert(webhookEndpoints)
|
|
89
|
+
.values({
|
|
90
|
+
workspaceId: context.workspaceId,
|
|
91
|
+
url: input.url,
|
|
92
|
+
description: input.description ?? null,
|
|
93
|
+
secret,
|
|
94
|
+
eventTypes: [...new Set(input.eventTypes ?? [])],
|
|
95
|
+
})
|
|
96
|
+
.returning();
|
|
97
|
+
return { endpoint: endpoint, secret };
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
if (isUniqueViolation(error, "webhook_endpoints_workspace_url_unique")) {
|
|
101
|
+
throw new ConflictError("That URL is already registered for this workspace.", {
|
|
102
|
+
cause: error,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
async list(context) {
|
|
109
|
+
return deps.db
|
|
110
|
+
.select()
|
|
111
|
+
.from(webhookEndpoints)
|
|
112
|
+
.where(eq(webhookEndpoints.workspaceId, context.workspaceId))
|
|
113
|
+
.orderBy(asc(webhookEndpoints.createdAt));
|
|
114
|
+
},
|
|
115
|
+
async get(context, endpointId) {
|
|
116
|
+
const endpoint = await requireOwnEndpoint(context, endpointId);
|
|
117
|
+
return { endpoint, secret: endpoint.secret };
|
|
118
|
+
},
|
|
119
|
+
async update(context, endpointId, patch) {
|
|
120
|
+
await requireOwnEndpoint(context, endpointId);
|
|
121
|
+
if (patch.url !== undefined) {
|
|
122
|
+
assertDeliverableUrl(patch.url);
|
|
123
|
+
}
|
|
124
|
+
const [updated] = await deps.db
|
|
125
|
+
.update(webhookEndpoints)
|
|
126
|
+
.set({
|
|
127
|
+
...(patch.url !== undefined ? { url: patch.url } : {}),
|
|
128
|
+
...(patch.description !== undefined ? { description: patch.description } : {}),
|
|
129
|
+
...(patch.eventTypes !== undefined
|
|
130
|
+
? { eventTypes: [...new Set(patch.eventTypes)] }
|
|
131
|
+
: {}),
|
|
132
|
+
...(patch.isActive !== undefined
|
|
133
|
+
? {
|
|
134
|
+
isActive: patch.isActive,
|
|
135
|
+
// Re-enabling clears the failure state, or it would disable again
|
|
136
|
+
// on the next failure regardless of what changed.
|
|
137
|
+
...(patch.isActive
|
|
138
|
+
? { consecutiveFailures: 0, disabledAt: null }
|
|
139
|
+
: { disabledAt: now() }),
|
|
140
|
+
}
|
|
141
|
+
: {}),
|
|
142
|
+
updatedAt: now(),
|
|
143
|
+
})
|
|
144
|
+
.where(eq(webhookEndpoints.id, endpointId))
|
|
145
|
+
.returning();
|
|
146
|
+
return updated;
|
|
147
|
+
},
|
|
148
|
+
async rotateSecret(context, endpointId) {
|
|
149
|
+
await requireOwnEndpoint(context, endpointId);
|
|
150
|
+
const secret = generateWebhookSecret();
|
|
151
|
+
const [updated] = await deps.db
|
|
152
|
+
.update(webhookEndpoints)
|
|
153
|
+
.set({ secret, updatedAt: now() })
|
|
154
|
+
.where(eq(webhookEndpoints.id, endpointId))
|
|
155
|
+
.returning();
|
|
156
|
+
return { endpoint: updated, secret };
|
|
157
|
+
},
|
|
158
|
+
async remove(context, endpointId) {
|
|
159
|
+
await requireOwnEndpoint(context, endpointId);
|
|
160
|
+
// Deliveries cascade; a removed endpoint has no history worth retrying.
|
|
161
|
+
await deps.db.delete(webhookEndpoints).where(eq(webhookEndpoints.id, endpointId));
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
return {
|
|
165
|
+
endpoints,
|
|
166
|
+
async dispatch(event, tx) {
|
|
167
|
+
if (event.workspaceId === null) {
|
|
168
|
+
/**
|
|
169
|
+
* Outgoing webhooks are a tenant feature. A platform-wide event has no
|
|
170
|
+
* workspace whose endpoints should receive it, and picking one would leak
|
|
171
|
+
* across tenants.
|
|
172
|
+
*/
|
|
173
|
+
return [];
|
|
174
|
+
}
|
|
175
|
+
const db = tx ?? deps.db;
|
|
176
|
+
const subscribed = await db
|
|
177
|
+
.select()
|
|
178
|
+
.from(webhookEndpoints)
|
|
179
|
+
.where(and(eq(webhookEndpoints.workspaceId, event.workspaceId), eq(webhookEndpoints.isActive, true),
|
|
180
|
+
// Empty `event_types` means "everything".
|
|
181
|
+
or(sql `cardinality(${webhookEndpoints.eventTypes}) = 0`, sql `${event.type} = any(${webhookEndpoints.eventTypes})`)));
|
|
182
|
+
if (subscribed.length === 0) {
|
|
183
|
+
return [];
|
|
184
|
+
}
|
|
185
|
+
const payload = {
|
|
186
|
+
id: event.id,
|
|
187
|
+
type: event.type,
|
|
188
|
+
workspaceId: event.workspaceId,
|
|
189
|
+
occurredAt: event.occurredAt.toISOString(),
|
|
190
|
+
data: event.data,
|
|
191
|
+
};
|
|
192
|
+
/**
|
|
193
|
+
* `onConflictDoNothing` on (endpoint, event) is the idempotency guarantee:
|
|
194
|
+
* dispatching the same event twice — a retried job, a replayed event —
|
|
195
|
+
* cannot produce a second delivery.
|
|
196
|
+
*/
|
|
197
|
+
return db
|
|
198
|
+
.insert(webhookDeliveries)
|
|
199
|
+
.values(subscribed.map((endpoint) => ({
|
|
200
|
+
endpointId: endpoint.id,
|
|
201
|
+
eventId: event.id,
|
|
202
|
+
eventType: event.type,
|
|
203
|
+
payload: payload,
|
|
204
|
+
status: "pending",
|
|
205
|
+
nextAttemptAt: now(),
|
|
206
|
+
})))
|
|
207
|
+
.onConflictDoNothing({
|
|
208
|
+
target: [webhookDeliveries.endpointId, webhookDeliveries.eventId],
|
|
209
|
+
})
|
|
210
|
+
.returning();
|
|
211
|
+
},
|
|
212
|
+
async claimDue(options = {}) {
|
|
213
|
+
const at = now();
|
|
214
|
+
return deps.db
|
|
215
|
+
.select()
|
|
216
|
+
.from(webhookDeliveries)
|
|
217
|
+
.where(and(inArray(webhookDeliveries.status, ["pending", "failed"]), lte(webhookDeliveries.nextAttemptAt, at)))
|
|
218
|
+
.orderBy(asc(webhookDeliveries.nextAttemptAt))
|
|
219
|
+
.limit(Math.min(options.limit ?? DEFAULT_CLAIM_LIMIT, MAX_CLAIM_LIMIT));
|
|
220
|
+
},
|
|
221
|
+
async attempt(delivery) {
|
|
222
|
+
const endpointRows = await deps.db
|
|
223
|
+
.select()
|
|
224
|
+
.from(webhookEndpoints)
|
|
225
|
+
.where(eq(webhookEndpoints.id, delivery.endpointId))
|
|
226
|
+
.limit(1);
|
|
227
|
+
const endpoint = endpointRows[0];
|
|
228
|
+
if (endpoint === undefined) {
|
|
229
|
+
throw new NotFoundError("Webhook endpoint not found.");
|
|
230
|
+
}
|
|
231
|
+
const at = now();
|
|
232
|
+
const body = JSON.stringify(delivery.payload);
|
|
233
|
+
const signature = signWebhookPayload({
|
|
234
|
+
rawBody: body,
|
|
235
|
+
secret: endpoint.secret,
|
|
236
|
+
timestampMs: at.getTime(),
|
|
237
|
+
});
|
|
238
|
+
let result;
|
|
239
|
+
try {
|
|
240
|
+
result = await deps.sender.send({
|
|
241
|
+
url: endpoint.url,
|
|
242
|
+
body,
|
|
243
|
+
headers: {
|
|
244
|
+
"Content-Type": "application/json",
|
|
245
|
+
[SIGNATURE_HEADER]: signature,
|
|
246
|
+
[TIMESTAMP_HEADER]: String(at.getTime()),
|
|
247
|
+
"X-Saas-Core-Event-Id": delivery.eventId,
|
|
248
|
+
"X-Saas-Core-Event-Type": delivery.eventType,
|
|
249
|
+
"X-Saas-Core-Delivery-Id": delivery.id,
|
|
250
|
+
"X-Saas-Core-Attempt": String(delivery.attemptCount + 1),
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
// A sender that throws is a failed attempt, not a crashed worker.
|
|
256
|
+
result = {
|
|
257
|
+
delivered: false,
|
|
258
|
+
error: truncate(error instanceof Error ? error.message : "Unknown error"),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
const attemptCount = delivery.attemptCount + 1;
|
|
262
|
+
if (result.delivered) {
|
|
263
|
+
await deps.db.transaction(async (tx) => {
|
|
264
|
+
await tx
|
|
265
|
+
.update(webhookDeliveries)
|
|
266
|
+
.set({
|
|
267
|
+
status: "succeeded",
|
|
268
|
+
attemptCount,
|
|
269
|
+
lastAttemptedAt: at,
|
|
270
|
+
nextAttemptAt: null,
|
|
271
|
+
responseStatus: result.responseStatus ?? null,
|
|
272
|
+
error: null,
|
|
273
|
+
updatedAt: at,
|
|
274
|
+
})
|
|
275
|
+
.where(eq(webhookDeliveries.id, delivery.id));
|
|
276
|
+
// Any success clears the failure streak.
|
|
277
|
+
await tx
|
|
278
|
+
.update(webhookEndpoints)
|
|
279
|
+
.set({ consecutiveFailures: 0, updatedAt: at })
|
|
280
|
+
.where(eq(webhookEndpoints.id, endpoint.id));
|
|
281
|
+
});
|
|
282
|
+
return result;
|
|
283
|
+
}
|
|
284
|
+
const exhausted = attemptCount >= maxAttempts;
|
|
285
|
+
await deps.db.transaction(async (tx) => {
|
|
286
|
+
await tx
|
|
287
|
+
.update(webhookDeliveries)
|
|
288
|
+
.set({
|
|
289
|
+
status: exhausted ? "exhausted" : "failed",
|
|
290
|
+
attemptCount,
|
|
291
|
+
lastAttemptedAt: at,
|
|
292
|
+
// Null once terminal, so an exhausted delivery is never re-claimed.
|
|
293
|
+
nextAttemptAt: exhausted ? null : new Date(at.getTime() + retryDelayMs(attemptCount)),
|
|
294
|
+
responseStatus: result.responseStatus ?? null,
|
|
295
|
+
error: result.error ?? "Delivery failed.",
|
|
296
|
+
updatedAt: at,
|
|
297
|
+
})
|
|
298
|
+
.where(eq(webhookDeliveries.id, delivery.id));
|
|
299
|
+
const failures = endpoint.consecutiveFailures + 1;
|
|
300
|
+
await tx
|
|
301
|
+
.update(webhookEndpoints)
|
|
302
|
+
.set({
|
|
303
|
+
consecutiveFailures: failures,
|
|
304
|
+
/**
|
|
305
|
+
* An endpoint broken for a long time should stop consuming retry
|
|
306
|
+
* budget. Disabling it is recoverable — an operator re-enables it,
|
|
307
|
+
* which clears the streak.
|
|
308
|
+
*/
|
|
309
|
+
...(failures >= failureThreshold
|
|
310
|
+
? { isActive: false, disabledAt: at }
|
|
311
|
+
: {}),
|
|
312
|
+
updatedAt: at,
|
|
313
|
+
})
|
|
314
|
+
.where(eq(webhookEndpoints.id, endpoint.id));
|
|
315
|
+
});
|
|
316
|
+
return result;
|
|
317
|
+
},
|
|
318
|
+
async listDeliveries(context, options = {}) {
|
|
319
|
+
const own = await deps.db
|
|
320
|
+
.select({ id: webhookEndpoints.id })
|
|
321
|
+
.from(webhookEndpoints)
|
|
322
|
+
.where(eq(webhookEndpoints.workspaceId, context.workspaceId));
|
|
323
|
+
const endpointIds = own.map((row) => row.id);
|
|
324
|
+
if (endpointIds.length === 0) {
|
|
325
|
+
return [];
|
|
326
|
+
}
|
|
327
|
+
if (options.endpointId !== undefined && !endpointIds.includes(options.endpointId)) {
|
|
328
|
+
// Another tenant's endpoint id: return nothing rather than confirming it.
|
|
329
|
+
return [];
|
|
330
|
+
}
|
|
331
|
+
return deps.db
|
|
332
|
+
.select()
|
|
333
|
+
.from(webhookDeliveries)
|
|
334
|
+
.where(options.endpointId !== undefined
|
|
335
|
+
? eq(webhookDeliveries.endpointId, options.endpointId)
|
|
336
|
+
: inArray(webhookDeliveries.endpointId, endpointIds))
|
|
337
|
+
.orderBy(sql `${webhookDeliveries.createdAt} desc`)
|
|
338
|
+
.limit(Math.min(options.limit ?? DEFAULT_CLAIM_LIMIT, MAX_CLAIM_LIMIT));
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
/** A {@link WebhookSender} over `fetch`, with a timeout. */
|
|
343
|
+
export function createFetchWebhookSender(options = {}) {
|
|
344
|
+
const timeoutMs = options.timeoutMs ?? 10_000;
|
|
345
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
346
|
+
return {
|
|
347
|
+
async send({ url, body, headers }) {
|
|
348
|
+
// Without a timeout, one slow receiver holds a worker slot indefinitely.
|
|
349
|
+
const controller = new AbortController();
|
|
350
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
351
|
+
try {
|
|
352
|
+
const response = await fetchImpl(url, {
|
|
353
|
+
method: "POST",
|
|
354
|
+
headers,
|
|
355
|
+
body,
|
|
356
|
+
signal: controller.signal,
|
|
357
|
+
// Never follow a redirect: it could land somewhere the URL check
|
|
358
|
+
// rejected.
|
|
359
|
+
redirect: "manual",
|
|
360
|
+
});
|
|
361
|
+
return {
|
|
362
|
+
delivered: response.status >= 200 && response.status < 300,
|
|
363
|
+
responseStatus: response.status,
|
|
364
|
+
...(response.status >= 300
|
|
365
|
+
? { error: `Receiver responded with ${response.status}.` }
|
|
366
|
+
: {}),
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
finally {
|
|
370
|
+
clearTimeout(timer);
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
//# sourceMappingURL=outgoing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outgoing.js","sourceRoot":"","sources":["../../src/webhooks/outgoing.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webhook signing, shared by both directions.
|
|
3
|
+
*
|
|
4
|
+
* The same scheme the core already *verifies* from Revolut: HMAC-SHA256 over
|
|
5
|
+
* `v1.{timestamp}.{body}`, presented as `v1=<hex>`. Using one scheme for outgoing
|
|
6
|
+
* signatures too means there is a single implementation to audit, and receivers
|
|
7
|
+
* can follow the same instructions Stripe and Revolut users already know.
|
|
8
|
+
*/
|
|
9
|
+
export declare const SIGNATURE_VERSION = "v1";
|
|
10
|
+
export declare const SIGNATURE_HEADER = "x-saas-core-signature";
|
|
11
|
+
export declare const TIMESTAMP_HEADER = "x-saas-core-timestamp";
|
|
12
|
+
/** 256 bits, base64url. Long enough that brute force is not a concern. */
|
|
13
|
+
export declare function generateWebhookSecret(): string;
|
|
14
|
+
/** The exact string that gets signed. Both sides must agree on it byte for byte. */
|
|
15
|
+
export declare function signingPayload(timestampMs: number, rawBody: string): string;
|
|
16
|
+
export declare function signWebhookPayload(input: {
|
|
17
|
+
rawBody: string;
|
|
18
|
+
secret: string;
|
|
19
|
+
timestampMs: number;
|
|
20
|
+
}): string;
|
|
21
|
+
/**
|
|
22
|
+
* Verify a signature over a raw body.
|
|
23
|
+
*
|
|
24
|
+
* Must be given the body **exactly as received**. Parsing and re-serialising JSON
|
|
25
|
+
* changes key order and whitespace, which invalidates the signature — so a route
|
|
26
|
+
* handler has to read it as text before touching it.
|
|
27
|
+
*
|
|
28
|
+
* Accepts several comma-separated signatures, because that is how a sender
|
|
29
|
+
* presents both the old and new secret while rotating.
|
|
30
|
+
*/
|
|
31
|
+
export declare function verifyWebhookSignature(input: {
|
|
32
|
+
rawBody: string;
|
|
33
|
+
signature: string;
|
|
34
|
+
timestamp: string | number;
|
|
35
|
+
/** One secret, or several during rotation. */
|
|
36
|
+
secret: string | readonly string[];
|
|
37
|
+
toleranceSeconds?: number;
|
|
38
|
+
now?: () => number;
|
|
39
|
+
}): boolean;
|
|
40
|
+
//# sourceMappingURL=signature.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signature.d.ts","sourceRoot":"","sources":["../../src/webhooks/signature.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,eAAO,MAAM,gBAAgB,0BAA0B,CAAC;AACxD,eAAO,MAAM,gBAAgB,0BAA0B,CAAC;AAKxD,0EAA0E;AAC1E,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C;AAED,oFAAoF;AACpF,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE3E;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,MAAM,CAMT;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,8CAA8C;IAC9C,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;IACnC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB,GAAG,OAAO,CA+CV"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* Webhook signing, shared by both directions.
|
|
4
|
+
*
|
|
5
|
+
* The same scheme the core already *verifies* from Revolut: HMAC-SHA256 over
|
|
6
|
+
* `v1.{timestamp}.{body}`, presented as `v1=<hex>`. Using one scheme for outgoing
|
|
7
|
+
* signatures too means there is a single implementation to audit, and receivers
|
|
8
|
+
* can follow the same instructions Stripe and Revolut users already know.
|
|
9
|
+
*/
|
|
10
|
+
export const SIGNATURE_VERSION = "v1";
|
|
11
|
+
export const SIGNATURE_HEADER = "x-saas-core-signature";
|
|
12
|
+
export const TIMESTAMP_HEADER = "x-saas-core-timestamp";
|
|
13
|
+
/** Default freshness window. A signature is valid forever without one. */
|
|
14
|
+
const DEFAULT_TOLERANCE_SECONDS = 300;
|
|
15
|
+
/** 256 bits, base64url. Long enough that brute force is not a concern. */
|
|
16
|
+
export function generateWebhookSecret() {
|
|
17
|
+
return `whsec_${randomBytes(32).toString("base64url")}`;
|
|
18
|
+
}
|
|
19
|
+
/** The exact string that gets signed. Both sides must agree on it byte for byte. */
|
|
20
|
+
export function signingPayload(timestampMs, rawBody) {
|
|
21
|
+
return `${SIGNATURE_VERSION}.${timestampMs}.${rawBody}`;
|
|
22
|
+
}
|
|
23
|
+
export function signWebhookPayload(input) {
|
|
24
|
+
const digest = createHmac("sha256", input.secret)
|
|
25
|
+
.update(signingPayload(input.timestampMs, input.rawBody))
|
|
26
|
+
.digest("hex");
|
|
27
|
+
return `${SIGNATURE_VERSION}=${digest}`;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Verify a signature over a raw body.
|
|
31
|
+
*
|
|
32
|
+
* Must be given the body **exactly as received**. Parsing and re-serialising JSON
|
|
33
|
+
* changes key order and whitespace, which invalidates the signature — so a route
|
|
34
|
+
* handler has to read it as text before touching it.
|
|
35
|
+
*
|
|
36
|
+
* Accepts several comma-separated signatures, because that is how a sender
|
|
37
|
+
* presents both the old and new secret while rotating.
|
|
38
|
+
*/
|
|
39
|
+
export function verifyWebhookSignature(input) {
|
|
40
|
+
const timestampMs = typeof input.timestamp === "number" ? input.timestamp : Number(input.timestamp);
|
|
41
|
+
if (!Number.isFinite(timestampMs)) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
const tolerance = (input.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS) * 1000;
|
|
45
|
+
const at = (input.now ?? Date.now)();
|
|
46
|
+
/**
|
|
47
|
+
* Stale *and* future timestamps are rejected. A signature stays valid forever,
|
|
48
|
+
* so without a window a captured request could be replayed indefinitely — and a
|
|
49
|
+
* clock skewed forward would widen that window rather than narrow it.
|
|
50
|
+
*/
|
|
51
|
+
if (Math.abs(at - timestampMs) > tolerance) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const secrets = typeof input.secret === "string" ? [input.secret] : input.secret;
|
|
55
|
+
const presented = input.signature
|
|
56
|
+
.split(",")
|
|
57
|
+
.map((part) => part.trim())
|
|
58
|
+
.map((part) => (part.startsWith(`${SIGNATURE_VERSION}=`) ? part.slice(3) : part))
|
|
59
|
+
.filter((part) => part.length > 0);
|
|
60
|
+
if (presented.length === 0) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
return secrets.some((secret) => {
|
|
64
|
+
const expected = Buffer.from(createHmac("sha256", secret)
|
|
65
|
+
.update(signingPayload(timestampMs, input.rawBody))
|
|
66
|
+
.digest("hex"), "utf8");
|
|
67
|
+
return presented.some((candidate) => {
|
|
68
|
+
const candidateBuffer = Buffer.from(candidate, "utf8");
|
|
69
|
+
if (candidateBuffer.length !== expected.length) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
// Constant-time: a hex comparison leaks how many leading digits matched.
|
|
73
|
+
return timingSafeEqual(candidateBuffer, expected);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=signature.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signature.js","sourceRoot":"","sources":["../../src/webhooks/signature.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A caller's identity within one workspace, proven to correspond to a real
|
|
3
|
+
* membership.
|
|
4
|
+
*
|
|
5
|
+
* Every tenant-scoped operation takes one of these. The `membershipVerified`
|
|
6
|
+
* marker exists so that a `workspaceId` taken straight from a request body,
|
|
7
|
+
* query string or path parameter cannot be passed where a verified context is
|
|
8
|
+
* required — the type will not accept it. Obtain one from
|
|
9
|
+
* `workspaces.resolveContext()` or `workspaces.requireContext()`, which check
|
|
10
|
+
* `workspace_members` first.
|
|
11
|
+
*
|
|
12
|
+
* The marker is a convention, not a cryptographic guarantee: it is greppable and
|
|
13
|
+
* makes bypassing the check a deliberate, visible act rather than an oversight.
|
|
14
|
+
*/
|
|
15
|
+
export interface WorkspaceContext {
|
|
16
|
+
readonly userId: string;
|
|
17
|
+
readonly workspaceId: string;
|
|
18
|
+
/** @internal Set only by a verified membership lookup. Do not construct by hand. */
|
|
19
|
+
readonly membershipVerified: true;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Construct a verified context.
|
|
23
|
+
*
|
|
24
|
+
* Internal to the core. Callers outside it must go through
|
|
25
|
+
* `workspaces.resolveContext()`, which performs the membership check.
|
|
26
|
+
*
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
export declare function unsafeWorkspaceContext(userId: string, workspaceId: string): WorkspaceContext;
|
|
30
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/workspaces/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,oFAAoF;IACpF,QAAQ,CAAC,kBAAkB,EAAE,IAAI,CAAC;CACnC;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,gBAAgB,CAE5F"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Construct a verified context.
|
|
3
|
+
*
|
|
4
|
+
* Internal to the core. Callers outside it must go through
|
|
5
|
+
* `workspaces.resolveContext()`, which performs the membership check.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export function unsafeWorkspaceContext(userId, workspaceId) {
|
|
10
|
+
return { userId, workspaceId, membershipVerified: true };
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/workspaces/context.ts"],"names":[],"mappings":"AAqBA;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAAc,EAAE,WAAmB;IACxE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC;AAC3D,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { type WorkspaceContext } from "./context.ts";
|
|
2
|
+
export { createWorkspacesService, DEFAULT_SYSTEM_ROLES, slugify, type AcceptInvitationInput, type CreateWorkspaceInput, type InvitationWithToken, type InviteMemberInput, type SystemRoleDefinition, type WorkspaceMemberSummary, type WorkspacesService, type WorkspacesServiceDeps, } from "./service.ts";
|
|
3
|
+
/** Default service, bound to the configuration-driven database handle. */
|
|
4
|
+
export declare const workspaces: import("./service.ts").WorkspacesService;
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/workspaces/index.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,OAAO,EACP,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,GAC3B,MAAM,cAAc,CAAC;AAEtB,0EAA0E;AAC1E,eAAO,MAAM,UAAU,0CAKrB,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public workspaces surface: `@digimuza/saas-core/workspaces`. Server-only.
|
|
3
|
+
*
|
|
4
|
+
* A workspace is the tenancy boundary. Every operation here takes a
|
|
5
|
+
* {@link WorkspaceContext}, which can only be produced by a verified membership
|
|
6
|
+
* lookup — so a `workspaceId` supplied by a client cannot reach a query without
|
|
7
|
+
* passing through `resolveContext()` first.
|
|
8
|
+
*/
|
|
9
|
+
import { getDatabase } from "../db/client.js";
|
|
10
|
+
import { events as coreEventsService } from "../events/index.js";
|
|
11
|
+
import { createWorkspacesService } from "./service.js";
|
|
12
|
+
export {} from "./context.js";
|
|
13
|
+
export { createWorkspacesService, DEFAULT_SYSTEM_ROLES, slugify, } from "./service.js";
|
|
14
|
+
/** Default service, bound to the configuration-driven database handle. */
|
|
15
|
+
export const workspaces = createWorkspacesService({
|
|
16
|
+
get db() {
|
|
17
|
+
return getDatabase();
|
|
18
|
+
},
|
|
19
|
+
events: coreEventsService,
|
|
20
|
+
});
|
|
21
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/workspaces/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,MAAM,IAAI,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAEvD,OAAO,EAAyB,MAAM,cAAc,CAAC;AACrD,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,OAAO,GASR,MAAM,cAAc,CAAC;AAEtB,0EAA0E;AAC1E,MAAM,CAAC,MAAM,UAAU,GAAG,uBAAuB,CAAC;IAChD,IAAI,EAAE;QACJ,OAAO,WAAW,EAAE,CAAC;IACvB,CAAC;IACD,MAAM,EAAE,iBAAiB;CAC1B,CAAC,CAAC"}
|