app_bridge 4.0.0-x86_64-linux → 5.0.0-x86_64-linux
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.
- checksums.yaml +4 -4
- data/.tool-versions +1 -1
- data/README.md +67 -0
- data/Rakefile +16 -13
- data/ext/app_bridge/docs/connection-config-schema.json +500 -0
- data/ext/app_bridge/wit/v4_1/world.wit +345 -0
- data/ext/app_bridge/wit/v5/world.wit +363 -0
- data/lib/app_bridge/{3.2 → 3.3}/app_bridge.so +0 -0
- data/lib/app_bridge/3.4/app_bridge.so +0 -0
- data/lib/app_bridge/4.0/app_bridge.so +0 -0
- data/lib/app_bridge/app.rb +10 -0
- data/lib/app_bridge/connection_config_validator.rb +101 -0
- data/lib/app_bridge/version.rb +1 -1
- data/lib/app_bridge.rb +2 -1
- data/tasks/fixtures.rake +25 -4
- metadata +25 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 42a6e98398ec928fdd633a6062f47600331ddee76382366fafbc87b5fcecc2f4
|
|
4
|
+
data.tar.gz: de9cb9e4c3169a3f9e70d57eff0a585dc7bc0620fb29e1cc7aa5aab412093728
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2dd0825cdcba66bd2862ba78937e8f5ccb58856f95c503a9fb25577c5c4f49540c3a6ff15e91c8c2a0e21a0d94062b6f60506070695d04a5cba75d8c5a55e675
|
|
7
|
+
data.tar.gz: 0aff9c1d8fe5ac5c0f85303e50dc13df59848d11e5559b9cf86a197af01a93d74ddd813fa640d32a9959e65ab27efa01ce792827bbae23dfe8d47d4fceba25f9
|
data/.tool-versions
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
ruby 3.4.
|
|
1
|
+
ruby 3.4.9
|
data/README.md
CHANGED
|
@@ -103,6 +103,67 @@ AppBridge.file_uploader = ->(file_data) {
|
|
|
103
103
|
|
|
104
104
|
The gem automatically replaces file data with the return value (in this example blob IDs) before returning the action response.
|
|
105
105
|
|
|
106
|
+
### Multipart Form Data
|
|
107
|
+
|
|
108
|
+
For `multipart/form-data`, you must build the body manually and set the `Content-Type` header with a boundary. In `standout:app@4.1.0` you can send raw bytes via `body-bytes`; earlier versions only support a string body.
|
|
109
|
+
|
|
110
|
+
#### In your WASM connector (Rust):
|
|
111
|
+
|
|
112
|
+
```rust
|
|
113
|
+
let boundary = "----app-bridge-boundary";
|
|
114
|
+
|
|
115
|
+
let mut body = Vec::new();
|
|
116
|
+
body.extend_from_slice(format!(
|
|
117
|
+
"--{b}\r\n\
|
|
118
|
+
Content-Disposition: form-data; name=\"metadata\"\r\n\r\n\
|
|
119
|
+
{metadata}\r\n\
|
|
120
|
+
--{b}\r\n\
|
|
121
|
+
Content-Disposition: form-data; name=\"file\"; filename=\"invoice.pdf\"\r\n\
|
|
122
|
+
Content-Type: application/pdf\r\n\r\n",
|
|
123
|
+
b = boundary,
|
|
124
|
+
metadata = metadata_json,
|
|
125
|
+
).as_bytes());
|
|
126
|
+
body.extend_from_slice(&file_bytes);
|
|
127
|
+
body.extend_from_slice(format!("\r\n--{b}--\r\n", b = boundary).as_bytes());
|
|
128
|
+
|
|
129
|
+
let response = RequestBuilder::new()
|
|
130
|
+
.method(Method::Post)
|
|
131
|
+
.url(upload_url)
|
|
132
|
+
.header("Content-Type", format!("multipart/form-data; boundary={}", boundary))
|
|
133
|
+
.body_bytes(body)
|
|
134
|
+
.send()?;
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
If you are targeting `standout:app@4.0.0` or earlier, you can only send a string body. That means multipart uploads must be base64-encoded and the receiving API must support `Content-Transfer-Encoding: base64`.
|
|
138
|
+
|
|
139
|
+
## Retry With Reference
|
|
140
|
+
|
|
141
|
+
`standout:app@4.1.0` introduces a structured retry error for actions. Connectors can return a retry error with a reference and status, and the platform can pass that back on subsequent retries via `action-context.reference-object`.
|
|
142
|
+
|
|
143
|
+
### In your WASM connector (Rust)
|
|
144
|
+
|
|
145
|
+
```rust
|
|
146
|
+
return Err(AppError {
|
|
147
|
+
code: ErrorCode::RetryWithReference(ReferenceObject {
|
|
148
|
+
reference: request_id.to_string(),
|
|
149
|
+
status: status.to_string(),
|
|
150
|
+
}),
|
|
151
|
+
message: "Background request still processing".to_string(),
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### In the Ruby platform
|
|
156
|
+
|
|
157
|
+
When the connector returns `retry-with-reference`, the bridge raises `AppBridge::RetryWithReferenceError`. You can access the structured fields:
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
rescue AppBridge::RetryWithReferenceError => e
|
|
161
|
+
e.reference # => "ref-123"
|
|
162
|
+
e.status # => "queued"
|
|
163
|
+
e.message # => "Background request still processing after 1 attempts."
|
|
164
|
+
end
|
|
165
|
+
```
|
|
166
|
+
|
|
106
167
|
## Backward Compatibility
|
|
107
168
|
|
|
108
169
|
The gem supports **multi-version WIT interfaces**, allowing connectors built against older WIT versions to continue working when the gem is updated.
|
|
@@ -244,6 +305,12 @@ impl v5::standout::app::new_feature::Host for AppState {
|
|
|
244
305
|
- **Gradual migration**: Update connectors to new WIT versions at your own pace
|
|
245
306
|
- **Type safety**: Each version's types are converted to the latest version internally
|
|
246
307
|
|
|
308
|
+
- **Connections interface** (WIT 5.0.0+): Embeds `connection-config` JSON in WASM; platform extracts it at version upload
|
|
309
|
+
|
|
310
|
+
## Connection configuration (WIT 5)
|
|
311
|
+
|
|
312
|
+
Connectors embed `connection-config.json` via `include_str!` and export it through the `connections` interface. Integrationer reads it **once when the WASM version is saved**, validates against [connection-config-schema.json](ext/app_bridge/docs/connection-config-schema.json), and stores it on the version. Runtime connection flows use the stored JSON, not a live WASM call.
|
|
313
|
+
|
|
247
314
|
## Development
|
|
248
315
|
|
|
249
316
|
To contribute or modify this gem, ensure you have the following dependencies installed:
|
data/Rakefile
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "bundler/gem_tasks"
|
|
4
|
-
require "rspec/core/rake_task"
|
|
5
|
-
|
|
6
|
-
require "rubocop/rake_task"
|
|
7
|
-
|
|
8
|
-
RuboCop::RakeTask.new
|
|
9
|
-
|
|
10
4
|
require "rb_sys/extensiontask"
|
|
11
5
|
|
|
12
6
|
task build: :compile
|
|
@@ -17,12 +11,21 @@ RbSys::ExtensionTask.new("app_bridge", GEMSPEC) do |ext|
|
|
|
17
11
|
ext.lib_dir = "lib/app_bridge"
|
|
18
12
|
end
|
|
19
13
|
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
if ENV.key?("RUBY_TARGET")
|
|
15
|
+
task default: :compile
|
|
16
|
+
else
|
|
17
|
+
require "rspec/core/rake_task"
|
|
18
|
+
require "rubocop/rake_task"
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
RSpec::Core::RakeTask.new(:spec) do |_t|
|
|
25
|
-
ENV["APP_BRIDGE_TEST_MODE"] = "1"
|
|
26
|
-
end
|
|
20
|
+
RuboCop::RakeTask.new
|
|
27
21
|
|
|
28
|
-
|
|
22
|
+
# Load all project specific rake tasks
|
|
23
|
+
Dir.glob(File.expand_path("tasks/**/*.rake", __dir__)).each { |file| load file }
|
|
24
|
+
|
|
25
|
+
# Set test mode environment variable for specs
|
|
26
|
+
RSpec::Core::RakeTask.new(:spec) do |_t|
|
|
27
|
+
ENV["APP_BRIDGE_TEST_MODE"] = "1"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
task default: %i[fixtures compile rust:test spec rubocop]
|
|
31
|
+
end
|
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:standout:schemas:connection-config:2",
|
|
4
|
+
"title": "Connection Config",
|
|
5
|
+
"description": "Declares how a connector authenticates and what runtime data the platform passes to actions/triggers. OAuth uses RFC 8414 authorization_server metadata and RFC 9728 protected_resource at the top level, with Standout-specific oauth_client credentials and platform hooks alongside. Generated at connector build time (see base-connector-tools). The platform does not fetch well-known metadata at runtime.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["strategies", "default_strategy", "runtime"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"protected_resource": {
|
|
10
|
+
"$ref": "#/$defs/protected_resource",
|
|
11
|
+
"description": "RFC 9728 protected resource metadata for the API. runtime.base_url defaults to resource when omitted."
|
|
12
|
+
},
|
|
13
|
+
"strategies": {
|
|
14
|
+
"type": "array",
|
|
15
|
+
"minItems": 0,
|
|
16
|
+
"description": "Connection strategies. An empty array disables WASM-driven connections.",
|
|
17
|
+
"items": { "$ref": "#/$defs/strategy" }
|
|
18
|
+
},
|
|
19
|
+
"default_strategy": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"description": "ID of the strategy used when connecting"
|
|
22
|
+
},
|
|
23
|
+
"connection_schema": {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"description": "JSON Schema for user input before connection (shared fallback). Field values listed in strategy storage are available as {{field}} in templates."
|
|
26
|
+
},
|
|
27
|
+
"runtime": {
|
|
28
|
+
"$ref": "#/$defs/runtime",
|
|
29
|
+
"description": "Default runtime payload merged with strategy.runtime. Headers and optional base_url override."
|
|
30
|
+
},
|
|
31
|
+
"post_connect": { "$ref": "#/$defs/post_connect_hook" }
|
|
32
|
+
},
|
|
33
|
+
"$defs": {
|
|
34
|
+
"templated_value": {
|
|
35
|
+
"description": "Literal string or template resolved by the platform. Use {{field}} for connection storage keys and {{env:NAME}} for app environment variables.",
|
|
36
|
+
"oneOf": [
|
|
37
|
+
{ "type": "string" },
|
|
38
|
+
{
|
|
39
|
+
"type": "object",
|
|
40
|
+
"required": ["template"],
|
|
41
|
+
"additionalProperties": false,
|
|
42
|
+
"properties": {
|
|
43
|
+
"template": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"description": "e.g. Bearer {{access_token}}, {{username}}:{{password}}, {{env:CLIENT_ID}}"
|
|
46
|
+
},
|
|
47
|
+
"as": {
|
|
48
|
+
"enum": ["basic_authorization"],
|
|
49
|
+
"description": "Interpolate template then encode as HTTP Basic (Basic + Base64)"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
"templated_uri": {
|
|
56
|
+
"description": "Static URI or template that resolves to a URI at runtime",
|
|
57
|
+
"oneOf": [
|
|
58
|
+
{ "type": "string", "format": "uri" },
|
|
59
|
+
{
|
|
60
|
+
"type": "object",
|
|
61
|
+
"required": ["template"],
|
|
62
|
+
"additionalProperties": false,
|
|
63
|
+
"properties": {
|
|
64
|
+
"template": {
|
|
65
|
+
"type": "string",
|
|
66
|
+
"description": "e.g. https://api.example.com/v1, https://{{subdomain}}.example.com/api, {{env:API_BASE_URL}}"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
"protected_resource": {
|
|
73
|
+
"type": "object",
|
|
74
|
+
"description": "RFC 9728 OAuth Protected Resource Metadata",
|
|
75
|
+
"required": ["resource", "authorization_servers"],
|
|
76
|
+
"properties": {
|
|
77
|
+
"resource": {
|
|
78
|
+
"type": "string",
|
|
79
|
+
"format": "uri",
|
|
80
|
+
"description": "API resource identifier (typically the API base URL)"
|
|
81
|
+
},
|
|
82
|
+
"authorization_servers": {
|
|
83
|
+
"type": "array",
|
|
84
|
+
"minItems": 1,
|
|
85
|
+
"items": { "type": "string", "format": "uri" },
|
|
86
|
+
"description": "Issuer URLs of authorization servers that can issue tokens for this resource"
|
|
87
|
+
},
|
|
88
|
+
"scopes_supported": {
|
|
89
|
+
"type": "array",
|
|
90
|
+
"items": { "type": "string" },
|
|
91
|
+
"description": "Scopes supported when accessing this resource"
|
|
92
|
+
},
|
|
93
|
+
"bearer_methods_supported": {
|
|
94
|
+
"type": "array",
|
|
95
|
+
"items": { "type": "string" },
|
|
96
|
+
"description": "e.g. header"
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
"authorization_server": {
|
|
101
|
+
"type": "object",
|
|
102
|
+
"description": "RFC 8414 OAuth Authorization Server Metadata (server capabilities and endpoints)",
|
|
103
|
+
"required": ["issuer", "token_endpoint"],
|
|
104
|
+
"properties": {
|
|
105
|
+
"issuer": {
|
|
106
|
+
"type": "string",
|
|
107
|
+
"format": "uri",
|
|
108
|
+
"description": "Authorization server issuer identifier"
|
|
109
|
+
},
|
|
110
|
+
"authorization_endpoint": {
|
|
111
|
+
"type": "string",
|
|
112
|
+
"format": "uri",
|
|
113
|
+
"description": "OAuth authorization endpoint"
|
|
114
|
+
},
|
|
115
|
+
"token_endpoint": {
|
|
116
|
+
"type": "string",
|
|
117
|
+
"format": "uri",
|
|
118
|
+
"description": "OAuth token endpoint"
|
|
119
|
+
},
|
|
120
|
+
"response_types_supported": {
|
|
121
|
+
"type": "array",
|
|
122
|
+
"items": { "type": "string" },
|
|
123
|
+
"description": "e.g. code"
|
|
124
|
+
},
|
|
125
|
+
"grant_types_supported": {
|
|
126
|
+
"type": "array",
|
|
127
|
+
"items": { "type": "string" },
|
|
128
|
+
"description": "e.g. authorization_code, refresh_token, client_credentials"
|
|
129
|
+
},
|
|
130
|
+
"scopes_supported": {
|
|
131
|
+
"type": "array",
|
|
132
|
+
"items": { "type": "string" },
|
|
133
|
+
"description": "Scopes the authorization server supports"
|
|
134
|
+
},
|
|
135
|
+
"code_challenge_methods_supported": {
|
|
136
|
+
"type": "array",
|
|
137
|
+
"items": { "type": "string" },
|
|
138
|
+
"description": "PKCE methods supported, e.g. S256"
|
|
139
|
+
},
|
|
140
|
+
"token_endpoint_auth_methods_supported": {
|
|
141
|
+
"type": "array",
|
|
142
|
+
"items": { "type": "string" },
|
|
143
|
+
"description": "e.g. client_secret_basic, client_secret_post"
|
|
144
|
+
},
|
|
145
|
+
"revocation_endpoint": {
|
|
146
|
+
"type": "string",
|
|
147
|
+
"format": "uri"
|
|
148
|
+
},
|
|
149
|
+
"introspection_endpoint": {
|
|
150
|
+
"type": "string",
|
|
151
|
+
"format": "uri"
|
|
152
|
+
},
|
|
153
|
+
"registration_endpoint": {
|
|
154
|
+
"type": "string",
|
|
155
|
+
"format": "uri"
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
"allOf": [
|
|
159
|
+
{
|
|
160
|
+
"if": {
|
|
161
|
+
"required": ["grant_types_supported"],
|
|
162
|
+
"properties": {
|
|
163
|
+
"grant_types_supported": {
|
|
164
|
+
"type": "array",
|
|
165
|
+
"contains": { "const": "client_credentials" },
|
|
166
|
+
"not": { "contains": { "const": "authorization_code" } }
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
"then": {},
|
|
171
|
+
"else": {
|
|
172
|
+
"required": ["authorization_endpoint"]
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
]
|
|
176
|
+
},
|
|
177
|
+
"oauth_client": {
|
|
178
|
+
"type": "object",
|
|
179
|
+
"description": "Standout client registration and OAuth request choices (not part of RFC 8414)",
|
|
180
|
+
"required": ["client_id", "client_secret"],
|
|
181
|
+
"properties": {
|
|
182
|
+
"grant_type": {
|
|
183
|
+
"enum": ["authorization_code", "client_credentials"],
|
|
184
|
+
"default": "authorization_code",
|
|
185
|
+
"description": "OAuth grant used when connecting"
|
|
186
|
+
},
|
|
187
|
+
"response_type": {
|
|
188
|
+
"type": "string",
|
|
189
|
+
"default": "code",
|
|
190
|
+
"description": "Override response_type; defaults to code or first response_types_supported"
|
|
191
|
+
},
|
|
192
|
+
"scopes": {
|
|
193
|
+
"type": "array",
|
|
194
|
+
"items": { "type": "string" },
|
|
195
|
+
"description": "Fixed scope list when scopes_from_field is not used"
|
|
196
|
+
},
|
|
197
|
+
"scopes_from_field": {
|
|
198
|
+
"type": "string",
|
|
199
|
+
"description": "Connection form field containing selected scopes (array of strings)"
|
|
200
|
+
},
|
|
201
|
+
"scopes_always_include": {
|
|
202
|
+
"type": "array",
|
|
203
|
+
"items": { "type": "string" },
|
|
204
|
+
"description": "Scopes always appended to scopes_from_field"
|
|
205
|
+
},
|
|
206
|
+
"client_id": {
|
|
207
|
+
"$ref": "#/$defs/templated_value",
|
|
208
|
+
"description": "OAuth client_id, e.g. {{env:OAUTH_CLIENT_ID}}"
|
|
209
|
+
},
|
|
210
|
+
"client_secret": {
|
|
211
|
+
"$ref": "#/$defs/templated_value",
|
|
212
|
+
"description": "OAuth client_secret, e.g. {{env:OAUTH_CLIENT_SECRET}}"
|
|
213
|
+
},
|
|
214
|
+
"authorization_parameters": {
|
|
215
|
+
"type": "object",
|
|
216
|
+
"description": "Extra query parameters appended to the authorization request",
|
|
217
|
+
"additionalProperties": { "type": "string" }
|
|
218
|
+
},
|
|
219
|
+
"authorization_parameters_from_form": {
|
|
220
|
+
"type": "object",
|
|
221
|
+
"description": "Map connection form field values to authorization query parameters",
|
|
222
|
+
"additionalProperties": {
|
|
223
|
+
"type": "object",
|
|
224
|
+
"additionalProperties": {
|
|
225
|
+
"type": "object",
|
|
226
|
+
"additionalProperties": { "type": "string" }
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
"token_request_method": {
|
|
231
|
+
"enum": ["POST", "GET"],
|
|
232
|
+
"default": "POST"
|
|
233
|
+
},
|
|
234
|
+
"token_request_parameters": {
|
|
235
|
+
"type": "object",
|
|
236
|
+
"description": "Extra parameters sent to the token endpoint",
|
|
237
|
+
"additionalProperties": { "type": "string" }
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
"strategy": {
|
|
242
|
+
"type": "object",
|
|
243
|
+
"required": ["id", "type"],
|
|
244
|
+
"properties": {
|
|
245
|
+
"id": { "type": "string" },
|
|
246
|
+
"title": {
|
|
247
|
+
"type": "string",
|
|
248
|
+
"description": "Label shown when the user picks authentication type before connecting"
|
|
249
|
+
},
|
|
250
|
+
"type": {
|
|
251
|
+
"enum": ["oauth2", "basic_auth", "api_key", "session", "none", "custom"]
|
|
252
|
+
},
|
|
253
|
+
"authorization_server": {
|
|
254
|
+
"$ref": "#/$defs/authorization_server",
|
|
255
|
+
"description": "RFC 8414 metadata (required when type is oauth2)"
|
|
256
|
+
},
|
|
257
|
+
"oauth_client": {
|
|
258
|
+
"$ref": "#/$defs/oauth_client",
|
|
259
|
+
"description": "Client credentials and request options (required when type is oauth2)"
|
|
260
|
+
},
|
|
261
|
+
"session": {
|
|
262
|
+
"$ref": "#/$defs/session",
|
|
263
|
+
"description": "Session login configuration (required when type is session)"
|
|
264
|
+
},
|
|
265
|
+
"token_response": {
|
|
266
|
+
"$ref": "#/$defs/token_response",
|
|
267
|
+
"description": "Maps token response fields to storage keys. Optional for oauth2 — platform defaults to standard RFC 6749 field names."
|
|
268
|
+
},
|
|
269
|
+
"refresh": {
|
|
270
|
+
"$ref": "#/$defs/refresh",
|
|
271
|
+
"description": "Token refresh policy. Optional for oauth2 — platform defaults to enabled for authorization_code."
|
|
272
|
+
},
|
|
273
|
+
"storage": {
|
|
274
|
+
"description": "Fields persisted in connection storage. Array of keys or object with optional display titles. Available as {{field}} in runtime templates.",
|
|
275
|
+
"oneOf": [
|
|
276
|
+
{
|
|
277
|
+
"type": "array",
|
|
278
|
+
"items": { "type": "string" },
|
|
279
|
+
"minItems": 1
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
"type": "object",
|
|
283
|
+
"minProperties": 1,
|
|
284
|
+
"additionalProperties": { "$ref": "#/$defs/storage_field" }
|
|
285
|
+
}
|
|
286
|
+
]
|
|
287
|
+
},
|
|
288
|
+
"connection_schema": {
|
|
289
|
+
"type": "object",
|
|
290
|
+
"description": "Strategy-specific pre-connection form schema (JSON Schema)"
|
|
291
|
+
},
|
|
292
|
+
"form_persist": {
|
|
293
|
+
"type": "object",
|
|
294
|
+
"description": "Map pre-connection form fields to account storage keys after connect",
|
|
295
|
+
"additionalProperties": {
|
|
296
|
+
"oneOf": [
|
|
297
|
+
{ "type": "string" },
|
|
298
|
+
{
|
|
299
|
+
"type": "object",
|
|
300
|
+
"required": ["as"],
|
|
301
|
+
"properties": {
|
|
302
|
+
"as": { "type": "string" },
|
|
303
|
+
"join": {
|
|
304
|
+
"type": "string",
|
|
305
|
+
"description": "Join array values before persisting, e.g. a space for OAuth scopes"
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
]
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
"runtime": {
|
|
313
|
+
"$ref": "#/$defs/runtime",
|
|
314
|
+
"description": "Strategy-specific runtime override"
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
"allOf": [
|
|
318
|
+
{
|
|
319
|
+
"if": {
|
|
320
|
+
"properties": { "type": { "const": "oauth2" } },
|
|
321
|
+
"required": ["type"]
|
|
322
|
+
},
|
|
323
|
+
"then": { "required": ["authorization_server", "oauth_client"] }
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
"if": {
|
|
327
|
+
"properties": { "type": { "const": "session" } },
|
|
328
|
+
"required": ["type"]
|
|
329
|
+
},
|
|
330
|
+
"then": { "required": ["session", "token_response"] }
|
|
331
|
+
}
|
|
332
|
+
]
|
|
333
|
+
},
|
|
334
|
+
"session": {
|
|
335
|
+
"type": "object",
|
|
336
|
+
"required": ["token_endpoint"],
|
|
337
|
+
"additionalProperties": false,
|
|
338
|
+
"description": "HTTP request that creates an API session",
|
|
339
|
+
"properties": {
|
|
340
|
+
"token_endpoint": {
|
|
341
|
+
"$ref": "#/$defs/templated_uri",
|
|
342
|
+
"description": "Login URL that returns a session token"
|
|
343
|
+
},
|
|
344
|
+
"method": {
|
|
345
|
+
"enum": ["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
346
|
+
"default": "POST"
|
|
347
|
+
},
|
|
348
|
+
"headers": {
|
|
349
|
+
"type": "object",
|
|
350
|
+
"additionalProperties": { "$ref": "#/$defs/templated_value" }
|
|
351
|
+
},
|
|
352
|
+
"body": {
|
|
353
|
+
"type": "object",
|
|
354
|
+
"additionalProperties": { "$ref": "#/$defs/templated_value" }
|
|
355
|
+
},
|
|
356
|
+
"body_format": {
|
|
357
|
+
"enum": ["json", "form"],
|
|
358
|
+
"default": "json"
|
|
359
|
+
},
|
|
360
|
+
"token_source": {
|
|
361
|
+
"enum": ["body", "header"],
|
|
362
|
+
"default": "body",
|
|
363
|
+
"description": "Whether token_response reads from JSON response body or a response header"
|
|
364
|
+
},
|
|
365
|
+
"token_header": {
|
|
366
|
+
"type": "string",
|
|
367
|
+
"description": "Response header name when token_source is header"
|
|
368
|
+
},
|
|
369
|
+
"success_status": {
|
|
370
|
+
"type": "array",
|
|
371
|
+
"items": { "type": "integer" },
|
|
372
|
+
"default": [200]
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
"token_response": {
|
|
377
|
+
"type": "object",
|
|
378
|
+
"description": "Maps token or session response fields to connection storage keys",
|
|
379
|
+
"properties": {
|
|
380
|
+
"access_token": { "type": "string" },
|
|
381
|
+
"refresh_token": { "type": "string" },
|
|
382
|
+
"expires_at": {
|
|
383
|
+
"type": "object",
|
|
384
|
+
"properties": {
|
|
385
|
+
"from": { "type": "string" },
|
|
386
|
+
"type": { "enum": ["relative_seconds", "absolute_timestamp", "iso8601"] }
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
"refresh": {
|
|
392
|
+
"type": "object",
|
|
393
|
+
"properties": {
|
|
394
|
+
"enabled": { "type": "boolean" },
|
|
395
|
+
"grant_type": { "enum": ["refresh_token", "client_credentials"] },
|
|
396
|
+
"token_endpoint": {
|
|
397
|
+
"$ref": "#/$defs/templated_uri",
|
|
398
|
+
"description": "Override authorization_server.token_endpoint for refresh"
|
|
399
|
+
},
|
|
400
|
+
"expires_latency": {
|
|
401
|
+
"type": "integer",
|
|
402
|
+
"minimum": 0,
|
|
403
|
+
"description": "Seconds to reduce token validity by before refresh (oauth2 expires_latency)"
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
"storage_field": {
|
|
408
|
+
"type": "object",
|
|
409
|
+
"description": "Storage field metadata for account display and connect forms.",
|
|
410
|
+
"additionalProperties": false,
|
|
411
|
+
"properties": {
|
|
412
|
+
"title": {
|
|
413
|
+
"type": "string",
|
|
414
|
+
"description": "Label shown for this field"
|
|
415
|
+
},
|
|
416
|
+
"description": {
|
|
417
|
+
"type": "string",
|
|
418
|
+
"description": "Help text shown on connect forms. Not persisted."
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
},
|
|
422
|
+
"post_connect_hook": {
|
|
423
|
+
"type": "object",
|
|
424
|
+
"description": "Platform-run validation after connect. Uses resolved runtime headers and base_url for the active strategy.",
|
|
425
|
+
"required": ["request"],
|
|
426
|
+
"properties": {
|
|
427
|
+
"request": {
|
|
428
|
+
"type": "object",
|
|
429
|
+
"properties": {
|
|
430
|
+
"method": {
|
|
431
|
+
"enum": ["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
432
|
+
"default": "GET"
|
|
433
|
+
},
|
|
434
|
+
"path": {
|
|
435
|
+
"type": "string",
|
|
436
|
+
"description": "Appended to runtime base_url, e.g. /v1/me"
|
|
437
|
+
},
|
|
438
|
+
"url": {
|
|
439
|
+
"$ref": "#/$defs/templated_uri",
|
|
440
|
+
"description": "Optional full URL override instead of base_url + path"
|
|
441
|
+
},
|
|
442
|
+
"body": {
|
|
443
|
+
"type": "object",
|
|
444
|
+
"additionalProperties": { "$ref": "#/$defs/templated_value" }
|
|
445
|
+
},
|
|
446
|
+
"body_format": {
|
|
447
|
+
"enum": ["json", "form"],
|
|
448
|
+
"default": "json"
|
|
449
|
+
}
|
|
450
|
+
},
|
|
451
|
+
"anyOf": [
|
|
452
|
+
{ "required": ["path"] },
|
|
453
|
+
{ "required": ["url"] }
|
|
454
|
+
]
|
|
455
|
+
},
|
|
456
|
+
"success_status": {
|
|
457
|
+
"type": "array",
|
|
458
|
+
"items": { "type": "integer" },
|
|
459
|
+
"default": [200]
|
|
460
|
+
},
|
|
461
|
+
"persist": {
|
|
462
|
+
"type": "object",
|
|
463
|
+
"description": "Map response values to connection storage keys",
|
|
464
|
+
"additionalProperties": {
|
|
465
|
+
"oneOf": [
|
|
466
|
+
{
|
|
467
|
+
"type": "string",
|
|
468
|
+
"description": "Dot path from response context, e.g. body.account.id"
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
"type": "object",
|
|
472
|
+
"required": ["from"],
|
|
473
|
+
"additionalProperties": false,
|
|
474
|
+
"properties": {
|
|
475
|
+
"from": { "type": "string" },
|
|
476
|
+
"as": { "enum": ["string"] }
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
]
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
},
|
|
484
|
+
"runtime": {
|
|
485
|
+
"type": "object",
|
|
486
|
+
"description": "Runtime payload for actions/triggers. base_url defaults to protected_resource.resource when omitted.",
|
|
487
|
+
"properties": {
|
|
488
|
+
"base_url": { "$ref": "#/$defs/templated_uri" },
|
|
489
|
+
"headers": {
|
|
490
|
+
"type": "object",
|
|
491
|
+
"additionalProperties": { "$ref": "#/$defs/templated_value" }
|
|
492
|
+
},
|
|
493
|
+
"fields": {
|
|
494
|
+
"type": "object",
|
|
495
|
+
"additionalProperties": { "$ref": "#/$defs/templated_value" }
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|