@toa.io/extensions.exposition 1.0.0-alpha.253 → 1.0.0-alpha.256

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 (106) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/components/identity.bans/operations/tsconfig.tsbuildinfo +1 -1
  3. package/components/identity.basic/manifest.toa.yaml +1 -1
  4. package/components/identity.basic/operations/tsconfig.tsbuildinfo +1 -1
  5. package/components/identity.credentials/operations/tsconfig.tsbuildinfo +1 -1
  6. package/components/identity.federation/manifest.toa.yaml +4 -4
  7. package/components/identity.federation/operations/authenticate.js +2 -1
  8. package/components/identity.federation/operations/authenticate.js.map +1 -1
  9. package/components/identity.federation/operations/decode.js +2 -1
  10. package/components/identity.federation/operations/decode.js.map +1 -1
  11. package/components/identity.federation/operations/delete.d.ts +2 -2
  12. package/components/identity.federation/operations/delete.js +3 -3
  13. package/components/identity.federation/operations/delete.js.map +1 -1
  14. package/components/identity.federation/operations/incept.js +2 -1
  15. package/components/identity.federation/operations/incept.js.map +1 -1
  16. package/components/identity.federation/operations/lib/Ctx.d.ts +2 -0
  17. package/components/identity.federation/operations/lib/decode.js +4 -27
  18. package/components/identity.federation/operations/lib/decode.js.map +1 -1
  19. package/components/identity.federation/operations/lib/discovery.d.ts +4 -3
  20. package/components/identity.federation/operations/lib/discovery.js +21 -31
  21. package/components/identity.federation/operations/lib/discovery.js.map +1 -1
  22. package/components/identity.federation/operations/lib/exchange.js +7 -6
  23. package/components/identity.federation/operations/lib/exchange.js.map +1 -1
  24. package/components/identity.federation/operations/lib/jose.d.ts +41 -0
  25. package/components/identity.federation/operations/lib/jose.js +27 -0
  26. package/components/identity.federation/operations/lib/jose.js.map +1 -0
  27. package/components/identity.federation/operations/list.js +2 -1
  28. package/components/identity.federation/operations/list.js.map +1 -1
  29. package/components/identity.federation/operations/tsconfig.tsbuildinfo +1 -1
  30. package/components/identity.federation/operations/types/context.d.ts +2 -0
  31. package/components/identity.federation/source/authenticate.ts +2 -1
  32. package/components/identity.federation/source/decode.ts +2 -1
  33. package/components/identity.federation/source/delete.ts +4 -4
  34. package/components/identity.federation/source/incept.ts +2 -1
  35. package/components/identity.federation/source/lib/Ctx.ts +2 -0
  36. package/components/identity.federation/source/lib/decode.ts +4 -5
  37. package/components/identity.federation/source/lib/discovery.test.ts +45 -0
  38. package/components/identity.federation/source/lib/discovery.ts +27 -8
  39. package/components/identity.federation/source/lib/exchange.ts +7 -7
  40. package/components/identity.federation/source/lib/jose.d.ts +1 -0
  41. package/components/identity.federation/source/lib/jose.js +5 -0
  42. package/components/identity.federation/source/list.test.ts +2 -1
  43. package/components/identity.federation/source/list.ts +2 -1
  44. package/components/identity.federation/source/types/context.ts +3 -0
  45. package/components/identity.federation/tsconfig.json +1 -4
  46. package/components/identity.keys/manifest.toa.yaml +1 -0
  47. package/components/identity.keys/operations/create.js +2 -2
  48. package/components/identity.keys/operations/create.js.map +1 -1
  49. package/components/identity.keys/operations/tsconfig.tsbuildinfo +1 -1
  50. package/components/identity.keys/source/create.ts +2 -2
  51. package/components/identity.otp/operations/tsconfig.tsbuildinfo +1 -1
  52. package/components/identity.passkeys/operations/tsconfig.tsbuildinfo +1 -1
  53. package/components/identity.roles/operations/tsconfig.tsbuildinfo +1 -1
  54. package/components/identity.tokens/manifest.toa.yaml +21 -4
  55. package/components/identity.tokens/operations/decrypt.d.ts +4 -2
  56. package/components/identity.tokens/operations/decrypt.js +60 -12
  57. package/components/identity.tokens/operations/decrypt.js.map +1 -1
  58. package/components/identity.tokens/operations/encrypt.js +14 -13
  59. package/components/identity.tokens/operations/encrypt.js.map +1 -1
  60. package/components/identity.tokens/operations/lib/index.d.ts +1 -0
  61. package/components/identity.tokens/operations/lib/index.js +3 -0
  62. package/components/identity.tokens/operations/lib/index.js.map +1 -1
  63. package/components/identity.tokens/operations/lib/jose.d.ts +41 -0
  64. package/components/identity.tokens/operations/lib/jose.js +27 -0
  65. package/components/identity.tokens/operations/lib/jose.js.map +1 -0
  66. package/components/identity.tokens/operations/lib/key.d.ts +1 -0
  67. package/components/identity.tokens/operations/lib/key.js +11 -0
  68. package/components/identity.tokens/operations/lib/key.js.map +1 -0
  69. package/components/identity.tokens/operations/lib/types.d.ts +10 -1
  70. package/components/identity.tokens/operations/tsconfig.tsbuildinfo +1 -1
  71. package/components/identity.tokens/source/authenticate.test.ts +4 -3
  72. package/components/identity.tokens/source/decrypt.test.ts +69 -8
  73. package/components/identity.tokens/source/decrypt.ts +72 -14
  74. package/components/identity.tokens/source/encrypt.test.ts +30 -3
  75. package/components/identity.tokens/source/encrypt.ts +18 -16
  76. package/components/identity.tokens/source/lib/index.ts +1 -0
  77. package/components/identity.tokens/source/lib/jose.d.ts +1 -0
  78. package/components/identity.tokens/source/lib/jose.js +5 -0
  79. package/components/identity.tokens/source/lib/key.ts +8 -0
  80. package/components/identity.tokens/source/lib/types.ts +12 -1
  81. package/{components/context.toa.yaml → context.toa.yaml} +6 -2
  82. package/documentation/authorities.md +1 -1
  83. package/documentation/components.md +70 -30
  84. package/documentation/identity.md +12 -14
  85. package/features/access.feature +1 -1
  86. package/features/cache.feature +5 -5
  87. package/features/cors.feature +4 -4
  88. package/features/credentials.feature +3 -3
  89. package/features/identity.basic.feature +1 -1
  90. package/features/identity.federation.feature +4 -5
  91. package/features/identity.roles.feature +1 -1
  92. package/features/identity.tokens.feature +3 -3
  93. package/features/steps/Gateway.ts +11 -3
  94. package/package.json +18 -21
  95. package/source/HTTP/formats/yaml.test.ts +9 -0
  96. package/source/HTTP/formats/yaml.ts +2 -1
  97. package/source/HTTP/messages.ts +1 -1
  98. package/source/directives/io/lib/throttle/Interval.ts +10 -1
  99. package/transpiled/HTTP/formats/yaml.js +2 -1
  100. package/transpiled/HTTP/formats/yaml.js.map +1 -1
  101. package/transpiled/HTTP/messages.js +24 -4
  102. package/transpiled/HTTP/messages.js.map +1 -1
  103. package/transpiled/directives/io/lib/throttle/Interval.d.ts +1 -0
  104. package/transpiled/directives/io/lib/throttle/Interval.js +9 -1
  105. package/transpiled/directives/io/lib/throttle/Interval.js.map +1 -1
  106. package/transpiled/tsconfig.tsbuildinfo +1 -1
