@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.
@@ -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
+ }
@@ -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
- /** The client, from the key store's `getAuthorizationConfig`. */
47
- client: IAuthorizationConfig | null;
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. `uaaClientSecret: ''` is a public
64
- * client: `passcode` takes it as no secret; the other two rows' providers
65
- * require a secret, so for them `''` is missing.
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: IAuthorizationConfig | null;
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: IAuthorizationConfig | null;
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`; `uaaClientSecret` `''` a public
130
- * client) and `samlTokenUrl` when stated.
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":"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"}
1
+ {"version":3,"file":"destinations.d.ts","sourceRoot":"","sources":["../src/destinations.ts"],"names":[],"mappings":"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"}
@@ -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. `uaaClientSecret: ''` is a public
156
- * client: `passcode` takes it as no secret; the other two rows' providers
157
- * require a secret, so for them `''` is missing.
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 (grant !== 'passcode' && !present(client?.uaaClientSecret)) {
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 new DestinationConfigError_1.DestinationConfigError(destination, lacking, `a jwt destination with grantType ${grant} lacks what its grant needs`);
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?.uaaClientSecret)
178
- ? client.uaaClientSecret
210
+ const clientSecret = !clientAuthentication && present(secretOf(client))
211
+ ? secretOf(client)
179
212
  : undefined;
180
- const hooks = { onTokens: row.onTokens };
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 new DestinationConfigError_1.DestinationConfigError(destination, lacking, `a jwt destination with grantType ${grant} lacks what its grant needs`);
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: stated(client?.uaaClientSecret),
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`; `uaaClientSecret` `''` a public
347
- * client) and `samlTokenUrl` when stated.
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 new DestinationConfigError_1.DestinationConfigError(destination, lacking, `a saml destination with grantType ${grant} lacks what its grant needs`);
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: stated(client?.uaaClientSecret),
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';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,YAAY,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,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,oBAAoB,GAC1B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5D,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"}
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` /