@protobase/server 0.68.5 → 0.69.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/better-auth/auth-settings.d.ts +155 -28
  2. package/dist/better-auth/auth-settings.js +3 -2
  3. package/dist/better-auth/connections/connection-account-hooks.js +62 -0
  4. package/dist/better-auth/connections/connection-events.d.ts +11 -0
  5. package/dist/better-auth/connections/connection-events.js +16 -0
  6. package/dist/better-auth/connections/connection-oauth-provider.js +129 -0
  7. package/dist/better-auth/connections/connections-plugin.js +98 -0
  8. package/dist/better-auth/connections/declared-connections.d.ts +21 -0
  9. package/dist/better-auth/connections/declared-connections.js +54 -0
  10. package/dist/better-auth/connections/revoke-token.js +34 -0
  11. package/dist/better-auth/create-auth.d.ts +162 -17
  12. package/dist/better-auth/create-auth.js +59 -5
  13. package/dist/better-auth/refresh-refusals.js +43 -0
  14. package/dist/better-auth/staff-checks.js +1 -0
  15. package/dist/create-admin.js +11 -1
  16. package/dist/forms/rate-limit.js +33 -11
  17. package/dist/forms/routes.js +1 -1
  18. package/dist/functions/verify-signature.js +4 -2
  19. package/dist/index.d.ts +2 -1
  20. package/dist/index.js +3 -2
  21. package/dist/locale/en.js +3 -0
  22. package/dist/locale/nl.js +3 -0
  23. package/dist/mcp/mcp-tools.d.ts +2 -1
  24. package/dist/mcp/mcp-tools.js +4 -2
  25. package/dist/notifications/history-routes.js +1 -1
  26. package/dist/package.js +1 -1
  27. package/dist/personal-mail/linked-accounts.js +17 -13
  28. package/dist/types.d.ts +2 -0
  29. package/dist/webhooks/attempt.js +2 -2
  30. package/dist/webhooks/table/webhook-table.js +1 -1
  31. package/dist/workflows/connections/connection-accounts.js +18 -0
  32. package/dist/workflows/connections/connection-consent.js +49 -0
  33. package/dist/workflows/connections/connection-provider.js +10 -0
  34. package/dist/workflows/connections/connection-tokens.js +54 -0
  35. package/dist/workflows/connections/connection-triggers.js +37 -0
  36. package/dist/workflows/connections/connection-wiring.js +73 -0
  37. package/dist/workflows/connections/enabler-authority.js +35 -0
  38. package/dist/workflows/connections/enabler-mailbox.js +15 -0
  39. package/dist/workflows/connections/single-flight.js +17 -0
  40. package/dist/workflows/hooks/hook-auth.js +40 -0
  41. package/dist/workflows/hooks/hook-event.js +51 -0
  42. package/dist/workflows/hooks/hook-paths.d.ts +16 -0
  43. package/dist/workflows/hooks/hook-paths.js +32 -0
  44. package/dist/workflows/hooks/hook-route.js +99 -0
  45. package/dist/workflows/hooks/hook-runs.js +32 -0
  46. package/dist/workflows/hooks/hook-secrets.js +42 -0
  47. package/dist/workflows/hooks/hook-store.js +35 -0
  48. package/dist/workflows/hooks/hooks-sql.js +20 -0
  49. package/dist/workflows/hooks/issue-body.js +21 -0
  50. package/dist/workflows/hooks/issue-route.js +23 -0
  51. package/dist/workflows/hooks/issue-webhook.js +46 -0
  52. package/dist/workflows/hooks/read-body.js +32 -0
  53. package/dist/workflows/may-update-workflow.js +26 -0
  54. package/dist/workflows/mcp/webhook-tools.d.ts +9 -0
  55. package/dist/workflows/mcp/webhook-tools.js +103 -0
  56. package/dist/workflows/mcp/workflow-instructions.js +1 -1
  57. package/dist/workflows/mcp/workflow-read-tools.d.ts +3 -0
  58. package/dist/workflows/plugin-records.js +1 -1
  59. package/dist/workflows/plugin-services.js +1 -1
  60. package/dist/workflows/ready/ready-start.js +1 -1
  61. package/dist/workflows/run-files.js +7 -1
  62. package/dist/workflows/save-hook.js +7 -2
  63. package/dist/workflows/schema-sql.js +17 -3
  64. package/dist/workflows/schema.js +3 -1
  65. package/dist/workflows/start-workflow.d.ts +3 -1
  66. package/dist/workflows/start-workflow.js +10 -4
  67. package/dist/workflows/triggers/delete-hook.js +15 -0
  68. package/dist/workflows/triggers/plugin-call.js +45 -0
  69. package/dist/workflows/triggers/poll-job.js +120 -0
  70. package/dist/workflows/triggers/poll-result.js +32 -0
  71. package/dist/workflows/triggers/poll-schedule.js +32 -0
  72. package/dist/workflows/triggers/trigger-claim.js +81 -0
  73. package/dist/workflows/triggers/trigger-jobs.js +53 -7
  74. package/dist/workflows/triggers/trigger-start.js +112 -0
  75. package/dist/workflows/triggers/trigger-sync.js +15 -10
  76. package/dist/workflows/workflow-engine.js +57 -9
  77. package/dist/workflows/workflow-types.d.ts +11 -4
  78. package/dist/workflows/workflow-types.js +21 -15
  79. package/dist/write-shared.js +1 -0
  80. package/package.json +6 -6
  81. package/dist/better-auth/end-replaced-session.d.ts +0 -17
package/dist/index.d.ts CHANGED
@@ -104,6 +104,7 @@ import { newWebhookSecret } from "./webhooks/standard-webhooks.js";
104
104
  import { checkWebhooksSchema, webhooksMigration } from "./webhooks/table/schema.js";
105
105
  import { checkWorkflowsSchema, workflowsMigration } from "./workflows/schema.js";
106
106
  import { StartWorkflowOptions, startWorkflow } from "./workflows/start-workflow.js";
107
+ import { hookPath, redactHookKeys, webhookIssuePath, webhookParams } from "./workflows/hooks/hook-paths.js";
107
108
  import { checkUserTablesSchema, userTablesMigration } from "./user-tables/schema.js";
108
109
  import { ownedDefinitionsMigration } from "./user-tables/owned-definitions.js";
109
110
  import { UserTables, userTableResources } from "./user-tables/user-table-resources.js";
@@ -196,4 +197,4 @@ import { DrainResult, JobsRuntime, StartJobsOptions, startJobs } from "./jobs/ru
196
197
  import { jobsDrainApp } from "./jobs/drain-route.js";
197
198
  import { checkJobsSchema, jobsSchema, jobsSchemaMigration } from "./jobs/schema.js";
198
199
  import { AdminAuth, CreateAuthOptions, UserCreatedHook, normalizeRoles } from "./better-auth/create-auth.js";
