@toa.io/extensions.exposition 0.9.0-canary.2 → 0.20.0-alpha.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 (363) hide show
  1. package/components/context.toa.yaml +15 -0
  2. package/components/identity.bans/manifest.toa.yaml +18 -0
  3. package/components/identity.basic/events/principal.js +9 -0
  4. package/components/identity.basic/manifest.toa.yaml +50 -0
  5. package/components/identity.basic/source/authenticate.ts +29 -0
  6. package/components/identity.basic/source/create.ts +19 -0
  7. package/components/identity.basic/source/transit.ts +64 -0
  8. package/components/identity.basic/source/types.ts +42 -0
  9. package/components/identity.basic/tsconfig.json +9 -0
  10. package/components/identity.roles/manifest.toa.yaml +31 -0
  11. package/components/identity.roles/source/list.ts +7 -0
  12. package/components/identity.roles/source/principal.ts +20 -0
  13. package/components/identity.roles/tsconfig.json +9 -0
  14. package/components/identity.tokens/manifest.toa.yaml +39 -0
  15. package/components/identity.tokens/receivers/identity.bans.updated.js +3 -0
  16. package/components/identity.tokens/source/authenticate.test.ts +56 -0
  17. package/components/identity.tokens/source/authenticate.ts +38 -0
  18. package/components/identity.tokens/source/decrypt.test.ts +59 -0
  19. package/components/identity.tokens/source/decrypt.ts +25 -0
  20. package/components/identity.tokens/source/encrypt.test.ts +35 -0
  21. package/components/identity.tokens/source/encrypt.ts +25 -0
  22. package/components/identity.tokens/source/revoke.ts +5 -0
  23. package/components/identity.tokens/source/types.ts +48 -0
  24. package/components/identity.tokens/tsconfig.json +9 -0
  25. package/cucumber.js +9 -0
  26. package/documentation/.assets/ia3-dark.jpg +0 -0
  27. package/documentation/.assets/ia3-light.jpg +0 -0
  28. package/documentation/.assets/overview-dark.jpg +0 -0
  29. package/documentation/.assets/overview-light.jpg +0 -0
  30. package/documentation/.assets/role-scopes-dark.jpg +0 -0
  31. package/documentation/.assets/role-scopes-light.jpg +0 -0
  32. package/documentation/.assets/rtd-dark.jpg +0 -0
  33. package/documentation/.assets/rtd-light.jpg +0 -0
  34. package/documentation/access.md +256 -0
  35. package/documentation/components.md +276 -0
  36. package/documentation/identity.md +156 -0
  37. package/documentation/protocol.md +15 -0
  38. package/documentation/query.md +226 -0
  39. package/documentation/tree.md +169 -0
  40. package/features/access.feature +442 -0
  41. package/features/annotation.feature +28 -0
  42. package/features/body.feature +21 -0
  43. package/features/directives.feature +56 -0
  44. package/features/dynamic.feature +89 -0
  45. package/features/errors.feature +208 -0
  46. package/features/identity.basic.feature +235 -0
  47. package/features/identity.feature +61 -0
  48. package/features/identity.roles.feature +51 -0
  49. package/features/identity.tokens.feature +116 -0
  50. package/features/queries.feature +214 -0
  51. package/features/routes.feature +49 -0
  52. package/features/steps/Common.ts +10 -0
  53. package/features/steps/Components.ts +43 -0
  54. package/features/steps/Database.ts +58 -0
  55. package/features/steps/Gateway.ts +105 -0
  56. package/features/steps/HTTP.ts +71 -0
  57. package/features/steps/Parameters.ts +12 -0
  58. package/features/steps/Workspace.ts +40 -0
  59. package/features/steps/components/greeter/manifest.toa.yaml +9 -0
  60. package/features/steps/components/greeter/operations/greet.js +7 -0
  61. package/features/steps/components/pots/manifest.toa.yaml +20 -0
  62. package/features/steps/components/users/manifest.toa.yaml +11 -0
  63. package/features/steps/tsconfig.json +9 -0
  64. package/package.json +31 -17
  65. package/readme.md +181 -0
  66. package/schemas/annotation.cos.yaml +4 -0
  67. package/schemas/directive.cos.yaml +3 -0
  68. package/schemas/method.cos.yaml +9 -0
  69. package/schemas/node.cos.yaml +5 -0
  70. package/schemas/query.cos.yaml +16 -0
  71. package/schemas/querystring.cos.yaml +5 -0
  72. package/schemas/range.cos.yaml +2 -0
  73. package/schemas/route.cos.yaml +2 -0
  74. package/source/Annotation.ts +6 -0
  75. package/source/Branch.ts +8 -0
  76. package/source/Composition.ts +58 -0
  77. package/source/Context.ts +6 -0
  78. package/source/Directive.test.ts +91 -0
  79. package/source/Directive.ts +120 -0
  80. package/source/Endpoint.ts +57 -0
  81. package/source/Factory.ts +53 -0
  82. package/source/Gateway.ts +93 -0
  83. package/source/HTTP/Server.fixtures.ts +45 -0
  84. package/source/HTTP/Server.test.ts +221 -0
  85. package/source/HTTP/Server.ts +135 -0
  86. package/source/HTTP/exceptions.ts +77 -0
  87. package/source/HTTP/formats/index.ts +19 -0
  88. package/source/HTTP/formats/json.ts +13 -0
  89. package/source/HTTP/formats/msgpack.ts +10 -0
  90. package/source/HTTP/formats/text.ts +9 -0
  91. package/source/HTTP/formats/yaml.ts +14 -0
  92. package/source/HTTP/index.ts +3 -0
  93. package/source/HTTP/messages.test.ts +116 -0
  94. package/source/HTTP/messages.ts +77 -0
  95. package/source/Mapping.ts +48 -0
  96. package/source/Query.test.ts +37 -0
  97. package/source/Query.ts +105 -0
  98. package/source/RTD/Context.ts +16 -0
  99. package/source/RTD/Directives.ts +9 -0
  100. package/source/RTD/Endpoint.ts +11 -0
  101. package/source/RTD/Match.ts +16 -0
  102. package/source/RTD/Method.ts +24 -0
  103. package/source/RTD/Node.ts +85 -0
  104. package/source/RTD/Route.ts +58 -0
  105. package/source/RTD/Tree.ts +57 -0
  106. package/source/RTD/factory.ts +47 -0
  107. package/source/RTD/index.ts +8 -0
  108. package/source/RTD/segment.test.ts +32 -0
  109. package/source/RTD/segment.ts +25 -0
  110. package/source/RTD/syntax/index.ts +2 -0
  111. package/source/RTD/syntax/parse.test.ts +188 -0
  112. package/source/RTD/syntax/parse.ts +153 -0
  113. package/source/RTD/syntax/types.ts +48 -0
  114. package/source/Remotes.test.ts +41 -0
  115. package/source/Remotes.ts +20 -0
  116. package/source/Tenant.ts +38 -0
  117. package/source/deployment.ts +42 -0
  118. package/source/directives/auth/Anonymous.ts +14 -0
  119. package/source/directives/auth/Echo.ts +12 -0
  120. package/source/directives/auth/Family.ts +145 -0
  121. package/source/directives/auth/Id.ts +19 -0
  122. package/source/directives/auth/Incept.ts +42 -0
  123. package/source/directives/auth/Role.test.ts +62 -0
  124. package/source/directives/auth/Role.ts +56 -0
  125. package/source/directives/auth/Rule.ts +28 -0
  126. package/source/directives/auth/Scheme.ts +26 -0
  127. package/source/directives/auth/index.ts +3 -0
  128. package/source/directives/auth/schemes.ts +8 -0
  129. package/source/directives/auth/split.ts +15 -0
  130. package/source/directives/auth/types.ts +37 -0
  131. package/source/directives/dev/Family.ts +34 -0
  132. package/source/directives/dev/Stub.ts +14 -0
  133. package/source/directives/dev/index.ts +3 -0
  134. package/source/directives/dev/types.ts +5 -0
  135. package/source/directives/index.ts +5 -0
  136. package/source/discovery.ts +1 -0
  137. package/source/exceptions.ts +17 -0
  138. package/source/index.test.ts +9 -0
  139. package/source/index.ts +6 -0
  140. package/source/manifest.test.ts +57 -0
  141. package/source/manifest.ts +35 -0
  142. package/source/root.ts +38 -0
  143. package/source/schemas.ts +9 -0
  144. package/transpiled/Annotation.d.ts +6 -0
  145. package/transpiled/Annotation.js +3 -0
  146. package/transpiled/Annotation.js.map +1 -0
  147. package/transpiled/Branch.d.ts +7 -0
  148. package/transpiled/Branch.js +3 -0
  149. package/transpiled/Branch.js.map +1 -0
  150. package/transpiled/Composition.d.ts +14 -0
  151. package/transpiled/Composition.js +43 -0
  152. package/transpiled/Composition.js.map +1 -0
  153. package/transpiled/Context.d.ts +5 -0
  154. package/transpiled/Context.js +3 -0
  155. package/transpiled/Context.js.map +1 -0
  156. package/transpiled/Directive.d.ts +32 -0
  157. package/transpiled/Directive.js +76 -0
  158. package/transpiled/Directive.js.map +1 -0
  159. package/transpiled/Endpoint.d.ts +20 -0
  160. package/transpiled/Endpoint.js +44 -0
  161. package/transpiled/Endpoint.js.map +1 -0
  162. package/transpiled/Factory.d.ts +11 -0
  163. package/transpiled/Factory.js +67 -0
  164. package/transpiled/Factory.js.map +1 -0
  165. package/transpiled/Gateway.d.ts +19 -0
  166. package/transpiled/Gateway.js +90 -0
  167. package/transpiled/Gateway.js.map +1 -0
  168. package/transpiled/HTTP/Server.d.ts +22 -0
  169. package/transpiled/HTTP/Server.fixtures.d.ts +12 -0
  170. package/transpiled/HTTP/Server.fixtures.js +36 -0
  171. package/transpiled/HTTP/Server.fixtures.js.map +1 -0
  172. package/transpiled/HTTP/Server.js +111 -0
  173. package/transpiled/HTTP/Server.js.map +1 -0
  174. package/transpiled/HTTP/exceptions.d.ts +39 -0
  175. package/transpiled/HTTP/exceptions.js +78 -0
  176. package/transpiled/HTTP/exceptions.js.map +1 -0
  177. package/transpiled/HTTP/formats/index.d.ts +8 -0
  178. package/transpiled/HTTP/formats/index.js +38 -0
  179. package/transpiled/HTTP/formats/index.js.map +1 -0
  180. package/transpiled/HTTP/formats/json.d.ts +4 -0
  181. package/transpiled/HTTP/formats/json.js +15 -0
  182. package/transpiled/HTTP/formats/json.js.map +1 -0
  183. package/transpiled/HTTP/formats/msgpack.d.ts +4 -0
  184. package/transpiled/HTTP/formats/msgpack.js +36 -0
  185. package/transpiled/HTTP/formats/msgpack.js.map +1 -0
  186. package/transpiled/HTTP/formats/text.d.ts +4 -0
  187. package/transpiled/HTTP/formats/text.js +13 -0
  188. package/transpiled/HTTP/formats/text.js.map +1 -0
  189. package/transpiled/HTTP/formats/yaml.d.ts +4 -0
  190. package/transpiled/HTTP/formats/yaml.js +39 -0
  191. package/transpiled/HTTP/formats/yaml.js.map +1 -0
  192. package/transpiled/HTTP/index.d.ts +3 -0
  193. package/transpiled/HTTP/index.js +20 -0
  194. package/transpiled/HTTP/index.js.map +1 -0
  195. package/transpiled/HTTP/messages.d.ts +27 -0
  196. package/transpiled/HTTP/messages.js +49 -0
  197. package/transpiled/HTTP/messages.js.map +1 -0
  198. package/transpiled/Mapping.d.ts +8 -0
  199. package/transpiled/Mapping.js +35 -0
  200. package/transpiled/Mapping.js.map +1 -0
  201. package/transpiled/Query.d.ts +13 -0
  202. package/transpiled/Query.js +107 -0
  203. package/transpiled/Query.js.map +1 -0
  204. package/transpiled/RTD/Context.d.ts +11 -0
  205. package/transpiled/RTD/Context.js +3 -0
  206. package/transpiled/RTD/Context.js.map +1 -0
  207. package/transpiled/RTD/Directives.d.ts +7 -0
  208. package/transpiled/RTD/Directives.js +3 -0
  209. package/transpiled/RTD/Directives.js.map +1 -0
  210. package/transpiled/RTD/Endpoint.d.ts +9 -0
  211. package/transpiled/RTD/Endpoint.js +3 -0
  212. package/transpiled/RTD/Endpoint.js.map +1 -0
  213. package/transpiled/RTD/Match.d.ts +11 -0
  214. package/transpiled/RTD/Match.js +3 -0
  215. package/transpiled/RTD/Match.js.map +1 -0
  216. package/transpiled/RTD/Method.d.ts +9 -0
  217. package/transpiled/RTD/Method.js +16 -0
  218. package/transpiled/RTD/Method.js.map +1 -0
  219. package/transpiled/RTD/Node.d.ts +21 -0
  220. package/transpiled/RTD/Node.js +61 -0
  221. package/transpiled/RTD/Node.js.map +1 -0
  222. package/transpiled/RTD/Route.d.ts +14 -0
  223. package/transpiled/RTD/Route.js +48 -0
  224. package/transpiled/RTD/Route.js.map +1 -0
  225. package/transpiled/RTD/Tree.d.ts +14 -0
  226. package/transpiled/RTD/Tree.js +45 -0
  227. package/transpiled/RTD/Tree.js.map +1 -0
  228. package/transpiled/RTD/factory.d.ts +6 -0
  229. package/transpiled/RTD/factory.js +36 -0
  230. package/transpiled/RTD/factory.js.map +1 -0
  231. package/transpiled/RTD/index.d.ts +8 -0
  232. package/transpiled/RTD/index.js +38 -0
  233. package/transpiled/RTD/index.js.map +1 -0
  234. package/transpiled/RTD/segment.d.ts +8 -0
  235. package/transpiled/RTD/segment.js +23 -0
  236. package/transpiled/RTD/segment.js.map +1 -0
  237. package/transpiled/RTD/syntax/index.d.ts +2 -0
  238. package/transpiled/RTD/syntax/index.js +19 -0
  239. package/transpiled/RTD/syntax/index.js.map +1 -0
  240. package/transpiled/RTD/syntax/parse.d.ts +4 -0
  241. package/transpiled/RTD/syntax/parse.js +128 -0
  242. package/transpiled/RTD/syntax/parse.js.map +1 -0
  243. package/transpiled/RTD/syntax/types.d.ts +41 -0
  244. package/transpiled/RTD/syntax/types.js +5 -0
  245. package/transpiled/RTD/syntax/types.js.map +1 -0
  246. package/transpiled/Remotes.d.ts +7 -0
  247. package/transpiled/Remotes.js +19 -0
  248. package/transpiled/Remotes.js.map +1 -0
  249. package/transpiled/Tenant.d.ts +12 -0
  250. package/transpiled/Tenant.js +30 -0
  251. package/transpiled/Tenant.js.map +1 -0
  252. package/transpiled/deployment.d.ts +3 -0
  253. package/transpiled/deployment.js +61 -0
  254. package/transpiled/deployment.js.map +1 -0
  255. package/transpiled/directives/auth/Anonymous.d.ts +6 -0
  256. package/transpiled/directives/auth/Anonymous.js +17 -0
  257. package/transpiled/directives/auth/Anonymous.js.map +1 -0
  258. package/transpiled/directives/auth/Echo.d.ts +6 -0
  259. package/transpiled/directives/auth/Echo.js +13 -0
  260. package/transpiled/directives/auth/Echo.js.map +1 -0
  261. package/transpiled/directives/auth/Family.d.ts +20 -0
  262. package/transpiled/directives/auth/Family.js +125 -0
  263. package/transpiled/directives/auth/Family.js.map +1 -0
  264. package/transpiled/directives/auth/Id.d.ts +7 -0
  265. package/transpiled/directives/auth/Id.js +17 -0
  266. package/transpiled/directives/auth/Id.js.map +1 -0
  267. package/transpiled/directives/auth/Incept.d.ts +10 -0
  268. package/transpiled/directives/auth/Incept.js +59 -0
  269. package/transpiled/directives/auth/Incept.js.map +1 -0
  270. package/transpiled/directives/auth/Role.d.ts +11 -0
  271. package/transpiled/directives/auth/Role.js +44 -0
  272. package/transpiled/directives/auth/Role.js.map +1 -0
  273. package/transpiled/directives/auth/Rule.d.ts +9 -0
  274. package/transpiled/directives/auth/Rule.js +22 -0
  275. package/transpiled/directives/auth/Rule.js.map +1 -0
  276. package/transpiled/directives/auth/Scheme.d.ts +7 -0
  277. package/transpiled/directives/auth/Scheme.js +47 -0
  278. package/transpiled/directives/auth/Scheme.js.map +1 -0
  279. package/transpiled/directives/auth/index.d.ts +2 -0
  280. package/transpiled/directives/auth/index.js +7 -0
  281. package/transpiled/directives/auth/index.js.map +1 -0
  282. package/transpiled/directives/auth/schemes.d.ts +3 -0
  283. package/transpiled/directives/auth/schemes.js +9 -0
  284. package/transpiled/directives/auth/schemes.js.map +1 -0
  285. package/transpiled/directives/auth/split.d.ts +2 -0
  286. package/transpiled/directives/auth/split.js +38 -0
  287. package/transpiled/directives/auth/split.js.map +1 -0
  288. package/transpiled/directives/auth/types.d.ts +31 -0
  289. package/transpiled/directives/auth/types.js +3 -0
  290. package/transpiled/directives/auth/types.js.map +1 -0
  291. package/transpiled/directives/dev/Family.d.ts +10 -0
  292. package/transpiled/directives/dev/Family.js +25 -0
  293. package/transpiled/directives/dev/Family.js.map +1 -0
  294. package/transpiled/directives/dev/Stub.d.ts +7 -0
  295. package/transpiled/directives/dev/Stub.js +14 -0
  296. package/transpiled/directives/dev/Stub.js.map +1 -0
  297. package/transpiled/directives/dev/index.d.ts +2 -0
  298. package/transpiled/directives/dev/index.js +7 -0
  299. package/transpiled/directives/dev/index.js.map +1 -0
  300. package/transpiled/directives/dev/types.d.ts +4 -0
  301. package/transpiled/directives/dev/types.js +3 -0
  302. package/transpiled/directives/dev/types.js.map +1 -0
  303. package/transpiled/directives/index.d.ts +2 -0
  304. package/transpiled/directives/index.js +10 -0
  305. package/transpiled/directives/index.js.map +1 -0
  306. package/transpiled/discovery.d.ts +1 -0
  307. package/transpiled/discovery.js +3 -0
  308. package/transpiled/discovery.js.map +1 -0
  309. package/transpiled/exceptions.d.ts +2 -0
  310. package/transpiled/exceptions.js +39 -0
  311. package/transpiled/exceptions.js.map +1 -0
  312. package/transpiled/index.d.ts +5 -0
  313. package/transpiled/index.js +12 -0
  314. package/transpiled/index.js.map +1 -0
  315. package/transpiled/manifest.d.ts +3 -0
  316. package/transpiled/manifest.js +30 -0
  317. package/transpiled/manifest.js.map +1 -0
  318. package/transpiled/root.d.ts +2 -0
  319. package/transpiled/root.js +39 -0
  320. package/transpiled/root.js.map +1 -0
  321. package/transpiled/schemas.d.ts +3 -0
  322. package/transpiled/schemas.js +14 -0
  323. package/transpiled/schemas.js.map +1 -0
  324. package/transpiled/tsconfig.tsbuildinfo +1 -0
  325. package/tsconfig.json +12 -0
  326. package/src/.manifest/index.js +0 -7
  327. package/src/.manifest/normalize.js +0 -58
  328. package/src/.manifest/schema.yaml +0 -71
  329. package/src/.manifest/validate.js +0 -17
  330. package/src/constants.js +0 -3
  331. package/src/deployment.js +0 -23
  332. package/src/exposition.js +0 -68
  333. package/src/factory.js +0 -76
  334. package/src/index.js +0 -9
  335. package/src/manifest.js +0 -12
  336. package/src/query/criteria.js +0 -55
  337. package/src/query/enum.js +0 -35
  338. package/src/query/index.js +0 -17
  339. package/src/query/query.js +0 -60
  340. package/src/query/range.js +0 -28
  341. package/src/query/sort.js +0 -19
  342. package/src/remote.js +0 -88
  343. package/src/server.js +0 -83
  344. package/src/tenant.js +0 -29
  345. package/src/translate/etag.js +0 -14
  346. package/src/translate/index.js +0 -7
  347. package/src/translate/request.js +0 -68
  348. package/src/translate/response.js +0 -62
  349. package/src/tree.js +0 -109
  350. package/test/manifest.normalize.fixtures.js +0 -37
  351. package/test/manifest.normalize.test.js +0 -37
  352. package/test/manifest.validate.test.js +0 -40
  353. package/test/query.range.test.js +0 -18
  354. package/test/tree.fixtures.js +0 -21
  355. package/test/tree.test.js +0 -44
  356. package/types/annotations.d.ts +0 -10
  357. package/types/declarations.d.ts +0 -31
  358. package/types/exposition.d.ts +0 -13
  359. package/types/http.d.ts +0 -13
  360. package/types/query.d.ts +0 -16
  361. package/types/remote.d.ts +0 -19
  362. package/types/server.d.ts +0 -13
  363. package/types/tree.d.ts +0 -33
