@open-mercato/core 0.6.5-develop.4384.1.ce2ec6eaaa → 0.6.5-develop.4393.1.de282b5dfd
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/.turbo/turbo-build.log +2 -2
- package/dist/generated/entities/channel_ingest_dead_letter/index.js +25 -0
- package/dist/generated/entities/channel_ingest_dead_letter/index.js.map +7 -0
- package/dist/generated/entities/channel_thread_mapping/index.js +25 -0
- package/dist/generated/entities/channel_thread_mapping/index.js.map +7 -0
- package/dist/generated/entities/channel_thread_token/index.js +17 -0
- package/dist/generated/entities/channel_thread_token/index.js.map +7 -0
- package/dist/generated/entities/communication_channel/index.js +43 -0
- package/dist/generated/entities/communication_channel/index.js.map +7 -0
- package/dist/generated/entities/customer_interaction/index.js +4 -0
- package/dist/generated/entities/customer_interaction/index.js.map +2 -2
- package/dist/generated/entities/external_conversation/index.js +25 -0
- package/dist/generated/entities/external_conversation/index.js.map +7 -0
- package/dist/generated/entities/external_message/index.js +25 -0
- package/dist/generated/entities/external_message/index.js.map +7 -0
- package/dist/generated/entities/integration_credentials/index.js +3 -1
- package/dist/generated/entities/integration_credentials/index.js.map +2 -2
- package/dist/generated/entities/message/index.js +2 -0
- package/dist/generated/entities/message/index.js.map +2 -2
- package/dist/generated/entities/message_channel_link/index.js +33 -0
- package/dist/generated/entities/message_channel_link/index.js.map +7 -0
- package/dist/generated/entities/message_reaction/index.js +25 -0
- package/dist/generated/entities/message_reaction/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +11 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +117 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/authFixtures.js +2 -1
- package/dist/helpers/integration/authFixtures.js.map +2 -2
- package/dist/helpers/integration/communicationChannelsFixtures.js +58 -0
- package/dist/helpers/integration/communicationChannelsFixtures.js.map +7 -0
- package/dist/modules/communication_channels/acl.js +47 -0
- package/dist/modules/communication_channels/acl.js.map +7 -0
- package/dist/modules/communication_channels/api/delete/channels/[id]/route.js +133 -0
- package/dist/modules/communication_channels/api/delete/channels/[id]/route.js.map +7 -0
- package/dist/modules/communication_channels/api/delete/messages/[messageId]/reactions/[reactionId]/route.js +113 -0
- package/dist/modules/communication_channels/api/delete/messages/[messageId]/reactions/[reactionId]/route.js.map +7 -0
- package/dist/modules/communication_channels/api/get/channels/[id]/health/route.js +138 -0
- package/dist/modules/communication_channels/api/get/channels/[id]/health/route.js.map +7 -0
- package/dist/modules/communication_channels/api/get/channels/[id]/route.js +93 -0
- package/dist/modules/communication_channels/api/get/channels/[id]/route.js.map +7 -0
- package/dist/modules/communication_channels/api/get/channels/route.js +96 -0
- package/dist/modules/communication_channels/api/get/channels/route.js.map +7 -0
- package/dist/modules/communication_channels/api/get/me/channels/route.js +82 -0
- package/dist/modules/communication_channels/api/get/me/channels/route.js.map +7 -0
- package/dist/modules/communication_channels/api/get/oauth/[provider]/callback/route.js +274 -0
- package/dist/modules/communication_channels/api/get/oauth/[provider]/callback/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/import-history/route.js +168 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/import-history/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/poll-now/route.js +143 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/poll-now/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/push/register/route.js +127 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/push/register/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/set-primary/route.js +99 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/set-primary/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js +197 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js +124 -0
- package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/messages/[messageId]/reactions/route.js +120 -0
- package/dist/modules/communication_channels/api/post/messages/[messageId]/reactions/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js +157 -0
- package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/send-as-user/route.js +115 -0
- package/dist/modules/communication_channels/api/post/send-as-user/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/test-seed/route.js +217 -0
- package/dist/modules/communication_channels/api/post/test-seed/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/webhook/[provider]/route.js +175 -0
- package/dist/modules/communication_channels/api/post/webhook/[provider]/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/webhooks/gmail/route.js +123 -0
- package/dist/modules/communication_channels/api/post/webhooks/gmail/route.js.map +7 -0
- package/dist/modules/communication_channels/api/put/threads/[threadId]/assign/route.js +117 -0
- package/dist/modules/communication_channels/api/put/threads/[threadId]/assign/route.js.map +7 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.js +180 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.js.map +7 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.meta.js +36 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.meta.js.map +7 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/page.js +107 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/page.js.map +7 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/page.meta.js +38 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/page.meta.js.map +7 -0
- package/dist/modules/communication_channels/backend/profile/communication-channels/page.js +727 -0
- package/dist/modules/communication_channels/backend/profile/communication-channels/page.js.map +7 -0
- package/dist/modules/communication_channels/backend/profile/communication-channels/page.meta.js +38 -0
- package/dist/modules/communication_channels/backend/profile/communication-channels/page.meta.js.map +7 -0
- package/dist/modules/communication_channels/commands/connect-credential-channel.js +154 -0
- package/dist/modules/communication_channels/commands/connect-credential-channel.js.map +7 -0
- package/dist/modules/communication_channels/commands/delete-channel.js +137 -0
- package/dist/modules/communication_channels/commands/delete-channel.js.map +7 -0
- package/dist/modules/communication_channels/commands/deliver-outbound-message.js +400 -0
- package/dist/modules/communication_channels/commands/deliver-outbound-message.js.map +7 -0
- package/dist/modules/communication_channels/commands/disconnect-channel.js +163 -0
- package/dist/modules/communication_channels/commands/disconnect-channel.js.map +7 -0
- package/dist/modules/communication_channels/commands/ingest-inbound-message.js +413 -0
- package/dist/modules/communication_channels/commands/ingest-inbound-message.js.map +7 -0
- package/dist/modules/communication_channels/commands/interceptors.js +68 -0
- package/dist/modules/communication_channels/commands/interceptors.js.map +7 -0
- package/dist/modules/communication_channels/commands/process-inbound-reaction.js +198 -0
- package/dist/modules/communication_channels/commands/process-inbound-reaction.js.map +7 -0
- package/dist/modules/communication_channels/commands/push-register.js +146 -0
- package/dist/modules/communication_channels/commands/push-register.js.map +7 -0
- package/dist/modules/communication_channels/commands/push-renew.js +23 -0
- package/dist/modules/communication_channels/commands/push-renew.js.map +7 -0
- package/dist/modules/communication_channels/commands/push-unregister.js +108 -0
- package/dist/modules/communication_channels/commands/push-unregister.js.map +7 -0
- package/dist/modules/communication_channels/commands/queue-import-history.js +113 -0
- package/dist/modules/communication_channels/commands/queue-import-history.js.map +7 -0
- package/dist/modules/communication_channels/commands/reassign-conversation.js +193 -0
- package/dist/modules/communication_channels/commands/reassign-conversation.js.map +7 -0
- package/dist/modules/communication_channels/commands/set-primary-channel.js +114 -0
- package/dist/modules/communication_channels/commands/set-primary-channel.js.map +7 -0
- package/dist/modules/communication_channels/commands/toggle-outbound-reaction.js +260 -0
- package/dist/modules/communication_channels/commands/toggle-outbound-reaction.js.map +7 -0
- package/dist/modules/communication_channels/data/enrichers.js +286 -0
- package/dist/modules/communication_channels/data/enrichers.js.map +7 -0
- package/dist/modules/communication_channels/data/entities.js +447 -0
- package/dist/modules/communication_channels/data/entities.js.map +7 -0
- package/dist/modules/communication_channels/data/extensions.js +67 -0
- package/dist/modules/communication_channels/data/extensions.js.map +7 -0
- package/dist/modules/communication_channels/data/validators.js +123 -0
- package/dist/modules/communication_channels/data/validators.js.map +7 -0
- package/dist/modules/communication_channels/di.js +35 -0
- package/dist/modules/communication_channels/di.js.map +7 -0
- package/dist/modules/communication_channels/encryption.js +12 -0
- package/dist/modules/communication_channels/encryption.js.map +7 -0
- package/dist/modules/communication_channels/events.js +124 -0
- package/dist/modules/communication_channels/events.js.map +7 -0
- package/dist/modules/communication_channels/index.js +20 -0
- package/dist/modules/communication_channels/index.js.map +7 -0
- package/dist/modules/communication_channels/lib/access-control.js +43 -0
- package/dist/modules/communication_channels/lib/access-control.js.map +7 -0
- package/dist/modules/communication_channels/lib/adapter-compat.js +36 -0
- package/dist/modules/communication_channels/lib/adapter-compat.js.map +7 -0
- package/dist/modules/communication_channels/lib/adapter-registry-singleton.js +22 -0
- package/dist/modules/communication_channels/lib/adapter-registry-singleton.js.map +7 -0
- package/dist/modules/communication_channels/lib/adapter.js +1 -0
- package/dist/modules/communication_channels/lib/adapter.js.map +7 -0
- package/dist/modules/communication_channels/lib/connect-channel.js +95 -0
- package/dist/modules/communication_channels/lib/connect-channel.js.map +7 -0
- package/dist/modules/communication_channels/lib/contact-resolver.js +79 -0
- package/dist/modules/communication_channels/lib/contact-resolver.js.map +7 -0
- package/dist/modules/communication_channels/lib/credential-refresh.js +97 -0
- package/dist/modules/communication_channels/lib/credential-refresh.js.map +7 -0
- package/dist/modules/communication_channels/lib/dead-letter.js +62 -0
- package/dist/modules/communication_channels/lib/dead-letter.js.map +7 -0
- package/dist/modules/communication_channels/lib/email-capabilities.js +47 -0
- package/dist/modules/communication_channels/lib/email-capabilities.js.map +7 -0
- package/dist/modules/communication_channels/lib/email-contact.js +14 -0
- package/dist/modules/communication_channels/lib/email-contact.js.map +7 -0
- package/dist/modules/communication_channels/lib/email-mime.js +259 -0
- package/dist/modules/communication_channels/lib/email-mime.js.map +7 -0
- package/dist/modules/communication_channels/lib/error-classification.js +101 -0
- package/dist/modules/communication_channels/lib/error-classification.js.map +7 -0
- package/dist/modules/communication_channels/lib/gmail-pubsub-jwt.js +185 -0
- package/dist/modules/communication_channels/lib/gmail-pubsub-jwt.js.map +7 -0
- package/dist/modules/communication_channels/lib/mutation-guards.js +114 -0
- package/dist/modules/communication_channels/lib/mutation-guards.js.map +7 -0
- package/dist/modules/communication_channels/lib/oauth-client-config.js +32 -0
- package/dist/modules/communication_channels/lib/oauth-client-config.js.map +7 -0
- package/dist/modules/communication_channels/lib/oauth-state.js +128 -0
- package/dist/modules/communication_channels/lib/oauth-state.js.map +7 -0
- package/dist/modules/communication_channels/lib/oauth-token.js +45 -0
- package/dist/modules/communication_channels/lib/oauth-token.js.map +7 -0
- package/dist/modules/communication_channels/lib/pg-errors.js +11 -0
- package/dist/modules/communication_channels/lib/pg-errors.js.map +7 -0
- package/dist/modules/communication_channels/lib/provider-health.js +24 -0
- package/dist/modules/communication_channels/lib/provider-health.js.map +7 -0
- package/dist/modules/communication_channels/lib/push-state.js +19 -0
- package/dist/modules/communication_channels/lib/push-state.js.map +7 -0
- package/dist/modules/communication_channels/lib/queue.js +54 -0
- package/dist/modules/communication_channels/lib/queue.js.map +7 -0
- package/dist/modules/communication_channels/lib/reaction-processor-types.js +5 -0
- package/dist/modules/communication_channels/lib/reaction-processor-types.js.map +7 -0
- package/dist/modules/communication_channels/lib/reaction-semantics.js +11 -0
- package/dist/modules/communication_channels/lib/reaction-semantics.js.map +7 -0
- package/dist/modules/communication_channels/lib/registry.js +67 -0
- package/dist/modules/communication_channels/lib/registry.js.map +7 -0
- package/dist/modules/communication_channels/lib/route-mutation-guard.js +43 -0
- package/dist/modules/communication_channels/lib/route-mutation-guard.js.map +7 -0
- package/dist/modules/communication_channels/lib/sanitize-channel-html.js +96 -0
- package/dist/modules/communication_channels/lib/sanitize-channel-html.js.map +7 -0
- package/dist/modules/communication_channels/lib/send-as-user.js +194 -0
- package/dist/modules/communication_channels/lib/send-as-user.js.map +7 -0
- package/dist/modules/communication_channels/lib/system-user.js +22 -0
- package/dist/modules/communication_channels/lib/system-user.js.map +7 -0
- package/dist/modules/communication_channels/lib/test-seed.js +68 -0
- package/dist/modules/communication_channels/lib/test-seed.js.map +7 -0
- package/dist/modules/communication_channels/lib/thread-matcher.js +263 -0
- package/dist/modules/communication_channels/lib/thread-matcher.js.map +7 -0
- package/dist/modules/communication_channels/lib/thread-token.js +219 -0
- package/dist/modules/communication_channels/lib/thread-token.js.map +7 -0
- package/dist/modules/communication_channels/lib/use-connect-channel.js +61 -0
- package/dist/modules/communication_channels/lib/use-connect-channel.js.map +7 -0
- package/dist/modules/communication_channels/migrations/Migration20260526134719_communication_channels.js +50 -0
- package/dist/modules/communication_channels/migrations/Migration20260526134719_communication_channels.js.map +7 -0
- package/dist/modules/communication_channels/migrations/Migration20260527195446_communication_channels.js +19 -0
- package/dist/modules/communication_channels/migrations/Migration20260527195446_communication_channels.js.map +7 -0
- package/dist/modules/communication_channels/migrations/Migration20260529231848_communication_channels.js +13 -0
- package/dist/modules/communication_channels/migrations/Migration20260529231848_communication_channels.js.map +7 -0
- package/dist/modules/communication_channels/migrations/Migration20260531120000_communication_channels.js +17 -0
- package/dist/modules/communication_channels/migrations/Migration20260531120000_communication_channels.js.map +7 -0
- package/dist/modules/communication_channels/notifications.client.js +51 -0
- package/dist/modules/communication_channels/notifications.client.js.map +7 -0
- package/dist/modules/communication_channels/notifications.handlers.js +53 -0
- package/dist/modules/communication_channels/notifications.handlers.js.map +7 -0
- package/dist/modules/communication_channels/notifications.js +56 -0
- package/dist/modules/communication_channels/notifications.js.map +7 -0
- package/dist/modules/communication_channels/setup.js +105 -0
- package/dist/modules/communication_channels/setup.js.map +7 -0
- package/dist/modules/communication_channels/subscribers/channel-requires-reauth-notification.js +71 -0
- package/dist/modules/communication_channels/subscribers/channel-requires-reauth-notification.js.map +7 -0
- package/dist/modules/communication_channels/subscribers/outbound-bridge.js +103 -0
- package/dist/modules/communication_channels/subscribers/outbound-bridge.js.map +7 -0
- package/dist/modules/communication_channels/subscribers/user-deleted-cascade.js +51 -0
- package/dist/modules/communication_channels/subscribers/user-deleted-cascade.js.map +7 -0
- package/dist/modules/communication_channels/widgets/components.js +7 -0
- package/dist/modules/communication_channels/widgets/components.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.client.js +18 -0
- package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.client.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.js +30 -0
- package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.client.js +185 -0
- package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.client.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.js +17 -0
- package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.js +44 -0
- package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.js +17 -0
- package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/profile-channels-menu/widget.js +23 -0
- package/dist/modules/communication_channels/widgets/injection/profile-channels-menu/widget.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.client.js +141 -0
- package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.client.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.js +17 -0
- package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection-table.js +38 -0
- package/dist/modules/communication_channels/widgets/injection-table.js.map +7 -0
- package/dist/modules/communication_channels/widgets/notifications/ChannelRequiresReauthRenderer.js +25 -0
- package/dist/modules/communication_channels/widgets/notifications/ChannelRequiresReauthRenderer.js.map +7 -0
- package/dist/modules/communication_channels/widgets/notifications/MessageReceivedRenderer.js +19 -0
- package/dist/modules/communication_channels/widgets/notifications/MessageReceivedRenderer.js.map +7 -0
- package/dist/modules/communication_channels/widgets/notifications/index.js +7 -0
- package/dist/modules/communication_channels/widgets/notifications/index.js.map +7 -0
- package/dist/modules/communication_channels/workers/channel-import-history.js +185 -0
- package/dist/modules/communication_channels/workers/channel-import-history.js.map +7 -0
- package/dist/modules/communication_channels/workers/gmail-history-sync.js +154 -0
- package/dist/modules/communication_channels/workers/gmail-history-sync.js.map +7 -0
- package/dist/modules/communication_channels/workers/gmail-renew-watch.js +95 -0
- package/dist/modules/communication_channels/workers/gmail-renew-watch.js.map +7 -0
- package/dist/modules/communication_channels/workers/inbound-processor.js +56 -0
- package/dist/modules/communication_channels/workers/inbound-processor.js.map +7 -0
- package/dist/modules/communication_channels/workers/outbound-delivery.js +85 -0
- package/dist/modules/communication_channels/workers/outbound-delivery.js.map +7 -0
- package/dist/modules/communication_channels/workers/poll-channel.js +240 -0
- package/dist/modules/communication_channels/workers/poll-channel.js.map +7 -0
- package/dist/modules/communication_channels/workers/poll-tick.js +132 -0
- package/dist/modules/communication_channels/workers/poll-tick.js.map +7 -0
- package/dist/modules/communication_channels/workers/reaction-processor.js +192 -0
- package/dist/modules/communication_channels/workers/reaction-processor.js.map +7 -0
- package/dist/modules/customers/acl.js +18 -0
- package/dist/modules/customers/acl.js.map +2 -2
- package/dist/modules/customers/api/activities/route.js +9 -0
- package/dist/modules/customers/api/activities/route.js.map +2 -2
- package/dist/modules/customers/api/companies/[id]/route.js +18 -7
- package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/[id]/visibility/route.js +151 -0
- package/dist/modules/customers/api/interactions/[id]/visibility/route.js.map +7 -0
- package/dist/modules/customers/api/interactions/counts/route.js +6 -0
- package/dist/modules/customers/api/interactions/counts/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/route.js +26 -7
- package/dist/modules/customers/api/interactions/route.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/email-threads/route.js +82 -0
- package/dist/modules/customers/api/people/[id]/email-threads/route.js.map +7 -0
- package/dist/modules/customers/api/people/[id]/emails/route.js +157 -0
- package/dist/modules/customers/api/people/[id]/emails/route.js.map +7 -0
- package/dist/modules/customers/api/people/[id]/route.js +12 -4
- package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +10 -0
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/commands/deals.js +46 -5
- package/dist/modules/customers/commands/deals.js.map +2 -2
- package/dist/modules/customers/commands/interactions.js +16 -0
- package/dist/modules/customers/commands/interactions.js.map +2 -2
- package/dist/modules/customers/components/detail/ActivityCard.js +32 -0
- package/dist/modules/customers/components/detail/ActivityCard.js.map +2 -2
- package/dist/modules/customers/components/detail/ComposeEmailDialog.js +242 -0
- package/dist/modules/customers/components/detail/ComposeEmailDialog.js.map +7 -0
- package/dist/modules/customers/components/detail/DealForm.js +2 -1
- package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
- package/dist/modules/customers/components/detail/DealsSection.js +10 -0
- package/dist/modules/customers/components/detail/DealsSection.js.map +2 -2
- package/dist/modules/customers/components/detail/EmailCardActions.js +179 -0
- package/dist/modules/customers/components/detail/EmailCardActions.js.map +7 -0
- package/dist/modules/customers/components/detail/EmailReplyForwardActions.js +52 -0
- package/dist/modules/customers/components/detail/EmailReplyForwardActions.js.map +7 -0
- package/dist/modules/customers/components/detail/PersonDetailTabs.js +7 -1
- package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
- package/dist/modules/customers/components/detail/PersonEmailThreadsTab.js +366 -0
- package/dist/modules/customers/components/detail/PersonEmailThreadsTab.js.map +7 -0
- package/dist/modules/customers/data/enrichers.js +133 -2
- package/dist/modules/customers/data/enrichers.js.map +2 -2
- package/dist/modules/customers/data/entities.js +18 -0
- package/dist/modules/customers/data/entities.js.map +2 -2
- package/dist/modules/customers/data/extensions.js +16 -0
- package/dist/modules/customers/data/extensions.js.map +7 -0
- package/dist/modules/customers/encryption.js +11 -0
- package/dist/modules/customers/encryption.js.map +2 -2
- package/dist/modules/customers/events.js +4 -1
- package/dist/modules/customers/events.js.map +2 -2
- package/dist/modules/customers/lib/findPeopleByAddresses.js +64 -0
- package/dist/modules/customers/lib/findPeopleByAddresses.js.map +7 -0
- package/dist/modules/customers/lib/kysely.js.map +2 -2
- package/dist/modules/customers/lib/link-channel-message-handler.js +303 -0
- package/dist/modules/customers/lib/link-channel-message-handler.js.map +7 -0
- package/dist/modules/customers/lib/personEmailThreads.js +205 -0
- package/dist/modules/customers/lib/personEmailThreads.js.map +7 -0
- package/dist/modules/customers/lib/visibilityFilter.js +51 -0
- package/dist/modules/customers/lib/visibilityFilter.js.map +7 -0
- package/dist/modules/customers/migrations/Migration20260527012240_customers.js +20 -0
- package/dist/modules/customers/migrations/Migration20260527012240_customers.js.map +7 -0
- package/dist/modules/customers/setup.js +2 -1
- package/dist/modules/customers/setup.js.map +2 -2
- package/dist/modules/customers/subscribers/link-channel-message-received.js +12 -0
- package/dist/modules/customers/subscribers/link-channel-message-received.js.map +7 -0
- package/dist/modules/customers/subscribers/link-channel-message-sent.js +12 -0
- package/dist/modules/customers/subscribers/link-channel-message-sent.js.map +7 -0
- package/dist/modules/integrations/data/entities.js +8 -1
- package/dist/modules/integrations/data/entities.js.map +2 -2
- package/dist/modules/integrations/lib/credentials-service.js +29 -14
- package/dist/modules/integrations/lib/credentials-service.js.map +2 -2
- package/dist/modules/integrations/migrations/Migration20260526154136_integrations.js +15 -0
- package/dist/modules/integrations/migrations/Migration20260526154136_integrations.js.map +7 -0
- package/dist/modules/messages/commands/messages.js +70 -8
- package/dist/modules/messages/commands/messages.js.map +2 -2
- package/dist/modules/messages/components/ComposeMessagePageClient.js +24 -13
- package/dist/modules/messages/components/ComposeMessagePageClient.js.map +2 -2
- package/dist/modules/messages/components/MessageDetailPageClient.js +39 -2
- package/dist/modules/messages/components/MessageDetailPageClient.js.map +2 -2
- package/dist/modules/messages/components/MessagesInboxPageClient.js +1 -0
- package/dist/modules/messages/components/MessagesInboxPageClient.js.map +2 -2
- package/dist/modules/messages/data/entities.js +8 -1
- package/dist/modules/messages/data/entities.js.map +2 -2
- package/dist/modules/messages/migrations/Migration20260531130000.js +15 -0
- package/dist/modules/messages/migrations/Migration20260531130000.js.map +7 -0
- package/dist/modules/messages/widgets/injection-table.js +7 -0
- package/dist/modules/messages/widgets/injection-table.js.map +7 -0
- package/generated/entities/channel_ingest_dead_letter/index.ts +11 -0
- package/generated/entities/channel_thread_mapping/index.ts +11 -0
- package/generated/entities/channel_thread_token/index.ts +7 -0
- package/generated/entities/communication_channel/index.ts +20 -0
- package/generated/entities/customer_interaction/index.ts +2 -0
- package/generated/entities/external_conversation/index.ts +11 -0
- package/generated/entities/external_message/index.ts +11 -0
- package/generated/entities/integration_credentials/index.ts +1 -0
- package/generated/entities/message/index.ts +1 -0
- package/generated/entities/message_channel_link/index.ts +15 -0
- package/generated/entities/message_reaction/index.ts +11 -0
- package/generated/entities.ids.generated.ts +11 -0
- package/generated/entity-fields-registry.ts +117 -0
- package/package.json +9 -7
- package/src/helpers/integration/authFixtures.ts +4 -1
- package/src/helpers/integration/communicationChannelsFixtures.ts +124 -0
- package/src/modules/communication_channels/acl.ts +43 -0
- package/src/modules/communication_channels/api/delete/channels/[id]/route.ts +163 -0
- package/src/modules/communication_channels/api/delete/messages/[messageId]/reactions/[reactionId]/route.ts +143 -0
- package/src/modules/communication_channels/api/get/channels/[id]/health/route.ts +173 -0
- package/src/modules/communication_channels/api/get/channels/[id]/route.ts +111 -0
- package/src/modules/communication_channels/api/get/channels/route.ts +109 -0
- package/src/modules/communication_channels/api/get/me/channels/route.ts +100 -0
- package/src/modules/communication_channels/api/get/oauth/[provider]/callback/route.ts +355 -0
- package/src/modules/communication_channels/api/post/channels/[id]/import-history/route.ts +206 -0
- package/src/modules/communication_channels/api/post/channels/[id]/poll-now/route.ts +174 -0
- package/src/modules/communication_channels/api/post/channels/[id]/push/register/route.ts +158 -0
- package/src/modules/communication_channels/api/post/channels/[id]/set-primary/route.ts +114 -0
- package/src/modules/communication_channels/api/post/channels/[id]/test-send/route.ts +241 -0
- package/src/modules/communication_channels/api/post/channels/connect/credentials/route.ts +134 -0
- package/src/modules/communication_channels/api/post/messages/[messageId]/reactions/route.ts +143 -0
- package/src/modules/communication_channels/api/post/oauth/[provider]/initiate/route.ts +192 -0
- package/src/modules/communication_channels/api/post/send-as-user/route.ts +125 -0
- package/src/modules/communication_channels/api/post/test-seed/route.ts +267 -0
- package/src/modules/communication_channels/api/post/webhook/[provider]/route.ts +227 -0
- package/src/modules/communication_channels/api/post/webhooks/gmail/route.ts +161 -0
- package/src/modules/communication_channels/api/put/threads/[threadId]/assign/route.ts +132 -0
- package/src/modules/communication_channels/backend/communication_channels/channels/[id]/page.meta.ts +34 -0
- package/src/modules/communication_channels/backend/communication_channels/channels/[id]/page.tsx +250 -0
- package/src/modules/communication_channels/backend/communication_channels/channels/page.meta.ts +36 -0
- package/src/modules/communication_channels/backend/communication_channels/channels/page.tsx +137 -0
- package/src/modules/communication_channels/backend/profile/communication-channels/page.meta.ts +36 -0
- package/src/modules/communication_channels/backend/profile/communication-channels/page.tsx +907 -0
- package/src/modules/communication_channels/commands/connect-credential-channel.ts +243 -0
- package/src/modules/communication_channels/commands/delete-channel.ts +193 -0
- package/src/modules/communication_channels/commands/deliver-outbound-message.ts +579 -0
- package/src/modules/communication_channels/commands/disconnect-channel.ts +241 -0
- package/src/modules/communication_channels/commands/ingest-inbound-message.ts +602 -0
- package/src/modules/communication_channels/commands/interceptors.ts +104 -0
- package/src/modules/communication_channels/commands/process-inbound-reaction.ts +265 -0
- package/src/modules/communication_channels/commands/push-register.ts +203 -0
- package/src/modules/communication_channels/commands/push-renew.ts +49 -0
- package/src/modules/communication_channels/commands/push-unregister.ts +168 -0
- package/src/modules/communication_channels/commands/queue-import-history.ts +180 -0
- package/src/modules/communication_channels/commands/reassign-conversation.ts +273 -0
- package/src/modules/communication_channels/commands/set-primary-channel.ts +154 -0
- package/src/modules/communication_channels/commands/toggle-outbound-reaction.ts +347 -0
- package/src/modules/communication_channels/data/enrichers.ts +413 -0
- package/src/modules/communication_channels/data/entities.ts +546 -0
- package/src/modules/communication_channels/data/extensions.ts +76 -0
- package/src/modules/communication_channels/data/validators.ts +138 -0
- package/src/modules/communication_channels/di.ts +40 -0
- package/src/modules/communication_channels/encryption.ts +44 -0
- package/src/modules/communication_channels/events.ts +122 -0
- package/src/modules/communication_channels/i18n/de.json +138 -0
- package/src/modules/communication_channels/i18n/en.json +138 -0
- package/src/modules/communication_channels/i18n/es.json +138 -0
- package/src/modules/communication_channels/i18n/pl.json +138 -0
- package/src/modules/communication_channels/index.ts +19 -0
- package/src/modules/communication_channels/lib/access-control.ts +110 -0
- package/src/modules/communication_channels/lib/adapter-compat.ts +57 -0
- package/src/modules/communication_channels/lib/adapter-registry-singleton.ts +35 -0
- package/src/modules/communication_channels/lib/adapter.ts +605 -0
- package/src/modules/communication_channels/lib/connect-channel.ts +163 -0
- package/src/modules/communication_channels/lib/contact-resolver.ts +162 -0
- package/src/modules/communication_channels/lib/credential-refresh.ts +197 -0
- package/src/modules/communication_channels/lib/dead-letter.ts +87 -0
- package/src/modules/communication_channels/lib/email-capabilities.ts +60 -0
- package/src/modules/communication_channels/lib/email-contact.ts +17 -0
- package/src/modules/communication_channels/lib/email-mime.ts +425 -0
- package/src/modules/communication_channels/lib/error-classification.ts +144 -0
- package/src/modules/communication_channels/lib/gmail-pubsub-jwt.ts +278 -0
- package/src/modules/communication_channels/lib/mutation-guards.ts +215 -0
- package/src/modules/communication_channels/lib/oauth-client-config.ts +79 -0
- package/src/modules/communication_channels/lib/oauth-state.ts +228 -0
- package/src/modules/communication_channels/lib/oauth-token.ts +81 -0
- package/src/modules/communication_channels/lib/pg-errors.ts +12 -0
- package/src/modules/communication_channels/lib/provider-health.ts +47 -0
- package/src/modules/communication_channels/lib/push-state.ts +38 -0
- package/src/modules/communication_channels/lib/queue.ts +66 -0
- package/src/modules/communication_channels/lib/reaction-processor-types.ts +51 -0
- package/src/modules/communication_channels/lib/reaction-semantics.ts +48 -0
- package/src/modules/communication_channels/lib/registry.ts +99 -0
- package/src/modules/communication_channels/lib/route-mutation-guard.ts +68 -0
- package/src/modules/communication_channels/lib/sanitize-channel-html.ts +129 -0
- package/src/modules/communication_channels/lib/send-as-user.ts +284 -0
- package/src/modules/communication_channels/lib/system-user.ts +74 -0
- package/src/modules/communication_channels/lib/test-seed.ts +140 -0
- package/src/modules/communication_channels/lib/thread-matcher.ts +430 -0
- package/src/modules/communication_channels/lib/thread-token.ts +355 -0
- package/src/modules/communication_channels/lib/use-connect-channel.ts +73 -0
- package/src/modules/communication_channels/migrations/.snapshot-open-mercato.json +2142 -0
- package/src/modules/communication_channels/migrations/Migration20260526134719_communication_channels.ts +55 -0
- package/src/modules/communication_channels/migrations/Migration20260527195446_communication_channels.ts +20 -0
- package/src/modules/communication_channels/migrations/Migration20260529231848_communication_channels.ts +13 -0
- package/src/modules/communication_channels/migrations/Migration20260531120000_communication_channels.ts +24 -0
- package/src/modules/communication_channels/notifications.client.ts +50 -0
- package/src/modules/communication_channels/notifications.handlers.ts +86 -0
- package/src/modules/communication_channels/notifications.ts +52 -0
- package/src/modules/communication_channels/setup.ts +158 -0
- package/src/modules/communication_channels/subscribers/channel-requires-reauth-notification.ts +118 -0
- package/src/modules/communication_channels/subscribers/outbound-bridge.ts +175 -0
- package/src/modules/communication_channels/subscribers/user-deleted-cascade.ts +100 -0
- package/src/modules/communication_channels/widgets/components.ts +36 -0
- package/src/modules/communication_channels/widgets/injection/channel-badge/widget.client.tsx +38 -0
- package/src/modules/communication_channels/widgets/injection/channel-badge/widget.ts +51 -0
- package/src/modules/communication_channels/widgets/injection/channel-info-panel/widget.client.tsx +278 -0
- package/src/modules/communication_channels/widgets/injection/channel-info-panel/widget.ts +24 -0
- package/src/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.tsx +63 -0
- package/src/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.ts +29 -0
- package/src/modules/communication_channels/widgets/injection/profile-channels-menu/widget.ts +34 -0
- package/src/modules/communication_channels/widgets/injection/reaction-bar/widget.client.tsx +177 -0
- package/src/modules/communication_channels/widgets/injection/reaction-bar/widget.ts +26 -0
- package/src/modules/communication_channels/widgets/injection-table.ts +47 -0
- package/src/modules/communication_channels/widgets/notifications/ChannelRequiresReauthRenderer.tsx +48 -0
- package/src/modules/communication_channels/widgets/notifications/MessageReceivedRenderer.tsx +45 -0
- package/src/modules/communication_channels/widgets/notifications/index.ts +2 -0
- package/src/modules/communication_channels/workers/channel-import-history.ts +252 -0
- package/src/modules/communication_channels/workers/gmail-history-sync.ts +223 -0
- package/src/modules/communication_channels/workers/gmail-renew-watch.ts +141 -0
- package/src/modules/communication_channels/workers/inbound-processor.ts +114 -0
- package/src/modules/communication_channels/workers/outbound-delivery.ts +155 -0
- package/src/modules/communication_channels/workers/poll-channel.ts +391 -0
- package/src/modules/communication_channels/workers/poll-tick.ts +210 -0
- package/src/modules/communication_channels/workers/reaction-processor.ts +264 -0
- package/src/modules/customers/acl.ts +18 -0
- package/src/modules/customers/api/activities/route.ts +13 -0
- package/src/modules/customers/api/companies/[id]/route.ts +21 -1
- package/src/modules/customers/api/interactions/[id]/visibility/route.ts +179 -0
- package/src/modules/customers/api/interactions/counts/route.ts +10 -0
- package/src/modules/customers/api/interactions/route.ts +51 -5
- package/src/modules/customers/api/people/[id]/email-threads/route.ts +92 -0
- package/src/modules/customers/api/people/[id]/emails/route.ts +184 -0
- package/src/modules/customers/api/people/[id]/route.ts +17 -2
- package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +11 -1
- package/src/modules/customers/commands/deals.ts +65 -6
- package/src/modules/customers/commands/interactions.ts +30 -0
- package/src/modules/customers/components/detail/ActivityCard.tsx +48 -0
- package/src/modules/customers/components/detail/ComposeEmailDialog.tsx +329 -0
- package/src/modules/customers/components/detail/DealForm.tsx +2 -1
- package/src/modules/customers/components/detail/DealsSection.tsx +26 -0
- package/src/modules/customers/components/detail/EmailCardActions.tsx +258 -0
- package/src/modules/customers/components/detail/EmailReplyForwardActions.tsx +53 -0
- package/src/modules/customers/components/detail/PersonDetailTabs.tsx +8 -1
- package/src/modules/customers/components/detail/PersonEmailThreadsTab.tsx +448 -0
- package/src/modules/customers/data/enrichers.ts +252 -1
- package/src/modules/customers/data/entities.ts +46 -1
- package/src/modules/customers/data/extensions.ts +26 -0
- package/src/modules/customers/encryption.ts +11 -0
- package/src/modules/customers/events.ts +4 -0
- package/src/modules/customers/i18n/de.json +41 -0
- package/src/modules/customers/i18n/en.json +41 -0
- package/src/modules/customers/i18n/es.json +41 -0
- package/src/modules/customers/i18n/pl.json +41 -0
- package/src/modules/customers/lib/findPeopleByAddresses.ts +107 -0
- package/src/modules/customers/lib/kysely.ts +16 -0
- package/src/modules/customers/lib/link-channel-message-handler.ts +571 -0
- package/src/modules/customers/lib/personEmailThreads.ts +325 -0
- package/src/modules/customers/lib/visibilityFilter.ts +152 -0
- package/src/modules/customers/migrations/.snapshot-open-mercato.json +61 -0
- package/src/modules/customers/migrations/Migration20260527012240_customers.ts +23 -0
- package/src/modules/customers/setup.ts +1 -0
- package/src/modules/customers/subscribers/link-channel-message-received.ts +21 -0
- package/src/modules/customers/subscribers/link-channel-message-sent.ts +21 -0
- package/src/modules/integrations/AGENTS.md +9 -0
- package/src/modules/integrations/data/entities.ts +21 -1
- package/src/modules/integrations/lib/credentials-service.ts +49 -13
- package/src/modules/integrations/migrations/.snapshot-open-mercato.json +26 -1
- package/src/modules/integrations/migrations/Migration20260526154136_integrations.ts +15 -0
- package/src/modules/messages/commands/messages.ts +101 -8
- package/src/modules/messages/components/ComposeMessagePageClient.tsx +17 -0
- package/src/modules/messages/components/MessageDetailPageClient.tsx +43 -0
- package/src/modules/messages/components/MessagesInboxPageClient.tsx +4 -0
- package/src/modules/messages/data/entities.ts +11 -0
- package/src/modules/messages/migrations/.snapshot-open-mercato.json +18 -0
- package/src/modules/messages/migrations/Migration20260531130000.ts +15 -0
- package/src/modules/messages/widgets/injection-table.ts +29 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import crypto from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Spec C § Phase C2 — Verify a Gmail Pub/Sub push request.
|
|
5
|
+
*
|
|
6
|
+
* Pub/Sub push subscriptions authenticate with a Google-signed RS256 JWT
|
|
7
|
+
* passed in the `Authorization: Bearer …` header. The token's claims contain
|
|
8
|
+
* - `iss`: `https://accounts.google.com`
|
|
9
|
+
* - `aud`: the configured audience (typically the webhook URL)
|
|
10
|
+
* - `email`: the publishing service-account address (e.g.
|
|
11
|
+
* `gmail-api-push@system.gserviceaccount.com` for Gmail watch)
|
|
12
|
+
* - `email_verified: true`
|
|
13
|
+
*
|
|
14
|
+
* The default verifier downloads Google's public x509 certs and caches them
|
|
15
|
+
* for an hour. Tests inject a mock verifier via `setGmailPubSubVerifier(...)`.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const GOOGLE_CERTS_URL = 'https://www.googleapis.com/oauth2/v1/certs'
|
|
19
|
+
const CERT_CACHE_TTL_MS = 60 * 60 * 1000
|
|
20
|
+
const CERT_FETCH_TIMEOUT_MS = 5000
|
|
21
|
+
// Google mints OIDC tokens with one of these two issuer strings.
|
|
22
|
+
const GOOGLE_ACCEPTED_ISSUERS = new Set(['https://accounts.google.com', 'accounts.google.com'])
|
|
23
|
+
|
|
24
|
+
export interface GmailPubSubJwtClaims {
|
|
25
|
+
iss: string
|
|
26
|
+
aud: string | string[]
|
|
27
|
+
email?: string
|
|
28
|
+
emailVerified?: boolean
|
|
29
|
+
exp: number
|
|
30
|
+
iat: number
|
|
31
|
+
sub?: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface GmailPubSubVerifyInput {
|
|
35
|
+
authorizationHeader: string | null | undefined
|
|
36
|
+
expectedAudience: string
|
|
37
|
+
expectedEmail: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface GmailPubSubVerifier {
|
|
41
|
+
verify(input: GmailPubSubVerifyInput): Promise<GmailPubSubJwtClaims>
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class GmailPubSubJwtError extends Error {
|
|
45
|
+
readonly code: 'missing_token' | 'invalid_format' | 'invalid_signature' | 'expired' | 'wrong_issuer' | 'wrong_audience' | 'wrong_email' | 'fetch_certs_failed'
|
|
46
|
+
constructor(message: string, code: GmailPubSubJwtError['code']) {
|
|
47
|
+
super(message)
|
|
48
|
+
this.name = 'GmailPubSubJwtError'
|
|
49
|
+
this.code = code
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface CertCacheEntry {
|
|
54
|
+
certs: Record<string, string>
|
|
55
|
+
fetchedAt: number
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
class FetchGmailPubSubVerifier implements GmailPubSubVerifier {
|
|
59
|
+
private certCache: CertCacheEntry | null = null
|
|
60
|
+
|
|
61
|
+
async verify(input: GmailPubSubVerifyInput): Promise<GmailPubSubJwtClaims> {
|
|
62
|
+
const token = extractBearer(input.authorizationHeader)
|
|
63
|
+
if (!token) throw new GmailPubSubJwtError('Missing Authorization bearer token', 'missing_token')
|
|
64
|
+
|
|
65
|
+
const parts = token.split('.')
|
|
66
|
+
if (parts.length !== 3) {
|
|
67
|
+
throw new GmailPubSubJwtError('JWT must have three dot-separated parts', 'invalid_format')
|
|
68
|
+
}
|
|
69
|
+
const [headerB64, payloadB64, signatureB64] = parts
|
|
70
|
+
let header: Record<string, unknown>
|
|
71
|
+
let claims: GmailPubSubJwtClaims
|
|
72
|
+
try {
|
|
73
|
+
header = JSON.parse(Buffer.from(headerB64, 'base64url').toString('utf-8')) as Record<string, unknown>
|
|
74
|
+
claims = JSON.parse(Buffer.from(payloadB64, 'base64url').toString('utf-8')) as GmailPubSubJwtClaims
|
|
75
|
+
} catch {
|
|
76
|
+
throw new GmailPubSubJwtError('JWT header/payload not parseable', 'invalid_format')
|
|
77
|
+
}
|
|
78
|
+
const kid = typeof header.kid === 'string' ? header.kid : null
|
|
79
|
+
const alg = typeof header.alg === 'string' ? header.alg : null
|
|
80
|
+
if (!kid || alg !== 'RS256') {
|
|
81
|
+
throw new GmailPubSubJwtError(`Unsupported JWT alg/kid: alg=${alg ?? '?'} kid=${kid ?? '?'}`, 'invalid_format')
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const certs = await this.getCerts()
|
|
85
|
+
const cert = certs[kid]
|
|
86
|
+
if (!cert) {
|
|
87
|
+
// Cert rotated; refetch once and retry.
|
|
88
|
+
this.certCache = null
|
|
89
|
+
const refreshed = await this.getCerts()
|
|
90
|
+
const fresh = refreshed[kid]
|
|
91
|
+
if (!fresh) throw new GmailPubSubJwtError(`No cert for kid=${kid}`, 'invalid_signature')
|
|
92
|
+
verifySignature(`${headerB64}.${payloadB64}`, signatureB64, fresh)
|
|
93
|
+
} else {
|
|
94
|
+
verifySignature(`${headerB64}.${payloadB64}`, signatureB64, cert)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
validateClaims(claims, input)
|
|
98
|
+
return claims
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private async getCerts(): Promise<Record<string, string>> {
|
|
102
|
+
if (this.certCache && Date.now() - this.certCache.fetchedAt < CERT_CACHE_TTL_MS) {
|
|
103
|
+
return this.certCache.certs
|
|
104
|
+
}
|
|
105
|
+
let res: Response
|
|
106
|
+
const controller = new AbortController()
|
|
107
|
+
const timer = setTimeout(() => controller.abort(), CERT_FETCH_TIMEOUT_MS)
|
|
108
|
+
try {
|
|
109
|
+
res = await fetch(GOOGLE_CERTS_URL, { signal: controller.signal })
|
|
110
|
+
} catch (err) {
|
|
111
|
+
throw new GmailPubSubJwtError(
|
|
112
|
+
`Failed to fetch Google certs: ${err instanceof Error ? err.message : String(err)}`,
|
|
113
|
+
'fetch_certs_failed',
|
|
114
|
+
)
|
|
115
|
+
} finally {
|
|
116
|
+
clearTimeout(timer)
|
|
117
|
+
}
|
|
118
|
+
if (!res.ok) {
|
|
119
|
+
throw new GmailPubSubJwtError(`Google certs endpoint returned ${res.status}`, 'fetch_certs_failed')
|
|
120
|
+
}
|
|
121
|
+
let parsed: unknown
|
|
122
|
+
try {
|
|
123
|
+
parsed = await res.json()
|
|
124
|
+
} catch (err) {
|
|
125
|
+
throw new GmailPubSubJwtError(
|
|
126
|
+
`Google certs endpoint returned invalid JSON: ${err instanceof Error ? err.message : String(err)}`,
|
|
127
|
+
'fetch_certs_failed',
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
const certs = toCertMap(parsed)
|
|
131
|
+
if (!certs) {
|
|
132
|
+
// Never cache an empty/malformed payload — a single bad 200 would otherwise
|
|
133
|
+
// disable Gmail push verification for the whole CERT_CACHE_TTL_MS window.
|
|
134
|
+
throw new GmailPubSubJwtError('Google certs endpoint returned an unexpected shape', 'fetch_certs_failed')
|
|
135
|
+
}
|
|
136
|
+
this.certCache = { certs, fetchedAt: Date.now() }
|
|
137
|
+
return certs
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Validates that a parsed Google certs response is a non-empty `kid → PEM` map.
|
|
143
|
+
* Returns the typed map on success, or `null` when the shape is unusable so the
|
|
144
|
+
* caller can fail closed instead of caching garbage.
|
|
145
|
+
*/
|
|
146
|
+
function toCertMap(value: unknown): Record<string, string> | null {
|
|
147
|
+
if (value == null || typeof value !== 'object' || Array.isArray(value)) return null
|
|
148
|
+
const entries = Object.entries(value as Record<string, unknown>)
|
|
149
|
+
if (entries.length === 0) return null
|
|
150
|
+
const certs: Record<string, string> = {}
|
|
151
|
+
for (const [kid, pem] of entries) {
|
|
152
|
+
if (typeof pem !== 'string' || pem.length === 0) return null
|
|
153
|
+
certs[kid] = pem
|
|
154
|
+
}
|
|
155
|
+
return certs
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function extractBearer(header: string | null | undefined): string | null {
|
|
159
|
+
if (!header) return null
|
|
160
|
+
const match = /^Bearer\s+(.+)$/i.exec(header.trim())
|
|
161
|
+
return match ? match[1].trim() : null
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function verifySignature(input: string, signatureB64: string, cert: string): void {
|
|
165
|
+
const verifier = crypto.createVerify('RSA-SHA256')
|
|
166
|
+
verifier.update(input)
|
|
167
|
+
verifier.end()
|
|
168
|
+
const signature = Buffer.from(signatureB64, 'base64url')
|
|
169
|
+
const ok = verifier.verify(cert, signature)
|
|
170
|
+
if (!ok) {
|
|
171
|
+
throw new GmailPubSubJwtError('JWT signature verification failed', 'invalid_signature')
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function validateClaims(claims: GmailPubSubJwtClaims, input: GmailPubSubVerifyInput): void {
|
|
176
|
+
const now = Math.floor(Date.now() / 1000)
|
|
177
|
+
// Fail closed: a token without a numeric `exp` has no expiry, so a captured
|
|
178
|
+
// push JWT could otherwise be replayed indefinitely. Require `exp` and reject
|
|
179
|
+
// anything already past it (with a small clock-skew allowance).
|
|
180
|
+
if (typeof claims.exp !== 'number' || claims.exp < now - 5) {
|
|
181
|
+
throw new GmailPubSubJwtError('JWT expired or missing exp', 'expired')
|
|
182
|
+
}
|
|
183
|
+
// Reject a future-dated `iat` beyond the clock-skew allowance: a token
|
|
184
|
+
// "issued" in the future signals a forged token or a badly-skewed clock.
|
|
185
|
+
// Reuses the `expired` code (both are temporal-validity failures → 401).
|
|
186
|
+
if (typeof claims.iat === 'number' && claims.iat > now + 5) {
|
|
187
|
+
throw new GmailPubSubJwtError('JWT issued in the future', 'expired')
|
|
188
|
+
}
|
|
189
|
+
// Verify the issuer is Google (defense-in-depth alongside the signature +
|
|
190
|
+
// service-account email checks; the file header documents this requirement).
|
|
191
|
+
if (typeof claims.iss !== 'string' || !GOOGLE_ACCEPTED_ISSUERS.has(claims.iss)) {
|
|
192
|
+
throw new GmailPubSubJwtError(
|
|
193
|
+
`JWT issuer not accepted (got ${typeof claims.iss === 'string' ? claims.iss : 'none'})`,
|
|
194
|
+
'wrong_issuer',
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
const audOk = Array.isArray(claims.aud)
|
|
198
|
+
? claims.aud.includes(input.expectedAudience)
|
|
199
|
+
: claims.aud === input.expectedAudience
|
|
200
|
+
if (!audOk) {
|
|
201
|
+
throw new GmailPubSubJwtError(
|
|
202
|
+
`JWT audience mismatch (expected ${input.expectedAudience})`,
|
|
203
|
+
'wrong_audience',
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
// Google uses `email_verified` in the wire format; our type uses camelCase.
|
|
207
|
+
// Accept either to be defensive.
|
|
208
|
+
const emailVerified =
|
|
209
|
+
claims.emailVerified === true ||
|
|
210
|
+
(claims as unknown as { email_verified?: boolean }).email_verified === true
|
|
211
|
+
if (!emailVerified || claims.email !== input.expectedEmail) {
|
|
212
|
+
throw new GmailPubSubJwtError(
|
|
213
|
+
`JWT email mismatch (expected ${input.expectedEmail})`,
|
|
214
|
+
'wrong_email',
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
let cachedVerifier: GmailPubSubVerifier | null = null
|
|
220
|
+
|
|
221
|
+
export function getGmailPubSubVerifier(): GmailPubSubVerifier {
|
|
222
|
+
if (!cachedVerifier) cachedVerifier = new FetchGmailPubSubVerifier()
|
|
223
|
+
return cachedVerifier
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function setGmailPubSubVerifier(verifier: GmailPubSubVerifier | null): void {
|
|
227
|
+
cachedVerifier = verifier
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Decode a Pub/Sub envelope from the webhook body.
|
|
232
|
+
*
|
|
233
|
+
* Shape: `{ message: { data: base64<JSON>, messageId, publishTime, attributes }, subscription }`.
|
|
234
|
+
*
|
|
235
|
+
* Gmail's payload (`data` field) decodes to `{ emailAddress, historyId }`.
|
|
236
|
+
*/
|
|
237
|
+
export interface PubSubEnvelope {
|
|
238
|
+
message: {
|
|
239
|
+
data: string
|
|
240
|
+
messageId: string
|
|
241
|
+
publishTime?: string
|
|
242
|
+
attributes?: Record<string, string>
|
|
243
|
+
}
|
|
244
|
+
subscription?: string
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export interface GmailPushPayload {
|
|
248
|
+
emailAddress: string
|
|
249
|
+
historyId: string | number
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function decodeGmailPubSubBody(rawBody: string): GmailPushPayload {
|
|
253
|
+
let envelope: PubSubEnvelope
|
|
254
|
+
try {
|
|
255
|
+
envelope = JSON.parse(rawBody) as PubSubEnvelope
|
|
256
|
+
} catch {
|
|
257
|
+
throw new GmailPubSubJwtError('Body is not valid JSON', 'invalid_format')
|
|
258
|
+
}
|
|
259
|
+
if (!envelope.message?.data) {
|
|
260
|
+
throw new GmailPubSubJwtError('Envelope missing message.data', 'invalid_format')
|
|
261
|
+
}
|
|
262
|
+
let payloadText: string
|
|
263
|
+
try {
|
|
264
|
+
payloadText = Buffer.from(envelope.message.data, 'base64').toString('utf-8')
|
|
265
|
+
} catch {
|
|
266
|
+
throw new GmailPubSubJwtError('message.data not base64 decodable', 'invalid_format')
|
|
267
|
+
}
|
|
268
|
+
let payload: GmailPushPayload
|
|
269
|
+
try {
|
|
270
|
+
payload = JSON.parse(payloadText) as GmailPushPayload
|
|
271
|
+
} catch {
|
|
272
|
+
throw new GmailPubSubJwtError('message.data JSON not parseable', 'invalid_format')
|
|
273
|
+
}
|
|
274
|
+
if (!payload.emailAddress || payload.historyId === undefined) {
|
|
275
|
+
throw new GmailPubSubJwtError('message.data missing emailAddress or historyId', 'invalid_format')
|
|
276
|
+
}
|
|
277
|
+
return payload
|
|
278
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
3
|
+
import {
|
|
4
|
+
CommunicationChannel,
|
|
5
|
+
ExternalConversation,
|
|
6
|
+
ExternalMessage,
|
|
7
|
+
MessageChannelLink,
|
|
8
|
+
} from '../data/entities'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Hub-side mutation guards (Phase 4 of the email integration spec).
|
|
12
|
+
*
|
|
13
|
+
* These guards are plain functions that any caller (CRUD route, command,
|
|
14
|
+
* subscriber) can invoke to enforce hub invariants before mutating state.
|
|
15
|
+
* They throw `ChannelMutationBlockedError` on violations so the caller can
|
|
16
|
+
* map the error to an HTTP 4xx (typically 422) without leaking internals.
|
|
17
|
+
*
|
|
18
|
+
* Why functions, not the generic `validateCrudMutationGuard`:
|
|
19
|
+
* The hub's writes are channel-shaped rather than entity-shaped — "deleting a
|
|
20
|
+
* channel with unread inbound" is not a per-entity invariant the generic
|
|
21
|
+
* CRUD guard layer can express. These functions encode hub semantics directly
|
|
22
|
+
* and stay invocation-site agnostic.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export type ChannelMutationGuardReason =
|
|
26
|
+
| 'channel_has_inbound_history'
|
|
27
|
+
| 'channel_requires_reauth'
|
|
28
|
+
| 'channel_disconnected'
|
|
29
|
+
| 'channel_not_found'
|
|
30
|
+
/**
|
|
31
|
+
* @deprecated Use `channel_has_inbound_history`. Kept for one minor release
|
|
32
|
+
* so external callers/tests catch a transition window. Round-2 F8 rename.
|
|
33
|
+
*/
|
|
34
|
+
| 'channel_has_unread_inbound'
|
|
35
|
+
|
|
36
|
+
export class ChannelMutationBlockedError extends Error {
|
|
37
|
+
readonly reason: ChannelMutationGuardReason
|
|
38
|
+
readonly channelId: string
|
|
39
|
+
/** Field-level error message keyed by `channelId` — for `createCrudFormError`. */
|
|
40
|
+
readonly errors: Record<string, string>
|
|
41
|
+
|
|
42
|
+
constructor(reason: ChannelMutationGuardReason, channelId: string, message: string) {
|
|
43
|
+
super(message)
|
|
44
|
+
this.name = 'ChannelMutationBlockedError'
|
|
45
|
+
this.reason = reason
|
|
46
|
+
this.channelId = channelId
|
|
47
|
+
this.errors = { channelId: message }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ChannelScope {
|
|
52
|
+
tenantId: string
|
|
53
|
+
organizationId?: string | null
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface GuardChannelDeleteInput {
|
|
57
|
+
channelId: string
|
|
58
|
+
scope: ChannelScope
|
|
59
|
+
/** Bypass the unread-inbound check; used by admin "force delete" actions. */
|
|
60
|
+
force?: boolean
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Guard `channel.delete`.
|
|
65
|
+
*
|
|
66
|
+
* Blocks when the channel still has ANY inbound `MessageChannelLink` rows.
|
|
67
|
+
* Implementation note: the hub doesn't track per-message read state — that
|
|
68
|
+
* lives on the messages module's `MessageRecipient.read_at`. Counting unread
|
|
69
|
+
* across the module boundary would require either raw cross-module SQL
|
|
70
|
+
* (forbidden by AGENTS.md) or a QueryEngine round-trip per delete. We pick
|
|
71
|
+
* the simpler safety contract: block delete when ANY inbound link exists,
|
|
72
|
+
* with `force: true` as the escape hatch.
|
|
73
|
+
*
|
|
74
|
+
* Pass `force: true` to bypass — exposed to admins so they can hard-delete a
|
|
75
|
+
* channel whose mailbox they no longer care about (e.g. ex-employee
|
|
76
|
+
* offboarding).
|
|
77
|
+
*/
|
|
78
|
+
export async function guardChannelDelete(
|
|
79
|
+
em: EntityManager,
|
|
80
|
+
input: GuardChannelDeleteInput,
|
|
81
|
+
): Promise<void> {
|
|
82
|
+
const channel = await findOneWithDecryption(
|
|
83
|
+
em,
|
|
84
|
+
CommunicationChannel,
|
|
85
|
+
{
|
|
86
|
+
id: input.channelId,
|
|
87
|
+
tenantId: input.scope.tenantId,
|
|
88
|
+
organizationId: input.scope.organizationId ?? null,
|
|
89
|
+
deletedAt: null,
|
|
90
|
+
},
|
|
91
|
+
undefined,
|
|
92
|
+
input.scope,
|
|
93
|
+
)
|
|
94
|
+
if (!channel) {
|
|
95
|
+
throw new ChannelMutationBlockedError(
|
|
96
|
+
'channel_not_found',
|
|
97
|
+
input.channelId,
|
|
98
|
+
'Channel not found in this tenant scope.',
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
if (input.force) return
|
|
102
|
+
|
|
103
|
+
const inboundCount = await countInboundLinksForChannel(em, input.channelId, input.scope)
|
|
104
|
+
if (inboundCount > 0) {
|
|
105
|
+
throw new ChannelMutationBlockedError(
|
|
106
|
+
'channel_has_inbound_history',
|
|
107
|
+
input.channelId,
|
|
108
|
+
`Channel has ${inboundCount} inbound message${inboundCount === 1 ? '' : 's'} in history. Pass force=true to delete anyway.`,
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface GuardOutboundCreateInput {
|
|
114
|
+
channelId: string
|
|
115
|
+
scope: ChannelScope
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Guard `message.create` for outbound sends.
|
|
120
|
+
*
|
|
121
|
+
* Blocks when the target channel is in `requires_reauth` or `disconnected` —
|
|
122
|
+
* outbound sends through a re-auth-needed channel will fail at the provider
|
|
123
|
+
* anyway, and blocking here gives a deterministic 422 with a field-level error
|
|
124
|
+
* instead of an opaque 500. The hub's `mark_channel_requires_reauth` command
|
|
125
|
+
* sets the status when refresh fails (slice 3b).
|
|
126
|
+
*/
|
|
127
|
+
export async function guardOutboundCreate(
|
|
128
|
+
em: EntityManager,
|
|
129
|
+
input: GuardOutboundCreateInput,
|
|
130
|
+
): Promise<void> {
|
|
131
|
+
const channel = await findOneWithDecryption(
|
|
132
|
+
em,
|
|
133
|
+
CommunicationChannel,
|
|
134
|
+
{
|
|
135
|
+
id: input.channelId,
|
|
136
|
+
tenantId: input.scope.tenantId,
|
|
137
|
+
organizationId: input.scope.organizationId ?? null,
|
|
138
|
+
deletedAt: null,
|
|
139
|
+
},
|
|
140
|
+
undefined,
|
|
141
|
+
input.scope,
|
|
142
|
+
)
|
|
143
|
+
if (!channel) {
|
|
144
|
+
throw new ChannelMutationBlockedError(
|
|
145
|
+
'channel_not_found',
|
|
146
|
+
input.channelId,
|
|
147
|
+
'Channel not found in this tenant scope.',
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
if (channel.status === 'requires_reauth') {
|
|
151
|
+
throw new ChannelMutationBlockedError(
|
|
152
|
+
'channel_requires_reauth',
|
|
153
|
+
input.channelId,
|
|
154
|
+
'This channel needs reconnection before it can send messages. Open Profile -> Communication channels to reconnect.',
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
if (channel.status === 'disconnected') {
|
|
158
|
+
throw new ChannelMutationBlockedError(
|
|
159
|
+
'channel_disconnected',
|
|
160
|
+
input.channelId,
|
|
161
|
+
'This channel is disconnected and cannot send messages.',
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Returns the count of inbound `MessageChannelLink` rows the bridge created for
|
|
168
|
+
* this channel. Used as the safety check before allowing a channel delete.
|
|
169
|
+
*
|
|
170
|
+
* Path: `external_conversations` (by channelId) → `message_channel_links` (by
|
|
171
|
+
* externalConversationId, direction='inbound'). Both tables are hub-owned, so
|
|
172
|
+
* we stay on the right side of the cross-module isolation rule. No raw SQL.
|
|
173
|
+
*
|
|
174
|
+
* Round-2 F8 rename (2026-05-26): was `countUnreadInboundForChannel`, but the
|
|
175
|
+
* function never counted "unread" — it always counted all inbound links. The
|
|
176
|
+
* old name is preserved as a `@deprecated` alias for one minor version.
|
|
177
|
+
*/
|
|
178
|
+
export async function countInboundLinksForChannel(
|
|
179
|
+
em: EntityManager,
|
|
180
|
+
channelId: string,
|
|
181
|
+
scope: ChannelScope,
|
|
182
|
+
): Promise<number> {
|
|
183
|
+
const conversations = await findWithDecryption(
|
|
184
|
+
em,
|
|
185
|
+
ExternalConversation,
|
|
186
|
+
{
|
|
187
|
+
channelId,
|
|
188
|
+
tenantId: scope.tenantId,
|
|
189
|
+
organizationId: scope.organizationId ?? null,
|
|
190
|
+
},
|
|
191
|
+
{ fields: ['id'] },
|
|
192
|
+
scope,
|
|
193
|
+
)
|
|
194
|
+
if (conversations.length === 0) return 0
|
|
195
|
+
const conversationIds = (conversations as Array<{ id: string }>).map((c) => c.id)
|
|
196
|
+
const count = await em.count(
|
|
197
|
+
MessageChannelLink,
|
|
198
|
+
{
|
|
199
|
+
externalConversationId: { $in: conversationIds },
|
|
200
|
+
direction: 'inbound',
|
|
201
|
+
tenantId: scope.tenantId,
|
|
202
|
+
organizationId: scope.organizationId ?? null,
|
|
203
|
+
},
|
|
204
|
+
)
|
|
205
|
+
return typeof count === 'number' ? count : 0
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* @deprecated Use `countInboundLinksForChannel`. Kept for one minor release so
|
|
210
|
+
* external callers catch the rename window. Removed in the next minor.
|
|
211
|
+
*/
|
|
212
|
+
export const countUnreadInboundForChannel = countInboundLinksForChannel
|
|
213
|
+
|
|
214
|
+
// Re-export entity types so callers can keep their typing tight.
|
|
215
|
+
export type { CommunicationChannel, ExternalMessage, MessageChannelLink }
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal shape of the integrations credentials service `resolve` we depend on.
|
|
3
|
+
* Declared locally so this helper compiles even when the integrations module is
|
|
4
|
+
* disabled in a downstream app.
|
|
5
|
+
*/
|
|
6
|
+
type CredentialsResolver = {
|
|
7
|
+
resolve: (
|
|
8
|
+
integrationId: string,
|
|
9
|
+
scope: { tenantId: string; organizationId: string; userId?: string | null },
|
|
10
|
+
) => Promise<Record<string, unknown> | null>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type OAuthClientCredentialsScope = {
|
|
14
|
+
tenantId: string
|
|
15
|
+
organizationId: string | null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Resolve a provider's tenant-level OAuth *client application* credentials
|
|
20
|
+
* (clientId / clientSecret / scopes) configured by an admin under the
|
|
21
|
+
* `channel_<provider>` integration in the Integrations UI.
|
|
22
|
+
*
|
|
23
|
+
* Why `channel_<provider>` and NOT `oauth_<provider>`: each provider package
|
|
24
|
+
* registers its OAuth client-credential fields on the `channel_<provider>`
|
|
25
|
+
* integration — that is the row the admin edits and the health check reads.
|
|
26
|
+
* Earlier code resolved a phantom `oauth_<provider>` id that nothing ever
|
|
27
|
+
* writes, so every connect / code-exchange / refresh failed with
|
|
28
|
+
* "Invalid … OAuth client credentials: expected string, received undefined"
|
|
29
|
+
* even while the integration showed as configured and healthy.
|
|
30
|
+
*
|
|
31
|
+
* Scoping: the client app config is stored at TENANT scope (`userId = null`),
|
|
32
|
+
* distinct from the per-user OAuth *tokens* that live under the SAME
|
|
33
|
+
* `channel_<provider>` id at USER scope. We therefore always resolve at
|
|
34
|
+
* `userId: null`, trying the caller's organization first and then the
|
|
35
|
+
* organization-agnostic (`organizationId: null`) row, so a single platform /
|
|
36
|
+
* tenant OAuth app can serve every organization (and so a config saved while
|
|
37
|
+
* the admin had no active organization is still found).
|
|
38
|
+
*
|
|
39
|
+
* Returns `null` when no usable client row exists — callers MUST surface an
|
|
40
|
+
* actionable "provider not configured" error instead of handing an empty
|
|
41
|
+
* object to the adapter.
|
|
42
|
+
*/
|
|
43
|
+
export async function resolveOAuthClientCredentials(
|
|
44
|
+
credentialsService: CredentialsResolver | null | undefined,
|
|
45
|
+
providerKey: string,
|
|
46
|
+
scope: OAuthClientCredentialsScope,
|
|
47
|
+
): Promise<Record<string, unknown> | null> {
|
|
48
|
+
if (!credentialsService) return null
|
|
49
|
+
const integrationId = `channel_${providerKey}`
|
|
50
|
+
const organizations: Array<string | null> = [scope.organizationId, null]
|
|
51
|
+
const tried = new Set<string | null>()
|
|
52
|
+
for (const organizationId of organizations) {
|
|
53
|
+
if (tried.has(organizationId)) continue
|
|
54
|
+
tried.add(organizationId)
|
|
55
|
+
let row: Record<string, unknown> | null = null
|
|
56
|
+
try {
|
|
57
|
+
// `organizationId` may be `null` to match the organization-agnostic row;
|
|
58
|
+
// the credentials filter translates `null` into a SQL `IS NULL` match.
|
|
59
|
+
row = await credentialsService.resolve(integrationId, {
|
|
60
|
+
tenantId: scope.tenantId,
|
|
61
|
+
organizationId: organizationId as unknown as string,
|
|
62
|
+
userId: null,
|
|
63
|
+
})
|
|
64
|
+
} catch (resolveErr) {
|
|
65
|
+
// A resolve error (e.g. a transient DB issue) for this org scope shouldn't
|
|
66
|
+
// abort the lookup — fall through to the next scope — but surface it so a
|
|
67
|
+
// real misconfiguration isn't silently swallowed.
|
|
68
|
+
console.warn(
|
|
69
|
+
'[internal] [communication_channels] resolveOAuthClientCredentials: credential resolve failed for an org scope:',
|
|
70
|
+
resolveErr instanceof Error ? resolveErr.message : resolveErr,
|
|
71
|
+
)
|
|
72
|
+
row = null
|
|
73
|
+
}
|
|
74
|
+
if (row && typeof row.clientId === 'string' && row.clientId.length > 0) {
|
|
75
|
+
return row
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return null
|
|
79
|
+
}
|