connectors 0.1.0 → 0.1.1

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/CONNECTORS_FRAMEWORK.md +54 -99
  4. data/MCP_CLIENT.md +5 -5
  5. data/README.md +5 -3
  6. data/app/connectors/gmail/api.rb +6 -14
  7. data/app/connectors/gmail/connector.rb +11 -24
  8. data/app/connectors/gmail/mime_builder.rb +0 -3
  9. data/app/connectors/gmail/mime_parser.rb +1 -2
  10. data/app/connectors/gmail/polling.rb +7 -14
  11. data/app/connectors/resend/connector.rb +2 -4
  12. data/app/controllers/connectors/actions_controller.rb +0 -6
  13. data/app/controllers/connectors/credentials_controller.rb +10 -23
  14. data/app/controllers/connectors/grants_controller.rb +6 -11
  15. data/app/controllers/connectors/types_controller.rb +18 -45
  16. data/app/controllers/connectors/webhooks_controller.rb +2 -4
  17. data/app/jobs/connectors/deliver_webhook_job.rb +2 -4
  18. data/app/models/connectors/credential_share.rb +3 -10
  19. data/app/models/connectors/grant.rb +2 -4
  20. data/config/routes.rb +6 -13
  21. data/db/migrate/20260518210324_create_connectors_grants.rb +4 -11
  22. data/db/migrate/20260521140000_create_connectors_credential_shares.rb +2 -8
  23. data/docs/releasing.md +4 -4
  24. data/lib/connectors/action.rb +4 -7
  25. data/lib/connectors/action_builder.rb +5 -12
  26. data/lib/connectors/action_runner.rb +5 -11
  27. data/lib/connectors/auth_injection.rb +7 -18
  28. data/lib/connectors/client_builder.rb +4 -10
  29. data/lib/connectors/configuration.rb +9 -39
  30. data/lib/connectors/connector.rb +31 -83
  31. data/lib/connectors/credential_schema.rb +12 -29
  32. data/lib/connectors/credential_tester.rb +3 -4
  33. data/lib/connectors/credential_type_registry.rb +9 -20
  34. data/lib/connectors/credential_types/http_auth.rb +18 -55
  35. data/lib/connectors/engine.rb +3 -6
  36. data/lib/connectors/errors.rb +2 -5
  37. data/lib/connectors/middleware/authenticate_generic.rb +4 -14
  38. data/lib/connectors/middleware/pre_authentication.rb +2 -13
  39. data/lib/connectors/oauth/authorize_url.rb +3 -9
  40. data/lib/connectors/oauth/client_credentials.rb +0 -4
  41. data/lib/connectors/oauth/pkce.rb +3 -9
  42. data/lib/connectors/oauth/revoke.rb +2 -6
  43. data/lib/connectors/oauth1.rb +4 -8
  44. data/lib/connectors/permission_check.rb +2 -4
  45. data/lib/connectors/poll_runner.rb +1 -5
  46. data/lib/connectors/pre_authentication_helpers.rb +4 -9
  47. data/lib/connectors/version.rb +1 -1
  48. data/lib/connectors/webhook_context.rb +8 -18
  49. data/lib/connectors/webhook_lifecycle.rb +1 -8
  50. data/lib/connectors/webhook_methods.rb +2 -6
  51. data/openapi.yaml +16 -16
  52. metadata +21 -1
@@ -6,13 +6,13 @@ module Connectors
6
6
  # # config/initializers/connectors.rb
7
7
  # Connectors.configure do |c|
8
8
  # c.owner_class_name = "User"
9
- # c.current_owner_resolver = ->(controller) { controller.current_user }
9
+ # c.current_owner_resolver = ->(controller) { controller.request.env["connectors.current_owner"] }
10
10
  # c.host_base_url = ENV.fetch("APP_BASE_URL", "http://localhost:3000")
11
11
  # c.oauth_credentials = {
12
12
  # slack: { client_id: ENV["SLACK_CLIENT_ID"], client_secret: ENV["SLACK_CLIENT_SECRET"] },
13
13
  # linear: { client_id: ENV["LINEAR_CLIENT_ID"], client_secret: ENV["LINEAR_CLIENT_SECRET"] }
14
14
  # }
15
- # c.on_webhook = ->(event) { Inbox::IngestJob.perform_later(event.id) }
15
+ # c.on_webhook = ->(event) { ProcessWebhookJob.perform_later(event.id) }
16
16
  # end
17
17
  class Configuration
18
18
  attr_accessor :owner_class_name,
@@ -58,39 +58,17 @@ module Connectors
58
58
  current_owner_resolver.call(controller)
59
59
  end
60
60
 
