@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
@@ -2,110 +2,28 @@
2
2
  /**
3
3
  * OIDC browser authorization code flow (capture code)
4
4
  */
5
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
- if (k2 === undefined) k2 = k;
7
- var desc = Object.getOwnPropertyDescriptor(m, k);
8
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
- desc = { enumerable: true, get: function() { return m[k]; } };
10
- }
11
- Object.defineProperty(o, k2, desc);
12
- }) : (function(o, m, k, k2) {
13
- if (k2 === undefined) k2 = k;
14
- o[k2] = m[k];
15
- }));
16
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
- Object.defineProperty(o, "default", { enumerable: true, value: v });
18
- }) : function(o, v) {
19
- o["default"] = v;
20
- });
21
- var __importStar = (this && this.__importStar) || (function () {
22
- var ownKeys = function(o) {
23
- ownKeys = Object.getOwnPropertyNames || function (o) {
24
- var ar = [];
25
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
- return ar;
27
- };
28
- return ownKeys(o);
29
- };
30
- return function (mod) {
31
- if (mod && mod.__esModule) return mod;
32
- var result = {};
33
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
- __setModuleDefault(result, mod);
35
- return result;
36
- };
37
- })();
38
5
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.startOidcBrowserAuth = startOidcBrowserAuth;
40
- const net = __importStar(require("node:net"));
6
+ exports.withOidcCallbackServer = void 0;
41
7
  const callbackServer_1 = require("./callbackServer");
