@toa.io/extensions.exposition 1.0.0-alpha.272 → 1.0.0-alpha.274
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.
- package/CHANGELOG.md +211 -0
- package/components/exposition.atom/manifest.toa.yaml +3 -1
- package/components/exposition.octets/manifest.toa.yaml +28 -10
- package/components/exposition.octets/operations/put.js +20 -5
- package/components/identity.bans/manifest.toa.yaml +26 -7
- package/components/identity.bans/operations/tsconfig.tsbuildinfo +1 -1
- package/components/identity.basic/manifest.toa.yaml +106 -30
- package/components/identity.basic/operations/authenticate.js +7 -4
- package/components/identity.basic/operations/authenticate.js.map +1 -1
- package/components/identity.basic/operations/incept.js +3 -2
- package/components/identity.basic/operations/incept.js.map +1 -1
- package/components/identity.basic/operations/transit.js +17 -6
- package/components/identity.basic/operations/transit.js.map +1 -1
- package/components/identity.basic/operations/tsconfig.tsbuildinfo +1 -1
- package/components/identity.basic/operations/types.d.ts +2 -2
- package/components/identity.basic/source/authenticate.ts +8 -4
- package/components/identity.basic/source/incept.ts +3 -2
- package/components/identity.basic/source/transit.ts +20 -6
- package/components/identity.basic/source/types.ts +2 -2
- package/components/identity.credentials/manifest.toa.yaml +34 -13
- package/components/identity.credentials/operations/tsconfig.tsbuildinfo +1 -1
- package/components/identity.federation/manifest.toa.yaml +120 -28
- package/components/identity.federation/operations/authenticate.js +3 -2
- package/components/identity.federation/operations/authenticate.js.map +1 -1
- package/components/identity.federation/operations/create.js +4 -2
- package/components/identity.federation/operations/create.js.map +1 -1
- package/components/identity.federation/operations/lib/errors.d.ts +70 -11
- package/components/identity.federation/operations/lib/errors.js +40 -11
- package/components/identity.federation/operations/lib/errors.js.map +1 -1
- package/components/identity.federation/operations/lib/exchange.js +2 -2
- package/components/identity.federation/operations/lib/exchange.js.map +1 -1
- package/components/identity.federation/operations/tsconfig.tsbuildinfo +1 -1
- package/components/identity.federation/operations/types/configuration.d.ts +3 -2
- package/components/identity.federation/source/authenticate.ts +3 -2
- package/components/identity.federation/source/create.ts +4 -2
- package/components/identity.federation/source/lib/errors.ts +49 -12
- package/components/identity.federation/source/lib/exchange.ts +2 -2
- package/components/identity.federation/source/types/configuration.ts +4 -2
- package/components/identity.keys/manifest.toa.yaml +14 -6
- package/components/identity.keys/operations/tsconfig.tsbuildinfo +1 -1
- package/components/identity.otp/manifest.toa.yaml +44 -15
- package/components/identity.otp/operations/authenticate.js +9 -4
- package/components/identity.otp/operations/authenticate.js.map +1 -1
- package/components/identity.otp/operations/tsconfig.tsbuildinfo +1 -1
- package/components/identity.otp/source/authenticate.ts +11 -4
- package/components/identity.passkeys/manifest.toa.yaml +38 -8
- package/components/identity.passkeys/operations/authenticate.js +3 -2
- package/components/identity.passkeys/operations/authenticate.js.map +1 -1
- package/components/identity.passkeys/operations/create.js +6 -3
- package/components/identity.passkeys/operations/create.js.map +1 -1
- package/components/identity.passkeys/operations/tsconfig.tsbuildinfo +1 -1
- package/components/identity.passkeys/operations/use.js +6 -3
- package/components/identity.passkeys/operations/use.js.map +1 -1
- package/components/identity.passkeys/source/authenticate.ts +3 -2
- package/components/identity.passkeys/source/create.ts +7 -3
- package/components/identity.passkeys/source/use.ts +7 -3
- package/components/identity.roles/manifest.toa.yaml +41 -10
- package/components/identity.roles/operations/grant.js +3 -2
- package/components/identity.roles/operations/grant.js.map +1 -1
- package/components/identity.roles/operations/tsconfig.tsbuildinfo +1 -1
- package/components/identity.roles/source/grant.ts +3 -2
- package/components/identity.tokens/manifest.toa.yaml +99 -39
- package/components/identity.tokens/operations/authenticate.js +6 -3
- package/components/identity.tokens/operations/authenticate.js.map +1 -1
- package/components/identity.tokens/operations/decrypt.js +10 -5
- package/components/identity.tokens/operations/decrypt.js.map +1 -1
- package/components/identity.tokens/operations/encrypt.js +4 -3
- package/components/identity.tokens/operations/encrypt.js.map +1 -1
- package/components/identity.tokens/operations/lib/types.d.ts +3 -2
- package/components/identity.tokens/operations/tsconfig.tsbuildinfo +1 -1
- package/components/identity.tokens/source/authenticate.test.ts +7 -2
- package/components/identity.tokens/source/authenticate.ts +7 -3
- package/components/identity.tokens/source/decrypt.test.ts +13 -7
- package/components/identity.tokens/source/decrypt.ts +12 -5
- package/components/identity.tokens/source/encrypt.test.ts +9 -4
- package/components/identity.tokens/source/encrypt.ts +4 -3
- package/components/identity.tokens/source/lib/types.ts +3 -2
- package/context.toa.yaml +2 -2
- package/documentation/components.md +1 -1
- package/documentation/identity.md +4 -1
- package/features/configuration.feature +207 -0
- package/features/cors.feature +1 -1
- package/features/dev.feature +1 -1
- package/features/identity.federation.feature +18 -9
- package/features/{identtiy.tokens.custom.feature → identity.tokens.custom.feature} +39 -0
- package/features/introspection.map.feature +16 -0
- package/features/map.feature +1 -1
- package/features/octets.download.feature +1 -1
- package/features/octets.meta.feature +1 -1
- package/features/passkeys.feature +8 -8
- package/features/require.feature +2 -2
- package/features/response.feature +3 -3
- package/features/steps/Components.ts +41 -4
- package/features/steps/Gateway.ts +61 -23
- package/features/steps/HTTP.ts +3 -5
- package/features/steps/Parameters.ts +2 -3
- package/features/steps/Realtime.ts +4 -4
- package/features/steps/Workspace.ts +8 -3
- package/features/steps/components/audit/manifest.toa.yaml +12 -5
- package/features/steps/components/echo/manifest.toa.yaml +33 -8
- package/features/steps/components/notify/manifest.toa.yaml +12 -5
- package/features/steps/components/octets.tester/manifest.toa.yaml +43 -14
- package/features/steps/components/orders/manifest.toa.yaml +22 -5
- package/features/steps/components/pots/manifest.toa.yaml +71 -17
- package/features/steps/components/pricing/manifest.toa.yaml +8 -2
- package/features/steps/components/sequences/manifest.toa.yaml +3 -1
- package/features/steps/components/users/manifest.toa.yaml +18 -3
- package/features/steps/components/users.properties/manifest.toa.yaml +12 -2
- package/package.json +5 -6
- package/schemas/annotation.cos.yaml +37 -17
- package/schemas/directive.cos.yaml +7 -3
- package/schemas/io/throttle.cos.yaml +47 -8
- package/schemas/method.cos.yaml +21 -9
- package/schemas/node.cos.yaml +22 -7
- package/schemas/octets/delete.cos.yaml +15 -2
- package/schemas/octets/get.cos.yaml +5 -2
- package/schemas/octets/put.cos.yaml +6 -3
- package/schemas/octets/workflow.cos.yaml +0 -1
- package/schemas/query.cos.yaml +31 -14
- package/schemas/querystring.cos.yaml +19 -9
- package/schemas/range.cos.yaml +10 -2
- package/schemas/route.cos.yaml +6 -2
- package/source/Branch.test.ts +37 -0
- package/source/Branch.ts +37 -0
- package/source/Factory.ts +5 -5
- package/source/Gateway.ts +20 -13
- package/source/HTTP/formats/yaml.test.ts +10 -2
- package/source/HTTP/formats/yaml.ts +21 -1
- package/source/RTD/Tree.ts +4 -1
- package/source/Tenant.ts +3 -3
- package/source/deployment.ts +3 -4
- package/source/directives/map/Headers.ts +8 -1
- package/source/directives/octets/Put.ts +6 -3
- package/source/root.ts +2 -2
- package/transpiled/Branch.d.ts +23 -0
- package/transpiled/Branch.js +16 -0
- package/transpiled/Branch.js.map +1 -1
- package/transpiled/Factory.js +4 -4
- package/transpiled/Factory.js.map +1 -1
- package/transpiled/Gateway.js +17 -7
- package/transpiled/Gateway.js.map +1 -1
- package/transpiled/HTTP/formats/yaml.js +15 -1
- package/transpiled/HTTP/formats/yaml.js.map +1 -1
- package/transpiled/RTD/Tree.js +4 -1
- package/transpiled/RTD/Tree.js.map +1 -1
- package/transpiled/Tenant.d.ts +1 -1
- package/transpiled/Tenant.js +1 -1
- package/transpiled/Tenant.js.map +1 -1
- package/transpiled/deployment.js +3 -4
- package/transpiled/deployment.js.map +1 -1
- package/transpiled/directives/map/Headers.js +7 -1
- package/transpiled/directives/map/Headers.js.map +1 -1
- package/transpiled/directives/octets/Put.js.map +1 -1
- package/transpiled/root.js +1 -1
- package/transpiled/root.js.map +1 -1
- package/transpiled/tsconfig.tsbuildinfo +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { generate } from 'randomstring'
|
|
2
2
|
import { Computation as Authenticate } from './authenticate'
|
|
3
3
|
import type { Configuration, Context, DecryptOutput, Identity } from './lib'
|
|
4
|
+
import type { Secret } from '@toa.io/types'
|
|
4
5
|
|
|
5
6
|
let configuration: Configuration
|
|
6
7
|
let context: Context
|
|
@@ -13,8 +14,8 @@ const authority = generate()
|
|
|
13
14
|
beforeEach(() => {
|
|
14
15
|
configuration = {
|
|
15
16
|
keys: [
|
|
16
|
-
{ id: 'key0', key: 'sTxL6qVOadKkUJwh3FveU53XgTEo3Sdfg7k2FfiIKfs' },
|
|
17
|
-
{ id: 'legacy0', key: 'k3.local.m28p8SrbS467t-2IUjQuSOqmjvi24TbXhyjAW_dOrog', format: 'paseto' }
|
|
17
|
+
{ id: 'key0', key: secret('sTxL6qVOadKkUJwh3FveU53XgTEo3Sdfg7k2FfiIKfs') },
|
|
18
|
+
{ id: 'legacy0', key: secret('k3.local.m28p8SrbS467t-2IUjQuSOqmjvi24TbXhyjAW_dOrog'), format: 'paseto' }
|
|
18
19
|
],
|
|
19
20
|
lifetime: 2592000,
|
|
20
21
|
refresh: 600,
|
|
@@ -68,3 +69,7 @@ it.each([true, false])('should return stale: %s',
|
|
|
68
69
|
|
|
69
70
|
expect(result).toEqual({ identity, refresh })
|
|
70
71
|
})
|
|
72
|
+
|
|
73
|
+
function secret (value: string): Secret {
|
|
74
|
+
return { unwrap: () => value }
|
|
75
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Err } from 'error-value'
|
|
2
1
|
import type { Maybe, Operation } from '@toa.io/types'
|
|
3
2
|
import type { AuthenticateInput, AuthenticateOutput, Context } from './lib'
|
|
4
3
|
|
|
@@ -43,5 +42,10 @@ export class Computation implements Operation {
|
|
|
43
42
|
}
|
|
44
43
|
}
|
|
45
44
|
|
|
46
|
-
const ERR_AUTHORITY = new
|
|
47
|
-
|
|
45
|
+
const ERR_AUTHORITY = new (class AuthorityMismatchError extends Error {
|
|
46
|
+
public readonly code = 'AUTHORITY_MISMATCH'
|
|
47
|
+
})()
|
|
48
|
+
|
|
49
|
+
const ERR_TOKEN_REVOKED = new (class TokenRevokedError extends Error {
|
|
50
|
+
public readonly code = 'TOKEN_REVOKED'
|
|
51
|
+
})()
|
|
@@ -3,6 +3,7 @@ import { V3 } from 'paseto'
|
|
|
3
3
|
import { Effect as Encrypt } from './encrypt'
|
|
4
4
|
import { Computation as Decrypt } from './decrypt'
|
|
5
5
|
import { type Configuration, type Context, type Identity } from './lib'
|
|
6
|
+
import type { Secret } from '@toa.io/types'
|
|
6
7
|
|
|
7
8
|
let configuration: Configuration
|
|
8
9
|
let context: Context
|
|
@@ -15,10 +16,11 @@ const authority = generate()
|
|
|
15
16
|
beforeEach(() => {
|
|
16
17
|
configuration = {
|
|
17
18
|
keys: [
|
|
18
|
-
{ id: 'key0', key: 'sTxL6qVOadKkUJwh3FveU53XgTEo3Sdfg7k2FfiIKfs' },
|
|
19
|
-
|
|
20
|
-
{ id: '
|
|
21
|
-
{ id: '
|
|
19
|
+
{ id: 'key0', key: secret('sTxL6qVOadKkUJwh3FveU53XgTEo3Sdfg7k2FfiIKfs') },
|
|
20
|
+
// keys are secrets, and a secret is an object
|
|
21
|
+
{ id: 'key1', key: secret('5I0iSKw3yfBkQ4AXfA8eR-tWR0Q1dpn4x3bPrPzHkP0') },
|
|
22
|
+
{ id: 'legacy0', key: secret('k3.local.m28p8SrbS467t-2IUjQuSOqmjvi24TbXhyjAW_dOrog'), format: 'paseto' },
|
|
23
|
+
{ id: 'legacy1', key: secret('k3.local.-498jfWenrZH-Dqw3-zQJih_hKzDgBgUMfe37OCqSOA'), format: 'paseto' }
|
|
22
24
|
],
|
|
23
25
|
lifetime: 1000,
|
|
24
26
|
refresh: 500,
|
|
@@ -77,7 +79,7 @@ it('should decrypt with key1', async () => {
|
|
|
77
79
|
it('should decrypt legacy PASETO and require refresh', async () => {
|
|
78
80
|
const identity: Identity = { id: generate(), roles: [] }
|
|
79
81
|
|
|
80
|
-
const token = await V3.encrypt({ iss: authority, identity }, configuration.keys[2].key, {
|
|
82
|
+
const token = await V3.encrypt({ iss: authority, identity }, configuration.keys[2].key.unwrap(), {
|
|
81
83
|
footer: { kid: 'legacy0' }
|
|
82
84
|
})
|
|
83
85
|
|
|
@@ -96,7 +98,7 @@ it('should separate JWE and PASETO keys with the same id by format', async () =>
|
|
|
96
98
|
|
|
97
99
|
const identity: Identity = { id: generate(), roles: [] }
|
|
98
100
|
|
|
99
|
-
const token = await V3.encrypt({ iss: authority, identity }, configuration.keys[2].key, {
|
|
101
|
+
const token = await V3.encrypt({ iss: authority, identity }, configuration.keys[2].key.unwrap(), {
|
|
100
102
|
footer: { kid: 'key0' }
|
|
101
103
|
})
|
|
102
104
|
|
|
@@ -125,7 +127,7 @@ it('should reject JWE with an unknown key', async () => {
|
|
|
125
127
|
const token = await encrypt.execute({
|
|
126
128
|
authority,
|
|
127
129
|
identity: { id: generate(), roles: [] },
|
|
128
|
-
key: { id: 'missing', key: configuration.keys[0].key, label: 'missing' }
|
|
130
|
+
key: { id: 'missing', key: configuration.keys[0].key.unwrap(), label: 'missing' }
|
|
129
131
|
})
|
|
130
132
|
|
|
131
133
|
if (token instanceof Error)
|
|
@@ -133,3 +135,7 @@ it('should reject JWE with an unknown key', async () => {
|
|
|
133
135
|
|
|
134
136
|
await expect(decrypt.execute(token)).resolves.toMatchObject({ code: 'INVALID_KEY' })
|
|
135
137
|
})
|
|
138
|
+
|
|
139
|
+
function secret (value: string): Secret {
|
|
140
|
+
return { unwrap: () => value }
|
|
141
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { V3 } from 'paseto'
|
|
2
|
-
import { Err } from 'error-value'
|
|
3
2
|
import { LRUCache } from 'lru-cache'
|
|
4
3
|
import { jweKey } from './lib'
|
|
5
4
|
import { load } from './lib/jose'
|
|
@@ -28,7 +27,7 @@ export class Computation implements Operation {
|
|
|
28
27
|
for (const { id, key, format } of context.configuration.keys) {
|
|
29
28
|
const branch = format === 'paseto' ? this.legacy : this.keys
|
|
30
29
|
|
|
31
|
-
branch[id] = { key }
|
|
30
|
+
branch[id] = { key: key.unwrap() }
|
|
32
31
|
}
|
|
33
32
|
}
|
|
34
33
|
|
|
@@ -161,6 +160,14 @@ interface KeyEntry {
|
|
|
161
160
|
value: Key | null
|
|
162
161
|
}
|
|
163
162
|
|
|
164
|
-
const ERR_INVALID_TOKEN = new
|
|
165
|
-
|
|
166
|
-
|
|
163
|
+
const ERR_INVALID_TOKEN = new (class InvalidTokenError extends Error {
|
|
164
|
+
public readonly code = 'INVALID_TOKEN'
|
|
165
|
+
})()
|
|
166
|
+
|
|
167
|
+
const ERR_INVALID_KEY = new (class InvalidKeyError extends Error {
|
|
168
|
+
public readonly code = 'INVALID_KEY'
|
|
169
|
+
})()
|
|
170
|
+
|
|
171
|
+
const ERR_FORGED_KEY = new (class ForgedKeyError extends Error {
|
|
172
|
+
public readonly code = 'FORGED_KEY'
|
|
173
|
+
})()
|
|
@@ -4,6 +4,7 @@ import { timeout } from '@toa.io/generic'
|
|
|
4
4
|
import { Effect as Encrypt } from './encrypt'
|
|
5
5
|
import { Computation as Decrypt } from './decrypt'
|
|
6
6
|
import { type Context, type Identity } from './lib'
|
|
7
|
+
import type { Secret } from '@toa.io/types'
|
|
7
8
|
|
|
8
9
|
let encrypt: Encrypt
|
|
9
10
|
let decrypt: Decrypt
|
|
@@ -18,8 +19,8 @@ const authority = generate()
|
|
|
18
19
|
beforeEach(() => {
|
|
19
20
|
context.configuration = {
|
|
20
21
|
keys: [
|
|
21
|
-
{ id: 'key0', key: 'sTxL6qVOadKkUJwh3FveU53XgTEo3Sdfg7k2FfiIKfs' },
|
|
22
|
-
{ id: 'legacy0', key: 'k3.local.m28p8SrbS467t-2IUjQuSOqmjvi24TbXhyjAW_dOrog', format: 'paseto' }
|
|
22
|
+
{ id: 'key0', key: secret('sTxL6qVOadKkUJwh3FveU53XgTEo3Sdfg7k2FfiIKfs') },
|
|
23
|
+
{ id: 'legacy0', key: secret('k3.local.m28p8SrbS467t-2IUjQuSOqmjvi24TbXhyjAW_dOrog'), format: 'paseto' }
|
|
23
24
|
],
|
|
24
25
|
lifetime: 1,
|
|
25
26
|
refresh: 2,
|
|
@@ -39,12 +40,12 @@ beforeEach(() => {
|
|
|
39
40
|
it('should use the first encryption key as active and expose its id as kid', async () => {
|
|
40
41
|
context.configuration.keys.unshift({
|
|
41
42
|
id: 'legacy-first',
|
|
42
|
-
key: 'k3.local.m28p8SrbS467t-2IUjQuSOqmjvi24TbXhyjAW_dOrog',
|
|
43
|
+
key: secret('k3.local.m28p8SrbS467t-2IUjQuSOqmjvi24TbXhyjAW_dOrog'),
|
|
43
44
|
format: 'paseto'
|
|
44
45
|
})
|
|
45
46
|
context.configuration.keys.push({
|
|
46
47
|
id: 'key1',
|
|
47
|
-
key: '5I0iSKw3yfBkQ4AXfA8eR-tWR0Q1dpn4x3bPrPzHkP0'
|
|
48
|
+
key: secret('5I0iSKw3yfBkQ4AXfA8eR-tWR0Q1dpn4x3bPrPzHkP0')
|
|
48
49
|
})
|
|
49
50
|
|
|
50
51
|
encrypt.mount(context)
|
|
@@ -119,3 +120,7 @@ it('should encrypt without lifetime INSECURE', async () => {
|
|
|
119
120
|
|
|
120
121
|
expect(decrypted.identity).toMatchObject(identity)
|
|
121
122
|
})
|
|
123
|
+
|
|
124
|
+
function secret (value: string): Secret {
|
|
125
|
+
return { unwrap: () => value }
|
|
126
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Err } from 'error-value'
|
|
2
1
|
import { jweKey } from './lib'
|
|
3
2
|
import { load } from './lib/jose'
|
|
4
3
|
import type { Operation, Maybe } from '@toa.io/types'
|
|
@@ -14,7 +13,7 @@ export class Effect implements Operation {
|
|
|
14
13
|
if (key === undefined)
|
|
15
14
|
throw new TypeError('At least one JWE key must be configured')
|
|
16
15
|
|
|
17
|
-
this.key = key
|
|
16
|
+
this.key = { id: key.id, key: key.key.unwrap() }
|
|
18
17
|
this.lifetime = context.configuration.lifetime * 1000
|
|
19
18
|
}
|
|
20
19
|
|
|
@@ -52,4 +51,6 @@ function within (scope: string, roles: string[]): boolean {
|
|
|
52
51
|
return roles.some((role) => role === scope || scope.startsWith(role + ':'))
|
|
53
52
|
}
|
|
54
53
|
|
|
55
|
-
const ERR_INACCESSIBLE_SCOPE = new
|
|
54
|
+
const ERR_INACCESSIBLE_SCOPE = new (class InaccessibleScopeError extends Error {
|
|
55
|
+
public readonly code = 'INACCESSIBLE_SCOPE'
|
|
56
|
+
})()
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Call, Maybe, Observation, telemetry } from '@toa.io/types'
|
|
1
|
+
import type { Call, Maybe, Observation, Secret, telemetry } from '@toa.io/types'
|
|
2
2
|
|
|
3
3
|
export interface Context {
|
|
4
4
|
local: {
|
|
@@ -31,7 +31,8 @@ export interface Configuration {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
export interface ConfiguredKey extends Pick<Key, 'id'
|
|
34
|
+
export interface ConfiguredKey extends Pick<Key, 'id'> {
|
|
35
|
+
key: Secret
|
|
35
36
|
format?: 'jwe' | 'paseto'
|
|
36
37
|
}
|
|
37
38
|
|
package/context.toa.yaml
CHANGED
|
@@ -10,9 +10,9 @@ configuration:
|
|
|
10
10
|
identity.tokens:
|
|
11
11
|
keys:
|
|
12
12
|
- id: key0
|
|
13
|
-
key:
|
|
13
|
+
key: $IDENTITY_TOKENS_ENCRYPTION_KEY0
|
|
14
14
|
- id: legacy0
|
|
15
|
-
key:
|
|
15
|
+
key: $IDENTITY_TOKENS_KEY0
|
|
16
16
|
format: paseto
|
|
17
17
|
|
|
18
18
|
identity.federation:
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
@security
|
|
2
|
+
Feature: Configuration values
|
|
3
|
+
|
|
4
|
+
The values component declares how it is exposed in its own manifest, and that manifest
|
|
5
|
+
is what this mounts. Reading takes `system:configuration:get`, creating takes
|
|
6
|
+
`system:configuration:create`, and the one who creates is recorded as the originator.
|
|
7
|
+
|
|
8
|
+
Background:
|
|
9
|
+
# developer:secret, user:12345
|
|
10
|
+
Given the `identity.basic` database contains:
|
|
11
|
+
| _id | authority | username | password |
|
|
12
|
+
| efe3a65ebbee47ed95a73edd911ea328 | nex | developer | $2b$10$ZRSKkgZoGnrcTNA5w5eCcu3pxDzdTduhteVYXcp56AaNcilNkwJ.O |
|
|
13
|
+
| e8e4f9c2a68d419b861403d71fabc915 | nex | user | $2b$10$Frszmrmsz9iwSXzBbRRMKeDVKsNxozkrLNSsN.SnVC.KPxLtQr/bK |
|
|
14
|
+
And the `identity.roles` database contains:
|
|
15
|
+
| _id | identity | role |
|
|
16
|
+
| 1c0b2a3d4e5f4a6b8c7d9e0f1a2b3c4d | efe3a65ebbee47ed95a73edd911ea328 | system:configuration |
|
|
17
|
+
| 2c0b2a3d4e5f4a6b8c7d9e0f1a2b3c4d | e8e4f9c2a68d419b861403d71fabc915 | system:configuration:get |
|
|
18
|
+
And the `identity.bans` database is empty
|
|
19
|
+
And the `configuration.values` database is empty
|
|
20
|
+
And the configuration values are deployed:
|
|
21
|
+
"""yaml
|
|
22
|
+
dummies.dummy:
|
|
23
|
+
epoch: e1
|
|
24
|
+
schema:
|
|
25
|
+
type: object
|
|
26
|
+
properties:
|
|
27
|
+
foo:
|
|
28
|
+
type: string
|
|
29
|
+
bar:
|
|
30
|
+
type: string
|
|
31
|
+
default: world
|
|
32
|
+
defaults:
|
|
33
|
+
foo: deployed
|
|
34
|
+
"""
|
|
35
|
+
And the configuration values are running
|
|
36
|
+
|
|
37
|
+
Scenario: Reading the deployed configuration
|
|
38
|
+
When the following request is received:
|
|
39
|
+
"""
|
|
40
|
+
GET /configuration/values/dummies.dummy/ HTTP/1.1
|
|
41
|
+
host: nex.toa.io
|
|
42
|
+
authorization: Basic dXNlcjoxMjM0NQ==
|
|
43
|
+
accept: application/yaml
|
|
44
|
+
"""
|
|
45
|
+
Then the following reply is sent:
|
|
46
|
+
"""
|
|
47
|
+
200 OK
|
|
48
|
+
content-type: application/yaml
|
|
49
|
+
|
|
50
|
+
configuration:
|
|
51
|
+
foo: deployed
|
|
52
|
+
schema:
|
|
53
|
+
type: object
|
|
54
|
+
properties:
|
|
55
|
+
foo:
|
|
56
|
+
type: string
|
|
57
|
+
bar:
|
|
58
|
+
type: string
|
|
59
|
+
default: world
|
|
60
|
+
epoch: e1
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
Scenario: Listing the configurations
|
|
64
|
+
When the following request is received:
|
|
65
|
+
"""
|
|
66
|
+
GET /configuration/values/ HTTP/1.1
|
|
67
|
+
host: nex.toa.io
|
|
68
|
+
authorization: Basic dXNlcjoxMjM0NQ==
|
|
69
|
+
accept: application/yaml
|
|
70
|
+
"""
|
|
71
|
+
Then the following reply is sent:
|
|
72
|
+
"""
|
|
73
|
+
200 OK
|
|
74
|
+
content-type: application/yaml
|
|
75
|
+
|
|
76
|
+
- component: dummies.dummy
|
|
77
|
+
epoch: e1
|
|
78
|
+
schema:
|
|
79
|
+
type: object
|
|
80
|
+
properties:
|
|
81
|
+
foo:
|
|
82
|
+
type: string
|
|
83
|
+
bar:
|
|
84
|
+
type: string
|
|
85
|
+
default: world
|
|
86
|
+
configuration:
|
|
87
|
+
foo: deployed
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
Scenario: Listing without the role
|
|
91
|
+
When the following request is received:
|
|
92
|
+
"""
|
|
93
|
+
GET /configuration/values/ HTTP/1.1
|
|
94
|
+
host: nex.toa.io
|
|
95
|
+
"""
|
|
96
|
+
Then the following reply is sent:
|
|
97
|
+
"""
|
|
98
|
+
401 Unauthorized
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
Scenario: Reading without the role
|
|
102
|
+
When the following request is received:
|
|
103
|
+
"""
|
|
104
|
+
GET /configuration/values/dummies.dummy/ HTTP/1.1
|
|
105
|
+
host: nex.toa.io
|
|
106
|
+
"""
|
|
107
|
+
Then the following reply is sent:
|
|
108
|
+
"""
|
|
109
|
+
401 Unauthorized
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
Scenario: Reading the configuration of an unknown component
|
|
113
|
+
When the following request is received:
|
|
114
|
+
"""
|
|
115
|
+
GET /configuration/values/dummies.unknown/ HTTP/1.1
|
|
116
|
+
host: nex.toa.io
|
|
117
|
+
authorization: Basic ZGV2ZWxvcGVyOnNlY3JldA==
|
|
118
|
+
"""
|
|
119
|
+
Then the following reply is sent:
|
|
120
|
+
"""
|
|
121
|
+
404 Not Found
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
Scenario: Creating configuration
|
|
125
|
+
When the following request is received:
|
|
126
|
+
"""
|
|
127
|
+
POST /configuration/values/dummies.dummy/ HTTP/1.1
|
|
128
|
+
host: nex.toa.io
|
|
129
|
+
authorization: Basic ZGV2ZWxvcGVyOnNlY3JldA==
|
|
130
|
+
accept: application/yaml
|
|
131
|
+
content-type: application/yaml
|
|
132
|
+
|
|
133
|
+
configuration:
|
|
134
|
+
foo: created
|
|
135
|
+
"""
|
|
136
|
+
Then the following reply is sent:
|
|
137
|
+
"""
|
|
138
|
+
201 Created
|
|
139
|
+
content-type: application/yaml
|
|
140
|
+
|
|
141
|
+
epoch: e1
|
|
142
|
+
"""
|
|
143
|
+
When the following request is received:
|
|
144
|
+
"""
|
|
145
|
+
GET /configuration/values/dummies.dummy/ HTTP/1.1
|
|
146
|
+
host: nex.toa.io
|
|
147
|
+
authorization: Basic dXNlcjoxMjM0NQ==
|
|
148
|
+
accept: application/yaml
|
|
149
|
+
"""
|
|
150
|
+
Then the following reply is sent:
|
|
151
|
+
"""
|
|
152
|
+
200 OK
|
|
153
|
+
content-type: application/yaml
|
|
154
|
+
|
|
155
|
+
configuration:
|
|
156
|
+
foo: created
|
|
157
|
+
bar: world
|
|
158
|
+
epoch: e1
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
Scenario: Creating configuration without the role
|
|
162
|
+
When the following request is received:
|
|
163
|
+
"""
|
|
164
|
+
POST /configuration/values/dummies.dummy/ HTTP/1.1
|
|
165
|
+
host: nex.toa.io
|
|
166
|
+
authorization: Basic dXNlcjoxMjM0NQ==
|
|
167
|
+
content-type: application/yaml
|
|
168
|
+
|
|
169
|
+
configuration:
|
|
170
|
+
foo: created
|
|
171
|
+
"""
|
|
172
|
+
Then the following reply is sent:
|
|
173
|
+
"""
|
|
174
|
+
403 Forbidden
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
Scenario: Creating configuration not fitting the schema
|
|
178
|
+
When the following request is received:
|
|
179
|
+
"""
|
|
180
|
+
POST /configuration/values/dummies.dummy/ HTTP/1.1
|
|
181
|
+
host: nex.toa.io
|
|
182
|
+
authorization: Basic ZGV2ZWxvcGVyOnNlY3JldA==
|
|
183
|
+
content-type: application/yaml
|
|
184
|
+
|
|
185
|
+
configuration:
|
|
186
|
+
foo:
|
|
187
|
+
nested: true
|
|
188
|
+
"""
|
|
189
|
+
Then the following reply is sent:
|
|
190
|
+
"""
|
|
191
|
+
422 Unprocessable Entity
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
Scenario: Creating configuration for an unknown component
|
|
195
|
+
When the following request is received:
|
|
196
|
+
"""
|
|
197
|
+
POST /configuration/values/dummies.unknown/ HTTP/1.1
|
|
198
|
+
host: nex.toa.io
|
|
199
|
+
authorization: Basic ZGV2ZWxvcGVyOnNlY3JldA==
|
|
200
|
+
content-type: application/yaml
|
|
201
|
+
|
|
202
|
+
configuration: {}
|
|
203
|
+
"""
|
|
204
|
+
Then the following reply is sent:
|
|
205
|
+
"""
|
|
206
|
+
422 Unprocessable Entity
|
|
207
|
+
"""
|
package/features/cors.feature
CHANGED
|
@@ -20,7 +20,7 @@ Feature: CORS Support
|
|
|
20
20
|
"""
|
|
21
21
|
204 No Content
|
|
22
22
|
access-control-allow-credentials: true
|
|
23
|
-
access-control-allow-headers: accept, authorization, content-type, if-match, if-none-match
|
|
23
|
+
access-control-allow-headers: accept, authorization, content-type, if-match, if-none-match
|
|
24
24
|
access-control-allow-methods: GET, POST, PUT, PATCH, DELETE, LOCK, UNLOCK
|
|
25
25
|
access-control-allow-origin: https://hello.world
|
|
26
26
|
access-control-max-age: 3600
|
package/features/dev.feature
CHANGED
|
@@ -74,7 +74,7 @@ Feature: Dev
|
|
|
74
74
|
Then the following reply is sent:
|
|
75
75
|
"""
|
|
76
76
|
204 No Content
|
|
77
|
-
access-control-allow-headers: accept, authorization, content-type, if-match, if-none-match
|
|
77
|
+
access-control-allow-headers: accept, authorization, content-type, faulty, if-match, if-none-match
|
|
78
78
|
"""
|
|
79
79
|
When the following request is received:
|
|
80
80
|
"""
|
|
@@ -195,13 +195,14 @@ Feature: Identity Federation
|
|
|
195
195
|
|
|
196
196
|
Then after 0.2 seconds
|
|
197
197
|
|
|
198
|
-
#
|
|
198
|
+
# the role is checked over the id token: a Token carries the roles held when it
|
|
199
|
+
# was issued, and this one was issued before the grant
|
|
199
200
|
When the following request is received:
|
|
200
201
|
"""
|
|
201
202
|
GET /identity/ HTTP/1.1
|
|
202
203
|
host: nex.toa.io
|
|
203
204
|
accept: application/yaml
|
|
204
|
-
authorization:
|
|
205
|
+
authorization: Bearer ${{ root.id_token }}
|
|
205
206
|
"""
|
|
206
207
|
Then the following reply is sent:
|
|
207
208
|
"""
|
|
@@ -334,7 +335,11 @@ Feature: Identity Federation
|
|
|
334
335
|
trust:
|
|
335
336
|
- iss: http://localhost:44444
|
|
336
337
|
aud: nex
|
|
337
|
-
secret:
|
|
338
|
+
secret: $IDP_SECRET
|
|
339
|
+
"""
|
|
340
|
+
And the configuration secrets:
|
|
341
|
+
"""yaml
|
|
342
|
+
IDP_SECRET: secret
|
|
338
343
|
"""
|
|
339
344
|
And auth code for Alice is issued for https://web.toa.io/callback/
|
|
340
345
|
When the following request is received:
|
|
@@ -361,12 +366,16 @@ Feature: Identity Federation
|
|
|
361
366
|
signature:
|
|
362
367
|
iss: io.toa.nex.id
|
|
363
368
|
kid: key-id
|
|
364
|
-
key:
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
369
|
+
key: $IDP_KEY
|
|
370
|
+
"""
|
|
371
|
+
And the configuration secrets:
|
|
372
|
+
"""yaml
|
|
373
|
+
IDP_KEY: |
|
|
374
|
+
-----BEGIN PRIVATE KEY-----
|
|
375
|
+
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg9x9DatwH0GZHSCo9
|
|
376
|
+
TMHTVXyeY0YQ8qb73jHV2v4MsyehRANCAAQwaYlna2hSV3G/RIlNLV41lg8Pm4Kf
|
|
377
|
+
HfCuKKiw3BIJTnSArAMJLSy1vYwSIMHz720mkmWTrWuQkkjvkDpZxfRv
|
|
378
|
+
-----END PRIVATE KEY-----
|
|
370
379
|
"""
|
|
371
380
|
And auth code for Bob is issued for https://web.toa.io/callback/
|
|
372
381
|
When the following request is received:
|
|
@@ -76,6 +76,45 @@ Feature: Custom tokens
|
|
|
76
76
|
200 OK
|
|
77
77
|
"""
|
|
78
78
|
|
|
79
|
+
Scenario: Issuing a token for another identity
|
|
80
|
+
Given the `identity.basic` database contains:
|
|
81
|
+
| _id | authority | username | password |
|
|
82
|
+
| efe3a65ebbee47ed95a73edd911ea328 | nex | developer | $2b$10$ZRSKkgZoGnrcTNA5w5eCcu3pxDzdTduhteVYXcp56AaNcilNkwJ.O |
|
|
83
|
+
| e8e4f9c2a68d419b861403d71fabc915 | nex | user | $2b$10$Frszmrmsz9iwSXzBbRRMKeDVKsNxozkrLNSsN.SnVC.KPxLtQr/bK |
|
|
84
|
+
And the `identity.roles` database contains:
|
|
85
|
+
| _id | identity | role |
|
|
86
|
+
| 9c4702490ff84f2a9e1b1da2ab64bdd4 | efe3a65ebbee47ed95a73edd911ea328 | system:identity:tokens |
|
|
87
|
+
When the following request is received:
|
|
88
|
+
"""
|
|
89
|
+
POST /identity/tokens/e8e4f9c2a68d419b861403d71fabc915/ HTTP/1.1
|
|
90
|
+
host: nex.toa.io
|
|
91
|
+
authorization: Basic ZGV2ZWxvcGVyOnNlY3JldA==
|
|
92
|
+
content-type: application/yaml
|
|
93
|
+
accept: application/yaml
|
|
94
|
+
|
|
95
|
+
label: On behalf
|
|
96
|
+
lifetime: 600
|
|
97
|
+
"""
|
|
98
|
+
Then the following reply is sent:
|
|
99
|
+
"""
|
|
100
|
+
201 Created
|
|
101
|
+
|
|
102
|
+
token: ${{ token }}
|
|
103
|
+
"""
|
|
104
|
+
When the following request is received:
|
|
105
|
+
"""
|
|
106
|
+
GET /identity/ HTTP/1.1
|
|
107
|
+
host: nex.toa.io
|
|
108
|
+
authorization: Token ${{ token }}
|
|
109
|
+
accept: application/yaml
|
|
110
|
+
"""
|
|
111
|
+
Then the following reply is sent:
|
|
112
|
+
"""
|
|
113
|
+
200 OK
|
|
114
|
+
|
|
115
|
+
id: e8e4f9c2a68d419b861403d71fabc915
|
|
116
|
+
"""
|
|
117
|
+
|
|
79
118
|
Scenario: Token with restricted scopes
|
|
80
119
|
When the following request is received:
|
|
81
120
|
"""
|
|
@@ -90,3 +90,19 @@ Feature: Reading the introspection map
|
|
|
90
90
|
"""
|
|
91
91
|
403 Forbidden
|
|
92
92
|
"""
|
|
93
|
+
|
|
94
|
+
Scenario: The map does not take the application's root directives
|
|
95
|
+
Given the annotation of the introspection map under:
|
|
96
|
+
"""yaml
|
|
97
|
+
/:
|
|
98
|
+
auth:anonymous: true
|
|
99
|
+
"""
|
|
100
|
+
When the following request is received:
|
|
101
|
+
"""
|
|
102
|
+
GET /introspection/nodes/ HTTP/1.1
|
|
103
|
+
host: nex.toa.io
|
|
104
|
+
"""
|
|
105
|
+
Then the following reply is sent:
|
|
106
|
+
"""
|
|
107
|
+
401 Unauthorized
|
|
108
|
+
"""
|
package/features/map.feature
CHANGED
|
@@ -84,7 +84,7 @@ Feature: HTTP context mapping
|
|
|
84
84
|
Then the following reply is sent:
|
|
85
85
|
"""
|
|
86
86
|
204 No Content
|
|
87
|
-
access-control-allow-headers: accept, accept-language, authorization, content-type, foo, if-match, if-none-match
|
|
87
|
+
access-control-allow-headers: accept, accept-language, authorization, content-type, foo, if-match, if-none-match
|
|
88
88
|
"""
|
|
89
89
|
|
|
90
90
|
Scenario: Mapping the value of an arbitrary header
|
|
@@ -186,5 +186,5 @@ Feature: Download external resources
|
|
|
186
186
|
Then the following reply is sent:
|
|
187
187
|
"""
|
|
188
188
|
204 No Content
|
|
189
|
-
access-control-allow-headers: accept, authorization, content-attributes, content-location, content-type, if-match, if-none-match
|
|
189
|
+
access-control-allow-headers: accept, authorization, content-attributes, content-location, content-type, if-match, if-none-match
|
|
190
190
|
"""
|
|
@@ -111,6 +111,6 @@ Feature: Octets metadata
|
|
|
111
111
|
Then the following reply is sent:
|
|
112
112
|
"""
|
|
113
113
|
204 No Content
|
|
114
|
-
access-control-allow-headers: accept, authorization, content-attributes, content-location, content-type, if-match, if-none-match
|
|
114
|
+
access-control-allow-headers: accept, authorization, content-attributes, content-location, content-type, if-match, if-none-match
|
|
115
115
|
access-control-allow-origin: https://example.com
|
|
116
116
|
"""
|
|
@@ -20,16 +20,16 @@ Feature: Web Authentication
|
|
|
20
20
|
201 Created
|
|
21
21
|
|
|
22
22
|
challenge: ${{ challenge }}
|
|
23
|
-
identity: ${{ identity.id }}
|
|
24
23
|
timeout: 60000
|
|
25
|
-
|
|
26
|
-
requireResidentKey: true
|
|
27
|
-
residentKey: required
|
|
24
|
+
identity: ${{ identity.id }}
|
|
28
25
|
pubKeyCredParams:
|
|
29
26
|
- type: public-key
|
|
30
27
|
alg: -7
|
|
31
28
|
- type: public-key
|
|
32
29
|
alg: -257
|
|
30
|
+
authenticatorSelection:
|
|
31
|
+
residentKey: required
|
|
32
|
+
requireResidentKey: true
|
|
33
33
|
"""
|
|
34
34
|
|
|
35
35
|
Scenario: Create a challenge to create a passkey to new identity
|
|
@@ -53,14 +53,14 @@ Feature: Web Authentication
|
|
|
53
53
|
authorization: Token ${{ token }}
|
|
54
54
|
|
|
55
55
|
challenge: ${{ challenge }}
|
|
56
|
-
identity: ${{ identity }}
|
|
57
56
|
timeout: 60000
|
|
58
|
-
|
|
59
|
-
requireResidentKey: true
|
|
60
|
-
residentKey: required
|
|
57
|
+
identity: ${{ identity }}
|
|
61
58
|
pubKeyCredParams:
|
|
62
59
|
- type: public-key
|
|
63
60
|
alg: -7
|
|
64
61
|
- type: public-key
|
|
65
62
|
alg: -257
|
|
63
|
+
authenticatorSelection:
|
|
64
|
+
residentKey: required
|
|
65
|
+
requireResidentKey: true
|
|
66
66
|
"""
|