@mcp-abap-adt/auth-broker 4.0.0 → 4.1.0
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 +135 -0
- package/README.md +379 -5
- package/dist/AuthBroker.d.ts +78 -8
- package/dist/AuthBroker.d.ts.map +1 -1
- package/dist/AuthBroker.js +204 -31
- package/dist/binding.d.ts +28 -6
- package/dist/binding.d.ts.map +1 -1
- package/dist/binding.js +20 -4
- package/dist/bindingOf.d.ts +2 -1
- package/dist/bindingOf.d.ts.map +1 -1
- package/dist/clientAuthentication.d.ts +109 -0
- package/dist/clientAuthentication.d.ts.map +1 -0
- package/dist/clientAuthentication.js +218 -0
- package/dist/destinations.d.ts +35 -10
- package/dist/destinations.d.ts.map +1 -1
- package/dist/destinations.js +61 -17
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/package.json +6 -5
package/dist/AuthBroker.d.ts
CHANGED
|
@@ -19,8 +19,9 @@
|
|
|
19
19
|
* broker does not repeat or override any of them.
|
|
20
20
|
*/
|
|
21
21
|
import type { IDeviceCodePresenter, OidcCallbackResult } from '@mcp-abap-adt/auth-providers';
|
|
22
|
-
import type { IAssertionReplayStore, IAuthorizationStrategy, IAuthProvider, IRefreshableTokenProvider, ITokenRefresher } from '@mcp-abap-adt/interfaces-auth';
|
|
22
|
+
import type { IAssertionReplayStore, IAuthorizationStrategy, IAuthProvider, IClientAuthentication, IRefreshableTokenProvider, ITokenRefresher } from '@mcp-abap-adt/interfaces-auth';
|
|
23
23
|
import type { ILogger } from '@mcp-abap-adt/interfaces-utils';
|
|
24
|
+
import { type ClientAuthenticationStrategy } from './clientAuthentication';
|
|
24
25
|
import type { IAuthorizationConfig, IConnectionConfig, IServiceKeyStore, ISessionStore } from './stores/interfaces';
|
|
25
26
|
/**
|
|
26
27
|
* Builds the provider for one destination, from what the stores hold for it.
|
|
@@ -31,10 +32,39 @@ import type { IAuthorizationConfig, IConnectionConfig, IServiceKeyStore, ISessio
|
|
|
31
32
|
* - `connConfig`: the session's connection config, with `serviceUrl` resolved
|
|
32
33
|
* and the last token the session stored, so the provider can reuse it while
|
|
33
34
|
* it is valid.
|
|
35
|
+
* - `client` (optional): passed only beside a `clientAuthentication` strategy,
|
|
36
|
+
* for a destination whose grant authenticates a client — the strategy's
|
|
37
|
+
* answer, the client identity and the bound refresh token; never PEM. See
|
|
38
|
+
* `TokenProviderClient`. Without a strategy the factory gets three
|
|
39
|
+
* arguments, as 4.0.0.
|
|
34
40
|
*
|
|
35
41
|
* Called once per destination; the broker keeps the provider it returns.
|
|
36
42
|
*/
|
|
37
|
-
export type TokenProviderFactory = (destination: string, authConfig: IAuthorizationConfig | null, connConfig: IConnectionConfig) => IRefreshableTokenProvider;
|
|
43
|
+
export type TokenProviderFactory = (destination: string, authConfig: IAuthorizationConfig | null, connConfig: IConnectionConfig, client?: TokenProviderClient) => IRefreshableTokenProvider;
|
|
44
|
+
/**
|
|
45
|
+
* The token API factory's fourth argument — present only when the broker was
|
|
46
|
+
* given a `clientAuthentication` strategy and the destination states a grant
|
|
47
|
+
* whose client authenticates; without one the factory is called with three
|
|
48
|
+
* arguments, as 4.0.0. It never carries a certificate, a key or a secret.
|
|
49
|
+
*/
|
|
50
|
+
export interface TokenProviderClient {
|
|
51
|
+
/** The strategy's answer, resolved before the factory is called. */
|
|
52
|
+
readonly clientAuthentication?: IClientAuthentication;
|
|
53
|
+
/**
|
|
54
|
+
* The client's identity — the secret client's `uaaUrl` when the stores hold
|
|
55
|
+
* one, else the certificate client's; absent when there is neither.
|
|
56
|
+
*/
|
|
57
|
+
readonly uaaUrl?: string;
|
|
58
|
+
/** The client id beside `uaaUrl`, from the same client. */
|
|
59
|
+
readonly clientId?: string;
|
|
60
|
+
/**
|
|
61
|
+
* The refresh token the session stored, only when the session is bound to
|
|
62
|
+
* this resource and this client identity — as `authConfig` carries it on
|
|
63
|
+
* the strategy path for a secret client, so a certificate client, whose
|
|
64
|
+
* `authConfig` is `null`, gets it too.
|
|
65
|
+
*/
|
|
66
|
+
readonly refreshToken?: string;
|
|
67
|
+
}
|
|
38
68
|
/** The grants whose provider takes an `IAuthorizationStrategy<string>`. */
|
|
39
69
|
export type StrategyGrant = 'authorization_code' | 'passcode' | 'saml2_pure' | 'saml2_bearer';
|
|
40
70
|
/**
|
|
@@ -71,6 +101,26 @@ export interface AuthBrokerConfig {
|
|
|
71
101
|
samlCookies?: (destination: string) => (samlResponse: string) => Promise<string>;
|
|
72
102
|
/** The replay store the SAML assertion validators share. */
|
|
73
103
|
assertionReplayStore?: (destination: string) => IAssertionReplayStore;
|
|
104
|
+
/**
|
|
105
|
+
* How a destination's client authenticates to the authorization server, for
|
|
106
|
+
* every grant that authenticates one (`ClientAuthenticationGrant`). Called
|
|
107
|
+
* once when the destination's provider is built, with the client the key
|
|
108
|
+
* store answers and its certificate, read only if the strategy asks.
|
|
109
|
+
*
|
|
110
|
+
* A given strategy always answers one, or throws: there is no "nothing"
|
|
111
|
+
* answer, so an explicit choice never falls back to the secret. A throw
|
|
112
|
+
* becomes a `DestinationConfigError` naming `clientAuthentication`, in fixed
|
|
113
|
+
* words. Absent: the client secret, as in 4.0.0, and nothing
|
|
114
|
+
* certificate-related is read. Shipped: `fromServiceKeyCertificate()`,
|
|
115
|
+
* `fromServiceKeySecret({ encoding })`.
|
|
116
|
+
*
|
|
117
|
+
* It applies to the providers the broker builds: `getProvider`'s, and the
|
|
118
|
+
* token API's `provider` factory, which gets its answer as a fourth
|
|
119
|
+
* argument (`TokenProviderClient`) for a grant that authenticates a client.
|
|
120
|
+
* An instance given as `provider` is already composed by the consumer, so
|
|
121
|
+
* the token API uses it as given and calls no strategy for it.
|
|
122
|
+
*/
|
|
123
|
+
clientAuthentication?: ClientAuthenticationStrategy;
|
|
74
124
|
}
|
|
75
125
|
/**
|
|
76
126
|
* AuthBroker manages authentication tokens for destinations
|
|
@@ -101,6 +151,7 @@ export declare class AuthBroker {
|
|
|
101
151
|
private readonly deviceCodePresenter;
|
|
102
152
|
private readonly samlCookies;
|
|
103
153
|
private readonly assertionReplayStore;
|
|
154
|
+
private readonly clientAuthentication;
|
|
104
155
|
/** getProvider's writes of the session secret, retried on their own. */
|
|
105
156
|
private readonly writer;
|
|
106
157
|
/**
|
|
@@ -163,12 +214,29 @@ export declare class AuthBroker {
|
|
|
163
214
|
*/
|
|
164
215
|
private consumerProviderFor;
|
|
165
216
|
/**
|
|
166
|
-
* The
|
|
167
|
-
*
|
|
168
|
-
*
|
|
217
|
+
* The token API's strategy path: with a `clientAuthentication` strategy and a
|
|
218
|
+
* destination stating a grant whose client authenticates, the strategy's
|
|
219
|
+
* answer — resolved inside the guard — and the client identity: the secret
|
|
220
|
+
* client, else the certificate client's, read through the context's memoised
|
|
221
|
+
* `readCertificate`. `null` without a strategy (4.0.0: nothing
|
|
222
|
+
* certificate-related is asked) and for a grant that authenticates no
|
|
223
|
+
* client (`saml2_pure`, `none`), as `getProvider` decides.
|
|
224
|
+
*
|
|
225
|
+
* The strategy is told the grant, so a destination that states none is
|
|
226
|
+
* refused before it is called rather than built without it. (An instance
|
|
227
|
+
* given as `provider` is the consumer's own composition: the strategy
|
|
228
|
+
* applies only to what the broker builds, so it is not used there.)
|
|
229
|
+
*/
|
|
230
|
+
private consumerClientAuthentication;
|
|
231
|
+
/**
|
|
232
|
+
* What a consumer's factory's credentials are composed from, read in the
|
|
233
|
+
* 3.x order: the session's authorization config; only when it has none, the
|
|
234
|
+
* session and then the key store's client. A session store of auth-stores 3
|
|
169
235
|
* answers no client, so the key store's is what is found.
|
|
170
236
|
*/
|
|
171
|
-
private
|
|
237
|
+
private readAuthorization;
|
|
238
|
+
/** The session the store holds for the destination, or null. */
|
|
239
|
+
private loadStoredSession;
|
|
172
240
|
/**
|
|
173
241
|
* `serviceUrl` for a consumer's provider, in the 3.x order: the session's,
|
|
174
242
|
* else the key store's, else an error before the provider is asked.
|
|
@@ -250,8 +318,10 @@ export declare class AuthBroker {
|
|
|
250
318
|
* write time, so a result without one does not erase the stored one — for
|
|
251
319
|
* a provider the broker built, only a stored one bound where this one is:
|
|
252
320
|
* a refresh token obtained for another resource or from another client is
|
|
253
|
-
* not carried into this secret.
|
|
254
|
-
*
|
|
321
|
+
* not carried into this secret. So too for a consumer's factory beside a
|
|
322
|
+
* `clientAuthentication` strategy, seeded only with a bound session. A
|
|
323
|
+
* consumer's provider without one was seeded with whatever the session
|
|
324
|
+
* held, so whatever it holds is carried (4.0.0).
|
|
255
325
|
* - `issuedFor` / `issuedBy`: the binding computed when the provider was
|
|
256
326
|
* built, each left out when the means lack its source — so the
|
|
257
327
|
* store clears it.
|
package/dist/AuthBroker.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthBroker.d.ts","sourceRoot":"","sources":["../src/AuthBroker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EACV,oBAAoB,EACpB,kBAAkB,EACnB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EACV,qBAAqB,EACrB,sBAAsB,EACtB,aAAa,EACb,yBAAyB,EACzB,eAAe,EAEhB,MAAM,+BAA+B,CAAC;AAGvC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"AuthBroker.d.ts","sourceRoot":"","sources":["../src/AuthBroker.ts"],"names":[],"mappings":"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"}
|
package/dist/AuthBroker.js
CHANGED
|
@@ -24,6 +24,7 @@ exports.AuthBroker = void 0;
|
|
|
24
24
|
const interfaces_auth_1 = require("@mcp-abap-adt/interfaces-auth");
|
|
25
25
|
const binding_1 = require("./binding");
|
|
26
26
|
const bindingOf_1 = require("./bindingOf");
|
|
27
|
+
const clientAuthentication_1 = require("./clientAuthentication");
|
|
27
28
|
const DestinationConfigError_1 = require("./DestinationConfigError");
|
|
28
29
|
const destinations_1 = require("./destinations");
|
|
29
30
|
const SessionWriter_1 = require("./SessionWriter");
|
|
@@ -43,6 +44,37 @@ const SECRET_FIELDS = [
|
|
|
43
44
|
'issuedBy',
|
|
44
45
|
];
|
|
45
46
|
const isSecretField = (field) => SECRET_FIELDS.includes(field);
|
|
47
|
+
/**
|
|
48
|
+
* The connection means a strategy-path seed carries — the connection's own,
|
|
49
|
+
* none of them a secret. Everything else a store's connection read answers
|
|
50
|
+
* (credentials, grant data, fields outside the type) stays behind.
|
|
51
|
+
*/
|
|
52
|
+
const SEED_MEANS = [
|
|
53
|
+
'serviceUrl',
|
|
54
|
+
'sapClient',
|
|
55
|
+
'language',
|
|
56
|
+
'authType',
|
|
57
|
+
'grantType',
|
|
58
|
+
];
|
|
59
|
+
/**
|
|
60
|
+
* The token API factory's seed on the `clientAuthentication` strategy path,
|
|
61
|
+
* from an allowlist only: `SEED_MEANS`, and the session secret's own fields
|
|
62
|
+
* (`SECRET_FIELDS`: the token or cookies, the expiry, the binding) only when
|
|
63
|
+
* that same read is bound here — each secret judged by the read it came from.
|
|
64
|
+
* `serviceUrl` is the one resolved for the destination.
|
|
65
|
+
*/
|
|
66
|
+
function strategySeed(connConfig, binding, serviceUrl) {
|
|
67
|
+
const source = (connConfig ?? {});
|
|
68
|
+
const fields = connConfig && (0, binding_1.boundHere)(connConfig, binding)
|
|
69
|
+
? [...SEED_MEANS, ...SECRET_FIELDS]
|
|
70
|
+
: SEED_MEANS;
|
|
71
|
+
const seed = {};
|
|
72
|
+
for (const field of fields) {
|
|
73
|
+
if (source[field] !== undefined)
|
|
74
|
+
seed[field] = source[field];
|
|
75
|
+
}
|
|
76
|
+
return { ...seed, serviceUrl };
|
|
77
|
+
}
|
|
46
78
|
/** When the result expires, in epoch ms: its own `expiresAt`, else `expiresIn` from now. */
|
|
47
79
|
function expiryOf(result) {
|
|
48
80
|
if (typeof result.expiresAt === 'number')
|
|
@@ -58,6 +90,39 @@ function obtainsTokens(provider) {
|
|
|
58
90
|
return (typeof candidate.getTokens === 'function' &&
|
|
59
91
|
typeof candidate.refreshTokens === 'function');
|
|
60
92
|
}
|
|
93
|
+
/** The refresh token a stored session holds, unchecked. */
|
|
94
|
+
function storedRefreshTokenOf(session) {
|
|
95
|
+
return typeof session?.refreshToken === 'string'
|
|
96
|
+
? session.refreshToken
|
|
97
|
+
: undefined;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The 3.x composition, the no-strategy path: the session's client as it is,
|
|
101
|
+
* else the key store's with `refreshToken` — the one given, else the key's
|
|
102
|
+
* own. (The strategy path takes `strategyAuthorization`.)
|
|
103
|
+
*/
|
|
104
|
+
function composeAuthorization(read, refreshToken) {
|
|
105
|
+
if (read.sessionAuth)
|
|
106
|
+
return read.sessionAuth;
|
|
107
|
+
if (!read.keyAuth)
|
|
108
|
+
return null;
|
|
109
|
+
return {
|
|
110
|
+
...read.keyAuth,
|
|
111
|
+
refreshToken: refreshToken ?? read.keyAuth.refreshToken,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* The client on the `clientAuthentication` strategy path: the session's own
|
|
116
|
+
* client, else the key store's, carrying only `refreshToken` — the one the
|
|
117
|
+
* session read whose binding was checked holds, when bound — never a refresh
|
|
118
|
+
* token either client read carried itself.
|
|
119
|
+
*/
|
|
120
|
+
function strategyAuthorization(read, refreshToken) {
|
|
121
|
+
// The same allowlist the strategy is told (`contextClient`): nothing else a
|
|
122
|
+
// store's authorization-config read carried reaches the factory.
|
|
123
|
+
const client = (0, clientAuthentication_1.contextClient)(read.sessionAuth ?? read.keyAuth);
|
|
124
|
+
return client ? { ...client, refreshToken } : null;
|
|
125
|
+
}
|
|
61
126
|
/** A stored string that counts as present: `''` is none. */
|
|
62
127
|
function present(value) {
|
|
63
128
|
return typeof value === 'string' && value !== '';
|
|
@@ -105,6 +170,7 @@ class AuthBroker {
|
|
|
105
170
|
deviceCodePresenter;
|
|
106
171
|
samlCookies;
|
|
107
172
|
assertionReplayStore;
|
|
173
|
+
clientAuthentication;
|
|
108
174
|
/** getProvider's writes of the session secret, retried on their own. */
|
|
109
175
|
writer;
|
|
110
176
|
/**
|
|
@@ -158,6 +224,7 @@ class AuthBroker {
|
|
|
158
224
|
this.deviceCodePresenter = config.deviceCodePresenter;
|
|
159
225
|
this.samlCookies = config.samlCookies;
|
|
160
226
|
this.assertionReplayStore = config.assertionReplayStore;
|
|
227
|
+
this.clientAuthentication = config.clientAuthentication;
|
|
161
228
|
this.logger = logger ?? noOpLogger;
|
|
162
229
|
this.writer = new SessionWriter_1.SessionWriter(async (destination, bound) => {
|
|
163
230
|
try {
|
|
@@ -268,14 +335,14 @@ class AuthBroker {
|
|
|
268
335
|
async obtainFromConsumer(destination, method, means) {
|
|
269
336
|
const connConfig = await this.read(destination, 'session connection config', () => this.sessionStore.getConnectionConfig(destination));
|
|
270
337
|
const serviceUrl = await this.resolveServiceUrl(destination, connConfig);
|
|
271
|
-
const { provider, binding } = await this.consumerProviderFor(destination, serviceUrl, connConfig, means);
|
|
338
|
+
const { provider, binding, carry } = await this.consumerProviderFor(destination, serviceUrl, connConfig, means);
|
|
272
339
|
this.logger.debug(`[AuthBroker] ${method} for ${destination}`);
|
|
273
340
|
const result = checked(destination, await provider[method]());
|
|
274
341
|
await this.writer.submit(destination, {
|
|
275
342
|
result: { ...result, expiresAt: expiryOf(result) },
|
|
276
343
|
original: result,
|
|
277
344
|
binding,
|
|
278
|
-
carry
|
|
345
|
+
carry,
|
|
279
346
|
});
|
|
280
347
|
this.throwFailedWrite(destination, result);
|
|
281
348
|
return result;
|
|
@@ -309,22 +376,76 @@ class AuthBroker {
|
|
|
309
376
|
return {
|
|
310
377
|
provider,
|
|
311
378
|
binding: (0, binding_1.consumerBinding)(serviceUrl, sapClient, null),
|
|
379
|
+
carry: 'any',
|
|
312
380
|
};
|
|
313
381
|
}
|
|
314
|
-
const
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
382
|
+
const read = await this.readAuthorization(destination);
|
|
383
|
+
const strategic = await this.consumerClientAuthentication(destination, means,
|
|
384
|
+
// The strategy is told the client, never a refresh token read with it.
|
|
385
|
+
strategyAuthorization(read, undefined));
|
|
386
|
+
let built;
|
|
387
|
+
let client;
|
|
388
|
+
let binding;
|
|
389
|
+
let carry;
|
|
390
|
+
if (strategic) {
|
|
391
|
+
// The strategy's answer and the client identity, resolved before the
|
|
392
|
+
// factory; the factory called inside the same guard: a throw is fixed
|
|
393
|
+
// words, nothing of the thrown value, no cause. The stored refresh
|
|
394
|
+
// token is carried only when the session is bound to this resource
|
|
395
|
+
// and this client identity — never to another authorization server.
|
|
396
|
+
binding = (0, binding_1.strategyBinding)((0, binding_1.consumerBinding)(serviceUrl, sapClient, strategic.identity));
|
|
397
|
+
const session = read.sessionRead
|
|
398
|
+
? read.session
|
|
399
|
+
: await this.loadStoredSession(destination);
|
|
400
|
+
const bound = !!session && (0, binding_1.boundHere)(session, binding);
|
|
401
|
+
const boundRefreshToken = bound
|
|
402
|
+
? storedRefreshTokenOf(session)
|
|
403
|
+
: undefined;
|
|
404
|
+
// The client without any refresh token of its own read: a session
|
|
405
|
+
// store's `getAuthorizationConfig` is another read than the session
|
|
406
|
+
// whose binding was checked, and another process may have written
|
|
407
|
+
// between them. The refresh token comes only from that checked read.
|
|
408
|
+
client = strategyAuthorization(read, boundRefreshToken);
|
|
409
|
+
// Each stored secret is judged by the read it came from: the seed's
|
|
410
|
+
// token, cookies and expiry by the binding the connection read itself
|
|
411
|
+
// carries — another process may have written between the two reads —
|
|
412
|
+
// else only the means seed the provider.
|
|
413
|
+
const seed = strategySeed(connConfig, binding, serviceUrl);
|
|
414
|
+
carry = 'bound';
|
|
415
|
+
const refreshToken = client ? client.refreshToken : boundRefreshToken;
|
|
416
|
+
const fourth = {
|
|
417
|
+
clientAuthentication: strategic.clientAuthentication,
|
|
418
|
+
...(strategic.identity
|
|
419
|
+
? {
|
|
420
|
+
uaaUrl: strategic.identity.uaaUrl,
|
|
421
|
+
clientId: strategic.identity.uaaClientId,
|
|
422
|
+
}
|
|
423
|
+
: {}),
|
|
424
|
+
...(present(refreshToken) ? { refreshToken } : {}),
|
|
425
|
+
};
|
|
426
|
+
try {
|
|
427
|
+
built = provider(destination, client, seed, fourth);
|
|
428
|
+
}
|
|
429
|
+
catch {
|
|
430
|
+
throw new DestinationConfigError_1.DestinationConfigError(destination, ['provider', 'clientAuthentication'], 'the token provider factory failed beside the clientAuthentication strategy');
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
// 4.0.0: the stored secret and refresh token carried over as read.
|
|
435
|
+
client = composeAuthorization(read, storedRefreshTokenOf(read.session));
|
|
436
|
+
binding = (0, binding_1.consumerBinding)(serviceUrl, sapClient, client);
|
|
437
|
+
carry = 'any';
|
|
438
|
+
built = provider(destination, client, {
|
|
439
|
+
...(connConfig ?? {}),
|
|
440
|
+
serviceUrl,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
319
443
|
this.logger.debug(`[AuthBroker] Provider built for ${destination}`, {
|
|
320
444
|
hasCredentials: !!client,
|
|
321
445
|
hasRefreshToken: !!client?.refreshToken,
|
|
322
446
|
hasStoredToken: !!(connConfig?.authorizationToken || connConfig?.sessionCookies),
|
|
323
447
|
});
|
|
324
|
-
return {
|
|
325
|
-
provider: built,
|
|
326
|
-
binding: (0, binding_1.consumerBinding)(serviceUrl, sapClient, client),
|
|
327
|
-
};
|
|
448
|
+
return { provider: built, binding, carry };
|
|
328
449
|
});
|
|
329
450
|
this.consumerBuilt.set(destination, build);
|
|
330
451
|
build.catch(() => {
|
|
@@ -335,31 +456,61 @@ class AuthBroker {
|
|
|
335
456
|
return build;
|
|
336
457
|
}
|
|
337
458
|
/**
|
|
338
|
-
* The
|
|
339
|
-
*
|
|
340
|
-
*
|
|
459
|
+
* The token API's strategy path: with a `clientAuthentication` strategy and a
|
|
460
|
+
* destination stating a grant whose client authenticates, the strategy's
|
|
461
|
+
* answer — resolved inside the guard — and the client identity: the secret
|
|
462
|
+
* client, else the certificate client's, read through the context's memoised
|
|
463
|
+
* `readCertificate`. `null` without a strategy (4.0.0: nothing
|
|
464
|
+
* certificate-related is asked) and for a grant that authenticates no
|
|
465
|
+
* client (`saml2_pure`, `none`), as `getProvider` decides.
|
|
466
|
+
*
|
|
467
|
+
* The strategy is told the grant, so a destination that states none is
|
|
468
|
+
* refused before it is called rather than built without it. (An instance
|
|
469
|
+
* given as `provider` is the consumer's own composition: the strategy
|
|
470
|
+
* applies only to what the broker builds, so it is not used there.)
|
|
471
|
+
*/
|
|
472
|
+
async consumerClientAuthentication(destination, means, client) {
|
|
473
|
+
const strategy = this.clientAuthentication;
|
|
474
|
+
if (!strategy) {
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
// basic and snc never get here: the token API refuses them first
|
|
478
|
+
// (`statedForTokens`).
|
|
479
|
+
if (!means || (means.authType !== 'jwt' && means.authType !== 'saml')) {
|
|
480
|
+
throw new DestinationConfigError_1.DestinationConfigError(destination, ['authType', 'grantType'], 'a clientAuthentication strategy is told the grant: the destination must state its authType and grantType');
|
|
481
|
+
}
|
|
482
|
+
const grant = (0, destinations_1.statedGrant)(destination, means.authType, means);
|
|
483
|
+
if (grant === 'saml2_pure' || grant === 'none') {
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
const serviceKeyStore = this.serviceKeyStore;
|
|
487
|
+
const context = (0, clientAuthentication_1.clientAuthenticationContext)(destination, grant, client, () => this.read(destination, 'client certificate', async () => serviceKeyStore?.getClientCertificate
|
|
488
|
+
? serviceKeyStore.getClientCertificate(destination)
|
|
489
|
+
: null));
|
|
490
|
+
const clientAuthentication = await (0, clientAuthentication_1.resolveClientAuthentication)(strategy, context);
|
|
491
|
+
return { clientAuthentication, identity: await (0, clientAuthentication_1.clientIdentity)(context) };
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* What a consumer's factory's credentials are composed from, read in the
|
|
495
|
+
* 3.x order: the session's authorization config; only when it has none, the
|
|
496
|
+
* session and then the key store's client. A session store of auth-stores 3
|
|
341
497
|
* answers no client, so the key store's is what is found.
|
|
342
498
|
*/
|
|
343
|
-
async
|
|
499
|
+
async readAuthorization(destination) {
|
|
344
500
|
const sessionAuth = await this.read(destination, 'session authorization config', () => this.sessionStore.getAuthorizationConfig(destination));
|
|
345
501
|
if (sessionAuth) {
|
|
346
|
-
return sessionAuth;
|
|
502
|
+
return { sessionAuth, keyAuth: null, session: null, sessionRead: false };
|
|
347
503
|
}
|
|
348
|
-
const session = await this.
|
|
349
|
-
const storedRefreshToken = typeof session?.refreshToken === 'string'
|
|
350
|
-
? session.refreshToken
|
|
351
|
-
: undefined;
|
|
504
|
+
const session = await this.loadStoredSession(destination);
|
|
352
505
|
const serviceKeyStore = this.serviceKeyStore;
|
|
353
506
|
const keyAuth = serviceKeyStore
|
|
354
507
|
? await this.read(destination, 'service key authorization config', () => serviceKeyStore.getAuthorizationConfig(destination))
|
|
355
508
|
: null;
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
refreshToken: storedRefreshToken ?? keyAuth.refreshToken,
|
|
362
|
-
};
|
|
509
|
+
return { sessionAuth: null, keyAuth, session, sessionRead: true };
|
|
510
|
+
}
|
|
511
|
+
/** The session the store holds for the destination, or null. */
|
|
512
|
+
loadStoredSession(destination) {
|
|
513
|
+
return this.read(destination, 'session', () => this.sessionStore.loadSession(destination));
|
|
363
514
|
}
|
|
364
515
|
/**
|
|
365
516
|
* `serviceUrl` for a consumer's provider, in the 3.x order: the session's,
|
|
@@ -509,10 +660,29 @@ class AuthBroker {
|
|
|
509
660
|
// saml2_pure), what a secret for this destination is bound to
|
|
510
661
|
// — written with every secret, and required of a stored one
|
|
511
662
|
// before it seeds — and the stored secret when it is.
|
|
512
|
-
const
|
|
663
|
+
const secretClient = grant === 'saml2_pure'
|
|
513
664
|
? null
|
|
514
665
|
: await this.read(destination, 'client', () => serviceKeyStore.getAuthorizationConfig(destination));
|
|
515
|
-
|
|
666
|
+
// The consumer's client authentication, resolved inside the guard
|
|
667
|
+
// before any provider exists: a strategy that throws leaves nothing
|
|
668
|
+
// built and nothing cached. Then the client identity the row and the
|
|
669
|
+
// binding take — the secret client, else the certificate client's —
|
|
670
|
+
// so the binding and the seed are chosen from it. Without a strategy
|
|
671
|
+
// nothing of this runs: 4.0.0's client, nothing certificate-related.
|
|
672
|
+
let client = secretClient;
|
|
673
|
+
let clientAuthentication;
|
|
674
|
+
if (this.clientAuthentication && grant !== 'saml2_pure') {
|
|
675
|
+
const context = (0, clientAuthentication_1.clientAuthenticationContext)(destination, grant, secretClient, () => this.read(destination, 'client certificate', async () => serviceKeyStore.getClientCertificate
|
|
676
|
+
? serviceKeyStore.getClientCertificate(destination)
|
|
677
|
+
: null));
|
|
678
|
+
clientAuthentication = await (0, clientAuthentication_1.resolveClientAuthentication)(this.clientAuthentication, context);
|
|
679
|
+
client = await (0, clientAuthentication_1.clientIdentity)(context);
|
|
680
|
+
}
|
|
681
|
+
const computed = (0, bindingOf_1.destinationBinding)(authType, grant, stated, client);
|
|
682
|
+
// The strategy path: a resource neither side states matches.
|
|
683
|
+
const binding = clientAuthentication
|
|
684
|
+
? (0, binding_1.strategyBinding)(computed)
|
|
685
|
+
: computed;
|
|
516
686
|
const stored = grant === 'client_credentials'
|
|
517
687
|
? null
|
|
518
688
|
: await this.read(destination, 'session', () => this.sessionStore.loadSession(destination));
|
|
@@ -531,6 +701,7 @@ class AuthBroker {
|
|
|
531
701
|
destination,
|
|
532
702
|
client,
|
|
533
703
|
secret,
|
|
704
|
+
...(clientAuthentication ? { clientAuthentication } : {}),
|
|
534
705
|
logger: this.logger,
|
|
535
706
|
onTokens,
|
|
536
707
|
};
|
|
@@ -623,8 +794,10 @@ class AuthBroker {
|
|
|
623
794
|
* write time, so a result without one does not erase the stored one — for
|
|
624
795
|
* a provider the broker built, only a stored one bound where this one is:
|
|
625
796
|
* a refresh token obtained for another resource or from another client is
|
|
626
|
-
* not carried into this secret.
|
|
627
|
-
*
|
|
797
|
+
* not carried into this secret. So too for a consumer's factory beside a
|
|
798
|
+
* `clientAuthentication` strategy, seeded only with a bound session. A
|
|
799
|
+
* consumer's provider without one was seeded with whatever the session
|
|
800
|
+
* held, so whatever it holds is carried (4.0.0).
|
|
628
801
|
* - `issuedFor` / `issuedBy`: the binding computed when the provider was
|
|
629
802
|
* built, each left out when the means lack its source — so the
|
|
630
803
|
* store clears it.
|
package/dist/binding.d.ts
CHANGED
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
*/
|
|
25
25
|
import type { IConfig, IConnectionConfig } from '@mcp-abap-adt/interfaces-auth-broker';
|
|
26
26
|
import type { IAuthorizationConfig } from '@mcp-abap-adt/interfaces-auth-sap';
|
|
27
|
+
/** What a binding reads of a client: who it is, never its secret. */
|
|
28
|
+
export type BoundClient = Pick<IAuthorizationConfig, 'uaaUrl' | 'uaaClientId'>;
|
|
27
29
|
/**
|
|
28
30
|
* The resource: `serviceUrl` with the SAP client as `sap-client` — the
|
|
29
31
|
* destination's `sapClient` when it states one, over a `sap-client` already in
|
|
@@ -51,19 +53,28 @@ export interface Binding {
|
|
|
51
53
|
* matches nothing.
|
|
52
54
|
*/
|
|
53
55
|
issuerStated: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Set on the `clientAuthentication` strategy path only: a resource neither
|
|
58
|
+
* side states — no `issuedFor` computed (no service URL, or one that does
|
|
59
|
+
* not parse, as the CLI's XSUAA placeholder) and none stored — matches, so
|
|
60
|
+
* the issuer and client alone decide. A resource stated on one side only
|
|
61
|
+
* never matches. Without it (the 4.0.0 path) an absent resource matches
|
|
62
|
+
* nothing.
|
|
63
|
+
*/
|
|
64
|
+
unstatedResourceMatches?: true;
|
|
54
65
|
}
|
|
55
66
|
/**
|
|
56
67
|
* The UAA grants (`authorization_code`, `client_credentials`, `passcode`) and
|
|
57
68
|
* `saml2_bearer`, whose token UAA issues to its client: the resource, and `uaaUrl` with `uaaClientId` — both required to match.
|
|
58
69
|
*/
|
|
59
|
-
export declare function uaaBinding(means: IConnectionConfig, client:
|
|
70
|
+
export declare function uaaBinding(means: IConnectionConfig, client: BoundClient | null): Binding;
|
|
60
71
|
/**
|
|
61
72
|
* The OIDC grants: the resource, and the issuer — `oidcIssuerUrl`, else
|
|
62
73
|
* `uaaUrl` — with `uaaClientId`; both required to match. Means that state
|
|
63
74
|
* neither issuer (explicit endpoints alone) bind no stored secret: one is then
|
|
64
75
|
* never seeded.
|
|
65
76
|
*/
|
|
66
|
-
export declare function oidcBinding(means: IConnectionConfig, client:
|
|
77
|
+
export declare function oidcBinding(means: IConnectionConfig, client: BoundClient | null): Binding;
|
|
67
78
|
/**
|
|
68
79
|
* `saml2_pure`: the resource, and the ACS of the system that sets the
|
|
69
80
|
* cookies — `samlAcsUrl`, origin and path; both required to match.
|
|
@@ -76,20 +87,31 @@ export declare function samlPureBinding(means: IConnectionConfig): Binding;
|
|
|
76
87
|
* handed no client, so no issuer is claimed for what it obtains: `issuedBy` is
|
|
77
88
|
* then absent, and `getProvider` never seeds from that secret.
|
|
78
89
|
*/
|
|
79
|
-
export declare function consumerBinding(serviceUrl: string, sapClient: string | undefined, client:
|
|
90
|
+
export declare function consumerBinding(serviceUrl: string, sapClient: string | undefined, client: BoundClient | null): Binding;
|
|
91
|
+
/**
|
|
92
|
+
* A binding on the `clientAuthentication` strategy path: a resource that
|
|
93
|
+
* neither the means nor the stored session state matches, so the issuer and
|
|
94
|
+
* client alone decide (`Binding.unstatedResourceMatches`).
|
|
95
|
+
*/
|
|
96
|
+
export declare function strategyBinding(binding: Binding): Binding;
|
|
80
97
|
/**
|
|
81
98
|
* A `none` row: the resource always; the issuer only when the means state one
|
|
82
99
|
* — `oidcIssuerUrl` (with `uaaClientId` when stated) or the client (`uaaUrl`
|
|
83
100
|
* and `uaaClientId`) for `jwt`, `samlAcsUrl` for `saml`.
|
|
84
101
|
*/
|
|
85
|
-
export declare function handedOverBinding(authType: 'jwt' | 'saml', means: IConnectionConfig, client:
|
|
86
|
-
/**
|
|
102
|
+
export declare function handedOverBinding(authType: 'jwt' | 'saml', means: IConnectionConfig, client: BoundClient | null): Binding;
|
|
103
|
+
/**
|
|
104
|
+
* The stored `issuedFor`, canonicalised, equals the destination's — or, with
|
|
105
|
+
* `unstatedResourceMatches`, neither states one.
|
|
106
|
+
*/
|
|
87
107
|
export declare function sameResource(stored: IConfig | null, binding: Binding): boolean;
|
|
88
108
|
/** The stored `issuedBy`, canonicalised as the destination's kind, equals it. */
|
|
89
109
|
export declare function sameIssuer(stored: IConfig | null, binding: Binding): boolean;
|
|
90
110
|
/**
|
|
91
111
|
* An obtained secret is this destination's only when **both** stored values
|
|
92
|
-
* equal the computed ones; either absent, on either side, is not a match
|
|
112
|
+
* equal the computed ones; either absent, on either side, is not a match —
|
|
113
|
+
* save a resource absent on both sides of a strategy-path binding
|
|
114
|
+
* (`unstatedResourceMatches`).
|
|
93
115
|
*/
|
|
94
116
|
export declare function boundHere(stored: IConfig | null, binding: Binding): boolean;
|
|
95
117
|
//# sourceMappingURL=binding.d.ts.map
|
package/dist/binding.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"binding.d.ts","sourceRoot":"","sources":["../src/binding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EACV,OAAO,EACP,iBAAiB,EAClB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"binding.d.ts","sourceRoot":"","sources":["../src/binding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EACV,OAAO,EACP,iBAAiB,EAClB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAE9E,qEAAqE;AACrE,MAAM,MAAM,WAAW,GAAG,IAAI,CAAC,oBAAoB,EAAE,QAAQ,GAAG,aAAa,CAAC,CAAC;AA6C/E;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,UAAU,EAAE,OAAO,EACnB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,GAAG,SAAS,CAEpB;AAED;;;GAGG;AACH,wBAAgB,SAAS,CACvB,SAAS,EAAE,OAAO,EAClB,QAAQ,CAAC,EAAE,MAAM,GAChB,MAAM,GAAG,SAAS,CAEpB;AAED,4EAA4E;AAC5E,wBAAgB,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAE1D;AAED,mFAAmF;AACnF,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,KAAK,CAAC;AAE1C,+FAA+F;AAC/F,MAAM,WAAW,OAAO;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,UAAU,EAAE,UAAU,CAAC;IACvB;;;;OAIG;IACH,YAAY,EAAE,OAAO,CAAC;IACtB;;;;;;;OAOG;IACH,uBAAuB,CAAC,EAAE,IAAI,CAAC;CAChC;AAUD;;;GAGG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,iBAAiB,EACxB,MAAM,EAAE,WAAW,GAAG,IAAI,GACzB,OAAO,CAUT;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,iBAAiB,EACxB,MAAM,EAAE,WAAW,GAAG,IAAI,GACzB,OAAO,CAWT;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAOjE;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,MAAM,EAAE,WAAW,GAAG,IAAI,GACzB,OAAO,CAET;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAEzD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,KAAK,GAAG,MAAM,EACxB,KAAK,EAAE,iBAAiB,EACxB,MAAM,EAAE,WAAW,GAAG,IAAI,GACzB,OAAO,CA4BT;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,OAAO,GAAG,IAAI,EACtB,OAAO,EAAE,OAAO,GACf,OAAO,CAOT;AAED,iFAAiF;AACjF,wBAAgB,UAAU,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAI5E;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAE3E"}
|
package/dist/binding.js
CHANGED
|
@@ -31,6 +31,7 @@ exports.uaaBinding = uaaBinding;
|
|
|
31
31
|
exports.oidcBinding = oidcBinding;
|
|
32
32
|
exports.samlPureBinding = samlPureBinding;
|
|
33
33
|
exports.consumerBinding = consumerBinding;
|
|
34
|
+
exports.strategyBinding = strategyBinding;
|
|
34
35
|
exports.handedOverBinding = handedOverBinding;
|
|
35
36
|
exports.sameResource = sameResource;
|
|
36
37
|
exports.sameIssuer = sameIssuer;
|
|
@@ -152,6 +153,14 @@ function samlPureBinding(means) {
|
|
|
152
153
|
function consumerBinding(serviceUrl, sapClient, client) {
|
|
153
154
|
return uaaBinding({ serviceUrl, sapClient }, client);
|
|
154
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* A binding on the `clientAuthentication` strategy path: a resource that
|
|
158
|
+
* neither the means nor the stored session state matches, so the issuer and
|
|
159
|
+
* client alone decide (`Binding.unstatedResourceMatches`).
|
|
160
|
+
*/
|
|
161
|
+
function strategyBinding(binding) {
|
|
162
|
+
return { ...binding, unstatedResourceMatches: true };
|
|
163
|
+
}
|
|
155
164
|
/**
|
|
156
165
|
* A `none` row: the resource always; the issuer only when the means state one
|
|
157
166
|
* — `oidcIssuerUrl` (with `uaaClientId` when stated) or the client (`uaaUrl`
|
|
@@ -186,10 +195,15 @@ function handedOverBinding(authType, means, client) {
|
|
|
186
195
|
issuerStated: stated,
|
|
187
196
|
};
|
|
188
197
|
}
|
|
189
|
-
/**
|
|
198
|
+
/**
|
|
199
|
+
* The stored `issuedFor`, canonicalised, equals the destination's — or, with
|
|
200
|
+
* `unstatedResourceMatches`, neither states one.
|
|
201
|
+
*/
|
|
190
202
|
function sameResource(stored, binding) {
|
|
191
|
-
|
|
192
|
-
|
|
203
|
+
if (binding.issuedFor === undefined) {
|
|
204
|
+
return (binding.unstatedResourceMatches === true && !present(stored?.issuedFor));
|
|
205
|
+
}
|
|
206
|
+
return resourceUri(stored?.issuedFor) === binding.issuedFor;
|
|
193
207
|
}
|
|
194
208
|
/** The stored `issuedBy`, canonicalised as the destination's kind, equals it. */
|
|
195
209
|
function sameIssuer(stored, binding) {
|
|
@@ -200,7 +214,9 @@ function sameIssuer(stored, binding) {
|
|
|
200
214
|
}
|
|
201
215
|
/**
|
|
202
216
|
* An obtained secret is this destination's only when **both** stored values
|
|
203
|
-
* equal the computed ones; either absent, on either side, is not a match
|
|
217
|
+
* equal the computed ones; either absent, on either side, is not a match —
|
|
218
|
+
* save a resource absent on both sides of a strategy-path binding
|
|
219
|
+
* (`unstatedResourceMatches`).
|
|
204
220
|
*/
|
|
205
221
|
function boundHere(stored, binding) {
|
|
206
222
|
return sameResource(stored, binding) && sameIssuer(stored, binding);
|
package/dist/bindingOf.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { DestinationGrant, IConnectionConfig } from '@mcp-abap-adt/interfaces-auth-broker';
|
|
8
8
|
import type { IAuthorizationConfig } from '@mcp-abap-adt/interfaces-auth-sap';
|
|
9
|
+
import type { BoundClient } from './binding';
|
|
9
10
|
import { type Binding } from './binding';
|
|
10
11
|
/**
|
|
11
12
|
* The binding of a `jwt` or `saml` destination with the grant it states: the
|
|
@@ -13,7 +14,7 @@ import { type Binding } from './binding';
|
|
|
13
14
|
* the issuer and the client, `saml2_pure` by its ACS, and `none` by what the
|
|
14
15
|
* means state.
|
|
15
16
|
*/
|
|
16
|
-
export declare function destinationBinding(authType: 'jwt' | 'saml', grant: DestinationGrant, means: IConnectionConfig, client:
|
|
17
|
+
export declare function destinationBinding(authType: 'jwt' | 'saml', grant: DestinationGrant, means: IConnectionConfig, client: BoundClient | null): Binding;
|
|
17
18
|
/** The two strings a session store keeps beside a secret; absent where the means cannot say. */
|
|
18
19
|
export interface SecretBinding {
|
|
19
20
|
issuedFor?: string;
|
package/dist/bindingOf.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bindingOf.d.ts","sourceRoot":"","sources":["../src/bindingOf.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EAClB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,EACL,KAAK,OAAO,EAKb,MAAM,WAAW,CAAC;AAGnB;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,KAAK,GAAG,MAAM,EACxB,KAAK,EAAE,gBAAgB,EACvB,KAAK,EAAE,iBAAiB,EACxB,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"bindingOf.d.ts","sourceRoot":"","sources":["../src/bindingOf.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EAClB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EACL,KAAK,OAAO,EAKb,MAAM,WAAW,CAAC;AAGnB;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,KAAK,GAAG,MAAM,EACxB,KAAK,EAAE,gBAAgB,EACvB,KAAK,EAAE,iBAAiB,EACxB,MAAM,EAAE,WAAW,GAAG,IAAI,GACzB,OAAO,CAKT;AAED,gGAAgG;AAChG,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,iBAAiB,EACxB,MAAM,GAAE,oBAAoB,GAAG,IAAW,GACzC,aAAa,CAcf"}
|