@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.
- package/CHANGELOG.md +52 -1
- package/README.md +34 -312
- package/package.json +15 -42
- package/CONTRIBUTORS.md +0 -33
- package/dist/__tests__/helpers/configHelpers.d.ts +0 -59
- package/dist/__tests__/helpers/configHelpers.d.ts.map +0 -1
- package/dist/__tests__/helpers/configHelpers.js +0 -206
- package/dist/__tests__/helpers/netHelpers.d.ts +0 -3
- package/dist/__tests__/helpers/netHelpers.d.ts.map +0 -1
- package/dist/__tests__/helpers/netHelpers.js +0 -63
- package/dist/__tests__/helpers/testLogger.d.ts +0 -7
- package/dist/__tests__/helpers/testLogger.d.ts.map +0 -1
- package/dist/__tests__/helpers/testLogger.js +0 -54
- package/dist/bin/mcp-auth.js +0 -752
- package/dist/bin/mcp-sso.js +0 -825
- package/dist/bin/mcpSsoConfig.js +0 -566
- package/dist/bin/samlMetadata.js +0 -175
- package/dist/bin/workDir.js +0 -79
package/dist/bin/mcp-auth.js
DELETED
|
@@ -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
|
-
});
|