@toa.io/extensions.exposition 1.0.0-alpha.256 → 1.0.0-alpha.258
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 +34 -0
- package/components/identity.basic/manifest.toa.yaml +7 -1
- package/components/identity.basic/operations/add.js +2 -1
- package/components/identity.basic/operations/add.js.map +1 -1
- package/components/identity.basic/operations/transit.js +2 -1
- 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 -0
- package/components/identity.basic/source/add.ts +2 -1
- package/components/identity.basic/source/transit.ts +2 -1
- package/components/identity.basic/source/types.ts +3 -0
- package/components/identity.federation/events/principal.js +2 -2
- package/components/identity.federation/manifest.toa.yaml +18 -7
- package/components/identity.federation/operations/authenticate.js +13 -15
- package/components/identity.federation/operations/authenticate.js.map +1 -1
- package/components/identity.federation/operations/create.d.ts +8 -0
- package/components/identity.federation/operations/create.js +27 -0
- package/components/identity.federation/operations/create.js.map +1 -0
- package/components/identity.federation/operations/delete.js +1 -1
- package/components/identity.federation/operations/delete.js.map +1 -1
- package/components/identity.federation/operations/incept.d.ts +1 -1
- package/components/identity.federation/operations/incept.js +3 -26
- package/components/identity.federation/operations/incept.js.map +1 -1
- package/components/identity.federation/operations/lib/exchange.js +1 -1
- package/components/identity.federation/operations/lib/exchange.js.map +1 -1
- package/components/identity.federation/operations/lib/index.d.ts +1 -0
- package/components/identity.federation/operations/lib/index.js +3 -1
- package/components/identity.federation/operations/lib/index.js.map +1 -1
- package/components/identity.federation/operations/lib/resolve.d.ts +3 -0
- package/components/identity.federation/operations/lib/resolve.js +17 -0
- package/components/identity.federation/operations/lib/resolve.js.map +1 -0
- package/components/identity.federation/operations/list.js +1 -5
- package/components/identity.federation/operations/list.js.map +1 -1
- package/components/identity.federation/operations/tsconfig.tsbuildinfo +1 -1
- package/components/identity.federation/operations/types/context.d.ts +2 -5
- package/components/identity.federation/operations/types/entity.d.ts +2 -1
- package/components/identity.federation/source/authenticate.ts +15 -18
- package/components/identity.federation/source/create.ts +39 -0
- package/components/identity.federation/source/delete.ts +1 -1
- package/components/identity.federation/source/incept.ts +5 -37
- package/components/identity.federation/source/lib/exchange.ts +1 -1
- package/components/identity.federation/source/lib/index.ts +1 -0
- package/components/identity.federation/source/lib/resolve.ts +17 -0
- package/components/identity.federation/source/list.test.ts +3 -5
- package/components/identity.federation/source/list.ts +1 -8
- package/components/identity.federation/source/types/context.ts +2 -6
- package/components/identity.federation/source/types/entity.ts +2 -1
- package/components/identity.roles/receivers/identity.federation.principal.js +6 -0
- package/documentation/credentials.md +5 -2
- package/documentation/identity.md +1 -1
- package/features/identity.basic.feature +54 -0
- package/features/identity.federation.feature +100 -2
- package/features/map.feature +2 -1
- package/package.json +3 -3
|
@@ -8,7 +8,7 @@ export interface Context {
|
|
|
8
8
|
observe: Observation<Entity>;
|
|
9
9
|
enumerate: Observation<Entity[], never, Entity>;
|
|
10
10
|
terminate: Transition<void, void, Entity>;
|
|
11
|
-
transit: Call<
|
|
11
|
+
transit: Call<Entity, TransitInput>;
|
|
12
12
|
ensure: Call<Entity>;
|
|
13
13
|
decode: Call<JWTPayload, string>;
|
|
14
14
|
};
|
|
@@ -27,10 +27,7 @@ export interface TransitInput {
|
|
|
27
27
|
readonly authority: string;
|
|
28
28
|
readonly iss: string;
|
|
29
29
|
readonly sub: string;
|
|
30
|
-
readonly identity
|
|
31
|
-
}
|
|
32
|
-
export interface TransitOutput {
|
|
33
|
-
id: string;
|
|
30
|
+
readonly identity: string;
|
|
34
31
|
}
|
|
35
32
|
interface IdentityTokensRevokeInput {
|
|
36
33
|
query: Query;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Err } from 'error-value'
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
2
|
+
import { newid } from '@toa.io/generic'
|
|
3
|
+
import { resolve } from './lib'
|
|
4
4
|
import type { JWTPayload } from 'jose'
|
|
5
5
|
import type { Maybe } from '@toa.io/types'
|
|
6
6
|
import type { Context, Scheme } from './types'
|
|
@@ -8,15 +8,7 @@ import type { Context, Scheme } from './types'
|
|
|
8
8
|
export async function effect ({ scheme, authority, credentials }: Input, context: Context): Promise<Maybe<Output>> {
|
|
9
9
|
context.logs.debug('Authenticating', { scheme, authority, credentials })
|
|
10
10
|
|
|
11
|
-
const
|
|
12
|
-
trust: context.configuration.trust,
|
|
13
|
-
logs: context.logs,
|
|
14
|
-
fetch: context.fetch
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const claims = scheme === 'bearer'
|
|
18
|
-
? await decode(credentials, ctx)
|
|
19
|
-
: await exchange(credentials, ctx)
|
|
11
|
+
const claims = await resolve(scheme, credentials, context)
|
|
20
12
|
|
|
21
13
|
if (claims instanceof Error)
|
|
22
14
|
return claims
|
|
@@ -25,17 +17,22 @@ export async function effect ({ scheme, authority, credentials }: Input, context
|
|
|
25
17
|
|
|
26
18
|
context.logs.debug('Token claims', claims)
|
|
27
19
|
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
20
|
+
const query = { criteria: `authority==${authority};iss==${iss};sub==${sub}` }
|
|
21
|
+
|
|
22
|
+
const credential = context.configuration.assert !== false
|
|
23
|
+
? await context.local.ensure({
|
|
24
|
+
query,
|
|
25
|
+
entity: { authority, iss, sub, identity: newid() }
|
|
26
|
+
})
|
|
27
|
+
: await context.local.observe({ query })
|
|
31
28
|
|
|
32
|
-
if (
|
|
29
|
+
if (credential === null)
|
|
33
30
|
return ERR_NOT_FOUND
|
|
34
31
|
|
|
35
|
-
if (
|
|
36
|
-
return
|
|
32
|
+
if (credential instanceof Error)
|
|
33
|
+
return credential
|
|
37
34
|
|
|
38
|
-
return { identity: { id:
|
|
35
|
+
return { identity: { id: credential.identity, claims } }
|
|
39
36
|
}
|
|
40
37
|
|
|
41
38
|
const ERR_NOT_FOUND = new Err('NOT_FOUND')
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Err } from 'error-value'
|
|
2
|
+
import { resolve } from './lib'
|
|
3
|
+
import type { Context, Entity, Scheme } from './types'
|
|
4
|
+
|
|
5
|
+
export async function effect (input: Input, context: Context): Promise<Entity | Error> {
|
|
6
|
+
const claims = await resolve(input.scheme, input.credentials, context)
|
|
7
|
+
|
|
8
|
+
if (claims instanceof Error)
|
|
9
|
+
return claims
|
|
10
|
+
|
|
11
|
+
const { iss, sub } = claims
|
|
12
|
+
|
|
13
|
+
const existent = await context.local.observe({
|
|
14
|
+
query: { criteria: `authority==${input.authority};iss==${iss};sub==${sub}`, deleted: true }
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const record = { authority: input.authority, iss, sub, identity: input.id }
|
|
18
|
+
|
|
19
|
+
if (existent === null)
|
|
20
|
+
return await context.local.transit({ input: record })
|
|
21
|
+
|
|
22
|
+
if (existent._deleted === undefined || existent._deleted === null)
|
|
23
|
+
return existent.identity === input.id ? existent : ERR_EXISTS
|
|
24
|
+
|
|
25
|
+
// a deleted record still occupies the unique index, so the transition revives it
|
|
26
|
+
return await context.local.transit({
|
|
27
|
+
input: record,
|
|
28
|
+
query: { id: existent.id, deleted: true }
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface Input {
|
|
33
|
+
authority: string
|
|
34
|
+
scheme: Scheme
|
|
35
|
+
credentials: string
|
|
36
|
+
id: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const ERR_EXISTS = new Err('EXISTS', 'Federation credentials are associated with another Identity')
|
|
@@ -3,7 +3,7 @@ import type { Context } from './types'
|
|
|
3
3
|
export async function effect ({ authority, identity, credential }: Input, context: Context): Promise<void | null> {
|
|
4
4
|
const object = await context.local.observe({ query: { id: credential } })
|
|
5
5
|
|
|
6
|
-
if (object === null || object.authority !== authority ||
|
|
6
|
+
if (object === null || object.authority !== authority || object.identity !== identity)
|
|
7
7
|
return null
|
|
8
8
|
|
|
9
9
|
return await context.local.terminate({ query: { id: credential } })
|
|
@@ -1,51 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import type { Request } from '@toa.io/types'
|
|
4
|
-
import type { Context, TransitInput, Scheme } from './types'
|
|
1
|
+
import { effect as create } from './create'
|
|
2
|
+
import type { Context, Scheme } from './types'
|
|
5
3
|
|
|
6
4
|
export async function effect (input: Input, context: Context): Promise<Output | Error> {
|
|
7
|
-
const
|
|
8
|
-
trust: context.configuration.trust,
|
|
9
|
-
logs: context.logs,
|
|
10
|
-
fetch: context.fetch
|
|
11
|
-
}
|
|
5
|
+
const credential = await create(input, context)
|
|
12
6
|
|
|
13
|
-
|
|
14
|
-
? await decode(input.credentials, ctx)
|
|
15
|
-
: await exchange(input.credentials, ctx)
|
|
16
|
-
|
|
17
|
-
if (claims instanceof Error)
|
|
18
|
-
return claims
|
|
19
|
-
|
|
20
|
-
const { iss, sub } = claims
|
|
21
|
-
|
|
22
|
-
if (input.id === undefined) {
|
|
23
|
-
const request: Request<TransitInput> = { input: { authority: input.authority, iss, sub } }
|
|
24
|
-
|
|
25
|
-
return await context.local.transit(request)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const existent = await context.local.observe({
|
|
29
|
-
query: { criteria: `authority==${input.authority};iss==${iss};sub==${sub}` }
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
if (existent !== null)
|
|
33
|
-
return (existent.identity ?? existent.id) === input.id ? { id: input.id } : ERR_EXISTS
|
|
34
|
-
|
|
35
|
-
return await context.local.transit({
|
|
36
|
-
input: { authority: input.authority, iss, sub, identity: input.id }
|
|
37
|
-
})
|
|
7
|
+
return credential instanceof Error ? credential : { id: credential.identity }
|
|
38
8
|
}
|
|
39
9
|
|
|
40
10
|
export interface Input {
|
|
41
11
|
authority: string
|
|
42
12
|
scheme: Scheme
|
|
43
13
|
credentials: string
|
|
44
|
-
id
|
|
14
|
+
id: string
|
|
45
15
|
}
|
|
46
16
|
|
|
47
17
|
export interface Output {
|
|
48
18
|
id: string
|
|
49
19
|
}
|
|
50
|
-
|
|
51
|
-
const ERR_EXISTS = new Err('EXISTS', 'Federation credentials are associated with another Identity')
|
|
@@ -95,7 +95,7 @@ async function sign (trust: Trust): Promise<string> {
|
|
|
95
95
|
const signature = trust.signature!
|
|
96
96
|
const aud = Array.isArray(trust.aud) ? trust.aud[0] : trust.aud!
|
|
97
97
|
const now = Math.floor(Date.now() / 1000)
|
|
98
|
-
const key = await jose.importPKCS8(
|
|
98
|
+
const key = await jose.importPKCS8(signature.key, 'ES256')
|
|
99
99
|
|
|
100
100
|
return await new jose.SignJWT({})
|
|
101
101
|
.setProtectedHeader({ alg: 'ES256', kid: signature.kid })
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { decode } from './decode'
|
|
2
|
+
import { exchange } from './exchange'
|
|
3
|
+
import type { Ctx } from './Ctx'
|
|
4
|
+
import type { Payload } from './Payload'
|
|
5
|
+
import type { Context, Scheme } from '../types'
|
|
6
|
+
|
|
7
|
+
export async function resolve (scheme: Scheme, credentials: string, context: Context): Promise<Payload | Error> {
|
|
8
|
+
const ctx: Ctx = {
|
|
9
|
+
trust: context.configuration.trust,
|
|
10
|
+
logs: context.logs,
|
|
11
|
+
fetch: context.fetch
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return scheme === 'bearer'
|
|
15
|
+
? await decode(credentials, ctx)
|
|
16
|
+
: await exchange(credentials, ctx)
|
|
17
|
+
}
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
import { computation } from './list'
|
|
2
2
|
|
|
3
|
-
it('lists indexed credentials
|
|
3
|
+
it('lists indexed credentials', async () => {
|
|
4
4
|
const current = { id: 'credential', authority: 'nex', identity: 'identity', iss: 'apple', sub: '1', _created: 2 }
|
|
5
|
-
const legacy = { id: 'identity', authority: 'nex', iss: 'google', sub: '2', _created: 1 }
|
|
6
5
|
|
|
7
6
|
const context = {
|
|
8
7
|
local: {
|
|
9
|
-
enumerate: jest.fn(async () => [current])
|
|
10
|
-
observe: jest.fn(async () => legacy)
|
|
8
|
+
enumerate: jest.fn(async () => [current])
|
|
11
9
|
}
|
|
12
10
|
}
|
|
13
11
|
|
|
14
12
|
await expect(computation({ authority: 'nex', identity: 'identity' }, context as never))
|
|
15
|
-
.resolves.toEqual([current
|
|
13
|
+
.resolves.toEqual([current])
|
|
16
14
|
expect(context.local.enumerate).toHaveBeenCalledWith({
|
|
17
15
|
query: {
|
|
18
16
|
criteria: 'authority==nex;identity==identity',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Context, Entity } from './types'
|
|
2
2
|
|
|
3
3
|
export async function computation ({ authority, identity }: Input, context: Context): Promise<Entity[]> {
|
|
4
|
-
|
|
4
|
+
return await context.local.enumerate({
|
|
5
5
|
query: {
|
|
6
6
|
criteria: `authority==${authority};identity==${identity}`,
|
|
7
7
|
projection: ['iss'],
|
|
@@ -9,13 +9,6 @@ export async function computation ({ authority, identity }: Input, context: Cont
|
|
|
9
9
|
limit: 100
|
|
10
10
|
}
|
|
11
11
|
})
|
|
12
|
-
|
|
13
|
-
const legacy = await context.local.observe({ query: { id: identity } })
|
|
14
|
-
|
|
15
|
-
if (legacy !== null && legacy.authority === authority && legacy.identity === undefined)
|
|
16
|
-
objects.push(legacy)
|
|
17
|
-
|
|
18
|
-
return objects.sort((a, b) => b._created - a._created)
|
|
19
12
|
}
|
|
20
13
|
|
|
21
14
|
interface Input {
|
|
@@ -9,7 +9,7 @@ export interface Context {
|
|
|
9
9
|
observe: Observation<Entity>
|
|
10
10
|
enumerate: Observation<Entity[], never, Entity>
|
|
11
11
|
terminate: Transition<void, void, Entity>
|
|
12
|
-
transit: Call<
|
|
12
|
+
transit: Call<Entity, TransitInput>
|
|
13
13
|
ensure: Call<Entity>
|
|
14
14
|
decode: Call<JWTPayload, string>
|
|
15
15
|
}
|
|
@@ -30,11 +30,7 @@ export interface TransitInput {
|
|
|
30
30
|
readonly authority: string
|
|
31
31
|
readonly iss: string
|
|
32
32
|
readonly sub: string
|
|
33
|
-
readonly identity
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface TransitOutput {
|
|
37
|
-
id: string
|
|
33
|
+
readonly identity: string
|
|
38
34
|
}
|
|
39
35
|
|
|
40
36
|
interface IdentityTokensRevokeInput {
|
|
@@ -57,12 +57,15 @@ constraints, and principal configuration.
|
|
|
57
57
|
|
|
58
58
|
## Federated credentials
|
|
59
59
|
|
|
60
|
-
- `POST /identity/federation/:identity/` associates credentials from a trusted issuer
|
|
60
|
+
- `POST /identity/federation/:identity/` associates credentials from a trusted issuer and returns
|
|
61
|
+
the associated credential as it is listed.
|
|
61
62
|
- `GET /identity/federation/:identity/` lists federated credentials.
|
|
62
63
|
- `DELETE /identity/federation/:identity/:credential/` removes one federated credential.
|
|
63
64
|
|
|
65
|
+
If the credentials are already associated with the Identity, the existing credential is returned.
|
|
66
|
+
|
|
64
67
|
An Identity can have credentials from multiple issuers and can have multiple subjects from the
|
|
65
|
-
same issuer. The `:credential` segment is the `id` returned by
|
|
68
|
+
same issuer. The `:credential` segment is the `id` returned by any of these resources. Federation
|
|
66
69
|
resources are provider-independent; issuer-specific authorization flows remain the responsibility
|
|
67
70
|
of the application.
|
|
68
71
|
|
|
@@ -474,3 +474,57 @@ Feature: Basic authentication
|
|
|
474
474
|
|
|
475
475
|
code: EXISTS
|
|
476
476
|
"""
|
|
477
|
+
|
|
478
|
+
Scenario: Adding basic credentials after they have been deleted
|
|
479
|
+
Given transient identity
|
|
480
|
+
When the following request is received:
|
|
481
|
+
"""
|
|
482
|
+
POST /identity/basic/${{ identity.id }}/ HTTP/1.1
|
|
483
|
+
host: nex.toa.io
|
|
484
|
+
authorization: Token ${{ identity.token }}
|
|
485
|
+
content-type: application/yaml
|
|
486
|
+
|
|
487
|
+
username: developer
|
|
488
|
+
password: secret#1234
|
|
489
|
+
"""
|
|
490
|
+
Then the following reply is sent:
|
|
491
|
+
"""
|
|
492
|
+
201 Created
|
|
493
|
+
"""
|
|
494
|
+
When the following request is received:
|
|
495
|
+
"""
|
|
496
|
+
DELETE /identity/basic/${{ identity.id }}/ HTTP/1.1
|
|
497
|
+
host: nex.toa.io
|
|
498
|
+
authorization: Token ${{ identity.token }}
|
|
499
|
+
"""
|
|
500
|
+
Then the following reply is sent:
|
|
501
|
+
"""
|
|
502
|
+
200 OK
|
|
503
|
+
"""
|
|
504
|
+
When the following request is received:
|
|
505
|
+
"""
|
|
506
|
+
POST /identity/basic/${{ identity.id }}/ HTTP/1.1
|
|
507
|
+
host: nex.toa.io
|
|
508
|
+
authorization: Token ${{ identity.token }}
|
|
509
|
+
content-type: application/yaml
|
|
510
|
+
|
|
511
|
+
username: painter
|
|
512
|
+
password: secret#4321
|
|
513
|
+
"""
|
|
514
|
+
Then the following reply is sent:
|
|
515
|
+
"""
|
|
516
|
+
201 Created
|
|
517
|
+
"""
|
|
518
|
+
When the following request is received:
|
|
519
|
+
"""
|
|
520
|
+
GET /identity/ HTTP/1.1
|
|
521
|
+
host: nex.toa.io
|
|
522
|
+
authorization: Basic cGFpbnRlcjpzZWNyZXQjNDMyMQ==
|
|
523
|
+
accept: application/yaml
|
|
524
|
+
"""
|
|
525
|
+
Then the following reply is sent:
|
|
526
|
+
"""
|
|
527
|
+
200 OK
|
|
528
|
+
|
|
529
|
+
id: ${{ identity.id }}
|
|
530
|
+
"""
|
|
@@ -54,6 +54,21 @@ Feature: Identity Federation
|
|
|
54
54
|
|
|
55
55
|
id: ${{ User.id }}
|
|
56
56
|
"""
|
|
57
|
+
# credential id is detached from the Identity
|
|
58
|
+
When the following request is received:
|
|
59
|
+
"""
|
|
60
|
+
GET /identity/federation/${{ User.id }}/ HTTP/1.1
|
|
61
|
+
host: nex.toa.io
|
|
62
|
+
authorization: Bearer ${{ User.id_token }}
|
|
63
|
+
accept: application/yaml
|
|
64
|
+
"""
|
|
65
|
+
Then the following reply is sent:
|
|
66
|
+
"""
|
|
67
|
+
200 OK
|
|
68
|
+
|
|
69
|
+
- id: ${{ User.credential }}
|
|
70
|
+
iss: http://localhost:44444
|
|
71
|
+
"""
|
|
57
72
|
|
|
58
73
|
Scenario: Creating an Identity using inception
|
|
59
74
|
Given the `identity.federation` configuration:
|
|
@@ -88,8 +103,10 @@ Feature: Identity Federation
|
|
|
88
103
|
"""
|
|
89
104
|
201 Created
|
|
90
105
|
authorization: Token ${{ Bill.token }}
|
|
106
|
+
|
|
107
|
+
id: ${{ Bill.id }}
|
|
91
108
|
"""
|
|
92
|
-
# check that both tokens authenticate the created
|
|
109
|
+
# check that both tokens authenticate the created user's Identity
|
|
93
110
|
When the following request is received:
|
|
94
111
|
"""
|
|
95
112
|
GET /identity/ HTTP/1.1
|
|
@@ -100,6 +117,8 @@ Feature: Identity Federation
|
|
|
100
117
|
Then the following reply is sent:
|
|
101
118
|
"""
|
|
102
119
|
200 OK
|
|
120
|
+
|
|
121
|
+
id: ${{ Bill.id }}
|
|
103
122
|
roles: []
|
|
104
123
|
"""
|
|
105
124
|
When the following request is received:
|
|
@@ -113,8 +132,24 @@ Feature: Identity Federation
|
|
|
113
132
|
"""
|
|
114
133
|
200 OK
|
|
115
134
|
|
|
135
|
+
id: ${{ Bill.id }}
|
|
116
136
|
roles: []
|
|
117
137
|
"""
|
|
138
|
+
# credential id is detached from the Identity
|
|
139
|
+
When the following request is received:
|
|
140
|
+
"""
|
|
141
|
+
GET /identity/federation/${{ Bill.id }}/ HTTP/1.1
|
|
142
|
+
host: nex.toa.io
|
|
143
|
+
authorization: Bearer ${{ Bill.id_token }}
|
|
144
|
+
accept: application/yaml
|
|
145
|
+
"""
|
|
146
|
+
Then the following reply is sent:
|
|
147
|
+
"""
|
|
148
|
+
200 OK
|
|
149
|
+
|
|
150
|
+
- id: ${{ Bill.credential }}
|
|
151
|
+
iss: http://localhost:44444
|
|
152
|
+
"""
|
|
118
153
|
And the following request is received:
|
|
119
154
|
# same credentials
|
|
120
155
|
"""
|
|
@@ -214,6 +249,64 @@ Feature: Identity Federation
|
|
|
214
249
|
content-type: application/yaml
|
|
215
250
|
accept: application/yaml
|
|
216
251
|
|
|
252
|
+
scheme: bearer
|
|
253
|
+
credentials: ${{ Bob.id_token }}
|
|
254
|
+
"""
|
|
255
|
+
Then the following reply is sent:
|
|
256
|
+
"""
|
|
257
|
+
201 Created
|
|
258
|
+
|
|
259
|
+
id: ${{ Bob.credential }}
|
|
260
|
+
iss: http://localhost:44444
|
|
261
|
+
"""
|
|
262
|
+
# the created credential is listed as is
|
|
263
|
+
When the following request is received:
|
|
264
|
+
"""
|
|
265
|
+
GET /identity/federation/${{ Bob.id }}/ HTTP/1.1
|
|
266
|
+
host: nex.toa.io
|
|
267
|
+
authorization: Basic #{{ basic Bob }}
|
|
268
|
+
accept: application/yaml
|
|
269
|
+
"""
|
|
270
|
+
Then the following reply is sent:
|
|
271
|
+
"""
|
|
272
|
+
200 OK
|
|
273
|
+
|
|
274
|
+
- id: ${{ Bob.credential }}
|
|
275
|
+
iss: http://localhost:44444
|
|
276
|
+
"""
|
|
277
|
+
And the following request is received:
|
|
278
|
+
"""
|
|
279
|
+
GET /identity/ HTTP/1.1
|
|
280
|
+
host: nex.toa.io
|
|
281
|
+
authorization: Bearer ${{ Bob.id_token }}
|
|
282
|
+
accept: application/yaml
|
|
283
|
+
"""
|
|
284
|
+
Then the following reply is sent:
|
|
285
|
+
"""
|
|
286
|
+
200 OK
|
|
287
|
+
|
|
288
|
+
id: ${{ Bob.id }}
|
|
289
|
+
"""
|
|
290
|
+
# delete the federation credential
|
|
291
|
+
When the following request is received:
|
|
292
|
+
"""
|
|
293
|
+
DELETE /identity/federation/${{ Bob.id }}/${{ Bob.credential }}/ HTTP/1.1
|
|
294
|
+
host: nex.toa.io
|
|
295
|
+
authorization: Basic #{{ basic Bob }}
|
|
296
|
+
"""
|
|
297
|
+
Then the following reply is sent:
|
|
298
|
+
"""
|
|
299
|
+
200 OK
|
|
300
|
+
"""
|
|
301
|
+
# add the same federation again
|
|
302
|
+
When the following request is received:
|
|
303
|
+
"""
|
|
304
|
+
POST /identity/federation/${{ Bob.id }}/ HTTP/1.1
|
|
305
|
+
host: nex.toa.io
|
|
306
|
+
authorization: Basic #{{ basic Bob }}
|
|
307
|
+
content-type: application/yaml
|
|
308
|
+
accept: application/yaml
|
|
309
|
+
|
|
217
310
|
scheme: bearer
|
|
218
311
|
credentials: ${{ Bob.id_token }}
|
|
219
312
|
"""
|
|
@@ -268,7 +361,12 @@ Feature: Identity Federation
|
|
|
268
361
|
signature:
|
|
269
362
|
iss: io.toa.nex.id
|
|
270
363
|
kid: key-id
|
|
271
|
-
key:
|
|
364
|
+
key: |
|
|
365
|
+
-----BEGIN PRIVATE KEY-----
|
|
366
|
+
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg9x9DatwH0GZHSCo9
|
|
367
|
+
TMHTVXyeY0YQ8qb73jHV2v4MsyehRANCAAQwaYlna2hSV3G/RIlNLV41lg8Pm4Kf
|
|
368
|
+
HfCuKKiw3BIJTnSArAMJLSy1vYwSIMHz720mkmWTrWuQkkjvkDpZxfRv
|
|
369
|
+
-----END PRIVATE KEY-----
|
|
272
370
|
"""
|
|
273
371
|
And auth code for Bob is issued for https://web.toa.io/callback/
|
|
274
372
|
When the following request is received:
|
package/features/map.feature
CHANGED
|
@@ -269,6 +269,7 @@ Feature: HTTP context mapping
|
|
|
269
269
|
"""yaml
|
|
270
270
|
trust:
|
|
271
271
|
- iss: http://localhost:44444
|
|
272
|
+
assert: false
|
|
272
273
|
"""
|
|
273
274
|
And the `pots` is running with the following manifest:
|
|
274
275
|
"""yaml
|
|
@@ -299,8 +300,8 @@ Feature: HTTP context mapping
|
|
|
299
300
|
201 Created
|
|
300
301
|
|
|
301
302
|
id: ${{ id }}
|
|
302
|
-
title: ${{ random.email }}
|
|
303
303
|
volume: 1.5
|
|
304
|
+
title: ${{ random.email }}
|
|
304
305
|
"""
|
|
305
306
|
|
|
306
307
|
Scenario: Mapping non-exposed properties
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/extensions.exposition",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.258",
|
|
4
4
|
"description": "Toa Exposition",
|
|
5
5
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/toa-io/toa#readme",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@simplewebauthn/server": "13.3.2",
|
|
21
|
-
"@toa.io/core": "1.0.0-alpha.
|
|
21
|
+
"@toa.io/core": "1.0.0-alpha.257",
|
|
22
22
|
"@toa.io/generic": "1.0.0-alpha.254",
|
|
23
23
|
"@toa.io/schemas": "1.0.0-alpha.254",
|
|
24
24
|
"bcryptjs": "3.0.3",
|
|
@@ -62,5 +62,5 @@
|
|
|
62
62
|
},
|
|
63
63
|
"testEnvironment": "node"
|
|
64
64
|
},
|
|
65
|
-
"gitHead": "
|
|
65
|
+
"gitHead": "35a7cb8922be6921239562ebb3ab833893b5bd6e"
|
|
66
66
|
}
|