42
- const BROWSER_MAP = {
43
- chrome: 'chrome',
44
- edge: 'msedge',
45
- firefox: 'firefox',
46
- system: undefined,
47
- auto: undefined,
48
- headless: null,
49
- none: null,
50
- };
51
- function isPortAvailable(port) {
52
- return new Promise((resolve) => {
53
- const server = net.createServer();
54
- server.listen(port, () => {
55
- server.once('close', () => resolve(true));
56
- server.close();
57
- });
58
- server.on('error', () => resolve(false));
59
- });
60
- }
61
- async function openBrowserUrl(authorizationUrl, browser, logger) {
62
- const browserApp = BROWSER_MAP[browser];
63
- if (browserApp === null) {
64
- logger?.info('[OIDC] Browser suppressed, open URL manually', {
65
- authorizationUrl,
66
- });
67
- return;
68
- }
69
- if (browser === 'auto') {
70
- try {
71
- const openModule = await Promise.resolve().then(() => __importStar(require('open')));
72
- const open = openModule.default;
73
- await open(authorizationUrl);
74
- logger?.info('[OIDC] Browser opened');
75
- return;
76
- }
77
- catch (error) {
78
- logger?.warn('[OIDC] Failed to open browser automatically', {
79
- error: error instanceof Error ? error.message : String(error),
80
- });
81
- logger?.info('[OIDC] Open URL manually', { authorizationUrl });
82
- return;
83
- }
84
- }
85
- try {
86
- const openModule = await Promise.resolve().then(() => __importStar(require('open')));
87
- const open = openModule.default;
88
- if (browserApp) {
89
- await open(authorizationUrl, { app: { name: browserApp } });
90
- }
91
- else {
92
- await open(authorizationUrl);
93
- }
94
- }
95
- catch (error) {
96
- logger?.warn('[OIDC] Failed to open browser', {
97
- error: error instanceof Error ? error.message : String(error),
98
- });
99
- logger?.info('[OIDC] Open URL manually', { authorizationUrl });
100
- }
101
- }
102
8
  const withOidcCallbackServer = (options, use) => (0, callbackServer_1.runCallbackScope)(options, (app, settle) => {
103
9
  app.get('/callback', (req, res) => {
10
+ // An IdP that declines says so explicitly. That is a finished login,
11
+ // not a stray request, and it must not wait for the timeout.
12
+ const { error, error_description, error_uri } = req.query;
13
+ if (error) {
14
+ const message = error_description
15
+ ? `${String(error)}: ${String(error_description)}`
16
+ : String(error);
17
+ res.status(400).send(`Authentication failed: ${message}`);
18
+ settle.err(new Error(`OIDC authentication failed: ${message}` +
19
+ (error_uri ? ` (${String(error_uri)})` : '')), res);
20
+ return;
21
+ }
104
22
  const code = req.query.code;
105
23
  const state = req.query.state;
106
24
  if (!code || typeof code !== 'string') {
107
- res.status(400).send('Missing authorization code');
108
- settle.err(new Error('Missing authorization code'), res);
25
+ res.status(400).send('Error: not an authorization callback');
26
+ settle.ignore('no code and no error in query', res);
109
27
  return;
110
28
  }
111
29
  res
@@ -114,31 +32,4 @@ const withOidcCallbackServer = (options, use) => (0, callbackServer_1.runCallbac
114
32
  settle.ok({ code, state: typeof state === 'string' ? state : undefined }, res);
115
33
  });
116
34
  }, use);
117
- /**
118
- * OIDC browser login.
119
- *
120
- * The callback socket belongs to the scope: it is released when the scope ends,
121
- * whatever ends it. Before this, the flow had no timeout at all — an abandoned
122
- * login never settled and the port was held for the life of the process.
123
- */
124
- async function startOidcBrowserAuth(authorizationUrl, browser, logger, port = 3001, timeoutMs = 30 * 1000) {
125
- // Pre-check kept for its message: AuthBroker matches /already in use/i to
126
- // distinguish a busy port from other failures.
127
- const portAvailable = await isPortAvailable(port);
128
- if (!portAvailable) {
129
- throw new Error(`Port ${port} is already in use. Please specify a different port or free the port.`);
130
- }
131
- return await withOidcCallbackServer({ port, timeoutMs }, async (server) => {
132
- logger?.info('[OIDC] Callback server listening', { port: server.port });
133
- const waiting = server.waitForResult();
134
- // Not awaited: a launcher that hangs must not delay the timeout or the
135
- // release of the port.
136
- void openBrowserUrl(authorizationUrl, browser, logger).catch((error) => {
137
- logger?.warn('[OIDC] Failed to open browser', {
138
- error: error instanceof Error ? error.message : String(error),
139
- });
140
- logger?.info('[OIDC] Open URL manually', { authorizationUrl });
141
- });
142
- return await waiting;
143
- });
144
- }
35
+ exports.withOidcCallbackServer = withOidcCallbackServer;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * SAML 2.0 auth helpers
3
3
  */
4
- import type { ILogger } from '@mcp-abap-adt/interfaces';
4
+ import type { CallbackServerFactory } from '@mcp-abap-adt/interfaces';
5
5
  export interface Saml2AuthConfig {
6
6
  idpSsoUrl: string;
7
7
  spEntityId: string;
@@ -10,13 +10,6 @@ export interface Saml2AuthConfig {
10
10
  authorizationUrl?: string;
11
11
  }
12
12
  export declare function buildSamlAuthorizationUrl(config: Saml2AuthConfig): string;
13
- /**
14
- * SAML browser login.
15
- *
16
- * The callback socket belongs to the scope: it is released when the scope ends,
17
- * whatever ends it. Before this, the flow had no timeout at all — an abandoned
18
- * login never settled and the port was held for the life of the process.
19
- */
20
- export declare function startSamlBrowserAuth(config: Saml2AuthConfig, browser: string, logger?: ILogger, port?: number, timeoutMs?: number): Promise<string>;
13
+ export declare const withSamlCallbackServer: CallbackServerFactory<string>;
21
14
  export declare function parseSamlNotOnOrAfter(samlResponse: string): number | undefined;
22
15
  //# sourceMappingURL=saml2Auth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"saml2Auth.d.ts","sourceRoot":"","sources":["../../src/auth/saml2Auth.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAIV,OAAO,EACR,MAAM,0BAA0B,CAAC;AAIlC,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AA8CD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CAazE;AA8ED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,eAAe,EACvB,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,OAAO,EAChB,IAAI,GAAE,MAAa,EACnB,SAAS,GAAE,MAAkB,GAC5B,OAAO,CAAC,MAAM,CAAC,CA2BjB;AAED,wBAAgB,qBAAqB,CACnC,YAAY,EAAE,MAAM,GACnB,MAAM,GAAG,SAAS,CAYpB"}
1
+ {"version":3,"file":"saml2Auth.d.ts","sourceRoot":"","sources":["../../src/auth/saml2Auth.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EACV,qBAAqB,EAGtB,MAAM,0BAA0B,CAAC;AAIlC,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAyBD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CAazE;AAED,eAAO,MAAM,sBAAsB,EAAE,qBAAqB,CAAC,MAAM,CAgC9D,CAAC;AAEJ,wBAAgB,qBAAqB,CACnC,YAAY,EAAE,MAAM,GACnB,MAAM,GAAG,SAAS,CAYpB"}
@@ -2,70 +2,17 @@
2
2
  /**
3
3
  * SAML 2.0 auth helpers
4
4
  */
5
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
- if (k2 === undefined) k2 = k;
7
- var desc = Object.getOwnPropertyDescriptor(m, k);
8
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
- desc = { enumerable: true, get: function() { return m[k]; } };
10
- }
11
- Object.defineProperty(o, k2, desc);
12
- }) : (function(o, m, k, k2) {
13
- if (k2 === undefined) k2 = k;
14
- o[k2] = m[k];
15
- }));
16
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
- Object.defineProperty(o, "default", { enumerable: true, value: v });
18
- }) : function(o, v) {
19
- o["default"] = v;
20
- });
21
- var __importStar = (this && this.__importStar) || (function () {
22
- var ownKeys = function(o) {
23
- ownKeys = Object.getOwnPropertyNames || function (o) {
24
- var ar = [];
25
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
- return ar;
27
- };
28
- return ownKeys(o);
29
- };
30
- return function (mod) {
31
- if (mod && mod.__esModule) return mod;
32
- var result = {};
33
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
- __setModuleDefault(result, mod);
35
- return result;
36
- };
37
- })();
38
5
  var __importDefault = (this && this.__importDefault) || function (mod) {
39
6
  return (mod && mod.__esModule) ? mod : { "default": mod };
40
7
  };
