@toa.io/extensions.exposition 1.0.0-alpha.278 → 1.0.0-alpha.282

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 (243) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/components/identity.basic/events/principal.js +5 -1
  3. package/components/identity.basic/manifest.toa.yaml +11 -1
  4. package/components/identity.basic/operations/authenticate.js +9 -5
  5. package/components/identity.basic/operations/authenticate.js.map +1 -1
  6. package/components/identity.basic/operations/check.js +2 -1
  7. package/components/identity.basic/operations/check.js.map +1 -1
  8. package/components/identity.basic/operations/incept.js +4 -5
  9. package/components/identity.basic/operations/incept.js.map +1 -1
  10. package/components/identity.basic/operations/lib/credentials.d.ts +5 -0
  11. package/components/identity.basic/operations/lib/credentials.js +12 -0
  12. package/components/identity.basic/operations/lib/credentials.js.map +1 -0
  13. package/components/identity.basic/operations/transit.d.ts +2 -0
  14. package/components/identity.basic/operations/transit.js +12 -2
  15. package/components/identity.basic/operations/transit.js.map +1 -1
  16. package/components/identity.basic/operations/types.d.ts +11 -1
  17. package/components/identity.basic/source/authenticate.ts +11 -5
  18. package/components/identity.basic/source/check.ts +2 -1
  19. package/components/identity.basic/source/incept.ts +5 -5
  20. package/components/identity.basic/source/lib/credentials.test.ts +18 -0
  21. package/components/identity.basic/source/lib/credentials.ts +13 -0
  22. package/components/identity.basic/source/transit.ts +16 -4
  23. package/components/identity.basic/source/types.ts +13 -1
  24. package/components/identity.basic/tsconfig.tsbuildinfo +1 -1
  25. package/components/identity.federation/events/principal.js +1 -0
  26. package/components/identity.federation/manifest.toa.yaml +5 -1
  27. package/components/identity.federation/operations/authenticate.js +3 -3
  28. package/components/identity.federation/operations/authenticate.js.map +1 -1
  29. package/components/identity.federation/operations/create.js +5 -1
  30. package/components/identity.federation/operations/create.js.map +1 -1
  31. package/components/identity.federation/operations/lib/decode.js +5 -1
  32. package/components/identity.federation/operations/lib/decode.js.map +1 -1
  33. package/components/identity.federation/operations/lib/exchange.js +3 -3
  34. package/components/identity.federation/operations/lib/exchange.js.map +1 -1
  35. package/components/identity.federation/operations/list.js +2 -1
  36. package/components/identity.federation/operations/list.js.map +1 -1
  37. package/components/identity.federation/operations/types/configuration.d.ts +2 -1
  38. package/components/identity.federation/source/authenticate.ts +3 -3
  39. package/components/identity.federation/source/create.ts +5 -1
  40. package/components/identity.federation/source/lib/decode.ts +5 -1
  41. package/components/identity.federation/source/lib/exchange.ts +3 -3
  42. package/components/identity.federation/source/list.test.ts +1 -1
  43. package/components/identity.federation/source/list.ts +2 -1
  44. package/components/identity.federation/source/types/configuration.ts +2 -1
  45. package/components/identity.federation/tsconfig.tsbuildinfo +1 -1
  46. package/components/identity.keys/manifest.toa.yaml +18 -1
  47. package/components/identity.keys/operations/disable.d.ts +5 -0
  48. package/components/identity.keys/operations/disable.js +5 -0
  49. package/components/identity.keys/operations/disable.js.map +1 -0
  50. package/components/identity.keys/operations/revoke.d.ts +15 -0
  51. package/components/identity.keys/operations/revoke.js +12 -0
  52. package/components/identity.keys/operations/revoke.js.map +1 -0
  53. package/components/identity.keys/receivers/identity.bans.created.js +1 -0
  54. package/components/identity.keys/receivers/identity.bans.updated.js +1 -0
  55. package/components/identity.keys/receivers/identity.roles.deleted.js +1 -0
  56. package/components/identity.keys/source/disable.ts +9 -0
  57. package/components/identity.keys/source/revoke.ts +30 -0
  58. package/components/identity.keys/tsconfig.tsbuildinfo +1 -1
  59. package/components/identity.otp/manifest.toa.yaml +6 -0
  60. package/components/identity.otp/operations/authenticate.js +13 -1
  61. package/components/identity.otp/operations/authenticate.js.map +1 -1
  62. package/components/identity.otp/operations/issue.js +3 -2
  63. package/components/identity.otp/operations/issue.js.map +1 -1
  64. package/components/identity.otp/operations/lib/Context.d.ts +1 -0
  65. package/components/identity.otp/source/authenticate.ts +19 -1
  66. package/components/identity.otp/source/issue.ts +3 -2
  67. package/components/identity.otp/source/lib/Context.ts +1 -0
  68. package/components/identity.otp/tsconfig.tsbuildinfo +1 -1
  69. package/components/identity.passkeys/operations/authenticate.js +2 -1
  70. package/components/identity.passkeys/operations/authenticate.js.map +1 -1
  71. package/components/identity.passkeys/operations/challenge.js +3 -3
  72. package/components/identity.passkeys/operations/challenge.js.map +1 -1
  73. package/components/identity.passkeys/operations/delete.js +2 -1
  74. package/components/identity.passkeys/operations/delete.js.map +1 -1
  75. package/components/identity.passkeys/operations/list.js +2 -1
  76. package/components/identity.passkeys/operations/list.js.map +1 -1
  77. package/components/identity.passkeys/source/authenticate.ts +2 -1
  78. package/components/identity.passkeys/source/challenge.ts +3 -3
  79. package/components/identity.passkeys/source/delete.ts +2 -1
  80. package/components/identity.passkeys/source/list.ts +2 -1
  81. package/components/identity.passkeys/tsconfig.tsbuildinfo +1 -1
  82. package/components/identity.roles/manifest.toa.yaml +4 -0
  83. package/components/identity.roles/operations/grant.js +5 -2
  84. package/components/identity.roles/operations/grant.js.map +1 -1
  85. package/components/identity.roles/source/grant.ts +6 -2
  86. package/components/identity.roles/tsconfig.tsbuildinfo +1 -1
  87. package/components/identity.tokens/manifest.toa.yaml +3 -0
  88. package/components/identity.tokens/operations/authenticate.js +4 -3
  89. package/components/identity.tokens/operations/authenticate.js.map +1 -1
  90. package/components/identity.tokens/operations/decrypt.js +7 -1
  91. package/components/identity.tokens/operations/decrypt.js.map +1 -1
  92. package/components/identity.tokens/operations/encrypt.js +3 -2
  93. package/components/identity.tokens/operations/encrypt.js.map +1 -1
  94. package/components/identity.tokens/operations/issue.js +2 -1
  95. package/components/identity.tokens/operations/issue.js.map +1 -1
  96. package/components/identity.tokens/operations/lib/types.d.ts +3 -0
  97. package/components/identity.tokens/source/authenticate.test.ts +26 -2
  98. package/components/identity.tokens/source/authenticate.ts +4 -3
  99. package/components/identity.tokens/source/decrypt.test.ts +35 -1
  100. package/components/identity.tokens/source/decrypt.ts +10 -1
  101. package/components/identity.tokens/source/encrypt.test.ts +15 -0
  102. package/components/identity.tokens/source/encrypt.ts +4 -2
  103. package/components/identity.tokens/source/issue.ts +2 -1
  104. package/components/identity.tokens/source/lib/types.ts +4 -0
  105. package/components/identity.tokens/tsconfig.tsbuildinfo +1 -1
  106. package/documentation/authorities.md +3 -0
  107. package/documentation/components.md +31 -11
  108. package/documentation/identity.md +38 -6
  109. package/documentation/io.md +2 -2
  110. package/documentation/ip.md +24 -0
  111. package/documentation/passkeys.md +6 -0
  112. package/features/auth.assert.feature +2 -4
  113. package/features/auth.claims.feature +1 -0
  114. package/features/auth.incept.feature +46 -0
  115. package/features/authorities.feature +39 -0
  116. package/features/authorities.federation.feature +1 -0
  117. package/features/identity.bans.feature +63 -0
  118. package/features/identity.basic.feature +95 -3
  119. package/features/identity.feature +151 -0
  120. package/features/identity.federation.feature +5 -0
  121. package/features/identity.otp.feature +41 -0
  122. package/features/identity.roles.feature +252 -0
  123. package/features/identity.tokens.custom.feature +145 -0
  124. package/features/map.feature +2 -0
  125. package/features/steps/Gateway.ts +8 -2
  126. package/features/steps/Realtime.ts +1 -1
  127. package/package.json +5 -5
  128. package/readme.md +3 -0
  129. package/schemas/annotation.cos.yaml +16 -0
  130. package/source/Annotation.ts +14 -0
  131. package/source/Composition.ts +5 -5
  132. package/source/Directive.test.ts +3 -1
  133. package/source/Directive.ts +6 -2
  134. package/source/Endpoint.ts +4 -2
  135. package/source/Factory.ts +12 -9
  136. package/source/HTTP/Context.ts +12 -2
  137. package/source/HTTP/Server.ts +16 -3
  138. package/source/HTTP/address.test.ts +22 -0
  139. package/source/HTTP/address.ts +17 -0
  140. package/source/Query.test.ts +15 -1
  141. package/source/Query.ts +3 -1
  142. package/source/RTD/Directives.ts +10 -1
  143. package/source/Remotes.test.ts +7 -12
  144. package/source/Remotes.ts +5 -5
  145. package/source/const.ts +3 -0
  146. package/source/deployment.ts +7 -1
  147. package/source/directives/auth/Assert.ts +3 -0
  148. package/source/directives/auth/Authorization.test.ts +24 -0
  149. package/source/directives/auth/Authorization.ts +80 -11
  150. package/source/directives/auth/Incept.ts +9 -4
  151. package/source/directives/auth/Role.test.ts +1 -1
  152. package/source/directives/auth/Role.ts +2 -1
  153. package/source/directives/auth/schemes.test.ts +14 -0
  154. package/source/directives/auth/schemes.ts +14 -0
  155. package/source/directives/auth/types.ts +3 -0
  156. package/source/directives/io/IO.ts +10 -3
  157. package/source/directives/io/lib/throttle/Keys.ts +10 -3
  158. package/source/directives/io/lib/throttle/Quotas.test.ts +14 -4
  159. package/source/directives/io/lib/throttle/Quotas.ts +15 -2
  160. package/source/directives/io/lib/throttle/Sync.test.ts +7 -9
  161. package/source/directives/io/lib/throttle/Sync.ts +4 -7
  162. package/source/directives/io/lib/throttle/Warning.test.ts +32 -0
  163. package/source/directives/io/lib/throttle/Warning.ts +30 -0
  164. package/source/directives/io/lib/throttle/components/Component.ts +2 -1
  165. package/source/directives/io/lib/throttle/components/IP.ts +19 -28
  166. package/source/directives/io/lib/throttle/index.ts +1 -0
  167. package/source/redact.test.ts +27 -0
  168. package/source/redact.ts +21 -0
  169. package/source/schemas.test.ts +17 -0
  170. package/transpiled/Annotation.d.ts +10 -0
  171. package/transpiled/Composition.d.ts +3 -3
  172. package/transpiled/Composition.js +4 -4
  173. package/transpiled/Composition.js.map +1 -1
  174. package/transpiled/Directive.d.ts +3 -2
  175. package/transpiled/Directive.js +3 -1
  176. package/transpiled/Directive.js.map +1 -1
  177. package/transpiled/Endpoint.js +3 -2
  178. package/transpiled/Endpoint.js.map +1 -1
  179. package/transpiled/Factory.d.ts +3 -3
  180. package/transpiled/Factory.js +11 -8
  181. package/transpiled/Factory.js.map +1 -1
  182. package/transpiled/HTTP/Context.d.ts +4 -0
  183. package/transpiled/HTTP/Context.js +7 -2
  184. package/transpiled/HTTP/Context.js.map +1 -1
  185. package/transpiled/HTTP/Server.d.ts +5 -0
  186. package/transpiled/HTTP/Server.js +8 -3
  187. package/transpiled/HTTP/Server.js.map +1 -1
  188. package/transpiled/HTTP/address.d.ts +7 -0
  189. package/transpiled/HTTP/address.js +14 -0
  190. package/transpiled/HTTP/address.js.map +1 -0
  191. package/transpiled/Query.js +3 -1
  192. package/transpiled/Query.js.map +1 -1
  193. package/transpiled/RTD/Directives.d.ts +9 -1
  194. package/transpiled/Remotes.d.ts +3 -3
  195. package/transpiled/Remotes.js +4 -4
  196. package/transpiled/Remotes.js.map +1 -1
  197. package/transpiled/const.d.ts +2 -0
  198. package/transpiled/const.js +2 -0
  199. package/transpiled/const.js.map +1 -1
  200. package/transpiled/deployment.js +4 -0
  201. package/transpiled/deployment.js.map +1 -1
  202. package/transpiled/directives/auth/Assert.js +2 -0
  203. package/transpiled/directives/auth/Assert.js.map +1 -1
  204. package/transpiled/directives/auth/Authorization.d.ts +8 -0
  205. package/transpiled/directives/auth/Authorization.js +61 -11
  206. package/transpiled/directives/auth/Authorization.js.map +1 -1
  207. package/transpiled/directives/auth/Incept.d.ts +3 -1
  208. package/transpiled/directives/auth/Incept.js +8 -4
  209. package/transpiled/directives/auth/Incept.js.map +1 -1
  210. package/transpiled/directives/auth/Role.js +2 -1
  211. package/transpiled/directives/auth/Role.js.map +1 -1
  212. package/transpiled/directives/auth/schemes.d.ts +10 -0
  213. package/transpiled/directives/auth/schemes.js +12 -0
  214. package/transpiled/directives/auth/schemes.js.map +1 -1
  215. package/transpiled/directives/auth/types.d.ts +2 -0
  216. package/transpiled/directives/io/IO.d.ts +4 -1
  217. package/transpiled/directives/io/IO.js +7 -3
  218. package/transpiled/directives/io/IO.js.map +1 -1
  219. package/transpiled/directives/io/lib/throttle/Keys.d.ts +2 -1
  220. package/transpiled/directives/io/lib/throttle/Keys.js +7 -2
  221. package/transpiled/directives/io/lib/throttle/Keys.js.map +1 -1
  222. package/transpiled/directives/io/lib/throttle/Quotas.d.ts +3 -2
  223. package/transpiled/directives/io/lib/throttle/Quotas.js +8 -1
  224. package/transpiled/directives/io/lib/throttle/Quotas.js.map +1 -1
  225. package/transpiled/directives/io/lib/throttle/Sync.d.ts +2 -3
  226. package/transpiled/directives/io/lib/throttle/Sync.js +1 -3
  227. package/transpiled/directives/io/lib/throttle/Sync.js.map +1 -1
  228. package/transpiled/directives/io/lib/throttle/Warning.d.ts +11 -0
  229. package/transpiled/directives/io/lib/throttle/Warning.js +24 -0
  230. package/transpiled/directives/io/lib/throttle/Warning.js.map +1 -0
  231. package/transpiled/directives/io/lib/throttle/components/Component.d.ts +2 -1
  232. package/transpiled/directives/io/lib/throttle/components/IP.d.ts +10 -2
  233. package/transpiled/directives/io/lib/throttle/components/IP.js +18 -23
  234. package/transpiled/directives/io/lib/throttle/components/IP.js.map +1 -1
  235. package/transpiled/directives/io/lib/throttle/index.d.ts +1 -0
  236. package/transpiled/directives/io/lib/throttle/index.js +1 -0
  237. package/transpiled/directives/io/lib/throttle/index.js.map +1 -1
  238. package/transpiled/redact.d.ts +5 -0
  239. package/transpiled/redact.js +17 -0
  240. package/transpiled/redact.js.map +1 -0
  241. package/tsconfig.tsbuildinfo +1 -1
  242. package/components/exposition.atom/manifest.toa.yaml +0 -21
  243. package/components/exposition.atom/operations/meter.js +0 -5
@@ -99,3 +99,44 @@ Feature: OTP authentication
99
99
 
100
100
  id: ${{ bob.id }}
101
101
  """
102
+
103
+ Scenario: Guessing OTP codes
104
+ Given the `identity.otp` configuration:
105
+ """yaml
106
+ lifetime: 60
107
+ attempts: 2
108
+ """
109
+ And the Gateway is running
110
+ And OTP for `alice` in `nex` authority is issued
111
+ # alice:wrong
112
+ When the following request is received:
113
+ """
114
+ GET /identity/ HTTP/1.1
115
+ host: nex.toa.io
116
+ authorization: OTP YWxpY2U6d3Jvbmc=
117
+ """
118
+ Then the following reply is sent:
119
+ """
120
+ 401 Unauthorized
121
+ """
122
+ When the following request is received:
123
+ """
124
+ GET /identity/ HTTP/1.1
125
+ host: nex.toa.io
126
+ authorization: OTP YWxpY2U6d3Jvbmc=
127
+ """
128
+ Then the following reply is sent:
129
+ """
130
+ 401 Unauthorized
131
+ """
132
+ # the attempts are spent, the code itself is refused
133
+ When the following request is received:
134
+ """
135
+ GET /identity/ HTTP/1.1
136
+ host: nex.toa.io
137
+ authorization: OTP ${{ alice.otp }}
138
+ """
139
+ Then the following reply is sent:
140
+ """
141
+ 401 Unauthorized
142
+ """
@@ -314,3 +314,255 @@ Feature: Roles management
314
314
  """
315
315
  500 Internal Server Error
316
316
  """
317
+
318
+ Scenario: Role changes reach a Token holder at refresh
319
+ # root:secret
320
+ # user:pass
321
+ Given the `identity.basic` database contains:
322
+ | _id | authority | username | password |
323
+ | 72cf9b0ab0ac4ab2b8036e4e940ddcae | nex | root | $2b$10$Qq/qnyyU5wjrbDXyWok14OnqAZv/z.pLhz.UddatjI6eHU/rFof4i |
324
+ | 4344518184ad44228baffce7a44fd0b1 | nex | user | $2b$10$JoiAQUS7tzobDAFIDBWhWeEIJv933dQetyjRzSmfQGaJE5ZlJbmYy |
325
+ And the `identity.roles` database contains:
326
+ | _id | identity | role |
327
+ | 9c4702490ff84f2a9e1b1da2ab64bdd4 | 72cf9b0ab0ac4ab2b8036e4e940ddcae | system:identity:roles |
328
+ And the `identity.tokens` configuration:
329
+ """yaml
330
+ refresh: 1
331
+ """
332
+ And the annotation:
333
+ """yaml
334
+ /:
335
+ io:output: true
336
+ auth:role: foo:bar
337
+ GET:
338
+ dev:stub:
339
+ access: granted!
340
+ """
341
+ When the following request is received:
342
+ """
343
+ GET /identity/ HTTP/1.1
344
+ host: nex.toa.io
345
+ authorization: Basic dXNlcjpwYXNz
346
+ """
347
+ Then the following reply is sent:
348
+ """
349
+ 200 OK
350
+ authorization: Token ${{ token }}
351
+ """
352
+ When the following request is received:
353
+ # root grants a role to the user
354
+ """
355
+ POST /identity/roles/4344518184ad44228baffce7a44fd0b1/ HTTP/1.1
356
+ host: nex.toa.io
357
+ authorization: Basic cm9vdDpzZWNyZXQ=
358
+ content-type: application/yaml
359
+
360
+ role: foo:bar
361
+ """
362
+ Then the following reply is sent:
363
+ """
364
+ 201 Created
365
+ """
366
+ # the token was issued before the grant
367
+ When the following request is received:
368
+ """
369
+ GET / HTTP/1.1
370
+ host: nex.toa.io
371
+ authorization: Token ${{ token }}
372
+ """
373
+ Then the following reply is sent:
374
+ """
375
+ 403 Forbidden
376
+ """
377
+ Then after 1 second
378
+ When the following request is received:
379
+ """
380
+ GET /identity/ HTTP/1.1
381
+ host: nex.toa.io
382
+ authorization: Token ${{ token }}
383
+ """
384
+ Then the following reply is sent:
385
+ """
386
+ 200 OK
387
+ authorization: Token ${{ granted }}
388
+ """
389
+ When the following request is received:
390
+ """
391
+ GET / HTTP/1.1
392
+ host: nex.toa.io
393
+ authorization: Token ${{ granted }}
394
+ accept: application/yaml
395
+ """
396
+ Then the following reply is sent:
397
+ """
398
+ 200 OK
399
+
400
+ access: granted!
401
+ """
402
+ When the following request is received:
403
+ # root revokes the role
404
+ """
405
+ DELETE /identity/roles/4344518184ad44228baffce7a44fd0b1/foo:bar/ HTTP/1.1
406
+ host: nex.toa.io
407
+ authorization: Basic cm9vdDpzZWNyZXQ=
408
+ """
409
+ Then the following reply is sent:
410
+ """
411
+ 200 OK
412
+ """
413
+ Then after 1 second
414
+ When the following request is received:
415
+ """
416
+ GET /identity/ HTTP/1.1
417
+ host: nex.toa.io
418
+ authorization: Token ${{ granted }}
419
+ """
420
+ Then the following reply is sent:
421
+ """
422
+ 200 OK
423
+ authorization: Token ${{ revoked }}
424
+ """
425
+ When the following request is received:
426
+ """
427
+ GET / HTTP/1.1
428
+ host: nex.toa.io
429
+ authorization: Token ${{ revoked }}
430
+ """
431
+ Then the following reply is sent:
432
+ """
433
+ 403 Forbidden
434
+ """
435
+
436
+ Scenario: Revoking a role without `system:identity:roles`
437
+ Given the `identity.basic` database contains:
438
+ | _id | authority | username | password |
439
+ | 4344518184ad44228baffce7a44fd0b1 | nex | user | $2b$10$JoiAQUS7tzobDAFIDBWhWeEIJv933dQetyjRzSmfQGaJE5ZlJbmYy |
440
+ And the `identity.roles` database contains:
441
+ | _id | identity | role |
442
+ | 30c969e05ff6437097ed5f07fc52358e | 4344518184ad44228baffce7a44fd0b1 | foo:bar |
443
+ When the following request is received:
444
+ """
445
+ DELETE /identity/roles/4344518184ad44228baffce7a44fd0b1/foo:bar/ HTTP/1.1
446
+ host: nex.toa.io
447
+ authorization: Basic dXNlcjpwYXNz
448
+ """
449
+ Then the following reply is sent:
450
+ """
451
+ 403 Forbidden
452
+ """
453
+
454
+ Scenario: Revoking a role revokes custom tokens
455
+ # root:secret
456
+ # user:pass
457
+ Given the `identity.basic` database contains:
458
+ | _id | authority | username | password |
459
+ | 72cf9b0ab0ac4ab2b8036e4e940ddcae | nex | root | $2b$10$Qq/qnyyU5wjrbDXyWok14OnqAZv/z.pLhz.UddatjI6eHU/rFof4i |
460
+ | 4344518184ad44228baffce7a44fd0b1 | nex | user | $2b$10$JoiAQUS7tzobDAFIDBWhWeEIJv933dQetyjRzSmfQGaJE5ZlJbmYy |
461
+ And the `identity.roles` database contains:
462
+ | _id | identity | role |
463
+ | 9c4702490ff84f2a9e1b1da2ab64bdd4 | 72cf9b0ab0ac4ab2b8036e4e940ddcae | system:identity:roles |
464
+ | 30c969e05ff6437097ed5f07fc52358e | 4344518184ad44228baffce7a44fd0b1 | foo:bar |
465
+ And the `identity.keys` database is empty
466
+ And the `identity.tokens` configuration:
467
+ """yaml
468
+ cache:
469
+ ttl: 1
470
+ """
471
+ And the annotation:
472
+ """yaml
473
+ /:
474
+ io:output: true
475
+ auth:role: foo:bar
476
+ GET:
477
+ dev:stub:
478
+ access: granted!
479
+ """
480
+ When the following request is received:
481
+ """
482
+ POST /identity/tokens/4344518184ad44228baffce7a44fd0b1/ HTTP/1.1
483
+ host: nex.toa.io
484
+ authorization: Basic dXNlcjpwYXNz
485
+ accept: application/yaml
486
+ content-type: application/yaml
487
+
488
+ label: Forever token
489
+ lifetime: 0
490
+ """
491
+ Then the following reply is sent:
492
+ """
493
+ 201 Created
494
+
495
+ token: ${{ token }}
496
+ """
497
+ When the following request is received:
498
+ """
499
+ GET / HTTP/1.1
500
+ host: nex.toa.io
501
+ authorization: Token ${{ token }}
502
+ """
503
+ Then the following reply is sent:
504
+ """
505
+ 200 OK
506
+ """
507
+ When the following request is received:
508
+ # root revokes the role
509
+ """
510
+ DELETE /identity/roles/4344518184ad44228baffce7a44fd0b1/foo:bar/ HTTP/1.1
511
+ host: nex.toa.io
512
+ authorization: Basic cm9vdDpzZWNyZXQ=
513
+ """
514
+ Then the following reply is sent:
515
+ """
516
+ 200 OK
517
+ """
518
+ # the key is revoked eventually, and the runtime forgets it after `cache.ttl`
519
+ Then after 2 seconds
520
+ When the following request is received:
521
+ """
522
+ GET / HTTP/1.1
523
+ host: nex.toa.io
524
+ authorization: Token ${{ token }}
525
+ """
526
+ Then the following reply is sent:
527
+ """
528
+ 401 Unauthorized
529
+ """
530
+
531
+ Scenario: Delegating the right to delegate
532
+ Given the `identity.basic` database contains:
533
+ | _id | authority | username | password |
534
+ | 72cf9b0ab0ac4ab2b8036e4e940ddcae | nex | moderator | $2b$10$Qq/qnyyU5wjrbDXyWok14OnqAZv/z.pLhz.UddatjI6eHU/rFof4i |
535
+ | 4344518184ad44228baffce7a44fd0b1 | nex | assistant | $2b$10$JoiAQUS7tzobDAFIDBWhWeEIJv933dQetyjRzSmfQGaJE5ZlJbmYy |
536
+ And the `identity.roles` database contains:
537
+ | _id | identity | role |
538
+ | 9c4702490ff84f2a9e1b1da2ab64bdd4 | 72cf9b0ab0ac4ab2b8036e4e940ddcae | system:identity:roles:delegation |
539
+ | 30c969e05ff6437097ed5f07fc52358e | 72cf9b0ab0ac4ab2b8036e4e940ddcae | app:moderation |
540
+ When the following request is received:
541
+ """
542
+ POST /identity/roles/4344518184ad44228baffce7a44fd0b1/ HTTP/1.1
543
+ host: nex.toa.io
544
+ accept: application/yaml
545
+ content-type: application/yaml
546
+ authorization: Basic bW9kZXJhdG9yOnNlY3JldA==
547
+
548
+ role: system:identity:roles:delegation
549
+ """
550
+ Then the following reply is sent:
551
+ """
552
+ 422 Unprocessable Entity
553
+
554
+ code: INACCESSIBLE_SCOPE
555
+ """
556
+ When the following request is received:
557
+ """
558
+ GET /identity/roles/4344518184ad44228baffce7a44fd0b1/ HTTP/1.1
559
+ host: nex.toa.io
560
+ accept: application/yaml
561
+ authorization: Basic YXNzaXN0YW50OnBhc3M=
562
+ """
563
+ Then the following reply is sent:
564
+ """
565
+ 200 OK
566
+
567
+ []
568
+ """
@@ -283,3 +283,148 @@ Feature: Custom tokens
283
283
  """
284
284
  401 Unauthorized
285
285
  """
286
+
287
+ Scenario: Custom token is not refreshed
288
+ Given the `identity.tokens` configuration:
289
+ """yaml
290
+ refresh: 1
291
+ """
292
+ When the following request is received:
293
+ """
294
+ POST /identity/tokens/efe3a65ebbee47ed95a73edd911ea328/ HTTP/1.1
295
+ host: nex.toa.io
296
+ authorization: Basic ZGV2ZWxvcGVyOnNlY3JldA==
297
+ accept: application/yaml
298
+ content-type: application/yaml
299
+
300
+ label: Restricted token
301
+ lifetime: 600
302
+ permissions:
303
+ /notes/: [GET]
304
+ """
305
+ Then the following reply is sent:
306
+ """
307
+ 201 Created
308
+
309
+ token: ${{ token }}
310
+ """
311
+ And after 1 second
312
+ When the following request is received:
313
+ """
314
+ GET /notes/ HTTP/1.1
315
+ host: nex.toa.io
316
+ authorization: Token ${{ token }}
317
+ """
318
+ Then the following reply is sent:
319
+ """
320
+ 200 OK
321
+ """
322
+ And the reply does not contain:
323
+ """
324
+ authorization: Token
325
+ """
326
+ When the following request is received:
327
+ """
328
+ GET /notes/public/ HTTP/1.1
329
+ host: nex.toa.io
330
+ authorization: Token ${{ token }}
331
+ """
332
+ Then the following reply is sent:
333
+ """
334
+ 403 Forbidden
335
+ """
336
+
337
+ Scenario: Permissions apply to the path the request is routed by
338
+ When the following request is received:
339
+ """
340
+ POST /identity/tokens/efe3a65ebbee47ed95a73edd911ea328/ HTTP/1.1
341
+ host: nex.toa.io
342
+ authorization: Basic ZGV2ZWxvcGVyOnNlY3JldA==
343
+ accept: application/yaml
344
+ content-type: application/yaml
345
+
346
+ label: Public notes token
347
+ lifetime: 0
348
+ permissions:
349
+ /notes/public/: [GET]
350
+ """
351
+ Then the following reply is sent:
352
+ """
353
+ 201 Created
354
+
355
+ token: ${{ token }}
356
+ """
357
+
358
+ # the query string is not part of the path
359
+ When the following request is received:
360
+ """
361
+ GET /notes/public/?page=2 HTTP/1.1
362
+ host: nex.toa.io
363
+ authorization: Token ${{ token }}
364
+ """
365
+ Then the following reply is sent:
366
+ """
367
+ 200 OK
368
+ """
369
+
370
+ Scenario: Changing the password revokes custom tokens
371
+ Given the `identity.basic` database contains:
372
+ | _id | _version | authority | username | password |
373
+ | efe3a65ebbee47ed95a73edd911ea328 | 1 | nex | developer | $2b$10$ZRSKkgZoGnrcTNA5w5eCcu3pxDzdTduhteVYXcp56AaNcilNkwJ.O |
374
+ And the `identity.tokens` configuration:
375
+ """yaml
376
+ cache:
377
+ ttl: 1
378
+ """
379
+ When the following request is received:
380
+ """
381
+ POST /identity/tokens/efe3a65ebbee47ed95a73edd911ea328/ HTTP/1.1
382
+ host: nex.toa.io
383
+ authorization: Basic ZGV2ZWxvcGVyOnNlY3JldA==
384
+ accept: application/yaml
385
+ content-type: application/yaml
386
+
387
+ label: Forever token
388
+ lifetime: 0
389
+ """
390
+ Then the following reply is sent:
391
+ """
392
+ 201 Created
393
+
394
+ token: ${{ token }}
395
+ """
396
+ When the following request is received:
397
+ """
398
+ GET /identity/ HTTP/1.1
399
+ host: nex.toa.io
400
+ authorization: Token ${{ token }}
401
+ """
402
+ Then the following reply is sent:
403
+ """
404
+ 200 OK
405
+ """
406
+ When the following request is received:
407
+ """
408
+ PATCH /identity/basic/efe3a65ebbee47ed95a73edd911ea328/ HTTP/1.1
409
+ host: nex.toa.io
410
+ authorization: Basic ZGV2ZWxvcGVyOnNlY3JldA==
411
+ content-type: application/yaml
412
+
413
+ password: new-secret
414
+ """
415
+ Then the following reply is sent:
416
+ """
417
+ 200 OK
418
+ """
419
+ # the runtime forgets the key after `cache.ttl`
420
+ Then after 1.5 seconds
421
+ When the following request is received:
422
+ """
423
+ GET /identity/ HTTP/1.1
424
+ host: nex.toa.io
425
+ authorization: Token ${{ token }}
426
+ """
427
+ Then the following reply is sent:
428
+ """
429
+ 401 Unauthorized
430
+ """
@@ -235,6 +235,7 @@ Feature: HTTP context mapping
235
235
  """yaml
236
236
  trust:
237
237
  - iss: http://localhost:44444
238
+ aud: test
238
239
  """
239
240
  And the `echo` is running with the following manifest:
240
241
  """yaml
@@ -269,6 +270,7 @@ Feature: HTTP context mapping
269
270
  """yaml
270
271
  trust:
271
272
  - iss: http://localhost:44444
273
+ aud: test
272
274
  assert: false
273
275
  """
274
276
  And the `pots` is running with the following manifest:
@@ -29,12 +29,18 @@ export class Gateway {
29
29
  process.env.TOA_EXPOSITION = JSON.stringify(tree)
30
30
  }
31
31
 
32
- const { debug, authorities } = annotation
32
+ const { debug, authorities, bouncer, ip } = annotation
33
33
  const properties = Object.assign({}, DEFAULT_PROPERTIES)
34
34
 
35
35
  if (debug !== undefined)
36
36
  properties.debug = debug
37
37
 
38
+ if (bouncer !== undefined)
39
+ properties.bouncer = bouncer
40
+
41
+ if (ip !== undefined)
42
+ properties.ip = ip
43
+
38
44
  if (authorities !== undefined)
39
45
  properties.authorities = authorities
40
46
 
@@ -130,7 +136,7 @@ export class Gateway {
130
136
 
131
137
  this.writeConfiguration()
132
138
 
133
- const factory = new Factory(boot)
139
+ const factory = new Factory(boot.host())
134
140
  const service = await factory.service()
135
141
 
136
142
  if (service === null)
@@ -40,7 +40,7 @@ export class Realtime {
40
40
 
41
41
  process.env.TOA_REALTIME = JSON.stringify(routes)
42
42
 
43
- const factory = new Factory(boot)
43
+ const factory = new Factory(boot.host())
44
44
 
45
45
  Realtime.instance = await factory.service()
46
46
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toa.io/extensions.exposition",
3
- "version": "1.0.0-alpha.278",
3
+ "version": "1.0.0-alpha.282",
4
4
  "type": "module",
5
5
  "description": "Toa Exposition",
6
6
  "author": "temich <tema.gurtovoy@gmail.com>",
@@ -19,9 +19,9 @@
19
19
  },
20
20
  "dependencies": {
21
21
  "@simplewebauthn/server": "14.0.0",
22
- "@toa.io/core": "1.0.0-alpha.278",
23
- "@toa.io/generic": "1.0.0-alpha.278",
24
- "@toa.io/schemas": "1.0.0-alpha.278",
22
+ "@toa.io/core": "1.0.0-alpha.282",
23
+ "@toa.io/generic": "1.0.0-alpha.282",
24
+ "@toa.io/schemas": "1.0.0-alpha.282",
25
25
  "bcryptjs": "3.0.3",
26
26
  "content-type": "3.0.0",
27
27
  "jose": "6.2.10",
@@ -56,5 +56,5 @@
56
56
  "@types/cors": "2.8.19",
57
57
  "@types/negotiator": "0.6.5"
58
58
  },
59
- "gitHead": "5872815748eed8021ea99d7beca1602f4fc5c958"
59
+ "gitHead": "4007c814725dd015d3bbebb9a28e28a247645c4f"
60
60
  }
package/readme.md CHANGED
@@ -136,6 +136,8 @@ exposition:
136
136
  | `class` | Ingress class. |
137
137
  | `annotations` | Ingress annotations. |
138
138
  | `debug` | Output server errors. Default `false`. |
139
+ | `ip` | Header the client address is read from. See [Client address](documentation/ip.md). |
140
+ | `bouncer` | Meter of failed authentications per address. See [Identity authentication](documentation/identity.md#failed-authentications). |
139
141
 
140
142
  ### Observability
141
143
 
@@ -201,6 +203,7 @@ See also [telemetry ready probe](../telemetry/readme.md#ready-probe) and
201
203
  ## See Also
202
204
 
203
205
  - [Protocol support](documentation/protocol.md)
206
+ - [Client address](documentation/ip.md)
204
207
  - [Resource Tree Definition](documentation/tree.md)
205
208
  - [Identity authentication](documentation/identity.md)
206
209
  - [Access authorization](documentation/access.md)
@@ -34,6 +34,19 @@ properties:
34
34
  type: string
35
35
  debug:
36
36
  type: boolean
37
+ ip:
38
+ type: string
39
+ minLength: 1
40
+ bouncer:
41
+ type: object
42
+ properties:
43
+ attempts:
44
+ type: integer
45
+ minimum: 1
46
+ interval:
47
+ type: integer
48
+ minimum: 1
49
+ additionalProperties: false
37
50
  protocol:
38
51
  type: string
39
52
  enum: [h1, h2c]
@@ -46,3 +59,6 @@ properties:
46
59
  type: object
47
60
  required:
48
61
  - authorities
62
+ dependencies:
63
+ bouncer:
64
+ - ip
@@ -17,5 +17,19 @@ export interface Annotation {
17
17
  service?: { annotations?: Record<string, string> }
18
18
 
19
19
  debug?: boolean
20
+
21
+ /** The header the client address is read from; the connection's without one. See `ip.md`. */
22
+ ip?: string
23
+
24
+ /** Failed authentications an address may make; none are metered unless set. See `identity.md`. */
25
+ bouncer?: Bouncer
20
26
  '/'?: object // parsed and validated by RTD.syntax.parse
21
27
  }
28
+
29
+ export interface Bouncer {
30
+ /** what an address may fail at once, 20 by default */
31
+ attempts?: number
32
+
33
+ /** seconds it takes to earn them back, 60 by default */
34
+ interval?: number
35
+ }
@@ -1,19 +1,19 @@
1
1
  import { readdirSync, type Dirent } from 'node:fs'
2
2
  import { resolve } from 'node:path'
3
3
  import { Connector } from '@toa.io/core'
4
- import { type Bootloader } from './Factory.js'
4
+ import { type Host } from './Factory.js'
5
5
 
6
6
  export class Composition extends Connector {
7
- private readonly boot: Bootloader
7
+ private readonly host: Host
8
8
 
9
- public constructor (boot: Bootloader) {
9
+ public constructor (host: Host) {
10
10
  super()
11
- this.boot = boot
11
+ this.host = host
12
12
  }
13
13
 
14
14
  protected override async open (): Promise<void> {
15
15
  const paths = find()
16
- const composition = await this.boot.composition(paths)
16
+ const composition = await this.host.composition(paths)
17
17
 
18
18
  await composition.connect()
19
19
 
@@ -5,6 +5,7 @@ import { generate } from 'randomstring'
5
5
  import { DirectivesFactory } from './Directive.js'
6
6
  import type { syntax, DirectiveFamily } from './RTD/index.js'
7
7
  import type { Remotes } from './Remotes.js'
8
+ import type { Host } from './Factory.js'
8
9
  import type { Context } from './HTTP/index.js'
9
10
 
10
11
  const sequence: string[] = []
@@ -51,7 +52,8 @@ beforeEach(() => {
51
52
  family.preflight.mock.mockImplementation(() => { sequence.push(family.name); return null })
52
53
  }
53
54
 
54
- factory = new DirectivesFactory(families, {} as unknown as Remotes)
55
+ factory = new DirectivesFactory(families, {} as unknown as Remotes, {} as unknown as Host,
56
+ { authorities: {} })
55
57
  })
56
58
 
57
59
  it('should create directive', async () => {
@@ -1,6 +1,7 @@
1
1
  import { console, type SpanOptions } from 'openspan'
2
- import type { Context, OutgoingMessage } from './HTTP/index.js'
2
+ import type { Context, OutgoingMessage, Options } from './HTTP/index.js'
3
3
  import type { Remotes } from './Remotes.js'
4
+ import type { Host } from './Factory.js'
4
5
  import type { Output } from './io.js'
5
6
  import type * as RTD from './RTD/index.js'
6
7
 
@@ -64,8 +65,11 @@ export class DirectivesFactory implements RTD.DirectiveFactory {
64
65
  private readonly mandatory: string[] = []
65
66
  private readonly instances: Directives[] = []
66
67
 
67
- public constructor (families: RTD.DirectiveFamily[], remotes: Remotes) {
68
+ // eslint-disable-next-line max-params
69
+ public constructor (families: RTD.DirectiveFamily[], remotes: Remotes, host: Host,
70
+ options: Options) {
68
71
  for (const family of families) {
72
+ family.mount?.(host, options)
69
73
  this.families[family.name] = family
70
74
 
71
75
  if (family.mandatory)
@@ -1,6 +1,7 @@
1
1
  import { Readable } from 'node:stream'
2
2
  import { console } from 'openspan'
3
3
  import { Mapping } from './Mapping.js'
4
+ import { redact } from './redact.js'
4
5
  import * as http from './HTTP/index.js'
5
6
  import type { Introspection, Schema } from './Introspection.js'
6
7
  import type { Remote } from '@toa.io/core'
@@ -29,11 +30,12 @@ export class Endpoint implements RTD.Endpoint {
29
30
 
30
31
  const endpoint = this.remote.locator.id + '.' + this.endpoint
31
32
 
32
- console.debug('Calling operation', { endpoint, request })
33
+ console.debug('Calling operation', { endpoint, request: redact(request) })
33
34
 
34
35
  const reply = await this.remote.invoke(this.endpoint, request)
35
36
 
36
- console.debug('Received reply', { endpoint, reply: reply instanceof Readable ? '[Readable stream]' : reply })
37
+ console.debug('Received reply',
38
+ { endpoint, reply: reply instanceof Readable ? '[Readable stream]' : redact(reply) })
37
39
 
38
40
  if (reply instanceof Error)
39
41
  throw new http.UnprocessableEntity(reply)