package/cucumber.js ADDED
@@ -0,0 +1,9 @@
1
+ module.exports = {
2
+ default: {
3
+ paths: ['features/**/*.feature'],
4
+ requireModule: ['ts-node/register'],
5
+ require: ['./features/**/*.ts'],
6
+ publishQuiet: true,
7
+ failFast: true
8
+ }
9
+ }
@@ -0,0 +1,256 @@
1
+ # Access authorization
2
+
3
+ > The Authorization is intrinsically linked with the [Authentication](./identity.md).
4
+
5
+ <a href="">
6
+ <picture>
7
+ <source media="(prefers-color-scheme: dark)" srcset="./.assets/ia3-dark.jpg">
8
+ <img alt="IA3" width="600" height="507" src="./.assets/ia3-light.jpg">
9
+ </picture>
10
+ </a>
11
+
12
+ ## Directives
13
+
14
+ The Authorization is implemented as a set of [RTD Directives](tree.md#directives).
15
+
16
+ Directives are executed in a predetermined order until one of them grants access to a resource. If
17
+ none of the
18
+ directives grants access, then the Authorization interrupts request processing and responds with an
19
+ authorization error.
20
+
21
+ > The Authorization directive provider is named `authorization`,
22
+ > so the full names of the directives are `authorization:{directive}`.
23
+
24
+ ### `anonymous`
25
+
26
+ Grants access if its value is `true` and no credentials were provided[^1].
27
+
28
+ [^1]: Credentials in the request make the
29
+ response [non-chachable](https://datatracker.ietf.org/doc/html/rfc7234#section-3).
30
+
31
+ ### `id`
32
+
33
+ Grants access if resolved Identity matches the value of the URL path segment placeholder named after
34
+ the directive's value.
35
+
36
+ #### Example
37
+
38
+ Given the Route declaration and corresponding HTTP request:
39
+
40
+ ```yaml
41
+ # context.toa.yaml
42
+
43
+ exposition:
44
+ /users/:user-id:
45
+ id: "user-id"
46
+ ```
47
+
48
+ ```http
49
+ GET /users/87480f2bd88048518c529d7957475ecd/
50
+ Authorization: ...
51
+ ```
52
+
53
+ For this request access will be granted if the resolved Identity value
54
+ is `87480f2bd88048518c529d7957475ecd`.
55
+
56
+ ### `role`
57
+
58
+ Grants access if resolved Identity has a role matching the directive's value or one of its values.
59
+
60
+ #### Example
61
+
62
+ ```yaml
63
+ # context.toa.yaml
64
+
65
+ exposition:
66
+ /code:
67
+ role: [developer, reviewer]
68
+ ```
69
+
70
+ Access will be granted if the resolved Identity has a role that matches `developer` or `reviewer`.
71
+
72
+ Read [Roles](#roles) section for more details.
73
+
74
+ ### `rule`
75
+
76
+ The Rule is a collection of authorization directives. It allows access only if all the specified
77
+ directives grant
78
+ access. The value of the `rule` directive can be a single Rule or a list of Rules.
79
+
80
+ #### Example
81
+
82
+ ```yaml
83
+ # context.toa.yaml
84
+
85
+ exposition:
86
+ /commits/:user-id:
87
+ rule:
88
+ id: user-id
89
+ role: developer
90
+ ```
91
+
92
+ Access will be granted if an Identity matches a `user-id` placeholder and has a Role of `developer`.
93
+
94
+ ## Roles
95
+
96
+ Role values are strings that can be assigned to an Identity and used for matching with values of
97
+ the [`role` directive](#role).
98
+
99
+ ### Hierarchy
100
+
101
+ Role values are alphanumeric tokens separated by a colon (`:`).
102
+ Each token defines a Role Scope, forming a hierarchy.
103
+ A Role matches the value of the `rule` directive if that Role has the specified Scope in a
104
+ directive.
105
+
106
+ #### Example
107
+
108
+ ```yaml
109
+ # context.toa.yaml
110
+
111
+ /exposition:
112
+ /commits/:user-id:
113
+ role: developer:senior
114
+ ```
115
+
116
+ The example above defines a `role` directive with the specified `developer:senior` Role Scope.
117
+ This directive matches the roles `developer:senior` and `developer`,
118
+ but it **does not** match the Role `developer:senior:javascript`.
119
+ In other words, the Identity must have a specified or more general Role.
120
+
121
+ <a href="https://miro.com/app/board/uXjVOoy0ImU=/?moveToWidget=3458764556008550471&cot=14">
122
+ <picture>
123
+ <source media="(prefers-color-scheme: dark)" srcset=".assets/role-scopes-dark.jpg">
124
+ <img alt="IA3" width="600" height="425" src=".assets/role-scopes-light.jpg">
125
+ </picture>
126
+ </a>
127
+
128
+
129
+ > The root-level Role Scope `system` is preserved and cannot be used with the `role` directives.
130
+
131
+ See also [role management resources](components.md#roles).
132
+
133
+ #### Authorization Directives
134
+
135
+ ```yaml
136
+ /identity/roles/:id:
137
+ role: system:roles
138
+ ````
139
+
140
+ ## Policies
141
+
142
+ Component Resource branches cannot have authorization directives.
143
+ Instead, they must declare Authorization Policies using `policy` directive to
144
+ be attached in the Context to a Resource Tree as a set of Authorization Directives
145
+ using `attachment` directive.
146
+
147
+ This restriction provides a separation of concerns, allowing components to be reused in different
148
+ Contexts with varying
149
+ access rules.
150
+
151
+ ```yaml
152
+ # manifest.toa.yaml
153
+
154
+ name: posts
155
+
156
+ exposition:
157
+ /:user-id:
158
+ GET:
159
+ endpoint: observe
160
+ policy: read:list
161
+ POST:
162
+ endpoint: transit
163
+ policy: post:submit
164
+ /:post-id:
165
+ GET:
166
+ endpoint: observe
167
+ policy: read:post
168
+ PUT:
169
+ endpoint: assign
170
+ policy: post:edit
171
+ ```
172
+
173
+ ```yaml
174
+ # context.toa.yaml
175
+
176
+ exposition:
177
+ /posts:
178
+ attachment:
179
+ read:
180
+ anonymous: true
181
+ post:
182
+ id: user-id
183
+ post:edit:
184
+ role: app:posts:editor
185
+ ```
186
+
187
+ Policy values as well as [Role](#roles) values define hierarchical Policy Scopes.
188
+
189
+ In the example above:
190
+
191
+ - an Attachment `read` attaches Directive `anonymous: true` to both `read:list` and `read:post`
192
+ Policy Scopes.
193
+ This means that a list of posts and each post can be accessed without authorization.
194
+ - an Attachment `post` attaches Directive `id: user-id` to both `post:submit` and `post:edit` Policy
195
+ Scopes.
196
+ This means that an Identity can submit and edit their own posts.
197
+ - an Attachment `post:edit` attaches Directive `role: app:posts:editor` to `post:edit` Policy Scope.
198
+ This means that an identity with the role scope `app:posts:editor` can edit posts by any author,
199
+ in addition to the fact that the author themselves can do this thanks to the previous Attachment.
200
+
201
+ ### Nesting
202
+
203
+ Policies are namespace-scoped, meaning they can be attached to any Route under the
204
+ corresponding `/{namespace}` prefix.
205
+
206
+ Attachment is applied to the node where it is declared, as well as its nested nodes.
207
+ Directives of the Attachment are applied to the node where the attached Policies are declared, as
208
+ well as their nested nodes.
209
+
210
+ Here's an example of how this works:
211
+
212
+ ```yaml
213
+ # manifest.toa.yaml
214
+
215
+ name: posts
216
+
217
+ exposition:
218
+ /:user-id:
219
+ GET:
220
+ endpoint: observe
221
+ policy: read
222
+ /:user-id/:post-id:
223
+ GET:
224
+ endpoint: observe
225
+ policy: read
226
+ ```
227
+
228
+ ```yaml
229
+ # context.toa.yaml
230
+
231
+ exposition:
232
+ /posts:
233
+ /:user-id:
234
+ attachment:
235
+ read:
236
+ anonymous: true
237
+ /:user-id/:post-id:
238
+ attachment:
239
+ read:
240
+ role: reader
241
+ ```
242
+
243
+ In the example above, the same Policy `read` is attached to two Routes with different Directives.
244
+
245
+ The following example demonstrates the attachment of the `read` Policy to both Routes with the same
246
+ Directive:
247
+
248
+ ```yaml
249
+ # context.toa.yaml
250
+
251
+ exposition:
252
+ /posts:
253
+ attachment:
254
+ read:
255
+ anonymous: true
256
+ ```
@@ -0,0 +1,276 @@
1
+ # Components and resources
2
+
3
+ Exposition comes with a set of components that run within the same process. These components are
4
+ configured in the same
5
+ way as if they were a part of the Context. Resources exposed by the components
6
+ are [isolated](tree.md#directives).
7
+
8
+ ## Basic credentials
9
+
10
+ The `identity.basic` component stores basic credentials.
11
+
12
+ ### Password hashing
13
+
14
+ Passwords are hashed using the [bcrypt](https://github.com/dcodeIO/bcrypt.js) algorithm with salt
15
+ and pepper.
16
+
17
+ ```yaml
18
+ # context.toa.yaml
19
+
20
+ configuration:
21
+ identity.basic:
22
+ rounds: 10 # salt rounds
23
+ peper: '' # hashing pepper
24
+ ```
25
+
26
+ ### Credentials constraints
27
+
28
+ Credential constraints are defined using a set of regular expressions (values must match all of
29
+ them).
30
+
31
+ ```yaml
32
+ # context.toa.yaml
33
+
34
+ configuration:
35
+ identity.basic:
36
+ username:
37
+ - ^\S{1,16}$
38
+ password:
39
+ - ^\S{8,32}$
40
+ ```
41
+
42
+ > Values in the example above are the default values.
43
+
44
+ ### Principal
45
+
46
+ When an application is deployed for the first time, there are no credentials, and therefore, there
47
+ is no Identity that
48
+ could have a Role to manage Roles of other Identities.
49
+
50
+ This issue is addressed by using the `principal` key in the configuration:
51
+
52
+ ```yaml
53
+ # context.toa.yaml
54
+
55
+ configuration:
56
+ identity.basic:
57
+ principal: root
58
+ ```
59
+
60
+ The value of the `principal` key corresponds to the `username` of the basic credentials. Once these
61
+ credentials are
62
+ created, the associated Identity will be assigned the `system` Role.
63
+
64
+ Once created, the username of the principal cannot be modified.
65
+
66
+ ### Resources
67
+
68
+ #### `/identity/basic/`
69
+
70
+ <code>POST</code> Create new Identity with Basic credentials. Request body is as follows:
71
+
72
+ ```yaml
73
+ username: string
74
+ password: string
75
+ ```
76
+
77
+ Access is [anonymous](access.md#anonymous).
78
+
79
+ #### `/identity/basic/:id/`
80
+
81
+ > `:id` placeholder refers to an Identity.
82
+
83
+ <code>PUT</code> Update basic credentials. Request body is as follows:
84
+
85
+ ```yaml
86
+ username?: string
87
+ password?: string
88
+ ```
89
+
90
+ Access requires basic credentials of the modified Identity or `system:identity:basic` role.
91
+
92
+ ## Stateless tokens
93
+
94
+ The `identity.tokens` component manages statless authentication tokens.
95
+
96
+ These tokens carry the information required to authenticate the Identity and authorize access.
97
+
98
+ ### Issuing tokens
99
+
100
+ The new token is issued each time the request is made:
101
+
102
+ 1. Using authentication scheme other than `Token`.
103
+ 2. Using `Token` authentication scheme with an [obsolete token](#token-rotation).
104
+
105
+ ### Token encryption
106
+
107
+ Issued tokens are encrypted
108
+ with [PASETO V3 encryption](https://github.com/panva/paseto/blob/main/docs/README.md#v3encryptpayload-key-options)
109
+ using the `key0` configuration value as a secret.
110
+
111
+ ```yaml
112
+ # context.toa.yaml
113
+
114
+ configuration:
115
+ identity.basic:
116
+ key0: $TOKEN_ENCRYPTION_KEY
117
+ ```
118
+
119
+ The `key0` configuration value is required.
120
+
121
+ > Valid secret key may be generated using the [`toa key` command](/runtime/cli/readme.md#key).
122
+
123
+ ### Token rotation
124
+
125
+ Issued tokens are valid for a `lifetime` period defined in the configuration. After the `refresh`
126
+ period, the token is
127
+ considered obsolete (yet still valid), and a new token is [issued](#issuing-tokens) unless the
128
+ provided one has
129
+ been [revoked](#token-revocation).
130
+
131
+ This essentially means that if the client uses the token at least once every `lifetime` period, it
132
+ will always have a
133
+ valid token to authenticate with. Also, token revocation or changing roles of an Identity will take
134
+ effect once
135
+ the `refresh` period of the currently issued tokens has expired.
136
+
137
+ Adjusting these two values is a delicate trade-off between security, performance and client
138
+ convinience.
139
+
140
+ ```yaml
141
+ # context.toa.yaml
142
+
143
+ configuration:
144
+ identity.basic:
145
+ lifetime: 2592000 # seconds, 30 days
146
+ refresh: 600 # seconds, 10 minutes
147
+ ```
148
+
149
+ > Values in the example above are the default values.
150
+
151
+ ### Token revocation
152
+
153
+ All currently issued tokens of an Identity are revoked when:
154
+
155
+ 1. Basic credentials associated with the Identity are [modified](#identitybasicid).
156
+ 2. Identity is [banned](#banned-identities).
157
+
158
+ Token revocation takes effect once the `refresh` period of the currently issued tokens has expired.
159
+
160
+ ### Secret rotation
161
+
162
+ Tokens are always encrypted using the `key0` configuration value, and they will be decrypted by
163
+ attempting both
164
+ the `key0` and `key1` values in order.
165
+
166
+ `key0` is considered the "current key," and `key1` is considered the "previous key."
167
+
168
+ ```yaml
169
+ # context.toa.yaml
170
+
171
+ configuration:
172
+ identity.basic:
173
+ key0: $TOKEN_ENCRYPTION_KEY_2023Q3
174
+ key1: $TOKEN_ENCRYPTION_KEY_2023Q2
175
+ ```
176
+
177
+ Secret rotation is performed by adding a new key as the `key0` value and moving the existing `key0`
178
+ to the `key1` value.
179
+
180
+ When rolling out the new secret key, there will be a period of time when the new key is deployed to
181
+ some Exposition
182
+ instances. During this time, these instances will start using the new key to encrypt tokens, while
183
+ other instances will
184
+ continue using the current key and will not be able to decrypt tokens encrypted with the new key.
185
+
186
+ To address this issue, the `key1` configuration value may be used as a "transient key."
187
+
188
+ The secret rotation is a 2-step process:
189
+
190
+ > The process **must not** be performed earlier than the `lifetime` period since the last rotation,
191
+ > as it may invalidate
192
+ > tokens before they expire. Therefore, it is guaranteed that there are no valid tokens issued with
193
+ > the current `key1`
194
+ > value.
195
+
196
+ 1. Deploy the new secret key to all Exposition instances as `key1`. This enables all instances to
197
+ decrypt tokens
198
+ encrypted with the new key while still using the current key for encryption.
199
+
200
+ ```yaml
201
+ # context.toa.yaml
202
+
203
+ configuration:
204
+ identity.basic:
205
+ key0: $TOKEN_ENCRYPTION_KEY_2023Q3
206
+ key1: $TOKEN_ENCRYPTION_KEY_2023Q4
207
+ ```
208
+
209
+ 2. Move the new secret key from `key1` to `key0`, and move the current key from `key0` to `key1`.
210
+ During this rollout,
211
+ all instances can decrypt tokens encrypted with both the new key and the current key.
212
+
213
+ ```yaml
214
+ # context.toa.yaml
215
+
216
+ configuration:
217
+ identity.basic:
218
+ key0: $TOKEN_ENCRYPTION_KEY_2023Q4
219
+ key1: $TOKEN_ENCRYPTION_KEY_2023Q3
220
+ ```
221
+
222
+ ## Roles
223
+
224
+ The `identity.roles` component manages roles of an Identity used by [access authorization](access.md#role).
225
+
226
+ ### Role resources
227
+
228
+ #### `/identity/roles/:id/`
229
+
230
+ `GET` Get roles of an Identity.
231
+
232
+ Access requires credentials of the Identity or `system:identity:roles` role.
233
+
234
+ `POST` Add a role to an Identity. Request body is as follows:
235
+
236
+ ```yaml
237
+ role: string
238
+ ```
239
+
240
+ Access requires `system:identity:roles` role.
241
+
242
+ ## Banned Identities
243
+
244
+ The `identity.bans` component manages banned identities.
245
+ A banned identity will fail to authenticate with any associated credentials (except [tokens](#stateless-tokens) within
246
+ the `refresh` period).
247
+
248
+ ```http
249
+ PUT /identity/bans/:id/
250
+ authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
251
+ content-type: application/yaml
252
+
253
+ banned: true
254
+ ```
255
+
256
+ Access requires `system:identity:bans` role.
257
+
258
+ ## Authentication echo
259
+
260
+ Exposition implements a predefined resource `/identity/` with the `GET` method, which returns the
261
+ Identity resolved by the provided credentials.
262
+
263
+ ```http
264
+ GET /identity/
265
+ authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
266
+ accept: application/yaml
267
+ ```
268
+
269
+ ```
270
+ 200 OK
271
+
272
+ id: fc8e66ddd51d45eea89602c9dd38a542
273
+ roles:
274
+ - developer
275
+ - system:identity:roles
276
+ ```
@@ -0,0 +1,156 @@
1
+ # Identity
2
+
3
+ Identity is the fundamental entity within an authentication system that represents the **unique
4
+ identifier** of an
5
+ individual, organization, application or device.
6
+
7
+ In order to prove its Identity, the request originator must provide a valid _credentials_ that are
8
+ associated with that
9
+ Identity.
10
+
11
+ Identity is intrinsically linked to credentials, as an Identity is established only when the first
12
+ set of credentials
13
+ for that Identity is created.
14
+ In other words, the creation of credentials marks the inception of an Identity.
15
+ Once the last credentials are removed from the Identity, it ceases to exist.
16
+ Without credentials, there is no basis for defining or asserting an Identity.
17
+
18
+ ## Authentication
19
+
20
+ The Authenticaiton system resolves provided credentials to an Identity using one of the supported
21
+ authentication
22
+ schemes.
23
+
24
+ The Authentication is request-agnostic, meaning it does not depend on the specific URL being
25
+ requested or the content of
26
+ the request body.
27
+ The only information it handles is the value of the `Authorization` header.
28
+
29
+ > Except for its own [management resources](#persistent-credentials).
30
+
31
+ If the provided credentials are not valid or not associated with an Identity, then Authentication
32
+ interrupts request
33
+ processing and responds with an authentication error.
34
+
35
+ ### Basic scheme
36
+
37
+ Classic username/password pair. See [RFC7617](https://datatracker.ietf.org/doc/html/rfc7617).
38
+
39
+ ```http
40
+ Authorization: Basic aGVsbG86d29ybGQK
41
+ ```
42
+
43
+ See [`identity.basic` component](components.md#basic-credentials).
44
+
45
+ ### Token scheme
46
+
47
+ Tokens issued by the Authentication system. These tokens are [PASETO](https://paseto.io).
48
+
49
+ ```http
50
+ Authrization: Token v4.local.eyJzdWIiOiJqb2hu...
51
+ ```
52
+
53
+ The `Token` is the **primary** authentication scheme.
54
+ If request originators use an alternative authentication scheme, they will receive a response
55
+ containing `Token`
56
+ credentials and will be required to switch to the `Token` scheme for any subsequent requests.
57
+ Continued use of other authentication schemes will result in temporary blocking of requests.
58
+
59
+ See [`identity.tokens` component](components.md#stateless-tokens).
60
+
61
+ ### Bearer scheme
62
+
63
+ OpenID tokens issued by trusted providers.
64
+ For more information, refer
65
+ to [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html),
66
+ [RFC6750](https://datatracker.ietf.org/doc/html/rfc6750).
67
+
68
+ ```http
69
+ Authorization: Bearer eyJhbGciOiJIUzI1...
70
+ ```
71
+
72
+ Trusted providers are specified using the `idenity.trust` property within the Exposition annotation.
73
+
74
+ ```yaml
75
+ # context.toa.yaml
76
+
77
+ exposition:
78
+ identity:
79
+ trust:
80
+ - https://accounts.google.com
81
+ - https://appleid.apple.com
82
+ ```
83
+
84
+ The example above demonstrates the default list of trusted providers.
85
+
86
+ ## Identity inception
87
+
88
+ The simplest way to establish a relationship between an Identity and an entity representing a user
89
+ is to synchronize their identifiers.
90
+
91
+ This can be achieved by using the `auth:incept` directive as follows:
92
+
93
+ ```yaml
94
+ # manifest.toa.yaml
95
+ name: users
96
+
97
+ entity:
98
+ schema:
99
+ name: string
100
+
101
+ exposition:
102
+ /:
103
+ POST:
104
+ incept: id
105
+ endpoint: transit
106
+ ```
107
+
108
+ The value of the `auth:incept` directive refers to the name of the response property that will be
109
+ returned by the `POST` operation, containing the created entity identifier.
110
+
111
+ A request with Identity inception must contain (non-existent) credentials that will be associated
112
+ with the created Identity.
113
+
114
+ ```http
115
+ POST /users/
116
+ authorization: Basic dXNlcjpwYXNz
117
+ accept: application/yaml
118
+ content-type: application/yaml
119
+
120
+ name: John
121
+ ```
122
+
123
+ ```
124
+ 201 Created
125
+ content-type: application/yaml
126
+
127
+ id: 2428c31ecb6e4a51a24ef52f0c4181b9
128
+ ```
129
+
130
+ As a result of processing the above request, the provided Basic credentials associated with the
131
+ Identity `2428c31ecb6e4a51a24ef52f0c4181b9` are created.
132
+
133
+ ## FAQ
134
+
135
+ <dl>
136
+ <dt>How can I log in a user?</dt>
137
+ <dd>
138
+ Technically speaking, since the Authentication is request-agnostic, user credentials
139
+ can be sent with any request.
140
+
141
+ However, it is most likely that a request originator will need to obtain an Identity value for
142
+ subsequent requests.
143
+ For this reason, it is recommended to make a `GET /identity/` request.
144
+ </dd>
145
+ <dt>How can I log out a user?</dt>
146
+ <dd>Delete <code>Token</code> credentials from the device.</dd>
147
+ <dt>Where are the sessions?</dt>
148
+ <dd>
149
+ The Authentication is stateless, meaning it does not store any information between
150
+ requests except for persistent credentials.</dd>
151
+ <dt>How can I pass the Identity to an operation call?</dt>
152
+ <dd>
153
+ This is not possible. Refer to the Resource Design Guidelines for more information.
154
+ <a href="https://github.com/toa-io/toa/issues/353">#353</a>
155
+ </dd>
156
+ </dl>