@toa.io/extensions.exposition 0.9.0-canary.2 → 0.20.0-alpha.1

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
@@ -0,0 +1,145 @@
1
+ import { type Component } from '@toa.io/core'
2
+ import { Nope } from 'nopeable'
3
+ import { type Parameter } from '../../RTD'
4
+ import { type Family, type Output } from '../../Directive'
5
+ import { type Remotes } from '../../Remotes'
6
+ import * as http from '../../HTTP'
7
+ import {
8
+ type AuthenticationResult,
9
+ type Ban,
10
+ type Directive,
11
+ type Discovery,
12
+ type Extension,
13
+ type Identity,
14
+ type Input, type Remote,
15
+ type Schemes
16
+ } from './types'
17
+ import { Anonymous } from './Anonymous'
18
+ import { Id } from './Id'
19
+ import { Role } from './Role'
20
+ import { Rule } from './Rule'
21
+ import { Incept } from './Incept'
22
+ import { split } from './split'
23
+ import { PRIMARY, PROVIDERS } from './schemes'
24
+ import { Scheme } from './Scheme'
25
+ import { Echo } from './Echo'
26
+
27
+ class Authorization implements Family<Directive, Extension> {
28
+ public readonly name: string = 'auth'
29
+ public readonly mandatory: boolean = true
30
+ private readonly schemes = {} as unknown as Schemes
31
+ private readonly discovery = {} as unknown as Discovery
32
+ private tokens: Component | null = null
33
+ private bans: Component | null = null
34
+
35
+ public create (name: string, value: any, remotes: Remotes): Directive {
36
+ const Class = CLASSES[name]
37
+
38
+ if (Class === undefined)
39
+ throw new Error(`Directive '${name}' is not provided by the '${this.name}' family.`)
40
+
41
+ for (const name of REMOTES)
42
+ this.discovery[name] ??= remotes.discover('identity', name)
43
+
44
+ if (Class === Role) return new Class(value, this.discovery.roles)
45
+ else if (Class === Rule) return new Class(value, this.create.bind(this))
46
+ else if (Class === Incept) return new Class(value, this.discovery)
47
+ else return new Class(value)
48
+ }
49
+
50
+ public async preflight
51
+ (directives: Directive[], input: Input, parameters: Parameter[]): Promise<Output> {
52
+ const identity = await this.resolve(input.headers.authorization)
53
+
54
+ input.identity = identity
55
+
56
+ for (const directive of directives) {
57
+ const allow = await directive.authorize(identity, input, parameters)
58
+
59
+ if (allow)
60
+ return directive.reply?.(identity) ?? null
61
+ }
62
+
63
+ if (identity === null) throw new http.Unauthorized()
64
+ else throw new http.Forbidden()
65
+ }
66
+
67
+ public async settle
68
+ (directives: Directive[], request: Input, response: http.OutgoingMessage): Promise<void> {
69
+ for (const directive of directives)
70
+ await directive.settle?.(request, response)
71
+
72
+ const identity = request.identity
73
+
74
+ if (identity === null)
75
+ return
76
+
77
+ if (identity.scheme === PRIMARY && !identity.refresh)
78
+ return
79
+
80
+ // Role directive may have already set the value
81
+ if (identity.roles === undefined)
82
+ await Role.set(identity, this.discovery.roles)
83
+
84
+ this.tokens ??= await this.discovery.tokens
85
+
86
+ const token = await this.tokens.invoke<string>('encrypt', { input: { identity } })
87
+ const authorization = `Token ${token}`
88
+
89
+ if (response.headers === undefined)
90
+ response.headers = {}
91
+
92
+ response.headers.authorization = authorization
93
+ }
94
+
95
+ private async resolve (authorization: string | undefined): Promise<Identity | null> {
96
+ if (authorization === undefined)
97
+ return null
98
+
99
+ const [scheme, credentials] = split(authorization)
100
+ const provider = PROVIDERS[scheme]
101
+
102
+ if (!(provider in this.discovery))
103
+ throw new http.Unauthorized(`Unknown authentication scheme '${scheme}'.`)
104
+
105
+ this.schemes[scheme] ??= await this.discovery[provider]
106
+
107
+ const result = await this.schemes[scheme]
108
+ .invoke<AuthenticationResult>('authenticate', { input: credentials })
109
+
110
+ if (result instanceof Nope)
111
+ return null
112
+
113
+ const identity = result.identity
114
+
115
+ if (scheme !== PRIMARY && await this.banned(identity))
116
+ throw new http.Unauthorized()
117
+
118
+ identity.scheme = scheme
119
+ identity.refresh = result.refresh
120
+
121
+ return identity
122
+ }
123
+
124
+ private async banned (identity: Identity): Promise<boolean> {
125
+ this.bans ??= await this.discovery.bans
126
+
127
+ const ban = await this.bans.invoke<Ban>('observe', { query: { id: identity.id } })
128
+
129
+ return ban.banned
130
+ }
131
+ }
132
+
133
+ const CLASSES: Record<string, new (value: any, argument?: any) => Directive> = {
134
+ anonymous: Anonymous,
135
+ id: Id,
136
+ role: Role,
137
+ rule: Rule,
138
+ incept: Incept,
139
+ scheme: Scheme,
140
+ echo: Echo
141
+ }
142
+
143
+ const REMOTES: Remote[] = ['basic', 'tokens', 'roles', 'bans']
144
+
145
+ export = new Authorization()
@@ -0,0 +1,19 @@
1
+ import { type Parameter } from '../../RTD'
2
+ import { type Directive, type Identity } from './types'
3
+
4
+ export class Id implements Directive {
5
+ private readonly parameter: string
6
+
7
+ public constructor (parameter: string) {
8
+ this.parameter = parameter
9
+ }
10
+
11
+ public authorize (identity: Identity | null, _: any, parameters: Parameter[]): boolean {
12
+ if (identity === null)
13
+ return false
14
+
15
+ const parameter = parameters.find((parameter) => parameter.name === this.parameter)
16
+
17
+ return parameter?.value === identity.id
18
+ }
19
+ }
@@ -0,0 +1,42 @@
1
+ import { Nope, type Nopeable } from 'nopeable'
2
+ import * as http from '../../HTTP'
3
+ import { type Directive, type Discovery, type Identity, type Input, type Schemes } from './types'
4
+ import { split } from './split'
5
+ import { PROVIDERS } from './schemes'
6
+
7
+ export class Incept implements Directive {
8
+ private readonly property: string
9
+ private readonly discovery: Discovery
10
+ private readonly schemes: Schemes = {} as unknown as Schemes
11
+
12
+ public constructor (property: string, discovery: Discovery) {
13
+ this.property = property
14
+ this.discovery = discovery
15
+ }
16
+
17
+ public authorize (identity: Identity | null, input: Input): boolean {
18
+ return identity === null && 'authorization' in input.headers
19
+ }
20
+
21
+ public async settle (request: Input, response: http.OutgoingMessage): Promise<void> {
22
+ const id = response.body?.[this.property]
23
+
24
+ if (id === undefined)
25
+ throw new http.Conflict('Identity inception has failed as the response body ' +
26
+ ` does not contain the '${this.property}' property.`)
27
+
28
+ const [scheme, credentials] = split(request.headers.authorization as string)
29
+ const provider = PROVIDERS[scheme]
30
+
31
+ this.schemes[scheme] ??= await this.discovery[provider]
32
+
33
+ const identity = await this.schemes[scheme]
34
+ .invoke<Nopeable<Identity>>('create', { input: { id, credentials } })
35
+
36
+ if (identity instanceof Nope)
37
+ throw new http.Conflict(identity)
38
+
39
+ request.identity = identity
40
+ request.identity.scheme = scheme
41
+ }
42
+ }
@@ -0,0 +1,62 @@
1
+ import { type Component } from '@toa.io/core'
2
+ import { generate } from 'randomstring'
3
+ import { Role } from './Role'
4
+ import { type Identity } from './types'
5
+
6
+ const remote = {
7
+ invoke: jest.fn()
8
+ } as unknown as jest.MockedObject<Component>
9
+
10
+ const discovery = Promise.resolve(remote)
11
+
12
+ beforeEach(() => {
13
+ jest.clearAllMocks()
14
+ })
15
+
16
+ it('should return false if not matched', async () => {
17
+ const roles = ['admin', 'user']
18
+ const directive = new Role(roles, discovery)
19
+ const identity: Identity = { id: generate(), scheme: '', refresh: false }
20
+
21
+ remote.invoke.mockResolvedValueOnce(['guest'])
22
+
23
+ const result = await directive.authorize(identity)
24
+
25
+ expect(result).toBe(false)
26
+
27
+ expect(remote.invoke)
28
+ .toBeCalledWith('list', { query: { criteria: `identity==${identity.id}`, limit: 1024 } })
29
+ })
30
+
31
+ it('should return true on exact match', async () => {
32
+ const result = await match(['admin', 'user'], ['user'])
33
+
34
+ expect(result).toBe(true)
35
+ })
36
+
37
+ it('should return true on scope match', async () => {
38
+ const result = await match(['system:identity:roles'], ['system'])
39
+
40
+ expect(result).toBe(true)
41
+ })
42
+
43
+ it('should return false on scope mismatch', async () => {
44
+ const result = await match(['system:identity'], ['system:identity:roles'])
45
+
46
+ expect(result).toBe(false)
47
+ })
48
+
49
+ it('should return false on non-scope substring match', async () => {
50
+ const result = await match(['system:identity'], ['system:iden'])
51
+
52
+ expect(result).toBe(false)
53
+ })
54
+
55
+ async function match (expected: string[], actual: string[]): Promise<boolean> {
56
+ const directive = new Role(expected, discovery)
57
+ const identity: Identity = { id: generate(), scheme: '', refresh: false }
58
+
59
+ remote.invoke.mockResolvedValueOnce(actual)
60
+
61
+ return await directive.authorize(identity)
62
+ }
@@ -0,0 +1,56 @@
1
+ import { type Component, type Query } from '@toa.io/core'
2
+ import { type Directive, type Identity } from './types'
3
+
4
+ export class Role implements Directive {
5
+ public static remote: Component | null = null
6
+ private readonly roles: string[]
7
+ private readonly discovery: Promise<Component>
8
+
9
+ public constructor (roles: string | string[], discovery: Promise<Component>) {
10
+ this.roles = typeof roles === 'string' ? [roles] : roles
11
+ this.discovery = discovery
12
+ }
13
+
14
+ public static async set (identity: Identity, discovery: Promise<Component>): Promise<void> {
15
+ this.remote ??= await discovery
16
+
17
+ const query: Query = { criteria: `identity==${identity.id}`, limit: 1024 }
18
+ const roles: string[] = await this.remote.invoke('list', { query })
19
+
20
+ identity.roles = roles
21
+ }
22
+
23
+ public async authorize (identity: Identity | null): Promise<boolean> {
24
+ if (identity === null)
25
+ return false
26
+
27
+ await Role.set(identity, this.discovery)
28
+
29
+ if (identity.roles === undefined)
30
+ return false
31
+
32
+ return this.match(identity.roles)
33
+ }
34
+
35
+ private match (roles: string[]): boolean {
36
+ for (const role of roles) {
37
+ const index = this.roles.findIndex((expected) => compare(expected, role))
38
+
39
+ if (index !== -1)
40
+ return true
41
+ }
42
+
43
+ return false
44
+ }
45
+ }
46
+
47
+ function compare (expected: string, actual: string): boolean {
48
+ const exp = expected.split(':')
49
+ const act = actual.split(':')
50
+
51
+ for (let i = 0; i < act.length; i++)
52
+ if (exp[i] !== act[i])
53
+ return false
54
+
55
+ return true
56
+ }
@@ -0,0 +1,28 @@
1
+ import { type Parameter } from '../../RTD'
2
+ import { type Directive, type Identity } from './types'
3
+
4
+ export class Rule implements Directive {
5
+ private readonly directives: Directive[] = []
6
+
7
+ public constructor (directives: Record<string, any>, create: Create) {
8
+ for (const [name, value] of Object.entries(directives)) {
9
+ const directive = create(name, value)
10
+
11
+ this.directives.push(directive)
12
+ }
13
+ }
14
+
15
+ public async authorize
16
+ (identity: Identity | null, input: any, parameters: Parameter[]): Promise<boolean> {
17
+ for (const directive of this.directives) {
18
+ const authorized = await directive.authorize(identity, input, parameters)
19
+
20
+ if (!authorized)
21
+ return false
22
+ }
23
+
24
+ return true
25
+ }
26
+ }
27
+
28
+ type Create = (name: string, value: any) => Directive
@@ -0,0 +1,26 @@
1
+ import * as http from '../../HTTP'
2
+ import { type Directive, type Identity, type Input } from './types'
3
+ import { split } from './split'
4
+
5
+ export class Scheme implements Directive {
6
+ private readonly scheme: string
7
+ private readonly Scheme: string
8
+
9
+ public constructor (scheme: string) {
10
+ this.scheme = scheme.toLowerCase()
11
+ this.Scheme = scheme[0].toUpperCase() + scheme.substring(1)
12
+ }
13
+
14
+ public authorize (_: Identity | null, input: Input): boolean {
15
+ if (input.headers.authorization === undefined)
16
+ return false
17
+
18
+ const [scheme] = split(input.headers.authorization)
19
+
20
+ if (scheme !== this.scheme)
21
+ throw new http.Forbidden(this.Scheme +
22
+ ' authentication scheme is required to access this resource.')
23
+
24
+ return false
25
+ }
26
+ }
@@ -0,0 +1,3 @@
1
+ import Family from './Family'
2
+
3
+ export = Family
@@ -0,0 +1,8 @@
1
+ import { type Remote, type Scheme } from './types'
2
+
3
+ export const PROVIDERS: Record<Scheme, Remote> = {
4
+ basic: 'basic',
5
+ token: 'tokens'
6
+ }
7
+
8
+ export const PRIMARY: Scheme = 'token'
@@ -0,0 +1,15 @@
1
+ import * as http from '../../HTTP'
2
+ import { type Scheme } from './types'
3
+
4
+ export function split (authorization: string): [Scheme, string] {
5
+ const space = authorization.indexOf(' ')
6
+
7
+ if (space === -1)
8
+ throw new http.Unauthorized('Malformed authorization header.')
9
+
10
+ const Scheme = authorization.slice(0, space)
11
+ const scheme = Scheme.toLowerCase() as Scheme
12
+ const value = authorization.slice(space + 1)
13
+
14
+ return [scheme, value]
15
+ }
@@ -0,0 +1,37 @@
1
+ import { type Component } from '@toa.io/core'
2
+ import { type Nopeable } from 'nopeable'
3
+ import { type Parameter } from '../../RTD'
4
+ import type * as http from '../../HTTP'
5
+ import type * as directive from '../../Directive'
6
+
7
+ export interface Directive {
8
+ authorize: (identity: Identity | null, input: Input, parameters: Parameter[]) =>
9
+ boolean | Promise<boolean>
10
+
11
+ reply?: (identity: Identity | null) => http.OutgoingMessage
12
+
13
+ settle?: (request: Input, response: http.OutgoingMessage) => Promise<void>
14
+ }
15
+
16
+ export interface Identity {
17
+ readonly id: string
18
+ scheme: string
19
+ roles?: string[]
20
+ refresh: boolean
21
+ }
22
+
23
+ export interface Extension {
24
+ identity: Identity | null
25
+ }
26
+
27
+ export interface Ban {
28
+ banned: boolean
29
+ }
30
+
31
+ export type Input = directive.Input & Extension
32
+ export type AuthenticationResult = Nopeable<{ identity: Identity, refresh: boolean }>
33
+
34
+ export type Scheme = 'basic' | 'token'
35
+ export type Remote = 'basic' | 'tokens' | 'roles' | 'bans'
36
+ export type Discovery = Record<Remote, Promise<Component>>
37
+ export type Schemes = Record<Scheme, Component>
@@ -0,0 +1,34 @@
1
+ import { type Input, type Output, type Family } from '../../Directive'
2
+ import { Stub } from './Stub'
3
+ import { type Directive } from './types'
4
+
5
+ class Development implements Family<Directive> {
6
+ public readonly name: string = 'dev'
7
+ public readonly mandatory: boolean = false
8
+
9
+ public create (name: string, value: any): Directive {
10
+ const Class = constructors[name]
11
+
12
+ if (Class === undefined)
13
+ throw new Error(`Directive '${name}' is not provided by the '${this.name}' family.`)
14
+
15
+ return new Class(value)
16
+ }
17
+
18
+ public preflight (directives: Directive[], input: Input): Output {
19
+ for (const directive of directives) {
20
+ const output = directive.apply(input)
21
+
22
+ if (output !== null)
23
+ return output
24
+ }
25
+
26
+ return null
27
+ }
28
+ }
29
+
30
+ const constructors: Record<string, new (value: any) => Directive> = {
31
+ stub: Stub
32
+ }
33
+
34
+ export = new Development()
@@ -0,0 +1,14 @@
1
+ import { type Output } from '../../Directive'
2
+ import { type Directive } from './types'
3
+
4
+ export class Stub implements Directive {
5
+ private readonly value: any
6
+
7
+ public constructor (value: any) {
8
+ this.value = value
9
+ }
10
+
11
+ public apply (): Output {
12
+ return { body: this.value }
13
+ }
14
+ }
@@ -0,0 +1,3 @@
1
+ import Family from './Family'
2
+
3
+ export = Family
@@ -0,0 +1,5 @@
1
+ import { type Input, type Output } from '../../Directive'
2
+
3
+ export interface Directive {
4
+ apply: (input: Input) => Output
5
+ }
@@ -0,0 +1,5 @@
1
+ import { type Family } from '../Directive'
2
+ import Dev from './dev'
3
+ import Auth from './auth'
4
+
5
+ export const families: Family[] = [Dev, Auth]
@@ -0,0 +1 @@
1
+ export type Label = 'ping' | 'expose'
@@ -0,0 +1,17 @@
1
+ import { type Exception } from '@toa.io/core'
2
+ import * as http from './HTTP'
3
+
4
+ export function rethrow (exception: Exception): void {
5
+ // see /runtime/core/src/exceptions.js
6
+
7
+ if ((exception.code >= 200 && exception.code < 210) || exception.code === 221)
8
+ throw new http.BadRequest(exception.message)
9
+
10
+ if (exception.code === 302)
11
+ throw new http.NotFound()
12
+
13
+ if (exception.code === 303)
14
+ throw new http.PreconditionFailed()
15
+
16
+ throw exception as unknown as Error
17
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * WebStorm can't find jest types if they are in the root of the project,
3
+ * while a current project has its own node_modules.
4
+ *
5
+ * Importing jest here fixes the issue.
6
+ */
7
+ import 'jest'
8
+
9
+ it.skip('', () => undefined) // prevent jest from complaining about missing tests
@@ -0,0 +1,6 @@
1
+ export { manifest } from './manifest'
2
+ export { deployment } from './deployment'
3
+ export { components } from './Composition'
4
+ export { Factory } from './Factory'
5
+
6
+ export type { Remotes } from './Remotes'
@@ -0,0 +1,57 @@
1
+ import { type Manifest } from '@toa.io/norm'
2
+ import { generate } from 'randomstring'
3
+ import { manifest } from './manifest'
4
+
5
+ const name = 'cm-' + generate()
6
+ const namespace = 'ns' + generate()
7
+
8
+ let mf: Manifest
9
+
10
+ beforeEach(() => {
11
+ mf = { namespace, name } as unknown as Manifest
12
+ })
13
+
14
+ const declaration = {
15
+ '/': { GET: 'observe' }
16
+ }
17
+
18
+ it('should create branch', async () => {
19
+ const node = manifest(declaration, mf)
20
+
21
+ expect(node).toBeDefined()
22
+
23
+ // namespace route
24
+ expect(node.routes).toHaveLength(1)
25
+ expect(node.routes[0].path).toBe('/' + namespace)
26
+
27
+ const ns = node.routes[0].node
28
+
29
+ // component route
30
+ expect(ns.routes).toHaveLength(1)
31
+ expect(ns.routes[0].path).toBe('/' + name)
32
+ })
33
+
34
+ it('should not create node for default namespace', async () => {
35
+ mf.namespace = 'default'
36
+
37
+ const node = manifest(declaration, mf)
38
+
39
+ expect(node.routes).toHaveLength(1)
40
+ expect(node.routes[0].path).toBe('/' + name)
41
+ })
42
+
43
+ it('should throw on invalid declaration type', async () => {
44
+ expect(() => manifest('hello' as unknown as object, mf)).toThrow('RTD parse error')
45
+ })
46
+
47
+ it('should set namespace and component', async () => {
48
+ const node = manifest(declaration, mf)
49
+
50
+ const ns = node.routes[0].node
51
+ const cm = ns.routes[0].node
52
+ const root = cm.routes[0].node
53
+ const GET = root.methods[0]
54
+
55
+ expect(GET.mapping?.namespace).toBe(namespace)
56
+ expect(GET.mapping?.component).toBe(name)
57
+ })
@@ -0,0 +1,35 @@
1
+ import { parse, type Node } from './RTD/syntax'
2
+ import { shortcuts } from './Directive'
3
+ import type { Manifest } from '@toa.io/norm'
4
+
5
+ export function manifest (declaration: object, manifest: Manifest): Node {
6
+ declaration = wrap(manifest.name, declaration)
7
+
8
+ if (manifest.namespace !== undefined && manifest.namespace !== 'default')
9
+ declaration = wrap(manifest.namespace, declaration)
10
+
11
+ const node = parse(declaration, shortcuts)
12
+
13
+ concretize(node, manifest)
14
+
15
+ return node
16
+ }
17
+
18
+ function wrap (segment: string, declaration: object): object {
19
+ return { ['/' + segment]: { protected: true, ...declaration } }
20
+ }
21
+
22
+ function concretize (node: Node, manifest: Manifest): void {
23
+ for (const route of node.routes) {
24
+ for (const method of route.node.methods) {
25
+ // eslint-disable-next-line max-depth
26
+ if (method.mapping === undefined)
27
+ continue
28
+
29
+ method.mapping.namespace = manifest.namespace
30
+ method.mapping.component = manifest.name
31
+ }
32
+
33
+ concretize(route.node, manifest)
34
+ }
35
+ }