@toa.io/extensions.exposition 1.0.0-alpha.254 → 1.0.0-alpha.257
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 +15 -6
- 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/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/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.passkeys/operations/tsconfig.tsbuildinfo +1 -1
- package/components/identity.roles/receivers/identity.federation.principal.js +6 -0
- package/{components/context.toa.yaml → context.toa.yaml} +0 -1
- package/documentation/credentials.md +5 -2
- package/features/identity.basic.feature +54 -0
- package/features/identity.federation.feature +94 -1
- package/features/map.feature +2 -1
- package/package.json +3 -3
- package/transpiled/tsconfig.tsbuildinfo +1 -1
|
@@ -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')
|
|
@@ -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 {
|