61
- # The host wires in a block that, given the controller (or any context
62
- # with `current_user`/etc.), returns an array of `[principal_type,
63
- # principal_id]` pairs representing every identity the requester has
64
- # for sharing-visibility purposes.
61
+ # Resolve managed secrets through the host callback. Returned values
62
+ # override stored credentials for the lifetime of the Grant instance.
65
63
  #
66
- # Default: just the owner — `[["<owner_class_name>", owner.id]]`. EE
67
- # hosts override to include teams / projects:
68
- #
69
- # c.principal_resolver = ->(ctrl) {
70
- # [
71
- # ["User", ctrl.current_user.id],
72
- # *ctrl.current_user.team_ids.map { |id| ["Team", id] },
73
- # ]
74
- # }
75
- # n8n parity: external secrets EE module
76
- # (`external-secrets.controller.ee.ts`). Host wires a block that takes a
77
- # Grant (one whose `is_managed?` is true) and returns the hash to merge
78
- # into the credentials at request time. Returned values overwrite the
79
- # DB-stored values for the lifetime of that Grant instance.
80
- #
81
- # c.secrets_resolver = ->(grant) {
82
- # vault_client.read("kv/data/connectors/#{grant.connector_key}/#{grant.external_ref}")
83
- # }
64
+ # c.secrets_resolver = ->(grant) { vault_client.read(grant.external_ref) }
84
65
  def resolve_secrets(grant)
85
66
  return {} if secrets_resolver.nil?
86
67
  secrets_resolver.call(grant).to_h.transform_keys(&:to_s)
87
68
  end
88
69
 
89
- # Per-connector list of field names sourced from the vault — surfaces
90
- # in `GET /connectors/types/:name` as `__overwritten_properties` so the
91
- # editor can hide / lock those fields. n8n parity:
92
- # `frontend.service.ts:681-705` — same shape, just emitted server-side
93
- # instead of recomputed in the editor.
70
+ # Vault-sourced field names exposed as __overwritten_properties
71
+ # so clients can display the fields as managed.
94
72
  def managed_fields_for(connector_key)
95
73
  return [] if secrets_managed_fields_for.nil?
96
74
  Array(secrets_managed_fields_for.call(connector_key)).map(&:to_s)
@@ -103,16 +81,8 @@ module Connectors
103
81
  [ [ owner.class.name, owner.id ] ]
104
82
  end
105
83
 
106
- # The URL the OAuth provider redirects back to after consent. Defaults
107
- # to the engine's own callback path (n8n-style: provider → backend →
108
- # render HTML → close popup). For split frontend/backend setups
109
- # (Activepieces-style: provider → frontend → POST exchange → backend),
110
- # set this to the FRONTEND's callback page, e.g.
111
- # `"http://localhost:3001/oauth/callback"`.
112
- #
113
- # Either way it must MATCH a value registered in the provider's app
114
- # console; if it doesn't, Google/Slack/etc. respond with
115
- # `redirect_uri_mismatch` before consent even renders.
84
+ # Use the configured frontend callback URL or the mounted engine callback.
85
+ # Register the resulting URL with the OAuth provider.
116
86
  def resolved_app_callback_url(connector_key)
117
87
  return app_callback_url if app_callback_url.present?
118
88
  base = host_base_url or
@@ -71,8 +71,7 @@ module Connectors
71
71
  @rate_limit_config = { limit: count, per: per }
72
72
  end
73
73
 
74
- # Declarative "Test connection" — mirrors n8n's `ICredentialTestRequest`
75
- # (packages/workflow/src/interfaces.ts:340-348). The test fires a real
74
+ # Declarative connection test. Fires a real
76
75
  # HTTP request via the connector's middleware stack (so credentials are
77
76
  # injected the same way they would be at runtime) and applies the
78
77
  # configured rules to decide pass/fail.
@@ -99,10 +98,6 @@ module Connectors
99
98
  }
100
99
  end
101
100
 
102
- # n8n-style declarative auth injection (mirrors `IAuthenticateGeneric`
103
- # at packages/workflow/src/interfaces.ts:278-288 + the shape of
104
- # `IRequestOptionsSimplifiedAuth` at :197-208).
105
- #
106
101
  # `properties` accepts any subset of:
107
102
  # headers: { "Authorization" => "=Bearer {{$credentials.api_key}}" }
108
103
  # qs: { "api_key" => "={{$credentials.api_key}}" }
@@ -118,12 +113,12 @@ module Connectors
118
113
  # headers: { "Authorization" => "=Bearer {{$credentials.api_key}}" }
119
114
  # }
120
115
  #
121
- # When declared, this replaces the old `api_key_in` imperative shim
116
+ # When declared, this replaces the imperative `api_key_in` configuration
122
117
  # at runtime — both the schema serializer and the Faraday client
123
118
  # honor the declarative form first, falling back to `api_key_in` only
