@toa.io/extensions.exposition 1.0.0-alpha.267 → 1.0.0-alpha.268

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 (115) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/components/{octets.storage → exposition.octets}/manifest.toa.yaml +2 -2
  3. package/components/exposition.stash/manifest.toa.yaml +12 -0
  4. package/components/exposition.stash/operations/count.js +7 -0
  5. package/components/identity.basic/operations/tsconfig.tsbuildinfo +1 -1
  6. package/components/identity.credentials/operations/tsconfig.tsbuildinfo +1 -1
  7. package/components/identity.federation/operations/tsconfig.tsbuildinfo +1 -1
  8. package/components/identity.keys/operations/tsconfig.tsbuildinfo +1 -1
  9. package/components/identity.otp/operations/tsconfig.tsbuildinfo +1 -1
  10. package/components/identity.passkeys/operations/tsconfig.tsbuildinfo +1 -1
  11. package/components/identity.roles/operations/tsconfig.tsbuildinfo +1 -1
  12. package/components/identity.tokens/operations/tsconfig.tsbuildinfo +1 -1
  13. package/documentation/io.md +45 -17
  14. package/documentation/notes/throttling.md +6 -0
  15. package/features/io.throttle.feature +127 -0
  16. package/features/map.feature +5 -5
  17. package/features/octets.cloudinary.feature +12 -9
  18. package/features/octets.download.feature +4 -3
  19. package/features/octets.entries.feature +5 -3
  20. package/features/octets.feature +4 -4
  21. package/features/octets.head.feature +1 -1
  22. package/features/octets.location.feature +1 -1
  23. package/features/octets.meta.feature +2 -2
  24. package/features/octets.workflows.feature +7 -7
  25. package/package.json +8 -8
  26. package/schemas/io/throttle.cos.yaml +9 -2
  27. package/source/Directive.test.ts +72 -7
  28. package/source/Directive.ts +15 -5
  29. package/source/HTTP/messages.ts +5 -1
  30. package/source/Interception.ts +8 -0
  31. package/source/RTD/Context.ts +3 -0
  32. package/source/RTD/Directives.ts +1 -1
  33. package/source/RTD/Tree.ts +17 -0
  34. package/source/RTD/factory.ts +11 -1
  35. package/source/directives/cors/CORS.ts +26 -9
  36. package/source/directives/io/Directive.ts +4 -2
  37. package/source/directives/io/IO.ts +18 -7
  38. package/source/directives/io/Throttle.ts +8 -6
  39. package/source/directives/io/lib/throttle/Configuration.test.ts +21 -0
  40. package/source/directives/io/lib/throttle/Configuration.ts +11 -4
  41. package/source/directives/io/lib/throttle/Keys.ts +12 -11
  42. package/source/directives/io/lib/throttle/Quotas.test.ts +207 -46
  43. package/source/directives/io/lib/throttle/Quotas.ts +59 -35
  44. package/source/directives/io/lib/throttle/components/Component.ts +2 -1
  45. package/source/directives/io/lib/throttle/components/Identity.ts +19 -0
  46. package/source/directives/io/lib/throttle/components/Route.ts +19 -0
  47. package/source/directives/io/lib/throttle/components/Segment.ts +19 -0
  48. package/source/directives/io/lib/throttle/components/index.ts +8 -2
  49. package/source/directives/io/schemas.test.ts +16 -0
  50. package/source/directives/octets/Octets.ts +1 -1
  51. package/source/directives/octets/workflows/Execution.ts +4 -2
  52. package/transpiled/Directive.d.ts +3 -1
  53. package/transpiled/Directive.js +12 -3
  54. package/transpiled/Directive.js.map +1 -1
  55. package/transpiled/HTTP/messages.js +4 -1
  56. package/transpiled/HTTP/messages.js.map +1 -1
  57. package/transpiled/Interception.d.ts +2 -0
  58. package/transpiled/Interception.js +4 -0
  59. package/transpiled/Interception.js.map +1 -1
  60. package/transpiled/RTD/Context.d.ts +2 -0
  61. package/transpiled/RTD/Directives.d.ts +1 -1
  62. package/transpiled/RTD/Tree.js +14 -0
  63. package/transpiled/RTD/Tree.js.map +1 -1
  64. package/transpiled/RTD/factory.js +8 -1
  65. package/transpiled/RTD/factory.js.map +1 -1
  66. package/transpiled/directives/cors/CORS.d.ts +8 -1
  67. package/transpiled/directives/cors/CORS.js +23 -9
  68. package/transpiled/directives/cors/CORS.js.map +1 -1
  69. package/transpiled/directives/io/Directive.d.ts +4 -2
  70. package/transpiled/directives/io/IO.d.ts +6 -3
  71. package/transpiled/directives/io/IO.js +11 -5
  72. package/transpiled/directives/io/IO.js.map +1 -1
  73. package/transpiled/directives/io/Throttle.d.ts +5 -3
  74. package/transpiled/directives/io/Throttle.js +6 -6
  75. package/transpiled/directives/io/Throttle.js.map +1 -1
  76. package/transpiled/directives/io/lib/throttle/Configuration.d.ts +6 -2
  77. package/transpiled/directives/io/lib/throttle/Configuration.js +5 -2
  78. package/transpiled/directives/io/lib/throttle/Configuration.js.map +1 -1
  79. package/transpiled/directives/io/lib/throttle/Keys.d.ts +4 -3
  80. package/transpiled/directives/io/lib/throttle/Keys.js +7 -10
  81. package/transpiled/directives/io/lib/throttle/Keys.js.map +1 -1
  82. package/transpiled/directives/io/lib/throttle/Quotas.d.ts +27 -8
  83. package/transpiled/directives/io/lib/throttle/Quotas.js +47 -31
  84. package/transpiled/directives/io/lib/throttle/Quotas.js.map +1 -1
  85. package/transpiled/directives/io/lib/throttle/components/Component.d.ts +2 -1
  86. package/transpiled/directives/io/lib/throttle/components/Identity.d.ts +11 -0
  87. package/transpiled/directives/io/lib/throttle/components/Identity.js +16 -0
  88. package/transpiled/directives/io/lib/throttle/components/Identity.js.map +1 -0
  89. package/transpiled/directives/io/lib/throttle/components/Route.d.ts +12 -0
  90. package/transpiled/directives/io/lib/throttle/components/Route.js +20 -0
  91. package/transpiled/directives/io/lib/throttle/components/Route.js.map +1 -0
  92. package/transpiled/directives/io/lib/throttle/components/Segment.d.ts +9 -0
  93. package/transpiled/directives/io/lib/throttle/components/Segment.js +20 -0
  94. package/transpiled/directives/io/lib/throttle/components/Segment.js.map +1 -0
  95. package/transpiled/directives/io/lib/throttle/components/index.d.ts +1 -1
  96. package/transpiled/directives/io/lib/throttle/components/index.js +7 -1
  97. package/transpiled/directives/io/lib/throttle/components/index.js.map +1 -1
  98. package/transpiled/directives/octets/Octets.js +1 -1
  99. package/transpiled/directives/octets/Octets.js.map +1 -1
  100. package/transpiled/directives/octets/workflows/Execution.js +3 -1
  101. package/transpiled/directives/octets/workflows/Execution.js.map +1 -1
  102. package/transpiled/tsconfig.tsbuildinfo +1 -1
  103. package/features/auth.issue.feature +0 -32
  104. package/source/directives/io/lib/throttle/Interval.ts +0 -40
  105. package/source/directives/io/lib/throttle/Quota.ts +0 -22
  106. package/transpiled/directives/io/lib/throttle/Interval.d.ts +0 -10
  107. package/transpiled/directives/io/lib/throttle/Interval.js +0 -39
  108. package/transpiled/directives/io/lib/throttle/Interval.js.map +0 -1
  109. package/transpiled/directives/io/lib/throttle/Quota.d.ts +0 -8
  110. package/transpiled/directives/io/lib/throttle/Quota.js +0 -22
  111. package/transpiled/directives/io/lib/throttle/Quota.js.map +0 -1
  112. /package/components/{octets.storage → exposition.octets}/operations/delete.js +0 -0
  113. /package/components/{octets.storage → exposition.octets}/operations/get.js +0 -0
  114. /package/components/{octets.storage → exposition.octets}/operations/head.js +0 -0
  115. /package/components/{octets.storage → exposition.octets}/operations/put.js +0 -0
