@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
|
@@ -0,0 +1,109 @@
|
|
|
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
|
+
* nothing of the thrown value, no `cause`.
|
|
11
|
+
*/
|
|
12
|
+
import type { IClientAuthentication } from '@mcp-abap-adt/interfaces-auth';
|
|
13
|
+
import type { IClientCertificate } from '@mcp-abap-adt/interfaces-auth-broker';
|
|
14
|
+
import type { IAuthorizationConfig } from '@mcp-abap-adt/interfaces-auth-sap';
|
|
15
|
+
/**
|
|
16
|
+
* Every grant whose client authenticates to the authorization server: the
|
|
17
|
+
* three UAA grants, the OIDC grants and `saml2_bearer`. (`saml2_pure` and the
|
|
18
|
+
* basic, SNC and handed-over rows authenticate no client and never call the
|
|
19
|
+
* strategy.)
|
|
20
|
+
*/
|
|
21
|
+
export type ClientAuthenticationGrant = 'client_credentials' | 'authorization_code' | 'passcode' | 'oidc_authorization_code' | 'device_code' | 'password' | 'token_exchange' | 'saml2_bearer';
|
|
22
|
+
/** What a strategy is given, once per build of a destination's provider. */
|
|
23
|
+
export interface ClientAuthenticationContext {
|
|
24
|
+
readonly destination: string;
|
|
25
|
+
readonly grant: ClientAuthenticationGrant;
|
|
26
|
+
/**
|
|
27
|
+
* The secret client the key store's `getAuthorizationConfig` answered —
|
|
28
|
+
* `uaaUrl`, `uaaClientId`, `uaaClientSecret` only, never a refresh token —
|
|
29
|
+
* or null.
|
|
30
|
+
*/
|
|
31
|
+
readonly client: IAuthorizationConfig | null;
|
|
32
|
+
/**
|
|
33
|
+
* The key store's certificate client, read only when called and at most
|
|
34
|
+
* once per build; `null` when the store has none or implements no
|
|
35
|
+
* `getClientCertificate`.
|
|
36
|
+
*/
|
|
37
|
+
readCertificate(): Promise<IClientCertificate | null>;
|
|
38
|
+
}
|
|
39
|
+
/** The strategy `AuthBrokerConfig.clientAuthentication` takes. */
|
|
40
|
+
export type ClientAuthenticationStrategy = (context: ClientAuthenticationContext) => Promise<IClientAuthentication>;
|
|
41
|
+
/**
|
|
42
|
+
* `tls_client_auth` with the certificate and key the key store holds, against
|
|
43
|
+
* `${certUrl}/oauth/token`. The material is handed over as given and checked
|
|
44
|
+
* before the factory answers — auth-providers checks it only on first use, so
|
|
45
|
+
* a malformed, incomplete or expired PEM would otherwise surface only at the
|
|
46
|
+
* first token request, in a provider already built and cached.
|
|
47
|
+
*
|
|
48
|
+
* Throws "the destination has no client certificate" when the store answers
|
|
49
|
+
* `null` or implements no `getClientCertificate`.
|
|
50
|
+
*/
|
|
51
|
+
export declare function fromServiceKeyCertificate(): ClientAuthenticationStrategy;
|
|
52
|
+
/** How `fromServiceKeySecret` writes the client id and secret. */
|
|
53
|
+
export interface FromServiceKeySecretOptions {
|
|
54
|
+
/**
|
|
55
|
+
* `'raw'`: `base64(id:secret)` as given — XSUAA accepts only this
|
|
56
|
+
* (measured). `'form'`: each component form-encoded first (RFC 6749
|
|
57
|
+
* §2.3.1) — UAA, Keycloak. Required: it depends on the server.
|
|
58
|
+
*/
|
|
59
|
+
readonly encoding: 'raw' | 'form';
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* `client_secret_basic` with the secret client the key store holds.
|
|
63
|
+
*
|
|
64
|
+
* Throws "the destination has no client secret" when the store answers no
|
|
65
|
+
* secret client, or one with an empty secret. A missing or unknown `encoding`
|
|
66
|
+
* is refused when the factory is made.
|
|
67
|
+
*/
|
|
68
|
+
export declare function fromServiceKeySecret(options: FromServiceKeySecretOptions): ClientAuthenticationStrategy;
|
|
69
|
+
/**
|
|
70
|
+
* What a strategy is told of the secret client: an allowlist — who it is
|
|
71
|
+
* (`uaaUrl`, `uaaClientId`) and its secret (`uaaClientSecret`, which
|
|
72
|
+
* `fromServiceKeySecret` sends) — never a refresh token or any other field a
|
|
73
|
+
* store's `getAuthorizationConfig` carried, which no binding has checked.
|
|
74
|
+
*/
|
|
75
|
+
export declare function contextClient(client: IAuthorizationConfig | null): IAuthorizationConfig | null;
|
|
76
|
+
/**
|
|
77
|
+
* The context for one build: the client through `contextClient`;
|
|
78
|
+
* `readCertificate` lazy and memoised — the first call reads, every later one
|
|
79
|
+
* gets the same answer.
|
|
80
|
+
*/
|
|
81
|
+
export declare function clientAuthenticationContext(destination: string, grant: ClientAuthenticationGrant, client: IAuthorizationConfig | null, read: () => Promise<IClientCertificate | null>): ClientAuthenticationContext;
|
|
82
|
+
/**
|
|
83
|
+
* Runs the strategy inside the guard. Any throw — the strategy's own, a
|
|
84
|
+
* store's, a malformed PEM — and any answer that is no client authentication
|
|
85
|
+
* becomes a `DestinationConfigError` naming `clientAuthentication`, in fixed
|
|
86
|
+
* words: no `cause`, nothing of the thrown value.
|
|
87
|
+
*/
|
|
88
|
+
export declare function resolveClientAuthentication(strategy: ClientAuthenticationStrategy, context: ClientAuthenticationContext): Promise<IClientAuthentication>;
|
|
89
|
+
/**
|
|
90
|
+
* Who the client is — its authorization server and client id — and nothing
|
|
91
|
+
* else: no secret field, so it can never be taken for a (public) secret
|
|
92
|
+
* client, and never PEM.
|
|
93
|
+
*/
|
|
94
|
+
export interface ClientIdentity {
|
|
95
|
+
readonly uaaUrl: string;
|
|
96
|
+
readonly uaaClientId: string;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The client identity a strategy-authenticated row and its binding take: the
|
|
100
|
+
* secret client's `uaaUrl` and `uaaClientId` when the key store has one, else
|
|
101
|
+
* the certificate client's `uaaUrl` and `clientId` — read through the
|
|
102
|
+
* context's memoised `readCertificate`, so at most once per build — else
|
|
103
|
+
* `null`. The secret stays behind: beside a strategy a row passes none.
|
|
104
|
+
*
|
|
105
|
+
* A failing read is a `DestinationConfigError` naming `clientAuthentication`
|
|
106
|
+
* in fixed words — nothing of what the store threw, no `cause`.
|
|
107
|
+
*/
|
|
108
|
+
export declare function clientIdentity(context: ClientAuthenticationContext): Promise<ClientIdentity | null>;
|
|
109
|
+
//# sourceMappingURL=clientAuthentication.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clientAuthentication.d.ts","sourceRoot":"","sources":["../src/clientAuthentication.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;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;CACvD;AAED,kEAAkE;AAClE,MAAM,MAAM,4BAA4B,GAAG,CACzC,OAAO,EAAE,2BAA2B,KACjC,OAAO,CAAC,qBAAqB,CAAC,CAAC;AAsBpC;;;;;;;;;GASG;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,GAC7C,2BAA2B,CAW7B;AAiCD;;;;;GAKG;AACH,wBAAsB,2BAA2B,CAC/C,QAAQ,EAAE,4BAA4B,EACtC,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,qBAAqB,CAAC,CAyBhC;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
|
+
* nothing of the thrown value, no `cause`.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.fromServiceKeyCertificate = fromServiceKeyCertificate;
|
|
15
|
+
exports.fromServiceKeySecret = fromServiceKeySecret;
|
|
16
|
+
exports.contextClient = contextClient;
|
|
17
|
+
exports.clientAuthenticationContext = clientAuthenticationContext;
|
|
18
|
+
exports.resolveClientAuthentication = resolveClientAuthentication;
|
|
19
|
+
exports.clientIdentity = clientIdentity;
|
|
20
|
+
const auth_providers_1 = require("@mcp-abap-adt/auth-providers");
|
|
21
|
+
const DestinationConfigError_1 = require("./DestinationConfigError");
|
|
22
|
+
const NO_CERTIFICATE = 'the destination has no client certificate';
|
|
23
|
+
const NO_SECRET = 'the destination has no client secret';
|
|
24
|
+
/**
|
|
25
|
+
* A shipped factory's refusal. Module-private, so nothing outside this file
|
|
26
|
+
* can make one — but a consumer composing a factory can catch and alter it,
|
|
27
|
+
* so the guard reads only `missing` and picks the words itself.
|
|
28
|
+
*/
|
|
29
|
+
class ClientUnavailableError extends Error {
|
|
30
|
+
missing;
|
|
31
|
+
constructor(missing) {
|
|
32
|
+
super(missing === 'certificate' ? NO_CERTIFICATE : NO_SECRET);
|
|
33
|
+
this.missing = missing;
|
|
34
|
+
this.name = 'ClientUnavailableError';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** The broker's words for auth-providers' CertificateMaterialError, by its flags. */
|
|
38
|
+
const CERTIFICATE_INCOMPLETE = 'the client certificate is incomplete';
|
|
39
|
+
const CERTIFICATE_EXPIRED = 'the client certificate has expired';
|
|
40
|
+
const CERTIFICATE_UNUSABLE = 'the client certificate could not be used';
|
|
41
|
+
/**
|
|
42
|
+
* `tls_client_auth` with the certificate and key the key store holds, against
|
|
43
|
+
* `${certUrl}/oauth/token`. The material is handed over as given and checked
|
|
44
|
+
* before the factory answers — auth-providers checks it only on first use, so
|
|
45
|
+
* a malformed, incomplete or expired PEM would otherwise surface only at the
|
|
46
|
+
* first token request, in a provider already built and cached.
|
|
47
|
+
*
|
|
48
|
+
* Throws "the destination has no client certificate" when the store answers
|
|
49
|
+
* `null` or implements no `getClientCertificate`.
|
|
50
|
+
*/
|
|
51
|
+
function fromServiceKeyCertificate() {
|
|
52
|
+
return async (context) => {
|
|
53
|
+
const certificate = await context.readCertificate();
|
|
54
|
+
if (!certificate) {
|
|
55
|
+
throw new ClientUnavailableError('certificate');
|
|
56
|
+
}
|
|
57
|
+
const authentication = (0, auth_providers_1.tlsClientCertificate)({
|
|
58
|
+
material: { cert: certificate.certificate, key: certificate.key },
|
|
59
|
+
endpoint: `${certificate.certUrl.replace(/\/+$/, '')}/oauth/token`,
|
|
60
|
+
});
|
|
61
|
+
if (typeof authentication.tlsMaterial !== 'function') {
|
|
62
|
+
throw new ClientUnavailableError('certificate');
|
|
63
|
+
}
|
|
64
|
+
await authentication.tlsMaterial();
|
|
65
|
+
return authentication;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* `client_secret_basic` with the secret client the key store holds.
|
|
70
|
+
*
|
|
71
|
+
* Throws "the destination has no client secret" when the store answers no
|
|
72
|
+
* secret client, or one with an empty secret. A missing or unknown `encoding`
|
|
73
|
+
* is refused when the factory is made.
|
|
74
|
+
*/
|
|
75
|
+
function fromServiceKeySecret(options) {
|
|
76
|
+
const encoding = options?.encoding;
|
|
77
|
+
if (encoding !== 'raw' && encoding !== 'form') {
|
|
78
|
+
throw new TypeError("fromServiceKeySecret: encoding must be 'raw' or 'form'");
|
|
79
|
+
}
|
|
80
|
+
return async (context) => {
|
|
81
|
+
const secret = context.client?.uaaClientSecret;
|
|
82
|
+
if (typeof secret !== 'string' || secret.length === 0) {
|
|
83
|
+
throw new ClientUnavailableError('secret');
|
|
84
|
+
}
|
|
85
|
+
return (0, auth_providers_1.clientSecretBasic)(secret, { encoding });
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* What a strategy is told of the secret client: an allowlist — who it is
|
|
90
|
+
* (`uaaUrl`, `uaaClientId`) and its secret (`uaaClientSecret`, which
|
|
91
|
+
* `fromServiceKeySecret` sends) — never a refresh token or any other field a
|
|
92
|
+
* store's `getAuthorizationConfig` carried, which no binding has checked.
|
|
93
|
+
*/
|
|
94
|
+
function contextClient(client) {
|
|
95
|
+
if (!client)
|
|
96
|
+
return null;
|
|
97
|
+
return {
|
|
98
|
+
uaaUrl: client.uaaUrl,
|
|
99
|
+
uaaClientId: client.uaaClientId,
|
|
100
|
+
uaaClientSecret: client.uaaClientSecret,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The certificate client as the contract declares it — `uaaUrl`, `clientId`,
|
|
105
|
+
* `certificate`, `key`, `certUrl` — and nothing else a store answered with it.
|
|
106
|
+
*/
|
|
107
|
+
function certificateOf(answer) {
|
|
108
|
+
if (!answer)
|
|
109
|
+
return null;
|
|
110
|
+
return {
|
|
111
|
+
uaaUrl: answer.uaaUrl,
|
|
112
|
+
clientId: answer.clientId,
|
|
113
|
+
certificate: answer.certificate,
|
|
114
|
+
key: answer.key,
|
|
115
|
+
certUrl: answer.certUrl,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The context for one build: the client through `contextClient`;
|
|
120
|
+
* `readCertificate` lazy and memoised — the first call reads, every later one
|
|
121
|
+
* gets the same answer.
|
|
122
|
+
*/
|
|
123
|
+
function clientAuthenticationContext(destination, grant, client, read) {
|
|
124
|
+
let certificate;
|
|
125
|
+
return {
|
|
126
|
+
destination,
|
|
127
|
+
grant,
|
|
128
|
+
client: contextClient(client),
|
|
129
|
+
readCertificate: () => {
|
|
130
|
+
certificate ??= read().then(certificateOf);
|
|
131
|
+
return certificate;
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
const FAILED = 'the clientAuthentication strategy failed';
|
|
136
|
+
const REFUSED = 'the clientAuthentication strategy refused';
|
|
137
|
+
/**
|
|
138
|
+
* The fixed words for what a strategy threw. Every word is this module's own:
|
|
139
|
+
* the thrown value only selects among them, through `instanceof` and flags
|
|
140
|
+
* compared with `===` — never a message, never its `words`. Classifying may
|
|
141
|
+
* itself throw (a Proxy, a getter), so it runs in its own try and falls back
|
|
142
|
+
* to the generic words.
|
|
143
|
+
*/
|
|
144
|
+
function refusalWords(error) {
|
|
145
|
+
try {
|
|
146
|
+
if (error instanceof ClientUnavailableError) {
|
|
147
|
+
const missing = error.missing;
|
|
148
|
+
if (missing === 'certificate')
|
|
149
|
+
return `${REFUSED}: ${NO_CERTIFICATE}`;
|
|
150
|
+
if (missing === 'secret')
|
|
151
|
+
return `${REFUSED}: ${NO_SECRET}`;
|
|
152
|
+
return FAILED;
|
|
153
|
+
}
|
|
154
|
+
if (error instanceof auth_providers_1.CertificateMaterialError) {
|
|
155
|
+
if (error.incomplete === true) {
|
|
156
|
+
return `${REFUSED}: ${CERTIFICATE_INCOMPLETE}`;
|
|
157
|
+
}
|
|
158
|
+
if (error.expired === true)
|
|
159
|
+
return `${REFUSED}: ${CERTIFICATE_EXPIRED}`;
|
|
160
|
+
return `${REFUSED}: ${CERTIFICATE_UNUSABLE}`;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// Fall through: the thrown value could not even be classified.
|
|
165
|
+
}
|
|
166
|
+
return FAILED;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Runs the strategy inside the guard. Any throw — the strategy's own, a
|
|
170
|
+
* store's, a malformed PEM — and any answer that is no client authentication
|
|
171
|
+
* becomes a `DestinationConfigError` naming `clientAuthentication`, in fixed
|
|
172
|
+
* words: no `cause`, nothing of the thrown value.
|
|
173
|
+
*/
|
|
174
|
+
async function resolveClientAuthentication(strategy, context) {
|
|
175
|
+
let answer;
|
|
176
|
+
let usable;
|
|
177
|
+
try {
|
|
178
|
+
answer = await strategy(context);
|
|
179
|
+
// Read inside the guard: a getter on the answer may throw too.
|
|
180
|
+
usable =
|
|
181
|
+
typeof answer === 'object' &&
|
|
182
|
+
answer !== null &&
|
|
183
|
+
typeof answer.authenticate === 'function';
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
throw new DestinationConfigError_1.DestinationConfigError(context.destination, ['clientAuthentication'], refusalWords(error));
|
|
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
|
+
}
|
package/dist/destinations.d.ts
CHANGED
|
@@ -6,16 +6,23 @@
|
|
|
6
6
|
* read to pick a row. Both come from the key store's means.
|
|
7
7
|
*/
|
|
8
8
|
import { type IDeviceCodePresenter, type OidcCallbackResult } from '@mcp-abap-adt/auth-providers';
|
|
9
|
-
import type { IAssertionReplayStore, IAuthorizationStrategy, IAuthProvider, ITokenResult } from '@mcp-abap-adt/interfaces-auth';
|
|
9
|
+
import type { IAssertionReplayStore, IAuthorizationStrategy, IAuthProvider, IClientAuthentication, ITokenResult } from '@mcp-abap-adt/interfaces-auth';
|
|
10
10
|
import type { DestinationGrant, IConfig, IConnectionConfig } from '@mcp-abap-adt/interfaces-auth-broker';
|
|
11
11
|
import type { IAuthorizationConfig } from '@mcp-abap-adt/interfaces-auth-sap';
|
|
12
12
|
import type { ILogger } from '@mcp-abap-adt/interfaces-utils';
|
|
13
13
|
import { type Binding } from './binding';
|
|
14
|
+
import type { ClientIdentity } from './clientAuthentication';
|
|
14
15
|
type StatedAuthType = NonNullable<IConnectionConfig['authType']>;
|
|
15
16
|
/** The stated `authType`, or the error naming it. */
|
|
16
17
|
export declare function statedAuthType(destination: string, means: IConnectionConfig | null): StatedAuthType;
|
|
17
18
|
/** The stated grant of a `jwt` or `saml` destination, or the error naming it. */
|
|
18
19
|
export declare function statedGrant(destination: string, authType: 'jwt' | 'saml', means: IConnectionConfig): DestinationGrant;
|
|
20
|
+
/**
|
|
21
|
+
* A row's client: the key store's secret client, or — on the strategy path
|
|
22
|
+
* without one — the certificate client's identity, which carries no secret
|
|
23
|
+
* field at all, so it can never pass for a (public) secret client.
|
|
24
|
+
*/
|
|
25
|
+
export type RowClient = IAuthorizationConfig | ClientIdentity;
|
|
19
26
|
/** `basic` → `BasicAuthProvider(username, password)`, from the means alone. */
|
|
20
27
|
export declare function basicProvider(destination: string, means: IConnectionConfig): IAuthProvider;
|
|
21
28
|
/**
|
|
@@ -43,10 +50,18 @@ export declare function isUaaGrant(grant: DestinationGrant): grant is UaaGrant;
|
|
|
43
50
|
export interface UaaRow {
|
|
44
51
|
destination: string;
|
|
45
52
|
grant: UaaGrant;
|
|
46
|
-
/**
|
|
47
|
-
|
|
53
|
+
/**
|
|
54
|
+
* The client: the key store's `getAuthorizationConfig` — or, with a
|
|
55
|
+
* strategy and no secret client, the certificate client's identity.
|
|
56
|
+
*/
|
|
57
|
+
client: RowClient | null;
|
|
48
58
|
/** The session secret — the seed; `null` when there is no session. */
|
|
49
59
|
secret: IConfig | null;
|
|
60
|
+
/**
|
|
61
|
+
* The consumer strategy's answer. Given, the provider authenticates its
|
|
62
|
+
* client with it and gets no `clientSecret`; absent, 4.0.0's secret.
|
|
63
|
+
*/
|
|
64
|
+
clientAuthentication?: IClientAuthentication;
|
|
50
65
|
/** The consumer's `authorization` option. */
|
|
51
66
|
authorization: ((destination: string, grant: 'authorization_code' | 'passcode') => IAuthorizationStrategy<string>) | undefined;
|
|
52
67
|
logger: ILogger;
|
|
@@ -60,9 +75,14 @@ export interface UaaRow {
|
|
|
60
75
|
*
|
|
61
76
|
* Every field and option the row lacks is named in one error, before the
|
|
62
77
|
* consumer's `authorization` is called. `serviceUrl` is not among them: the
|
|
63
|
-
* providers read the client and `uaaUrl` only.
|
|
64
|
-
* client: `passcode` takes it as no secret;
|
|
65
|
-
* require a secret, so for them `''` is
|
|
78
|
+
* providers read the client and `uaaUrl` only. Without a strategy,
|
|
79
|
+
* `uaaClientSecret: ''` is a public client: `passcode` takes it as no secret;
|
|
80
|
+
* the other two rows' providers require a secret, so for them `''` is
|
|
81
|
+
* missing. On the strategy path the client may be a certificate client's
|
|
82
|
+
* identity, which holds no secret field at all (`RowClient`).
|
|
83
|
+
*
|
|
84
|
+
* With the strategy's answer (`clientAuthentication`) no secret is required
|
|
85
|
+
* and none is passed: the provider authenticates its client with the answer.
|
|
66
86
|
*/
|
|
67
87
|
export declare function uaaProvider(row: UaaRow): IAuthProvider;
|
|
68
88
|
/** The OIDC grants: a client, and an issuer or explicit endpoints. */
|
|
@@ -74,9 +94,11 @@ export interface OidcRow {
|
|
|
74
94
|
grant: OidcGrant;
|
|
75
95
|
means: IConnectionConfig;
|
|
76
96
|
/** The client, from the key store's `getAuthorizationConfig`. */
|
|
77
|
-
client:
|
|
97
|
+
client: RowClient | null;
|
|
78
98
|
/** The session secret — the seed; `null` when there is none, or it is not bound here. */
|
|
79
99
|
secret: IConfig | null;
|
|
100
|
+
/** The consumer strategy's answer: given, it replaces the secret. */
|
|
101
|
+
clientAuthentication?: IClientAuthentication;
|
|
80
102
|
oidcAuthorization: ((destination: string) => IAuthorizationStrategy<OidcCallbackResult>) | undefined;
|
|
81
103
|
deviceCodePresenter: ((destination: string) => IDeviceCodePresenter) | undefined;
|
|
82
104
|
logger: ILogger;
|
|
@@ -105,9 +127,11 @@ export interface SamlRow {
|
|
|
105
127
|
grant: SamlGrant;
|
|
106
128
|
means: IConnectionConfig;
|
|
107
129
|
/** `saml2_bearer`'s client, from the key store; `null` for `saml2_pure`. */
|
|
108
|
-
client:
|
|
130
|
+
client: RowClient | null;
|
|
109
131
|
/** The session secret — the seed; `null` when there is none, or it is not bound here. */
|
|
110
132
|
secret: IConfig | null;
|
|
133
|
+
/** `saml2_bearer`: the consumer strategy's answer; given, it replaces the secret. */
|
|
134
|
+
clientAuthentication?: IClientAuthentication;
|
|
111
135
|
authorization: ((destination: string, grant: SamlGrant) => IAuthorizationStrategy<string>) | undefined;
|
|
112
136
|
samlCookies: ((destination: string) => (samlResponse: string) => Promise<string>) | undefined;
|
|
113
137
|
assertionReplayStore: ((destination: string) => IAssertionReplayStore) | undefined;
|
|
@@ -126,8 +150,9 @@ export interface SamlRow {
|
|
|
126
150
|
*
|
|
127
151
|
* `saml2_pure` is seeded with the stored cookies and `expiresAt`;
|
|
128
152
|
* `saml2_bearer` with the stored token, its refresh token and expiry, and
|
|
129
|
-
* takes the client (`uaaUrl`, `uaaClientId`;
|
|
130
|
-
*
|
|
153
|
+
* takes the client (`uaaUrl`, `uaaClientId`; without a strategy,
|
|
154
|
+
* `uaaClientSecret` `''` a public client; on the strategy path the identity
|
|
155
|
+
* alone, no secret) and `samlTokenUrl` when stated.
|
|
131
156
|
*
|
|
132
157
|
* Every field and option the row lacks is named in one error, before any
|
|
133
158
|
* collaborator is called; so is a `samlClockSkewMs` that is not a whole number
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"destinations.d.ts","sourceRoot":"","sources":["../src/destinations.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAML,KAAK,oBAAoB,EAEzB,KAAK,kBAAkB,EAWxB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EACV,qBAAqB,EAErB,sBAAsB,EACtB,aAAa,EACb,YAAY,EACb,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EACV,gBAAgB,EAChB,OAAO,EACP,iBAAiB,EAClB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;AAC9D,OAAO,EAAE,KAAK,OAAO,EAA4B,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"destinations.d.ts","sourceRoot":"","sources":["../src/destinations.ts"],"names":[],"mappings":"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"}
|
package/dist/destinations.js
CHANGED
|
@@ -64,6 +64,32 @@ function statedGrant(destination, authType, means) {
|
|
|
64
64
|
}
|
|
65
65
|
return grantType;
|
|
66
66
|
}
|
|
67
|
+
/** The secret a row's client holds; an identity holds none. */
|
|
68
|
+
function secretOf(client) {
|
|
69
|
+
return client && 'uaaClientSecret' in client
|
|
70
|
+
? client.uaaClientSecret
|
|
71
|
+
: undefined;
|
|
72
|
+
}
|
|
73
|
+
/** Added to a row's refusal when it has no client and no strategy was given. */
|
|
74
|
+
const CERTIFICATE_HINT = 'a certificate client needs a clientAuthentication strategy';
|
|
75
|
+
/**
|
|
76
|
+
* Whether a client row's refusal gets the hint: no strategy, and no client
|
|
77
|
+
* at all — the key store answered none, or one without a client id. A
|
|
78
|
+
* client that lacks only its secret or its URL is a secret client with a
|
|
79
|
+
* gap, and gets 4.0.0's words alone. Decided from what was read, never by
|
|
80
|
+
* asking the store about a certificate.
|
|
81
|
+
*/
|
|
82
|
+
function noClientHint(clientAuthentication, client) {
|
|
83
|
+
return !clientAuthentication && !present(client?.uaaClientId);
|
|
84
|
+
}
|
|
85
|
+
/** A client row's refusal: 4.0.0's words, and the hint when `hinted`. */
|
|
86
|
+
function clientRowError(destination, lacking, reason, hinted) {
|
|
87
|
+
return new DestinationConfigError_1.DestinationConfigError(destination, lacking, hinted ? `${reason}; ${CERTIFICATE_HINT}` : reason);
|
|
88
|
+
}
|
|
89
|
+
/** `clientAuthentication` for a provider's config: present only when given. */
|
|
90
|
+
function authenticatedBy(clientAuthentication) {
|
|
91
|
+
return clientAuthentication ? { clientAuthentication } : {};
|
|
92
|
+
}
|
|
67
93
|
/** The names of the fields among `names` that `source` lacks or holds as `''`. */
|
|
68
94
|
function missing(source, names) {
|
|
69
95
|
return names.filter((name) => !present(source?.[name]));
|
|
@@ -152,37 +178,47 @@ function isUaaGrant(grant) {
|
|
|
152
178
|
*
|
|
153
179
|
* Every field and option the row lacks is named in one error, before the
|
|
154
180
|
* consumer's `authorization` is called. `serviceUrl` is not among them: the
|
|
155
|
-
* providers read the client and `uaaUrl` only.
|
|
156
|
-
* client: `passcode` takes it as no secret;
|
|
157
|
-
* require a secret, so for them `''` is
|
|
181
|
+
* providers read the client and `uaaUrl` only. Without a strategy,
|
|
182
|
+
* `uaaClientSecret: ''` is a public client: `passcode` takes it as no secret;
|
|
183
|
+
* the other two rows' providers require a secret, so for them `''` is
|
|
184
|
+
* missing. On the strategy path the client may be a certificate client's
|
|
185
|
+
* identity, which holds no secret field at all (`RowClient`).
|
|
186
|
+
*
|
|
187
|
+
* With the strategy's answer (`clientAuthentication`) no secret is required
|
|
188
|
+
* and none is passed: the provider authenticates its client with the answer.
|
|
158
189
|
*/
|
|
159
190
|
function uaaProvider(row) {
|
|
160
|
-
const { destination, grant, client, secret } = row;
|
|
191
|
+
const { destination, grant, client, secret, clientAuthentication } = row;
|
|
161
192
|
const lacking = [];
|
|
162
193
|
if (!present(client?.uaaUrl))
|
|
163
194
|
lacking.push('uaaUrl');
|
|
164
195
|
if (!present(client?.uaaClientId))
|
|
165
196
|
lacking.push('uaaClientId');
|
|
166
|
-
if (
|
|
197
|
+
if (!clientAuthentication &&
|
|
198
|
+
grant !== 'passcode' &&
|
|
199
|
+
!present(secretOf(client))) {
|
|
167
200
|
lacking.push('uaaClientSecret');
|
|
168
201
|
}
|
|
169
202
|
if (grant !== 'client_credentials' && !row.authorization) {
|
|
170
203
|
lacking.push('authorization');
|
|
171
204
|
}
|
|
172
205
|
if (lacking.length > 0) {
|
|
173
|
-
throw
|
|
206
|
+
throw clientRowError(destination, lacking, `a jwt destination with grantType ${grant} lacks what its grant needs`, noClientHint(clientAuthentication, client));
|
|
174
207
|
}
|
|
175
208
|
const uaaUrl = client?.uaaUrl;
|
|
176
209
|
const clientId = client?.uaaClientId;
|
|
177
|
-
const clientSecret = present(client
|
|
178
|
-
? client
|
|
210
|
+
const clientSecret = !clientAuthentication && present(secretOf(client))
|
|
211
|
+
? secretOf(client)
|
|
179
212
|
: undefined;
|
|
180
|
-
const hooks = {
|
|
213
|
+
const hooks = {
|
|
214
|
+
onTokens: row.onTokens,
|
|
215
|
+
...authenticatedBy(clientAuthentication),
|
|
216
|
+
};
|
|
181
217
|
if (grant === 'client_credentials') {
|
|
182
218
|
return new auth_providers_1.ClientCredentialsProvider({
|
|
183
219
|
uaaUrl,
|
|
184
220
|
clientId,
|
|
185
|
-
clientSecret: clientSecret,
|
|
221
|
+
...(clientSecret === undefined ? {} : { clientSecret }),
|
|
186
222
|
logger: row.logger,
|
|
187
223
|
...hooks,
|
|
188
224
|
});
|
|
@@ -194,7 +230,7 @@ function uaaProvider(row) {
|
|
|
194
230
|
return new auth_providers_1.AuthorizationCodeProvider({
|
|
195
231
|
uaaUrl,
|
|
196
232
|
clientId,
|
|
197
|
-
clientSecret: clientSecret,
|
|
233
|
+
...(clientSecret === undefined ? {} : { clientSecret }),
|
|
198
234
|
authorization,
|
|
199
235
|
...seed,
|
|
200
236
|
logger: row.logger,
|
|
@@ -279,7 +315,7 @@ function oidcProvider(row) {
|
|
|
279
315
|
lacking.push('deviceCodePresenter');
|
|
280
316
|
}
|
|
281
317
|
if (lacking.length > 0) {
|
|
282
|
-
throw
|
|
318
|
+
throw clientRowError(destination, lacking, `a jwt destination with grantType ${grant} lacks what its grant needs`, noClientHint(row.clientAuthentication, client));
|
|
283
319
|
}
|
|
284
320
|
const scopes = Array.isArray(means.oidcScopes) && means.oidcScopes.length > 0
|
|
285
321
|
? means.oidcScopes
|
|
@@ -287,7 +323,10 @@ function oidcProvider(row) {
|
|
|
287
323
|
const common = {
|
|
288
324
|
issuerUrl: stated(means.oidcIssuerUrl),
|
|
289
325
|
clientId: client?.uaaClientId,
|
|
290
|
-
clientSecret:
|
|
326
|
+
clientSecret: row.clientAuthentication
|
|
327
|
+
? undefined
|
|
328
|
+
: stated(secretOf(client)),
|
|
329
|
+
...authenticatedBy(row.clientAuthentication),
|
|
291
330
|
tokenEndpoint: stated(means.oidcTokenEndpoint),
|
|
292
331
|
...tokenSeed(row.secret),
|
|
293
332
|
logger: row.logger,
|
|
@@ -343,8 +382,9 @@ function isSamlGrant(grant) {
|
|
|
343
382
|
*
|
|
344
383
|
* `saml2_pure` is seeded with the stored cookies and `expiresAt`;
|
|
345
384
|
* `saml2_bearer` with the stored token, its refresh token and expiry, and
|
|
346
|
-
* takes the client (`uaaUrl`, `uaaClientId`;
|
|
347
|
-
*
|
|
385
|
+
* takes the client (`uaaUrl`, `uaaClientId`; without a strategy,
|
|
386
|
+
* `uaaClientSecret` `''` a public client; on the strategy path the identity
|
|
387
|
+
* alone, no secret) and `samlTokenUrl` when stated.
|
|
348
388
|
*
|
|
349
389
|
* Every field and option the row lacks is named in one error, before any
|
|
350
390
|
* collaborator is called; so is a `samlClockSkewMs` that is not a whole number
|
|
@@ -374,7 +414,8 @@ function samlProvider(row) {
|
|
|
374
414
|
if (!row.assertionReplayStore)
|
|
375
415
|
lacking.push('assertionReplayStore');
|
|
376
416
|
if (lacking.length > 0) {
|
|
377
|
-
throw
|
|
417
|
+
throw clientRowError(destination, lacking, `a saml destination with grantType ${grant} lacks what its grant needs`, grant === 'saml2_bearer' &&
|
|
418
|
+
noClientHint(row.clientAuthentication, client));
|
|
378
419
|
}
|
|
379
420
|
const skew = means.samlClockSkewMs;
|
|
380
421
|
if (skew !== undefined && !(Number.isInteger(skew) && skew >= 0)) {
|
|
@@ -423,7 +464,10 @@ function samlProvider(row) {
|
|
|
423
464
|
tokenUrl: stated(means.samlTokenUrl),
|
|
424
465
|
uaaUrl: client?.uaaUrl,
|
|
425
466
|
clientId: client?.uaaClientId,
|
|
426
|
-
clientSecret:
|
|
467
|
+
clientSecret: row.clientAuthentication
|
|
468
|
+
? undefined
|
|
469
|
+
: stated(secretOf(client)),
|
|
470
|
+
...authenticatedBy(row.clientAuthentication),
|
|
427
471
|
...tokenSeed(row.secret),
|
|
428
472
|
});
|
|
429
473
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
* secret, and stores back what that provider obtains; the token API serves a
|
|
6
6
|
* token from the same provider, or from one the consumer injects.
|
|
7
7
|
*/
|
|
8
|
-
export type { ITokenRefresher } from '@mcp-abap-adt/interfaces-auth';
|
|
8
|
+
export type { IClientAuthentication, ITokenRefresher, } from '@mcp-abap-adt/interfaces-auth';
|
|
9
|
+
export type { IClientCertificate } from '@mcp-abap-adt/interfaces-auth-broker';
|
|
9
10
|
export type { AuthType } from '@mcp-abap-adt/interfaces-auth-sap';
|
|
10
11
|
export type { ILogger } from '@mcp-abap-adt/interfaces-utils';
|
|
11
|
-
export { AuthBroker, type AuthBrokerConfig, type StrategyGrant, type TokenProviderFactory, } from './AuthBroker';
|
|
12
|
+
export { AuthBroker, type AuthBrokerConfig, type StrategyGrant, type TokenProviderClient, type TokenProviderFactory, } from './AuthBroker';
|
|
12
13
|
export { bindingOf, type SecretBinding } from './bindingOf';
|
|
14
|
+
export { type ClientAuthenticationContext, type ClientAuthenticationGrant, type ClientAuthenticationStrategy, type FromServiceKeySecretOptions, fromServiceKeyCertificate, fromServiceKeySecret, } from './clientAuthentication';
|
|
13
15
|
export { DestinationConfigError } from './DestinationConfigError';
|
|
14
16
|
export type { IRefreshableTokenProvider, ITokenProvider, ITokenResult, TokenProviderOptions, } from './providers';
|
|
15
17
|
export type { IAuthorizationConfig, IConnectionConfig, IServiceKeyStore, ISessionStore, } from './stores/interfaces';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,YAAY,EACV,qBAAqB,EACrB,eAAe,GAChB,MAAM,+BAA+B,CAAC;AACvC,YAAY,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAC/E,YAAY,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AAClE,YAAY,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;AAC9D,OAAO,EACL,UAAU,EACV,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,GAC1B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EACL,KAAK,2BAA2B,EAChC,KAAK,yBAAyB,EAC9B,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,yBAAyB,EACzB,oBAAoB,GACrB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAElE,YAAY,EACV,yBAAyB,EACzB,cAAc,EACd,YAAY,EACZ,oBAAoB,GACrB,MAAM,aAAa,CAAC;AAGrB,YAAY,EACV,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,GACd,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -7,11 +7,14 @@
|
|
|
7
7
|
* token from the same provider, or from one the consumer injects.
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.DestinationConfigError = exports.bindingOf = exports.AuthBroker = void 0;
|
|
10
|
+
exports.DestinationConfigError = exports.fromServiceKeySecret = exports.fromServiceKeyCertificate = exports.bindingOf = exports.AuthBroker = void 0;
|
|
11
11
|
var AuthBroker_1 = require("./AuthBroker");
|
|
12
12
|
Object.defineProperty(exports, "AuthBroker", { enumerable: true, get: function () { return AuthBroker_1.AuthBroker; } });
|
|
13
13
|
var bindingOf_1 = require("./bindingOf");
|
|
14
14
|
Object.defineProperty(exports, "bindingOf", { enumerable: true, get: function () { return bindingOf_1.bindingOf; } });
|
|
15
|
+
var clientAuthentication_1 = require("./clientAuthentication");
|
|
16
|
+
Object.defineProperty(exports, "fromServiceKeyCertificate", { enumerable: true, get: function () { return clientAuthentication_1.fromServiceKeyCertificate; } });
|
|
17
|
+
Object.defineProperty(exports, "fromServiceKeySecret", { enumerable: true, get: function () { return clientAuthentication_1.fromServiceKeySecret; } });
|
|
15
18
|
var DestinationConfigError_1 = require("./DestinationConfigError");
|
|
16
19
|
Object.defineProperty(exports, "DestinationConfigError", { enumerable: true, get: function () { return DestinationConfigError_1.DestinationConfigError; } });
|
|
17
20
|
// Store implementations: `@mcp-abap-adt/auth-stores`, or any `ISessionStore` /
|