41
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.withSamlCallbackServer = void 0;
42
10
  exports.buildSamlAuthorizationUrl = buildSamlAuthorizationUrl;
43
- exports.startSamlBrowserAuth = startSamlBrowserAuth;
44
11
  exports.parseSamlNotOnOrAfter = parseSamlNotOnOrAfter;
45
12
  const node_crypto_1 = require("node:crypto");
46
- const net = __importStar(require("node:net"));
47
13
  const node_zlib_1 = require("node:zlib");
48
14
  const express_1 = __importDefault(require("express"));
49
15
  const callbackServer_1 = require("./callbackServer");
50
- const BROWSER_MAP = {
51
- chrome: 'chrome',
52
- edge: 'msedge',
53
- firefox: 'firefox',
54
- system: undefined,
55
- auto: undefined,
56
- headless: null,
57
- none: null,
58
- };
59
- function isPortAvailable(port) {
60
- return new Promise((resolve) => {
61
- const server = net.createServer();
62
- server.listen(port, () => {
63
- server.once('close', () => resolve(true));
64
- server.close();
65
- });
66
- server.on('error', () => resolve(false));
67
- });
68
- }
69
16
  function base64Encode(input) {
70
17
  return Buffer.isBuffer(input)
71
18
  ? input.toString('base64')
@@ -99,59 +46,20 @@ function buildSamlAuthorizationUrl(config) {
99
46
  : '';
100
47
  return `${config.idpSsoUrl}?SAMLRequest=${samlRequest}${relayState}`;
101
48
  }
102
- async function openBrowserUrl(authorizationUrl, browser, logger) {
103
- const browserApp = BROWSER_MAP[browser];
104
- if (browserApp === null) {
105
- logger?.info('[SAML] Browser suppressed, open URL manually', {
106
- authorizationUrl,
107
- });
108
- return;
109
- }
110
- if (browser === 'auto') {
111
- try {
112
- const openModule = await Promise.resolve().then(() => __importStar(require('open')));
113
- const open = openModule.default;
114
- await open(authorizationUrl);
115
- logger?.info('[SAML] Browser opened');
116
- return;
117
- }
118
- catch (error) {
119
- logger?.warn('[SAML] Failed to open browser automatically', {
120
- error: error instanceof Error ? error.message : String(error),
121
- });
122
- logger?.info('[SAML] Open URL manually', { authorizationUrl });
123
- return;
124
- }
125
- }
126
- try {
127
- const openModule = await Promise.resolve().then(() => __importStar(require('open')));
128
- const open = openModule.default;
129
- if (browserApp) {
130
- await open(authorizationUrl, { app: { name: browserApp } });
131
- }
132
- else {
133
- await open(authorizationUrl);
134
- }
135
- }
136
- catch (error) {
137
- logger?.warn('[SAML] Failed to open browser', {
138
- error: error instanceof Error ? error.message : String(error),
139
- });
140
- logger?.info('[SAML] Open URL manually', { authorizationUrl });
141
- }
142
- }
143
49
  const withSamlCallbackServer = (options, use) => (0, callbackServer_1.runCallbackScope)(options, (app, settle) => {
144
50
  app.use(express_1.default.urlencoded({ extended: false, limit: '5mb' }));
145
51
  const handle = (samlResponse, res) => {
146
- res
147
- .status(200)
148
- .send('SAML authentication complete. You can close this window.');
52
+ // The response is decided after the payload is examined. Answering 200
53
+ // first told a request that carried nothing that it had authenticated.
149
54
  if (typeof samlResponse === 'string' && samlResponse) {
55
+ res
56
+ .status(200)
57
+ .send('SAML authentication complete. You can close this window.');
150
58
  settle.ok(samlResponse, res);
59
+ return;
151
60
  }
152
- else {
153
- settle.err(new Error('Missing SAMLResponse'), res);
154
- }
61
+ res.status(400).send('Error: not a SAML assertion callback');
62
+ settle.ignore('no SAMLResponse in the request', res);
155
63
  };
156
64
  app.post('/callback', (req, res) => {
157
65
  handle(req.body?.SAMLResponse, res);
@@ -160,35 +68,7 @@ const withSamlCallbackServer = (options, use) => (0, callbackServer_1.runCallbac
160
68
  handle(req.query.SAMLResponse, res);
161
69
  });
162
70
  }, use);
163
- /**
164
- * SAML browser login.
165
- *
166
- * The callback socket belongs to the scope: it is released when the scope ends,
167
- * whatever ends it. Before this, the flow had no timeout at all — an abandoned
168
- * login never settled and the port was held for the life of the process.
169
- */
170
- async function startSamlBrowserAuth(config, browser, logger, port = 3001, timeoutMs = 30 * 1000) {
171
- // Pre-check kept for its message: AuthBroker matches /already in use/i to
172
- // distinguish a busy port from other failures.
173
- const portAvailable = await isPortAvailable(port);
174
- if (!portAvailable) {
175
- throw new Error(`Port ${port} is already in use. Please specify a different port or free the port.`);
176
- }
177
- const authorizationUrl = buildSamlAuthorizationUrl(config);
178
- return await withSamlCallbackServer({ port, timeoutMs }, async (server) => {
179
- logger?.info('[SAML] Callback server listening', { port: server.port });
180
- const waiting = server.waitForResult();
181
- // Not awaited: a launcher that hangs must not delay the timeout or the
182
- // release of the port.
183
- void openBrowserUrl(authorizationUrl, browser, logger).catch((error) => {
184
- logger?.warn('[SAML] Failed to open browser', {
185
- error: error instanceof Error ? error.message : String(error),
186
- });
187
- logger?.info('[SAML] Open URL manually', { authorizationUrl });
188
- });
189
- return await waiting;
190
- });
191
- }
71
+ exports.withSamlCallbackServer = withSamlCallbackServer;
192
72
  function parseSamlNotOnOrAfter(samlResponse) {
193
73
  try {
194
74
  const decoded = Buffer.from(samlResponse, 'base64').toString('utf8');
package/dist/index.d.ts CHANGED
@@ -4,9 +4,15 @@
4
4
  *
5
5
  * Provides token providers
6
6
  */
7
+ export { withBrowserCallbackServer } from './auth/callbackServer';
8
+ export type { OidcCallbackResult } from './auth/oidcBrowserAuth';
9
+ export { withOidcCallbackServer } from './auth/oidcBrowserAuth';
10
+ export { withSamlCallbackServer } from './auth/saml2Auth';
7
11
  export { BrowserAuthError, RefreshError, ServiceKeyError, SessionDataError, TokenProviderError, ValidationError, } from './errors/TokenProviderErrors';
8
12
  export type { AuthorizationCodeProviderConfig, ClientCredentialsProviderConfig, DeviceFlowProviderConfig, OidcBrowserProviderConfig, OidcDeviceFlowProviderConfig, OidcPasswordProviderConfig, OidcTokenExchangeProviderConfig, Saml2BearerProviderConfig, Saml2PureProviderConfig, } from './providers';
9
13
  export { AuthorizationCodeProvider, BaseTokenProvider, ClientCredentialsProvider, DeviceFlowProvider, OidcBrowserProvider, OidcDeviceFlowProvider, OidcPasswordProvider, OidcTokenExchangeProvider, Saml2BearerProvider, Saml2PureProvider, } from './providers';
10
14
  export { SsoProviderFactory } from './sso/SsoProviderFactory';
11
15
  export type { SsoProviderConfig, SsoProviderInstance } from './sso/types';
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';
12
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,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,wBAAwB,EACxB,yBAAyB,EACzB,4BAA4B,EAC5B,0BAA0B,EAC1B,+BAA+B,EAC/B,yBAAyB,EACzB,uBAAuB,GACxB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,yBAAyB,EACzB,iBAAiB,EACjB,yBAAyB,EACzB,kBAAkB,EAClB,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,yBAAyB,EACzB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,YAAY,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC"}
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,wBAAwB,EACxB,yBAAyB,EACzB,4BAA4B,EAC5B,0BAA0B,EAC1B,+BAA+B,EAC/B,yBAAyB,EACzB,uBAAuB,GACxB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,yBAAyB,EACzB,iBAAiB,EACjB,yBAAyB,EACzB,kBAAkB,EAClB,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,yBAAyB,EACzB,mBAAmB,EACnB,iBAAiB,GAClB,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,mBAAmB,EACnB,0BAA0B,EAC1B,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,cAAc,CAAC"}
package/dist/index.js CHANGED
@@ -6,7 +6,14 @@
6
6
  * Provides token providers
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.SsoProviderFactory = exports.Saml2PureProvider = exports.Saml2BearerProvider = exports.OidcTokenExchangeProvider = exports.OidcPasswordProvider = exports.OidcDeviceFlowProvider = exports.OidcBrowserProvider = exports.DeviceFlowProvider = exports.ClientCredentialsProvider = exports.BaseTokenProvider = exports.AuthorizationCodeProvider = exports.ValidationError = exports.TokenProviderError = exports.SessionDataError = exports.ServiceKeyError = exports.RefreshError = exports.BrowserAuthError = void 0;
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.DeviceFlowProvider = 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
+ // Callback server factories — "take the transport this package gives".
11
+ var callbackServer_1 = require("./auth/callbackServer");
12
+ Object.defineProperty(exports, "withBrowserCallbackServer", { enumerable: true, get: function () { return callbackServer_1.withBrowserCallbackServer; } });
13
+ var oidcBrowserAuth_1 = require("./auth/oidcBrowserAuth");
14
+ Object.defineProperty(exports, "withOidcCallbackServer", { enumerable: true, get: function () { return oidcBrowserAuth_1.withOidcCallbackServer; } });
15
+ var saml2Auth_1 = require("./auth/saml2Auth");
16
+ Object.defineProperty(exports, "withSamlCallbackServer", { enumerable: true, get: function () { return saml2Auth_1.withSamlCallbackServer; } });
10
17
  // Errors
11
18
  var TokenProviderErrors_1 = require("./errors/TokenProviderErrors");
12
19
  Object.defineProperty(exports, "BrowserAuthError", { enumerable: true, get: function () { return TokenProviderErrors_1.BrowserAuthError; } });
@@ -30,3 +37,16 @@ Object.defineProperty(exports, "Saml2PureProvider", { enumerable: true, get: fun
30
37
  // SSO factory
31
38
  var SsoProviderFactory_1 = require("./sso/SsoProviderFactory");
32
39
  Object.defineProperty(exports, "SsoProviderFactory", { enumerable: true, get: function () { return SsoProviderFactory_1.SsoProviderFactory; } });
40
+ // Authorization strategies — or bring your own IAuthorizationStrategy.
41
+ var strategies_1 = require("./strategies");
42
+ Object.defineProperty(exports, "asOidcResult", { enumerable: true, get: function () { return strategies_1.asOidcResult; } });
43
+ Object.defineProperty(exports, "BrowserCallbackStrategy", { enumerable: true, get: function () { return strategies_1.BrowserCallbackStrategy; } });
44
+ Object.defineProperty(exports, "browserCallbackStrategy", { enumerable: true, get: function () { return strategies_1.browserCallbackStrategy; } });
45
+ Object.defineProperty(exports, "DEFAULT_CALLBACK_PORT", { enumerable: true, get: function () { return strategies_1.DEFAULT_CALLBACK_PORT; } });
46
+ Object.defineProperty(exports, "DEFAULT_LOGIN_TIMEOUT_MS", { enumerable: true, get: function () { return strategies_1.DEFAULT_LOGIN_TIMEOUT_MS; } });
47
+ Object.defineProperty(exports, "externalCodeStrategy", { enumerable: true, get: function () { return strategies_1.externalCodeStrategy; } });
48
+ Object.defineProperty(exports, "manualPasteStrategy", { enumerable: true, get: function () { return strategies_1.manualPasteStrategy; } });
49
+ Object.defineProperty(exports, "manualSamlResponseStrategy", { enumerable: true, get: function () { return strategies_1.manualSamlResponseStrategy; } });
50
+ Object.defineProperty(exports, "oidcCallbackStrategy", { enumerable: true, get: function () { return strategies_1.oidcCallbackStrategy; } });
51
+ Object.defineProperty(exports, "samlCallbackStrategy", { enumerable: true, get: function () { return strategies_1.samlCallbackStrategy; } });
52
+ Object.defineProperty(exports, "staticCodeStrategy", { enumerable: true, get: function () { return strategies_1.staticCodeStrategy; } });
@@ -4,15 +4,19 @@
4
4
  * Uses authorization_code grant type with browser-based OAuth2 flow.
5
5
  * Supports pre-built authorization URLs and automatic refresh.
6
6
  */
7
- import type { ILogger, ITokenResult, OAuth2GrantType } from '@mcp-abap-adt/interfaces';
7
+ import type { IAuthorizationStrategy, ILogger, ITokenResult, OAuth2GrantType } from '@mcp-abap-adt/interfaces';
8
8
  import { BaseTokenProvider } from './BaseTokenProvider';
9
9
  export interface AuthorizationCodeProviderConfig {
10
10
  uaaUrl: string;
11
11
  clientId: string;
12
12
  clientSecret: string;
13
+ /** Pre-built authorization URL. Carries its own redirect; see the guard below. */
13
14
  authorizationUrl?: string;
14
- browser?: string;
15
- redirectPort?: number;
15
+ /**
16
+ * How the login is conducted. Omitted means a browser callback on the default
17
+ * port — the package's own transport, which a consumer may replace wholesale.
18
+ */
19
+ authorization?: IAuthorizationStrategy<string>;
16
20
  accessToken?: string;
17
21
  refreshToken?: string;
18
22
  logger?: ILogger;
@@ -1 +1 @@
1
- {"version":3,"file":"AuthorizationCodeProvider.d.ts","sourceRoot":"","sources":["../../src/providers/AuthorizationCodeProvider.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAEV,OAAO,EACP,YAAY,EACZ,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAIlC,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAKxD,MAAM,WAAW,+BAA+B;IAE9C,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IAGrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAG1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IAGtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IAGtB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;GAKG;AACH,qBAAa,yBAA0B,SAAQ,iBAAiB;IAC9D,OAAO,CAAC,MAAM,CAAkC;gBAEpC,MAAM,EAAE,+BAA+B;IA8D7C,SAAS,IAAI,OAAO,CAAC,YAAY,CAAC;IAIxC,SAAS,CAAC,WAAW,IAAI,eAAe;cAIxB,YAAY,IAAI,OAAO,CAAC,YAAY,CAAC;cAoErC,cAAc,IAAI,OAAO,CAAC,YAAY,CAAC;CA0CxD"}
1
+ {"version":3,"file":"AuthorizationCodeProvider.d.ts","sourceRoot":"","sources":["../../src/providers/AuthorizationCodeProvider.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAEV,sBAAsB,EACtB,OAAO,EACP,YAAY,EACZ,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAQlC,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,WAAW,+BAA+B;IAE9C,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IAErB,kFAAkF;IAClF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;OAGG;IACH,aAAa,CAAC,EAAE,sBAAsB,CAAC,MAAM,CAAC,CAAC;IAG/C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;GAKG;AACH,qBAAa,yBAA0B,SAAQ,iBAAiB;IAC9D,OAAO,CAAC,MAAM,CAAkC;gBAEpC,MAAM,EAAE,+BAA+B;IA4D7C,SAAS,IAAI,OAAO,CAAC,YAAY,CAAC;IAIxC,SAAS,CAAC,WAAW,IAAI,eAAe;cAIxB,YAAY,IAAI,OAAO,CAAC,YAAY,CAAC;cA+ErC,cAAc,IAAI,OAAO,CAAC,YAAY,CAAC;CA0CxD"}
@@ -10,9 +10,8 @@ exports.AuthorizationCodeProvider = void 0;
10
10
  const interfaces_1 = require("@mcp-abap-adt/interfaces");
11
11
  const browserAuth_1 = require("../auth/browserAuth");
12
12
  const tokenRefresher_1 = require("../auth/tokenRefresher");
13
+ const strategies_1 = require("../strategies");
13
14
  const BaseTokenProvider_1 = require("./BaseTokenProvider");
14
- /** How long an interactive login may wait for its callback. */
15
- const LOGIN_TIMEOUT_MS = 30 * 1000;
16
15
  /**
17
16
  * Authorization Code token provider
18
17
  *
@@ -32,8 +31,6 @@ class AuthorizationCodeProvider extends BaseTokenProvider_1.BaseTokenProvider {
32
31
  hasRefreshToken: !!config.refreshToken,
33
32
  accessToken: this.formatToken(config.accessToken),
34
33
  refreshToken: this.formatToken(config.refreshToken),
35
- browser: config.browser || 'none',
36
- redirectPort: config.redirectPort || 3001,
37
34
  });
38
35
  const missingFields = [];
39
36
  if (!config.uaaUrl) {
@@ -78,53 +75,67 @@ class AuthorizationCodeProvider extends BaseTokenProvider_1.BaseTokenProvider {
78
75
  return interfaces_1.AUTH_TYPE_AUTHORIZATION_CODE;
79
76
  }
80
77
  async performLogin() {
81
- // Build authorization config
82
- // If authorizationUrl is provided, use it; otherwise startBrowserAuth will build it from uaaUrl + clientId
83
78
  const authConfig = {
84
79
  uaaUrl: this.config.uaaUrl,
85
80
  uaaClientId: this.config.clientId,
86
81
  uaaClientSecret: this.config.clientSecret,
87
82
  };
88
- // If pre-built URL provided, use it
89
- if (this.config.authorizationUrl) {
90
- authConfig.authorizationUrl = this.config.authorizationUrl;
83
+ const prebuilt = this.config.authorizationUrl;
84
+ const declaredRedirect = prebuilt
85
+ ? new URL(prebuilt).searchParams.get('redirect_uri')
86
+ : null;
87
+ const mismatch = (redirectUri) => `Pre-built authorizationUrl declares redirect_uri ${declaredRedirect}, ` +
88
+ `but the authorization strategy used ${redirectUri}, which does not match. ` +
89
+ 'An ephemeral port cannot be used with a pre-built URL.';
90
+ // The provider owns the URL; the strategy owns where it is answered. The
91
+ // guard lives here rather than after the fact because a mismatched redirect
92
+ // produces no callback at all — checking the outcome would mean waiting for
93
+ // a timeout that explains nothing.
94
+ const request = {
95
+ logger: this.logger,
96
+ buildAuthorizationUrl: async (redirectUri) => {
97
+ if (prebuilt) {
98
+ if (declaredRedirect && declaredRedirect !== redirectUri) {
99
+ throw new Error(mismatch(redirectUri));
100
+ }
101
+ return prebuilt;
102
+ }
103
+ return (0, browserAuth_1.getJwtAuthorizationUrl)(authConfig, redirectUri);
104
+ },
105
+ };
106
+ // Constructed here means disposed here: whoever constructs, disposes.
107
+ const supplied = this.config.authorization;
108
+ const strategy = supplied ?? (0, strategies_1.browserCallbackStrategy)();
109
+ let outcome;
110
+ try {
111
+ outcome = await strategy.authorize(request);
91
112
  }
92
- // Use provided browser or default to 'none' (prints URL to console)
93
- const browser = this.config.browser || 'none';
94
- const redirectPort = this.config.redirectPort || 3001;
95
- // Build authorization URL for logging (same logic as in startBrowserAuth)
96
- const authorizationUrl = authConfig.authorizationUrl ??
97
- `${authConfig.uaaUrl}/oauth/authorize?client_id=${encodeURIComponent(authConfig.uaaClientId)}&redirect_uri=${encodeURIComponent(`http://localhost:${redirectPort}/callback`)}&response_type=code`;
98
- this.logger?.info('[AuthorizationCodeProvider] Performing login via browser', {
99
- browser,
100
- redirectPort,
101
- authorizationUrl,
102
- uaaUrl: authConfig.uaaUrl,
103
- clientId: authConfig.uaaClientId,
104
- });
105
- // One timeout, owned by whoever owns the socket.
106
- //
107
- // This used to race startBrowserAuth against a second timer of the same
108
- // 30 seconds, so which fired was down to scheduling — and when the outer
109
- // one won, the provider rejected while the callback port was still bound.
110
- // That timer was never cleared either, keeping the event loop alive for the
111
- // rest of the window after a login that succeeded in a second.
112
- const result = await (0, browserAuth_1.startBrowserAuth)(authConfig, browser, this.logger || undefined, // Pass logger to browserAuth
113
- redirectPort, LOGIN_TIMEOUT_MS);
114
- this.logger?.info('[AuthorizationCodeProvider] Login completed', {
115
- hasAccessToken: !!result.accessToken,
116
- hasRefreshToken: !!result.refreshToken,
117
- accessToken: this.formatToken(result.accessToken),
118
- refreshToken: this.formatToken(result.refreshToken),
119
- accessTokenLength: result.accessToken?.length || 0,
113
+ finally {
114
+ if (!supplied) {
115
+ // A cleanup failure must not replace the reason the login failed.
116
+ await strategy.dispose?.().catch((error) => {
117
+ this.logger?.warn('[AuthorizationCodeProvider] dispose failed', {
118
+ error: error instanceof Error ? error.message : String(error),
119
+ });
120
+ });
121
+ }
122
+ }
123
+ // The second net. A strategy that never called the builder — `staticCodeStrategy`
124
+ // holds its payload already — passed the first check by not participating in
125
+ // it, and would otherwise reach the exchange with a redirect_uri the
126
+ // pre-built URL never advertised, earning an opaque `invalid_grant`.
127
+ if (declaredRedirect && declaredRedirect !== outcome.redirectUri) {
128
+ throw new Error(mismatch(outcome.redirectUri));
129
+ }
130
+ this.logger?.info('[AuthorizationCodeProvider] Code received', {
131
+ redirectUri: outcome.redirectUri,
120
132
  });
121
- // Parse expiration from JWT
122
- const expiresIn = this.calculateExpiresIn(result.accessToken);
133
+ const result = await (0, browserAuth_1.exchangeCodeForToken)(authConfig, outcome.payload, outcome.redirectUri, this.logger);
123
134
  return {
124
135
  authorizationToken: result.accessToken,
125
136
  refreshToken: result.refreshToken,
126
137
  authType: interfaces_1.AUTH_TYPE_AUTHORIZATION_CODE,
127
- expiresIn,
138
+ expiresIn: this.calculateExpiresIn(result.accessToken),
128
139
  };
129
140
  }
130
141
  async performRefresh() {
@@ -4,7 +4,7 @@
4
4
  * Uses OAuth2 Device Flow for devices without browser or input capabilities.
5
5
  * User authorizes on another device by entering a code.
6
6
  */
7
- import type { ITokenResult, OAuth2GrantType } from '@mcp-abap-adt/interfaces';
7
+ import type { ILogger, ITokenResult, OAuth2GrantType } from '@mcp-abap-adt/interfaces';
8
8
  import { BaseTokenProvider } from './BaseTokenProvider';
9
9
  export interface DeviceFlowProviderConfig {
10
10
  uaaUrl: string;
@@ -13,6 +13,7 @@ export interface DeviceFlowProviderConfig {
13
13
  scope?: string;
14
14
  accessToken?: string;
15
15
  refreshToken?: string;
16
+ logger?: ILogger;
16
17
  }
17
18
  /**
18
19
  * Device Flow token provider
@@ -1 +1 @@
1
- {"version":3,"file":"DeviceFlowProvider.d.ts","sourceRoot":"","sources":["../../src/providers/DeviceFlowProvider.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAO9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;GAKG;AACH,qBAAa,kBAAmB,SAAQ,iBAAiB;IACvD,OAAO,CAAC,MAAM,CAA2B;gBAE7B,MAAM,EAAE,wBAAwB;IAa5C,SAAS,CAAC,WAAW,IAAI,eAAe;cAOxB,YAAY,IAAI,OAAO,CAAC,YAAY,CAAC;cA0CrC,cAAc,IAAI,OAAO,CAAC,YAAY,CAAC;CA2BxD"}
1
+ {"version":3,"file":"DeviceFlowProvider.d.ts","sourceRoot":"","sources":["../../src/providers/DeviceFlowProvider.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,OAAO,EACP,YAAY,EACZ,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAQlC,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;GAKG;AACH,qBAAa,kBAAmB,SAAQ,iBAAiB;IACvD,OAAO,CAAC,MAAM,CAA2B;gBAE7B,MAAM,EAAE,wBAAwB;IAc5C,SAAS,CAAC,WAAW,IAAI,eAAe;cAOxB,YAAY,IAAI,OAAO,CAAC,YAAY,CAAC;cAyCrC,cAAc,IAAI,OAAO,CAAC,YAAY,CAAC;CA2BxD"}