@@ -57,9 +57,7 @@ Output restrictions are not applied to stream responses and errors.
57
57
 
58
58
  ## Throttling
59
59
 
60
- The `io:throttle` directive is used to limit the rate of the requests meeting the specified
61
- criteria.
62
- See [algorithm description](notes/throttling.md).
60
+ The `io:throttle` directive limits the rate of requests meeting the specified criteria.
63
61
 
64
62
  ```yaml
65
63
  exposition:
@@ -68,29 +66,59 @@ exposition:
68
66
  key:
69
67
  - route
70
68
  - ip
71
- - identity
72
- - segment: id
73
- - header: x-real-ip
74
- - status: [4xx, 5xx]
69
+ condition:
70
+ status: 404
75
71
  requests: 500
76
72
  interval: 30
77
73
  cooldown: 30
78
- status: 429
79
74
  ```
80
75
 
81
- Once the rate limit is reached, the server will block the requests with the specified `status`
82
- code (429 by default) until the `cooldown` period expires.
76
+ Requests are counted per `key`. Once `requests` are counted within `interval` seconds, further
77
+ requests carrying that key are answered `429 Too Many Requests` until `cooldown` seconds pass.
78
+ `interval` and `cooldown` are in seconds.
83
79
 
84
- > Currently, only `header` and `status` key components are supported.
80
+ `requests` is a budget for the **whole group** of gateway instances, not for one of them. Counting
81
+ goes through Redis, so the instances converge on one number rather than each enforcing the limit
82
+ separately. The number they act on is a lower bound: it never claims more requests than were really
83
+ made, and it can lag by up to one interval. Precise per-request enforcement is not what this is for.
85
84
 