124
119
  # if `authenticate` is absent.
125
120
  def authenticate(type:, properties:)
126
- raise ArgumentError, "only type: :generic is supported (n8n's IAuthenticateGeneric)" \
121
+ raise ArgumentError, "only type: :generic is supported" \
127
122
  unless type.to_sym == :generic
128
123
  @authenticate_config = { "type" => "generic", "properties" => properties }
129
124
  end
@@ -136,15 +131,8 @@ module Connectors
136
131
  @authenticate_config || @credential_schema&.resolved_authenticate
137
132
  end
138
133
 
139
- # n8n's `preAuthentication` hook (interfaces.ts:374-377). The block
140
- # runs BEFORE each outgoing request, but only when the grant's
141
- # credentials look stale (`expires_at` missing or in the past).
142
- # Returns a hash merged into the grant's credentials before the
143
- # AuthenticateGeneric step injects them into the request.
144
- #
145
- # Reference impl: CrowdStrikeOAuth2Api.credentials.ts:62-76 —
146
- # fetches a session token from /oauth2/token using client_id +
147
- # client_secret, returns `{ session_token, expires_at }`.
134
+ # Run before outgoing requests when expires_at is absent or past.
135
+ # Merge returned credentials before authentication injection.
148
136
  #
149
137
  # pre_authentication do |credentials, helpers|
150
138
  # response = helpers.http_request(
@@ -162,13 +150,11 @@ module Connectors
162
150
 
163
151
  attr_reader :pre_authentication_block
164
152
 
165
- # Imperative API-key shim — kept for back-compat with connectors that
166
- # declared `api_key_in` before Phase 1. New code should use the
167
- # declarative `authenticate` DSL above. When both are declared,
168
- # `authenticate` wins.
153
+ # Imperative API-key injection. A declared authenticate block takes
154
+ # precedence over this setting.
169
155
  #
170
156
  # api_key_in :header, name: "Authorization", prefix: "token "
171
- # api_key_in :query, name: "api_key"
157
+ # api_key_in :query, name: "api_key"
172
158
  def api_key_in(location, name:, prefix: nil)
173
159
  @api_key_options = { location: location, name: name, prefix: prefix }
174
160
  end
@@ -178,14 +164,13 @@ module Connectors
178
164
  # Declares OAuth2 provider endpoints + default scope. Client id/secret
179
165
  # are supplied by the host via Connectors.configuration.oauth_credentials.
180
166
  #
181
- # `grant_type:` mirrors n8n's `OAuth2Api.credentials.ts:33-46` enum and
182
- # selects the runtime flow:
167
+ # `grant_type:` selects the runtime flow:
183
168
  # - "authorizationCode" (default) — standard redirect flow
184
169
  # - "clientCredentials" — server-to-server, no user redirect
185
170
  # - "pkce" — RFC 7636 with S256 challenge
186
171
  #
187
172
  # `authentication:` (header | body) — how client credentials are sent
188
- # to the token endpoint. Matches n8n's same-named field. Default
173
+ # to the token endpoint. Default
189
174
  # `"header"` (HTTP Basic).
190
175
  #
191
176
  # oauth2 authorize_url: "https://slack.com/oauth/v2/authorize",
@@ -214,8 +199,7 @@ module Connectors
214
199
 
215
200
  # Declares OAuth1.0a provider endpoints + consumer signature method.
216
201
  # Consumer key/secret come from `Connectors.configuration.oauth_credentials`
217
- # (same `client_id` / `client_secret` slot as OAuth2 — they're
218
- # semantically identical in n8n's storage too).
202
+ # using the same client_id / client_secret configuration keys as OAuth2.
219
203
  #
220
204
  # oauth1 request_token_url: "https://api.twitter.com/oauth/request_token",
221
205
  # authorize_url: "https://api.twitter.com/oauth/authorize",
@@ -257,10 +241,8 @@ module Connectors
257
241
 
258
242
  attr_reader :revoke_token_block
259
243
 
260
- # n8n's `__skipManagedCreation` (frontend.service.ts:707-711). When
261
- # an admin disables managed-credential creation for this type, the
262
- # editor hides the "Use external secret" toggle AND the API refuses
263
- # POSTs that pass `is_managed: true`. Surfaces on the types endpoint.
244
+ # Disable managed-credential creation in the catalog and API.
245
+ # Requests passing is_managed: true are rejected for this type.
264
246
  def skip_managed_creation!
265
247
  @skip_managed_creation = true
266
248
  end
@@ -269,16 +251,10 @@ module Connectors
269
251
  @skip_managed_creation == true
270
252
  end
271
253
 
272
- # n8n's `genericAuth: boolean` flag on `ICredentialType`
273
- # (interfaces.ts:379). Marks the credential as eligible for the future
274
- # generic HTTP-Request node's picker. Most provider-specific connectors
275
- # leave this off; generic HTTP types (HttpBearerAuth etc.) flip it on.
276
- # Connector-level call so non-`extends` connectors can opt in directly:
254
+ # Enable generic HTTP-request credential use. The setting is also
255
+ # inherited from base credential schemas.
277
256
  #
278
257
  # generic_auth!
279
- #
280
- # When the connector `extends` an Http*Auth schema, the schema's flag
281
- # already propagates — but this stays available as a per-connector override.
282
258
  def generic_auth!
283
259
  @generic_auth = true
284
260
  end
@@ -288,14 +264,10 @@ module Connectors
288
264
  @credential_schema&.generic_auth? || false
289
265
  end
290
266
 
291
- # n8n's `supportedNodes: string[]` (interfaces.ts:381). When set, only
292
- # the listed node types are allowed to use this credential. Empty/unset
293
- # means "any node may use it" — same default as n8n.
267
+ # Restrict credential use to listed node types; an empty list is unrestricted.
268
+ # Hosts enforce this through Connectors::PermissionCheck.permit!.
294
269
  #
295
270
  # supported_nodes :slack_send_message, :slack_get_channel
296
- #
297
- # The check itself is `Connectors::PermissionCheck.permit!` — call it
298
- # from your node's execution wrapper to enforce.
299
271
  def supported_nodes(*names)
300
272
  if names.empty?
301
273
  @supported_nodes || []
@@ -304,11 +276,7 @@ module Connectors
304
276
  end
305
277
  end
306
278
 
307
- # n8n's `httpRequestNode: { name, docsUrl, apiBaseUrl | apiBaseUrlPlaceholder, hidden? }`
308
- # (interfaces.ts:380; type `ICredentialHttpRequestNode` at :350-354).
309
- # Advertises the connector to the generic HTTP-Request node's
310
- # "credential picker" UI — the user sees "Linear API" alongside the
311
- # docs link + a pre-filled base URL.
279
+ # Optional label, documentation and base URL for an HTTP credential picker.
312
280
  #
313
281
  # http_request_node name: "Linear API",
314
282
  # docs_url: "https://developers.linear.app/",
@@ -319,8 +287,7 @@ module Connectors
319
287
  # + `docs_url:` + one of the base-URL slots.
320
288
  return @http_request_node if name.nil? && docs_url.nil?
321
289
  unless api_base_url || api_base_url_placeholder
322
- raise ArgumentError, "http_request_node requires either api_base_url or api_base_url_placeholder " \
323
- "(n8n's `ICredentialHttpRequestNode` union, interfaces.ts:350-354)"
290
+ raise ArgumentError, "http_request_node requires either api_base_url or api_base_url_placeholder"
324
291
  end
325
292
  @http_request_node = {
326
293
  "name" => name,
@@ -331,11 +298,8 @@ module Connectors
331
298
  }.compact
332
299
  end
333
300
 
334
- # Combined predicate used by `Connectors::PermissionCheck.permit!`.
335
- # n8n's logic (oauth/credentials.controller flow, applied at credential
336
- # picker render time): if `supportedNodes` is non-empty, the requesting
337
- # node must be in it. The generic HTTP-Request node bypasses this
338
- # check entirely when `genericAuth` is true on the credential type.
301
+ # An empty supported_nodes list allows any node. Otherwise require an
302
+ # allowlist match, with an exception for :http_request when generic_auth is set.
339
303
  def supports_node?(node_type)
340
304
  node_type = node_type.to_sym
341
305
  return true if supported_nodes.empty? # no allowlist → permitted
@@ -399,12 +363,9 @@ module Connectors
399
363
  (@webhook_groups || {}).keys
400
364
  end
401
365
 
402
- # Polling primitive — n8n's `polling: true` flag + `poll()` method
403
- # on trigger nodes (interfaces.ts:2060; reference impl
404
- # `nodes-base/nodes/Google/Gmail/GmailTrigger.node.ts:65, 281-553`).
405
- # The block runs once per scheduler tick (or once per manual POST to
406
- # /poll), receives the per-grant scratch hash for cursor persistence,
407
- # and returns the new items.
366
+ # Runs once per host-scheduled poll and returns new items. PollRunner
367
+ # supplies cursor state scoped to the instance_key, or to the grant
368
+ # when no instance_key is provided. The host owns scheduling.
408
369
  #
409
370
  # polling do |grant, static_data|
410
371
  # since = static_data["last_id"]
@@ -412,9 +373,6 @@ module Connectors
412
373
  # static_data["last_id"] = items.first["id"] if items.any?
413
374
  # items
414
375
  # end
415
- #
416
- # The scheduler itself is a workflow-roadmap concern; the connector
417
- # side just provides the block + cursor contract.
418
376
  def polling(&block)
419
377
  @polling_block = block
420
378
  end
@@ -464,12 +422,8 @@ module Connectors
464
422
  end
465
423
  end
466
424
 
467
- # Declarative action manifest — what this connector can DO with a
468
- # credential. Mirrors n8n's `(resource, operation)` slot
469
- # (packages/workflow/src/interfaces.ts NodeProperties) and Activepieces'
470
- # `createAction(...)`. Each action gets a typed param schema, an
471
- # optional output schema, and an execute block that runs in the
472
- # connector instance's context.
425
+ # Declare a provider operation with input fields, optional output fields
426
+ # and an execute block evaluated in the connector instance.
473
427
  #
474
428
  # action :send_email,
475
429
  # display_name: "Send Email",
@@ -521,10 +475,8 @@ module Connectors
521
475
  @grant = grant
522
476
  end
523
477
 
524
- # The Faraday client with the full middleware stack assembled. When the
525
- # connector class (or its inherited credential schema, via Phase 2)
526
- # declares an `authenticate` block, AuthenticateGeneric middleware
527
- # applies it; otherwise the imperative `auth_scheme` is used.
478
+ # Build the Faraday middleware stack. A connector or inherited schema
479
+ # authenticate block takes precedence over the imperative auth_scheme.
528
480
  def client
