@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.
Files changed (53) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/components/identity.basic/manifest.toa.yaml +7 -1
  3. package/components/identity.basic/operations/add.js +2 -1
  4. package/components/identity.basic/operations/add.js.map +1 -1
  5. package/components/identity.basic/operations/transit.js +2 -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/operations/types.d.ts +2 -0
  9. package/components/identity.basic/source/add.ts +2 -1
  10. package/components/identity.basic/source/transit.ts +2 -1
  11. package/components/identity.basic/source/types.ts +3 -0
  12. package/components/identity.federation/events/principal.js +2 -2
  13. package/components/identity.federation/manifest.toa.yaml +15 -6
  14. package/components/identity.federation/operations/authenticate.js +13 -15
  15. package/components/identity.federation/operations/authenticate.js.map +1 -1
  16. package/components/identity.federation/operations/create.d.ts +8 -0
  17. package/components/identity.federation/operations/create.js +27 -0
  18. package/components/identity.federation/operations/create.js.map +1 -0
  19. package/components/identity.federation/operations/delete.js +1 -1
  20. package/components/identity.federation/operations/delete.js.map +1 -1
  21. package/components/identity.federation/operations/incept.d.ts +1 -1
  22. package/components/identity.federation/operations/incept.js +3 -26
  23. package/components/identity.federation/operations/incept.js.map +1 -1
  24. package/components/identity.federation/operations/lib/index.d.ts +1 -0
  25. package/components/identity.federation/operations/lib/index.js +3 -1
  26. package/components/identity.federation/operations/lib/index.js.map +1 -1
  27. package/components/identity.federation/operations/lib/resolve.d.ts +3 -0
  28. package/components/identity.federation/operations/lib/resolve.js +17 -0
  29. package/components/identity.federation/operations/lib/resolve.js.map +1 -0
  30. package/components/identity.federation/operations/list.js +1 -5
  31. package/components/identity.federation/operations/list.js.map +1 -1
  32. package/components/identity.federation/operations/tsconfig.tsbuildinfo +1 -1
  33. package/components/identity.federation/operations/types/context.d.ts +2 -5
  34. package/components/identity.federation/operations/types/entity.d.ts +2 -1
  35. package/components/identity.federation/source/authenticate.ts +15 -18
  36. package/components/identity.federation/source/create.ts +39 -0
  37. package/components/identity.federation/source/delete.ts +1 -1
  38. package/components/identity.federation/source/incept.ts +5 -37
  39. package/components/identity.federation/source/lib/index.ts +1 -0
  40. package/components/identity.federation/source/lib/resolve.ts +17 -0
  41. package/components/identity.federation/source/list.test.ts +3 -5
  42. package/components/identity.federation/source/list.ts +1 -8
  43. package/components/identity.federation/source/types/context.ts +2 -6
  44. package/components/identity.federation/source/types/entity.ts +2 -1
  45. package/components/identity.passkeys/operations/tsconfig.tsbuildinfo +1 -1
  46. package/components/identity.roles/receivers/identity.federation.principal.js +6 -0
  47. package/{components/context.toa.yaml → context.toa.yaml} +0 -1
  48. package/documentation/credentials.md +5 -2
  49. package/features/identity.basic.feature +54 -0
  50. package/features/identity.federation.feature +94 -1
  51. package/features/map.feature +2 -1
  52. package/package.json +3 -3
  53. 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<TransitOutput, TransitInput>;
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?: string;
31
- }
32
- export interface TransitOutput {
33
- id: string;
30
+ readonly identity: string;
34
31
  }
35
32
  interface IdentityTokensRevokeInput {
36
33
  query: Query;
@@ -3,6 +3,7 @@ export interface Entity {
3
3
  authority: string;
4
4
  iss: string;
5
5
  sub: string;
6
- identity?: string;
6
+ identity: string;
7
7
  _created: number;
8
+ _deleted?: number | null;
8
9
  }
@@ -1,6 +1,6 @@
1
1
  import { Err } from 'error-value'
2
- import { decode, exchange } from './lib'
3
- import type { Ctx } from './lib'
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 ctx: Ctx = {
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 identity = context.configuration.assert !== false
29
- ? await context.local.ensure({ entity: { authority, iss, sub } })
30
- : await context.local.observe({ query: { criteria: `authority==${authority};iss==${iss};sub==${sub}` } })
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 (identity === null)
29
+ if (credential === null)
33
30
  return ERR_NOT_FOUND
34
31
 
35
- if (identity instanceof Error)
36
- return identity
32
+ if (credential instanceof Error)
33
+ return credential
37
34
 
38
- return { identity: { id: identity.identity ?? identity.id, claims } }
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 || (object.identity ?? object.id) !== identity)
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 { Err } from 'error-value'
2
- import { decode, exchange, type Ctx } from './lib'
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 ctx: Ctx = {
8
- trust: context.configuration.trust,
9
- logs: context.logs,
10
- fetch: context.fetch
11
- }
5
+ const credential = await create(input, context)
12
6
 
13
- const claims = input.scheme === 'bearer'
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?: string
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')
@@ -1,3 +1,4 @@
1
1
  export { decode } from './decode'
2
2
  export { exchange } from './exchange'
3
+ export { resolve } from './resolve'
3
4
  export { type Ctx } from './Ctx'
@@ -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 and a legacy associated credential', async () => {
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, legacy])
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
- const objects = await context.local.enumerate({
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<TransitOutput, TransitInput>
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?: string
34
- }
35
-
36
- export interface TransitOutput {
37
- id: string
33
+ readonly identity: string
38
34
  }
39
35
 
40
36
  interface IdentityTokensRevokeInput {
@@ -3,6 +3,7 @@ export interface Entity {
3
3
  authority: string
4
4
  iss: string
5
5
  sub: string
6
- identity?: string
6
+ identity: string
7
7
  _created: number
8
+ _deleted?: number | null
8
9
  }