86
- ### Request key components
85
+ The lag is worth knowing about when `requests` is small. An instance sends what it has counted once
86
+ an interval, and what the group made of it only comes back an interval later — so between its send
87
+ and that reply, an instance is going on what it alone has counted since. A limit of `1` blocks on
88
+ the first request whatever happens; a limit of a handful can be overshot in the first interval of a
89
+ burst, before any instance has a group number to act on. Set `requests` for the rate you want to
90
+ stop, not for an exact ceiling.
87
91
 
88
- Request components used to track quota usage and block requests:
92
+ An instance that cannot reach Redis keeps throttling on what it has seen itself, and keeps serving.
89
93
 
90
- - `header`: name of the header (or a list of header names), which value(s) will be used
94
+ ### Key components
91
95
 
92
- ### Response key components
96
+ What a request is counted against. Give one, or a list — a list keys on the combination, so
97
+ `[route, ip]` counts each address separately on each route.
93
98
 
94
- Response components used only to track quota usage:
99
+ - `ip` — the client address, read from `X-Forwarded-For` where it is set and not private,
100
+ and from the connection otherwise.
101
+ - `path` — the path the request came in on, `/users/1`.
102
+ - `route` — the route as declared, `/users/:id`. Every path matching the route shares one budget,
103
+ which `path` cannot do: keyed on `path`, walking ids is a way around the limit.
104
+ - `identity` — the authenticated identity. Requests carrying none share a single budget between
105
+ them, so pair it with `ip` if that matters.
106
+ - `segment: <name>` — the value bound to a named route segment, `1` for `:id` in `/users/:id`.
95
107
 
96
- - `status`: response status code (or a list of status codes)
108
+ ### Conditions
109
+
110
+ What is counted, as opposed to what it is counted against. A request that fails a condition is
111
+ served and not counted. Conditions are evaluated against the response, so they cannot decide whether
112
+ to block — only whether to count.
113
+
114
+ - `status: <code>` — count only responses with this status.
115
+
116
+ ```yaml
117
+ io:throttle:
118
+ key: ip
119
+ condition:
120
+ status: 404
121
+ requests: 20
122
+ interval: 60
123
+ cooldown: 600
124
+ ```
@@ -1,5 +1,11 @@
1
1
  # Decentralized Request Throttling
2
2
 