@@ -3,6 +3,7 @@ import type { Call, Observation, Query, telemetry, Transition } from '@toa.io/ty
3
3
  import type { Entity } from './entity';
4
4
  import type { Configuration } from './configuration';
5
5
  export interface Context {
6
+ fetch: Fetch;
6
7
  local: {
7
8
  observe: Observation<Entity>;
8
9
  enumerate: Observation<Entity[], never, Entity>;
@@ -21,6 +22,7 @@ export interface Context {
21
22
  logs: telemetry.Logs;
22
23
  configuration: Configuration;
23
24
  }
25
+ export type Fetch = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
24
26
  export interface TransitInput {
25
27
  readonly authority: string;
26
28
  readonly iss: string;
@@ -10,7 +10,8 @@ export async function effect ({ scheme, authority, credentials }: Input, context
10
10
 
11
11
  const ctx: Ctx = {
12
12
  trust: context.configuration.trust,
13
- logs: context.logs
13
+ logs: context.logs,
14
+ fetch: context.fetch
14
15
  }
15
16
 
16
17
  const claims = scheme === 'bearer'
@@ -5,6 +5,7 @@ import type { Context } from './types'
5
5
  export async function effect (token: string, context: Context): Promise<JWTPayload | Error> {
6
6
  return await decode(token, {
7
7
  trust: context.configuration.trust,
8
- logs: context.logs
8
+ logs: context.logs,
9
+ fetch: context.fetch
9
10
  })
10
11
  }
@@ -1,16 +1,16 @@
1
1
  import type { Context } from './types'
2
2
 
3
- export async function effect ({ authority, identity, id }: Input, context: Context): Promise<void | null> {
4
- const object = await context.local.observe({ query: { id } })
3
+ export async function effect ({ authority, identity, credential }: Input, context: Context): Promise<void | null> {
4
+ const object = await context.local.observe({ query: { id: credential } })
5
5
 
6
6
  if (object === null || object.authority !== authority || (object.identity ?? object.id) !== identity)
7
7
  return null
8
8
 
9
- return await context.local.terminate({ query: { id } })
9
+ return await context.local.terminate({ query: { id: credential } })
10
10
  }
11
11
 
12
12
  interface Input {
13
13
  authority: string
14
14
  identity: string
15
- id: string
15
+ credential: string
16
16
  }
@@ -6,7 +6,8 @@ import type { Context, TransitInput, Scheme } from './types'
6
6
  export async function effect (input: Input, context: Context): Promise<Output | Error> {
7
7
  const ctx: Ctx = {
8
8
  trust: context.configuration.trust,
9
- logs: context.logs
9
+ logs: context.logs,
10
+ fetch: context.fetch
10
11
  }
11
12
 
12
13
  const claims = input.scheme === 'bearer'
@@ -1,7 +1,9 @@
1
1
  import type { telemetry } from '@toa.io/types'
2
2
  import type { Trust } from '../types'
3
+ import type { Fetch } from '../types/context'
3
4
 
4
5
  export interface Ctx {
5
6
  trust: Trust[]
6
7
  logs: telemetry.Logs
8
+ fetch: Fetch
7
9
  }
@@ -1,12 +1,11 @@
1
- import * as jose from 'jose'
1
+ import { load } from './jose'
2
2
  import { createRemoteJWKSet } from './discovery'
3
3
  import { ERR_TRUST, ERR_ISS, ERR_SUB } from './errors'
4
4
  import type { Ctx } from './Ctx'
5
5
  import type { Payload } from './Payload'
6
6
 
7
- const jwks: Record<string, Awaited<ReturnType<typeof createRemoteJWKSet>>> = {}
8
-
9
7
  export async function decode (token: string, ctx: Ctx): Promise<Payload | Error> {
8
+ const jose = await load()
10
9
  const { iss, sub } = jose.decodeJwt(token)
11
10
 
12
11
  if (typeof iss !== 'string')
@@ -20,9 +19,9 @@ export async function decode (token: string, ctx: Ctx): Promise<Payload | Error>
20
19
  if (trusted === undefined)
21
20
  return ERR_TRUST
22
21
 
23
- jwks[iss] ??= await createRemoteJWKSet(iss)
22
+ const jwks = await createRemoteJWKSet(iss, ctx.fetch)
24
23
 
25
- const { payload } = await jose.jwtVerify(token, jwks[iss], { audience: trusted.aud })
24
+ const { payload } = await jose.jwtVerify(token, jwks, { audience: trusted.aud })
26
25
 
27
26
  return payload as Payload
28
27
  }
@@ -0,0 +1,45 @@
1
+ import { createRemoteJWKSet, discover } from './discovery'
2
+ import { load } from './jose'
3
+ import type { Fetch } from '../types/context'
4
+
5
+ it('discovers an issuer using the component fetch', async () => {
6
+ const iss = `https://${Math.random().toString(36).slice(2)}.example.com`
7
+ const configuration = { issuer: iss, jwks_uri: `${iss}/jwks` }
8
+
9
+ const fetch = jest.fn(async (_input: string | URL | Request) =>
10
+ Response.json(configuration)) as jest.MockedFunction<Fetch>
11
+
12
+ await expect(discover(iss, fetch)).resolves.toMatchObject(configuration)
13
+ expect(fetch).toHaveBeenCalledWith(`${iss}/.well-known/openid-configuration`)
14
+ })
15
+
16
+ it('loads and caches remote keys using the component fetch', async () => {
17
+ const jose = await load()
18
+ const iss = `https://${Math.random().toString(36).slice(2)}.example.com`
19
+ const jwksUri = `${iss}/jwks`
20
+ const { privateKey, publicKey } = await jose.generateKeyPair('ES256')
21
+ const jwk = await jose.exportJWK(publicKey)
22
+
23
+ const fetch = jest.fn(async (input: string | URL | Request) => {
24
+ const url = input instanceof Request ? input.url : input.toString()
25
+
26
+ if (url.endsWith('/.well-known/openid-configuration'))
27
+ return Response.json({ issuer: iss, jwks_uri: jwksUri })
28
+
29
+ return Response.json({ keys: [{ ...jwk, alg: 'ES256', kid: 'key0', use: 'sig' }] })
30
+ }) as jest.MockedFunction<Fetch>
31
+
32
+ const token = await new jose.SignJWT({ sub: 'subject' })
33
+ .setProtectedHeader({ alg: 'ES256', kid: 'key0' })
34
+ .setIssuer(iss)
35
+ .sign(privateKey)
36
+
37
+ const first = await createRemoteJWKSet(iss, fetch)
38
+ const second = await createRemoteJWKSet(iss, fetch)
39
+
40
+ expect(second).toBe(first)
41
+ await expect(jose.jwtVerify(token, first, { issuer: iss })).resolves.toMatchObject({
42
+ payload: { sub: 'subject' }
43
+ })
44
+ expect(fetch).toHaveBeenCalledTimes(2)
45
+ })
@@ -1,11 +1,14 @@
1
- import * as jose from 'jose'
1
+ import { load } from './jose'
2
+ import type * as jose from 'jose'
2
3
  import type { Configuration } from './Configuration'
4
+ import type { Fetch } from '../types/context'
3
5
 
4
6
  const cache = new Map<string, Configuration>()
7
+ const resolvers = new WeakMap<Fetch, Map<string, Promise<jose.RemoteJWKSet>>>()
5
8
 
6
- export async function discover (iss: string): Promise<Configuration> {
9
+ export async function discover (iss: string, fetch: Fetch): Promise<Configuration> {
7
10
  if (!cache.has(iss)) {
8
- const configuration = await fetchConfiguration(iss)
11
+ const configuration = await fetchConfiguration(iss, fetch)
9
12
 
10
13
  cache.set(iss, configuration)
11
14
  }
@@ -13,7 +16,7 @@ export async function discover (iss: string): Promise<Configuration> {
13
16
  return cache.get(iss)!
14
17
  }
15
18
 
16
- async function fetchConfiguration (iss: string): Promise<Configuration> {
19
+ async function fetchConfiguration (iss: string, fetch: Fetch): Promise<Configuration> {
17
20
  const response = await fetch(`${iss}/.well-known/openid-configuration`)
18
21
 
19
22
  if (!response.ok)
@@ -22,9 +25,25 @@ async function fetchConfiguration (iss: string): Promise<Configuration> {
22
25
  return await response.json() as Configuration
23
26
  }
24
27
 
25
- export async function createRemoteJWKSet (iss: string): Promise<ReturnType<typeof jose.createRemoteJWKSet>> {
26
- const configuration = await discover(iss)
27
- const url = new URL(configuration.jwks_uri)
28
+ export async function createRemoteJWKSet (iss: string, fetch: Fetch): Promise<jose.RemoteJWKSet> {
29
+ let entries = resolvers.get(fetch)
28
30
 
29
- return jose.createRemoteJWKSet(url)
31
+ if (entries === undefined) {
32
+ entries = new Map()
33
+ resolvers.set(fetch, entries)
34
+ }
35
+
36
+ if (!entries.has(iss))
37
+ entries.set(iss, create(iss, fetch))
38
+
39
+ return await entries.get(iss)!
40
+ }
41
+
42
+ async function create (iss: string, fetch: Fetch): Promise<jose.RemoteJWKSet> {
43
+ const jose = await load()
44
+ const configuration = await discover(iss, fetch)
45
+
46
+ return jose.createRemoteJWKSet(new URL(configuration.jwks_uri), {
47
+ [jose.customFetch]: fetch
48
+ })
30
49
  }
@@ -1,13 +1,12 @@
1
- import * as jose from 'jose'
1
+ import { load } from './jose'
2
2
  import { createRemoteJWKSet, discover } from './discovery'
3
3
  import * as errors from './errors'
4
4
  import type { Trust } from '../types'
5
5
  import type { Ctx } from './Ctx'
6
6
  import type { Payload } from './Payload'
7
7
 
8
- const jwks: Record<string, Awaited<ReturnType<typeof createRemoteJWKSet>>> = {}
9
-
10
8
  export async function exchange (credentials: string, ctx: Ctx): Promise<Payload | Error> {
9
+ const jose = await load()
11
10
  const properties = decode(credentials)
12
11
 
13
12
  if (properties instanceof Error)
@@ -23,7 +22,7 @@ export async function exchange (credentials: string, ctx: Ctx): Promise<Payload
23
22
  if (trusted.aud === undefined || (trusted.secret === undefined && trusted.signature === undefined))
24
23
  return errors.ERR_CODE_NOT_ENABLED
25
24
 
26
- const configuration = await discover(iss)
25
+ const configuration = await discover(iss, ctx.fetch)
27
26
 
28
27
  if (configuration.token_endpoint === undefined)
29
28
  return errors.ERR_CONFIG
@@ -47,7 +46,7 @@ export async function exchange (credentials: string, ctx: Ctx): Promise<Payload
47
46
  code
48
47
  })
49
48
 
50
- const response = await fetch(configuration.token_endpoint, {
49
+ const response = await ctx.fetch(configuration.token_endpoint, {
51
50
  method: 'POST',
52
51
  headers: {
53
52
  'Content-Type': 'application/x-www-form-urlencoded'
@@ -66,9 +65,9 @@ export async function exchange (credentials: string, ctx: Ctx): Promise<Payload
66
65
  if (tokens.id_token === undefined)
67
66
  return errors.ERR_NO_TOKEN
68
67
 
69
- jwks[iss] ??= await createRemoteJWKSet(iss)
68
+ const jwks = await createRemoteJWKSet(iss, ctx.fetch)
70
69
 
71
- const { payload } = await jose.jwtVerify(tokens.id_token, jwks[iss], {
70
+ const { payload } = await jose.jwtVerify(tokens.id_token, jwks, {
72
71
  audience: trusted.aud,
73
72
  issuer: iss
74
73
  })
@@ -92,6 +91,7 @@ function decode (credentials: string): Properties | Error {
92
91
  }
93
92
 
94
93
  async function sign (trust: Trust): Promise<string> {
94
+ const jose = await load()
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)
@@ -0,0 +1 @@
1
+ export function load (): Promise<typeof import('jose')>
@@ -0,0 +1,5 @@
1
+ 'use strict'
2
+
3
+ const load = async () => await import('jose')
4
+
5
+ exports.load = load
@@ -17,7 +17,8 @@ it('lists indexed credentials and a legacy associated credential', async () => {
17
17
  query: {
18
18
  criteria: 'authority==nex;identity==identity',
19
19
  projection: ['iss'],
20
- sort: ['_created:desc']
20
+ sort: ['_created:desc'],
21
+ limit: 100
21
22
  }
22
23
  })
23
24
  })
@@ -5,7 +5,8 @@ export async function computation ({ authority, identity }: Input, context: Cont
5
5
  query: {
6
6
  criteria: `authority==${authority};identity==${identity}`,
7
7
  projection: ['iss'],
8
- sort: ['_created:desc']
8
+ sort: ['_created:desc'],
9
+ limit: 100
9
10
  }
10
11
  })
11
12
 
@@ -4,6 +4,7 @@ import type { Entity } from './entity'
4
4
  import type { Configuration } from './configuration'
5
5
 
6
6
  export interface Context {
7
+ fetch: Fetch
7
8
  local: {
8
9
  observe: Observation<Entity>
9
10
  enumerate: Observation<Entity[], never, Entity>
@@ -23,6 +24,8 @@ export interface Context {
23
24
  configuration: Configuration
24
25
  }
25
26
 
27
+ export type Fetch = (input: string | URL | Request, init?: RequestInit) => Promise<Response>
28
+
26
29
  export interface TransitInput {
27
30
  readonly authority: string
28
31
  readonly iss: string
@@ -1,10 +1,7 @@
1
1
  {
2
2
  "extends": "../../tsconfig.json",
3
3
  "compilerOptions": {
4
- "outDir": "./operations",
5
- "module": "Node16",
6
- "moduleResolution": "Node16",
7
- "target": "ES2022"
4
+ "outDir": "./operations"
8
5
  },
9
6
  "include": ["source"]
10
7
  }
@@ -42,6 +42,7 @@ operations:
42
42
  required:
43
43
  - id
44
44
  - key
45
+
45
46
  exposition:
46
47
  /:identity:
47
48
  auth:id: identity
@@ -1,10 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Transition = void 0;
4
- const paseto_1 = require("paseto");
4
+ const node_crypto_1 = require("node:crypto");
5
5
  class Transition {
6
6
  async execute(input, object) {
7
- object.key = await paseto_1.V3.generateKey('local', { format: 'paserk' });
7
+ object.key = (0, node_crypto_1.randomBytes)(32).toString('base64url');
8
8
  object.identity = input.identity;
9
9
  object.label = input.label;
10
10
  if (input.expires !== undefined)
@@ -1 +1 @@
1
- {"version":3,"file":"create.js","sourceRoot":"","sources":["../source/create.ts"],"names":[],"mappings":";;;AAAA,mCAA2B;AAG3B,MAAa,UAAU;IACd,KAAK,CAAC,OAAO,CAAE,KAAY,EAAE,MAAW;QAC7C,MAAM,CAAC,GAAG,GAAG,MAAM,WAAE,CAAC,WAAW,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAA;QAChE,MAAM,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAA;QAChC,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAA;QAE1B,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS;YAC7B,MAAM,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAA;QAEhC,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,CAAA;IAChE,CAAC;CACF;AAXD,gCAWC"}
1
+ {"version":3,"file":"create.js","sourceRoot":"","sources":["../source/create.ts"],"names":[],"mappings":";;;AAAA,6CAAyC;AAGzC,MAAa,UAAU;IACd,KAAK,CAAC,OAAO,CAAE,KAAY,EAAE,MAAW;QAC7C,MAAM,CAAC,GAAG,GAAG,IAAA,yBAAW,EAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAA;QAClD,MAAM,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAA;QAChC,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAA;QAE1B,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS;YAC7B,MAAM,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAA;QAEhC,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,CAAA;IAChE,CAAC;CACF;AAXD,gCAWC"}