@mcp-abap-adt/auth-broker 3.0.4 → 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,752 +0,0 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
- /**
4
- * MCP Auth - Get tokens and generate .env files from service keys
5
- *
6
- * Usage:
7
- * mcp-auth --service-key <path> --output <path> [--env <path>] [--type abap|xsuaa] [--credential] [--browser auto|none|chrome|edge|firefox|system] [--format json|env]
8
- *
9
- * Examples:
10
- * # Generate .env file with authorization_code (default)
11
- * mcp-auth --service-key ./service-key.json --output ./mcp.env --type xsuaa
12
- *
13
- * # With authorization_code, show URL in console (no browser)
14
- * mcp-auth --service-key ./service-key.json --output ./mcp.env --type xsuaa --browser none
15
- *
16
- * # With client_credentials (special cases)
17
- * mcp-auth --service-key ./service-key.json --output ./mcp.env --type xsuaa --credential
18
- *
19
- * # Generate .env file for ABAP
20
- * mcp-auth --service-key ./abap-key.json --output ./abap.env --type abap
21
- */
22
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
23
- if (k2 === undefined) k2 = k;
24
- var desc = Object.getOwnPropertyDescriptor(m, k);
25
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
26
- desc = { enumerable: true, get: function() { return m[k]; } };
27
- }
28
- Object.defineProperty(o, k2, desc);
29
- }) : (function(o, m, k, k2) {
30
- if (k2 === undefined) k2 = k;
31
- o[k2] = m[k];
32
- }));
33
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
34
- Object.defineProperty(o, "default", { enumerable: true, value: v });
35
- }) : function(o, v) {
36
- o["default"] = v;
37
- });
38
- var __importStar = (this && this.__importStar) || (function () {
39
- var ownKeys = function(o) {
40
- ownKeys = Object.getOwnPropertyNames || function (o) {
41
- var ar = [];
42
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
43
- return ar;
44
- };
45
- return ownKeys(o);
46
- };
47
- return function (mod) {
48
- if (mod && mod.__esModule) return mod;
49
- var result = {};
50
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
51
- __setModuleDefault(result, mod);
52
- return result;
53
- };
54
- })();
55
- Object.defineProperty(exports, "__esModule", { value: true });
56
- const child_process_1 = require("child_process");
57
- const fs = __importStar(require("fs"));
58
- const path = __importStar(require("path"));
59
- // Use require for CommonJS dist files with absolute path
60
- const distPath = path.resolve(__dirname, '..', 'index.js');
61
- const { AuthBroker } = require(distPath);
62
- const auth_providers_1 = require("@mcp-abap-adt/auth-providers");
63
- const auth_stores_1 = require("@mcp-abap-adt/auth-stores");
64
- const workDir_1 = require("./workDir");
65
- /**
66
- * A person completes this login at a browser; the provider's own default
67
- * (30s) is sized for an unattended caller instead.
68
- */
69
- const INTERACTIVE_LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
70
- function getVersion() {
71
- try {
72
- const candidates = [
73
- path.join(__dirname, 'package.json'),
74
- path.join(__dirname, '..', 'package.json'),
75
- path.join(__dirname, '..', '..', 'package.json'),
76
- ];
77
- for (const candidate of candidates) {
78
- if (fs.existsSync(candidate)) {
79
- const packageJson = JSON.parse(fs.readFileSync(candidate, 'utf8'));
80
- return packageJson.version || 'unknown';
81
- }
82
- }
83
- const localRequire = require('module').createRequire(__filename);
84
- const resolved = localRequire.resolve('@mcp-abap-adt/auth-broker/package.json');
85
- const packageJson = JSON.parse(fs.readFileSync(resolved, 'utf8'));
86
- return packageJson.version || 'unknown';
87
- }
88
- catch {
89
- return 'unknown';
90
- }
91
- }
92
- function showHelp() {
93
- console.log('MCP Auth - Get tokens and generate .env files from service keys');
94
- console.log('');
95
- console.log('Usage:');
96
- console.log(' mcp-auth <auth-code|oidc|saml2-pure|saml2-bearer> [options]');
97
- console.log(' mcp-auth --service-key <path> --output <path> [options]');
98
- console.log('');
99
- console.log('Required Options:');
100
- console.log(' --output <path> Output file path');
101
- console.log('');
102
- console.log('Service Key or Env (one required):');
103
- console.log(' --service-key <path> Path to service key JSON file');
104
- console.log(' --env <path> Path to existing .env file (used for refresh token)');
105
- console.log('');
106
- console.log('Optional Options:');
107
- console.log(' --type <type> Auth type: abap or xsuaa (default: abap)');
108
- console.log(' --dev Enable in-progress commands (saml2-bearer)');
109
- console.log(' --credential Use client_credentials flow (clientId/clientSecret, no browser)');
110
- console.log(' By default uses authorization_code flow');
111
- console.log(' --browser <browser> Browser for authorization_code flow (default: auto):');
112
- console.log(' - auto: Try to open browser, fallback to showing URL (like cf login)');
113
- console.log(' - none/headless: Show URL in console and wait for callback');
114
- console.log(' - system/chrome/edge/firefox: Open specific browser');
115
- console.log(' --format <format> Output format: json or env (default: env)');
116
- console.log(' --service-url <url> Service URL (SAP URL for ABAP, MCP URL for XSUAA). For XSUAA, optional.');
117
- console.log(' --redirect-port <port> Port for OAuth redirect URI (default: from auth-providers, currently 61001).');
118
- console.log(' Must match XSUAA redirect-uris config.');
119
- console.log('');
120
- console.log('SAML (saml2-pure, saml2-bearer; passed to mcp-sso, see mcp-sso --help for all):');
121
- console.log(' --idp-metadata <url|path> IdP SAML metadata (https or file): certificate, entityID, SSO URL');
122
- console.log(' e.g. https://<ias-tenant>.accounts.ondemand.com/saml2/metadata');
123
- console.log(' --idp-cert <path> IdP signing certificate (PEM or DER); repeat for key rotation');
124
- console.log(' --idp-entity-id <id> IdP entityID; the Issuer the assertion must name');
125
- console.log(' --idp-sso-url <url> IdP SSO endpoint (read from --idp-metadata when not given)');
126
- console.log(' --idp-initiated The IdP starts the login; required for saml2-bearer against XSUAA');
127
- console.log(' --authn-request-id <id> AuthnRequest ID an --assertion answers, when sent elsewhere');
128
- console.log(' --sp-entity-id <id> SP entityID, the Audience (saml2-bearer: read from XSUAA metadata)');
129
- console.log(' --acs-url <url> The Recipient (saml2-bearer: read from XSUAA metadata)');
130
- console.log(' --saml-metadata <path> XSUAA SP metadata file; with --service-key <uaa.url>/saml/metadata is read');
131
- console.log(' --assertion <base64> A SAMLResponse obtained elsewhere');
132
- console.log(' --assertion-flow <flow> browser|manual|assertion');
133
- console.log('');
134
- console.log(' version, --version, -v Show version number');
135
- console.log(' help, --help, -h Show this help message');
136
- console.log('');
137
- console.log('Examples:');
138
- console.log(' # Auth code (default flow via service key)');
139
- console.log(' mcp-auth auth-code --service-key ./service-key.json --output ./mcp.env --type xsuaa');
140
- console.log('');
141
- console.log(' # OIDC SSO (device/password/browser/token-exchange)');
142
- console.log(' mcp-auth oidc --flow device --issuer https://issuer --client-id my-client --output ./sso.env --type xsuaa');
143
- console.log('');
144
- console.log(' # SAML2 pure (cookies); every assertion is validated: see mcp-sso --help');
145
- console.log(' mcp-auth saml2-pure --idp-sso-url https://idp/sso --sp-entity-id my-sp --idp-cert ./idp.pem --idp-entity-id https://idp/metadata --output ./saml.env --type abap');
146
- console.log('');
147
- console.log(' # SAML2 bearer (in progress, requires --dev)');
148
- console.log(' mcp-auth saml2-bearer --dev --service-key ./service-key.json --idp-metadata https://<ias-tenant>.accounts.ondemand.com/saml2/metadata --idp-initiated --output ./sso.env --type xsuaa');
149
- console.log('');
150
- console.log(' # XSUAA with authorization_code (default, opens browser)');
151
- console.log(' mcp-auth --service-key ./service-key.json --output ./mcp.env --type xsuaa');
152
- console.log('');
153
- console.log(' # XSUAA with authorization_code (show URL in console, no browser)');
154
- console.log(' mcp-auth --service-key ./service-key.json --output ./mcp.env --type xsuaa --browser none');
155
- console.log('');
156
- console.log(' # XSUAA using existing .env refresh token, fallback to service key');
157
- console.log(' mcp-auth --env ./mcp.env --service-key ./service-key.json --output ./mcp.env --type xsuaa');
158
- console.log('');
159
- console.log(' # XSUAA with client_credentials (special cases)');
160
- console.log(' mcp-auth --service-key ./service-key.json --output ./mcp.env --type xsuaa --credential');
161
- console.log('');
162
- console.log(' # XSUAA with custom redirect port');
163
- console.log(' mcp-auth --service-key ./service-key.json --output ./mcp.env --type xsuaa --redirect-port 8080');
164
- console.log('');
165
- console.log(' # ABAP with authorization_code (default)');
166
- console.log(' mcp-auth --service-key ./abap-key.json --output ./abap.env --type abap');
167
- console.log('');
168
- console.log(' # ABAP with client_credentials (special cases)');
169
- console.log(' mcp-auth --service-key ./abap-key.json --output ./abap.env --type abap --credential');
170
- console.log('');
171
- console.log('Notes:');
172
- console.log(' - --type determines the provider (xsuaa or abap)');
173
- console.log(' - If --env is provided and file exists, refresh token is attempted first');
174
- console.log(' - If refresh fails or env file is missing, service key auth is used');
175
- console.log(' - Authentication flow:');
176
- console.log(' * Default: authorization_code (browser-based OAuth2)');
177
- console.log(' * --credential: client_credentials (clientId/clientSecret, no browser)');
178
- console.log(' - Browser options for authorization_code:');
179
- console.log(' * auto (default): Try to open browser, fallback to showing URL');
180
- console.log(' * none/headless: Show URL in console and wait for callback');
181
- console.log(' * system/chrome/edge/firefox: Open specific browser');
182
- console.log(' - Both providers (xsuaa and abap) support both flows');
183
- console.log(' - --redirect-port: Port for OAuth redirect URI (default: from auth-providers, currently 61001)');
184
- console.log(' * Must match redirect_uri configured in XSUAA/ABAP OAuth2 settings');
185
- console.log(' * If your registered redirect URI uses a different port (e.g. 3001, 8080), pass it explicitly');
186
- console.log(' - For XSUAA, serviceUrl (MCP URL) is optional - can be provided via --service-url or service key');
187
- console.log(' - For ABAP, serviceUrl (SAP URL) is required - can be provided via --service-url or service key');
188
- console.log(' - SAP_URL/XSUAA_MCP_URL is written to .env (from --service-url, service key, or placeholder)');
189
- }
190
- function parseArgs(args = process.argv.slice(2)) {
191
- // Handle --version and --help first
192
- if (args.length === 0 ||
193
- args[0] === 'help' ||
194
- args.includes('--help') ||
195
- args.includes('-h')) {
196
- showHelp();
197
- process.exit(0);
198
- }
199
- if (args[0] === 'version' ||
200
- args.includes('--version') ||
201
- args.includes('-v')) {
202
- console.log(getVersion());
203
- process.exit(0);
204
- }
205
- let serviceKeyPath;
206
- let envFilePath;
207
- let outputFile;
208
- let authType = 'abap';
209
- let browser = 'auto'; // Default to auto for authorization_code flow
210
- let credential = false; // Use client_credentials instead of authorization_code
211
- let format = 'env';
212
- let serviceUrl;
213
- let redirectPort;
214
- // Parse arguments
215
- for (let i = 0; i < args.length; i++) {
216
- if (args[i] === '--service-key' && i + 1 < args.length) {
217
- serviceKeyPath = args[i + 1];
218
- i++;
219
- }
220
- else if (args[i] === '--env' && i + 1 < args.length) {
221
- envFilePath = args[i + 1];
222
- i++;
223
- }
224
- else if (args[i] === '--output' && i + 1 < args.length) {
225
- outputFile = args[i + 1];
226
- i++;
227
- }
228
- else if (args[i] === '--type' && i + 1 < args.length) {
229
- const type = args[i + 1];
230
- if (type === 'abap' || type === 'xsuaa') {
231
- authType = type;
232
- }
233
- else {
234
- console.error(`Invalid auth type: ${type}. Must be 'abap' or 'xsuaa'`);
235
- process.exit(1);
236
- }
237
- i++;
238
- }
239
- else if (args[i] === '--browser' && i + 1 < args.length) {
240
- browser = args[i + 1];
241
- if (![
242
- 'none',
243
- 'chrome',
244
- 'edge',
245
- 'firefox',
246
- 'system',
247
- 'headless',
248
- 'auto',
249
- ].includes(browser)) {
250
- console.error(`Invalid browser: ${browser}. Must be one of: none, chrome, edge, firefox, system, headless, auto`);
251
- process.exit(1);
252
- }
253
- i++;
254
- }
255
- else if (args[i] === '--format' && i + 1 < args.length) {
256
- const fmt = args[i + 1];
257
- if (fmt === 'json' || fmt === 'env') {
258
- format = fmt;
259
- }
260
- else {
261
- console.error(`Invalid format: ${fmt}. Must be 'json' or 'env'`);
262
- process.exit(1);
263
- }
264
- i++;
265
- }
266
- else if (args[i] === '--service-url' && i + 1 < args.length) {
267
- serviceUrl = args[i + 1];
268
- i++;
269
- }
270
- else if (args[i] === '--redirect-port' && i + 1 < args.length) {
271
- const port = parseInt(args[i + 1], 10);
272
- if (isNaN(port) || port < 1 || port > 65535) {
273
- console.error(`Invalid redirect port: ${args[i + 1]}. Must be a number between 1 and 65535`);
274
- process.exit(1);
275
- }
276
- redirectPort = port;
277
- i++;
278
- }
279
- else if (args[i] === '--credential') {
280
- credential = true;
281
- }
282
- else {
283
- console.error(`Unknown option: ${args[i]}`);
284
- console.error('Run "mcp-auth --help" for usage information');
285
- process.exit(1);
286
- }
287
- }
288
- // Validate required arguments
289
- if (!outputFile) {
290
- console.error('Error: --output is required');
291
- console.error('');
292
- console.error('Usage: mcp-auth --output <path> [--service-key <path> | --env <path>] [options]');
293
- console.error('Run "mcp-auth --help" for more information');
294
- process.exit(1);
295
- }
296
- // Either service-key or env must be provided
297
- if (!serviceKeyPath && !envFilePath) {
298
- console.error('Error: Either --service-key or --env must be provided');
299
- console.error('');
300
- console.error('Usage: mcp-auth --output <path> [--service-key <path> | --env <path>] [options]');
301
- console.error('Run "mcp-auth --help" for more information');
302
- process.exit(1);
303
- }
304
- return {
305
- serviceKeyPath,
306
- envFilePath,
307
- outputFile,
308
- authType,
309
- browser,
310
- credential,
311
- format,
312
- serviceUrl,
313
- redirectPort,
314
- };
315
- }
316
- function writeEnvFile(outputPath, authType, token, refreshToken, serviceUrl, uaaUrl, uaaClientId, uaaClientSecret) {
317
- const lines = [];
318
- if (authType === 'abap') {
319
- // ABAP format
320
- if (serviceUrl) {
321
- lines.push(`${auth_stores_1.ABAP_CONNECTION_VARS.SERVICE_URL}=${serviceUrl}`);
322
- }
323
- lines.push(`${auth_stores_1.ABAP_CONNECTION_VARS.AUTHORIZATION_TOKEN}=${token}`);
324
- if (refreshToken) {
325
- lines.push(`${auth_stores_1.ABAP_AUTHORIZATION_VARS.REFRESH_TOKEN}=${refreshToken}`);
326
- }
327
- if (uaaUrl) {
328
- lines.push(`${auth_stores_1.ABAP_AUTHORIZATION_VARS.UAA_URL}=${uaaUrl}`);
329
- }
330
- if (uaaClientId) {
331
- lines.push(`${auth_stores_1.ABAP_AUTHORIZATION_VARS.UAA_CLIENT_ID}=${uaaClientId}`);
332
- }
333
- if (uaaClientSecret) {
334
- lines.push(`${auth_stores_1.ABAP_AUTHORIZATION_VARS.UAA_CLIENT_SECRET}=${uaaClientSecret}`);
335
- }
336
- }
337
- else {
338
- // XSUAA format
339
- if (serviceUrl) {
340
- lines.push(`XSUAA_MCP_URL=${serviceUrl}`);
341
- }
342
- lines.push(`${auth_stores_1.XSUAA_CONNECTION_VARS.AUTHORIZATION_TOKEN}=${token}`);
343
- if (refreshToken) {
344
- lines.push(`${auth_stores_1.XSUAA_AUTHORIZATION_VARS.REFRESH_TOKEN}=${refreshToken}`);
345
- }
346
- if (uaaUrl) {
347
- lines.push(`${auth_stores_1.XSUAA_AUTHORIZATION_VARS.UAA_URL}=${uaaUrl}`);
348
- }
349
- if (uaaClientId) {
350
- lines.push(`${auth_stores_1.XSUAA_AUTHORIZATION_VARS.UAA_CLIENT_ID}=${uaaClientId}`);
351
- }
352
- if (uaaClientSecret) {
353
- lines.push(`${auth_stores_1.XSUAA_AUTHORIZATION_VARS.UAA_CLIENT_SECRET}=${uaaClientSecret}`);
354
- }
355
- }
356
- // Ensure output directory exists
357
- const outputDir = path.dirname(outputPath);
358
- if (!fs.existsSync(outputDir)) {
359
- fs.mkdirSync(outputDir, { recursive: true });
360
- }
361
- // Write to file
362
- fs.writeFileSync(outputPath, lines.join('\n') + '\n', 'utf8');
363
- }
364
- function writeJsonFile(outputPath, token, refreshToken, serviceUrl, uaaUrl, uaaClientId, uaaClientSecret) {
365
- const outputData = {
366
- accessToken: token,
367
- };
368
- if (refreshToken) {
369
- outputData.refreshToken = refreshToken;
370
- }
371
- if (serviceUrl) {
372
- outputData.serviceUrl = serviceUrl;
373
- }
374
- if (uaaUrl) {
375
- outputData.uaaUrl = uaaUrl;
376
- }
377
- if (uaaClientId) {
378
- outputData.uaaClientId = uaaClientId;
379
- }
380
- if (uaaClientSecret) {
381
- outputData.uaaClientSecret = uaaClientSecret;
382
- }
383
- // Ensure output directory exists
384
- const outputDir = path.dirname(outputPath);
385
- if (!fs.existsSync(outputDir)) {
386
- fs.mkdirSync(outputDir, { recursive: true });
387
- }
388
- // Write to file
389
- fs.writeFileSync(outputPath, JSON.stringify(outputData, null, 2), 'utf8');
390
- }
391
- function runMcpSso(args) {
392
- const mcpSsoPath = path.resolve(__dirname, 'mcp-sso.js');
393
- const result = (0, child_process_1.spawnSync)(process.execPath, [mcpSsoPath, ...args], {
394
- stdio: 'inherit',
395
- });
396
- if (result.error) {
397
- throw result.error;
398
- }
399
- process.exit(result.status ?? 1);
400
- }
401
- /** The run's private working directory, created on first use and removed on any exit. */
402
- let runWorkDir;
403
- function workDir() {
404
- runWorkDir ??= (0, workDir_1.createWorkDir)('mcp-auth');
405
- return runWorkDir;
406
- }
407
- async function main() {
408
- const rawArgs = process.argv.slice(2);
409
- // `help` and `version` as commands, like every CLI in the family; they answer
410
- // before any subcommand is dispatched.
411
- if (rawArgs[0] === 'help') {
412
- showHelp();
413
- process.exit(0);
414
- }
415
- if (rawArgs[0] === 'version') {
416
- console.log(getVersion());
417
- process.exit(0);
418
- }
419
- const subcommand = rawArgs[0];
420
- const hasSubcommand = subcommand && !subcommand.startsWith('-') && subcommand.length > 0;
421
- if (hasSubcommand) {
422
- const remaining = rawArgs.slice(1);
423
- const ensureNoProtocol = () => {
424
- if (remaining.includes('--protocol')) {
425
- console.error('❌ --protocol is not supported with subcommands.');
426
- process.exit(1);
427
- }
428
- };
429
- switch (subcommand) {
430
- case 'auth-code': {
431
- break;
432
- }
433
- case 'oidc': {
434
- ensureNoProtocol();
435
- runMcpSso(['oidc', ...remaining]);
436
- return;
437
- }
438
- case 'saml2-pure': {
439
- ensureNoProtocol();
440
- if (remaining.includes('--flow')) {
441
- const idx = remaining.indexOf('--flow');
442
- const flow = remaining[idx + 1];
443
- if (flow && flow !== 'pure') {
444
- console.error('❌ saml2-pure requires --flow pure.');
445
- process.exit(1);
446
- }
447
- }
448
- else {
449
- remaining.unshift('pure');
450
- remaining.unshift('--flow');
451
- }
452
- runMcpSso(['saml2', ...remaining]);
453
- return;
454
- }
455
- case 'saml2-bearer': {
456
- ensureNoProtocol();
457
- if (!remaining.includes('--dev')) {
458
- console.error('⚠️ saml2-bearer is in progress. Re-run with --dev to enable.');
459
- process.exit(1);
460
- }
461
- const filtered = remaining.filter((arg) => arg !== '--dev');
462
- if (filtered.includes('--flow')) {
463
- console.error('❌ saml2-bearer does not accept --flow.');
464
- process.exit(1);
465
- }
466
- runMcpSso(['bearer', ...filtered]);
467
- return;
468
- }
469
- default: {
470
- console.error(`Unknown command: ${subcommand}`);
471
- showHelp();
472
- process.exit(1);
473
- }
474
- }
475
- }
476
- const options = parseArgs(hasSubcommand ? rawArgs.slice(1) : rawArgs);
477
- if (!options) {
478
- // Help or version was shown, exit already handled
479
- return;
480
- }
481
- // Resolve paths
482
- const resolvedOutputPath = path.resolve(options.outputFile);
483
- const resolvedEnvPath = options.envFilePath
484
- ? path.resolve(options.envFilePath)
485
- : undefined;
486
- // If service key is provided, use it; optionally read session from env
487
- let serviceKeyStore = null;
488
- let destination;
489
- const envExists = resolvedEnvPath ? fs.existsSync(resolvedEnvPath) : false;
490
- if (resolvedEnvPath) {
491
- destination = path.basename(resolvedEnvPath, path.extname(resolvedEnvPath));
492
- }
493
- // Store raw service key JSON for fallback parsing
494
- let rawServiceKeyJson = null;
495
- if (options.serviceKeyPath) {
496
- const resolvedServiceKeyPath = path.resolve(options.serviceKeyPath);
497
- let serviceKeyDir = path.dirname(resolvedServiceKeyPath);
498
- // Check if service key file exists
499
- if (!fs.existsSync(resolvedServiceKeyPath)) {
500
- console.error(`❌ Service key file not found: ${resolvedServiceKeyPath}`);
501
- process.exit(1);
502
- }
503
- const serviceKeyFileName = path.basename(resolvedServiceKeyPath, '.json');
504
- if (destination && destination !== serviceKeyFileName) {
505
- console.error(`❌ Destination mismatch: env file (${destination}) vs service key (${serviceKeyFileName})`);
506
- process.exit(1);
507
- }
508
- destination = serviceKeyFileName;
509
- // Determine which store to use based on service key content
510
- // ABAP format has nested "uaa" object, XSUAA format has flat structure
511
- let isAbapFormat = options.authType === 'abap'; // default fallback
512
- try {
513
- // Use JsonFileHandler to ensure consistent parsing behavior
514
- const json = await auth_stores_1.JsonFileHandler.load(path.basename(resolvedServiceKeyPath), serviceKeyDir);
515
- let effectiveJson = json;
516
- if (json && json.credentials) {
517
- console.log('🔍 Detected "credentials" wrapper -> unwrapping to temp file');
518
- effectiveJson = json.credentials;
519
- // Create temp file with unwrapped content to make it compatible with standard stores
520
- const tempDir = workDir();
521
- const tempKeyPath = path.join(tempDir, `${destination}.json`);
522
- fs.writeFileSync(tempKeyPath, JSON.stringify(effectiveJson, null, 2));
523
- // Point store to temp directory so it reads the unwrapped file
524
- serviceKeyDir = tempDir;
525
- }
526
- // Store raw JSON for fallback parsing
527
- rawServiceKeyJson = effectiveJson;
528
- if (effectiveJson) {
529
- // If it has uaa property (even if null/string/object), XSUAA parser rejects it.
530
- // So we must use ABAP store which expects uaa object.
531
- if (effectiveJson.uaa) {
532
- isAbapFormat = true;
533
- }
534
- else {
535
- isAbapFormat = false;
536
- }
537
- }
538
- }
539
- catch (e) {
540
- // If parsing fails here, let the store handle the error
541
- }
542
- // Create appropriate stores based on detected format
543
- serviceKeyStore = isAbapFormat
544
- ? new auth_stores_1.AbapServiceKeyStore(serviceKeyDir)
545
- : new auth_stores_1.XsuaaServiceKeyStore(serviceKeyDir);
546
- // Patch serviceKeyStore for XSUAA to ensure serviceUrl is present (AuthBroker requirement)
547
- // even if not in the file. XSUAA doesn't strictly need it, but AuthBroker enforces it.
548
- if (options.authType === 'xsuaa') {
549
- const originalGetConnectionConfig = serviceKeyStore.getConnectionConfig.bind(serviceKeyStore);
550
- serviceKeyStore.getConnectionConfig = async (dest) => {
551
- const config = await originalGetConnectionConfig(dest);
552
- if (config && !config.serviceUrl) {
553
- config.serviceUrl = '<SERVICE_URL>';
554
- }
555
- return config;
556
- };
557
- }
558
- }
559
- if (!destination) {
560
- console.error('❌ Destination could not be determined from inputs');
561
- process.exit(1);
562
- }
563
- console.log(`📁 Output file: ${resolvedOutputPath}`);
564
- if (resolvedEnvPath) {
565
- console.log(`📁 Env file: ${resolvedEnvPath} (${envExists ? 'found' : 'not found'})`);
566
- }
567
- if (options.serviceKeyPath) {
568
- console.log(`📁 Service key: ${path.resolve(options.serviceKeyPath)}`);
569
- }
570
- console.log(`🔐 Auth type: ${options.authType}`);
571
- console.log(`🔑 Flow: ${options.credential ? 'client_credentials' : 'authorization_code'}`);
572
- if (!options.credential) {
573
- console.log(`🌐 Browser: ${options.browser}`);
574
- }
575
- console.log(`📄 Format: ${options.format}`);
576
- if (options.serviceUrl) {
577
- console.log(`🔗 Service URL: ${options.serviceUrl}`);
578
- }
579
- try {
580
- if (!envExists && !serviceKeyStore) {
581
- throw new Error('Env file not found and no service key provided.');
582
- }
583
- // Create temporary session store (work off a temp copy of env file)
584
- const tempSessionDir = workDir();
585
- if (envExists && resolvedEnvPath) {
586
- const tempEnvPath = path.join(tempSessionDir, `${destination}.env`);
587
- fs.copyFileSync(resolvedEnvPath, tempEnvPath);
588
- }
589
- // Resolve serviceUrl from service key if not provided explicitly
590
- let actualServiceUrl = options.serviceUrl;
591
- if (!actualServiceUrl && serviceKeyStore) {
592
- try {
593
- const serviceKeyConn = await serviceKeyStore.getConnectionConfig(destination);
594
- actualServiceUrl = serviceKeyConn?.serviceUrl;
595
- }
596
- catch {
597
- // For XSUAA, serviceUrl is optional and may not exist in service key
598
- // This is expected - continue without serviceUrl
599
- }
600
- }
601
- // For XSUAA, serviceUrl is optional - use placeholder only for AuthBroker internal work
602
- const brokerServiceUrl = actualServiceUrl || '<SERVICE_URL>';
603
- const sessionStore = options.authType === 'xsuaa'
604
- ? new auth_stores_1.XsuaaSessionStore(tempSessionDir, brokerServiceUrl)
605
- : new auth_stores_1.AbapSessionStore(tempSessionDir, undefined, actualServiceUrl);
606
- const sessionAuthConfig = await sessionStore.getAuthorizationConfig(destination);
607
- let serviceKeyAuthConfig = null;
608
- if (serviceKeyStore?.getAuthorizationConfig) {
609
- try {
610
- serviceKeyAuthConfig =
611
- await serviceKeyStore.getAuthorizationConfig(destination);
612
- }
613
- catch (e) {
614
- // Service key parsing might fail - try fallback parsing from raw JSON
615
- console.log(`ℹ️ Store could not parse service key, using fallback parsing`);
616
- }
617
- }
618
- // Fallback: if stores failed, try to construct authConfig from raw service key JSON
619
- let fallbackAuthConfig = null;
620
- if (!sessionAuthConfig && !serviceKeyAuthConfig && rawServiceKeyJson) {
621
- // XSUAA format: clientid, clientsecret, url at top level
622
- // ABAP format: uaa.clientid, uaa.clientsecret, uaa.url
623
- const uaa = rawServiceKeyJson.uaa || rawServiceKeyJson;
624
- if (uaa.clientid && uaa.clientsecret && uaa.url) {
625
- fallbackAuthConfig = {
626
- uaaUrl: uaa.url,
627
- uaaClientId: uaa.clientid,
628
- uaaClientSecret: uaa.clientsecret,
629
- };
630
- console.log(`✅ Constructed auth config from raw service key`);
631
- }
632
- }
633
- const authConfig = sessionAuthConfig || serviceKeyAuthConfig || fallbackAuthConfig;
634
- if (!authConfig) {
635
- throw new Error(`Authorization config not found for ${destination}. Service key must contain clientid, clientsecret, and url fields.`);
636
- }
637
- // Default: authorization_code flow with browser
638
- // --credential: client_credentials flow (no browser needed)
639
- // The callback port is the provider's own choice (DEFAULT_CALLBACK_PORT,
640
- // currently 61001) unless the user overrides it with --redirect-port.
641
- // Only resolved to a concrete number here for the URL preview below —
642
- // the strategy itself re-derives the same default independently when
643
- // `port` is left undefined.
644
- const redirectPort = options.redirectPort ?? auth_providers_1.DEFAULT_CALLBACK_PORT;
645
- // Log authorization URL for debugging
646
- if (!options.credential) {
647
- const redirectUri = `http://localhost:${redirectPort}/callback`;
648
- const authorizationUrl = `${authConfig.uaaUrl}/oauth/authorize?client_id=${encodeURIComponent(authConfig.uaaClientId)}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code`;
649
- console.log(`🔗 Authorization URL: ${authorizationUrl}`);
650
- console.log(`📍 Redirect URI: ${redirectUri}`);
651
- }
652
- const tokenProvider = options.credential
653
- ? new auth_providers_1.ClientCredentialsProvider({
654
- uaaUrl: authConfig.uaaUrl,
655
- clientId: authConfig.uaaClientId,
656
- clientSecret: authConfig.uaaClientSecret,
657
- })
658
- : new auth_providers_1.AuthorizationCodeProvider({
659
- uaaUrl: authConfig.uaaUrl,
660
- clientId: authConfig.uaaClientId,
661
- clientSecret: authConfig.uaaClientSecret,
662
- refreshToken: authConfig.refreshToken,
663
- // Passing `options.redirectPort` (not the resolved `redirectPort`
664
- // above) so an omitted --redirect-port lets the strategy bind its
665
- // own default rather than this CLI pinning a number it doesn't own.
666
- authorization: (0, auth_providers_1.browserCallbackStrategy)({
667
- browser: options.browser,
668
- port: options.redirectPort,
669
- timeoutMs: INTERACTIVE_LOGIN_TIMEOUT_MS,
670
- }),
671
- });
672
- // The output file is a self-contained session: whoever reads it refreshes
673
- // the token with the UAA credentials it carries, and has no service key.
674
- // So this CLI writes the credentials into its temporary session itself —
675
- // its own decision about its own output. The broker no longer copies the
676
- // client secret into a session store, and without these the refresh token
677
- // the login obtains would not be readable back below.
678
- if (!sessionAuthConfig) {
679
- await sessionStore.setAuthorizationConfig(destination, authConfig);
680
- }
681
- const broker = new AuthBroker({
682
- sessionStore,
683
- serviceKeyStore: serviceKeyStore || undefined,
684
- provider: tokenProvider,
685
- });
686
- console.log(`🔐 Getting token for destination "${destination}"...`);
687
- const token = await broker.getToken(destination);
688
- console.log(`✅ Token obtained successfully`);
689
- const connConfig = await sessionStore.getConnectionConfig(destination);
690
- // What the login left in the session, not what was known before it:
691
- // `authConfig` came from the service key, which holds no refresh token,
692
- // and the refresh token the login obtained went into the session store.
693
- // Writing `authConfig` dropped it with the temporary session directory.
694
- const savedAuthConfig = (await sessionStore.getAuthorizationConfig(destination)) ?? authConfig;
695
- if (!token) {
696
- throw new Error(`Token provider did not return authorization token for destination "${destination}"`);
697
- }
698
- const outputServiceUrl = options.serviceUrl || connConfig?.serviceUrl || actualServiceUrl;
699
- // Filter out placeholder service URL
700
- const finalServiceUrl = outputServiceUrl === '<SERVICE_URL>' ? undefined : outputServiceUrl;
701
- // Write output file based on format
702
- if (options.format === 'env') {
703
- writeEnvFile(resolvedOutputPath, options.authType, token, savedAuthConfig?.refreshToken, finalServiceUrl, savedAuthConfig?.uaaUrl, savedAuthConfig?.uaaClientId, savedAuthConfig?.uaaClientSecret);
704
- console.log(`✅ .env file created: ${resolvedOutputPath}`);
705
- // Show what was written
706
- console.log(`📋 .env file contains:`);
707
- if (options.authType === 'abap') {
708
- if (finalServiceUrl) {
709
- console.log(` - ${auth_stores_1.ABAP_CONNECTION_VARS.SERVICE_URL}=${finalServiceUrl}`);
710
- }
711
- console.log(` - ${auth_stores_1.ABAP_CONNECTION_VARS.AUTHORIZATION_TOKEN}=<redacted, ${token.length} chars>`);
712
- if (savedAuthConfig?.refreshToken) {
713
- console.log(` - ${auth_stores_1.ABAP_AUTHORIZATION_VARS.REFRESH_TOKEN}=<redacted, ${savedAuthConfig.refreshToken.length} chars>`);
714
- }
715
- }
716
- else {
717
- if (finalServiceUrl) {
718
- console.log(` - XSUAA_MCP_URL=${finalServiceUrl}`);
719
- }
720
- console.log(` - ${auth_stores_1.XSUAA_CONNECTION_VARS.AUTHORIZATION_TOKEN}=<redacted, ${token.length} chars>`);
721
- if (savedAuthConfig?.refreshToken) {
722
- console.log(` - ${auth_stores_1.XSUAA_AUTHORIZATION_VARS.REFRESH_TOKEN}=<redacted, ${savedAuthConfig.refreshToken.length} chars>`);
723
- }
724
- }
725
- }
726
- else {
727
- writeJsonFile(resolvedOutputPath, token, savedAuthConfig?.refreshToken, finalServiceUrl, savedAuthConfig?.uaaUrl, savedAuthConfig?.uaaClientId, savedAuthConfig?.uaaClientSecret);
728
- console.log(`✅ JSON file created: ${resolvedOutputPath}`);
729
- console.log(`📋 Output contains:`);
730
- console.log(` - accessToken: <redacted, ${token.length} chars>`);
731
- if (savedAuthConfig?.refreshToken) {
732
- console.log(` - refreshToken: <redacted, ${savedAuthConfig.refreshToken.length} chars>`);
733
- }
734
- if (finalServiceUrl) {
735
- console.log(` - serviceUrl: ${finalServiceUrl}`);
736
- }
737
- }
738
- // Exit explicitly to close any open handles (e.g., OAuth callback server)
739
- process.exit(0);
740
- }
741
- catch (error) {
742
- console.error(`❌ Error: ${error.message}`);
743
- if (error.stack) {
744
- console.error(error.stack);
745
- }
746
- process.exit(1);
747
- }
748
- }
749
- main().catch((error) => {
750
- console.error('Fatal error:', error);
751
- process.exit(1);
752
- });