3
+ > **Superseded.** The design below was never implemented. `io:throttle` now counts through
4
+ > [comcount](https://github.com/temich/comcount), by way of the `stash` extension's `count`, which
5
+ > solves the same problem the same way — per-process buffers, periodically reconciled through Redis,
6
+ > never failing the request path — with the interval number derived from Redis' own clock instead of
7
+ > each node's. This note is kept for the problem statement and the forces, which have not changed.
8
+
3
9
  ## Problem
4
10
 
5
11
  To prevent an unnecessary load, API requests should be throttled when made maliciously or due to an
@@ -38,3 +38,130 @@ Feature: Request throttling
38
38
  """
39
39
  200 OK
40
40
  """
41
+
42
+ Scenario: Throttle requests to a route, whichever path they came in on
43
+ Given the `echo.beacon` is running with the following manifest:
44
+ """yaml
45
+ exposition:
46
+ /:
47
+ /:id:
48
+ io:throttle:
49
+ key: [route]
50
+ requests: 1
51
+ interval: 5
52
+ cooldown: 5
53
+ GET:
54
+ dev:stub:
55
+ hello: true
56
+ """
57
+ When the following request is received:
58
+ """
59
+ GET /echo/beacon/1/ HTTP/1.1
60
+ """
61
+ Then the following reply is sent:
62
+ """
63
+ 200 OK
64
+ """
65
+ # a different path of the same route, and the budget is already spent —
66
+ # keyed on `path` this would be 200
67
+ When the following request is received:
68
+ """
69
+ GET /echo/beacon/2/ HTTP/1.1
70
+ """
71
+ Then the following reply is sent:
72
+ """
73
+ 429 Too Many Requests
74
+ """
75
+
76
+ Scenario: Throttle requests per route segment
77
+ Given the `echo.beacon` is running with the following manifest:
78
+ """yaml
79
+ exposition:
80
+ /:
81
+ /:id:
82
+ io:throttle:
83
+ key:
84
+ - segment: id
85
+ requests: 1
86
+ interval: 5
87
+ cooldown: 5
88
+ GET:
89
+ dev:stub:
90
+ hello: true
91
+ """
92
+ When the following request is received:
93
+ """
94
+ GET /echo/beacon/1/ HTTP/1.1
95
+ """
96
+ Then the following reply is sent:
97
+ """
98
+ 200 OK
99
+ """
100
+ When the following request is received:
101
+ """
102
+ GET /echo/beacon/1/ HTTP/1.1
103
+ """
104
+ Then the following reply is sent:
105
+ """
106
+ 429 Too Many Requests
107
+ """
108
+ # another segment value, another budget
109
+ When the following request is received:
110
+ """
111
+ GET /echo/beacon/2/ HTTP/1.1
112
+ """
113
+ Then the following reply is sent:
114
+ """
115
+ 200 OK
116
+ """
117
+
118
+ Scenario: Throttle requests per identity
119
+ Given the `echo.beacon` is running with the following manifest:
120
+ """yaml
121
+ exposition:
122
+ /:
123
+ auth:anyone: true
124
+ GET:
125
+ # declared after `auth:` on the node, and mandatory families run in
126
+ # their own order regardless: `auth` has to resolve the identity
127
+ # before `io` can key a quota on it
128
+ io:throttle:
129
+ key: [identity]
130
+ requests: 1
131
+ interval: 5
132
+ cooldown: 5
133
+ endpoint: hello
134
+ """
135
+ And transient identity alice
136
+ And transient identity bob
137
+ When the following request is received:
138
+ """
139
+ GET /echo/beacon/ HTTP/1.1
140
+ host: nex.toa.io
141
+ authorization: Token ${{ alice.token }}
142
+ """
143
+ Then the following reply is sent:
144
+ """
145
+ 200 OK
146
+ """
147
+ When the following request is received:
148
+ """
149
+ GET /echo/beacon/ HTTP/1.1
150
+ host: nex.toa.io
151
+ authorization: Token ${{ alice.token }}
152
+ """
153
+ Then the following reply is sent:
154
+ """
155
+ 429 Too Many Requests
156
+ """
157
+ # bob's own budget, which requires auth to have resolved him before io keyed on him
158
+ When the following request is received:
159
+ """
160
+ GET /echo/beacon/ HTTP/1.1
161
+ host: nex.toa.io
162
+ authorization: Token ${{ bob.token }}
163
+ """
164
+ Then the following reply is sent:
165
+ """
166
+ 200 OK
167
+ """
@@ -21,9 +21,9 @@ Feature: HTTP context mapping
21
21
  Then the following reply is sent:
22
22
  """
23
23
  200 OK
24
- content-type: application/yaml
25
24
  content-language: <result>
26
- vary: accept-language, accept
25
+ vary: origin, accept-language, accept
26
+ content-type: application/yaml
27
27
 
28
28
  Hello <result>
29
29
  """
@@ -84,7 +84,7 @@ Feature: HTTP context mapping
84
84
  Then the following reply is sent:
85
85
  """
86
86
  204 No Content
87
- access-control-allow-headers: accept, authorization, content-type, etag, if-match, if-none-match, accept-language, foo
87
+ access-control-allow-headers: accept, accept-language, authorization, content-type, foo, if-match, if-none-match, origin
88
88
  """
89
89
 
90
90
  Scenario: Mapping the value of an arbitrary header
@@ -108,8 +108,8 @@ Feature: HTTP context mapping
108
108
  Then the following reply is sent:
109
109
  """
110
110
  200 OK
111
+ vary: origin, foo, accept
111
112
  content-type: application/yaml
112
- vary: foo, accept
113
113
 
114
114
  Hello bar
115
115
  """
@@ -135,8 +135,8 @@ Feature: HTTP context mapping
135
135
  Then the following reply is sent:
136
136
  """
137
137
  200 OK
138
+ vary: origin, accept
138
139
  content-type: application/yaml
139
- vary: accept
140
140
 
141
141
  Hello nex.toa.io
142
142
  """
@@ -37,8 +37,8 @@ Feature: Octets with Cloudinary storage
37
37
  content-type: application/yaml
38
38
 
39
39
  id: ${{ id }}
40
- type: image/png
41
40
  size: 473831
41
+ type: image/png
42
42
  """
43
43
  When the following request is received:
44
44
  """
@@ -49,18 +49,20 @@ Feature: Octets with Cloudinary storage
49
49
  Then the stream equals to `lenna.png` is sent with the following headers:
50
50
  """
51
51
  200 OK
52
- content-type: image/png
53
- content-length: 473831
54
52
  access-control-allow-origin: https://toa.io
53
+ content-length: 473831
54
+ content-type: image/png
55
55
  """
56
56
 
57
+ # SVG has no binary signature, so the type on upload is whatever the client states;
58
+ # on the way back it is what Cloudinary serves
57
59
  Scenario: Upload an svg
58
60
  When the stream of `sample.svg` is received with the following headers:
59
61
  """
60
62
  POST / HTTP/1.1
61
63
  host: nex.toa.io
62
64
  accept: application/yaml
63
- content-type: application/octet-stream
65
+ content-type: image/svg+xml
64
66
  """
65
67
  Then the following reply is sent:
66
68
  """
@@ -75,7 +77,8 @@ Feature: Octets with Cloudinary storage
75
77
  GET /${{ id }} HTTP/1.1
76
78
  host: nex.toa.io
77
79
  """
78
- Then the stream equals to `lenna.png` is sent with the following headers:
80
+ # Cloudinary rewrites the SVG it stores, so only the type is asserted
81
+ Then the following reply is sent:
79
82
  """
80
83
  200 OK
81
84
  content-type: image/svg+xml
@@ -140,8 +143,8 @@ Feature: Octets with Cloudinary storage
140
143
  Then the following reply is sent:
141
144
  """
142
145
  200 OK
143
- transfer-encoding: chunked
144
146
  content-type: video/mp4
147
+ transfer-encoding: chunked
145
148
  """
146
149
 
147
150
  # after a while, Cloudinary returns a content-length response
@@ -153,8 +156,8 @@ Feature: Octets with Cloudinary storage
153
156
  Then the following reply is sent:
154
157
  """
155
158
  200 OK
156
- content-type: video/mp4
157
159
  content-length: 175043
160
+ content-type: video/mp4
158
161
  """
159
162
 
160
163
  When the following request is received:
@@ -165,8 +168,8 @@ Feature: Octets with Cloudinary storage
165
168
  Then the following reply is sent:
166
169
  """
167
170
  200 OK
168
- transfer-encoding: chunked
169
171
  content-type: video/mp4
172
+ transfer-encoding: chunked
170
173
  """
171
174
 
172
175
  When the following request is received:
@@ -206,8 +209,8 @@ Feature: Octets with Cloudinary storage
206
209
  Then the following reply is sent:
207
210
  """
208
211
  206 Partial Content
209
- content-type: video/mp4
210
212
  content-length: 2
213
+ content-type: video/mp4
211
214
  """
212
215
 
213
216
  # gif
@@ -32,6 +32,7 @@ Feature: Download external resources
32
32
 
33
33
  id: ${{ id }}
34
34
  type: image/png
35
+ checksum: ${{ checksum }}
35
36
  """
36
37
 
37
38
  When the following request is received:
@@ -42,9 +43,9 @@ Feature: Download external resources
42
43
  Then the following reply is sent:
43
44
  """
44
45
  200 OK
45
- content-type: image/png
46
46
  content-length: 1288
47
- etag: "${{ id }}"
47
+ content-type: image/png
48
+ etag: "${{ checksum }}"
48
49
  """
49
50
 
50
51
  # origin is stored in the entry
@@ -185,5 +186,5 @@ Feature: Download external resources
185
186
  Then the following reply is sent:
186
187
  """
187
188
  204 No Content
188
- access-control-allow-headers: accept, authorization, content-type, etag, if-match, if-none-match, content-attributes, content-location
189
+ access-control-allow-headers: accept, authorization, content-attributes, content-location, content-type, if-match, if-none-match, origin
189
190
  """
@@ -56,6 +56,7 @@ Feature: Accessing metadata
56
56
  host: nex.toa.io
57
57
  accept: application/yaml
58
58
  content-type: application/octet-stream
59
+ content-id: lenna-ascii
59
60
  """
60
61
  And the stream of `lenna.png` is received with the following headers:
61
62
  """
@@ -63,10 +64,11 @@ Feature: Accessing metadata
63
64
  host: nex.toa.io
64
65
  accept: application/yaml
65
66
  content-type: application/octet-stream
67
+ content-id: lenna-png
66
68
  """
67
69
  When the following request is received:
68
70
  """
69
- GET /10cf16b458f759e0d617f2f3d83599ff HTTP/1.1
71
+ GET /lenna-ascii HTTP/1.1
70
72
  host: nex.toa.io
71
73
  accept: application/vnd.toa.octets.entry+yaml
72
74
  """
@@ -75,7 +77,7 @@ Feature: Accessing metadata
75
77
  200 OK
76
78
  content-type: application/yaml
77
79
 
78
- id: 10cf16b458f759e0d617f2f3d83599ff
79
- type: application/octet-stream
80
+ id: lenna-ascii
80
81
  size: 8169
82
+ type: application/octet-stream
81
83
  """
@@ -56,8 +56,8 @@ Feature: Octets directive family
56
56
  content-type: application/yaml
57
57
 
58
58
  id: ${{ id }}
59
- type: application/octet-stream
60
59
  size: 8169
60
+ type: application/octet-stream
61
61
  """
62
62
  When the following request is received:
63
63
  """
@@ -67,15 +67,15 @@ Feature: Octets directive family
67
67
  Then the stream equals to `lenna.ascii` is sent with the following headers:
68
68
  """
69
69
  200 OK
70
- content-type: application/octet-stream
71
70
  content-length: 8169
72
- etag: ${{ ETAG }}
71
+ content-type: application/octet-stream
72
+ etag: "${{ ETAG }}"
73
73
  """
74
74
  When the following request is received:
75
75
  """
76
76
  GET /${{ id }} HTTP/1.1
77
77
  host: nex.toa.io
78
- if-none-match: ${{ ETAG }}
78
+ if-none-match: "${{ ETAG }}"
79
79
  """
80
80
  Then the following reply is sent:
81
81
  """
@@ -35,6 +35,6 @@ Feature: Octets headers
35
35
  Then the following reply is sent:
36
36
  """
37
37
  200 OK
38
- content-type: application/octet-stream
39
38
  content-length: 8169
39
+ content-type: application/octet-stream
40
40
  """
@@ -71,8 +71,8 @@ Feature: Octets location
71
71
  --cut
72
72
 
73
73
  id: ${{ id }}
74
- type: application/octet-stream
75
74
  size: 8169
75
+ type: application/octet-stream
76
76
  --cut
77
77
 
78
78
  step: echo
@@ -85,8 +85,8 @@ Feature: Octets metadata
85
85
  200 OK
86
86
 
87
87
  id: ${{ id }}
88
- type: application/octet-stream
89
88
  size: 8169
89
+ type: application/octet-stream
90
90
  checksum: 10cf16b458f759e0d617f2f3d83599ff
91
91
  attributes:
92
92
  foo: 'true'
@@ -111,6 +111,6 @@ Feature: Octets metadata
111
111
  Then the following reply is sent:
112
112
  """
113
113
  204 No Content
114
+ access-control-allow-headers: accept, authorization, content-attributes, content-location, content-type, if-match, if-none-match, origin
114
115
  access-control-allow-origin: https://example.com
115
- access-control-allow-headers: accept, authorization, content-type, etag, if-match, if-none-match, content-attributes
116
116
  """
@@ -39,14 +39,14 @@ Feature: Octets storage workflows
39
39
  --cut
40
40
 
41
41
  step: add-bar
42
+ status: completed
42
43
  output:
43
44
  bar: baz
44
- status: completed
45
45
  --cut
46
46
 
47
47
  step: add-baz
48
48
  status: completed
49
- output:
49
+ output:
50
50
  add-foo:
51
51
  foo: bar
52
52
  add-bar:
@@ -115,8 +115,8 @@ Feature: Octets storage workflows
115
115
 
116
116
  --cut
117
117
  id: ${{ id }}
118
- type: application/octet-stream
119
118
  size: 8169
119
+ type: application/octet-stream
120
120
  --cut
121
121
 
122
122
  step: add-foo
@@ -124,10 +124,10 @@ Feature: Octets storage workflows
124
124
  --cut
125
125
 
126
126
  step: add-bar
127
+ status: completed
127
128
  error:
128
129
  code: ERROR
129
130
  message: Something went wrong
130
- status: completed
131
131
  --cut--
132
132
  """
133
133
 
@@ -281,8 +281,8 @@ Feature: Octets storage workflows
281
281
  --cut
282
282
 
283
283
  id: ${{ id }}
284
- type: application/octet-stream
285
284
  size: 8169
285
+ type: application/octet-stream
286
286
  --cut
287
287
 
288
288
  step: concat
@@ -320,8 +320,8 @@ Feature: Octets storage workflows
320
320
  --cut
321
321
 
322
322
  id: ${{ id }}
323
- type: application/octet-stream
324
323
  size: 8169
324
+ type: application/octet-stream
325
325
  --cut
326
326
 
327
327
  step: authority
@@ -361,8 +361,8 @@ Feature: Octets storage workflows
361
361
  --cut
362
362
 
363
363
  id: ${{ id }}
364
- type: application/octet-stream
365
364
  size: 8169
365
+ type: application/octet-stream
366
366
  --cut
367
367
 
368
368
  step: identity
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toa.io/extensions.exposition",
3
- "version": "1.0.0-alpha.267",
3
+ "version": "1.0.0-alpha.268",
4
4
  "description": "Toa Exposition",
5
5
  "author": "temich <tema.gurtovoy@gmail.com>",
6
6
  "homepage": "https://github.com/toa-io/toa#readme",
@@ -17,20 +17,20 @@
17
17
  "access": "public"
18
18
  },
