@toa.io/extensions.exposition 0.24.0-alpha.17 → 0.24.0-alpha.19

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 (234) hide show
  1. package/components/context.toa.yaml +12 -0
  2. package/components/identity.bans/manifest.toa.yaml +1 -1
  3. package/components/identity.basic/manifest.toa.yaml +1 -1
  4. package/components/identity.basic/operations/authenticate.js +1 -2
  5. package/components/identity.basic/operations/authenticate.js.map +1 -1
  6. package/components/identity.basic/operations/transit.js.map +1 -1
  7. package/components/identity.basic/operations/tsconfig.tsbuildinfo +1 -1
  8. package/components/identity.basic/source/authenticate.ts +0 -1
  9. package/components/identity.federation/events/principal.js +22 -0
  10. package/components/identity.federation/manifest.toa.yaml +88 -0
  11. package/components/identity.federation/operations/assertions-as-values.cjs +45 -0
  12. package/components/identity.federation/operations/assertions-as-values.cjs.map +1 -0
  13. package/components/identity.federation/operations/assertions-as-values.d.cts +4 -0
  14. package/components/identity.federation/operations/authenticate.d.ts +3 -0
  15. package/components/identity.federation/operations/authenticate.js +20 -0
  16. package/components/identity.federation/operations/authenticate.js.map +1 -0
  17. package/components/identity.federation/operations/create.d.ts +10 -0
  18. package/components/identity.federation/operations/create.js +15 -0
  19. package/components/identity.federation/operations/create.js.map +1 -0
  20. package/components/identity.federation/operations/jwt.cjs +112 -0
  21. package/components/identity.federation/operations/jwt.cjs.map +1 -0
  22. package/components/identity.federation/operations/jwt.d.cts +19 -0
  23. package/components/identity.federation/operations/schemas.d.ts +43 -0
  24. package/components/identity.federation/operations/schemas.js +9 -0
  25. package/components/identity.federation/operations/schemas.js.map +1 -0
  26. package/components/identity.federation/operations/tsconfig.tsbuildinfo +1 -0
  27. package/components/identity.federation/operations/types.d.ts +51 -0
  28. package/components/identity.federation/operations/types.js +3 -0
  29. package/components/identity.federation/operations/types.js.map +1 -0
  30. package/components/identity.federation/source/assertions-as-values.cts +20 -0
  31. package/components/identity.federation/source/authenticate.ts +28 -0
  32. package/components/identity.federation/source/create.ts +26 -0
  33. package/components/identity.federation/source/jwt.cts +143 -0
  34. package/components/identity.federation/source/schemas.ts +45 -0
  35. package/components/identity.federation/source/types.ts +56 -0
  36. package/components/identity.federation/tsconfig.json +9 -0
  37. package/components/identity.roles/operations/tsconfig.tsbuildinfo +1 -1
  38. package/components/identity.tokens/manifest.toa.yaml +1 -1
  39. package/components/identity.tokens/operations/authenticate.js.map +1 -1
  40. package/components/identity.tokens/operations/decrypt.js.map +1 -1
  41. package/components/identity.tokens/operations/tsconfig.tsbuildinfo +1 -1
  42. package/cucumber.js +0 -1
  43. package/documentation/components.md +24 -1
  44. package/documentation/identity.md +7 -7
  45. package/documentation/protocol.md +20 -1
  46. package/documentation/query.md +1 -1
  47. package/documentation/vary.md +69 -0
  48. package/features/cors.feature +6 -26
  49. package/features/identity.feature +17 -1
  50. package/features/identity.federation.feature +125 -0
  51. package/features/response.feature +0 -0
  52. package/features/steps/Captures.ts +5 -0
  53. package/features/steps/Components.ts +5 -0
  54. package/features/steps/HTTP.ts +33 -5
  55. package/features/steps/IdP.ts +120 -0
  56. package/features/steps/Parameters.ts +8 -2
  57. package/features/steps/Workspace.ts +5 -7
  58. package/features/vary.feature +120 -0
  59. package/package.json +17 -18
  60. package/source/Directive.test.ts +8 -2
  61. package/source/Directive.ts +19 -16
  62. package/source/Factory.ts +8 -7
  63. package/source/Gateway.ts +22 -8
  64. package/source/HTTP/Server.fixtures.ts +0 -1
  65. package/source/HTTP/Server.test.ts +61 -138
  66. package/source/HTTP/Server.ts +45 -31
  67. package/source/HTTP/formats/text.ts +1 -1
  68. package/source/HTTP/formats/yaml.ts +1 -1
  69. package/source/HTTP/messages.ts +8 -2
  70. package/source/Interception.ts +24 -0
  71. package/source/RTD/Directives.ts +2 -2
  72. package/source/RTD/syntax/parse.ts +6 -6
  73. package/source/RTD/syntax/types.ts +1 -1
  74. package/source/directives/auth/{Family.ts → Authorization.ts} +29 -33
  75. package/source/directives/auth/Incept.ts +1 -1
  76. package/source/directives/auth/Rule.ts +2 -2
  77. package/source/directives/auth/index.ts +2 -2
  78. package/source/directives/auth/schemes.ts +2 -1
  79. package/source/directives/auth/types.ts +9 -6
  80. package/source/directives/cache/{Family.ts → Cache.ts} +4 -5
  81. package/source/directives/cache/index.ts +2 -2
  82. package/source/directives/cache/types.ts +1 -1
  83. package/source/directives/cors/CORS.ts +52 -0
  84. package/source/directives/cors/index.ts +3 -0
  85. package/source/directives/dev/{Family.ts → Development.ts} +3 -4
  86. package/source/directives/dev/Stub.ts +4 -4
  87. package/source/directives/dev/Throw.ts +4 -4
  88. package/source/directives/dev/index.ts +2 -2
  89. package/source/directives/dev/types.ts +1 -1
  90. package/source/directives/index.ts +10 -6
  91. package/source/directives/octets/Context.ts +1 -1
  92. package/source/directives/octets/Delete.ts +1 -2
  93. package/source/directives/octets/Fetch.ts +1 -1
  94. package/source/directives/octets/List.ts +1 -1
  95. package/source/directives/octets/{Family.ts → Octets.ts} +3 -4
  96. package/source/directives/octets/Permute.ts +1 -1
  97. package/source/directives/octets/Store.ts +3 -3
  98. package/source/directives/octets/index.ts +2 -2
  99. package/source/directives/octets/types.ts +3 -3
  100. package/source/directives/vary/Directive.ts +6 -0
  101. package/source/directives/vary/Embed.ts +62 -0
  102. package/source/directives/vary/Properties.ts +17 -0
  103. package/source/directives/vary/Vary.ts +48 -0
  104. package/source/directives/vary/embeddings/Embedding.ts +6 -0
  105. package/source/directives/vary/embeddings/Header.ts +30 -0
  106. package/source/directives/vary/embeddings/Language.ts +31 -0
  107. package/source/directives/vary/embeddings/index.ts +11 -0
  108. package/source/directives/vary/index.ts +3 -0
  109. package/source/io.ts +4 -0
  110. package/transpiled/Composition.js.map +1 -1
  111. package/transpiled/Directive.d.ts +6 -7
  112. package/transpiled/Directive.js +12 -10
  113. package/transpiled/Directive.js.map +1 -1
  114. package/transpiled/Factory.d.ts +0 -1
  115. package/transpiled/Factory.js +7 -6
  116. package/transpiled/Factory.js.map +1 -1
  117. package/transpiled/Gateway.d.ts +8 -5
  118. package/transpiled/Gateway.js +12 -2
  119. package/transpiled/Gateway.js.map +1 -1
  120. package/transpiled/HTTP/Server.d.ts +4 -2
  121. package/transpiled/HTTP/Server.fixtures.d.ts +0 -1
  122. package/transpiled/HTTP/Server.fixtures.js +1 -2
  123. package/transpiled/HTTP/Server.fixtures.js.map +1 -1
  124. package/transpiled/HTTP/Server.js +33 -22
  125. package/transpiled/HTTP/Server.js.map +1 -1
  126. package/transpiled/HTTP/formats/text.d.ts +3 -1
  127. package/transpiled/HTTP/formats/text.js.map +1 -1
  128. package/transpiled/HTTP/formats/yaml.js +1 -1
  129. package/transpiled/HTTP/formats/yaml.js.map +1 -1
  130. package/transpiled/HTTP/messages.d.ts +4 -0
  131. package/transpiled/HTTP/messages.js +4 -2
  132. package/transpiled/HTTP/messages.js.map +1 -1
  133. package/transpiled/Interception.d.ts +9 -0
  134. package/transpiled/Interception.js +19 -0
  135. package/transpiled/Interception.js.map +1 -0
  136. package/transpiled/Query.js.map +1 -1
  137. package/transpiled/RTD/Directives.d.ts +2 -2
  138. package/transpiled/RTD/Node.js.map +1 -1
  139. package/transpiled/RTD/Route.js.map +1 -1
  140. package/transpiled/RTD/syntax/parse.js +1 -1
  141. package/transpiled/RTD/syntax/parse.js.map +1 -1
  142. package/transpiled/RTD/syntax/types.js +1 -1
  143. package/transpiled/RTD/syntax/types.js.map +1 -1
  144. package/transpiled/deployment.js.map +1 -1
  145. package/transpiled/directives/auth/{Family.d.ts → Authorization.d.ts} +4 -4
  146. package/transpiled/directives/auth/{Family.js → Authorization.js} +15 -8
  147. package/transpiled/directives/auth/Authorization.js.map +1 -0
  148. package/transpiled/directives/auth/Incept.js.map +1 -1
  149. package/transpiled/directives/auth/Role.js.map +1 -1
  150. package/transpiled/directives/auth/Rule.d.ts +2 -2
  151. package/transpiled/directives/auth/Rule.js.map +1 -1
  152. package/transpiled/directives/auth/index.d.ts +2 -2
  153. package/transpiled/directives/auth/index.js +4 -5
  154. package/transpiled/directives/auth/index.js.map +1 -1
  155. package/transpiled/directives/auth/schemes.js +2 -1
  156. package/transpiled/directives/auth/schemes.js.map +1 -1
  157. package/transpiled/directives/auth/types.d.ts +4 -4
  158. package/transpiled/directives/cache/{Family.d.ts → Cache.d.ts} +4 -5
  159. package/transpiled/directives/cache/{Family.js → Cache.js} +4 -2
  160. package/transpiled/directives/cache/{Family.js.map → Cache.js.map} +1 -1
  161. package/transpiled/directives/cache/index.d.ts +2 -2
  162. package/transpiled/directives/cache/index.js +4 -5
  163. package/transpiled/directives/cache/index.js.map +1 -1
  164. package/transpiled/directives/cache/types.d.ts +1 -1
  165. package/transpiled/directives/cors/CORS.d.ts +14 -0
  166. package/transpiled/directives/cors/CORS.js +42 -0
  167. package/transpiled/directives/cors/CORS.js.map +1 -0
  168. package/transpiled/directives/cors/index.d.ts +2 -0
  169. package/transpiled/directives/cors/index.js +6 -0
  170. package/transpiled/directives/cors/index.js.map +1 -0
  171. package/transpiled/directives/dev/{Family.d.ts → Development.d.ts} +3 -4
  172. package/transpiled/directives/dev/{Family.js → Development.js} +4 -2
  173. package/transpiled/directives/dev/Development.js.map +1 -0
  174. package/transpiled/directives/dev/Stub.d.ts +3 -3
  175. package/transpiled/directives/dev/Stub.js.map +1 -1
  176. package/transpiled/directives/dev/Throw.d.ts +3 -3
  177. package/transpiled/directives/dev/Throw.js.map +1 -1
  178. package/transpiled/directives/dev/index.d.ts +2 -2
  179. package/transpiled/directives/dev/index.js +4 -5
  180. package/transpiled/directives/dev/index.js.map +1 -1
  181. package/transpiled/directives/dev/types.d.ts +1 -1
  182. package/transpiled/directives/index.d.ts +3 -1
  183. package/transpiled/directives/index.js +9 -9
  184. package/transpiled/directives/index.js.map +1 -1
  185. package/transpiled/directives/octets/Context.d.ts +1 -1
  186. package/transpiled/directives/octets/Delete.d.ts +1 -1
  187. package/transpiled/directives/octets/Delete.js.map +1 -1
  188. package/transpiled/directives/octets/Fetch.d.ts +1 -1
  189. package/transpiled/directives/octets/List.d.ts +1 -1
  190. package/transpiled/directives/octets/{Family.d.ts → Octets.d.ts} +3 -4
  191. package/transpiled/directives/octets/{Family.js → Octets.js} +4 -2
  192. package/transpiled/directives/octets/Octets.js.map +1 -0
  193. package/transpiled/directives/octets/Permute.d.ts +1 -1
  194. package/transpiled/directives/octets/Store.d.ts +1 -1
  195. package/transpiled/directives/octets/Store.js.map +1 -1
  196. package/transpiled/directives/octets/index.d.ts +2 -2
  197. package/transpiled/directives/octets/index.js +4 -5
  198. package/transpiled/directives/octets/index.js.map +1 -1
  199. package/transpiled/directives/octets/types.d.ts +3 -3
  200. package/transpiled/directives/vary/Directive.d.ts +5 -0
  201. package/transpiled/directives/vary/Directive.js +3 -0
  202. package/transpiled/directives/vary/Directive.js.map +1 -0
  203. package/transpiled/directives/vary/Embed.d.ts +10 -0
  204. package/transpiled/directives/vary/Embed.js +49 -0
  205. package/transpiled/directives/vary/Embed.js.map +1 -0
  206. package/transpiled/directives/vary/Properties.d.ts +9 -0
  207. package/transpiled/directives/vary/Properties.js +16 -0
  208. package/transpiled/directives/vary/Properties.js.map +1 -0
  209. package/transpiled/directives/vary/Vary.d.ts +10 -0
  210. package/transpiled/directives/vary/Vary.js +36 -0
  211. package/transpiled/directives/vary/Vary.js.map +1 -0
  212. package/transpiled/directives/vary/embeddings/Embedding.d.ts +5 -0
  213. package/transpiled/directives/vary/embeddings/Embedding.js +3 -0
  214. package/transpiled/directives/vary/embeddings/Embedding.js.map +1 -0
  215. package/transpiled/directives/vary/embeddings/Header.d.ts +7 -0
  216. package/transpiled/directives/vary/embeddings/Header.js +26 -0
  217. package/transpiled/directives/vary/embeddings/Header.js.map +1 -0
  218. package/transpiled/directives/vary/embeddings/Language.d.ts +7 -0
  219. package/transpiled/directives/vary/embeddings/Language.js +28 -0
  220. package/transpiled/directives/vary/embeddings/Language.js.map +1 -0
  221. package/transpiled/directives/vary/embeddings/index.d.ts +5 -0
  222. package/transpiled/directives/vary/embeddings/index.js +10 -0
  223. package/transpiled/directives/vary/embeddings/index.js.map +1 -0
  224. package/transpiled/directives/vary/index.d.ts +2 -0
  225. package/transpiled/directives/vary/index.js +6 -0
  226. package/transpiled/directives/vary/index.js.map +1 -0
  227. package/transpiled/io.d.ts +3 -0
  228. package/transpiled/io.js +3 -0
  229. package/transpiled/io.js.map +1 -0
  230. package/transpiled/manifest.js.map +1 -1
  231. package/transpiled/tsconfig.tsbuildinfo +1 -1
  232. package/transpiled/directives/auth/Family.js.map +0 -1
  233. package/transpiled/directives/dev/Family.js.map +0 -1
  234. package/transpiled/directives/octets/Family.js.map +0 -1
