@mcp-abap-adt/auth-broker 4.0.0 → 5.0.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 +273 -1
- package/README.md +1028 -908
- package/dist/AuthBroker.d.ts +353 -99
- package/dist/AuthBroker.d.ts.map +1 -1
- package/dist/AuthBroker.js +806 -258
- package/dist/DestinationConfigError.d.ts +35 -6
- package/dist/DestinationConfigError.d.ts.map +1 -1
- package/dist/DestinationConfigError.js +51 -9
- package/dist/SessionWriter.d.ts +70 -33
- package/dist/SessionWriter.d.ts.map +1 -1
- package/dist/SessionWriter.js +118 -94
- package/dist/binding.d.ts +137 -64
- package/dist/binding.d.ts.map +1 -1
- package/dist/binding.js +330 -131
- package/dist/bindingOf.d.ts +33 -14
- package/dist/bindingOf.d.ts.map +1 -1
- package/dist/bindingOf.js +50 -20
- package/dist/buildIdentity.d.ts +137 -0
- package/dist/buildIdentity.d.ts.map +1 -0
- package/dist/buildIdentity.js +393 -0
- package/dist/clientAuthentication.d.ts +124 -0
- package/dist/clientAuthentication.d.ts.map +1 -0
- package/dist/clientAuthentication.js +218 -0
- package/dist/contractShape.d.ts +24 -0
- package/dist/contractShape.d.ts.map +1 -0
- package/dist/contractShape.js +24 -0
- package/dist/destinations.d.ts +174 -53
- package/dist/destinations.d.ts.map +1 -1
- package/dist/destinations.js +460 -150
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/quietLogger.d.ts +24 -0
- package/dist/quietLogger.d.ts.map +1 -0
- package/dist/quietLogger.js +63 -0
- package/package.json +9 -7
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a destination's client authenticates to the authorization server — the
|
|
3
|
+
* consumer's choice, as a strategy (`AuthBrokerConfig.clientAuthentication`).
|
|
4
|
+
*
|
|
5
|
+
* The broker ships two factories, each failing closed: one answers the
|
|
6
|
+
* certificate the key store holds, one the secret. Neither falls back to the
|
|
7
|
+
* other; a consumer that wants a fallback composes it, and its order is the
|
|
8
|
+
* consumer's statement. Whatever a strategy throws, the broker turns into a
|
|
9
|
+
* `DestinationConfigError` in fixed words (`resolveClientAuthentication`),
|
|
10
|
+
* carrying the thrown value as auth-errors reads it — never its message, no
|
|
11
|
+
* `cause`.
|
|
12
|
+
*/
|
|
13
|
+
import type { IClientAuthentication } from '@mcp-abap-adt/interfaces-auth';
|
|
14
|
+
import type { IClientCertificate } from '@mcp-abap-adt/interfaces-auth-broker';
|
|
15
|
+
import type { IAuthorizationConfig } from '@mcp-abap-adt/interfaces-auth-sap';
|
|
16
|
+
/**
|
|
17
|
+
* Every grant whose client authenticates to the authorization server: the
|
|
18
|
+
* three UAA grants, the OIDC grants and `saml2_bearer`. (`saml2_pure` and the
|
|
19
|
+
* basic, SNC and handed-over rows authenticate no client and never call the
|
|
20
|
+
* strategy.)
|
|
21
|
+
*/
|
|
22
|
+
export type ClientAuthenticationGrant = 'client_credentials' | 'authorization_code' | 'passcode' | 'oidc_authorization_code' | 'device_code' | 'password' | 'token_exchange' | 'saml2_bearer';
|
|
23
|
+
/** What a strategy is given, once per build of a destination's provider. */
|
|
24
|
+
export interface ClientAuthenticationContext {
|
|
25
|
+
readonly destination: string;
|
|
26
|
+
readonly grant: ClientAuthenticationGrant;
|
|
27
|
+
/**
|
|
28
|
+
* The secret client the key store's `getAuthorizationConfig` answered —
|
|
29
|
+
* `uaaUrl`, `uaaClientId`, `uaaClientSecret` only, never a refresh token —
|
|
30
|
+
* or null.
|
|
31
|
+
*/
|
|
32
|
+
readonly client: IAuthorizationConfig | null;
|
|
33
|
+
/**
|
|
34
|
+
* The key store's certificate client, read only when called and at most
|
|
35
|
+
* once per build; `null` when the store has none or implements no
|
|
36
|
+
* `getClientCertificate`.
|
|
37
|
+
*/
|
|
38
|
+
readCertificate(): Promise<IClientCertificate | null>;
|
|
39
|
+
/**
|
|
40
|
+
* The build's attempt: aborts when every caller waiting on the build has
|
|
41
|
+
* gone. A strategy that waits on the network — a loader's
|
|
42
|
+
* `tlsMaterial()` — ends its wait on it; the broker sets no bound of its
|
|
43
|
+
* own.
|
|
44
|
+
*/
|
|
45
|
+
readonly signal: AbortSignal;
|
|
46
|
+
}
|
|
47
|
+
/** The strategy `AuthBrokerConfig.clientAuthentication` takes. */
|
|
48
|
+
export type ClientAuthenticationStrategy = (context: ClientAuthenticationContext) => Promise<IClientAuthentication>;
|
|
49
|
+
/**
|
|
50
|
+
* `tls_client_auth` with the certificate and key the key store holds, against
|
|
51
|
+
* `${certUrl}/oauth/token`. The material is handed over as given and checked
|
|
52
|
+
* before the factory answers — auth-providers checks it only on first use, so
|
|
53
|
+
* a malformed, incomplete or expired PEM would otherwise surface only at the
|
|
54
|
+
* first token request, in a provider already built and cached. What
|
|
55
|
+
* `tlsMaterial()` throws — auth-providers' `client-certificate` failure — is
|
|
56
|
+
* carried by the guard as auth-errors reads it.
|
|
57
|
+
*
|
|
58
|
+
* Throws "the destination has no client certificate" when the store answers
|
|
59
|
+
* `null` or implements no `getClientCertificate`.
|
|
60
|
+
*/
|
|
61
|
+
export declare function fromServiceKeyCertificate(): ClientAuthenticationStrategy;
|
|
62
|
+
/** How `fromServiceKeySecret` writes the client id and secret. */
|
|
63
|
+
export interface FromServiceKeySecretOptions {
|
|
64
|
+
/**
|
|
65
|
+
* `'raw'`: `base64(id:secret)` as given — XSUAA accepts only this
|
|
66
|
+
* (measured). `'form'`: each component form-encoded first (RFC 6749
|
|
67
|
+
* §2.3.1) — UAA, Keycloak. Required: it depends on the server.
|
|
68
|
+
*/
|
|
69
|
+
readonly encoding: 'raw' | 'form';
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* `client_secret_basic` with the secret client the key store holds.
|
|
73
|
+
*
|
|
74
|
+
* Throws "the destination has no client secret" when the store answers no
|
|
75
|
+
* secret client, or one with an empty secret. A missing or unknown `encoding`
|
|
76
|
+
* is refused when the factory is made.
|
|
77
|
+
*/
|
|
78
|
+
export declare function fromServiceKeySecret(options: FromServiceKeySecretOptions): ClientAuthenticationStrategy;
|
|
79
|
+
/**
|
|
80
|
+
* What a strategy is told of the secret client: an allowlist — who it is
|
|
81
|
+
* (`uaaUrl`, `uaaClientId`) and its secret (`uaaClientSecret`, which
|
|
82
|
+
* `fromServiceKeySecret` sends) — never a refresh token or any other field a
|
|
83
|
+
* store's `getAuthorizationConfig` carried, which no binding has checked.
|
|
84
|
+
*/
|
|
85
|
+
export declare function contextClient(client: IAuthorizationConfig | null): IAuthorizationConfig | null;
|
|
86
|
+
/**
|
|
87
|
+
* The context for one build: the client through `contextClient`;
|
|
88
|
+
* `readCertificate` lazy and memoised — the first call reads, every later one
|
|
89
|
+
* gets the same answer; `signal`, the build's attempt.
|
|
90
|
+
*/
|
|
91
|
+
export declare function clientAuthenticationContext(destination: string, grant: ClientAuthenticationGrant, client: IAuthorizationConfig | null, read: () => Promise<IClientCertificate | null>, signal: AbortSignal): ClientAuthenticationContext;
|
|
92
|
+
/**
|
|
93
|
+
* Runs the strategy inside the guard. Any throw — the strategy's own, a
|
|
94
|
+
* store's, a malformed PEM — and any answer that is no client authentication
|
|
95
|
+
* becomes a `DestinationConfigError` naming `clientAuthentication`, in fixed
|
|
96
|
+
* words. A shipped factory's own refusal keeps the broker's words; anything
|
|
97
|
+
* else is "the clientAuthentication strategy failed", carrying
|
|
98
|
+
* `readFailure(thrown, 'client-authentication-strategy')` — a provider's
|
|
99
|
+
* failure (an unusable certificate) as the provider made it, a strategy's own
|
|
100
|
+
* error only as its classification. No `cause`, no message of the thrown
|
|
101
|
+
* value.
|
|
102
|
+
*/
|
|
103
|
+
export declare function resolveClientAuthentication(strategy: ClientAuthenticationStrategy, context: ClientAuthenticationContext): Promise<IClientAuthentication>;
|
|
104
|
+
/**
|
|
105
|
+
* Who the client is — its authorization server and client id — and nothing
|
|
106
|
+
* else: no secret field, so it can never be taken for a (public) secret
|
|
107
|
+
* client, and never PEM.
|
|
108
|
+
*/
|
|
109
|
+
export interface ClientIdentity {
|
|
110
|
+
readonly uaaUrl: string;
|
|
111
|
+
readonly uaaClientId: string;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The client identity a strategy-authenticated row and its binding take: the
|
|
115
|
+
* secret client's `uaaUrl` and `uaaClientId` when the key store has one, else
|
|
116
|
+
* the certificate client's `uaaUrl` and `clientId` — read through the
|
|
117
|
+
* context's memoised `readCertificate`, so at most once per build — else
|
|
118
|
+
* `null`. The secret stays behind: beside a strategy a row passes none.
|
|
119
|
+
*
|
|
120
|
+
* A failing read is a `DestinationConfigError` naming `clientAuthentication`
|
|
121
|
+
* in fixed words — nothing of what the store threw, no `cause`.
|
|
122
|
+
*/
|
|
123
|
+
export declare function clientIdentity(context: ClientAuthenticationContext): Promise<ClientIdentity | null>;
|
|
124
|
+
//# sourceMappingURL=clientAuthentication.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clientAuthentication.d.ts","sourceRoot":"","sources":["../src/clientAuthentication.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAOH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAC/E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAG9E;;;;;GAKG;AACH,MAAM,MAAM,yBAAyB,GACjC,oBAAoB,GACpB,oBAAoB,GACpB,UAAU,GACV,yBAAyB,GACzB,aAAa,GACb,UAAU,GACV,gBAAgB,GAChB,cAAc,CAAC;AAEnB,4EAA4E;AAC5E,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,yBAAyB,CAAC;IAC1C;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC7C;;;;OAIG;IACH,eAAe,IAAI,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC;IACtD;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;CAC9B;AAED,kEAAkE;AAClE,MAAM,MAAM,4BAA4B,GAAG,CACzC,OAAO,EAAE,2BAA2B,KACjC,OAAO,CAAC,qBAAqB,CAAC,CAAC;AAgCpC;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,IAAI,4BAA4B,CAgBxE;AAED,kEAAkE;AAClE,MAAM,WAAW,2BAA2B;IAC1C;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,KAAK,GAAG,MAAM,CAAC;CACnC;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,2BAA2B,GACnC,4BAA4B,CAc9B;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,oBAAoB,GAAG,IAAI,GAClC,oBAAoB,GAAG,IAAI,CAO7B;AAmBD;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,yBAAyB,EAChC,MAAM,EAAE,oBAAoB,GAAG,IAAI,EACnC,IAAI,EAAE,MAAM,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,EAC9C,MAAM,EAAE,WAAW,GAClB,2BAA2B,CAY7B;AAcD;;;;;;;;;;GAUG;AACH,wBAAsB,2BAA2B,CAC/C,QAAQ,EAAE,4BAA4B,EACtC,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,qBAAqB,CAAC,CAkChC;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAiBhC"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* How a destination's client authenticates to the authorization server — the
|
|
4
|
+
* consumer's choice, as a strategy (`AuthBrokerConfig.clientAuthentication`).
|
|
5
|
+
*
|
|
6
|
+
* The broker ships two factories, each failing closed: one answers the
|
|
7
|
+
* certificate the key store holds, one the secret. Neither falls back to the
|
|
8
|
+
* other; a consumer that wants a fallback composes it, and its order is the
|
|
9
|
+
* consumer's statement. Whatever a strategy throws, the broker turns into a
|
|
10
|
+
* `DestinationConfigError` in fixed words (`resolveClientAuthentication`),
|
|
11
|
+
* carrying the thrown value as auth-errors reads it — never its message, no
|
|
12
|
+
* `cause`.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.fromServiceKeyCertificate = fromServiceKeyCertificate;
|
|
16
|
+
exports.fromServiceKeySecret = fromServiceKeySecret;
|
|
17
|
+
exports.contextClient = contextClient;
|
|
18
|
+
exports.clientAuthenticationContext = clientAuthenticationContext;
|
|
19
|
+
exports.resolveClientAuthentication = resolveClientAuthentication;
|
|
20
|
+
exports.clientIdentity = clientIdentity;
|
|
21
|
+
const auth_errors_1 = require("@mcp-abap-adt/auth-errors");
|
|
22
|
+
const auth_providers_1 = require("@mcp-abap-adt/auth-providers");
|
|
23
|
+
const DestinationConfigError_1 = require("./DestinationConfigError");
|
|
24
|
+
const NO_CERTIFICATE = 'the destination has no client certificate';
|
|
25
|
+
const NO_SECRET = 'the destination has no client secret';
|
|
26
|
+
/**
|
|
27
|
+
* The shipped factories' refusals, and what each lacks: recognised by
|
|
28
|
+
* identity in this module-private map — never by `instanceof` or a property
|
|
29
|
+
* of the thrown value — so nothing outside this file can make one, and a
|
|
30
|
+
* consumer that catches and alters one changes nothing the guard reads.
|
|
31
|
+
*/
|
|
32
|
+
const unavailable = new WeakMap();
|
|
33
|
+
function clientUnavailable(missing) {
|
|
34
|
+
const error = new Error(missing === 'certificate' ? NO_CERTIFICATE : NO_SECRET);
|
|
35
|
+
unavailable.set(error, missing);
|
|
36
|
+
return error;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* `value` without the slashes it ends with, in one backwards pass: linear in
|
|
40
|
+
* the length, whatever the store answered — no regular expression on a
|
|
41
|
+
* stored value.
|
|
42
|
+
*/
|
|
43
|
+
function withoutTrailingSlashes(value) {
|
|
44
|
+
let end = value.length;
|
|
45
|
+
while (end > 0 && value.charCodeAt(end - 1) === 0x2f)
|
|
46
|
+
end -= 1;
|
|
47
|
+
return value.slice(0, end);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* `tls_client_auth` with the certificate and key the key store holds, against
|
|
51
|
+
* `${certUrl}/oauth/token`. The material is handed over as given and checked
|
|
52
|
+
* before the factory answers — auth-providers checks it only on first use, so
|
|
53
|
+
* a malformed, incomplete or expired PEM would otherwise surface only at the
|
|
54
|
+
* first token request, in a provider already built and cached. What
|
|
55
|
+
* `tlsMaterial()` throws — auth-providers' `client-certificate` failure — is
|
|
56
|
+
* carried by the guard as auth-errors reads it.
|
|
57
|
+
*
|
|
58
|
+
* Throws "the destination has no client certificate" when the store answers
|
|
59
|
+
* `null` or implements no `getClientCertificate`.
|
|
60
|
+
*/
|
|
61
|
+
function fromServiceKeyCertificate() {
|
|
62
|
+
return async (context) => {
|
|
63
|
+
const certificate = await context.readCertificate();
|
|
64
|
+
if (!certificate) {
|
|
65
|
+
throw clientUnavailable('certificate');
|
|
66
|
+
}
|
|
67
|
+
const authentication = (0, auth_providers_1.tlsClientCertificate)({
|
|
68
|
+
material: { cert: certificate.certificate, key: certificate.key },
|
|
69
|
+
endpoint: `${withoutTrailingSlashes(certificate.certUrl)}/oauth/token`,
|
|
70
|
+
});
|
|
71
|
+
if (typeof authentication.tlsMaterial !== 'function') {
|
|
72
|
+
throw clientUnavailable('certificate');
|
|
73
|
+
}
|
|
74
|
+
await authentication.tlsMaterial();
|
|
75
|
+
return authentication;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* `client_secret_basic` with the secret client the key store holds.
|
|
80
|
+
*
|
|
81
|
+
* Throws "the destination has no client secret" when the store answers no
|
|
82
|
+
* secret client, or one with an empty secret. A missing or unknown `encoding`
|
|
83
|
+
* is refused when the factory is made.
|
|
84
|
+
*/
|
|
85
|
+
function fromServiceKeySecret(options) {
|
|
86
|
+
const encoding = options?.encoding;
|
|
87
|
+
if (encoding !== 'raw' && encoding !== 'form') {
|
|
88
|
+
throw new TypeError("fromServiceKeySecret: encoding must be 'raw' or 'form'");
|
|
89
|
+
}
|
|
90
|
+
return async (context) => {
|
|
91
|
+
const secret = context.client?.uaaClientSecret;
|
|
92
|
+
if (typeof secret !== 'string' || secret.length === 0) {
|
|
93
|
+
throw clientUnavailable('secret');
|
|
94
|
+
}
|
|
95
|
+
return (0, auth_providers_1.clientSecretBasic)(secret, { encoding });
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* What a strategy is told of the secret client: an allowlist — who it is
|
|
100
|
+
* (`uaaUrl`, `uaaClientId`) and its secret (`uaaClientSecret`, which
|
|
101
|
+
* `fromServiceKeySecret` sends) — never a refresh token or any other field a
|
|
102
|
+
* store's `getAuthorizationConfig` carried, which no binding has checked.
|
|
103
|
+
*/
|
|
104
|
+
function contextClient(client) {
|
|
105
|
+
if (!client)
|
|
106
|
+
return null;
|
|
107
|
+
return {
|
|
108
|
+
uaaUrl: client.uaaUrl,
|
|
109
|
+
uaaClientId: client.uaaClientId,
|
|
110
|
+
uaaClientSecret: client.uaaClientSecret,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The certificate client as the contract declares it — `uaaUrl`, `clientId`,
|
|
115
|
+
* `certificate`, `key`, `certUrl` — and nothing else a store answered with it.
|
|
116
|
+
*/
|
|
117
|
+
function certificateOf(answer) {
|
|
118
|
+
if (!answer)
|
|
119
|
+
return null;
|
|
120
|
+
return {
|
|
121
|
+
uaaUrl: answer.uaaUrl,
|
|
122
|
+
clientId: answer.clientId,
|
|
123
|
+
certificate: answer.certificate,
|
|
124
|
+
key: answer.key,
|
|
125
|
+
certUrl: answer.certUrl,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* The context for one build: the client through `contextClient`;
|
|
130
|
+
* `readCertificate` lazy and memoised — the first call reads, every later one
|
|
131
|
+
* gets the same answer; `signal`, the build's attempt.
|
|
132
|
+
*/
|
|
133
|
+
function clientAuthenticationContext(destination, grant, client, read, signal) {
|
|
134
|
+
let certificate;
|
|
135
|
+
return {
|
|
136
|
+
destination,
|
|
137
|
+
grant,
|
|
138
|
+
client: contextClient(client),
|
|
139
|
+
readCertificate: () => {
|
|
140
|
+
certificate ??= read().then(certificateOf);
|
|
141
|
+
return certificate;
|
|
142
|
+
},
|
|
143
|
+
signal,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const FAILED = 'the clientAuthentication strategy failed';
|
|
147
|
+
const REFUSED = 'the clientAuthentication strategy refused';
|
|
148
|
+
/** A shipped factory's refusal: the broker's own words, by what it lacks. */
|
|
149
|
+
function unavailableWords(error) {
|
|
150
|
+
if (typeof error !== 'object' || error === null)
|
|
151
|
+
return undefined;
|
|
152
|
+
const missing = unavailable.get(error);
|
|
153
|
+
if (missing === 'certificate')
|
|
154
|
+
return `${REFUSED}: ${NO_CERTIFICATE}`;
|
|
155
|
+
if (missing === 'secret')
|
|
156
|
+
return `${REFUSED}: ${NO_SECRET}`;
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Runs the strategy inside the guard. Any throw — the strategy's own, a
|
|
161
|
+
* store's, a malformed PEM — and any answer that is no client authentication
|
|
162
|
+
* becomes a `DestinationConfigError` naming `clientAuthentication`, in fixed
|
|
163
|
+
* words. A shipped factory's own refusal keeps the broker's words; anything
|
|
164
|
+
* else is "the clientAuthentication strategy failed", carrying
|
|
165
|
+
* `readFailure(thrown, 'client-authentication-strategy')` — a provider's
|
|
166
|
+
* failure (an unusable certificate) as the provider made it, a strategy's own
|
|
167
|
+
* error only as its classification. No `cause`, no message of the thrown
|
|
168
|
+
* value.
|
|
169
|
+
*/
|
|
170
|
+
async function resolveClientAuthentication(strategy, context) {
|
|
171
|
+
let answer;
|
|
172
|
+
let usable;
|
|
173
|
+
try {
|
|
174
|
+
answer = await strategy(context);
|
|
175
|
+
// Read inside the guard: a getter on the answer may throw too.
|
|
176
|
+
usable =
|
|
177
|
+
typeof answer === 'object' &&
|
|
178
|
+
answer !== null &&
|
|
179
|
+
typeof answer.authenticate === 'function';
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
const words = unavailableWords(error);
|
|
183
|
+
if (words !== undefined) {
|
|
184
|
+
throw new DestinationConfigError_1.DestinationConfigError(context.destination, ['clientAuthentication'], words);
|
|
185
|
+
}
|
|
186
|
+
throw new DestinationConfigError_1.DestinationConfigError(context.destination, ['clientAuthentication'], FAILED, (0, auth_errors_1.readFailure)(error, 'client-authentication-strategy'));
|
|
187
|
+
}
|
|
188
|
+
if (!usable) {
|
|
189
|
+
throw new DestinationConfigError_1.DestinationConfigError(context.destination, ['clientAuthentication'], 'the clientAuthentication strategy answered no client authentication');
|
|
190
|
+
}
|
|
191
|
+
return answer;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* The client identity a strategy-authenticated row and its binding take: the
|
|
195
|
+
* secret client's `uaaUrl` and `uaaClientId` when the key store has one, else
|
|
196
|
+
* the certificate client's `uaaUrl` and `clientId` — read through the
|
|
197
|
+
* context's memoised `readCertificate`, so at most once per build — else
|
|
198
|
+
* `null`. The secret stays behind: beside a strategy a row passes none.
|
|
199
|
+
*
|
|
200
|
+
* A failing read is a `DestinationConfigError` naming `clientAuthentication`
|
|
201
|
+
* in fixed words — nothing of what the store threw, no `cause`.
|
|
202
|
+
*/
|
|
203
|
+
async function clientIdentity(context) {
|
|
204
|
+
const client = context.client;
|
|
205
|
+
if (client) {
|
|
206
|
+
return { uaaUrl: client.uaaUrl, uaaClientId: client.uaaClientId };
|
|
207
|
+
}
|
|
208
|
+
let certificate;
|
|
209
|
+
try {
|
|
210
|
+
certificate = await context.readCertificate();
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
throw new DestinationConfigError_1.DestinationConfigError(context.destination, ['clientAuthentication'], 'the client certificate could not be read');
|
|
214
|
+
}
|
|
215
|
+
if (!certificate)
|
|
216
|
+
return null;
|
|
217
|
+
return { uaaUrl: certificate.uaaUrl, uaaClientId: certificate.clientId };
|
|
218
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shape of a contract object as this package has always built it.
|
|
3
|
+
*
|
|
4
|
+
* The contract packages (`@mcp-abap-adt/interfaces-auth`, `-auth-broker`,
|
|
5
|
+
* `-auth-sap`) and `@mcp-abap-adt/auth-providers` declare their optional
|
|
6
|
+
* fields `?: T`, which under `exactOptionalPropertyTypes` means "absent, never
|
|
7
|
+
* `undefined`". The broker builds those objects — a provider's configuration,
|
|
8
|
+
* the session it writes, the client it hands a consumer factory — with an
|
|
9
|
+
* optional field that has no value present and set to `undefined`
|
|
10
|
+
* (`refreshToken: undefined`), and a store, a provider or a consumer may
|
|
11
|
+
* depend on the key being there (`'refreshToken' in config`, `Object.keys`,
|
|
12
|
+
* `{ ...stored, ...secret }`). The key's presence is behaviour, kept as it was.
|
|
13
|
+
*/
|
|
14
|
+
/** `T` whose optional fields may also hold an explicit `undefined`. */
|
|
15
|
+
export type WithUndefined<T> = {
|
|
16
|
+
[K in keyof T]: T[K] | (undefined extends T[K] ? undefined : never);
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* The contract type of an object built with explicit `undefined`s. The one
|
|
20
|
+
* assertion between the two shapes: every field still has the contract's
|
|
21
|
+
* type, and no field is added or dropped.
|
|
22
|
+
*/
|
|
23
|
+
export declare function asContract<T>(value: WithUndefined<T>): T;
|
|
24
|
+
//# sourceMappingURL=contractShape.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contractShape.d.ts","sourceRoot":"","sources":["../src/contractShape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,uEAAuE;AACvE,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI;KAC5B,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,SAAS,GAAG,KAAK,CAAC;CACpE,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,CAExD"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The shape of a contract object as this package has always built it.
|
|
4
|
+
*
|
|
5
|
+
* The contract packages (`@mcp-abap-adt/interfaces-auth`, `-auth-broker`,
|
|
6
|
+
* `-auth-sap`) and `@mcp-abap-adt/auth-providers` declare their optional
|
|
7
|
+
* fields `?: T`, which under `exactOptionalPropertyTypes` means "absent, never
|
|
8
|
+
* `undefined`". The broker builds those objects — a provider's configuration,
|
|
9
|
+
* the session it writes, the client it hands a consumer factory — with an
|
|
10
|
+
* optional field that has no value present and set to `undefined`
|
|
11
|
+
* (`refreshToken: undefined`), and a store, a provider or a consumer may
|
|
12
|
+
* depend on the key being there (`'refreshToken' in config`, `Object.keys`,
|
|
13
|
+
* `{ ...stored, ...secret }`). The key's presence is behaviour, kept as it was.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.asContract = asContract;
|
|
17
|
+
/**
|
|
18
|
+
* The contract type of an object built with explicit `undefined`s. The one
|
|
19
|
+
* assertion between the two shapes: every field still has the contract's
|
|
20
|
+
* type, and no field is added or dropped.
|
|
21
|
+
*/
|
|
22
|
+
function asContract(value) {
|
|
23
|
+
return value;
|
|
24
|
+
}
|