19
19
  "dependencies": {
20
- "@simplewebauthn/server": "13.3.2",
20
+ "@simplewebauthn/server": "13.3.3",
21
21
  "@toa.io/core": "1.0.0-alpha.266",
22
22
  "@toa.io/generic": "1.0.0-alpha.254",
23
23
  "@toa.io/schemas": "1.0.0-alpha.263",
24
24
  "bcryptjs": "3.0.3",
25
- "content-type": "2.0.0",
25
+ "content-type": "2.1.0",
26
26
  "error-value": "0.4.4",
27
- "jose": "6.2.8",
28
- "js-yaml": "5.2.3",
27
+ "jose": "6.2.10",
28
+ "js-yaml": "5.4.1",
29
29
  "lru-cache": "11.5.2",
30
30
  "matchacho": "0.6.0",
31
31
  "minimatch": "10.2.6",
32
- "msgpackr": "2.0.5",
33
- "negotiator": "1.0.0",
32
+ "msgpackr": "2.1.0",
33
+ "negotiator": "1.1.0",
34
34
  "openspan": "1.0.0-alpha.264",
35
35
  "paseto": "3.1.4"
36
36
  },
@@ -62,5 +62,5 @@
62
62
  },
63
63
  "testEnvironment": "node"
64
64
  },
65
- "gitHead": "d2d1a69ee30f426975a835f77ed41bfc1ce4785e"
65
+ "gitHead": "0c2fe0acb44653a68b12600151d4d25f46a444be"
66
66
  }
@@ -1,7 +1,14 @@
1
1
  definitions:
2
2
  component: &component
3
- type: string
4
- enum: [ip, path]
3
+ anyOf:
4
+ - type: string
5
+ enum: [ip, path, route, identity]
6
+ - type: object
7
+ properties:
8
+ segment:
9
+ type: string
10
+ required: [segment]
11
+ additionalProperties: false
5
12
  condition: &condition
6
13
  type: object
7
14
  properties: