connectors 0.1.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 (112) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +35 -0
  3. data/CONNECTORS_FRAMEWORK.md +799 -0
  4. data/CONTRIBUTING.md +60 -0
  5. data/MCP_CLIENT.md +168 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +146 -0
  8. data/app/connectors/clickup/connector.rb +13 -0
  9. data/app/connectors/gmail/api.rb +114 -0
  10. data/app/connectors/gmail/connector.rb +921 -0
  11. data/app/connectors/gmail/mime_builder.rb +261 -0
  12. data/app/connectors/gmail/mime_parser.rb +106 -0
  13. data/app/connectors/gmail/polling.rb +154 -0
  14. data/app/connectors/remote_mcp/connector.rb +18 -0
  15. data/app/connectors/resend/connector.rb +218 -0
  16. data/app/controllers/concerns/connectors/grant_access.rb +43 -0
  17. data/app/controllers/connectors/actions_controller.rb +66 -0
  18. data/app/controllers/connectors/application_controller.rb +5 -0
  19. data/app/controllers/connectors/credentials_controller.rb +245 -0
  20. data/app/controllers/connectors/grants_controller.rb +123 -0
  21. data/app/controllers/connectors/mcp_controller.rb +88 -0
  22. data/app/controllers/connectors/oauth_controller.rb +134 -0
  23. data/app/controllers/connectors/types_controller.rb +144 -0
  24. data/app/controllers/connectors/webhooks_controller.rb +105 -0
  25. data/app/jobs/connectors/application_job.rb +4 -0
  26. data/app/jobs/connectors/deliver_webhook_job.rb +27 -0
  27. data/app/jobs/connectors/poll_job.rb +49 -0
  28. data/app/models/connectors/application_record.rb +5 -0
  29. data/app/models/connectors/credential_share.rb +31 -0
  30. data/app/models/connectors/grant.rb +103 -0
  31. data/app/models/connectors/mcp_authorization.rb +6 -0
  32. data/app/models/connectors/mcp_interaction.rb +6 -0
  33. data/app/models/connectors/poll_state.rb +17 -0
  34. data/app/models/connectors/webhook_event.rb +19 -0
  35. data/config/routes.rb +68 -0
  36. data/db/migrate/20260518210324_create_connectors_grants.rb +49 -0
  37. data/db/migrate/20260518214609_create_connectors_webhook_events.rb +35 -0
  38. data/db/migrate/20260521140000_create_connectors_credential_shares.rb +26 -0
  39. data/db/migrate/20260922120000_create_connectors_poll_states.rb +12 -0
  40. data/db/migrate/20260922130000_create_connectors_mcp_transactions.rb +18 -0
  41. data/docs/adding-connectors.md +92 -0
  42. data/docs/architecture.md +71 -0
  43. data/docs/releasing.md +60 -0
  44. data/lib/connectors/action.rb +90 -0
  45. data/lib/connectors/action_builder.rb +125 -0
  46. data/lib/connectors/action_runner.rb +99 -0
  47. data/lib/connectors/auth/scheme/api_key.rb +51 -0
  48. data/lib/connectors/auth/scheme/oauth2.rb +23 -0
  49. data/lib/connectors/auth/scheme.rb +35 -0
  50. data/lib/connectors/auth.rb +4 -0
  51. data/lib/connectors/auth_injection.rb +65 -0
  52. data/lib/connectors/client_builder.rb +87 -0
  53. data/lib/connectors/configuration.rb +138 -0
  54. data/lib/connectors/connector.rb +565 -0
  55. data/lib/connectors/credential_schema.rb +219 -0
  56. data/lib/connectors/credential_tester.rb +85 -0
  57. data/lib/connectors/credential_type_registry.rb +162 -0
  58. data/lib/connectors/credential_types/http_auth.rb +171 -0
  59. data/lib/connectors/engine.rb +123 -0
  60. data/lib/connectors/errors.rb +88 -0
  61. data/lib/connectors/grant_policy.rb +13 -0
  62. data/lib/connectors/mcp/access.rb +50 -0
  63. data/lib/connectors/mcp/authorization.rb +177 -0
  64. data/lib/connectors/mcp/authorization_context.rb +25 -0
  65. data/lib/connectors/mcp/authorization_discovery.rb +42 -0
  66. data/lib/connectors/mcp/cancellation.rb +36 -0
  67. data/lib/connectors/mcp/client.rb +137 -0
  68. data/lib/connectors/mcp/connection_config.rb +48 -0
  69. data/lib/connectors/mcp/http.rb +108 -0
  70. data/lib/connectors/mcp/interaction.rb +82 -0
  71. data/lib/connectors/mcp/pending_transaction.rb +26 -0
  72. data/lib/connectors/mcp/protocol/2026-07-28.json +3963 -0
  73. data/lib/connectors/mcp/protocol/LICENSE +216 -0
  74. data/lib/connectors/mcp/protocol/README.md +8 -0
  75. data/lib/connectors/mcp/protocol_schema.rb +30 -0
  76. data/lib/connectors/mcp/schema.rb +45 -0
  77. data/lib/connectors/mcp/settings.rb +27 -0
  78. data/lib/connectors/mcp/token_endpoint.rb +48 -0
  79. data/lib/connectors/mcp/transport.rb +105 -0
  80. data/lib/connectors/mcp.rb +69 -0
  81. data/lib/connectors/middleware/authenticate_generic.rb +79 -0
  82. data/lib/connectors/middleware/auto_refresh.rb +71 -0
  83. data/lib/connectors/middleware/error_normalization.rb +45 -0
  84. data/lib/connectors/middleware/grant_status.rb +19 -0
  85. data/lib/connectors/middleware/pre_authentication.rb +54 -0
  86. data/lib/connectors/middleware/rate_limit.rb +40 -0
  87. data/lib/connectors/oauth/authorize_url.rb +78 -0
  88. data/lib/connectors/oauth/client_authentication.rb +24 -0
  89. data/lib/connectors/oauth/client_credentials.rb +43 -0
  90. data/lib/connectors/oauth/grant_writer.rb +73 -0
  91. data/lib/connectors/oauth/pkce.rb +32 -0
  92. data/lib/connectors/oauth/revoke.rb +72 -0
  93. data/lib/connectors/oauth/state.rb +41 -0
  94. data/lib/connectors/oauth/token_exchange.rb +69 -0
  95. data/lib/connectors/oauth/token_response.rb +40 -0
  96. data/lib/connectors/oauth.rb +4 -0
  97. data/lib/connectors/oauth1.rb +151 -0
  98. data/lib/connectors/permission_check.rb +33 -0
  99. data/lib/connectors/poll_runner.rb +50 -0
  100. data/lib/connectors/pre_authentication_helpers.rb +76 -0
  101. data/lib/connectors/registry.rb +38 -0
  102. data/lib/connectors/version.rb +3 -0
  103. data/lib/connectors/webhook_context.rb +62 -0
  104. data/lib/connectors/webhook_lifecycle.rb +69 -0
  105. data/lib/connectors/webhook_methods.rb +48 -0
  106. data/lib/connectors/webhooks/verifier.rb +31 -0
  107. data/lib/connectors/webhooks.rb +5 -0
  108. data/lib/connectors.rb +50 -0
  109. data/lib/tasks/connectors_mcp.rake +9 -0
  110. data/lib/tasks/connectors_tasks.rake +4 -0
  111. data/openapi.yaml +1099 -0
  112. metadata +263 -0
