@mcp-abap-adt/auth-providers 1.2.0 → 2.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.
Files changed (63) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/README.md +373 -62
  3. package/bin/auth-authorization-code.ts +5 -3
  4. package/dist/__tests__/helpers/netHelpers.d.ts +16 -0
  5. package/dist/__tests__/helpers/netHelpers.d.ts.map +1 -1
  6. package/dist/__tests__/helpers/netHelpers.js +29 -0
  7. package/dist/auth/announce.d.ts +11 -0
  8. package/dist/auth/announce.d.ts.map +1 -0
  9. package/dist/auth/announce.js +19 -0
  10. package/dist/auth/browserAuth.d.ts +21 -14
  11. package/dist/auth/browserAuth.d.ts.map +1 -1
  12. package/dist/auth/browserAuth.js +25 -111
  13. package/dist/auth/callbackServer.d.ts +6 -0
  14. package/dist/auth/callbackServer.d.ts.map +1 -1
  15. package/dist/auth/callbackServer.js +31 -21
  16. package/dist/auth/oidcBrowserAuth.d.ts +2 -9
  17. package/dist/auth/oidcBrowserAuth.d.ts.map +1 -1
  18. package/dist/auth/oidcBrowserAuth.js +16 -125
  19. package/dist/auth/saml2Auth.d.ts +2 -9
  20. package/dist/auth/saml2Auth.d.ts.map +1 -1
  21. package/dist/auth/saml2Auth.js +10 -130
  22. package/dist/index.d.ts +6 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +21 -1
  25. package/dist/providers/AuthorizationCodeProvider.d.ts +7 -3
  26. package/dist/providers/AuthorizationCodeProvider.d.ts.map +1 -1
  27. package/dist/providers/AuthorizationCodeProvider.js +51 -40
  28. package/dist/providers/DeviceFlowProvider.d.ts +2 -1
  29. package/dist/providers/DeviceFlowProvider.d.ts.map +1 -1
  30. package/dist/providers/DeviceFlowProvider.js +13 -10
  31. package/dist/providers/OidcBrowserProvider.d.ts +4 -7
  32. package/dist/providers/OidcBrowserProvider.d.ts.map +1 -1
  33. package/dist/providers/OidcBrowserProvider.js +54 -57
  34. package/dist/providers/OidcDeviceFlowProvider.d.ts.map +1 -1
  35. package/dist/providers/OidcDeviceFlowProvider.js +9 -7
  36. package/dist/providers/Saml2BearerProvider.d.ts +2 -2
  37. package/dist/providers/Saml2BearerProvider.d.ts.map +1 -1
  38. package/dist/providers/Saml2BearerProvider.js +3 -0
  39. package/dist/providers/Saml2PureProvider.d.ts +2 -2
  40. package/dist/providers/Saml2PureProvider.d.ts.map +1 -1
  41. package/dist/providers/Saml2PureProvider.js +3 -0
  42. package/dist/providers/saml2Utils.d.ts +12 -12
  43. package/dist/providers/saml2Utils.d.ts.map +1 -1
  44. package/dist/providers/saml2Utils.js +46 -26
  45. package/dist/strategies/BrowserCallbackStrategy.d.ts +64 -0
  46. package/dist/strategies/BrowserCallbackStrategy.d.ts.map +1 -0
  47. package/dist/strategies/BrowserCallbackStrategy.js +210 -0
  48. package/dist/strategies/asOidcResult.d.ts +11 -0
  49. package/dist/strategies/asOidcResult.d.ts.map +1 -0
  50. package/dist/strategies/asOidcResult.js +30 -0
  51. package/dist/strategies/codeStrategies.d.ts +22 -0
  52. package/dist/strategies/codeStrategies.d.ts.map +1 -0
  53. package/dist/strategies/codeStrategies.js +39 -0
  54. package/dist/strategies/index.d.ts +8 -0
  55. package/dist/strategies/index.d.ts.map +1 -0
  56. package/dist/strategies/index.js +18 -0
  57. package/dist/strategies/manualStrategies.d.ts +20 -0
  58. package/dist/strategies/manualStrategies.d.ts.map +1 -0
  59. package/dist/strategies/manualStrategies.js +77 -0
  60. package/package.json +2 -2
  61. package/dist/auth/manualInput.d.ts +0 -5
  62. package/dist/auth/manualInput.d.ts.map +0 -1
  63. package/dist/auth/manualInput.js +0 -19