@@ -0,0 +1,125 @@
1
+ Feature: Identity Federation
2
+
3
+ Background:
4
+ Given the `identity.federation` database is empty
5
+ Given local IDP is running
6
+
7
+
8
+ Scenario: Getting identity for a new user
9
+ Given the `identity.federation` configuration:
10
+ """yaml
11
+ explicit_identity_creation: false
12
+ trust:
13
+ - issuer: http://localhost:44444
14
+ """
15
+ And the IDP token for User is issued
16
+ When the following request is received:
17
+ """
18
+ GET /identity/ HTTP/1.1
19
+ authorization: Bearer ${{ User.id_token }}
20
+ accept: application/yaml
21
+ content-type: application/yaml
22
+ """
23
+ Then the following reply is sent:
24
+ """
25
+ 200 OK
26
+ authorization: Token ${{ User.token }}
27
+
28
+ id: ${{ User.id }}
29
+ roles: []
30
+ scheme: bearer
31
+ """
32
+ # validate token
33
+ When the following request is received:
34
+ """
35
+ GET /identity/ HTTP/1.1
36
+ accept: application/yaml
37
+ authorization: Token ${{ User.token }}
38
+ """
39
+ Then the following reply is sent:
40
+ """
41
+ 200 OK
42
+ id: ${{ User.id }}
43
+ """
44
+ # ensuring identity idemptotency
45
+ When the following request is received:
46
+ """
47
+ GET /identity/ HTTP/1.1
48
+ authorization: Bearer ${{ User.id_token }}
49
+ accept: application/yaml
50
+ """
51
+ Then the following reply is sent:
52
+ """
53
+ 200 OK
54
+ id: ${{ User.id }}
55
+ """
56
+
57
+ Scenario: Creating an Identity using inception with existing credentials
58
+ Given the `identity.federation` configuration:
59
+ """yaml
60
+ trust:
61
+ - issuer: http://localhost:44444
62
+ """
63
+ Given the `users` is running with the following manifest:
64
+ """yaml
65
+ exposition:
66
+ /:
67
+ anonymous: true
68
+ POST:
69
+ incept: id
70
+ endpoint: transit
71
+ """
72
+ And the IDP token for Bill is issued
73
+ When the following request is received:
74
+ # identity inception
75
+ """
76
+ POST /users/ HTTP/1.1
77
+ authorization: Bearer ${{ Bill.id_token }}
78
+ accept: application/yaml
79
+ content-type: application/yaml
80
+
81
+ name: Bill Smith
82
+ """
83
+ Then the following reply is sent:
84
+ """
85
+ 201 Created
86
+ authorization: Token ${{ Bill.token }}
87
+
88
+ id: ${{ Bill.id }}
89
+ """
90
+ # check that both tokens corresponds to the same id
91
+ When the following request is received:
92
+ """
93
+ GET /identity/ HTTP/1.1
94
+ authorization: Token ${{ Bill.token }}
95
+ accept: application/yaml
96
+ """
97
+ Then the following reply is sent:
98
+ """
99
+ 200 OK
100
+ id: ${{ Bill.id }}
101
+ """
102
+ When the following request is received:
103
+ """
104
+ GET /identity/ HTTP/1.1
105
+ authorization: Bearer ${{ Bill.id_token }}
106
+ accept: application/yaml
107
+ """
108
+ Then the following reply is sent:
109
+ """
110
+ 200 OK
111
+ id: ${{ Bill.id }}
112
+ """
113
+ And the following request is received:
114
+ # same credentials
115
+ """
116
+ POST /users/ HTTP/1.1
117
+ authorization: Bearer ${{ Bill.id_token }}
118
+ content-type: text/plain
119
+
120
+ name: Mary Louis
121
+ """
122
+ Then the following reply is sent:
123
+ """
124
+ 403 Forbidden
125
+ """
Binary file
@@ -0,0 +1,5 @@
1
+ import * as http from '@toa.io/http'
2
+ import { binding } from 'cucumber-tsflow'
3
+
4
+ @binding()
5
+ export class Captures extends http.Captures {}
@@ -1,3 +1,4 @@
1
+ import * as assert from 'node:assert'
1
2
  import { after, binding, given } from 'cucumber-tsflow'