529
481
  @client ||= ClientBuilder.new(
530
482
  base_url: self.class.base_url,
@@ -546,13 +498,9 @@ module Connectors
546
498
  # the connector opts into polling.
547
499
  def poll; end
548
500
 
549
- # Override in subclass. Called by the webhook dispatcher when an event
550
- # arrives for this grant. The argument is a `Connectors::WebhookContext`
551
- # (Phase 7) exposing `.body`, `.headers`, `.query`, `.raw_body`,
552
- # `.webhook_name`, `.signature`, `.grant`, plus `.event` for direct
553
- # access to the persisted `Connectors::WebhookEvent`. The context
554
- # delegates `.payload_hash` / `.payload` for back-compat with handlers
555
- # written before Phase 7 — the same method works on both.
501
+ # Override to process a WebhookContext from DeliverWebhookJob.
502
+ # The context exposes body, headers, query, raw_body, webhook_name,
503
+ # signature and grant, plus the persisted event and payload aliases.
556
504
  def handle_webhook(ctx); end
557
505
 
558
506
  # Convenience — validates the grant's credentials against the connector's
@@ -1,8 +1,5 @@
1
1
  module Connectors
2
- # Declarative description of every field a credential type's data hash can
3
- # contain. Mirrors n8n's `INodeProperties` shape exactly (n8n source:
4
- # packages/workflow/src/interfaces.ts:1773-1812) so a single frontend
5
- # renderer handles every connector with no per-connector logic.
2
+ # Credential form schema used by client renderers.
6
3
  #
7
4
  # credentials do
8
5
  # field :api_key,
@@ -17,12 +14,9 @@ module Connectors
17
14
  # Inheritance: a connector can `extends :oauth2` (or any other registered
18
15
  # base type) and `field` calls in the block can REDECLARE inherited fields
19
16
  # — typically as `type: "hidden"` with a fixed `default:` to lock provider
20
- # endpoints (matches n8n's Slack/Google override pattern at
21
- # SlackOAuth2Api.credentials.ts:38-122).
17
+ # endpoints.
22
18
  class CredentialSchema
23
- # n8n's `NodePropertyTypes` enum (interfaces.ts:1561-1584). Wave A ships
24
- # the most commonly-used subset; later waves add resourceLocator /
25
- # collection / fixedCollection / dateTime as connectors demand them.
19
+ # Supported credential form field types.
26
20
  ALLOWED_TYPES = %w[
27
21
  string number boolean
28
22
  options multiOptions
@@ -43,9 +37,7 @@ module Connectors
43
37
  (type_options || {}).any? { |k, v| k.to_s == "password" && v == true }
44
38
  end
45
39
 
46
- # Frontend-shaped hash. n8n serializes property names in camelCase
47
- # (`displayName`, `typeOptions`); we mirror that so the editor's
48
- # generic form renderer eats it without translation.
40
+ # Serialize form metadata with camelCase field keys for client renderers.
49
41
  def to_property
50
42
  {
51
43
  name: name.to_s,
@@ -90,7 +82,7 @@ module Connectors
90
82
  end
91
83
 
92
84
  # Inherit fields from one or more registered base credential schemas
93
- # (today only `:oauth2`). Inherited fields can be REDECLARED below via
85
+ # such as :oauth2 or :http_bearer_auth. Redeclare inherited fields via
94
86
  # `field` — typically as `type: "hidden", default: "..."` to lock
95
87
  # provider-specific endpoints. Doubles as a getter when called with
96
88
  # no args (the serializer needs to read it back).
@@ -99,20 +91,15 @@ module Connectors
99
91
  @extends = base_names.flatten.map(&:to_sym)
100
92
  end
101
93
 
102
- # n8n-shape declarative auth injection (mirrors `ICredentialType.authenticate`
103
- # at packages/workflow/src/interfaces.ts:367; type `IAuthenticateGeneric`
104
- # at :278-288). Per-credential-type so any connector that `extends` this
105
- # schema inherits the injection contract automatically.
94
+ # Declare authentication properties inherited by connectors extending
95
+ # this credential schema.
106
96
  def authenticate(type: nil, properties: nil)
107
97
  return @authenticate if type.nil? && properties.nil?
108
98
  raise ArgumentError, "authenticate type: must be :generic" unless type.to_sym == :generic
109
99
  @authenticate = { "type" => "generic", "properties" => properties }
110
100
  end
111
101
 
112
- # n8n flag: this credential type is visible to the future generic HTTP
113
- # Request node's credential picker (`interfaces.ts:379`; example
114
- # consumer `HttpBearerAuth.credentials.ts:12`). Stored here so the
115
- # serializer surfaces it; runtime enforcement lands in Phase 5.
102
+ # Allow generic HTTP-request use and advertise it in the catalog.
116
103
  def generic_auth!
117
104
  @generic_auth = true
118
105
  end
@@ -176,10 +163,8 @@ module Connectors
176
163
  @fields.values
177
164
  end
178
165
 
179
- # n8n's editor walks `extends` and merges parent properties before
180
- # rendering. We do the walk server-side and emit the union so the
181
- # frontend doesn't need to fetch each parent type separately. Children
182
- # override parents by re-declaring with the same name.
166
+ # Resolve inherited fields server-side. Children replace parent fields
167
+ # with the same name, so clients receive a complete form schema.
183
168
  def resolved_fields
184
169
  parent_fields = @extends.flat_map { |name| CredentialTypeRegistry.fetch(name).own_fields }
185
170
  union = {}
@@ -187,10 +172,8 @@ module Connectors
187
172
  union.values
188
173
  end
189
174
 
190
- # Walk `extends` to find the inherited authenticate block; the child's
191
- # own block (if declared) overrides. Returns nil when no parent in the
192
- # chain declared one. Matches n8n's runtime credential-walker pattern
193
- # at packages/cli/src/credential-types.ts:26-37.
175
+ # Resolve the nearest authenticate declaration, preferring this schema.
176
+ # Returns nil when no schema in the inheritance chain declares one.
194
177
  def resolved_authenticate
195
178
  return @authenticate if @authenticate
196
179
  @extends.each do |name|
@@ -1,7 +1,6 @@
1
1
  module Connectors
2
2
  # Executes a connector's `test_request` declaration against the live
3
- # provider, applying configured rules to decide pass/fail. Mirrors n8n's
4
- # `CredentialsTester` (packages/cli/src/services/credentials-tester.service.ts).
3
+ # provider, applying configured rules to decide pass/fail.
5
4
  #
6
5
  # result = CredentialTester.run(grant)
7
6
  # # => { status: "OK", message: "Connection successful" }
@@ -9,8 +8,8 @@ module Connectors
9
8
  # # => { status: "Error", message: "Slack token is invalid", details: {...} }
10
9
  #
11
10
  # Reuses the connector's Faraday client so the same auth middleware,
12
- # auto-refresh, rate-limit, and error-normalization apply — a test that
13
- # passes here means a real request will pass too.
11
+ # auto-refresh, rate-limit and error normalization apply. A successful
12
+ # test verifies this request, not permissions for every provider operation.
14
13
  class CredentialTester
15
14
  OK = "OK".freeze
16
15
  ERROR = "Error".freeze
@@ -1,9 +1,6 @@
1
1
  module Connectors
2
- # Registry of reusable base credential schemas that connectors `extends`.
3
- # Mirrors n8n's pattern where every OAuth2 provider extends a single
4
- # canonical `OAuth2Api` credential type (packages/nodes-base/credentials/
5
- # OAuth2Api.credentials.ts:1-238). The registry is populated at engine
6
- # boot — see `Connectors::Engine`.
2
+ # Registry of reusable credential schemas populated at engine boot.
3
+ # Connectors inherit registered schemas through extends.
7
4
  class CredentialTypeRegistry
8
5
  class << self
9
6
  def register(name, schema)
@@ -31,10 +28,8 @@ module Connectors
31
28
  end
32
29
  end
33
30
 
34
- # Canonical OAuth2 credential schema — every OAuth2 provider connector
35
- # extends this and overrides specific fields as `type: "hidden"` with a
36
- # fixed `default`. Field set matches n8n's OAuth2Api credentials type
37
- # (packages/nodes-base/credentials/OAuth2Api.credentials.ts:1-238).
31
+ # Base OAuth2 form. Provider connectors can override fields with hidden
32
+ # defaults to fix their endpoints and other configuration.
38
33
  OAUTH2 = CredentialSchema.build do
39
34
  field :grant_type,
40
35
  type: "options",
@@ -95,10 +90,8 @@ module Connectors
95
90
  { name: "Body", value: "body" }
96
91
  ]
97
92
 
98
- # Phase 11 polish — OAuth2 advanced fields. n8n parity:
99
- # `OAuth2Api.credentials.ts:208-237`. Form-only at the engine level
100
- # (no runtime JWE decryption); ship the schema so the editor can
101
- # render the configuration UI for enterprise SSO scenarios.
93
+ # JWE configuration metadata for form rendering only.
94
+ # The engine does not decrypt JWE tokens.
102
95
  field :jwe_enabled,
103
96
  type: "boolean",
104
97
  display_name: "Encrypted Tokens (JWE)",
@@ -114,15 +107,11 @@ module Connectors
114
107
  display_options: { show: { jwe_enabled: [ true ] } }
115
108
  end
116
109
 
117
- # n8n's canonical `OAuth1Api` credential type
118
- # (packages/nodes-base/credentials/OAuth1Api.credentials.ts:1-72). Same
119
- # storage shape as OAuth2's `client_id` / `client_secret` — they map to
120
- # the `consumerKey` / `consumerSecret` here. Connectors that extend this
121
- # type generally `field :authorization_url, type: "hidden", default: "..."`
122
- # to lock the provider endpoint.
110
+ # Base OAuth1 form. Client ID and secret represent the consumer key
111
+ # and secret. Provider connectors can fix endpoint fields with hidden defaults.
123
112
  OAUTH1 = CredentialSchema.build do
124
113
  display_name "OAuth1 API"
125
- documentation_url "httprequest"
114
+ documentation_url "https://github.com/jackhelio/connectors/blob/main/CONNECTORS_FRAMEWORK.md#6-built-in-base-credential-types"
126
115
  generic_auth!
127
116
 
128
117
  field :authorization_url,
@@ -1,29 +1,13 @@
1
1
  module Connectors
2
- # Generic HTTP authentication credential types — n8n parity with the six
3
- # `Http*Auth.credentials.ts` types shipped in
4
- # `packages/nodes-base/credentials/`. Each is `generic_auth: true` so a
5
- # future HTTP-Request-style node can offer them in its credential picker
6
- # (the runtime check lands in Phase 5).
7
- #
8
- # Connectors inherit any of these via `extends :http_bearer_auth` etc.
9
- # When a connector extends one of the runtime-supported types
10
- # (basic / bearer / header / query), the AuthenticateGeneric middleware
11
- # picks up the inherited `authenticate` block and injects per-request —
12
- # no `api_key_in` boilerplate needed.
13
- #
14
- # n8n source: `packages/nodes-base/credentials/HttpBasicAuth.credentials.ts`,
15
- # `HttpBearerAuth.credentials.ts`, `HttpHeaderAuth.credentials.ts`,
16
- # `HttpQueryAuth.credentials.ts`, `HttpDigestAuth.credentials.ts`,
17
- # `HttpCustomAuth.credentials.ts`.
2
+ # Reusable HTTP authentication schemas. Basic, bearer, header and query
3
+ # types declare runtime injection inherited by extending connectors.
4
+ # Digest and custom JSON types provide form metadata only.
5
+ # All types advertise generic_auth for HTTP-request credential selection.
18
6
  module CredentialTypes
19
- # --- :http_basic_auth -------------------------------------------------
20
- # n8n: HttpBasicAuth.credentials.ts:1-33. n8n's HTTP node implements
21
- # Basic auth itself (no `authenticate` block on the credential). We
22
- # synthesize one using AuthenticateGeneric's `auth: { username, password }`
23
- # shortcut so the declarative path works end-to-end.
7
+ # Basic authentication via the declarative username/password shortcut.
24
8
  HTTP_BASIC_AUTH = CredentialSchema.build do
25
9
  display_name "Basic Auth"
26
- documentation_url "httprequest"
10
+ documentation_url "https://github.com/jackhelio/connectors/blob/main/CONNECTORS_FRAMEWORK.md#6-built-in-base-credential-types"
27
11
  generic_auth!
28
12
 
29
13
  field :user,
@@ -41,11 +25,10 @@ module Connectors
41
25
  }
42
26
  end
43
27
 
44
- # --- :http_bearer_auth ------------------------------------------------
45
- # n8n: HttpBearerAuth.credentials.ts:1-43.
28
+ # Bearer token authentication.
46
29
  HTTP_BEARER_AUTH = CredentialSchema.build do
47
30
  display_name "Bearer Auth"
48
- documentation_url "httprequest"
31
+ documentation_url "https://github.com/jackhelio/connectors/blob/main/CONNECTORS_FRAMEWORK.md#6-built-in-base-credential-types"
49
32
  generic_auth!
50
33
 
51
34
  field :token,
@@ -65,13 +48,10 @@ module Connectors
65
48
  }