199
- export { type Admin, type AdminAuth, type AdminOptions, type AggregateParams, type ApiFunction, type ApiKeys, type ApiKeysOptions, type ApnsEnvironment, type AppIcons, type AppIdentity, type AppPoolConfig, type ApplyOptions, type ApplyResult, type ApprovalAnswer, type ApprovalRequest, type ArgumentsPayload, type AssistantBackend, type AssistantOptions, type AssistantSettings, type AssistantTool, type AuditEvent, type AuditQueue, type AuthHooks, type AuthSettings, type AuthenticateSettings, type Authenticator, type BaseToolContext, type BatchOperation, type BatchResult, type BetterAuthAuthenticator, type BetterAuthAuthenticatorOptions, type CallBackend, type CallMember, type Caller, type CallerOptions, type CallsAccess, type CallsOptions, type CapturedRow, type CascadingKey, type ChannelBroadcast, type ChannelSender, type ChatMessage, type ChatTextPart, type CheckAiInput, type CleanupEntry, type CleanupReport, type CleanupSchedule, type CoalescedChange, type CommentItem, type CommentToolsInput, type CommentUsers, type Completion, type CompletionOptions, ConnectionsBusy, type Conversation, type ConversationRecord, type ConversationStore, type ConversationsOptions, type CorsOptions, type CorsOrigins, type CreateAdminInput, type CreateAuthOptions, type DecryptField, type Device, type DeviceColumn, type DeviceKind, type DeviceStore, DeviceStoreError, type DrainResult, type EmailComponent, type EmailOptions, type EmailTemplate, type EncryptionStatus, type EncryptionTools, type EncryptionToolsInput, type EnqueueJob, type EnqueueOptions, type ErrorReporter, type ErrorReportingOptions, type ErrorReportingSettings, type EventData, type EventsOptions, FatalJobError, type FieldApplied, type FieldDecrypter, type FieldReader, type FieldStatus, type FileCleanupInput, type FileLimits, type FileObject, type FileProvider, type FileProviderSettings, type FileRef, type FileStore, type FileVariantObject, type FilesOptions, type FunctionApp, type FunctionCaller, type FunctionContext, type FunctionEnv, type FunctionHandler, type FunctionOptions, type FunctionRecords, type FunctionSocket, type FunctionSource, type HistoryColumn, type HistoryOptions, type HistoryPosition, type HistoryStore, HistoryStoreError, type HookContext, HttpProblem, type IssueTokenOptions, type JobContext, type JobDeclaration, type JobDefinition, type JobName, type JobOptions, type JobPayload, type JobRun, type JobsRuntime, type JsonObject, type JsonValue, type JwtOptions, type JwtVerifyOptions, type KeyFunctionContext, type LinkedAccount, type ListParams, type LiveKitBackend, type LocalFilesOptions, type MailAttachment, type MailMessage, MailServerTimeoutError, type Mailer, type MapLayer, type MapOptions, type MapSettings, type McpApprovals, type McpGateInput, type McpHandlerInput, type McpRoutesInput, type McpServerInput, type McpToolsInput, type McpWrites, type MemberInput, type Membership, MissingCredentials, type MobileConfig, ModelError, type ModelSettings, type NewDevice, type NewNotification, type NewOrganization, type NewUser, type NotificationRecipients, NotificationSendError, type NotificationsOptions, type OperatorProvider, type OrganizationRole, type OrganizationsOptions, type PdfComponent, type PdfOptions, type PdfTemplate, PersonalMailError, type PersonalMailErrorCode, type PersonalMailProviderId, type PgModule, type PipelineHook, type PlatformSignIn, type PluginOption, type PluginRef, type PngIcon, type ProtectedFunctionOptions, type PublicFunctionContext, type PublicFunctionEnv, type PublicFunctionHandler, type PublicFunctionOptions, type PushAnswer, type PushMessage, PushMessageError, type PushSender, type QueryOptions, type QueryResult, type ReadableResources, type RealtimeOptions, type RealtimePrune, type ReasoningDetail, type ReasoningEffort, type RebindOption, type RecordGroups, type RecordKey, type RecordKeyInput, type RecordPage, type RenderedEmail, type ResourceChange, type ResourceSource, type RoleDefinition, type RolesInput, type RunContext, type RuntimeOptions, type S3Bucket, type S3FilesOptions, type S3ListPage, type S3Object, type ScanGuardMode, type ScheduledDelete, type SendNotification, type Session, type SessionOrganization, type Sharp, type SignInPolicy, type SignInRule, type SignatureOptions, type SocketEvents, type SourceOption, type SourceRef, type StaffAccessRule, type StaffSignIn, type StandardSchema, type StandardWebhookOptions, type StartJobsOptions, type StartWorkflowOptions, type StatusOptions, type StoreFileInput, type StoredMember, type StoredNotification, type StoredObject, type StoredOrganization, type StoredUser, type StrayValues, type SubscriptionDefinition, type SubscriptionEvent, type SubscriptionOptions, type TenantValue, type ToolAnnotations, type ToolCall, type ToolComment, type ToolCommentPage, type ToolComments, type ToolContext, type ToolRecord, type ToolRecords, type ToolSpec, type TrustedApp, type TurnOptions, type UnreadableEntry, type Urgency, type User, type UserCreatedHook, type UserTables, type VapidSettings, type ViewSource, type WebPushFields, type WebPushOptions, type WebhooksOptions, type WidgetOptions, type WorkflowCatalog, type WorkflowTypes, type WorkflowsOptions, type WriteEvent, type WriteOperation, type WriteRecordToolsInput, addAppRoleToOwners, addCommentTool, addMember, apiKeyCaller, apiKeySettings, apiKeyVariables, appNameOver, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaDrift, authSchemaMigration, authStoreTables, backgroundSettled, badRequest, batchWriteTool, beforeSignInPluginsVariable, betterAuthTokens, callsMigration, capSocketMessages, changesMigration, channelSendsMigration, checkAi, checkAuthSchema, checkCalls, checkChanges, checkChannelSends, checkComments, checkComputed, checkJobsSchema, checkNotificationsSchema, checkQueryRunner, checkRealtimeRelay, checkResourcesOutsideAuthStore, checkSourceNames, checkStatusSchema, checkUserTablesSchema, checkViewQueries, checkWebhooksSchema, checkWorkflowsSchema, coalesce, commentTools, commentsMigration, computedMigration, configExports, consoleAuditQueue, conversationKey, countRecordsTool, createAdmin, createConversations, createEncryptionTools, createFileCleanup, createOrganization, createRecordTool, createUser, defaultAppName, defaultHistoryPage, defaultSignInPolicy, defineEmail, defineFunction, defineJob, definePdf, defineTool, deleteRecordTool, deleteUser, describeResourceTool, deviceIdParam, deviceStore, devicesPath, enqueueJob, errorReportingSettings, errorReportingVariable, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, formatUri, functionApp, generateVapidKeys, getRecordTool, goTruePath, handleDroppedConnections, hasUsers, historyIdParam, historyIdPattern, historyLimits, historyPath, historyStore, honorSocketMessageCaps, imageSize, isBackgroundWork, isCaptured, isJobDefinition, isOAuthWellKnownPath, isSchedule, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, jwtVerifier, limitedFileStore, listCommentsTool, listOrganizations, listRecordsTool, listResourcesTool, listUsers, localFileStore, longQueryTimeoutMs, mapSettings, mapStyleUrl, mapStyleVariable, maxDevicesPerUser, maxHistoryPage, maxMessageBytes, maxRecipients, maxTokenTtlSeconds, maxTtlSeconds, maxUnreadCount, maxWidgetPropsBytes, mcpApprovals, mcpGate, mcpHandler, mcpIcons, mcpInstructions, mcpRoutes, mcpServer, mcpToolContext, mcpTools, migrationCommand, newWebhookSecret, normalizeRoles, notFound, notificationSenders, notificationsMigration, oauthWellKnownPaths, objectKey, onDoNotDisturb, ownedDefinitionsMigration, parseColumns, parseRoles, passwordLength, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, pngIcons, poolWaitMs, postgresDeviceStore, postgresHistoryStore, processor, pushMessage, queryRunnerMigration, readDbConnections, readDefinitionsVersion, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readRecordTools, readVapidSettings, readWriteQueryTool, readableResources, realtimeLimits, realtimeRelayMigration, recordOf, refusalOf, refused, relayed, reloadingAdmin, removeDevicesWithUsers, removeHistoryWithUsers, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runInBackground, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, seesAssistant, seesRuntime, sendNotification, sessionOf, setMemberRoles, setUserBanned, setUserRole, shownAppName, signInPolicyMethods, socketMessageCapOf, sourceOptions, sourcesFor, sourcesVariable, sqlSends, startErrorReporting, startJobs, startWorkflow, statusMigration, statusNames, storageCleanupSchedule, storageProvider, storeFile, storeUsage, streamChatCompletion, subscription, supabasePath, thumbHash, unauthorized, updateRecordTool, urgencies, userTableResources, userTablesMigration, vapidPrivateKeyVariable, vapidPublicKeyVariable, vapidSubjectVariable, variantPath, verifySignature, verifyStandardWebhook, waitUntil, wantsLongQueries, webManifestPath, webPushSender, webauthnPath, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps, workflowInstructions, workflowReadTools, workflowTypes, workflowWriteTools, workflowsMigration, writeRecordTools };
200
+ export { type Admin, type AdminAuth, type AdminOptions, type AggregateParams, type ApiFunction, type ApiKeys, type ApiKeysOptions, type ApnsEnvironment, type AppIcons, type AppIdentity, type AppPoolConfig, type ApplyOptions, type ApplyResult, type ApprovalAnswer, type ApprovalRequest, type ArgumentsPayload, type AssistantBackend, type AssistantOptions, type AssistantSettings, type AssistantTool, type AuditEvent, type AuditQueue, type AuthHooks, type AuthSettings, type AuthenticateSettings, type Authenticator, type BaseToolContext, type BatchOperation, type BatchResult, type BetterAuthAuthenticator, type BetterAuthAuthenticatorOptions, type CallBackend, type CallMember, type Caller, type CallerOptions, type CallsAccess, type CallsOptions, type CapturedRow, type CascadingKey, type ChannelBroadcast, type ChannelSender, type ChatMessage, type ChatTextPart, type CheckAiInput, type CleanupEntry, type CleanupReport, type CleanupSchedule, type CoalescedChange, type CommentItem, type CommentToolsInput, type CommentUsers, type Completion, type CompletionOptions, ConnectionsBusy, type Conversation, type ConversationRecord, type ConversationStore, type ConversationsOptions, type CorsOptions, type CorsOrigins, type CreateAdminInput, type CreateAuthOptions, type DecryptField, type Device, type DeviceColumn, type DeviceKind, type DeviceStore, DeviceStoreError, type DrainResult, type EmailComponent, type EmailOptions, type EmailTemplate, type EncryptionStatus, type EncryptionTools, type EncryptionToolsInput, type EnqueueJob, type EnqueueOptions, type ErrorReporter, type ErrorReportingOptions, type ErrorReportingSettings, type EventData, type EventsOptions, FatalJobError, type FieldApplied, type FieldDecrypter, type FieldReader, type FieldStatus, type FileCleanupInput, type FileLimits, type FileObject, type FileProvider, type FileProviderSettings, type FileRef, type FileStore, type FileVariantObject, type FilesOptions, type FunctionApp, type FunctionCaller, type FunctionContext, type FunctionEnv, type FunctionHandler, type FunctionOptions, type FunctionRecords, type FunctionSocket, type FunctionSource, type HistoryColumn, type HistoryOptions, type HistoryPosition, type HistoryStore, HistoryStoreError, type HookContext, HttpProblem, type IssueTokenOptions, type JobContext, type JobDeclaration, type JobDefinition, type JobName, type JobOptions, type JobPayload, type JobRun, type JobsRuntime, type JsonObject, type JsonValue, type JwtOptions, type JwtVerifyOptions, type KeyFunctionContext, type LinkedAccount, type ListParams, type LiveKitBackend, type LocalFilesOptions, type MailAttachment, type MailMessage, MailServerTimeoutError, type Mailer, type MapLayer, type MapOptions, type MapSettings, type McpApprovals, type McpGateInput, type McpHandlerInput, type McpRoutesInput, type McpServerInput, type McpToolsInput, type McpWrites, type MemberInput, type Membership, MissingCredentials, type MobileConfig, ModelError, type ModelSettings, type NewDevice, type NewNotification, type NewOrganization, type NewUser, type NotificationRecipients, NotificationSendError, type NotificationsOptions, type OperatorProvider, type OrganizationRole, type OrganizationsOptions, type PdfComponent, type PdfOptions, type PdfTemplate, PersonalMailError, type PersonalMailErrorCode, type PersonalMailProviderId, type PgModule, type PipelineHook, type PlatformSignIn, type PluginOption, type PluginRef, type PngIcon, type ProtectedFunctionOptions, type PublicFunctionContext, type PublicFunctionEnv, type PublicFunctionHandler, type PublicFunctionOptions, type PushAnswer, type PushMessage, PushMessageError, type PushSender, type QueryOptions, type QueryResult, type ReadableResources, type RealtimeOptions, type RealtimePrune, type ReasoningDetail, type ReasoningEffort, type RebindOption, type RecordGroups, type RecordKey, type RecordKeyInput, type RecordPage, type RenderedEmail, type ResourceChange, type ResourceSource, type RoleDefinition, type RolesInput, type RunContext, type RuntimeOptions, type S3Bucket, type S3FilesOptions, type S3ListPage, type S3Object, type ScanGuardMode, type ScheduledDelete, type SendNotification, type Session, type SessionOrganization, type Sharp, type SignInPolicy, type SignInRule, type SignatureOptions, type SocketEvents, type SourceOption, type SourceRef, type StaffAccessRule, type StaffSignIn, type StandardSchema, type StandardWebhookOptions, type StartJobsOptions, type StartWorkflowOptions, type StatusOptions, type StoreFileInput, type StoredMember, type StoredNotification, type StoredObject, type StoredOrganization, type StoredUser, type StrayValues, type SubscriptionDefinition, type SubscriptionEvent, type SubscriptionOptions, type TenantValue, type ToolAnnotations, type ToolCall, type ToolComment, type ToolCommentPage, type ToolComments, type ToolContext, type ToolRecord, type ToolRecords, type ToolSpec, type TrustedApp, type TurnOptions, type UnreadableEntry, type Urgency, type User, type UserCreatedHook, type UserTables, type VapidSettings, type ViewSource, type WebPushFields, type WebPushOptions, type WebhooksOptions, type WidgetOptions, type WorkflowCatalog, type WorkflowTypes, type WorkflowsOptions, type WriteEvent, type WriteOperation, type WriteRecordToolsInput, addAppRoleToOwners, addCommentTool, addMember, apiKeyCaller, apiKeySettings, apiKeyVariables, appNameOver, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaDrift, authSchemaMigration, authStoreTables, backgroundSettled, badRequest, batchWriteTool, beforeSignInPluginsVariable, betterAuthTokens, callsMigration, capSocketMessages, changesMigration, channelSendsMigration, checkAi, checkAuthSchema, checkCalls, checkChanges, checkChannelSends, checkComments, checkComputed, checkJobsSchema, checkNotificationsSchema, checkQueryRunner, checkRealtimeRelay, checkResourcesOutsideAuthStore, checkSourceNames, checkStatusSchema, checkUserTablesSchema, checkViewQueries, checkWebhooksSchema, checkWorkflowsSchema, coalesce, commentTools, commentsMigration, computedMigration, configExports, consoleAuditQueue, conversationKey, countRecordsTool, createAdmin, createConversations, createEncryptionTools, createFileCleanup, createOrganization, createRecordTool, createUser, defaultAppName, defaultHistoryPage, defaultSignInPolicy, defineEmail, defineFunction, defineJob, definePdf, defineTool, deleteRecordTool, deleteUser, describeResourceTool, deviceIdParam, deviceStore, devicesPath, enqueueJob, errorReportingSettings, errorReportingVariable, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, formatUri, functionApp, generateVapidKeys, getRecordTool, goTruePath, handleDroppedConnections, hasUsers, historyIdParam, historyIdPattern, historyLimits, historyPath, historyStore, honorSocketMessageCaps, hookPath, imageSize, isBackgroundWork, isCaptured, isJobDefinition, isOAuthWellKnownPath, isSchedule, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, jwtVerifier, limitedFileStore, listCommentsTool, listOrganizations, listRecordsTool, listResourcesTool, listUsers, localFileStore, longQueryTimeoutMs, mapSettings, mapStyleUrl, mapStyleVariable, maxDevicesPerUser, maxHistoryPage, maxMessageBytes, maxRecipients, maxTokenTtlSeconds, maxTtlSeconds, maxUnreadCount, maxWidgetPropsBytes, mcpApprovals, mcpGate, mcpHandler, mcpIcons, mcpInstructions, mcpRoutes, mcpServer, mcpToolContext, mcpTools, migrationCommand, newWebhookSecret, normalizeRoles, notFound, notificationSenders, notificationsMigration, oauthWellKnownPaths, objectKey, onDoNotDisturb, ownedDefinitionsMigration, parseColumns, parseRoles, passwordLength, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, pngIcons, poolWaitMs, postgresDeviceStore, postgresHistoryStore, processor, pushMessage, queryRunnerMigration, readDbConnections, readDefinitionsVersion, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readRecordTools, readVapidSettings, readWriteQueryTool, readableResources, realtimeLimits, realtimeRelayMigration, recordOf, redactHookKeys, refusalOf, refused, relayed, reloadingAdmin, removeDevicesWithUsers, removeHistoryWithUsers, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runInBackground, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, seesAssistant, seesRuntime, sendNotification, sessionOf, setMemberRoles, setUserBanned, setUserRole, shownAppName, signInPolicyMethods, socketMessageCapOf, sourceOptions, sourcesFor, sourcesVariable, sqlSends, startErrorReporting, startJobs, startWorkflow, statusMigration, statusNames, storageCleanupSchedule, storageProvider, storeFile, storeUsage, streamChatCompletion, subscription, supabasePath, thumbHash, unauthorized, updateRecordTool, urgencies, userTableResources, userTablesMigration, vapidPrivateKeyVariable, vapidPublicKeyVariable, vapidSubjectVariable, variantPath, verifySignature, verifyStandardWebhook, waitUntil, wantsLongQueries, webManifestPath, webPushSender, webauthnPath, webhookIssuePath, webhookParams, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps, workflowInstructions, workflowReadTools, workflowTypes, workflowWriteTools, workflowsMigration, writeRecordTools };
package/dist/index.js CHANGED
@@ -68,6 +68,8 @@ import { recordOf, resourceChange } from "./changes/resource-change.js";
68
68
  import { defineFunction } from "./functions/define-function.js";