@@ -0,0 +1,210 @@
1
+ "use strict";
2
+ /**
3
+ * Reaching the authorization URL with a browser, receiving the redirect on a
4
+ * local socket.
5
+ *
6
+ * The transport is injected rather than assumed: a consumer that already runs
7
+ * an HTTP server can pass its own `CallbackServerFactory` and keep everything
8
+ * else here.
9
+ */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.BrowserCallbackStrategy = exports.DEFAULT_LOGIN_TIMEOUT_MS = exports.DEFAULT_CALLBACK_PORT = void 0;
45
+ exports.browserCallbackStrategy = browserCallbackStrategy;
46
+ exports.oidcCallbackStrategy = oidcCallbackStrategy;
47
+ exports.samlCallbackStrategy = samlCallbackStrategy;
48
+ const net = __importStar(require("node:net"));
49
+ const announce_1 = require("../auth/announce");
50
+ const browserAuth_1 = require("../auth/browserAuth");
51
+ const callbackServer_1 = require("../auth/callbackServer");
52
+ const oidcBrowserAuth_1 = require("../auth/oidcBrowserAuth");
53
+ const saml2Auth_1 = require("../auth/saml2Auth");
54
+ /**
55
+ * Above Linux's `ip_local_port_range` (32768–60999), so an outbound connection
56
+ * never squats on it, and far from the 3001/3333 range application servers use.
57
+ */
58
+ exports.DEFAULT_CALLBACK_PORT = 61001;
59
+ /** How long an interactive login may wait for its callback. */
60
+ exports.DEFAULT_LOGIN_TIMEOUT_MS = 30_000;
61
+ /**
62
+ * Kept for its wording, not its certainty.
63
+ *
64
+ * `AuthBroker` matches /already in use/i to tell a busy port from every other
65
+ * failure, and the bind error Node raises says `EADDRINUSE` instead. Skipped
66
+ * entirely for an ephemeral port: there is nothing to check, and the answer
67
+ * would be about a port we are not going to get.
68
+ */
69
+ async function assertPortAvailable(port) {
70
+ if (port === 0)
71
+ return;
72
+ const free = await new Promise((resolve) => {
73
+ const probe = net.createServer();
74
+ probe.once('error', () => resolve(false));
75
+ probe.listen(port, () => probe.close(() => resolve(true)));
76
+ });
77
+ if (!free) {
78
+ throw new Error(`Port ${port} is already in use. Please specify a different port or free the port.`);
79
+ }
80
+ }
81
+ class BrowserCallbackStrategy {
82
+ options;
83
+ inFlight = null;
84
+ controller = null;
85
+ disposed = false;
86
+ constructor(options) {
87
+ this.options = options;
88
+ }
89
+ async authorize(request) {
90
+ if (this.disposed) {
91
+ throw new Error('BrowserCallbackStrategy has been disposed');
92
+ }
93
+ if (this.inFlight) {
94
+ throw new Error('BrowserCallbackStrategy is already authorizing; it holds a single port');
95
+ }
96
+ const port = this.options.port ?? exports.DEFAULT_CALLBACK_PORT;
97
+ const controller = new AbortController();
98
+ this.controller = controller;
99
+ const relay = () => controller.abort();
100
+ this.options.signal?.addEventListener('abort', relay, { once: true });
101
+ // A signal that was already aborted fires no event, so registering a
102
+ // listener for it is not enough — the login would proceed as if nobody had
103
+ // cancelled it.
104
+ if (this.options.signal?.aborted)
105
+ controller.abort();
106
+ const announce = (0, announce_1.announcer)(request.logger);
107
+ const browser = this.options.browser ?? 'none';
108
+ // Wrapped in an immediately-invoked async function, and assigned to
109
+ // `inFlight` in the same synchronous turn as the controller. The port probe
110
+ // awaits, and a `dispose` landing in that window used to find both fields
111
+ // still null: it resolved, reporting everything released, and the login
112
+ // then went on to bind a socket behind it.
113
+ const run = (async () => {
114
+ await assertPortAvailable(port);
115
+ if (controller.signal.aborted) {
116
+ throw new Error('Authorization aborted before the callback server bound');
117
+ }
118
+ return await this.options.callbackServer({
119
+ port,
120
+ timeoutMs: this.options.timeoutMs ?? exports.DEFAULT_LOGIN_TIMEOUT_MS,
121
+ signal: controller.signal,
122
+ logger: request.logger,
123
+ }, async (server) => {
124
+ // Thrown before anything is opened: a redirect the provider cannot
125
+ // honour must fail here, not as a callback that never arrives.
126
+ const url = await request.buildAuthorizationUrl(server.redirectUri);
127
+ const waiting = server.waitForResult();
128
+ // Built here, not earlier: the launcher's messages name the URI that is
129
+ // actually bound, which with `port: 0` nothing knew until now.
130
+ const open = this.options.openUrl ??
131
+ ((u, which, redirectUri) => (0, browserAuth_1.launchBrowser)(u, which, redirectUri, announce, request.logger ?? null, this.options.remoteHint?.(redirectUri)));
132
+ // Not awaited: a launcher that hangs must not delay the timeout or the
133
+ // release, and one that fails ends the scope through `fail`.
134
+ void open(url, browser, server.redirectUri).catch((error) => {
135
+ const message = error instanceof Error ? error.message : String(error);
136
+ request.logger?.error(`Failed to open browser: ${message}. Open manually: ${url}`, { error: message, url });
137
+ server.fail(new Error(`Browser opening failed. Open manually: ${url}`));
138
+ });
139
+ return {
140
+ payload: await waiting,
141
+ redirectUri: server.redirectUri,
142
+ };
143
+ });
144
+ })();
145
+ this.inFlight = run;
146
+ try {
147
+ return await run;
148
+ }
149
+ finally {
150
+ this.options.signal?.removeEventListener('abort', relay);
151
+ this.controller = null;
152
+ this.inFlight = null;
153
+ }
154
+ }
155
+ /**
156
+ * Idempotent; ends an authorization in flight and resolves only once the
157
+ * factory has settled — which it does after the socket is free.
158
+ */
159
+ async dispose() {
160
+ this.disposed = true;
161
+ const pending = this.inFlight;
162
+ this.controller?.abort();
163
+ if (pending)
164
+ await pending.catch(() => undefined);
165
+ }
166
+ }
167
+ exports.BrowserCallbackStrategy = BrowserCallbackStrategy;
168
+ // Each ready constructor defaults the transport rather than dictating it: a
169
+ // supplied `callbackServer` wins, which is what makes substitution reachable
170
+ // without dropping to the class.
171
+ /**
172
+ * The paste form `withBrowserCallbackServer` serves on `/` — and nothing else
173
+ * does. Since the terminal-paste channel left with `startBrowserAuth`, this
174
+ * form is the only remaining way in for a browser on another machine, so the
175
+ * address it names has to be one that machine can actually reach.
176
+ *
177
+ * Which is why the host is left as a placeholder rather than taken from
178
+ * `redirectUri`. That URI is bound for *this* process, so its origin is
179
+ * `http://localhost:<port>` — precisely the address that does not work from
180
+ * anywhere else, addressed to the one reader who is not here. The port is real
181
+ * and is kept; the host is the reader's to fill in.
182
+ */
183
+ const uaaPasteHint = (redirectUri) => {
184
+ const { protocol, port } = new URL(redirectUri);
185
+ return (' If your browser is on another machine, copy the `code` from the ' +
186
+ `address bar after login and paste it at ${protocol}//<this-host>:${port}/`);
187
+ };
188
+ function browserCallbackStrategy(options = {}) {
189
+ return new BrowserCallbackStrategy({
190
+ ...options,
191
+ callbackServer: options.callbackServer ?? callbackServer_1.withBrowserCallbackServer,
192
+ // An explicit hint always wins. Otherwise the default applies only when we
193
+ // supplied the transport: an injected receiver may have no `/` route, and
194
+ // the replaceable receiver is the whole point of this design, so assuming
195
+ // one would advertise a 404 to exactly the consumers the design is for.
196
+ remoteHint: options.remoteHint ?? (options.callbackServer ? undefined : uaaPasteHint),
197
+ });
198
+ }
199
+ function oidcCallbackStrategy(options = {}) {
200
+ return new BrowserCallbackStrategy({
201
+ ...options,
202
+ callbackServer: options.callbackServer ?? oidcBrowserAuth_1.withOidcCallbackServer,
203
+ });
204
+ }
205
+ function samlCallbackStrategy(options = {}) {
206
+ return new BrowserCallbackStrategy({
207
+ ...options,
208
+ callbackServer: options.callbackServer ?? saml2Auth_1.withSamlCallbackServer,
209
+ });
210
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Adapts a code-producing strategy to the OIDC provider's payload type.
3
+ *
4
+ * Everything a human or a consumer hands back is a string; only a real callback
5
+ * carries `state` beside the code. Rather than an OIDC twin of every strategy,
6
+ * the mismatch is bridged in one place.
7
+ */
8
+ import type { IAuthorizationStrategy } from '@mcp-abap-adt/interfaces';
9
+ import type { OidcCallbackResult } from '../auth/oidcBrowserAuth';
10
+ export declare function asOidcResult(inner: IAuthorizationStrategy<string>): IAuthorizationStrategy<OidcCallbackResult>;
11
+ //# sourceMappingURL=asOidcResult.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asOidcResult.d.ts","sourceRoot":"","sources":["../../src/strategies/asOidcResult.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAGV,sBAAsB,EACvB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAElE,wBAAgB,YAAY,CAC1B,KAAK,EAAE,sBAAsB,CAAC,MAAM,CAAC,GACpC,sBAAsB,CAAC,kBAAkB,CAAC,CAqB5C"}
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ /**
3
+ * Adapts a code-producing strategy to the OIDC provider's payload type.
4
+ *
5
+ * Everything a human or a consumer hands back is a string; only a real callback
6
+ * carries `state` beside the code. Rather than an OIDC twin of every strategy,
7
+ * the mismatch is bridged in one place.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.asOidcResult = asOidcResult;
11
+ function asOidcResult(inner) {
12
+ const adapted = {
13
+ async authorize(request) {
14
+ const outcome = await inner.authorize(request);
15
+ // No `state`: a value that never travelled through a redirect has none
16
+ // to check.
17
+ return {
18
+ payload: { code: outcome.payload },
19
+ redirectUri: outcome.redirectUri,
20
+ };
21
+ },
22
+ };
23
+ // Delegated, not reimplemented: the consumer holds only the adapter, so an
24
+ // undelegated dispose would strand whatever the wrapped strategy owns. Absent
25
+ // when the wrapped strategy has none, so optionality survives the wrapping.
26
+ if (inner.dispose) {
27
+ adapted.dispose = () => inner.dispose?.() ?? Promise.resolve();
28
+ }
29
+ return adapted;
30
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Strategies where the consumer supplies the payload.
3
+ *
4
+ * The two are separate because one needs the authorization URL and the other
5
+ * does not — and asking for a URL that is not needed would drag in OIDC
6
+ * discovery that a static payload never required.
7
+ */
8
+ import type { IAuthorizationStrategy } from '@mcp-abap-adt/interfaces';
9
+ export interface ExternalCodeStrategyOptions {
10
+ redirectUri?: string;
11
+ /** Receives the assembled URL — so the code returned matches its PKCE challenge. */
12
+ provide: (authorizationUrl: string) => Promise<string>;
13
+ }
14
+ export interface StaticCodeStrategyOptions {
15
+ redirectUri?: string;
16
+ payload: string;
17
+ }
18
+ /** The consumer drives its own interactive flow and needs the URL to do it. */
19
+ export declare function externalCodeStrategy(options: ExternalCodeStrategyOptions): IAuthorizationStrategy<string>;
20
+ /** The consumer already holds the payload; the builder is never called. */
21
+ export declare function staticCodeStrategy(options: StaticCodeStrategyOptions): IAuthorizationStrategy<string>;
22
+ //# sourceMappingURL=codeStrategies.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codeStrategies.d.ts","sourceRoot":"","sources":["../../src/strategies/codeStrategies.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAGV,sBAAsB,EACvB,MAAM,0BAA0B,CAAC;AAMlC,MAAM,WAAW,2BAA2B;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oFAAoF;IACpF,OAAO,EAAE,CAAC,gBAAgB,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACxD;AAED,MAAM,WAAW,yBAAyB;IACxC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,+EAA+E;AAC/E,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,2BAA2B,GACnC,sBAAsB,CAAC,MAAM,CAAC,CAchC;AAED,2EAA2E;AAC3E,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,yBAAyB,GACjC,sBAAsB,CAAC,MAAM,CAAC,CAUhC"}
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ /**
3
+ * Strategies where the consumer supplies the payload.
4
+ *
5
+ * The two are separate because one needs the authorization URL and the other
6
+ * does not — and asking for a URL that is not needed would drag in OIDC
7
+ * discovery that a static payload never required.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.externalCodeStrategy = externalCodeStrategy;
11
+ exports.staticCodeStrategy = staticCodeStrategy;
12
+ const BrowserCallbackStrategy_1 = require("./BrowserCallbackStrategy");
13
+ const defaultRedirectUri = () => `http://localhost:${BrowserCallbackStrategy_1.DEFAULT_CALLBACK_PORT}/callback`;
14
+ /** The consumer drives its own interactive flow and needs the URL to do it. */
15
+ function externalCodeStrategy(options) {
16
+ const redirectUri = options.redirectUri ?? defaultRedirectUri();
17
+ return {
18
+ async authorize(request) {
19
+ const url = await request.buildAuthorizationUrl(redirectUri);
20
+ const payload = await options.provide(url);
21
+ if (!payload) {
22
+ throw new Error('Authorization code provider returned an empty value');
23
+ }
24
+ return { payload, redirectUri };
25
+ },
26
+ };
27
+ }
28
+ /** The consumer already holds the payload; the builder is never called. */
29
+ function staticCodeStrategy(options) {
30
+ const redirectUri = options.redirectUri ?? defaultRedirectUri();
31
+ if (!options.payload) {
32
+ throw new Error('staticCodeStrategy requires a payload');
33
+ }
34
+ return {
35
+ async authorize() {
36
+ return { payload: options.payload, redirectUri };
37
+ },
38
+ };
39
+ }
@@ -0,0 +1,8 @@
1
+ export { asOidcResult } from './asOidcResult';
2
+ export type { BrowserCallbackStrategyOptions, CallbackStrategyOptions, } from './BrowserCallbackStrategy';
3
+ export { BrowserCallbackStrategy, browserCallbackStrategy, DEFAULT_CALLBACK_PORT, DEFAULT_LOGIN_TIMEOUT_MS, oidcCallbackStrategy, samlCallbackStrategy, } from './BrowserCallbackStrategy';
4
+ export type { ExternalCodeStrategyOptions, StaticCodeStrategyOptions, } from './codeStrategies';
5
+ export { externalCodeStrategy, staticCodeStrategy, } from './codeStrategies';
6
+ export type { ManualStrategyOptions } from './manualStrategies';
7
+ export { manualPasteStrategy, manualSamlResponseStrategy, } from './manualStrategies';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/strategies/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EACV,8BAA8B,EAC9B,uBAAuB,GACxB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,qBAAqB,EACrB,wBAAwB,EACxB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EACV,2BAA2B,EAC3B,yBAAyB,GAC1B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EACL,mBAAmB,EACnB,0BAA0B,GAC3B,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.manualSamlResponseStrategy = exports.manualPasteStrategy = exports.staticCodeStrategy = exports.externalCodeStrategy = exports.samlCallbackStrategy = exports.oidcCallbackStrategy = exports.DEFAULT_LOGIN_TIMEOUT_MS = exports.DEFAULT_CALLBACK_PORT = exports.browserCallbackStrategy = exports.BrowserCallbackStrategy = exports.asOidcResult = void 0;
4
+ var asOidcResult_1 = require("./asOidcResult");
5
+ Object.defineProperty(exports, "asOidcResult", { enumerable: true, get: function () { return asOidcResult_1.asOidcResult; } });
6
+ var BrowserCallbackStrategy_1 = require("./BrowserCallbackStrategy");
7
+ Object.defineProperty(exports, "BrowserCallbackStrategy", { enumerable: true, get: function () { return BrowserCallbackStrategy_1.BrowserCallbackStrategy; } });
8
+ Object.defineProperty(exports, "browserCallbackStrategy", { enumerable: true, get: function () { return BrowserCallbackStrategy_1.browserCallbackStrategy; } });
9
+ Object.defineProperty(exports, "DEFAULT_CALLBACK_PORT", { enumerable: true, get: function () { return BrowserCallbackStrategy_1.DEFAULT_CALLBACK_PORT; } });
10
+ Object.defineProperty(exports, "DEFAULT_LOGIN_TIMEOUT_MS", { enumerable: true, get: function () { return BrowserCallbackStrategy_1.DEFAULT_LOGIN_TIMEOUT_MS; } });
11
+ Object.defineProperty(exports, "oidcCallbackStrategy", { enumerable: true, get: function () { return BrowserCallbackStrategy_1.oidcCallbackStrategy; } });
12
+ Object.defineProperty(exports, "samlCallbackStrategy", { enumerable: true, get: function () { return BrowserCallbackStrategy_1.samlCallbackStrategy; } });
13
+ var codeStrategies_1 = require("./codeStrategies");
14
+ Object.defineProperty(exports, "externalCodeStrategy", { enumerable: true, get: function () { return codeStrategies_1.externalCodeStrategy; } });
15
+ Object.defineProperty(exports, "staticCodeStrategy", { enumerable: true, get: function () { return codeStrategies_1.staticCodeStrategy; } });
16
+ var manualStrategies_1 = require("./manualStrategies");
17
+ Object.defineProperty(exports, "manualPasteStrategy", { enumerable: true, get: function () { return manualStrategies_1.manualPasteStrategy; } });
18
+ Object.defineProperty(exports, "manualSamlResponseStrategy", { enumerable: true, get: function () { return manualStrategies_1.manualSamlResponseStrategy; } });
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Strategies where a human moves the payload.
3
+ *
4
+ * There are two because the payload is not acquired the same way. An
5
+ * authorization code lands in the browser's address bar; a `SAMLResponse` does
6
+ * not — our `AuthnRequest` declares the HTTP-POST binding, so the IdP posts it
7
+ * in a form body and the user must lift it from there.
8
+ */
9
+ import type { IAuthorizationStrategy } from '@mcp-abap-adt/interfaces';
10
+ export interface ManualStrategyOptions {
11
+ /** Must match what the authorization request advertises and the exchange sends. */
12
+ redirectUri?: string;
13
+ /** Where the pasted value comes from. Defaults to an interactive stdin read. */
14
+ read?: (prompt: string) => Promise<string>;
15
+ }
16
+ /** The user copies the `code` out of the address bar after the redirect. */
17
+ export declare function manualPasteStrategy(options?: ManualStrategyOptions): IAuthorizationStrategy<string>;
18
+ /** The user lifts `SAMLResponse` from the POST body — it never reaches the URL. */
19
+ export declare function manualSamlResponseStrategy(options?: ManualStrategyOptions): IAuthorizationStrategy<string>;
20
+ //# sourceMappingURL=manualStrategies.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manualStrategies.d.ts","sourceRoot":"","sources":["../../src/strategies/manualStrategies.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAGV,sBAAsB,EACvB,MAAM,0BAA0B,CAAC;AAIlC,MAAM,WAAW,qBAAqB;IACpC,mFAAmF;IACnF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CAC5C;AAiCD,4EAA4E;AAC5E,wBAAgB,mBAAmB,CACjC,OAAO,GAAE,qBAA0B,GAClC,sBAAsB,CAAC,MAAM,CAAC,CAmBhC;AAED,mFAAmF;AACnF,wBAAgB,0BAA0B,CACxC,OAAO,GAAE,qBAA0B,GAClC,sBAAsB,CAAC,MAAM,CAAC,CAiBhC"}
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ /**
3
+ * Strategies where a human moves the payload.
4
+ *
5
+ * There are two because the payload is not acquired the same way. An
6
+ * authorization code lands in the browser's address bar; a `SAMLResponse` does
7
+ * not — our `AuthnRequest` declares the HTTP-POST binding, so the IdP posts it
8
+ * in a form body and the user must lift it from there.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.manualPasteStrategy = manualPasteStrategy;
12
+ exports.manualSamlResponseStrategy = manualSamlResponseStrategy;
13
+ const node_readline_1 = require("node:readline");
14
+ const browserAuth_1 = require("../auth/browserAuth");
15
+ const BrowserCallbackStrategy_1 = require("./BrowserCallbackStrategy");
16
+ const defaultRedirectUri = () => `http://localhost:${BrowserCallbackStrategy_1.DEFAULT_CALLBACK_PORT}/callback`;
17
+ /**
18
+ * Reads one line from stdin.
19
+ *
20
+ * The prompt goes to stderr, never stdout, and stdin is touched only when it is
21
+ * a terminal: under a stdio RPC transport those streams carry the protocol.
22
+ */
23
+ async function readFromTerminal(prompt) {
24
+ if (!process.stdin.isTTY) {
25
+ throw new Error('Manual input needs an interactive terminal. Supply `read` to source the value elsewhere.');
26
+ }
27
+ process.stderr.write(prompt);
28
+ const rl = (0, node_readline_1.createInterface)({ input: process.stdin });
29
+ try {
30
+ for await (const line of rl)
31
+ return line.trim();
32
+ }
33
+ finally {
34
+ rl.close();
35
+ }
36
+ throw new Error('No input received');
37
+ }
38
+ function announce(request, url) {
39
+ const message = `Open this URL to authenticate:\n${url}`;
40
+ if (request.logger)
41
+ request.logger.info(message);
42
+ else
43
+ process.stderr.write(`${message}\n`);
44
+ }
45
+ /** The user copies the `code` out of the address bar after the redirect. */
46
+ function manualPasteStrategy(options = {}) {
47
+ const redirectUri = options.redirectUri ?? defaultRedirectUri();
48
+ const read = options.read ?? readFromTerminal;
49
+ return {
50
+ async authorize(request) {
51
+ const url = await request.buildAuthorizationUrl(redirectUri);
52
+ announce(request, url);
53
+ const raw = await read('Paste the authorization code (or the whole redirected URL): ');
54
+ const code = (0, browserAuth_1.extractCode)(raw);
55
+ if (!code) {
56
+ throw new Error('Could not read an authorization code from that input');
57
+ }
58
+ return { payload: code, redirectUri };
59
+ },
60
+ };
61
+ }
62
+ /** The user lifts `SAMLResponse` from the POST body — it never reaches the URL. */
63
+ function manualSamlResponseStrategy(options = {}) {
64
+ const redirectUri = options.redirectUri ?? defaultRedirectUri();
65
+ const read = options.read ?? readFromTerminal;
66
+ return {
67
+ async authorize(request) {
68
+ const url = await request.buildAuthorizationUrl(redirectUri);
69
+ announce(request, url);
70
+ const raw = await read('Paste the SAMLResponse (from the POST body — it is not in the address bar): ');
71
+ const assertion = raw.trim();
72
+ if (!assertion)
73
+ throw new Error('No SAMLResponse was provided');
74
+ return { payload: assertion, redirectUri };
75
+ },
76
+ };
77
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-abap-adt/auth-providers",
3
- "version": "1.2.0",
3
+ "version": "2.0.0",
4
4
  "description": "Token providers for MCP ABAP ADT auth-broker",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -58,7 +58,7 @@
58
58
  "node": ">=18.2.0"
59
59
  },
60
60
  "dependencies": {
61
- "@mcp-abap-adt/interfaces": "^11.4.0",
61
+ "@mcp-abap-adt/interfaces": "^11.6.0",
62
62
  "axios": "^1.13.5",
63
63
  "express": "^5.1.0",
64
64
  "open": "^11.0.0"
@@ -1,5 +0,0 @@
1
- /**
2
- * Manual input helper (stdin)
3
- */
4
- export declare function readManualInput(prompt: string): Promise<string>;
5
- //# sourceMappingURL=manualInput.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"manualInput.d.ts","sourceRoot":"","sources":["../../src/auth/manualInput.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAYrE"}
@@ -1,19 +0,0 @@
1
- "use strict";
2
- /**
3
- * Manual input helper (stdin)
4
- */
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.readManualInput = readManualInput;
7
- const node_readline_1 = require("node:readline");
8
- async function readManualInput(prompt) {
9
- const rl = (0, node_readline_1.createInterface)({
10
- input: process.stdin,
11
- output: process.stdout,
12
- });
13
- return new Promise((resolve) => {
14
- rl.question(prompt, (answer) => {
15
- rl.close();
16
- resolve(answer.trim());
17
- });
18
- });
19
- }