66
49
  end
67
50
 
68
- # --- :http_header_auth ------------------------------------------------
69
- # n8n: HttpHeaderAuth.credentials.ts:1-45. Both header NAME and VALUE
70
- # are templated — AuthInjection.walk resolves keys as well as values
71
- # (see auth_injection.rb).
51
+ # Both header name and value are resolved from credential templates.
72
52
  HTTP_HEADER_AUTH = CredentialSchema.build do
73
53
  display_name "Header Auth"
74
- documentation_url "httprequest"
54
+ documentation_url "https://github.com/jackhelio/connectors/blob/main/CONNECTORS_FRAMEWORK.md#6-built-in-base-credential-types"
75
55
  generic_auth!
76
56
 
77
57
  field :name,
@@ -94,12 +74,10 @@ module Connectors
94
74
  }
95
75
  end
96
76
 
97
- # --- :http_query_auth -------------------------------------------------
98
- # n8n: HttpQueryAuth.credentials.ts:1-30. n8n's HTTP node handles
99
- # injection imperatively; we synthesize the equivalent generic block.
77
+ # Query parameter authentication.
100
78
  HTTP_QUERY_AUTH = CredentialSchema.build do
101
79
  display_name "Query Auth"
102
- documentation_url "httprequest"
80
+ documentation_url "https://github.com/jackhelio/connectors/blob/main/CONNECTORS_FRAMEWORK.md#6-built-in-base-credential-types"
103
81
  generic_auth!