69
69
  import { newWebhookSecret } from "./webhooks/standard-webhooks.js";
70
70
  import { subscription } from "./webhooks/subscription.js";
71
+ import { verifySignature, verifyStandardWebhook } from "./functions/verify-signature.js";
72
+ import { hookPath, redactHookKeys, webhookIssuePath, webhookParams } from "./workflows/hooks/hook-paths.js";
71
73
  import { startWorkflow } from "./workflows/start-workflow.js";
72
74
  import { workflowTypes } from "./workflows/workflow-types.js";
73
75
  import { historyIdPattern, historyLimits } from "./notifications/history.js";
@@ -135,7 +137,6 @@ import { checkQueryRunner, queryRunnerMigration } from "./assistant/query-runner
135
137
  import { startErrorReporting } from "./error-reporting/error-reporter.js";
136
138
  import { functionApp } from "./functions/function-app.js";
137
139
  import { readJson } from "./functions/read-json.js";
138
- import { verifySignature, verifyStandardWebhook } from "./functions/verify-signature.js";
139
140
  import { requireEnv } from "./functions/require-env.js";
140
141
  import { storeFile } from "./files/store-file.js";
141
142
  import { createFileCleanup } from "./files/file-cleanup.js";
@@ -146,4 +147,4 @@ import { defineEmail } from "./mail/define-email.js";
146
147
  import { definePdf } from "./pdf/define-pdf.js";
