@mcp-abap-adt/auth-providers 2.2.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +98 -0
- package/README.md +231 -13
- package/dist/__tests__/integration/stand/formLogin.d.ts +68 -0
- package/dist/__tests__/integration/stand/formLogin.d.ts.map +1 -0
- package/dist/__tests__/integration/stand/formLogin.js +194 -0
- package/dist/auth/callbackServer.js +2 -2
- package/dist/auth/passcodeAuth.d.ts +25 -0
- package/dist/auth/passcodeAuth.d.ts.map +1 -0
- package/dist/auth/passcodeAuth.js +62 -0
- package/dist/auth/samlBearerAssertion.d.ts +24 -0
- package/dist/auth/samlBearerAssertion.d.ts.map +1 -0
- package/dist/auth/samlBearerAssertion.js +101 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/providers/Saml2BearerProvider.d.ts.map +1 -1
- package/dist/providers/Saml2BearerProvider.js +4 -1
- package/dist/providers/UaaPasscodeProvider.d.ts +43 -0
- package/dist/providers/UaaPasscodeProvider.d.ts.map +1 -0
- package/dist/providers/UaaPasscodeProvider.js +86 -0
- package/dist/providers/index.d.ts +2 -2
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +3 -3
- package/dist/strategies/index.d.ts +1 -1
- package/dist/strategies/index.d.ts.map +1 -1
- package/dist/strategies/index.js +2 -1
- package/dist/strategies/manualStrategies.d.ts +7 -0
- package/dist/strategies/manualStrategies.d.ts.map +1 -1
- package/dist/strategies/manualStrategies.js +21 -0
- package/package.json +10 -5
- package/bin/auth-device-flow.ts +0 -114
- package/dist/auth/deviceFlowAuth.d.ts +0 -43
- package/dist/auth/deviceFlowAuth.d.ts.map +0 -1
- package/dist/auth/deviceFlowAuth.js +0 -168
- package/dist/providers/DeviceFlowProvider.d.ts +0 -32
- package/dist/providers/DeviceFlowProvider.d.ts.map +0 -1
- package/dist/providers/DeviceFlowProvider.js +0 -86
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Plays the user in an interactive login against a stand server's own login
|
|
4
|
+
* page: follows redirects, keeps cookies, finds the form with a password
|
|
5
|
+
* field, fills it in with every hidden field it carries (UAA's CSRF token,
|
|
6
|
+
* Keycloak's session code), and submits it.
|
|
7
|
+
*
|
|
8
|
+
* Deliberately small: enough for UAA's and Keycloak's stock login pages, which
|
|
9
|
+
* the pinned image versions keep stable. It is a test helper, not a browser.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.FormBrowser = void 0;
|
|
13
|
+
exports.authorizeByForm = authorizeByForm;
|
|
14
|
+
exports.approveDevice = approveDevice;
|
|
15
|
+
exports.samlResponseByForm = samlResponseByForm;
|
|
16
|
+
const MAX_HOPS = 20;
|
|
17
|
+
class FormBrowser {
|
|
18
|
+
cookies = new Map();
|
|
19
|
+
/** GET `url` and follow redirects until `stop(url)` or a page is served. */
|
|
20
|
+
async open(url, stop = () => false) {
|
|
21
|
+
return this.follow(url, { method: 'GET' }, stop);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Submit the login form on `page`, then follow redirects until `stop` says
|
|
25
|
+
* the next location is the one the caller wants — typically the client's
|
|
26
|
+
* redirect URI carrying the code — without requesting it.
|
|
27
|
+
*/
|
|
28
|
+
async submitLogin(page, credentials, stop = () => false) {
|
|
29
|
+
const form = findPasswordForm(page.html ?? '');
|
|
30
|
+
if (!form) {
|
|
31
|
+
throw new Error(`no login form on ${page.url}`);
|
|
32
|
+
}
|
|
33
|
+
const body = new URLSearchParams(form.hidden);
|
|
34
|
+
body.set(form.userField, credentials.username);
|
|
35
|
+
body.set(form.passwordField, credentials.password);
|
|
36
|
+
return this.follow(new URL(form.action, page.url).toString(), {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
39
|
+
body: body.toString(),
|
|
40
|
+
}, stop);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Accept a consent page ("Do you grant these access privileges?"): submit
|
|
44
|
+
* the form that carries an `accept` button, with its hidden fields.
|
|
45
|
+
*/
|
|
46
|
+
async acceptConsent(page) {
|
|
47
|
+
for (const match of (page.html ?? '').matchAll(/<form\b[^>]*>[\s\S]*?<\/form>/gi)) {
|
|
48
|
+
const formHtml = match[0];
|
|
49
|
+
const inputs = [...formHtml.matchAll(/<(?:input|button)\b[^>]*>/gi)].map((m) => m[0]);
|
|
50
|
+
const accept = inputs.find((i) => attribute(i, 'name') === 'accept');
|
|
51
|
+
if (!accept)
|
|
52
|
+
continue;
|
|
53
|
+
const body = new URLSearchParams();
|
|
54
|
+
for (const input of inputs) {
|
|
55
|
+
const name = attribute(input, 'name');
|
|
56
|
+
if (name && attribute(input, 'type') === 'hidden') {
|
|
57
|
+
body.set(name, attribute(input, 'value') ?? '');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
body.set('accept', attribute(accept, 'value') ?? 'Yes');
|
|
61
|
+
const formTag = /<form\b[^>]*>/i.exec(formHtml)?.[0] ?? '';
|
|
62
|
+
return this.follow(new URL(attribute(formTag, 'action') ?? '', page.url).toString(), {
|
|
63
|
+
method: 'POST',
|
|
64
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
65
|
+
body: body.toString(),
|
|
66
|
+
}, () => false);
|
|
67
|
+
}
|
|
68
|
+
throw new Error(`no consent form on ${page.url}`);
|
|
69
|
+
}
|
|
70
|
+
async follow(url, init, stop) {
|
|
71
|
+
let current = url;
|
|
72
|
+
let request = init;
|
|
73
|
+
for (let hop = 0; hop < MAX_HOPS; hop++) {
|
|
74
|
+
const response = await fetch(current, {
|
|
75
|
+
...request,
|
|
76
|
+
redirect: 'manual',
|
|
77
|
+
headers: { ...(request.headers ?? {}), Cookie: this.cookieHeader() },
|
|
78
|
+
signal: AbortSignal.timeout(15_000),
|
|
79
|
+
});
|
|
80
|
+
this.remember(response);
|
|
81
|
+
const location = response.headers.get('location');
|
|
82
|
+
if (response.status >= 300 && response.status < 400 && location) {
|
|
83
|
+
const next = new URL(location, current).toString();
|
|
84
|
+
if (stop(next))
|
|
85
|
+
return { url: next };
|
|
86
|
+
current = next;
|
|
87
|
+
request = { method: 'GET' };
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
return { url: current, html: await response.text() };
|
|
91
|
+
}
|
|
92
|
+
throw new Error(`more than ${MAX_HOPS} redirects from ${url}`);
|
|
93
|
+
}
|
|
94
|
+
remember(response) {
|
|
95
|
+
for (const line of response.headers.getSetCookie()) {
|
|
96
|
+
const [pair] = line.split(';');
|
|
97
|
+
const eq = pair.indexOf('=');
|
|
98
|
+
if (eq > 0) {
|
|
99
|
+
this.cookies.set(pair.slice(0, eq).trim(), pair.slice(eq + 1).trim());
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
cookieHeader() {
|
|
104
|
+
return [...this.cookies].map(([k, v]) => `${k}=${v}`).join('; ');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
exports.FormBrowser = FormBrowser;
|
|
108
|
+
/**
|
|
109
|
+
* Log in through the authorization URL and return the redirect URI the server
|
|
110
|
+
* sends the browser back to, with its `code` — not requested, since nothing
|
|
111
|
+
* listens there.
|
|
112
|
+
*/
|
|
113
|
+
async function authorizeByForm(authorizationUrl, redirectUri, credentials) {
|
|
114
|
+
const reached = (next) => next.startsWith(redirectUri);
|
|
115
|
+
const browser = new FormBrowser();
|
|
116
|
+
const page = await browser.open(authorizationUrl, reached);
|
|
117
|
+
const done = page.html === undefined
|
|
118
|
+
? page
|
|
119
|
+
: await browser.submitLogin(page, credentials, reached);
|
|
120
|
+
if (!reached(done.url)) {
|
|
121
|
+
throw new Error(`login did not return to ${redirectUri}; ended at ${done.url}`);
|
|
122
|
+
}
|
|
123
|
+
return new URL(done.url);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Approve a device authorization the way a user would: open the verification
|
|
127
|
+
* URI (with the user code already in it), log in, and grant access.
|
|
128
|
+
*/
|
|
129
|
+
async function approveDevice(verificationUriComplete, credentials) {
|
|
130
|
+
const browser = new FormBrowser();
|
|
131
|
+
const page = await browser.open(verificationUriComplete);
|
|
132
|
+
const consent = await browser.submitLogin(page, credentials);
|
|
133
|
+
await browser.acceptConsent(consent);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* A SAML login at an identity provider: open the AuthnRequest URL, log in,
|
|
137
|
+
* and take the SAMLResponse from the auto-posting form the IdP answers with —
|
|
138
|
+
* what a browser would post to the assertion consumer service.
|
|
139
|
+
*/
|
|
140
|
+
async function samlResponseByForm(authnRequestUrl, credentials) {
|
|
141
|
+
const browser = new FormBrowser();
|
|
142
|
+
const page = await browser.submitLogin(await browser.open(authnRequestUrl), credentials);
|
|
143
|
+
const html = page.html ?? '';
|
|
144
|
+
const input = [...html.matchAll(/<input\b[^>]*>/gi)]
|
|
145
|
+
.map((m) => m[0])
|
|
146
|
+
.find((i) => attribute(i, 'name') === 'SAMLResponse');
|
|
147
|
+
const samlResponse = input ? attribute(input, 'value') : undefined;
|
|
148
|
+
const formTag = /<form\b[^>]*>/i.exec(html)?.[0] ?? '';
|
|
149
|
+
if (!samlResponse) {
|
|
150
|
+
throw new Error(`no SAMLResponse form on ${page.url}`);
|
|
151
|
+
}
|
|
152
|
+
return { samlResponse, acsUrl: attribute(formTag, 'action') ?? '' };
|
|
153
|
+
}
|
|
154
|
+
const decode = (value) => value
|
|
155
|
+
.replace(/&/g, '&')
|
|
156
|
+
.replace(/"/g, '"')
|
|
157
|
+
.replace(/'/g, "'")
|
|
158
|
+
.replace(/</g, '<')
|
|
159
|
+
.replace(/>/g, '>');
|
|
160
|
+
const attribute = (tag, name) => {
|
|
161
|
+
const match = new RegExp(`\\s${name}\\s*=\\s*("([^"]*)"|'([^']*)')`, 'i').exec(tag);
|
|
162
|
+
return match ? decode(match[2] ?? match[3] ?? '') : undefined;
|
|
163
|
+
};
|
|
164
|
+
function findPasswordForm(html) {
|
|
165
|
+
for (const match of html.matchAll(/<form\b[^>]*>[\s\S]*?<\/form>/gi)) {
|
|
166
|
+
const formHtml = match[0];
|
|
167
|
+
const inputs = [...formHtml.matchAll(/<input\b[^>]*>/gi)].map((m) => m[0]);
|
|
168
|
+
const password = inputs.find((i) => attribute(i, 'type') === 'password');
|
|
169
|
+
if (!password)
|
|
170
|
+
continue;
|
|
171
|
+
const hidden = {};
|
|
172
|
+
let userField = 'username';
|
|
173
|
+
for (const input of inputs) {
|
|
174
|
+
const type = (attribute(input, 'type') ?? 'text').toLowerCase();
|
|
175
|
+
const name = attribute(input, 'name');
|
|
176
|
+
if (!name)
|
|
177
|
+
continue;
|
|
178
|
+
if (type === 'hidden')
|
|
179
|
+
hidden[name] = attribute(input, 'value') ?? '';
|
|
180
|
+
if ((type === 'text' || type === 'email') &&
|
|
181
|
+
/user|email|login/i.test(name)) {
|
|
182
|
+
userField = name;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const formTag = /<form\b[^>]*>/i.exec(formHtml)?.[0] ?? '';
|
|
186
|
+
return {
|
|
187
|
+
action: attribute(formTag, 'action') ?? '',
|
|
188
|
+
userField,
|
|
189
|
+
passwordField: attribute(password, 'name') ?? 'password',
|
|
190
|
+
hidden,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
@@ -163,8 +163,8 @@ async function runCallbackScope(options, routes, use) {
|
|
|
163
163
|
setTimeout(() => {
|
|
164
164
|
if (finished)
|
|
165
165
|
return;
|
|
166
|
-
// Grace expired —
|
|
167
|
-
//
|
|
166
|
+
// Grace expired — an active connection may simply be stuck. Force it,
|
|
167
|
+
// bounded.
|
|
168
168
|
server.closeIdleConnections?.();
|
|
169
169
|
server.closeAllConnections?.();
|
|
170
170
|
for (const socket of sockets)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The UAA one-time passcode exchange — what `cf login --sso` does.
|
|
3
|
+
*
|
|
4
|
+
* The user opens `<uaa>/passcode` in any browser, logs in however the
|
|
5
|
+
* identity zone lets them (SSO, a corporate IdP, MFA), and copies the
|
|
6
|
+
* "Temporary Authentication Code" shown there. That code is exchanged here
|
|
7
|
+
* through the password grant, with `passcode` in place of a username and
|
|
8
|
+
* password. It is a UAA extension, not an RFC; XSUAA inherits it.
|
|
9
|
+
*
|
|
10
|
+
* UAA hands the request to its passcode filter chain only when its Accept
|
|
11
|
+
* header names JSON (`passcodeTokenMatcher` accepts `application/json` or
|
|
12
|
+
* `application/x-www-form-urlencoded`). Otherwise it falls through to the
|
|
13
|
+
* ordinary password grant, which answers `invalid_client: No password
|
|
14
|
+
* supplied` — what a bare `fetch`, which sends `*\/*`, gets. axios's default
|
|
15
|
+
* Accept happens to include `application/json`; the header is set explicitly
|
|
16
|
+
* so the exchange does not depend on an HTTP client's defaults.
|
|
17
|
+
*/
|
|
18
|
+
import type { ILogger } from '@mcp-abap-adt/interfaces-utils';
|
|
19
|
+
export interface PasscodeTokens {
|
|
20
|
+
accessToken: string;
|
|
21
|
+
refreshToken?: string;
|
|
22
|
+
expiresIn?: number;
|
|
23
|
+
}
|
|
24
|
+
export declare function exchangePasscode(uaaUrl: string, clientId: string, clientSecret: string | undefined, passcode: string, logger?: ILogger): Promise<PasscodeTokens>;
|
|
25
|
+
//# sourceMappingURL=passcodeAuth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"passcodeAuth.d.ts","sourceRoot":"","sources":["../../src/auth/passcodeAuth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;AAG9D,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,OAAO,GACf,OAAO,CAAC,cAAc,CAAC,CAiDzB"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The UAA one-time passcode exchange — what `cf login --sso` does.
|
|
4
|
+
*
|
|
5
|
+
* The user opens `<uaa>/passcode` in any browser, logs in however the
|
|
6
|
+
* identity zone lets them (SSO, a corporate IdP, MFA), and copies the
|
|
7
|
+
* "Temporary Authentication Code" shown there. That code is exchanged here
|
|
8
|
+
* through the password grant, with `passcode` in place of a username and
|
|
9
|
+
* password. It is a UAA extension, not an RFC; XSUAA inherits it.
|
|
10
|
+
*
|
|
11
|
+
* UAA hands the request to its passcode filter chain only when its Accept
|
|
12
|
+
* header names JSON (`passcodeTokenMatcher` accepts `application/json` or
|
|
13
|
+
* `application/x-www-form-urlencoded`). Otherwise it falls through to the
|
|
14
|
+
* ordinary password grant, which answers `invalid_client: No password
|
|
15
|
+
* supplied` — what a bare `fetch`, which sends `*\/*`, gets. axios's default
|
|
16
|
+
* Accept happens to include `application/json`; the header is set explicitly
|
|
17
|
+
* so the exchange does not depend on an HTTP client's defaults.
|
|
18
|
+
*/
|
|
19
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
20
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
21
|
+
};
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.exchangePasscode = exchangePasscode;
|
|
24
|
+
const axios_1 = __importDefault(require("axios"));
|
|
25
|
+
async function exchangePasscode(uaaUrl, clientId, clientSecret, passcode, logger) {
|
|
26
|
+
const tokenUrl = `${uaaUrl.replace(/\/+$/, '')}/oauth/token`;
|
|
27
|
+
const params = new URLSearchParams();
|
|
28
|
+
params.append('grant_type', 'password');
|
|
29
|
+
params.append('passcode', passcode);
|
|
30
|
+
logger?.info('[UAA] Exchanging passcode for token', { tokenUrl });
|
|
31
|
+
// A public client — `cf` is one — authenticates with an empty secret.
|
|
32
|
+
const basic = Buffer.from(`${clientId}:${clientSecret ?? ''}`).toString('base64');
|
|
33
|
+
let response;
|
|
34
|
+
try {
|
|
35
|
+
response = await axios_1.default.post(tokenUrl, params.toString(), {
|
|
36
|
+
headers: {
|
|
37
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
38
|
+
Accept: 'application/json',
|
|
39
|
+
Authorization: `Basic ${basic}`,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
// UAA says why in the body — "Invalid passcode" for a mistyped or
|
|
45
|
+
// already spent code — which is what the user needs to read.
|
|
46
|
+
if (axios_1.default.isAxiosError(error) && error.response) {
|
|
47
|
+
const body = error.response.data;
|
|
48
|
+
const reason = body?.error_description ?? body?.error ?? 'no reason given';
|
|
49
|
+
throw new Error(`Passcode exchange failed (${error.response.status}): ${reason}`);
|
|
50
|
+
}
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
const data = response.data;
|
|
54
|
+
if (!data?.access_token) {
|
|
55
|
+
throw new Error('Passcode exchange returned no access_token');
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
accessToken: data.access_token,
|
|
59
|
+
refreshToken: data.refresh_token,
|
|
60
|
+
expiresIn: data.expires_in,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the SAML 2.0 bearer grant accepts, from what a SAML login delivers.
|
|
3
|
+
*
|
|
4
|
+
* RFC 7522 §2.1: the `assertion` parameter is a single SAML 2.0 Assertion,
|
|
5
|
+
* base64url-encoded. An interactive login delivers something else — the
|
|
6
|
+
* identity provider's whole `samlp:Response`, in standard base64 — and a
|
|
7
|
+
* token endpoint that follows the RFC refuses it: Cloud Foundry UAA answers
|
|
8
|
+
* 401 to a Response in either encoding. So the Assertion is taken out of the
|
|
9
|
+
* Response here and re-encoded.
|
|
10
|
+
*
|
|
11
|
+
* The Assertion is serialised as an element of its own, and every namespace
|
|
12
|
+
* declaration it inherited from the Response is copied onto it first. A
|
|
13
|
+
* serializer would add back only the prefixes used in element and attribute
|
|
14
|
+
* names; a prefix used only inside a value — `xsi:type="xs:string"` — would be
|
|
15
|
+
* lost, leaving a QName that no longer resolves. Copying all of them keeps the
|
|
16
|
+
* Assertion's in-scope namespaces exactly what they were. Its signature, over
|
|
17
|
+
* exclusive canonical XML, still verifies: canonicalisation renders a
|
|
18
|
+
* namespace where it is used, or where the signature's InclusiveNamespaces
|
|
19
|
+
* names it, not where it was declared. A signature over the Response alone does not
|
|
20
|
+
* survive the cut; the token endpoint then refuses the Assertion, as it
|
|
21
|
+
* would any unsigned one.
|
|
22
|
+
*/
|
|
23
|
+
export declare function toBearerAssertion(payload: string): string;
|
|
24
|
+
//# sourceMappingURL=samlBearerAssertion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"samlBearerAssertion.d.ts","sourceRoot":"","sources":["../../src/auth/samlBearerAssertion.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAOH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAmDzD"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* What the SAML 2.0 bearer grant accepts, from what a SAML login delivers.
|
|
4
|
+
*
|
|
5
|
+
* RFC 7522 §2.1: the `assertion` parameter is a single SAML 2.0 Assertion,
|
|
6
|
+
* base64url-encoded. An interactive login delivers something else — the
|
|
7
|
+
* identity provider's whole `samlp:Response`, in standard base64 — and a
|
|
8
|
+
* token endpoint that follows the RFC refuses it: Cloud Foundry UAA answers
|
|
9
|
+
* 401 to a Response in either encoding. So the Assertion is taken out of the
|
|
10
|
+
* Response here and re-encoded.
|
|
11
|
+
*
|
|
12
|
+
* The Assertion is serialised as an element of its own, and every namespace
|
|
13
|
+
* declaration it inherited from the Response is copied onto it first. A
|
|
14
|
+
* serializer would add back only the prefixes used in element and attribute
|
|
15
|
+
* names; a prefix used only inside a value — `xsi:type="xs:string"` — would be
|
|
16
|
+
* lost, leaving a QName that no longer resolves. Copying all of them keeps the
|
|
17
|
+
* Assertion's in-scope namespaces exactly what they were. Its signature, over
|
|
18
|
+
* exclusive canonical XML, still verifies: canonicalisation renders a
|
|
19
|
+
* namespace where it is used, or where the signature's InclusiveNamespaces
|
|
20
|
+
* names it, not where it was declared. A signature over the Response alone does not
|
|
21
|
+
* survive the cut; the token endpoint then refuses the Assertion, as it
|
|
22
|
+
* would any unsigned one.
|
|
23
|
+
*/
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.toBearerAssertion = toBearerAssertion;
|
|
26
|
+
const xmldom_1 = require("@xmldom/xmldom");
|
|
27
|
+
const SAML_ASSERTION_NS = 'urn:oasis:names:tc:SAML:2.0:assertion';
|
|
28
|
+
const SAML_PROTOCOL_NS = 'urn:oasis:names:tc:SAML:2.0:protocol';
|
|
29
|
+
function toBearerAssertion(payload) {
|
|
30
|
+
// Node's base64 decoder accepts both alphabets, so this reads either.
|
|
31
|
+
const xml = Buffer.from(payload.trim(), 'base64').toString('utf8');
|
|
32
|
+
if (!xml.trimStart().startsWith('<')) {
|
|
33
|
+
throw new Error('SAML bearer payload is not base64-encoded XML');
|
|
34
|
+
}
|
|
35
|
+
let root;
|
|
36
|
+
try {
|
|
37
|
+
root = new xmldom_1.DOMParser().parseFromString(xml, 'text/xml').documentElement;
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
throw new Error(`SAML bearer payload is not well-formed XML: ${error instanceof Error ? error.message : String(error)}`);
|
|
41
|
+
}
|
|
42
|
+
if (isElement(root, SAML_ASSERTION_NS, 'Assertion')) {
|
|
43
|
+
return Buffer.from(xml, 'utf8').toString('base64url');
|
|
44
|
+
}
|
|
45
|
+
if (!isElement(root, SAML_PROTOCOL_NS, 'Response')) {
|
|
46
|
+
throw new Error('SAML bearer payload is neither a SAML Response nor an Assertion');
|
|
47
|
+
}
|
|
48
|
+
const children = childElements(root);
|
|
49
|
+
const assertions = children.filter((e) => isElement(e, SAML_ASSERTION_NS, 'Assertion'));
|
|
50
|
+
if (assertions.length === 0) {
|
|
51
|
+
if (children.some((e) => isElement(e, SAML_ASSERTION_NS, 'EncryptedAssertion'))) {
|
|
52
|
+
throw new Error('SAML Response carries only an EncryptedAssertion; encrypted Assertions are not supported');
|
|
53
|
+
}
|
|
54
|
+
throw new Error('SAML Response carries no Assertion');
|
|
55
|
+
}
|
|
56
|
+
if (assertions.length > 1) {
|
|
57
|
+
throw new Error(`SAML Response carries ${assertions.length} Assertions; a bearer grant takes one`);
|
|
58
|
+
}
|
|
59
|
+
const assertion = assertions[0];
|
|
60
|
+
declareInheritedNamespaces(assertion);
|
|
61
|
+
const serialized = new xmldom_1.XMLSerializer().serializeToString(assertion);
|
|
62
|
+
return Buffer.from(serialized, 'utf8').toString('base64url');
|
|
63
|
+
}
|
|
64
|
+
function isElement(node, namespace, localName) {
|
|
65
|
+
return (!!node && node.namespaceURI === namespace && node.localName === localName);
|
|
66
|
+
}
|
|
67
|
+
const XMLNS_NS = 'http://www.w3.org/2000/xmlns/';
|
|
68
|
+
const isNamespaceDeclaration = (name) => name === 'xmlns' || name.startsWith('xmlns:');
|
|
69
|
+
/**
|
|
70
|
+
* Copies onto `element` every namespace declaration in scope from its
|
|
71
|
+
* ancestors that it does not make itself. Ancestors are walked innermost
|
|
72
|
+
* first, so the nearest declaration of a prefix wins, as it did in place.
|
|
73
|
+
*/
|
|
74
|
+
function declareInheritedNamespaces(element) {
|
|
75
|
+
const declared = new Set();
|
|
76
|
+
for (let i = 0; i < element.attributes.length; i++) {
|
|
77
|
+
const name = element.attributes.item(i)?.name;
|
|
78
|
+
if (name && isNamespaceDeclaration(name))
|
|
79
|
+
declared.add(name);
|
|
80
|
+
}
|
|
81
|
+
for (let ancestor = element.parentNode; ancestor && ancestor.nodeType === 1; ancestor = ancestor.parentNode) {
|
|
82
|
+
const attributes = ancestor.attributes;
|
|
83
|
+
for (let i = 0; i < attributes.length; i++) {
|
|
84
|
+
const attribute = attributes.item(i);
|
|
85
|
+
if (attribute &&
|
|
86
|
+
isNamespaceDeclaration(attribute.name) &&
|
|
87
|
+
!declared.has(attribute.name)) {
|
|
88
|
+
element.setAttributeNS(XMLNS_NS, attribute.name, attribute.value);
|
|
89
|
+
declared.add(attribute.name);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function childElements(parent) {
|
|
95
|
+
const out = [];
|
|
96
|
+
for (let n = parent.firstChild; n; n = n.nextSibling) {
|
|
97
|
+
if (n.nodeType === 1)
|
|
98
|
+
out.push(n);
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -9,10 +9,10 @@ export type { OidcCallbackResult } from './auth/oidcBrowserAuth';
|
|
|
9
9
|
export { withOidcCallbackServer } from './auth/oidcBrowserAuth';
|
|
10
10
|
export { withSamlCallbackServer } from './auth/saml2Auth';
|
|
11
11
|
export { BrowserAuthError, RefreshError, ServiceKeyError, SessionDataError, TokenProviderError, ValidationError, } from './errors/TokenProviderErrors';
|
|
12
|
-
export type { AuthorizationCodeProviderConfig, ClientCredentialsProviderConfig,
|
|
13
|
-
export { AuthorizationCodeProvider, BaseTokenProvider, ClientCredentialsProvider,
|
|
12
|
+
export type { AuthorizationCodeProviderConfig, ClientCredentialsProviderConfig, OidcBrowserProviderConfig, OidcDeviceFlowProviderConfig, OidcPasswordProviderConfig, OidcTokenExchangeProviderConfig, Saml2BearerProviderConfig, Saml2PureProviderConfig, UaaPasscodeProviderConfig, } from './providers';
|
|
13
|
+
export { AuthorizationCodeProvider, BaseTokenProvider, ClientCredentialsProvider, OidcBrowserProvider, OidcDeviceFlowProvider, OidcPasswordProvider, OidcTokenExchangeProvider, Saml2BearerProvider, Saml2PureProvider, UaaPasscodeProvider, } from './providers';
|
|
14
14
|
export { SsoProviderFactory } from './sso/SsoProviderFactory';
|
|
15
15
|
export type { SsoProviderConfig, SsoProviderInstance } from './sso/types';
|
|
16
16
|
export type { BrowserCallbackStrategyOptions, CallbackStrategyOptions, ExternalCodeStrategyOptions, ManualStrategyOptions, StaticCodeStrategyOptions, } from './strategies';
|
|
17
|
-
export { asOidcResult, BrowserCallbackStrategy, browserCallbackStrategy, DEFAULT_CALLBACK_PORT, DEFAULT_LOGIN_TIMEOUT_MS, externalCodeStrategy, manualPasteStrategy, manualSamlResponseStrategy, oidcCallbackStrategy, samlCallbackStrategy, staticCodeStrategy, } from './strategies';
|
|
17
|
+
export { asOidcResult, BrowserCallbackStrategy, browserCallbackStrategy, DEFAULT_CALLBACK_PORT, DEFAULT_LOGIN_TIMEOUT_MS, externalCodeStrategy, manualPasscodeStrategy, manualPasteStrategy, manualSamlResponseStrategy, oidcCallbackStrategy, samlCallbackStrategy, staticCodeStrategy, } from './strategies';
|
|
18
18
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAClE,YAAY,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,GAChB,MAAM,8BAA8B,CAAC;AACtC,YAAY,EACV,+BAA+B,EAC/B,+BAA+B,EAC/B,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAClE,YAAY,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,GAChB,MAAM,8BAA8B,CAAC;AACtC,YAAY,EACV,+BAA+B,EAC/B,+BAA+B,EAC/B,yBAAyB,EACzB,4BAA4B,EAC5B,0BAA0B,EAC1B,+BAA+B,EAC/B,yBAAyB,EACzB,uBAAuB,EACvB,yBAAyB,GAC1B,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,yBAAyB,EACzB,iBAAiB,EACjB,yBAAyB,EACzB,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,yBAAyB,EACzB,mBAAmB,EACnB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,YAAY,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAC1E,YAAY,EACV,8BAA8B,EAC9B,uBAAuB,EACvB,2BAA2B,EAC3B,qBAAqB,EACrB,yBAAyB,GAC1B,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,YAAY,EACZ,uBAAuB,EACvB,uBAAuB,EACvB,qBAAqB,EACrB,wBAAwB,EACxB,oBAAoB,EACpB,sBAAsB,EACtB,mBAAmB,EACnB,0BAA0B,EAC1B,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,cAAc,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Provides token providers
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.staticCodeStrategy = exports.samlCallbackStrategy = exports.oidcCallbackStrategy = exports.manualSamlResponseStrategy = exports.manualPasteStrategy = exports.externalCodeStrategy = exports.DEFAULT_LOGIN_TIMEOUT_MS = exports.DEFAULT_CALLBACK_PORT = exports.browserCallbackStrategy = exports.BrowserCallbackStrategy = exports.asOidcResult = exports.SsoProviderFactory = exports.Saml2PureProvider = exports.Saml2BearerProvider = exports.OidcTokenExchangeProvider = exports.OidcPasswordProvider = exports.OidcDeviceFlowProvider = exports.OidcBrowserProvider = exports.
|
|
9
|
+
exports.staticCodeStrategy = exports.samlCallbackStrategy = exports.oidcCallbackStrategy = exports.manualSamlResponseStrategy = exports.manualPasteStrategy = exports.manualPasscodeStrategy = exports.externalCodeStrategy = exports.DEFAULT_LOGIN_TIMEOUT_MS = exports.DEFAULT_CALLBACK_PORT = exports.browserCallbackStrategy = exports.BrowserCallbackStrategy = exports.asOidcResult = exports.SsoProviderFactory = exports.UaaPasscodeProvider = exports.Saml2PureProvider = exports.Saml2BearerProvider = exports.OidcTokenExchangeProvider = exports.OidcPasswordProvider = exports.OidcDeviceFlowProvider = exports.OidcBrowserProvider = exports.ClientCredentialsProvider = exports.BaseTokenProvider = exports.AuthorizationCodeProvider = exports.ValidationError = exports.TokenProviderError = exports.SessionDataError = exports.ServiceKeyError = exports.RefreshError = exports.BrowserAuthError = exports.withSamlCallbackServer = exports.withOidcCallbackServer = exports.withBrowserCallbackServer = void 0;
|
|
10
10
|
// Callback server factories — "take the transport this package gives".
|
|
11
11
|
var callbackServer_1 = require("./auth/callbackServer");
|
|
12
12
|
Object.defineProperty(exports, "withBrowserCallbackServer", { enumerable: true, get: function () { return callbackServer_1.withBrowserCallbackServer; } });
|
|
@@ -27,13 +27,13 @@ var providers_1 = require("./providers");
|
|
|
27
27
|
Object.defineProperty(exports, "AuthorizationCodeProvider", { enumerable: true, get: function () { return providers_1.AuthorizationCodeProvider; } });
|
|
28
28
|
Object.defineProperty(exports, "BaseTokenProvider", { enumerable: true, get: function () { return providers_1.BaseTokenProvider; } });
|
|
29
29
|
Object.defineProperty(exports, "ClientCredentialsProvider", { enumerable: true, get: function () { return providers_1.ClientCredentialsProvider; } });
|
|
30
|
-
Object.defineProperty(exports, "DeviceFlowProvider", { enumerable: true, get: function () { return providers_1.DeviceFlowProvider; } });
|
|
31
30
|
Object.defineProperty(exports, "OidcBrowserProvider", { enumerable: true, get: function () { return providers_1.OidcBrowserProvider; } });
|
|
32
31
|
Object.defineProperty(exports, "OidcDeviceFlowProvider", { enumerable: true, get: function () { return providers_1.OidcDeviceFlowProvider; } });
|
|
33
32
|
Object.defineProperty(exports, "OidcPasswordProvider", { enumerable: true, get: function () { return providers_1.OidcPasswordProvider; } });
|
|
34
33
|
Object.defineProperty(exports, "OidcTokenExchangeProvider", { enumerable: true, get: function () { return providers_1.OidcTokenExchangeProvider; } });
|
|
35
34
|
Object.defineProperty(exports, "Saml2BearerProvider", { enumerable: true, get: function () { return providers_1.Saml2BearerProvider; } });
|
|
36
35
|
Object.defineProperty(exports, "Saml2PureProvider", { enumerable: true, get: function () { return providers_1.Saml2PureProvider; } });
|
|
36
|
+
Object.defineProperty(exports, "UaaPasscodeProvider", { enumerable: true, get: function () { return providers_1.UaaPasscodeProvider; } });
|
|
37
37
|
// SSO factory
|
|
38
38
|
var SsoProviderFactory_1 = require("./sso/SsoProviderFactory");
|
|
39
39
|
Object.defineProperty(exports, "SsoProviderFactory", { enumerable: true, get: function () { return SsoProviderFactory_1.SsoProviderFactory; } });
|
|
@@ -45,6 +45,7 @@ Object.defineProperty(exports, "browserCallbackStrategy", { enumerable: true, ge
|
|
|
45
45
|
Object.defineProperty(exports, "DEFAULT_CALLBACK_PORT", { enumerable: true, get: function () { return strategies_1.DEFAULT_CALLBACK_PORT; } });
|
|
46
46
|
Object.defineProperty(exports, "DEFAULT_LOGIN_TIMEOUT_MS", { enumerable: true, get: function () { return strategies_1.DEFAULT_LOGIN_TIMEOUT_MS; } });
|
|
47
47
|
Object.defineProperty(exports, "externalCodeStrategy", { enumerable: true, get: function () { return strategies_1.externalCodeStrategy; } });
|
|
48
|
+
Object.defineProperty(exports, "manualPasscodeStrategy", { enumerable: true, get: function () { return strategies_1.manualPasscodeStrategy; } });
|
|
48
49
|
Object.defineProperty(exports, "manualPasteStrategy", { enumerable: true, get: function () { return strategies_1.manualPasteStrategy; } });
|
|
49
50
|
Object.defineProperty(exports, "manualSamlResponseStrategy", { enumerable: true, get: function () { return strategies_1.manualSamlResponseStrategy; } });
|
|
50
51
|
Object.defineProperty(exports, "oidcCallbackStrategy", { enumerable: true, get: function () { return strategies_1.oidcCallbackStrategy; } });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Saml2BearerProvider.d.ts","sourceRoot":"","sources":["../../src/providers/Saml2BearerProvider.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,eAAe,EAChB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"Saml2BearerProvider.d.ts","sourceRoot":"","sources":["../../src/providers/Saml2BearerProvider.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,eAAe,EAChB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;AAM9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EACV,yBAAyB,EACzB,iBAAiB,EAClB,MAAM,cAAc,CAAC;AAOtB,MAAM,WAAW,yBACf,SAAQ,iBAAiB,EACvB,yBAAyB;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,qBAAa,mBAAoB,SAAQ,iBAAiB;IACxD,OAAO,CAAC,MAAM,CAA4B;gBAE9B,MAAM,EAAE,yBAAyB;IAiB7C,SAAS,CAAC,WAAW,IAAI,eAAe;cAIxB,YAAY,IAAI,OAAO,CAAC,YAAY,CAAC;IAsBrD;;;;OAIG;cACa,cAAc,IAAI,OAAO,CAAC,YAAY,CAAC;CAqBxD"}
|
|
@@ -8,6 +8,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
8
8
|
exports.Saml2BearerProvider = void 0;
|
|
9
9
|
const interfaces_auth_1 = require("@mcp-abap-adt/interfaces-auth");
|
|
10
10
|
const saml2TokenExchange_1 = require("../auth/saml2TokenExchange");
|
|
11
|
+
const samlBearerAssertion_1 = require("../auth/samlBearerAssertion");
|
|
11
12
|
const BaseTokenProvider_1 = require("./BaseTokenProvider");
|
|
12
13
|
const saml2Utils_1 = require("./saml2Utils");
|
|
13
14
|
class Saml2BearerProvider extends BaseTokenProvider_1.BaseTokenProvider {
|
|
@@ -33,7 +34,9 @@ class Saml2BearerProvider extends BaseTokenProvider_1.BaseTokenProvider {
|
|
|
33
34
|
async performLogin() {
|
|
34
35
|
const samlResponse = await (0, saml2Utils_1.getSamlAssertion)(this.config);
|
|
35
36
|
const tokenUrl = (0, saml2Utils_1.resolveTokenUrl)(this.config);
|
|
36
|
-
|
|
37
|
+
// RFC 7522 takes one base64url Assertion; a login delivers the whole
|
|
38
|
+
// Response in standard base64, which a conforming endpoint refuses.
|
|
39
|
+
const tokens = await (0, saml2TokenExchange_1.exchangeSamlAssertion)((0, samlBearerAssertion_1.toBearerAssertion)(samlResponse), tokenUrl, this.config.clientId, this.config.clientSecret, this.logger);
|
|
37
40
|
return {
|
|
38
41
|
authorizationToken: tokens.accessToken,
|
|
39
42
|
refreshToken: tokens.refreshToken,
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UAA / XSUAA one-time passcode provider — the login `cf login --sso` uses.
|
|
3
|
+
*
|
|
4
|
+
* Nothing is opened and nothing listens on this machine: the user fetches a
|
|
5
|
+
* code from `<uaaUrl>/passcode` in any browser, anywhere, logging in however
|
|
6
|
+
* the identity zone asks (SSO through a corporate IdP, MFA), and hands it to
|
|
7
|
+
* the strategy. The provider exchanges it for tokens and refreshes them
|
|
8
|
+
* afterwards, so the user is asked again only when the refresh token is gone.
|
|
9
|
+
*/
|
|
10
|
+
import type { IAuthorizationStrategy, ITokenResult, OAuth2GrantType } from '@mcp-abap-adt/interfaces-auth';
|
|
11
|
+
import type { ILogger } from '@mcp-abap-adt/interfaces-utils';
|
|
12
|
+
import { BaseTokenProvider } from './BaseTokenProvider';
|
|
13
|
+
export interface UaaPasscodeProviderConfig {
|
|
14
|
+
/** UAA / XSUAA base URL, e.g. `https://<subdomain>.authentication.<region>.hana.ondemand.com`. */
|
|
15
|
+
uaaUrl: string;
|
|
16
|
+
/** A client allowed the `password` grant; add `refresh_token` to keep the session. */
|
|
17
|
+
clientId: string;
|
|
18
|
+
/** Omitted for a public client, which authenticates with an empty secret. */
|
|
19
|
+
clientSecret?: string;
|
|
20
|
+
/**
|
|
21
|
+
* How the user's code reaches the provider. The strategy is handed
|
|
22
|
+
* `<uaaUrl>/passcode` as the URL to send the user to, and returns the code.
|
|
23
|
+
* Defaults to `manualPasscodeStrategy()`: announce the URL, read the code
|
|
24
|
+
* from the terminal.
|
|
25
|
+
*/
|
|
26
|
+
authorization?: IAuthorizationStrategy<string>;
|
|
27
|
+
accessToken?: string;
|
|
28
|
+
refreshToken?: string;
|
|
29
|
+
logger?: ILogger;
|
|
30
|
+
}
|
|
31
|
+
export declare class UaaPasscodeProvider extends BaseTokenProvider {
|
|
32
|
+
private readonly config;
|
|
33
|
+
constructor(config: UaaPasscodeProviderConfig);
|
|
34
|
+
protected getAuthType(): OAuth2GrantType;
|
|
35
|
+
private get baseUrl();
|
|
36
|
+
protected performLogin(): Promise<ITokenResult>;
|
|
37
|
+
/**
|
|
38
|
+
* A failure is thrown, not handled: BaseTokenProvider drops the refresh
|
|
39
|
+
* token and asks for a new passcode through performLogin().
|
|
40
|
+
*/
|
|
41
|
+
protected performRefresh(): Promise<ITokenResult>;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=UaaPasscodeProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UaaPasscodeProvider.d.ts","sourceRoot":"","sources":["../../src/providers/UaaPasscodeProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EACV,sBAAsB,EACtB,YAAY,EACZ,eAAe,EAChB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;AAI9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,WAAW,yBAAyB;IACxC,kGAAkG;IAClG,MAAM,EAAE,MAAM,CAAC;IACf,sFAAsF;IACtF,QAAQ,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,sBAAsB,CAAC,MAAM,CAAC,CAAC;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,qBAAa,mBAAoB,SAAQ,iBAAiB;IACxD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA4B;gBAEvC,MAAM,EAAE,yBAAyB;IAa7C,SAAS,CAAC,WAAW,IAAI,eAAe;IAIxC,OAAO,KAAK,OAAO,GAElB;cAEe,YAAY,IAAI,OAAO,CAAC,YAAY,CAAC;IAqCrD;;;OAGG;cACa,cAAc,IAAI,OAAO,CAAC,YAAY,CAAC;CAkBxD"}
|