104
82
 
105
83
  field :name,
@@ -117,19 +95,10 @@ module Connectors
117
95
  }
118
96
  end
119
97
 
120
- # --- :http_digest_auth ------------------------------------------------
121
- # n8n: HttpDigestAuth.credentials.ts:1-32. n8n consumes this via axios's
122
- # built-in digest challenge handling. Faraday has no equivalent
123
- # middleware out of the box — we ship the SCHEMA (so the frontend can
124
- # render the form + a future generic HTTP-Request node can offer the
125
- # type) but the AuthenticateGeneric middleware can't perform the
126
- # challenge-response handshake on its own.
127
- #
128
- # Runtime support requires a Faraday-Digest middleware. Tracked as a
129
- # follow-up; for now the type is form-only.
98
+ # Digest credential form only; no challenge-response runtime is provided.
130
99
  HTTP_DIGEST_AUTH = CredentialSchema.build do
131
100
  display_name "Digest Auth"
132
- documentation_url "httprequest"
101
+ documentation_url "https://github.com/jackhelio/connectors/blob/main/CONNECTORS_FRAMEWORK.md#6-built-in-base-credential-types"
133
102
  generic_auth!
134
103
 
135
104
  field :user,
@@ -142,20 +111,14 @@ module Connectors
142
111
  default: "",
143
112
  secret: true
144
113
 
145
- # No authenticate block — requires challenge-response middleware
146
- # (Faraday-Digest gem or equivalent). Phase 2 ships the schema only.
114
+ # No authenticate block: digest challenge handling is not implemented.
147
115
  end
148
116
 
149
- # --- :http_custom_auth ------------------------------------------------
150
- # n8n: HttpCustomAuth.credentials.ts:1-29. Single `json` field carrying
151
- # `{ headers, body, qs }`. n8n's HTTP node parses this JSON at request
152
- # time and applies it. Our AuthenticateGeneric middleware works on a
153
- # static `properties` block; runtime injection of a user-supplied JSON
154
- # is deferred until a Custom-API-Call style node lands (workflow
155
- # roadmap). Phase 2 ships the schema only.
117
+ # Custom JSON credential form only; user-supplied JSON is not
118
+ # applied to outbound requests by this schema.
156
119
  HTTP_CUSTOM_AUTH = CredentialSchema.build do
157
120
  display_name "Custom Auth"
158
- documentation_url "httprequest"
121
+ documentation_url "https://github.com/jackhelio/connectors/blob/main/CONNECTORS_FRAMEWORK.md#6-built-in-base-credential-types"
159
122
  generic_auth!
160
123
 
161
124
  field :json,