@mcp-abap-adt/auth-broker 3.1.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 +531 -0
- package/README.md +1062 -220
- package/dist/AuthBroker.d.ts +249 -30
- package/dist/AuthBroker.d.ts.map +1 -1
- package/dist/AuthBroker.js +681 -115
- package/dist/DestinationConfigError.d.ts +27 -0
- package/dist/DestinationConfigError.d.ts.map +1 -0
- package/dist/DestinationConfigError.js +36 -0
- package/dist/SessionWriter.d.ts +54 -0
- package/dist/SessionWriter.d.ts.map +1 -0
- package/dist/SessionWriter.js +135 -0
- package/dist/binding.d.ts +117 -0
- package/dist/binding.d.ts.map +1 -0
- package/dist/binding.js +223 -0
- package/dist/bindingOf.d.ts +37 -0
- package/dist/bindingOf.d.ts.map +1 -0
- package/dist/bindingOf.js +53 -0
- 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 +164 -0
- package/dist/destinations.d.ts.map +1 -0
- package/dist/destinations.js +473 -0
- package/dist/index.d.ts +10 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -7
- package/dist/providers/ITokenProvider.d.ts +2 -1
- package/dist/providers/ITokenProvider.d.ts.map +1 -1
- package/dist/providers/index.d.ts +2 -2
- package/dist/providers/index.js +2 -2
- package/dist/stores/index.d.ts +2 -2
- package/dist/stores/index.js +2 -2
- package/dist/stores/interfaces.d.ts +4 -3
- package/dist/stores/interfaces.d.ts.map +1 -1
- package/dist/stores/interfaces.js +2 -2
- package/dist/types.d.ts +5 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +3 -1
- package/package.json +16 -8
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* What a destination's means bind its secret to: the one
|
|
4
|
+
* place the broker chooses which binding a destination has. `getProvider`
|
|
5
|
+
* builds each provider with it, and `persist` writes what it gives beside every
|
|
6
|
+
* secret; `bindingOf` hands the same to a consumer.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.destinationBinding = destinationBinding;
|
|
10
|
+
exports.bindingOf = bindingOf;
|
|
11
|
+
const binding_1 = require("./binding");
|
|
12
|
+
const destinations_1 = require("./destinations");
|
|
13
|
+
/**
|
|
14
|
+
* The binding of a `jwt` or `saml` destination with the grant it states: the
|
|
15
|
+
* UAA grants and `saml2_bearer` by `uaaUrl` and the client, the OIDC grants by
|
|
16
|
+
* the issuer and the client, `saml2_pure` by its ACS, and `none` by what the
|
|
17
|
+
* means state.
|
|
18
|
+
*/
|
|
19
|
+
function destinationBinding(authType, grant, means, client) {
|
|
20
|
+
if (grant === 'none')
|
|
21
|
+
return (0, binding_1.handedOverBinding)(authType, means, client);
|
|
22
|
+
if ((0, destinations_1.isOidcGrant)(grant))
|
|
23
|
+
return (0, binding_1.oidcBinding)(means, client);
|
|
24
|
+
if (grant === 'saml2_pure')
|
|
25
|
+
return (0, binding_1.samlPureBinding)(means);
|
|
26
|
+
return (0, binding_1.uaaBinding)(means, client);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The binding the broker writes beside a secret for a destination with these
|
|
30
|
+
* means and this client — canonical, exactly what `getProvider` compares a
|
|
31
|
+
* stored secret against. For a consumer that hands over a credential the
|
|
32
|
+
* broker cannot obtain (a `none` destination: a token or cookies it was
|
|
33
|
+
* given) and writes it to the session store itself: written with this binding,
|
|
34
|
+
* `getProvider` presents it; written with any other, it refuses.
|
|
35
|
+
*
|
|
36
|
+
* `means` are the key store's (`authType`, `grantType`, `serviceUrl`,
|
|
37
|
+
* `sapClient`, the issuer or ACS); `client` is the key store's client, when the
|
|
38
|
+
* destination has one. A destination that states no `jwt` / `saml` type or no
|
|
39
|
+
* grant binds nothing: `{}`. Never logs, never throws.
|
|
40
|
+
*/
|
|
41
|
+
function bindingOf(means, client = null) {
|
|
42
|
+
const authType = means.authType;
|
|
43
|
+
const grant = means.grantType;
|
|
44
|
+
if ((authType !== 'jwt' && authType !== 'saml') || !grant)
|
|
45
|
+
return {};
|
|
46
|
+
const { issuedFor, issuedBy } = destinationBinding(authType, grant, means, client);
|
|
47
|
+
const binding = {};
|
|
48
|
+
if (issuedFor !== undefined)
|
|
49
|
+
binding.issuedFor = issuedFor;
|
|
50
|
+
if (issuedBy !== undefined)
|
|
51
|
+
binding.issuedBy = issuedBy;
|
|
52
|
+
return binding;
|
|
53
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a destination states, and the provider it states.
|
|
3
|
+
*
|
|
4
|
+
* Two fields choose the provider — `authType` and, for `jwt` and `saml`,
|
|
5
|
+
* `grantType` — and nothing else: no field's presence or absence is ever
|
|
6
|
+
* read to pick a row. Both come from the key store's means.
|
|
7
|
+
*/
|
|
8
|
+
import { type IDeviceCodePresenter, type OidcCallbackResult } from '@mcp-abap-adt/auth-providers';
|
|
9
|
+
import type { IAssertionReplayStore, IAuthorizationStrategy, IAuthProvider, IClientAuthentication, ITokenResult } from '@mcp-abap-adt/interfaces-auth';
|
|
10
|
+
import type { DestinationGrant, IConfig, IConnectionConfig } from '@mcp-abap-adt/interfaces-auth-broker';
|
|
11
|
+
import type { IAuthorizationConfig } from '@mcp-abap-adt/interfaces-auth-sap';
|
|
12
|
+
import type { ILogger } from '@mcp-abap-adt/interfaces-utils';
|
|
13
|
+
import { type Binding } from './binding';
|
|
14
|
+
import type { ClientIdentity } from './clientAuthentication';
|
|
15
|
+
type StatedAuthType = NonNullable<IConnectionConfig['authType']>;
|
|
16
|
+
/** The stated `authType`, or the error naming it. */
|
|
17
|
+
export declare function statedAuthType(destination: string, means: IConnectionConfig | null): StatedAuthType;
|
|
18
|
+
/** The stated grant of a `jwt` or `saml` destination, or the error naming it. */
|
|
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;
|
|
26
|
+
/** `basic` → `BasicAuthProvider(username, password)`, from the means alone. */
|
|
27
|
+
export declare function basicProvider(destination: string, means: IConnectionConfig): IAuthProvider;
|
|
28
|
+
/**
|
|
29
|
+
* `snc` → the Secure Login Client recipe, from the four SNC fields. Only the
|
|
30
|
+
* partner name is required: the contract says the library is discovered when
|
|
31
|
+
* `sncLib` is absent and the user's name taken from the credential when
|
|
32
|
+
* `sncMyName` is, and the provider documents its own `qop` default.
|
|
33
|
+
*/
|
|
34
|
+
export declare function sncProvider(destination: string, means: IConnectionConfig, logger: ILogger): IAuthProvider;
|
|
35
|
+
/**
|
|
36
|
+
* `none` → the credential handed over, from the session: the only way in, so
|
|
37
|
+
* required. Read from the secret alone — the means are not a seed.
|
|
38
|
+
*
|
|
39
|
+
* The broker did not obtain it and cannot obtain it again, so a binding that
|
|
40
|
+
* does not match is refused, never discarded: `issuedFor`
|
|
41
|
+
* must always equal the destination's resource — it is what stops the
|
|
42
|
+
* credential going to another one; `issuedBy` is compared only when the means
|
|
43
|
+
* state an issuer.
|
|
44
|
+
*/
|
|
45
|
+
export declare function handedOverProvider(destination: string, authType: 'jwt' | 'saml', secret: IConfig | null, binding: Binding): IAuthProvider;
|
|
46
|
+
/** The UAA grants: one client, one token endpoint (`<uaaUrl>/oauth/token`). */
|
|
47
|
+
export type UaaGrant = 'authorization_code' | 'client_credentials' | 'passcode';
|
|
48
|
+
export declare function isUaaGrant(grant: DestinationGrant): grant is UaaGrant;
|
|
49
|
+
/** What a UAA row is built from. */
|
|
50
|
+
export interface UaaRow {
|
|
51
|
+
destination: string;
|
|
52
|
+
grant: UaaGrant;
|
|
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;
|
|
58
|
+
/** The session secret — the seed; `null` when there is no session. */
|
|
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;
|
|
65
|
+
/** The consumer's `authorization` option. */
|
|
66
|
+
authorization: ((destination: string, grant: 'authorization_code' | 'passcode') => IAuthorizationStrategy<string>) | undefined;
|
|
67
|
+
logger: ILogger;
|
|
68
|
+
onTokens: (result: ITokenResult) => Promise<void>;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* `jwt` / `authorization_code` → `AuthorizationCodeProvider`,
|
|
72
|
+
* `client_credentials` → `ClientCredentialsProvider`, `passcode` →
|
|
73
|
+
* `UaaPasscodeProvider`: the client from the key store, the seed from the
|
|
74
|
+
* session (not for `client_credentials`, whose row takes the client alone).
|
|
75
|
+
*
|
|
76
|
+
* Every field and option the row lacks is named in one error, before the
|
|
77
|
+
* consumer's `authorization` is called. `serviceUrl` is not among them: the
|
|
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.
|
|
86
|
+
*/
|
|
87
|
+
export declare function uaaProvider(row: UaaRow): IAuthProvider;
|
|
88
|
+
/** The OIDC grants: a client, and an issuer or explicit endpoints. */
|
|
89
|
+
export type OidcGrant = 'oidc_authorization_code' | 'device_code' | 'password' | 'token_exchange';
|
|
90
|
+
export declare function isOidcGrant(grant: DestinationGrant): grant is OidcGrant;
|
|
91
|
+
/** What an OIDC row is built from. */
|
|
92
|
+
export interface OidcRow {
|
|
93
|
+
destination: string;
|
|
94
|
+
grant: OidcGrant;
|
|
95
|
+
means: IConnectionConfig;
|
|
96
|
+
/** The client, from the key store's `getAuthorizationConfig`. */
|
|
97
|
+
client: RowClient | null;
|
|
98
|
+
/** The session secret — the seed; `null` when there is none, or it is not bound here. */
|
|
99
|
+
secret: IConfig | null;
|
|
100
|
+
/** The consumer strategy's answer: given, it replaces the secret. */
|
|
101
|
+
clientAuthentication?: IClientAuthentication;
|
|
102
|
+
oidcAuthorization: ((destination: string) => IAuthorizationStrategy<OidcCallbackResult>) | undefined;
|
|
103
|
+
deviceCodePresenter: ((destination: string) => IDeviceCodePresenter) | undefined;
|
|
104
|
+
logger: ILogger;
|
|
105
|
+
onTokens: (result: ITokenResult) => Promise<void>;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* `jwt` / `oidc_authorization_code` → `OidcBrowserProvider`, `device_code` →
|
|
109
|
+
* `OidcDeviceFlowProvider`, `password` → `OidcPasswordProvider`,
|
|
110
|
+
* `token_exchange` → `OidcTokenExchangeProvider`: the client id (and secret,
|
|
111
|
+
* `''` being a public client) from the key store's client, the grant's data
|
|
112
|
+
* from its `oidc*` fields, the seed from the session.
|
|
113
|
+
*
|
|
114
|
+
* The endpoints: `oidcIssuerUrl`, from which the provider discovers them, or
|
|
115
|
+
* every explicit endpoint the row uses — without either the error names the
|
|
116
|
+
* issuer and the endpoints missing. Every field and option the row lacks is
|
|
117
|
+
* named in one error, before any collaborator is called. `token_exchange`
|
|
118
|
+
* takes one scope string: the stored scopes joined by a space.
|
|
119
|
+
*/
|
|
120
|
+
export declare function oidcProvider(row: OidcRow): IAuthProvider;
|
|
121
|
+
/** The SAML grants: an identity provider's assertion, validated first. */
|
|
122
|
+
export type SamlGrant = 'saml2_pure' | 'saml2_bearer';
|
|
123
|
+
export declare function isSamlGrant(grant: DestinationGrant): grant is SamlGrant;
|
|
124
|
+
/** What a SAML row is built from. */
|
|
125
|
+
export interface SamlRow {
|
|
126
|
+
destination: string;
|
|
127
|
+
grant: SamlGrant;
|
|
128
|
+
means: IConnectionConfig;
|
|
129
|
+
/** `saml2_bearer`'s client, from the key store; `null` for `saml2_pure`. */
|
|
130
|
+
client: RowClient | null;
|
|
131
|
+
/** The session secret — the seed; `null` when there is none, or it is not bound here. */
|
|
132
|
+
secret: IConfig | null;
|
|
133
|
+
/** `saml2_bearer`: the consumer strategy's answer; given, it replaces the secret. */
|
|
134
|
+
clientAuthentication?: IClientAuthentication;
|
|
135
|
+
authorization: ((destination: string, grant: SamlGrant) => IAuthorizationStrategy<string>) | undefined;
|
|
136
|
+
samlCookies: ((destination: string) => (samlResponse: string) => Promise<string>) | undefined;
|
|
137
|
+
assertionReplayStore: ((destination: string) => IAssertionReplayStore) | undefined;
|
|
138
|
+
logger: ILogger;
|
|
139
|
+
onTokens: (result: ITokenResult) => Promise<void>;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* `saml` / `saml2_pure` → `Saml2PureProvider`, `saml2_bearer` →
|
|
143
|
+
* `Saml2BearerProvider`, each validating the assertion before anything uses
|
|
144
|
+
* it, with the validator the broker composes from the destination's trust and
|
|
145
|
+
* the consumer's replay store: `createSignedResponseValidator` for
|
|
146
|
+
* `saml2_pure` — the Response signed, as the cookies' system receives it whole
|
|
147
|
+
* — and `createSignedAssertionValidator` for `saml2_bearer`, whose token
|
|
148
|
+
* endpoint gets the Assertion alone (auth-providers' own pairing). The
|
|
149
|
+
* expected issuer is `samlIdpEntityId`.
|
|
150
|
+
*
|
|
151
|
+
* `saml2_pure` is seeded with the stored cookies and `expiresAt`;
|
|
152
|
+
* `saml2_bearer` with the stored token, its refresh token and expiry, and
|
|
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.
|
|
156
|
+
*
|
|
157
|
+
* Every field and option the row lacks is named in one error, before any
|
|
158
|
+
* collaborator is called; so is a `samlClockSkewMs` that is not a whole number
|
|
159
|
+
* of milliseconds, and a certificate the validator cannot read — by name, never
|
|
160
|
+
* the value.
|
|
161
|
+
*/
|
|
162
|
+
export declare function samlProvider(row: SamlRow): IAuthProvider;
|
|
163
|
+
export {};
|
|
164
|
+
//# sourceMappingURL=destinations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"destinations.d.ts","sourceRoot":"","sources":["../src/destinations.ts"],"names":[],"mappings":"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"}
|