147
148
  import { startJobs } from "./jobs/runtime.js";
148
149
  import { jobsDrainApp } from "./jobs/drain-route.js";
149
- export { ConnectionsBusy, DeviceStoreError, FatalJobError, HistoryStoreError, HttpProblem, MailServerTimeoutError, MissingCredentials, ModelError, NotificationSendError, PersonalMailError, PushMessageError, addAppRoleToOwners, addCommentTool, addMember, apiKeyCaller, apiKeySettings, apiKeyVariables, appNameOver, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaDrift, authSchemaMigration, authStoreTables, backgroundSettled, badRequest, batchWriteTool, beforeSignInPluginsVariable, betterAuthTokens, callsMigration, capSocketMessages, changesMigration, channelSendsMigration, checkAi, checkAuthSchema, checkCalls, checkChanges, checkChannelSends, checkComments, checkComputed, checkJobsSchema, checkNotificationsSchema, checkQueryRunner, checkRealtimeRelay, checkResourcesOutsideAuthStore, checkSourceNames, checkStatusSchema, checkUserTablesSchema, checkViewQueries, checkWebhooksSchema, checkWorkflowsSchema, coalesce, commentTools, commentsMigration, computedMigration, configExports, consoleAuditQueue, conversationKey, countRecordsTool, createAdmin, createConversations, createEncryptionTools, createFileCleanup, createOrganization, createRecordTool, createUser, defaultAppName, defaultHistoryPage, defaultSignInPolicy, defineEmail, defineFunction, defineJob, definePdf, defineTool, deleteRecordTool, deleteUser, describeResourceTool, deviceIdParam, deviceStore, devicesPath, enqueueJob, errorReportingSettings, errorReportingVariable, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, formatUri, functionApp, generateVapidKeys, getRecordTool, goTruePath, handleDroppedConnections, hasUsers, historyIdParam, historyIdPattern, historyLimits, historyPath, historyStore, honorSocketMessageCaps, imageSize, isBackgroundWork, isCaptured, isJobDefinition, isOAuthWellKnownPath, isSchedule, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, jwtVerifier, limitedFileStore, listCommentsTool, listOrganizations, listRecordsTool, listResourcesTool, listUsers, localFileStore, longQueryTimeoutMs, mapSettings, mapStyleUrl, mapStyleVariable, maxDevicesPerUser, maxHistoryPage, maxMessageBytes, maxRecipients, maxTokenTtlSeconds, maxTtlSeconds, maxUnreadCount, maxWidgetPropsBytes, mcpApprovals, mcpGate, mcpHandler, mcpIcons, mcpInstructions, mcpRoutes, mcpServer, mcpToolContext, mcpTools, migrationCommand, newWebhookSecret, normalizeRoles, notFound, notificationSenders, notificationsMigration, oauthWellKnownPaths, objectKey, onDoNotDisturb, ownedDefinitionsMigration, parseColumns, parseRoles, passwordLength, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, pngIcons, poolWaitMs, postgresDeviceStore, postgresHistoryStore, processor, pushMessage, queryRunnerMigration, readDbConnections, readDefinitionsVersion, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readRecordTools, readVapidSettings, readWriteQueryTool, readableResources, realtimeLimits, realtimeRelayMigration, recordOf, refusalOf, refused, relayed, reloadingAdmin, removeDevicesWithUsers, removeHistoryWithUsers, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runInBackground, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, seesAssistant, seesRuntime, sendNotification, sessionOf, setMemberRoles, setUserBanned, setUserRole, shownAppName, signInPolicyMethods, socketMessageCapOf, sourceOptions, sourcesFor, sourcesVariable, sqlSends, startErrorReporting, startJobs, startWorkflow, statusMigration, statusNames, storageCleanupSchedule, storageProvider, storeFile, storeUsage, streamChatCompletion, subscription, supabasePath, thumbHash, unauthorized, updateRecordTool, urgencies, userTableResources, userTablesMigration, vapidPrivateKeyVariable, vapidPublicKeyVariable, vapidSubjectVariable, variantPath, verifySignature, verifyStandardWebhook, waitUntil, wantsLongQueries, webManifestPath, webPushSender, webauthnPath, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps, workflowInstructions, workflowReadTools, workflowTypes, workflowWriteTools, workflowsMigration, writeRecordTools };
150
+ export { ConnectionsBusy, DeviceStoreError, FatalJobError, HistoryStoreError, HttpProblem, MailServerTimeoutError, MissingCredentials, ModelError, NotificationSendError, PersonalMailError, PushMessageError, addAppRoleToOwners, addCommentTool, addMember, apiKeyCaller, apiKeySettings, apiKeyVariables, appNameOver, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaDrift, authSchemaMigration, authStoreTables, backgroundSettled, badRequest, batchWriteTool, beforeSignInPluginsVariable, betterAuthTokens, callsMigration, capSocketMessages, changesMigration, channelSendsMigration, checkAi, checkAuthSchema, checkCalls, checkChanges, checkChannelSends, checkComments, checkComputed, checkJobsSchema, checkNotificationsSchema, checkQueryRunner, checkRealtimeRelay, checkResourcesOutsideAuthStore, checkSourceNames, checkStatusSchema, checkUserTablesSchema, checkViewQueries, checkWebhooksSchema, checkWorkflowsSchema, coalesce, commentTools, commentsMigration, computedMigration, configExports, consoleAuditQueue, conversationKey, countRecordsTool, createAdmin, createConversations, createEncryptionTools, createFileCleanup, createOrganization, createRecordTool, createUser, defaultAppName, defaultHistoryPage, defaultSignInPolicy, defineEmail, defineFunction, defineJob, definePdf, defineTool, deleteRecordTool, deleteUser, describeResourceTool, deviceIdParam, deviceStore, devicesPath, enqueueJob, errorReportingSettings, errorReportingVariable, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, formatUri, functionApp, generateVapidKeys, getRecordTool, goTruePath, handleDroppedConnections, hasUsers, historyIdParam, historyIdPattern, historyLimits, historyPath, historyStore, honorSocketMessageCaps, hookPath, imageSize, isBackgroundWork, isCaptured, isJobDefinition, isOAuthWellKnownPath, isSchedule, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, jwtVerifier, limitedFileStore, listCommentsTool, listOrganizations, listRecordsTool, listResourcesTool, listUsers, localFileStore, longQueryTimeoutMs, mapSettings, mapStyleUrl, mapStyleVariable, maxDevicesPerUser, maxHistoryPage, maxMessageBytes, maxRecipients, maxTokenTtlSeconds, maxTtlSeconds, maxUnreadCount, maxWidgetPropsBytes, mcpApprovals, mcpGate, mcpHandler, mcpIcons, mcpInstructions, mcpRoutes, mcpServer, mcpToolContext, mcpTools, migrationCommand, newWebhookSecret, normalizeRoles, notFound, notificationSenders, notificationsMigration, oauthWellKnownPaths, objectKey, onDoNotDisturb, ownedDefinitionsMigration, parseColumns, parseRoles, passwordLength, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, pngIcons, poolWaitMs, postgresDeviceStore, postgresHistoryStore, processor, pushMessage, queryRunnerMigration, readDbConnections, readDefinitionsVersion, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readRecordTools, readVapidSettings, readWriteQueryTool, readableResources, realtimeLimits, realtimeRelayMigration, recordOf, redactHookKeys, refusalOf, refused, relayed, reloadingAdmin, removeDevicesWithUsers, removeHistoryWithUsers, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runInBackground, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, seesAssistant, seesRuntime, sendNotification, sessionOf, setMemberRoles, setUserBanned, setUserRole, shownAppName, signInPolicyMethods, socketMessageCapOf, sourceOptions, sourcesFor, sourcesVariable, sqlSends, startErrorReporting, startJobs, startWorkflow, statusMigration, statusNames, storageCleanupSchedule, storageProvider, storeFile, storeUsage, streamChatCompletion, subscription, supabasePath, thumbHash, unauthorized, updateRecordTool, urgencies, userTableResources, userTablesMigration, vapidPrivateKeyVariable, vapidPublicKeyVariable, vapidSubjectVariable, variantPath, verifySignature, verifyStandardWebhook, waitUntil, wantsLongQueries, webManifestPath, webPushSender, webauthnPath, webhookIssuePath, webhookParams, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps, workflowInstructions, workflowReadTools, workflowTypes, workflowWriteTools, workflowsMigration, writeRecordTools };
package/dist/locale/en.js CHANGED
@@ -26,6 +26,9 @@ var en_default = {
26
26
  "hidden": "hidden",
27
27
  "yes": "Yes",
28
28
  "no": "No",
29
+ "webhookTitle": "Issue a new webhook URL for {record}?",
30
+ "webhookBody": "Its old URL stops at once, and the new URL and secret are shown to this client once.",
31
+ "issue": "Issue",
29
32
  "createNamedAction": "Create {resource} \"{title}\""
30
33
  },