@@ -0,0 +1,219 @@
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.
6
+ #
7
+ # credentials do
8
+ # field :api_key,
9
+ # type: "string",
10
+ # display_name: "API Key",
11
+ # required: true,
12
+ # type_options: { password: true },
13
+ # placeholder: "re_...",
14
+ # description: "Generate at https://resend.com/api-keys"
15
+ # end
16
+ #
17
+ # Inheritance: a connector can `extends :oauth2` (or any other registered
18
+ # base type) and `field` calls in the block can REDECLARE inherited fields
19
+ # — 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).
22
+ 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.
26
+ ALLOWED_TYPES = %w[
27
+ string number boolean
28
+ options multiOptions
29
+ json hidden notice
30
+ ].freeze
31
+
32
+ Field = Struct.new(
33
+ :name, :display_name, :type, :default, :placeholder, :description, :hint,
34
+ :required, :no_data_expression,
35
+ :type_options, :display_options, :options,
36
+ keyword_init: true
37
+ ) do
38
+ def required?
39
+ required == true
40
+ end
41
+
42
+ def password?
43
+ (type_options || {}).any? { |k, v| k.to_s == "password" && v == true }
44
+ end
45
+
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.
49
+ def to_property
50
+ {
51
+ name: name.to_s,
52
+ displayName: display_name || name.to_s.tr("_", " ").capitalize,
53
+ type: type.to_s,
54
+ default: default,
55
+ placeholder: placeholder,
56
+ description: description,
57
+ hint: hint,
58
+ required: required == true,
59
+ noDataExpression: no_data_expression == true,
60
+ typeOptions: stringify(type_options),
61
+ displayOptions: stringify(display_options),
62
+ options: options&.map { |o| stringify(o) }
63
+ }.compact
64
+ end
65
+
66
+ private
67
+
68
+ def stringify(v)
69
+ case v
70
+ when Hash then v.each_with_object({}) { |(k, vv), out| out[k.to_s] = stringify(vv) }
71
+ when Array then v.map { |vv| stringify(vv) }
72
+ else v
73
+ end
74
+ end
75
+ end
76
+
77
+ def self.build(&block)
78
+ schema = new
79
+ schema.instance_eval(&block) if block
80
+ schema
81
+ end
82
+
83
+ def initialize
84
+ @fields = {}
85
+ @extends = []
86
+ @authenticate = nil
87
+ @generic_auth = false
88
+ @display_name = nil
89
+ @documentation_url = nil
90
+ end
91
+
92
+ # Inherit fields from one or more registered base credential schemas
93
+ # (today only `:oauth2`). Inherited fields can be REDECLARED below via
94
+ # `field` — typically as `type: "hidden", default: "..."` to lock
95
+ # provider-specific endpoints. Doubles as a getter when called with
96
+ # no args (the serializer needs to read it back).
97
+ def extends(*base_names)
98
+ return @extends if base_names.empty?
99
+ @extends = base_names.flatten.map(&:to_sym)
100
+ end
101
+
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.
106
+ def authenticate(type: nil, properties: nil)
107
+ return @authenticate if type.nil? && properties.nil?
108
+ raise ArgumentError, "authenticate type: must be :generic" unless type.to_sym == :generic
109
+ @authenticate = { "type" => "generic", "properties" => properties }
110
+ end
111
+
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.
116
+ def generic_auth!
117
+ @generic_auth = true
118
+ end
119
+
120
+ def generic_auth?
121
+ @generic_auth == true || @extends.any? { |b| CredentialTypeRegistry.fetch(b).generic_auth? rescue false }
122
+ end
123
+
124
+ # Optional metadata that lives on the credential type rather than the
125
+ # connector class. Set on base credential types (HttpBearerAuth's
126
+ # "Bearer Auth" name) — the serializer prefers these over connector-
127
+ # level fallbacks when present.
128
+ def display_name(value = nil)
129
+ return @display_name if value.nil?
130
+ @display_name = value.to_s
131
+ end
132
+
133
+ def documentation_url(value = nil)
134
+ return @documentation_url if value.nil?
135
+ @documentation_url = value.to_s
136
+ end
137
+
138
+ def field(name,
139
+ type: "string",
140
+ display_name: nil,
141
+ default: nil,
142
+ placeholder: nil,
143
+ description: nil,
144
+ hint: nil,
145
+ required: false,
146
+ no_data_expression: false,
147
+ secret: false, # convenience — sets type_options[:password] = true
148
+ type_options: nil,
149
+ display_options: nil,
150
+ options: nil)
151
+ raise ArgumentError, "unknown credential field type #{type.inspect}; allowed: #{ALLOWED_TYPES.inspect}" \
152
+ unless ALLOWED_TYPES.include?(type.to_s)
153
+
154
+ effective_type_options = (type_options || {}).dup
155
+ effective_type_options[:password] = true if secret
156
+
157
+ @fields[name.to_sym] = Field.new(
158
+ name: name.to_sym,
159
+ display_name: display_name,
160
+ type: type.to_s,
161
+ default: default,
162
+ placeholder: placeholder,
163
+ description: description,
164
+ hint: hint,
165
+ required: required,
166
+ no_data_expression: no_data_expression,
167
+ type_options: effective_type_options.empty? ? nil : effective_type_options,
168
+ display_options: display_options,
169
+ options: options
170
+ )
171
+ end
172
+
173
+ # The fields declared directly on this schema, in declaration order.
174
+ # For the full resolved form (parents + own), use `resolved_fields`.
175
+ def own_fields
176
+ @fields.values
177
+ end
178
+
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.
183
+ def resolved_fields
184
+ parent_fields = @extends.flat_map { |name| CredentialTypeRegistry.fetch(name).own_fields }
185
+ union = {}
186
+ (parent_fields + own_fields).each { |f| union[f.name] = f }
187
+ union.values
188
+ end
189
+
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.
194
+ def resolved_authenticate
195
+ return @authenticate if @authenticate
196
+ @extends.each do |name|
197
+ parent = CredentialTypeRegistry.fetch(name) rescue nil
198
+ next if parent.nil?
199
+ block = parent.resolved_authenticate
200
+ return block if block
201
+ end
202
+ nil
203
+ end
204
+
205
+ # Back-compat — older code referenced `.fields` and `.required_fields`.
206
+ alias_method :fields, :resolved_fields
207
+
208
+ def required_fields
209
+ resolved_fields.select(&:required?)
210
+ end
211
+
212
+ def validate!(credentials_hash)
213
+ data = (credentials_hash || {}).transform_keys(&:to_s)
214
+ missing = required_fields.map { |f| f.name.to_s }.reject { |k| data[k].to_s.length.positive? }
215
+ raise MissingCredentialFields.new(missing) if missing.any?
216
+ true
217
+ end
218
+ end
219
+ end
@@ -0,0 +1,85 @@
1
+ module Connectors
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).
5
+ #
6
+ # result = CredentialTester.run(grant)
7
+ # # => { status: "OK", message: "Connection successful" }
8
+ # # or
9
+ # # => { status: "Error", message: "Slack token is invalid", details: {...} }
10
+ #
11
+ # 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.
14
+ class CredentialTester
15
+ OK = "OK".freeze
16
+ ERROR = "Error".freeze
17
+
18
+ def self.run(grant)
19
+ new(grant).run
20
+ end
21
+
22
+ def initialize(grant)
23
+ @grant = grant
24
+ @klass = grant.connector.class
25
+ @cfg = @klass.test_request_config
26
+ end
27
+
28
+ def run
29
+ return inconclusive("No test_request declared for #{@klass.connector_key}") if @cfg.nil?
30
+
31
+ response = issue_request
32
+ verdict_for(response)
33
+ rescue Connectors::AuthenticationFailed => e
34
+ { status: ERROR, message: e.message }
35
+ rescue Connectors::ApiError => e
36
+ { status: ERROR, message: e.message, details: { status: e.respond_to?(:status) ? e.status : nil } }
37
+ rescue => e
38
+ { status: ERROR, message: "#{e.class}: #{e.message}" }
39
+ end
40
+
41
+ private
42
+
43
+ def issue_request
44
+ client = @grant.connector.client
45
+ client.public_send(@cfg[:method], @cfg[:url], @cfg[:query]) do |req|
46
+ (@cfg[:headers] || {}).each { |k, v| req.headers[k] = v }
47
+ end
48
+ end
49
+
50
+ def verdict_for(response)
51
+ expected = @cfg[:expect_status]
52
+ if expected && response.status.to_i != expected.to_i
53
+ return { status: ERROR, message: "Expected HTTP #{expected}, got #{response.status}" }
54
+ end
55
+
56
+ Array(@cfg[:rules]).each do |rule|
57
+ verdict = apply_rule(rule, response)
58
+ return verdict if verdict
59
+ end
60
+
61
+ { status: OK, message: "Connection successful" }
62
+ end
63
+
64
+ # Each rule short-circuits with an Error verdict when it matches the
65
+ # failure condition; returns nil to mean "this rule said nothing, keep
66
+ # going".
67
+ def apply_rule(rule, response)
68
+ case rule[:type].to_sym
69
+ when :response_code
70
+ return nil if response.status.to_i == rule[:value].to_i
71
+ { status: ERROR, message: rule[:message] || "Unexpected status #{response.status}" }
72
+ when :response_success_body
73
+ actual = response.body.is_a?(Hash) ? response.body[rule[:key].to_s] : nil
74
+ return nil unless actual == rule[:value]
75
+ { status: ERROR, message: rule[:message] || "Response body indicated failure" }
76
+ else
77
+ raise Connectors::Error, "unknown test rule type #{rule[:type].inspect}"
78
+ end
79
+ end
80
+
81
+ def inconclusive(msg)
82
+ { status: ERROR, message: msg, details: { kind: "test_not_configured" } }
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,162 @@
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`.
7
+ class CredentialTypeRegistry
8
+ class << self
9
+ def register(name, schema)
10
+ store[name.to_sym] = schema
11
+ end
12
+
13
+ def fetch(name)
14
+ store.fetch(name.to_sym) do
15
+ raise Error.new("unknown base credential type #{name.inspect}; known: #{store.keys.inspect}")
16
+ end
17
+ end
18
+
19
+ def all
20
+ store.dup
21
+ end
22
+
23
+ def clear!
24
+ store.clear
25
+ end
26
+
27
+ private
28
+
29
+ def store
30
+ @store ||= {}
31
+ end
32
+ end
33
+
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).
38
+ OAUTH2 = CredentialSchema.build do
39
+ field :grant_type,
40
+ type: "options",
41
+ display_name: "Grant Type",
42
+ default: "authorizationCode",
43
+ required: true,
44
+ description: "OAuth2 grant flow to use.",
45
+ options: [
46
+ { name: "Authorization Code", value: "authorizationCode" },
47
+ { name: "Client Credentials", value: "clientCredentials" },
48
+ { name: "Authorization Code (PKCE)", value: "pkce" }
49
+ ]
50
+
51
+ field :authorization_url,
52
+ type: "string",
53
+ display_name: "Authorization URL",
54
+ required: true,
55
+ placeholder: "https://example.com/oauth/authorize",
56
+ display_options: { show: { grant_type: %w[authorizationCode pkce] } }
57
+
58
+ field :access_token_url,
59
+ type: "string",
60
+ display_name: "Access Token URL",
61
+ required: true,
62
+ placeholder: "https://example.com/oauth/token"
63
+
64
+ field :client_id,
65
+ type: "string",
66
+ display_name: "Client ID",
67
+ required: true
68
+
69
+ field :client_secret,
70
+ type: "string",
71
+ display_name: "Client Secret",
72
+ required: true,
73
+ secret: true
74
+
75
+ field :scope,
76
+ type: "string",
77
+ display_name: "Scope",
78
+ default: "",
79
+ description: "Space-separated list of OAuth scopes to request."
80
+
81
+ field :auth_query_parameters,
82
+ type: "string",
83
+ display_name: "Auth URI Query Parameters",
84
+ default: "",
85
+ description: "Additional query parameters appended to the authorize URL " \
86
+ "(e.g., `access_type=offline&prompt=consent`)."
87
+
88
+ field :authentication,
89
+ type: "options",
90
+ display_name: "Authentication",
91
+ default: "header",
92
+ description: "How the client_id / client_secret are sent during token exchange.",
93
+ options: [
94
+ { name: "Header", value: "header" },
95
+ { name: "Body", value: "body" }
96
+ ]
97
+
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.
102
+ field :jwe_enabled,
103
+ type: "boolean",
104
+ display_name: "Encrypted Tokens (JWE)",
105
+ default: false,
106
+ description: "Whether the IdP returns tokens encrypted as JWE to the public key at this " \
107
+ "instance's JWKS endpoint."
108
+ field :jwks_uri,
109
+ type: "string",
110
+ display_name: "JWKS URI",
111
+ default: "",
112
+ description: "Provide this URL to your IdP so it can fetch the public key used to " \
113
+ "encrypt access and ID tokens for this instance.",
114
+ display_options: { show: { jwe_enabled: [ true ] } }
115
+ end
116
+
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.
123
+ OAUTH1 = CredentialSchema.build do
124
+ display_name "OAuth1 API"
125
+ documentation_url "httprequest"
126
+ generic_auth!
127
+
128
+ field :authorization_url,
129
+ type: "string",
130
+ display_name: "Authorization URL",
131
+ required: true
132
+ field :access_token_url,
133
+ type: "string",
134
+ display_name: "Access Token URL",
135
+ required: true
136
+ field :request_token_url,
137
+ type: "string",
138
+ display_name: "Request Token URL",
139
+ required: true
140
+ field :consumer_key,
141
+ type: "string",
142
+ display_name: "Consumer Key",
143
+ required: true,
144
+ secret: true
145
+ field :consumer_secret,
146
+ type: "string",
147
+ display_name: "Consumer Secret",
148
+ required: true,
149
+ secret: true
150
+ field :signature_method,
151
+ type: "options",
152
+ display_name: "Signature Method",
153
+ required: true,
154
+ default: "HMAC-SHA1",
155
+ options: [
156
+ { name: "HMAC-SHA1", value: "HMAC-SHA1" },
157
+ { name: "HMAC-SHA256", value: "HMAC-SHA256" },
158
+ { name: "HMAC-SHA512", value: "HMAC-SHA512" }
159
+ ]
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,171 @@
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`.
18
+ 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.
24
+ HTTP_BASIC_AUTH = CredentialSchema.build do
25
+ display_name "Basic Auth"
26
+ documentation_url "httprequest"
27
+ generic_auth!
28
+
29
+ field :user,
30
+ type: "string",
31
+ display_name: "User",
32
+ default: ""
33
+ field :password,
34
+ type: "string",
35
+ display_name: "Password",
36
+ default: "",
37
+ secret: true
38
+
39
+ authenticate type: :generic, properties: {
40
+ auth: { username: "={{$credentials.user}}", password: "={{$credentials.password}}" }
41
+ }
42
+ end
43
+
44
+ # --- :http_bearer_auth ------------------------------------------------
45
+ # n8n: HttpBearerAuth.credentials.ts:1-43.
46
+ HTTP_BEARER_AUTH = CredentialSchema.build do
47
+ display_name "Bearer Auth"
48
+ documentation_url "httprequest"
49
+ generic_auth!
50
+
51
+ field :token,
52
+ type: "string",
53
+ display_name: "Bearer Token",
54
+ default: "",
55
+ secret: true
56
+
57
+ field :_notice_custom_auth,
58
+ type: "notice",
59
+ display_name: 'This credential uses the "Authorization" header. ' \
60
+ 'To use a custom header, use a "Header Auth" credential instead',
61
+ default: ""
62
+
63
+ authenticate type: :generic, properties: {
64
+ headers: { "Authorization" => "=Bearer {{$credentials.token}}" }
65
+ }
66
+ end
67
+
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).
72
+ HTTP_HEADER_AUTH = CredentialSchema.build do
73
+ display_name "Header Auth"
74
+ documentation_url "httprequest"
75
+ generic_auth!
76
+
77
+ field :name,
78
+ type: "string",
79
+ display_name: "Name",
80
+ default: ""
81
+ field :value,
82
+ type: "string",
83
+ display_name: "Value",
84
+ default: "",
85
+ secret: true
86
+
87
+ field :_notice_multi,
88
+ type: "notice",
89
+ display_name: 'To send multiple headers, use a "Custom Auth" credential instead',
90
+ default: ""
91
+
92
+ authenticate type: :generic, properties: {
93
+ headers: { "={{$credentials.name}}" => "={{$credentials.value}}" }
94
+ }
95
+ end
96
+
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.
100
+ HTTP_QUERY_AUTH = CredentialSchema.build do
101
+ display_name "Query Auth"
102
+ documentation_url "httprequest"
103
+ generic_auth!
104
+
105
+ field :name,
106
+ type: "string",
107
+ display_name: "Name",
108
+ default: ""
109
+ field :value,
110
+ type: "string",
111
+ display_name: "Value",
112
+ default: "",
113
+ secret: true
114
+
115
+ authenticate type: :generic, properties: {
116
+ qs: { "={{$credentials.name}}" => "={{$credentials.value}}" }
117
+ }
118
+ end
119
+
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.
130
+ HTTP_DIGEST_AUTH = CredentialSchema.build do
131
+ display_name "Digest Auth"
132
+ documentation_url "httprequest"
133
+ generic_auth!
134
+
135
+ field :user,
136
+ type: "string",
137
+ display_name: "User",
138
+ default: ""
139
+ field :password,
140
+ type: "string",
141
+ display_name: "Password",
142
+ default: "",
143
+ secret: true
144
+
145
+ # No authenticate block — requires challenge-response middleware
146
+ # (Faraday-Digest gem or equivalent). Phase 2 ships the schema only.
147
+ end
148
+
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.
156
+ HTTP_CUSTOM_AUTH = CredentialSchema.build do
157
+ display_name "Custom Auth"
158
+ documentation_url "httprequest"
159
+ generic_auth!
160
+
161
+ field :json,
162
+ type: "json",
163
+ display_name: "JSON",
164
+ required: true,
165
+ default: "",
166
+ description: "Use JSON to specify authentication values for headers, body and qs.",
167
+ placeholder: '{ "headers": { "key" : "value" }, "body": { "key": "value" }, "qs": { "key": "value" } }',
168
+ type_options: { redactJsonLeaves: true }
169
+ end
170
+ end
171
+ end