@mcp-abap-adt/auth-broker 3.0.3 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,825 +0,0 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
- /**
4
- * MCP SSO - Get tokens via SSO providers and generate .env files
5
- *
6
- * Usage:
7
- * mcp-sso <oidc|saml2|bearer> [options]
8
- * mcp-sso --protocol <oidc|saml2> --flow <flow> --output <path> [options]
9
- *
10
- * Examples:
11
- * # OIDC browser flow (authorization code with local callback)
12
- * mcp-sso oidc --flow browser --issuer https://issuer --client-id my-client --output ./sso.env --type xsuaa
13
- *
14
- * # OIDC device flow
15
- * mcp-sso oidc --flow device --issuer https://issuer --client-id my-client --output ./sso.env --type xsuaa
16
- *
17
- * # OIDC password flow
18
- * mcp-sso oidc --flow password --token-endpoint https://issuer/oauth/token --client-id my-client --username user --password pass --output ./sso.env --type xsuaa
19
- *
20
- * # OIDC token exchange
21
- * mcp-sso oidc --flow token_exchange --issuer https://issuer --client-id my-client --subject-token <token> --output ./sso.env --type xsuaa
22
- *
23
- * # SAML bearer flow with a service key: the Audience, Recipient and token alias come from
24
- * # <uaa.url>/saml/metadata, the IdP's trust from its metadata
25
- * mcp-sso bearer --service-key ./service-key.json --idp-metadata https://<ias-tenant>.accounts.ondemand.com/saml2/metadata --idp-initiated --output ./sso.env --type xsuaa
26
- *
27
- * # SAML bearer flow, everything stated (IdP-initiated assertion, as UAA/XSUAA require)
28
- * mcp-sso bearer --idp-sso-url https://idp/sso --sp-entity-id <uaa-entity-id> --acs-url <uaa-bearer-acs> --idp-cert ./idp-signing.pem --idp-entity-id https://idp/metadata --idp-initiated --token-endpoint https://uaa.example/oauth/token --assertion <base64> --output ./sso.env --type xsuaa
29
- *
30
- * # SAML pure flow (cookie)
31
- * mcp-sso saml2 --flow pure --idp-sso-url https://idp/sso --sp-entity-id my-sp --idp-cert ./idp-signing.pem --idp-entity-id https://idp/metadata --cookie "SAP_SESSION=..." --output ./sso.env --type abap
32
- */
33
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
34
- if (k2 === undefined) k2 = k;
35
- var desc = Object.getOwnPropertyDescriptor(m, k);
36
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
37
- desc = { enumerable: true, get: function() { return m[k]; } };
38
- }
39
- Object.defineProperty(o, k2, desc);
40
- }) : (function(o, m, k, k2) {
41
- if (k2 === undefined) k2 = k;
42
- o[k2] = m[k];
43
- }));
44
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
45
- Object.defineProperty(o, "default", { enumerable: true, value: v });
46
- }) : function(o, v) {
47
- o["default"] = v;
48
- });
49
- var __importStar = (this && this.__importStar) || (function () {
50
- var ownKeys = function(o) {
51
- ownKeys = Object.getOwnPropertyNames || function (o) {
52
- var ar = [];
53
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
54
- return ar;
55
- };
56
- return ownKeys(o);
57
- };
58
- return function (mod) {
59
- if (mod && mod.__esModule) return mod;
60
- var result = {};
61
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
62
- __setModuleDefault(result, mod);
63
- return result;
64
- };
65
- })();
66
- Object.defineProperty(exports, "__esModule", { value: true });
67
- const fs = __importStar(require("fs"));
68
- const path = __importStar(require("path"));
69
- // Use require for CommonJS dist files with absolute path
70
- const distPath = path.resolve(__dirname, '..', 'index.js');
71
- const { AuthBroker } = require(distPath);
72
- const auth_providers_1 = require("@mcp-abap-adt/auth-providers");
73
- const auth_stores_1 = require("@mcp-abap-adt/auth-stores");
74
- const logger_1 = require("@mcp-abap-adt/logger");
75
- const mcpSsoConfig_1 = require("./mcpSsoConfig");
76
- const samlMetadata_1 = require("./samlMetadata");
77
- const workDir_1 = require("./workDir");
78
- function getVersion() {
79
- try {
80
- const candidates = [
81
- path.join(__dirname, 'package.json'),
82
- path.join(__dirname, '..', 'package.json'),
83
- path.join(__dirname, '..', '..', 'package.json'),
84
- ];
85
- for (const candidate of candidates) {
86
- if (fs.existsSync(candidate)) {
87
- const packageJson = JSON.parse(fs.readFileSync(candidate, 'utf8'));
88
- return packageJson.version || 'unknown';
89
- }
90
- }
91
- const localRequire = require('module').createRequire(__filename);
92
- const resolved = localRequire.resolve('@mcp-abap-adt/auth-broker/package.json');
93
- const packageJson = JSON.parse(fs.readFileSync(resolved, 'utf8'));
94
- return packageJson.version || 'unknown';
95
- }
96
- catch {
97
- return 'unknown';
98
- }
99
- }
100
- function showHelp() {
101
- console.log('MCP SSO - Get tokens via SSO providers and generate .env files');
102
- console.log('');
103
- console.log('Usage:');
104
- console.log(' mcp-sso <oidc|saml2|bearer> [options]');
105
- console.log(' mcp-sso --protocol <oidc|saml2> --flow <flow> --output <path> [options]');
106
- console.log('');
107
- console.log('Required Options:');
108
- console.log(' --output <path> Output file path');
109
- console.log(' --protocol <oidc|saml2> Protocol (if no subcommand)');
110
- console.log(' --flow <flow> Flow for protocol (if no subcommand)');
111
- console.log('');
112
- console.log('Common Options:');
113
- console.log(' --service-key <path> Service key JSON (XSUAA/ABAP)');
114
- console.log(' --type <abap|xsuaa> Output type (default: abap)');
115
- console.log(' --format <env|json> Output format (default: env)');
116
- console.log(' --env <path> Optional existing env file (used for refresh)');
117
- console.log(' --destination <name> Destination name (default: output file base)');
118
- console.log(' --service-url <url> Service URL (ABAP: SAP URL, XSUAA: MCP URL)');
119
- console.log(' --config <path> JSON config file (SSO provider config)');
120
- console.log(' --browser <browser> Browser: auto|none|system|chrome|edge|firefox');
121
- console.log(' --redirect-port <port> Redirect port for browser flows (default: from auth-providers, currently 61001)');
122
- console.log(' --redirect-uri <uri> Custom redirect URI (OOB/manual code flows)');
123
- console.log('');
124
- console.log('OIDC Options:');
125
- console.log(' --issuer <url> OIDC issuer/discovery URL');
126
- console.log(' --authorization-endpoint <url> Authorization endpoint');
127
- console.log(' --token-endpoint <url> Token endpoint');
128
- console.log(' --device-authorization-endpoint <url> Device auth endpoint');
129
- console.log(' --client-id <id> OAuth client id');
130
- console.log(' --client-secret <secret> OAuth client secret');
131
- console.log(' --scopes <csv> Scopes list (comma or space-separated)');
132
- console.log(' --scope <value> Scope for token exchange');
133
- console.log(' --code <value> Authorization code (manual)');
134
- console.log(' --username <value> Username for password flow');
135
- console.log(' --password <value> Password for password flow');
136
- console.log(' --passcode <value> Passcode (alias for password, username=passcode)');
137
- console.log(' --subject-token <token> Subject token for token exchange');
138
- console.log(' --subject-token-type <type> Subject token type (default: access_token)');
139
- console.log(' --audience <value> Audience for token exchange');
140
- console.log(' --actor-token <token> Actor token for token exchange');
141
- console.log(' --actor-token-type <type> Actor token type for token exchange');
142
- console.log(' --uaa-url <url> UAA base URL (used to build token endpoint)');
143
- console.log('');
144
- console.log('SAML Options:');
145
- console.log(' --idp-sso-url <url> IdP SSO URL');
146
- console.log(' --sp-entity-id <id> SP Entity ID; also the Audience the assertion must name');
147
- console.log(' --acs-url <url> ACS URL; the Recipient the assertion must name (default: http://localhost:<port>/callback)');
148
- console.log(' --idp-cert <path> IdP signing certificate file (PEM or DER); repeat for key rotation');
149
- console.log(' --idp-entity-id <id> IdP entityID; the Issuer the assertion must name');
150
- console.log(' --idp-metadata <url|path> IdP SAML metadata (https or file): fills --idp-cert, --idp-entity-id');
151
- console.log(' and --idp-sso-url where not given, e.g. https://<ias>/saml2/metadata');
152
- console.log(' --idp-initiated The IdP starts the login; no AuthnRequest is sent (required for bearer');
153
- console.log(' against UAA/XSUAA). Use with --assertion or --assertion-flow manual');
154
- console.log(' --authn-request-id <id> AuthnRequest ID an --assertion answers (SP-initiated, request sent elsewhere)');
155
- console.log(' --saml-metadata <path> XSUAA SP metadata; bearer reads it for the token alias, --acs-url and');
156
- console.log(' --sp-entity-id. With --service-key, <uaa.url>/saml/metadata is read instead');
157
- console.log(' --relay-state <value> RelayState (optional)');
158
- console.log(' --assertion-flow <flow> browser|manual|assertion (default: browser; manual with --idp-initiated)');
159
- console.log(' --assertion <base64> SAMLResponse (base64)');
160
- console.log(' --cookie <value> Session cookies (for pure SAML)');
161
- console.log(' --token-endpoint <url> Token endpoint for SAML bearer exchange');
162
- console.log('');
163
- console.log(' Every SAML assertion is validated before use. Both SAML flows require --idp-cert');
164
- console.log(' and --idp-entity-id, or --idp-metadata (or idpCertificates/idpEntityId in --config). An --assertion');
165
- console.log(' also needs --idp-initiated or --authn-request-id; the browser and manual flows');
166
- console.log(' send their own request unless --idp-initiated is given.');
167
- console.log('');
168
- console.log(' version, --version, -v Show version number');
169
- console.log(' help, --help, -h Show this help message');
170
- }
171
- function parseScopes(value) {
172
- if (!value)
173
- return undefined;
174
- const parts = value
175
- .split(/[,\s]+/)
176
- .map((p) => p.trim())
177
- .filter(Boolean);
178
- return parts.length > 0 ? parts : undefined;
179
- }
180
- function createCliLogger(prefix = 'SSO') {
181
- const isEnabled = () => {
182
- if (process.env.DEBUG_SSO === 'false' ||
183
- process.env.DEBUG_AUTH_SSO === 'false') {
184
- return false;
185
- }
186
- if (process.env.DEBUG_SSO === 'true' ||
187
- process.env.DEBUG_AUTH_SSO === 'true' ||
188
- process.env.DEBUG === 'true' ||
189
- process.env.DEBUG?.includes('sso') === true ||
190
- process.env.DEBUG?.includes('auth-sso') === true) {
191
- return true;
192
- }
193
- return false;
194
- };
195
- const baseLogger = new logger_1.DefaultLogger((0, logger_1.getLogLevel)());
196
- return {
197
- debug: (message, meta) => {
198
- if (isEnabled()) {
199
- baseLogger.debug(`[${prefix}] ${message}`, meta);
200
- }
201
- },
202
- info: (message, meta) => {
203
- if (isEnabled()) {
204
- baseLogger.info(`[${prefix}] ${message}`, meta);
205
- }
206
- },
207
- warn: (message, meta) => {
208
- if (isEnabled()) {
209
- baseLogger.warn(`[${prefix}] ${message}`, meta);
210
- }
211
- },
212
- error: (message, meta) => {
213
- if (isEnabled()) {
214
- baseLogger.error(`[${prefix}] ${message}`, meta);
215
- }
216
- },
217
- };
218
- }
219
- function parseArgs() {
220
- let args = process.argv.slice(2);
221
- if (args.length === 0 ||
222
- args[0] === 'help' ||
223
- args.includes('--help') ||
224
- args.includes('-h')) {
225
- showHelp();
226
- process.exit(0);
227
- }
228
- if (args[0] === 'version' ||
229
- args.includes('--version') ||
230
- args.includes('-v')) {
231
- console.log(getVersion());
232
- process.exit(0);
233
- }
234
- let outputFile;
235
- let envFilePath;
236
- let destination;
237
- let serviceKeyPath;
238
- let authType = 'abap';
239
- let format = 'env';
240
- let protocol;
241
- let flow;
242
- let configPath;
243
- let serviceUrl;
244
- let browser;
245
- let redirectPort;
246
- let redirectUri;
247
- let issuerUrl;
248
- let authorizationEndpoint;
249
- let tokenEndpoint;
250
- let deviceAuthorizationEndpoint;
251
- let clientId;
252
- let clientSecret;
253
- let scopes;
254
- let scope;
255
- let code;
256
- let username;
257
- let password;
258
- let passcode;
259
- let subjectToken;
260
- let subjectTokenType;
261
- let audience;
262
- let actorToken;
263
- let actorTokenType;
264
- let idpSsoUrl;
265
- let spEntityId;
266
- let acsUrl;
267
- let relayState;
268
- let assertionFlow;
269
- let assertion;
270
- let cookie;
271
- let uaaUrl;
272
- let samlMetadataPath;
273
- const samlTrust = {};
274
- const firstArg = args[0];
275
- if (firstArg && !firstArg.startsWith('-')) {
276
- if (firstArg === 'oidc') {
277
- protocol = 'oidc';
278
- }
279
- else if (firstArg === 'saml2') {
280
- protocol = 'saml2';
281
- }
282
- else if (firstArg === 'bearer') {
283
- protocol = 'saml2';
284
- flow = 'bearer';
285
- }
286
- else {
287
- console.error(`Unknown command: ${firstArg}`);
288
- showHelp();
289
- process.exit(1);
290
- }
291
- args = args.slice(1);
292
- }
293
- for (let i = 0; i < args.length; i++) {
294
- const arg = args[i];
295
- const next = i + 1 < args.length ? args[i + 1] : undefined;
296
- switch (arg) {
297
- case '--output':
298
- outputFile = next;
299
- i++;
300
- break;
301
- case '--env':
302
- envFilePath = next;
303
- i++;
304
- break;
305
- case '--service-key':
306
- serviceKeyPath = next;
307
- i++;
308
- break;
309
- case '--destination':
310
- destination = next;
311
- i++;
312
- break;
313
- case '--type':
314
- if (next === 'abap' || next === 'xsuaa') {
315
- authType = next;
316
- }
317
- else {
318
- console.error(`Invalid type: ${next}. Use abap or xsuaa.`);
319
- process.exit(1);
320
- }
321
- i++;
322
- break;
323
- case '--format':
324
- if (next === 'env' || next === 'json') {
325
- format = next;
326
- }
327
- else {
328
- console.error(`Invalid format: ${next}. Use env or json.`);
329
- process.exit(1);
330
- }
331
- i++;
332
- break;
333
- case '--protocol':
334
- if (next === 'oidc' || next === 'saml2') {
335
- protocol = next;
336
- }
337
- else {
338
- console.error(`Invalid protocol: ${next}. Use oidc or saml2.`);
339
- process.exit(1);
340
- }
341
- i++;
342
- break;
343
- case '--flow':
344
- flow = next;
345
- i++;
346
- break;
347
- case '--config':
348
- configPath = next;
349
- i++;
350
- break;
351
- case '--service-url':
352
- serviceUrl = next;
353
- i++;
354
- break;
355
- case '--browser':
356
- browser = next;
357
- i++;
358
- break;
359
- case '--redirect-port':
360
- if (!next)
361
- break;
362
- redirectPort = parseInt(next, 10);
363
- if (Number.isNaN(redirectPort) ||
364
- redirectPort < 1 ||
365
- redirectPort > 65535) {
366
- console.error(`Invalid redirect port: ${next}`);
367
- process.exit(1);
368
- }
369
- i++;
370
- break;
371
- case '--redirect-uri':
372
- redirectUri = next;
373
- i++;
374
- break;
375
- case '--issuer':
376
- issuerUrl = next;
377
- i++;
378
- break;
379
- case '--authorization-endpoint':
380
- authorizationEndpoint = next;
381
- i++;
382
- break;
383
- case '--token-endpoint':
384
- tokenEndpoint = next;
385
- i++;
386
- break;
387
- case '--device-authorization-endpoint':
388
- deviceAuthorizationEndpoint = next;
389
- i++;
390
- break;
391
- case '--client-id':
392
- clientId = next;
393
- i++;
394
- break;
395
- case '--client-secret':
396
- clientSecret = next;
397
- i++;
398
- break;
399
- case '--scopes':
400
- scopes = parseScopes(next);
401
- i++;
402
- break;
403
- case '--scope':
404
- scope = next;
405
- i++;
406
- break;
407
- case '--code':
408
- code = next;
409
- i++;
410
- break;
411
- case '--username':
412
- username = next;
413
- i++;
414
- break;
415
- case '--password':
416
- password = next;
417
- i++;
418
- break;
419
- case '--passcode':
420
- passcode = next;
421
- i++;
422
- break;
423
- case '--subject-token':
424
- subjectToken = next;
425
- i++;
426
- break;
427
- case '--subject-token-type':
428
- subjectTokenType = next;
429
- i++;
430
- break;
431
- case '--audience':
432
- audience = next;
433
- i++;
434
- break;
435
- case '--actor-token':
436
- actorToken = next;
437
- i++;
438
- break;
439
- case '--actor-token-type':
440
- actorTokenType = next;
441
- i++;
442
- break;
443
- case '--idp-sso-url':
444
- idpSsoUrl = next;
445
- i++;
446
- break;
447
- case '--sp-entity-id':
448
- spEntityId = next;
449
- i++;
450
- break;
451
- case '--acs-url':
452
- acsUrl = next;
453
- i++;
454
- break;
455
- case '--relay-state':
456
- relayState = next;
457
- i++;
458
- break;
459
- case '--assertion-flow':
460
- if (next === 'browser' || next === 'manual' || next === 'assertion') {
461
- assertionFlow = next;
462
- }
463
- else {
464
- console.error(`Invalid assertion flow: ${next}. Use browser, manual, or assertion.`);
465
- process.exit(1);
466
- }
467
- i++;
468
- break;
469
- case '--assertion':
470
- assertion = next;
471
- i++;
472
- break;
473
- case '--cookie':
474
- cookie = next;
475
- i++;
476
- break;
477
- case '--uaa-url':
478
- uaaUrl = next;
479
- i++;
480
- break;
481
- case '--saml-metadata':
482
- samlMetadataPath = next;
483
- i++;
484
- break;
485
- default:
486
- i += (0, mcpSsoConfig_1.parseSamlTrustArg)(samlTrust, arg, next);
487
- break;
488
- }
489
- }
490
- return {
491
- outputFile,
492
- envFilePath,
493
- destination,
494
- serviceKeyPath,
495
- authType,
496
- format,
497
- protocol,
498
- flow: flow,
499
- configPath,
500
- serviceUrl,
501
- browser,
502
- redirectPort,
503
- redirectUri,
504
- issuerUrl,
505
- authorizationEndpoint,
506
- tokenEndpoint,
507
- deviceAuthorizationEndpoint,
508
- clientId,
509
- clientSecret,
510
- scopes,
511
- scope,
512
- code,
513
- username,
514
- password,
515
- passcode,
516
- subjectToken,
517
- subjectTokenType,
518
- audience,
519
- actorToken,
520
- actorTokenType,
521
- idpSsoUrl,
522
- spEntityId,
523
- acsUrl,
524
- relayState,
525
- assertionFlow,
526
- assertion,
527
- cookie,
528
- uaaUrl,
529
- samlMetadataPath,
530
- ...samlTrust,
531
- };
532
- }
533
- async function main() {
534
- const options = parseArgs();
535
- if (!options) {
536
- return;
537
- }
538
- const logger = createCliLogger();
539
- if (!options.outputFile) {
540
- console.error('❌ Missing required --output');
541
- process.exit(1);
542
- }
543
- const resolvedOutputPath = path.resolve(options.outputFile);
544
- const resolvedEnvPath = options.envFilePath
545
- ? path.resolve(options.envFilePath)
546
- : undefined;
547
- let destination = options.destination;
548
- if (options.serviceKeyPath) {
549
- const resolvedServiceKeyPath = path.resolve(options.serviceKeyPath);
550
- if (!fs.existsSync(resolvedServiceKeyPath)) {
551
- console.error(`❌ Service key file not found: ${resolvedServiceKeyPath}`);
552
- process.exit(1);
553
- }
554
- const serviceKeyFileName = path.basename(resolvedServiceKeyPath, path.extname(resolvedServiceKeyPath));
555
- if (destination && destination !== serviceKeyFileName) {
556
- console.error(`❌ Destination mismatch: service key (${serviceKeyFileName}) vs output (${destination})`);
557
- process.exit(1);
558
- }
559
- destination = serviceKeyFileName;
560
- }
561
- if (!destination) {
562
- destination = path.basename(resolvedOutputPath, path.extname(resolvedOutputPath));
563
- }
564
- if (resolvedEnvPath) {
565
- const envName = path.basename(resolvedEnvPath, path.extname(resolvedEnvPath));
566
- if (destination && envName !== destination) {
567
- console.error(`❌ Destination mismatch: env file (${envName}) vs output (${destination})`);
568
- process.exit(1);
569
- }
570
- }
571
- const allowTokenEndpointWithServiceKey = options.protocol === 'saml2' && options.flow === 'bearer';
572
- const serviceKeyConflicts = options.serviceKeyPath &&
573
- (options.configPath ||
574
- options.issuerUrl ||
575
- options.authorizationEndpoint ||
576
- (!allowTokenEndpointWithServiceKey && options.tokenEndpoint) ||
577
- options.deviceAuthorizationEndpoint ||
578
- options.clientId ||
579
- options.clientSecret ||
580
- options.uaaUrl);
581
- if (serviceKeyConflicts) {
582
- console.error('❌ Use either --service-key or explicit OIDC/SAML parameters (issuer/token/client/uaa).');
583
- process.exit(1);
584
- }
585
- if (options.serviceKeyPath && options.authType !== 'xsuaa') {
586
- console.error('❌ --service-key is supported only for XSUAA flows.');
587
- process.exit(1);
588
- }
589
- let providerConfigFromFile = null;
590
- if (options.configPath) {
591
- const resolvedConfigPath = path.resolve(options.configPath);
592
- if (!fs.existsSync(resolvedConfigPath)) {
593
- console.error(`❌ Config file not found: ${resolvedConfigPath}`);
594
- process.exit(1);
595
- }
596
- const raw = JSON.parse(fs.readFileSync(resolvedConfigPath, 'utf8'));
597
- providerConfigFromFile = (0, mcpSsoConfig_1.normalizeProviderConfig)(raw);
598
- if (!providerConfigFromFile) {
599
- console.error(`❌ Config file does not contain provider config`);
600
- process.exit(1);
601
- }
602
- }
603
- // Merge the file into `options` *before* anything downstream reads
604
- // options.protocol/flow or builds a strategy from them — a run driven by
605
- // --config alone must reach exactly the same validation and
606
- // strategy-building code a --protocol/--flow run does, or a field the file
607
- // carries (including a legacy one 2.0.0 removed) can reach the provider
608
- // untouched with no error and no warning. CLI flags already parsed above
609
- // are left alone; the file only fills what they didn't set. A no-op when
610
- // --config wasn't given.
611
- (0, mcpSsoConfig_1.applyFileConfig)(options, providerConfigFromFile);
612
- // The user's own --token-endpoint, before a service key sets the plain
613
- // /oauth/token, which a saml2-bearer grant must not use.
614
- const explicitTokenEndpoint = options.tokenEndpoint;
615
- if (options.serviceKeyPath) {
616
- const resolvedServiceKeyPath = path.resolve(options.serviceKeyPath);
617
- const serviceKeyDir = path.dirname(resolvedServiceKeyPath);
618
- const serviceKeyStore = new auth_stores_1.XsuaaServiceKeyStore(serviceKeyDir);
619
- const authConfig = await serviceKeyStore.getAuthorizationConfig(destination);
620
- if (!authConfig) {
621
- console.error(`❌ Authorization config not found for ${destination}. Service key must contain clientid, clientsecret, and url fields.`);
622
- process.exit(1);
623
- }
624
- const uaaUrl = authConfig.uaaUrl;
625
- if (!uaaUrl) {
626
- console.error(`❌ Service key missing UAA URL for ${destination}.`);
627
- process.exit(1);
628
- }
629
- options.uaaUrl = uaaUrl;
630
- options.clientId = authConfig.uaaClientId;
631
- options.clientSecret = authConfig.uaaClientSecret;
632
- if (!options.issuerUrl) {
633
- options.issuerUrl = uaaUrl;
634
- }
635
- if (!options.tokenEndpoint) {
636
- options.tokenEndpoint = `${uaaUrl.replace(/\/+$/, '')}/oauth/token`;
637
- }
638
- if (!options.authorizationEndpoint) {
639
- options.authorizationEndpoint = `${uaaUrl.replace(/\/+$/, '')}/oauth/authorize`;
640
- }
641
- }
642
- // What the SAML metadata states and the caller did not: the identity
643
- // provider's trust from --idp-metadata, and for saml2-bearer the Audience,
644
- // Recipient and token endpoint from XSUAA's own metadata (--saml-metadata,
645
- // else <uaa.url>/saml/metadata from the service key).
646
- try {
647
- await (0, samlMetadata_1.applySamlMetadata)(options, explicitTokenEndpoint);
648
- }
649
- catch (error) {
650
- console.error(`❌ SAML metadata: ${error instanceof Error ? error.message : String(error)}`);
651
- process.exit(1);
652
- }
653
- if (options.protocol === 'oidc' && options.flow === 'password') {
654
- if (options.uaaUrl && !options.tokenEndpoint) {
655
- options.tokenEndpoint = `${options.uaaUrl.replace(/\/+$/, '')}/oauth/token`;
656
- }
657
- }
658
- if (options.protocol === 'oidc' && options.flow) {
659
- const valid = ['browser', 'device', 'password', 'token_exchange'];
660
- if (!valid.includes(options.flow)) {
661
- console.error(`❌ Invalid OIDC flow: ${options.flow}. Use one of: ${valid.join(', ')}`);
662
- process.exit(1);
663
- }
664
- }
665
- if (options.protocol === 'saml2' && options.flow) {
666
- const valid = ['bearer', 'pure'];
667
- if (!valid.includes(options.flow)) {
668
- console.error(`❌ Invalid SAML flow: ${options.flow}. Use one of: ${valid.join(', ')}`);
669
- process.exit(1);
670
- }
671
- }
672
- if (options.protocol === 'oidc' && options.flow === 'password') {
673
- if (!options.passcode && !options.password) {
674
- options.passcode = await (0, mcpSsoConfig_1.readManualInput)('Paste passcode: ');
675
- }
676
- }
677
- // Removed on any exit, error and signal included: it holds the secret.
678
- const tempSessionDir = (0, workDir_1.createWorkDir)('mcp-sso');
679
- if (resolvedEnvPath && fs.existsSync(resolvedEnvPath)) {
680
- const tempEnvPath = path.join(tempSessionDir, `${destination}.env`);
681
- fs.copyFileSync(resolvedEnvPath, tempEnvPath);
682
- }
683
- const placeholderServiceUrl = '<SERVICE_URL>';
684
- const defaultServiceUrl = options.authType === 'xsuaa'
685
- ? options.serviceUrl || placeholderServiceUrl
686
- : options.serviceUrl || '';
687
- const sessionStore = options.authType === 'xsuaa'
688
- ? new auth_stores_1.XsuaaSessionStore(tempSessionDir, defaultServiceUrl)
689
- : new auth_stores_1.AbapSessionStore(tempSessionDir, undefined, options.serviceUrl);
690
- const existingConn = await sessionStore.getConnectionConfig(destination);
691
- const existingAuth = await sessionStore.getAuthorizationConfig(destination);
692
- const serviceUrl = options.serviceUrl || existingConn?.serviceUrl;
693
- if (options.authType === 'abap' && !serviceUrl) {
694
- console.error('❌ ABAP requires --service-url or existing env with SAP URL');
695
- process.exit(1);
696
- }
697
- if (options.protocol === 'saml2' &&
698
- options.flow === 'pure' &&
699
- options.authType === 'xsuaa') {
700
- console.error('❌ SAML pure flow is only supported for ABAP sessions (cookies)');
701
- process.exit(1);
702
- }
703
- const isSamlPureAbap = options.authType === 'abap' &&
704
- options.protocol === 'saml2' &&
705
- options.flow === 'pure';
706
- await sessionStore.setConnectionConfig(destination, {
707
- serviceUrl: serviceUrl ||
708
- (options.authType === 'xsuaa' ? defaultServiceUrl : undefined),
709
- authorizationToken: isSamlPureAbap
710
- ? existingConn?.authorizationToken || '__init__'
711
- : existingConn?.authorizationToken,
712
- sessionCookies: existingConn?.sessionCookies,
713
- });
714
- let stripClientSecret = false;
715
- const authUaaUrl = options.uaaUrl || options.tokenEndpoint || options.issuerUrl || undefined;
716
- if (options.clientId && authUaaUrl) {
717
- let clientSecret = options.clientSecret;
718
- if (!clientSecret) {
719
- clientSecret = '__public__';
720
- stripClientSecret = true;
721
- }
722
- await sessionStore.setAuthorizationConfig(destination, {
723
- uaaUrl: authUaaUrl,
724
- uaaClientId: options.clientId,
725
- uaaClientSecret: clientSecret,
726
- refreshToken: existingAuth?.refreshToken,
727
- });
728
- }
729
- const withLogger = (config) => config.config
730
- ? {
731
- ...config,
732
- config: {
733
- ...config.config,
734
- logger: config.config?.logger ?? logger,
735
- },
736
- }
737
- : config;
738
- const tokenProvider = auth_providers_1.SsoProviderFactory.create(withLogger((0, mcpSsoConfig_1.buildProviderConfig)(options, existingAuth, existingConn)));
739
- const broker = new AuthBroker({
740
- sessionStore,
741
- provider: tokenProvider,
742
- }, logger);
743
- console.log(`🔐 Getting token for destination "${destination}"...`);
744
- await broker.getToken(destination);
745
- console.log(`✅ Token obtained successfully`);
746
- const connConfig = await sessionStore.getConnectionConfig(destination);
747
- const authConfig = await sessionStore.getAuthorizationConfig(destination);
748
- if (!connConfig) {
749
- throw new Error('Connection config not found after authentication');
750
- }
751
- const isSaml = !!connConfig.sessionCookies && !connConfig.authorizationToken;
752
- const token = isSaml
753
- ? connConfig.sessionCookies
754
- : connConfig.authorizationToken;
755
- if (!token) {
756
- throw new Error('Token provider did not return authorization token');
757
- }
758
- if (options.format === 'env') {
759
- const tempEnvPath = path.join(tempSessionDir, `${destination}.env`);
760
- if (!fs.existsSync(tempEnvPath)) {
761
- throw new Error(`Temp env file not found: ${tempEnvPath}`);
762
- }
763
- const outputDir = path.dirname(resolvedOutputPath);
764
- if (!fs.existsSync(outputDir)) {
765
- fs.mkdirSync(outputDir, { recursive: true });
766
- }
767
- let envContent = fs.readFileSync(tempEnvPath, 'utf8');
768
- if (options.authType === 'xsuaa' && !serviceUrl) {
769
- const lines = envContent
770
- .split('\n')
771
- .filter((line) => !line.startsWith('XSUAA_MCP_URL='));
772
- envContent = `${lines.join('\n')}\n`;
773
- }
774
- if (stripClientSecret) {
775
- const lines = envContent
776
- .split('\n')
777
- .filter((line) => !line.startsWith('XSUAA_UAA_CLIENT_SECRET=') &&
778
- !line.startsWith('SAP_UAA_CLIENT_SECRET='));
779
- envContent = `${lines.join('\n')}\n`;
780
- }
781
- fs.writeFileSync(resolvedOutputPath, envContent, 'utf8');
782
- console.log(`✅ .env file created: ${resolvedOutputPath}`);
783
- }
784
- else {
785
- const outputData = {
786
- tokenType: isSaml ? 'saml' : 'jwt',
787
- };
788
- if (isSaml) {
789
- outputData.sessionCookies = token;
790
- }
791
- else {
792
- outputData.accessToken = token;
793
- }
794
- if (authConfig?.refreshToken) {
795
- outputData.refreshToken = authConfig.refreshToken;
796
- }
797
- if (serviceUrl) {
798
- outputData.serviceUrl = serviceUrl;
799
- }
800
- if (authConfig?.uaaUrl) {
801
- outputData.uaaUrl = authConfig.uaaUrl;
802
- }
803
- if (authConfig?.uaaClientId) {
804
- outputData.uaaClientId = authConfig.uaaClientId;
805
- }
806
- if (authConfig?.uaaClientSecret) {
807
- if (!stripClientSecret) {
808
- outputData.uaaClientSecret = authConfig.uaaClientSecret;
809
- }
810
- }
811
- const outputDir = path.dirname(resolvedOutputPath);
812
- if (!fs.existsSync(outputDir)) {
813
- fs.mkdirSync(outputDir, { recursive: true });
814
- }
815
- fs.writeFileSync(resolvedOutputPath, JSON.stringify(outputData, null, 2), 'utf8');
816
- console.log(`✅ JSON file created: ${resolvedOutputPath}`);
817
- }
818
- }
819
- main().catch((error) => {
820
- console.error(`❌ Error: ${error.message}`);
821
- if (error.stack) {
822
- console.error(error.stack);
823
- }
824
- process.exit(1);
825
- });