31
34
  "mcp/approval": {
package/dist/locale/nl.js CHANGED
@@ -26,6 +26,9 @@ var nl_default = {
26
26
  "hidden": "verborgen",
27
27
  "yes": "Ja",
28
28
  "no": "Nee",
29
+ "webhookTitle": "Een nieuwe webhook-URL uitgeven voor {record}?",
30
+ "webhookBody": "De oude URL werkt meteen niet meer, en de nieuwe URL en het geheim worden eenmalig aan deze client getoond.",
31
+ "issue": "Uitgeven",
29
32
  "createNamedAction": "{resource} \"{title}\" aanmaken"
30
33
  },
31
34
  "mcp/approval": {
@@ -21,7 +21,8 @@ export type McpToolsInput = {
21
21
  commented?: string[];
22
22
  /**
23
23
  * Offers the workflow tools, for an app with `workflows`: reading workflows, their types and runs, and checking
24
- * definitions against the app's types; with writes on, `create_workflow`, `update_workflow` and `run_workflow` too.
24
+ * definitions against the app's types; with writes on, `create_workflow`, `update_workflow`, `run_workflow`,
25
+ * `enable_workflow` and `new_webhook_url` too.
25
26
  * Off by default.
26
27
  */
27
28
  workflows?: WorkflowCatalog;
@@ -7,6 +7,7 @@ import { batchWriteTool } from "../assistant/batch-write-tool.js";
7
7
  import { commentTools } from "../assistant/comment-tools.js";
8
8
  import { workflowReadTools } from "../workflows/mcp/workflow-read-tools.js";
9
9
  import { workflowWriteTools } from "../workflows/mcp/workflow-write-tools.js";
10
+ import { webhookTools } from "../workflows/mcp/webhook-tools.js";
10
11
  //#region src/mcp/mcp-tools.ts
11
12
  const writeTools = (input) => [...writeRecordTools(input), batchWriteTool(input)];
12
13
  const builtInNames = (db) => new Set([
@@ -21,7 +22,8 @@ const builtInNames = (db) => new Set([
21
22
  triggers: {}
22
23
  })
23
24
  }),
24
- ...workflowWriteTools()
25
+ ...workflowWriteTools(),
26
+ ...webhookTools({}, () => Promise.reject(/* @__PURE__ */ new Error("Not called")))
25
27
  ].map((tool) => tool.name));
26
28
  /** The app's own tools; one whose name is taken twice, or by a built-in tool, stops the server at startup. */
27
29
  const checkedOwnTools = (db, own) => {
@@ -43,7 +45,7 @@ const mcpTools = ({ db, sql, writes, commented = [], workflows, own = [] }) => {
43
45
  const limits = writes === false ? false : writes === true || !writes.resources ? {} : { resources: writes.resources };
44
46
  const write = limits === false ? [] : writeTools(limits);
45
47
  const comments = commentTools({ commented }, limits);
46
- const flows = workflows ? [...workflowReadTools(workflows), ...limits === false ? [] : workflowWriteTools(limits)] : [];
48
+ const flows = workflows ? [...workflowReadTools(workflows), ...limits === false ? [] : [...workflowWriteTools(limits), ...webhookTools(limits, workflows.issueWebhook)]] : [];
47
49
  const query = sql ? [readOnlyQueryTool(db)] : [];
48
50
  return (session) => [
49
51
  ...read,
@@ -1,7 +1,7 @@
1
1
  import { badRequest } from "../problem.js";
2
+ import { ownSignInUser } from "./caller.js";
2
3
  import { historyIdPattern } from "./history.js";
3
4
  import "./history-store.js";
4
- import { ownSignInUser } from "./caller.js";
5
5
  import { historyCursor, historyPosition } from "./history-cursor.js";
6
6
  import { jsonBody, jsonOnly, limitedBody, queryOf } from "./history-request.js";
7
7
  import { historyUrl } from "./history-url.js";
package/dist/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.68.5";
2
+ var version = "0.69.0";
3
3
  //#endregion
4
4
  export { version };
@@ -1,3 +1,4 @@
1
+ import { watchRefusals } from "../better-auth/refresh-refusals.js";
1
2
  import { PersonalMailError } from "./personal-mail-error.js";
2
3
  import { APIError } from "better-auth/api";
3
4
  //#region src/personal-mail/linked-accounts.ts
@@ -5,7 +6,7 @@ const storedScopes = (scope) => (scope ?? "").split(/[\s,]+/).filter(Boolean);
5
6
  /**
6
7
  * The accounts Better Auth keeps, with the tokens it stores, decrypted when `encryptOAuthTokens` is on. Its
7
8
  * `getAccessToken` refreshes a token within five seconds of expiry with the provider's refresh token and stores the new
8
- * one; when the provider refuses the refresh, as for a revoked grant, that is `link_expired`.
9
+ * one; when the provider refuses the refresh token, as for a revoked grant, that is `link_expired`.
9
10
  */
10
11
  const betterAuthLinkedAccounts = (auth) => ({
11
12
  accounts: async (userId) => {
@@ -17,21 +18,24 @@ const betterAuthLinkedAccounts = (auth) => ({
17
18
  }));
18
19
  },
19
20
  accessToken: async (userId, account, provider) => {
20
- const accessToken = await validAccessToken(auth, userId, account.id, provider);
21
+ const accessToken = await accountAccessToken(auth, userId, account.id);
21
22
  if (!accessToken) throw new PersonalMailError("link_expired", provider);
22
23
  return accessToken;
23
24
  }
24
25
  });
25
- const validAccessToken = async (auth, userId, accountId, provider) => {
26
- try {
27
- return (await auth.api.getAccessToken({ body: {
28
- accountId,
29
- userId
30
- } })).accessToken;
31
- } catch (error) {
32
- if (error instanceof APIError && error.body?.code === "FAILED_TO_GET_ACCESS_TOKEN") throw new PersonalMailError("link_expired", provider);
33
- throw error;
34
- }
26
+ /**
27
+ * A valid access token of the account, refreshed first when it expired; undefined when the provider refused the refresh
28
+ * token (`invalid_grant`) or the account holds none. A refresh that failed otherwise, as when the provider was down,
29
+ * throws, so the caller tries again later rather than asking the person to connect the account again.
30
+ */
31
+ const accountAccessToken = async (auth, userId, accountId) => {
32
+ const answer = await watchRefusals(() => auth.api.getAccessToken({ body: {
33
+ accountId,
34
+ userId
35
+ } }));
36
+ if ("value" in answer) return answer.value.accessToken || void 0;
37
+ if (answer.refused && answer.error instanceof APIError && answer.error.body?.code === "FAILED_TO_GET_ACCESS_TOKEN") return;
38
+ throw answer.error;
35
39
  };
36
40
  //#endregion
37
- export { betterAuthLinkedAccounts, storedScopes };
41
+ export { accountAccessToken, betterAuthLinkedAccounts, storedScopes };
package/dist/types.d.ts CHANGED
@@ -76,6 +76,8 @@ export type WriteEvent = {
76
76
  tenant?: TenantValue;
77
77
  /** The writer is a visitor without a token, through a public form. */
78
78
  anonymous?: true;
79
+ /** The writer's session as the API resolved it, for a hook that allows a write only for someone signed in as themselves (`ownSignInUser`). */
80
+ session: Session;
79
81
  before?: Row;
80
82
  after?: Row;
81
83
  };
@@ -1,9 +1,9 @@
1
+ import { AddressRefusedError, pinnedRequest } from "./table/pinned-request.js";
2
+ import { readLimited } from "./table/read-limited.js";
1
3
  import { invokeFunction } from "../functions/define-function.js";
2
4
  import { retryAfterMs } from "./retry-schedule.js";
3
5
  import { standardWebhookHeaders } from "./standard-webhooks.js";
4
6
  import { subscriberSession } from "./subscriber-view.js";
5
- import { AddressRefusedError, pinnedRequest } from "./table/pinned-request.js";
6
- import { readLimited } from "./table/read-limited.js";
7
7
  //#region src/webhooks/attempt.ts
8
8
  /** How long a receiver has to answer. */
9
9
  const attemptTimeoutMs = 15e3;
@@ -1,5 +1,5 @@
1
- import { betterAuthOwnerRoles } from "./owner-roles.js";
2
1
  import { checkWebhookUrl } from "./address-check.js";
2
+ import { betterAuthOwnerRoles } from "./owner-roles.js";
3
3
  import { webhooksSchema } from "./options.js";
4
4
  import { tableSubscriptions } from "./table-subscriptions.js";
5
5
  //#region src/webhooks/table/webhook-table.ts
@@ -0,0 +1,18 @@
1
+ import { accountAccessToken, storedScopes } from "../../personal-mail/linked-accounts.js";
2
+ //#region src/workflows/connections/connection-accounts.ts
3
+ /** The accounts Better Auth keeps, read through its internal adapter, with its refreshing `getAccessToken`. */
4
+ const betterAuthConnectionAccounts = (auth) => ({
5
+ find: async (userId, accountId) => {
6
+ const account = (await (await auth.$context).internalAdapter.findAccounts(userId)).find((candidate) => candidate.id === accountId);
7
+ if (!account) return;
8
+ return {
9
+ id: account.id,
10
+ providerId: account.providerId,
11
+ scopes: storedScopes(account.scope),
12
+ refreshToken: account.refreshToken ?? null
13
+ };
14
+ },
15
+ accessToken: (userId, accountId) => accountAccessToken(auth, userId, accountId)
16
+ });
17
+ //#endregion
18
+ export { betterAuthConnectionAccounts };
@@ -0,0 +1,49 @@
1
+ import { badRequest, forbidden } from "../../problem.js";
2
+ import { connectionProviderId, holdsScopes } from "./connection-provider.js";
3
+ import { ownSignInUser } from "../../notifications/caller.js";
4
+ import { sql } from "kysely";
5
+ import { usedConnections } from "@protobase/workflows";
6
+ //#region src/workflows/connections/connection-consent.ts
7
+ /** The refusal of every account the caller may not turn on, the same whether it exists or not. */
8
+ const notOwnConnection = () => forbidden("own-sign-in-only", "Only the owner of each connection this workflow uses, signed in as themselves, can turn it on: turn it off to save it, and its owner turns it on again in the admin app");
9
+ /**
10
+ * Turning a version on is the consent that lets its runs sign in through the accounts it binds: it is refused unless
11
+ * the caller is signed in as themselves (not staff signed in as them, nor an outside app acting for them) and owns
12
+ * every bound account, of its connection's provider and holding its scopes. Returns who consented, which the version
13
+ * keeps as `enabled_by`: the caller when they are signed in as themselves, else nobody, as for a version that binds
14
+ * no account turned on by an outside app.
15
+ */
16
+ const connectionConsent = ({ accounts, declarationOf, connectionOf }) => async (session, definition) => {
17
+ const keys = [...usedConnections(definition, connectionOf)];
18
+ if (keys.length === 0) {
19
+ if (session.anonymous || session.client || session.signIn?.staff !== false) return null;
20
+ return String(session.user.id);
21
+ }
22
+ const unbound = keys.filter((key) => !definition.connections?.[key]);
23
+ if (unbound.length > 0) throw badRequest("invalid-workflow", `Connect an account for ${unbound.join(", ")} in the workflow's connections before turning it on`);
24
+ const userId = ownSignInUser(session, "turn on a workflow that signs in through their connection");
25
+ for (const key of keys) {
26
+ const declaration = declarationOf(key);
27
+ const [plugin, name] = key.split(":");
28
+ const account = await accounts.find(userId, definition.connections[key]);
29
+ if (!account || account.providerId !== connectionProviderId(plugin, name, declaration) || !holdsScopes(account.scopes, declaration)) throw notOwnConnection();
30
+ }
31
+ return userId;
32
+ };
33
+ /**
34
+ * Keeps who turned a version on, as its runs' steps read it, and withdraws the consent when the workflow is turned
35
+ * off: every version's runs then sign in through no account until it is turned on again. Someone else turning a
36
+ * version on withdraws the earlier person's consent on every other version, so a waiting run of one of them signs in
37
+ * through no account of theirs; the earlier versions the same person turned on keep it.
38
+ */
39
+ const saveConsent = async (trx, schema, workflowId, enabled, enabledBy) => {
40
+ const versions = sql.id(schema, "versions");
41
+ if (enabled === null) {
42
+ await sql`update ${versions} set enabled_by = null where workflow_id = ${workflowId} and enabled_by is not null`.execute(trx);
43
+ return;
44
+ }
45
+ await sql`update ${versions} set enabled_by = null where workflow_id = ${workflowId} and version <> ${enabled} and enabled_by is distinct from ${enabledBy}`.execute(trx);
46
+ await sql`update ${versions} set enabled_by = ${enabledBy} where workflow_id = ${workflowId} and version = ${enabled}`.execute(trx);
47
+ };
48
+ //#endregion
49
+ export { connectionConsent, notOwnConnection, saveConsent };
@@ -0,0 +1,10 @@
1
+ //#region src/workflows/connections/connection-provider.ts
2
+ /**
3
+ * The Better Auth provider id a connection's accounts are kept under: the app's sign-in provider it names, or one of
4
+ * its own, `workflow-<plugin>-<connection>`, for an `oauth` connection; plugin and connection names hold no `-`.
5
+ */
6
+ const connectionProviderId = (plugin, name, declaration) => "signIn" in declaration ? declaration.signIn : `workflow-${plugin}-${name}`;
7
+ /** Whether an account's stored scopes hold every scope the connection asks for. */
8
+ const holdsScopes = (stored, declaration) => (declaration.scopes ?? []).every((scope) => stored.includes(scope));
9
+ //#endregion
10
+ export { connectionProviderId, holdsScopes };
@@ -0,0 +1,54 @@
1
+ import { connectionProviderId, holdsScopes } from "./connection-provider.js";
2
+ import { enablerMayUpdate } from "./enabler-authority.js";
3
+ import { singleFlight } from "./single-flight.js";
4
+ import { sql } from "kysely";
5
+ import { ConnectionError } from "@protobase/workflows/plugin";
6
+ //#region src/workflows/connections/connection-tokens.ts
7
+ /**
8
+ * An account's valid access token, refreshed when it expired, with overlapping refreshes of one account joined into
9
+ * one. Another worker may rotate the refresh token between this one's read (`before`, the stored refresh token) and its
10
+ * refresh, which the provider then refuses: reading the account once more tells that apart, and the stored token is
11
+ * then the fresh one. Undefined when the provider refused it.
12
+ */
13
+ const sharedRefresh = (accounts) => {
14
+ const refresh = singleFlight();
15
+ return async (userId, accountId, before) => {
16
+ const token = await refresh(accountId, () => accounts.accessToken(userId, accountId));
17
+ if (token) return token;
18
+ const after = await accounts.find(userId, accountId);
19
+ if (!after || after.refreshToken === before) return;
20
+ return refresh(accountId, () => accounts.accessToken(userId, accountId));
21
+ };
22
+ };
23
+ /**
24
+ * The connection a step or trigger declared, bound by its workflow. Each `accessToken()` checks afresh, caching nothing:
25
+ * the version it runs binds an account to the connection, the person who turned that version on owns that account of
26
+ * the connection's provider with its scopes, and may still update the workflow in its tenant. Anything else is
27
+ * `not_connected`, with one message that tells nothing of whether the account exists; a refresh the provider refuses
28
+ * is `link_expired`. The token is blanked out of all the step records (`secret`).
29
+ */
30
+ const connectionTokens = ({ deps, schema, roles, accounts }) => {
31
+ const tokenOf = sharedRefresh(accounts);
32
+ return (use) => {
33
+ const key = `${use.plugin}:${use.name}`;
34
+ const notConnected = () => new ConnectionError("not_connected", `This workflow has no connection it may use for ${key}: its owner connects an account and turns the workflow on`);
35
+ return { accessToken: async () => {
36
+ const ref = await use.version();
37
+ if (!ref) throw notConnected();
38
+ const db = deps().db;
39
+ const { rows } = await sql`select v.definition -> 'connections' ->> ${key} as account, v.enabled_by, v.tenant_id
40
+ from ${sql.id(schema, "versions")} v where v.workflow_id = ${ref.workflowId} and v.version = ${ref.version}`.execute(db);
41
+ const consent = rows[0];
42
+ if (!consent?.account || !consent.enabled_by) throw notConnected();
43
+ if (!await enablerMayUpdate(db, deps(), roles, consent.enabled_by, consent.tenant_id ?? void 0, ref.workflowId)) throw notConnected();
44
+ const account = await accounts.find(consent.enabled_by, consent.account);
45
+ if (!account || account.providerId !== connectionProviderId(use.plugin, use.name, use.declaration) || !holdsScopes(account.scopes, use.declaration)) throw notConnected();
46
+ const token = await tokenOf(consent.enabled_by, account.id, account.refreshToken);
47
+ if (!token) throw new ConnectionError("link_expired", `The account connected for ${key} no longer signs in: its owner connects it again`);
48
+ use.secret(token);
49
+ return token;
50
+ } };
51
+ };
52
+ };
53
+ //#endregion
54
+ export { connectionTokens, sharedRefresh };
@@ -0,0 +1,37 @@
1
+ import { sql } from "kysely";
2
+ //#region src/workflows/connections/connection-triggers.ts
3
+ /**
4
+ * The triggers of connected accounts, as sign-in tells of them: a link resumes the triggers waiting on that account's
5
+ * sign-in, which poll again from the cursor they kept or renew the subscription they kept; a disconnect makes the
6
+ * running ones wait for a new sign-in.
7
+ */
8
+ const connectionTriggers = (db, schema, jobs) => {
9
+ const triggers = sql.id(schema, "triggers");
10
+ return {
11
+ linked: async (accountId) => {
12
+ await db().transaction().execute(async (trx) => {
13
+ const { rows } = await sql`update ${triggers} set status = 'active', consecutive_failures = 0, last_error = null, updated_at = now()
14
+ where connection_id = ${accountId} and status = 'needs_sign_in' returning id, generation`.execute(trx);
15
+ for (const row of rows) await jobs.start.enqueue(trx, {
16
+ trigger: row.id,
17
+ generation: row.generation
18
+ });
19
+ });
20
+ },
21
+ disconnected: async (accountId) => {
22
+ const error = JSON.stringify({
23
+ code: "link_expired",
24
+ message: "The connected account was disconnected: its owner connects it again",
25
+ at: (/* @__PURE__ */ new Date()).toISOString()
26
+ });
27
+ await sql`update ${triggers} set status = 'needs_sign_in', last_error = ${error}::jsonb, poll_job_id = null, poll_attempt = null, next_poll_at = null, updated_at = now()
28
+ where connection_id = ${accountId} and status in ('active', 'failing')`.execute(db());
29
+ },
30
+ needsSignIn: async (accountIds) => {
31
+ const { rows } = await sql`select distinct connection_id from ${triggers} where status = 'needs_sign_in' and connection_id in (${sql.join(accountIds)})`.execute(db());
32
+ return rows.map((row) => row.connection_id);
33
+ }
34
+ };
35
+ };
36
+ //#endregion
37
+ export { connectionTriggers };
@@ -0,0 +1,73 @@
1
+ import { connectionConsent } from "./connection-consent.js";
2
+ import { enablerMayUpdate } from "./enabler-authority.js";
3
+ import { enablerMailbox } from "./enabler-mailbox.js";
4
+ import { connectionTokens } from "./connection-tokens.js";
5
+ import { sql } from "kysely";
6
+ import { ConnectionError } from "@protobase/workflows/plugin";
7
+ //#region src/workflows/connections/connection-wiring.ts
8
+ const noAccounts = {
9
+ find: async () => void 0,
10
+ accessToken: async () => void 0
11
+ };
12
+ /**
13
+ * The connections of an app's workflows: the consent a save checks as a version is turned on, and the connection each
14
+ * step and trigger that declares one gets, bound by the version its run or trigger runs.
15
+ */
16
+ const connectionWiring = ({ deps, schema, roles, accounts = noAccounts, plugins, connectionOf, personalMailer }) => {
17
+ const declarationOf = (key) => {
18
+ const [plugin, name] = key.split(":");
19
+ return plugins.find((candidate) => candidate.name === plugin)?.connections?.[name];
20
+ };
21
+ const consent = connectionConsent({
22
+ accounts,
23
+ declarationOf,
24
+ connectionOf
25
+ });
26
+ const tokens = connectionTokens({
27
+ deps,
28
+ schema,
29
+ roles,
30
+ accounts
31
+ });
32
+ const use = (type, version, secret) => {
33
+ const key = connectionOf[type];
34
+ const declaration = key === void 0 ? void 0 : declarationOf(key);
35
+ if (!key || !declaration) return;
36
+ const [plugin, name] = key.split(":");
37
+ return tokens({
38
+ plugin,
39
+ name,
40
+ declaration,
41
+ version,
42
+ secret
43
+ });
44
+ };
45
+ const runVersion = async (runId) => {
46
+ const { rows } = await sql`select workflow_id, version from ${sql.id(schema, "runs")} where id = ${runId}`.execute(deps().db);
47
+ const run = rows[0];
48
+ return run?.version == null ? void 0 : {
49
+ workflowId: run.workflow_id,
50
+ version: run.version
51
+ };
52
+ };
53
+ const step = (stepId, name, task) => use(stepId, () => runVersion(task.runId), task.secret) ?? { accessToken: () => Promise.reject(new ConnectionError("not_connected", `${stepId} signs in through "${name}", which this app does not declare`)) };
54
+ const trigger = (row, secret) => use(row.type, async () => row.workflow_id === null ? void 0 : {
55
+ workflowId: row.workflow_id,
56
+ version: row.version
57
+ }, secret);
58
+ const runEnabler = async (runId) => {
59
+ const { rows } = await sql`select r.workflow_id, v.enabled_by, v.tenant_id from ${sql.id(schema, "runs")} r
60
+ join ${sql.id(schema, "versions")} v on v.workflow_id = r.workflow_id and v.version = r.version where r.id = ${runId}`.execute(deps().db);
61
+ const row = rows[0];
62
+ if (!row?.enabled_by) return null;
63
+ return await enablerMayUpdate(deps().db, deps(), roles, row.enabled_by, row.tenant_id ?? void 0, row.workflow_id) ? row.enabled_by : null;
64
+ };
65
+ return {
66
+ consent,
67
+ step,
68
+ trigger,
69
+ mailbox: personalMailer && ((task) => enablerMailbox(() => runEnabler(task.runId), personalMailer))
70
+ };
71
+ };
72
+ //#endregion
73
+ export { connectionWiring };
@@ -0,0 +1,35 @@
1
+ import { requestAccess } from "../../request-access.js";
2
+ import { deletedRows } from "../../params.js";
3
+ import { fetchReadable } from "../../fetch-readable.js";
4
+ import { mayUpdateWorkflow } from "../may-update-workflow.js";
5
+ //#region src/workflows/connections/enabler-authority.ts
6
+ /**
7
+ * Whether the person who turned a workflow on may still do so: with the roles they hold now in its tenant, they may
8
+ * read this one and update it, as its update row filter and record-level rule decide. Losing either, as when they were
9
+ * demoted or left the organization, withdraws the connection their consent gave its runs.
10
+ */
11
+ const enablerMayUpdate = async (db, deps, roles, enabledBy, tenant, workflowId) => {
12
+ const held = await roles({
13
+ id: enabledBy,
14
+ ...tenant !== void 0 && { tenant }
15
+ });
16
+ if (held.length === 0) return false;
17
+ const entry = deps.registry.find("workflows");
18
+ const session = {
19
+ user: {
20
+ id: enabledBy,
21
+ roles: held
22
+ },
23
+ ...tenant !== void 0 && {
24
+ tenant,
25
+ organization: { id: tenant }
26
+ }
27
+ };
28
+ const access = await requestAccess(deps, entry, session);
29
+ if (!access.resolved.operations.update) return false;
30
+ const found = await fetchReadable(db, access, [workflowId], deletedRows(access.full, false), {});
31
+ if (!found) return false;
32
+ return mayUpdateWorkflow(db, access, workflowId, found.record);
33
+ };
34
+ //#endregion
35
+ export { enablerMayUpdate };
@@ -0,0 +1,15 @@
1
+ import { PersonalMailError } from "../../personal-mail/personal-mail-error.js";
2
+ //#region src/workflows/connections/enabler-mailbox.ts
3
+ /**
4
+ * The step context's `mailbox`: each message goes out from the own mailbox of the person who turned the run's version
5
+ * on, signed in as themselves, as `enablerOf` reads them afresh; nobody (a version an outside app turned on, a workflow
6
+ * turned off, or someone who may no longer change it) sends nothing and fails `not_linked`. A job's own code gets no
7
+ * such mailer: only this consent lets mail leave someone's mailbox without their own request.
8
+ */
9
+ const enablerMailbox = (enablerOf, personalMailer) => ({ send: async (message) => {
10
+ const enabledBy = await enablerOf();
11
+ if (!enabledBy) throw new PersonalMailError("not_linked");
12
+ await personalMailer(enabledBy).send(message);
13
+ } });
14
+ //#endregion
15
+ export { enablerMailbox };
@@ -0,0 +1,17 @@
1
+ //#region src/workflows/connections/single-flight.ts
2
+ /**
3
+ * Joins calls of one key that overlap: the second waits for the first's result instead of starting its own, such as a
4
+ * second refresh of one account's token. An entry lives only while its call runs, so the map holds the calls in flight.
5
+ */
6
+ const singleFlight = () => {
7
+ const running = /* @__PURE__ */ new Map();
8
+ return (key, call) => {
9
+ const joined = running.get(key);
10
+ if (joined) return joined;
11
+ const started = call().finally(() => running.delete(key));
12
+ running.set(key, started);
13
+ return started;
14
+ };
15
+ };
16
+ //#endregion
17
+ export { singleFlight };
@@ -0,0 +1,40 @@
1
+ import { HttpProblem } from "../../problem.js";
2
+ import { verifySignature, verifyStandardWebhook } from "../../functions/verify-signature.js";
3
+ import { bearerMatches, usableSecret } from "./hook-secrets.js";
4
+ //#region src/workflows/hooks/hook-auth.ts
5
+ /** The hmac options of a trigger's `auth`, and the header its signature comes in, which the run never sees. */
6
+ const hmacOptions = (input) => input.auth ?? {};
7
+ const verified = (check) => check.then(() => true, (error) => {
8
+ if (error instanceof HttpProblem && error.status === 401) return false;
9
+ throw error;
10
+ });
11
+ /**
12
+ * Whether the call proves it comes from the sender, by the trigger's mode: a Standard Webhooks signature within five
13
+ * minutes, an HMAC of the body in the configured header, or the bearer secret. A secret the mode cannot check with, as
14
+ * after the mode changed since the URL was issued, proves nothing. `signed` holds the bytes already read.
15
+ */
16
+ const authenticated = async (signed, mode, hook) => {
17
+ if (mode === "bearer") {
18
+ const token = /^Bearer (.+)$/.exec(signed.headers.get("authorization") ?? "")?.[1];
19
+ return token !== void 0 && bearerMatches(token, hook.secret_hash);
20
+ }
21
+ if (!usableSecret(mode, hook.secret)) return false;
22
+ if (mode === "standard_webhooks") return verified(verifyStandardWebhook(signed, { secret: hook.secret }));
23
+ const { header, prefix, encoding, hash } = hmacOptions(hook.input);
24
+ if (!header) return false;
25
+ return verified(verifySignature(signed, {
26
+ secret: hook.secret,
27
+ header,
28
+ ...prefix !== void 0 && { prefix },
29
+ ...encoding && { encoding },
30
+ ...hash && { hash }
31
+ }));
32
+ };
33
+ /** The key a call starts its run once under: the signed `webhook-id`, or the configured idempotency header's value. */
34
+ const idempotencyKey = (request, mode, input) => {
35
+ if (mode === "standard_webhooks") return request.headers.get("webhook-id") ?? void 0;
36
+ const header = input.idempotency_header;
37
+ return typeof header === "string" ? request.headers.get(header) ?? void 0 : void 0;
38
+ };
39
+ //#endregion
40
+ export { authenticated, hmacOptions, idempotencyKey };