2
3
  import * as boot from '@toa.io/boot'
3
4
  import { timeout } from '@toa.io/generic'
@@ -30,9 +31,13 @@ export class Components {
30
31
  @given('the `{word}` is stopped')
31
32
  public async stop (_?: string): Promise<void> {
32
33
  await this.composition?.disconnect()
34
+
35
+ this.composition = null
33
36
  }
34
37
 
35
38
  private async runComponent (name: string, manifest?: object): Promise<void> {
39
+ assert.ok(this.composition === null, 'Composition is already running')
40
+
36
41
  const path = await this.workspace.addComponent(name, manifest)
37
42
 
38
43
  this.composition = await boot.composition([path])
@@ -1,22 +1,29 @@
1
+ import * as assert from 'node:assert'
2
+ import * as fs from 'node:fs'
3
+ import * as path from 'node:path'
1
4
  import { binding, then, when } from 'cucumber-tsflow'
2
5
  import * as http from '@toa.io/http'
3
- import { open } from '../../../storages/source/test/util'
6
+ import * as msgpack from 'msgpackr'
7
+ import * as YAML from 'js-yaml'
8
+ import { Captures } from './Captures'
4
9
  import { Parameters } from './Parameters'
5
10
  import { Gateway } from './Gateway'
11
+ import type { Readable } from 'node:stream'
6
12
 
7
- @binding([Gateway, Parameters])
13
+ @binding([Gateway, Parameters, Captures])
8
14
  export class HTTP extends http.Agent {
9
15
  private readonly gateway: Gateway
16
+ private fetched: Response | null = null
10
17
 
11
- public constructor (gateway: Gateway, parameters: Parameters) {
12
- super(parameters.origin)
18
+ public constructor (gateway: Gateway, parameters: Parameters, captures: Captures) {
19
+ super(parameters.origin, captures)
13
20
  this.gateway = gateway
14
21
  }
15
22
 
16
23
  @when('the following request is received:')
17
24
  public override async request (input: string): Promise<any> {
18
25
  await this.gateway.start()
19
- await super.request(input)
26
+ this.fetched = await super.request(input)
20
27
  }
21
28
 
22
29
  @then('the following reply is sent:')
@@ -24,6 +31,17 @@ export class HTTP extends http.Agent {
24
31
  super.responseIncludes(expected)
25
32
  }
26
33
 
34
+ @then('response body contains {word}-encoded value:')
35
+ public async bodyIs (format: string, yaml: string): Promise<void> {
36
+ assert.ok(this.fetched !== null, 'Response is null')
37
+
38
+ const buf = await this.fetched.arrayBuffer()
39
+ const value = encoders[format]?.(buf as Buffer)
40
+ const expected = YAML.load(yaml)
41
+
42
+ assert.deepEqual(value, expected, 'Values are not equal')
43
+ }
44
+
27
45
  @then('the reply does not contain:')
28
46
  public override responseExcludes (expected: string): void {
29
47
  super.responseExcludes(expected)
@@ -44,3 +62,13 @@ export class HTTP extends http.Agent {
44
62
  await super.streamMatch(head, stream)
45
63
  }
46
64
  }
65
+
66
+ const FILEDIR = path.resolve(__dirname, '../../../storages/source/test')
67
+
68
+ function open (filename: string): Readable {
69
+ return fs.createReadStream(path.join(FILEDIR, filename))
70
+ }
71
+
72
+ const encoders: Record<string, (buf: Buffer | Uint8Array) => any> = {
73
+ MessagePack: msgpack.decode
74
+ }
@@ -0,0 +1,120 @@
1
+ import { once } from 'node:events'
2
+ import * as crypto from 'node:crypto'
3
+ import * as http from 'node:http'
4
+ import * as assert from 'node:assert'
5
+ import * as util from 'node:util'
6
+ import { binding, given, afterAll } from 'cucumber-tsflow'
7
+ import { Captures } from './Captures'
8
+
9
+ import type { AddressInfo } from 'node:net'
10
+
11
+ @binding([Captures])
12
+ export class IdP {
13
+ private static server?: http.Server
14
+ private static privateKey?: crypto.KeyObject
15
+ private static issuer?: string
16
+
17
+ public constructor (private readonly captures: Captures) {}
18
+
19
+ @afterAll()
20
+ public static async stop (): Promise<void> {
21
+ if (this.server instanceof http.Server) {
22
+ this.server.close()
23
+ await once(this.server, 'close')
24
+ }
25
+ }
26
+
27
+ @given(/local IDP is running/i)
28
+ public async start (): Promise<void> {
29
+ if (IdP.server instanceof http.Server) return
30
+
31
+ // creating the key
32
+ const { publicKey, privateKey } = await util.promisify(crypto.generateKeyPair)('rsa', {
33
+ modulusLength: 2048
34
+ })
35
+
36
+ IdP.privateKey = privateKey
37
+
38
+ const jwk = JSON.stringify({
39
+ keys: [{ use: 'sig', alg: 'RS256', ...publicKey.export({ format: 'jwk' }) }]
40
+ })
41
+
42
+ const JWK_URL = '/.well-known/jwks'
43
+
44
+ const server = http.createServer((request, response) => {
45
+ switch (request.url) {
46
+ case JWK_URL:
47
+ response.writeHead(200, {
48
+ 'Content-Type': 'application/json',
49
+ 'Content-Length': jwk.length,
50
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
51
+ Pragma: 'no-cache',
52
+ Expires: '0'
53
+ })
54
+ response.end(jwk)
55
+ break
56
+
57
+ case '/.well-known/openid-configuration':
58
+ {
59
+ const openIdConfiguration = JSON.stringify({
60
+ issuer: IdP.issuer,
61
+ jwks_uri: IdP.issuer + JWK_URL,
62
+ response_types_supported: ['id_token'],
63
+ subject_types_supported: ['public'],
64
+ id_token_signing_alg_values_supported: ['RS256'],
65
+ scopes_supported: ['openid']
66
+ })
67
+
68
+ response.writeHead(200, {
69
+ 'Content-Type': 'application/json',
70
+ 'Cache-Control': 'public, max-age=3600',
71
+ 'Content-Length': openIdConfiguration.length
72
+ })
73
+ response.end(openIdConfiguration)
74
+ }
75
+
76
+ break
77
+
78
+ default:
79
+ response.writeHead(404, 'Not found')
80
+ response.end()
81
+ }
82
+ })
83
+
84
+ server.listen(44444, 'localhost')
85
+ await once(server, 'listening')
86
+
87
+ const address = server.address() as AddressInfo
88
+
89
+ console.log('IdP is listening on %s:%s', address.address, address.port)
90
+ IdP.server = server
91
+ IdP.issuer = `http://localhost:${address.port}`
92
+ }
93
+
94
+ @given('the IDP token for {word} is issued')
95
+ public async issueToken (user: string): Promise<void> {
96
+ assert.ok(IdP.privateKey, 'IdP private key is not available')
97
+
98
+ const jwt = [
99
+ {
100
+ typ: 'JWT',
101
+ alg: 'RS256'
102
+ },
103
+ {
104
+ iss: IdP.issuer,
105
+ sub: `${user}-mock-id`,
106
+ aud: 'test',
107
+ iat: Math.floor(Date.now() / 1000),
108
+ exp: Math.floor((Date.now() + 1000 * 60 * 5) / 1000)
109
+ }
110
+ ]
111
+ .map((v) => Buffer.from(JSON.stringify(v)).toString('base64url'))
112
+ .join('.')
113
+
114
+ const signature = crypto.createSign('RSA-SHA256').end(jwt).sign(IdP.privateKey, 'base64url')
115
+
116
+ const idToken = `${jwt}.${signature}`
117
+
118
+ this.captures.set(`${user}.id_token`, idToken)
119
+ }
120
+ }
@@ -1,4 +1,5 @@
1
1
  import { setDefaultTimeout } from '@cucumber/cucumber'
2
+ import { encode } from '@toa.io/generic'
2
3
 
3
4
  export class Parameters {
4
5
  public readonly origin: string
@@ -9,7 +10,12 @@ export class Parameters {
9
10
  }
10
11
 
11
12
  setDefaultTimeout(30 * 1000)
13
+
12
14
  process.env.TOA_DEV = '1'
13
15
 
14
- // { octets: tmp:///exposition-octets }
15
- process.env.TOA_STORAGES = '3gABpm9jdGV0c7h0bXA6Ly8vZXhwb3NpdGlvbi1vY3RldHM='
16
+ process.env.TOA_STORAGES = encode({
17
+ octets: {
18
+ provider: 'tmp',
19
+ prefix: 'test'
20
+ }
21
+ })
@@ -1,17 +1,17 @@
1
1
  import { join } from 'node:path'
2
- import { tmpdir } from 'node:os'
3
- import { mkdtemp, copy } from 'fs-extra'
2
+ import { tmpdir, devNull } from 'node:os'
3
+ import { mkdtemp, cp } from 'node:fs/promises'
4
4
  import * as yaml from '@toa.io/yaml'
5
5
 
6
6
  export class Workspace {
7
- private root: string = devnull
7
+ private root: string = devNull
8
8
 
9
9
  public static exists
10
10
  (_0: unknown, _1: unknown, descriptor: PropertyDescriptor): PropertyDescriptor {
11
11
  const method = descriptor.value
12
12
 
13
13
  descriptor.value = async function (this: Workspace, ...args: any[]): Promise<any> {
14
- if (this.root === devnull) this.root =
14
+ if (this.root === devNull) this.root =
15
15
  await mkdtemp(join(tmpdir(), Math.random().toString(36).slice(2)))
16
16
 
17
17
  return method.apply(this, args)
@@ -25,7 +25,7 @@ export class Workspace {
25
25
  const source = join(__dirname, 'components', name)
26
26
  const target = join(this.root, name)
27
27
 
28
- await copy(source, target)
28
+ await cp(source, target, { force: true, recursive: true })
29
29
 
30
30
  if (patch !== undefined)
31
31
  await this.patchManifest(target, patch)
@@ -39,5 +39,3 @@ export class Workspace {
39
39
  await yaml.patch(path, patch)
40
40
  }
41
41
  }
42
-
43
- const devnull = '/dev/null'
@@ -0,0 +1,120 @@
1
+ Feature: The Vary directive family
2
+
3
+ Scenario Outline: Embedding a language code
4
+ Given the `echo` is running with the following manifest:
5
+ """yaml
6
+ exposition:
7
+ /:
8
+ vary:languages: [en, fr]
9
+ GET:
10
+ vary:embed:
11
+ name: language # embed resolved language code as a `name` property of the operation input
12
+ endpoint: compute
13
+ """
14
+ When the following request is received:
15
+ """
16
+ GET /echo/ HTTP/1.1
17
+ accept: application/yaml
18
+ accept-language: <accept>
19
+ """
20
+ Then the following reply is sent:
21
+ """
22
+ 200 OK
23
+ content-type: application/yaml
24
+ content-language: <result>
25
+ vary: accept-language, accept
26
+
27
+ Hello <result>
28
+ """
29
+ Examples:
30
+ | accept | result |
31
+ | en | en |
32
+ | en_US | en |
33
+ | fr | fr |
34
+ | sw | en |
35
+
36
+ Scenario: Embedding a value of an arbitrary header
37
+ Given the `echo` is running with the following manifest:
38
+ """yaml
39
+ exposition:
40
+ /:
41
+ GET:
42
+ vary:embed:
43
+ name: :foo
44
+ endpoint: compute
45
+ """
46
+ When the following request is received:
47
+ """
48
+ GET /echo/ HTTP/1.1
49
+ accept: application/yaml
50
+ foo: bar
51
+ """
52
+ Then the following reply is sent:
53
+ """
54
+ 200 OK
55
+ content-type: application/yaml
56
+ vary: foo, accept
57
+
58
+ Hello bar
59
+ """
60
+
61
+ Scenario Outline: Embedding a value from the list of options
62
+ Given the `echo` is running with the following manifest:
63
+ """yaml
64
+ exposition:
65
+ /:
66
+ vary:languages: [en, fr]
67
+ GET:
68
+ vary:embed:
69
+ name:
70
+ - :foo
71
+ - :bar
72
+ - language
73
+ endpoint: compute
74
+ """
75
+ When the following request is received:
76
+ """
77
+ GET /echo/ HTTP/1.1
78
+ accept: application/yaml
79
+ <header>: <value>
80
+ """
81
+ Then the following reply is sent:
82
+ """
83
+ 200 OK
84
+ content-type: application/yaml
85
+ vary: <header>, accept
86
+
87
+ Hello <value>
88
+ """
89
+ Examples:
90
+ | header | value |
91
+ | foo | bar |
92
+ | bar | baz |
93
+ | accept-language | en |
94
+
95
+ Scenario: Adding headers used by defined embeddings to CORS permissions
96
+ Given the `echo` is running with the following manifest:
97
+ """yaml
98
+ exposition:
99
+ /:
100
+ vary:languages: [en, fr]
101
+ GET:
102
+ vary:embed:
103
+ name:
104
+ - language
105
+ - :foo
106
+ - :FOO
107
+ - :bar
108
+ endpoint: compute
109
+ """
110
+ When the following request is received:
111
+ """
112
+ OPTIONS / HTTP/1.1
113
+ origin: http://example.com
114
+ access-control-request-headers: whatever
115
+ """
116
+ Then the following reply is sent:
117
+ """
118
+ 204 No Content
119
+ access-control-allow-headers: accept, content-type, accept-language, foo, bar
120
+ """
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toa.io/extensions.exposition",
3
- "version": "0.24.0-alpha.17",
3
+ "version": "0.24.0-alpha.19",
4
4
  "description": "Toa Exposition",
5
5
  "author": "temich <tema.gurtovoy@gmail.com>",
6
6
  "homepage": "https://github.com/toa-io/toa#readme",
@@ -17,17 +17,16 @@
17
17
  "access": "public"
18
18
  },
19
19
  "dependencies": {
20
- "@toa.io/core": "0.24.0-alpha.17",
21
- "@toa.io/generic": "0.24.0-alpha.17",
22
- "@toa.io/schemas": "0.24.0-alpha.17",
23
- "@toa.io/streams": "0.24.0-alpha.17",
20
+ "@toa.io/core": "0.24.0-alpha.19",
21
+ "@toa.io/generic": "0.24.0-alpha.19",
22
+ "@toa.io/schemas": "0.24.0-alpha.19",
23
+ "@toa.io/streams": "0.24.0-alpha.19",
24
24
  "bcryptjs": "2.4.3",
25
- "cors": "2.8.5",
26
25
  "error-value": "0.3.0",
27
26
  "express": "4.18.2",
28
27
  "js-yaml": "4.1.0",
29
28
  "matchacho": "0.3.5",
30
- "msgpackr": "1.9.5",
29
+ "msgpackr": "1.10.1",
31
30
  "negotiator": "0.6.3",
32
31
  "paseto": "3.1.4"
33
32
  },
@@ -37,21 +36,21 @@
37
36
  },
38
37
  "scripts": {
39
38
  "test": "jest",
40
- "transpile": "npx tsc && npm run transpile:basic && npm run transpile:tokens && npm run transpile:roles",
41
- "transpile:basic": "npx tsc -p ./components/identity.basic",
42
- "transpile:tokens": "npx tsc -p ./components/identity.tokens",
43
- "transpile:roles": "npx tsc -p ./components/identity.roles",
44
- "features": "npx cucumber-js"
39
+ "transpile": "tsc && npm run transpile:basic && npm run transpile:tokens && npm run transpile:roles && npm run transpile:federation",
40
+ "transpile:basic": "tsc -p ./components/identity.basic",
41
+ "pretranspile:federation": "js-yaml components/identity.federation/manifest.toa.yaml | jq -M '{ type: \"object\", properties: {configuration: .configuration.schema, entity: .entity.schema }, additionalProperties: false}' > schemas.json && json2ts -i schemas.json -o components/identity.federation/source/schemas.ts && rm schemas.json",
42
+ "transpile:federation": "tsc -p ./components/identity.federation",
43
+ "transpile:tokens": "tsc -p ./components/identity.tokens",
44
+ "transpile:roles": "tsc -p ./components/identity.roles",
45
+ "features": "cucumber-js"
45
46
  },
46
47
  "devDependencies": {
47
- "@toa.io/extensions.storages": "0.24.0-alpha.17",
48
- "@toa.io/http": "0.24.0-alpha.17",
48
+ "@toa.io/extensions.storages": "0.24.0-alpha.19",
49
+ "@toa.io/http": "0.24.0-alpha.19",
49
50
  "@types/bcryptjs": "2.4.3",
50
51
  "@types/cors": "2.8.13",
51
52
  "@types/express": "4.17.17",
52
- "@types/fs-extra": "11.0.4",
53
- "@types/negotiator": "0.6.1",
54
- "fs-extra": "11.1.1"
53
+ "@types/negotiator": "0.6.1"
55
54
  },
56
- "gitHead": "6eddd2ce5172a38c0ae9c41017f23a28398cf839"
55
+ "gitHead": "5e251e2f9ec49448c0a038d7aa07b01ba2b8d065"
57
56
  }
@@ -1,10 +1,11 @@
1
+ import assert from 'node:assert'
1
2
  import { generate } from 'randomstring'
2
3
  import { DirectivesFactory, type Family } from './Directive'
3
4
  import { type syntax } from './RTD'
4
5
  import { type IncomingMessage } from './HTTP'
5
6
  import { type Remotes } from './Remotes'
6
7
 
7
- const families: Array<jest.MockedObject<Family>> = [
8
+ const families: Array<jest.MockedObjectDeep<Family>> = [
8
9
  {
9
10
  name: 'foo',
10
11
  mandatory: true,
@@ -26,6 +27,9 @@ let factory: DirectivesFactory
26
27
  beforeEach(() => {
27
28
  jest.clearAllMocks()
28
29
 
30
+ assert.ok(families[0].preflight !== undefined)
31
+ assert.ok(families[1].preflight !== undefined)
32
+
29
33
  families[0].preflight.mockImplementation(() => null)
30
34
  families[1].preflight.mockImplementation(() => null)
31
35
  factory = new DirectivesFactory(families, {} as unknown as Remotes)
@@ -61,7 +65,7 @@ it('should throw error if directive family is not found', async () => {
61
65
  }
62
66
 
63
67
  expect(() => factory.create([declaration]))
64
- .toThrowError(`Directive family '${declaration.family}' not found.`)
68
+ .toThrowError(`Directive family '${declaration.family}' is not found.`)
65
69
  })
66
70
 
67
71
  it('should apply directive', async () => {
@@ -77,6 +81,8 @@ it('should apply directive', async () => {
77
81
 
78
82
  await directives.preflight(request, [])
79
83
 
84
+ assert.ok(families[0].preflight !== undefined)
85
+
80
86
  expect(families[0].preflight.mock.calls[0][0]).toStrictEqual([directive])
81
87
  expect(families[0].preflight.mock.calls[0][1]).toEqual(request)
82
88
  })