@babamba2/mcp-abap-connection 1.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +522 -0
  3. package/bin/sap-abap-auth.js +665 -0
  4. package/dist/config/sapConfig.d.ts +5 -0
  5. package/dist/config/sapConfig.d.ts.map +1 -0
  6. package/dist/config/sapConfig.js +26 -0
  7. package/dist/connection/AbapConnection.d.ts +4 -0
  8. package/dist/connection/AbapConnection.d.ts.map +1 -0
  9. package/dist/connection/AbapConnection.js +2 -0
  10. package/dist/connection/AbstractAbapConnection.d.ts +89 -0
  11. package/dist/connection/AbstractAbapConnection.d.ts.map +1 -0
  12. package/dist/connection/AbstractAbapConnection.js +612 -0
  13. package/dist/connection/BaseAbapConnection.d.ts +19 -0
  14. package/dist/connection/BaseAbapConnection.d.ts.map +1 -0
  15. package/dist/connection/BaseAbapConnection.js +65 -0
  16. package/dist/connection/GenericWebSocketTransport.d.ts +46 -0
  17. package/dist/connection/GenericWebSocketTransport.d.ts.map +1 -0
  18. package/dist/connection/GenericWebSocketTransport.js +135 -0
  19. package/dist/connection/JwtAbapConnection.d.ts +37 -0
  20. package/dist/connection/JwtAbapConnection.d.ts.map +1 -0
  21. package/dist/connection/JwtAbapConnection.js +181 -0
  22. package/dist/connection/RfcAbapConnection.d.ts +49 -0
  23. package/dist/connection/RfcAbapConnection.d.ts.map +1 -0
  24. package/dist/connection/RfcAbapConnection.js +322 -0
  25. package/dist/connection/SamlAbapConnection.d.ts +21 -0
  26. package/dist/connection/SamlAbapConnection.d.ts.map +1 -0
  27. package/dist/connection/SamlAbapConnection.js +65 -0
  28. package/dist/connection/connectionFactory.d.ts +8 -0
  29. package/dist/connection/connectionFactory.d.ts.map +1 -0
  30. package/dist/connection/connectionFactory.js +23 -0
  31. package/dist/connection/csrfConfig.d.ts +46 -0
  32. package/dist/connection/csrfConfig.d.ts.map +1 -0
  33. package/dist/connection/csrfConfig.js +48 -0
  34. package/dist/index.d.ts +14 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +32 -0
  37. package/dist/logger.d.ts +3 -0
  38. package/dist/logger.d.ts.map +1 -0
  39. package/dist/logger.js +2 -0
  40. package/dist/utils/timeouts.d.ts +5 -0
  41. package/dist/utils/timeouts.d.ts.map +1 -0
  42. package/dist/utils/timeouts.js +21 -0
  43. package/dist/utils/tokenRefresh.d.ts +15 -0
  44. package/dist/utils/tokenRefresh.d.ts.map +1 -0
  45. package/dist/utils/tokenRefresh.js +55 -0
  46. package/package.json +70 -0
@@ -0,0 +1,665 @@
1
+ #!/usr/bin/env node
2
+ const fs = require("fs");
3
+ const path = require("path");
4
+ const axios = require("axios");
5
+ const { program } = require("commander");
6
+ const express = require("express");
7
+ const open = require("open").default;
8
+ const http = require("http");
9
+
10
+ /**
11
+ * Get .env file path
12
+ * @param {string} customPath Optional custom path
13
+ * @returns {string} Path to .env file
14
+ */
15
+ function getEnvFilePath(customPath) {
16
+ if (customPath) {
17
+ return path.resolve(process.cwd(), customPath);
18
+ }
19
+ return path.resolve(process.cwd(), ".env");
20
+ }
21
+
22
+ // Browser selection via --browser option (chrome, edge, firefox, system, none)
23
+ const BROWSER_MAP = {
24
+ chrome: "chrome",
25
+ edge: "msedge",
26
+ firefox: "firefox",
27
+ system: undefined, // system default
28
+ none: null, // no browser, manual URL copy
29
+ };
30
+
31
+ /**
32
+ * Reads a JSON service key file
33
+ * @param {string} filePath Path to the service key file
34
+ * @returns {object} Service key data object
35
+ */
36
+ function readServiceKey(filePath) {
37
+ try {
38
+ const fullPath = path.resolve(process.cwd(), filePath);
39
+ if (!fs.existsSync(fullPath)) {
40
+ console.error(`File not found: ${fullPath}`);
41
+ process.exit(1);
42
+ }
43
+
44
+ const fileContent = fs.readFileSync(fullPath, "utf8");
45
+ return JSON.parse(fileContent);
46
+ } catch (error) {
47
+ console.error(`Error reading service key: ${error.message}`);
48
+ process.exit(1);
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Reads existing .env file and parses it
54
+ * @param {string} envFilePath Path to .env file
55
+ * @returns {Object} Parsed .env values
56
+ */
57
+ function readEnvFile(envFilePath) {
58
+ try {
59
+ if (!fs.existsSync(envFilePath)) {
60
+ return {};
61
+ }
62
+ const content = fs.readFileSync(envFilePath, "utf8");
63
+ const env = {};
64
+ content.split("\n").forEach((line) => {
65
+ line = line.trim();
66
+ if (line && !line.startsWith("#")) {
67
+ const [key, ...valueParts] = line.split("=");
68
+ if (key && valueParts.length > 0) {
69
+ env[key.trim()] = valueParts.join("=").trim();
70
+ }
71
+ }
72
+ });
73
+ return env;
74
+ } catch (error) {
75
+ console.error(`Error reading .env file: ${error.message}`);
76
+ return {};
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Attempts to refresh JWT token using refresh token
82
+ * @param {string} refreshToken Refresh token from .env
83
+ * @param {string} uaaUrl UAA URL from .env
84
+ * @param {string} clientId UAA client ID from .env
85
+ * @param {string} clientSecret UAA client secret from .env
86
+ * @returns {Promise<{accessToken: string, refreshToken: string}|null>} New tokens or null if failed
87
+ */
88
+ async function tryRefreshToken(refreshToken, uaaUrl, clientId, clientSecret) {
89
+ try {
90
+ console.log("🔄 Attempting to refresh existing JWT token...");
91
+ const tokenUrl = `${uaaUrl}/oauth/token`;
92
+
93
+ const params = new URLSearchParams();
94
+ params.append("grant_type", "refresh_token");
95
+ params.append("refresh_token", refreshToken);
96
+
97
+ const authString = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
98
+
99
+ const response = await axios({
100
+ method: "post",
101
+ url: tokenUrl,
102
+ headers: {
103
+ Authorization: `Basic ${authString}`,
104
+ "Content-Type": "application/x-www-form-urlencoded",
105
+ },
106
+ data: params.toString(),
107
+ timeout: 10000, // 10 second timeout
108
+ });
109
+
110
+ if (response.data && response.data.access_token) {
111
+ console.log("✅ Token refreshed successfully!");
112
+ return {
113
+ accessToken: response.data.access_token,
114
+ refreshToken: response.data.refresh_token || refreshToken,
115
+ };
116
+ }
117
+ return null;
118
+ } catch (error) {
119
+ console.log(`⚠️ Token refresh failed: ${error.message}`);
120
+ console.log("📝 Falling back to browser authentication...");
121
+ return null;
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Decodes JWT token and extracts expiration time
127
+ * @param {string} token JWT token string
128
+ * @returns {Object|null} Object with expiration date and timestamp, or null if decoding fails
129
+ */
130
+ function getTokenExpiry(token) {
131
+ try {
132
+ if (!token) return null;
133
+
134
+ // JWT format: header.payload.signature
135
+ const parts = token.split('.');
136
+ if (parts.length !== 3) return null;
137
+
138
+ // Decode payload (base64url)
139
+ const payload = parts[1];
140
+ // Add padding if needed
141
+ const paddedPayload = payload + '='.repeat((4 - payload.length % 4) % 4);
142
+ const decodedPayload = Buffer.from(paddedPayload, 'base64').toString('utf8');
143
+ const payloadObj = JSON.parse(decodedPayload);
144
+
145
+ if (!payloadObj.exp) return null;
146
+
147
+ // exp is Unix timestamp in seconds
148
+ const expiryTimestamp = payloadObj.exp * 1000; // Convert to milliseconds
149
+ const expiryDate = new Date(expiryTimestamp);
150
+
151
+ return {
152
+ timestamp: expiryTimestamp,
153
+ date: expiryDate,
154
+ dateString: expiryDate.toISOString(),
155
+ readableDate: expiryDate.toLocaleString('en-US', {
156
+ timeZone: 'UTC',
157
+ dateStyle: 'full',
158
+ timeStyle: 'long'
159
+ })
160
+ };
161
+ } catch (error) {
162
+ // Silently fail - token might not be a valid JWT or might be in different format
163
+ return null;
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Updates the .env file with new values
169
+ * @param {Object} updates Object with updated values
170
+ * @param {string} envFilePath Path to .env file
171
+ */
172
+ function updateEnvFile(updates, envFilePath) {
173
+ try {
174
+ // Always remove the old .env file if it exists
175
+ if (fs.existsSync(envFilePath)) {
176
+ fs.unlinkSync(envFilePath);
177
+ }
178
+ let lines = [];
179
+
180
+ // Get token expiry information
181
+ const jwtTokenExpiry = getTokenExpiry(updates.SAP_JWT_TOKEN);
182
+ const refreshTokenExpiry = getTokenExpiry(updates.SAP_REFRESH_TOKEN);
183
+
184
+ // Add token expiry comments at the beginning if JWT auth
185
+ if (updates.SAP_AUTH_TYPE === "jwt") {
186
+ lines.push("# Token Expiry Information (auto-generated)");
187
+ if (jwtTokenExpiry) {
188
+ lines.push(`# JWT Token expires: ${jwtTokenExpiry.readableDate} (UTC)`);
189
+ lines.push(`# JWT Token expires at: ${jwtTokenExpiry.dateString}`);
190
+ } else {
191
+ lines.push("# JWT Token expiry: Unable to determine (token may not be a standard JWT)");
192
+ }
193
+ if (refreshTokenExpiry) {
194
+ lines.push(`# Refresh Token expires: ${refreshTokenExpiry.readableDate} (UTC)`);
195
+ lines.push(`# Refresh Token expires at: ${refreshTokenExpiry.dateString}`);
196
+ } else if (updates.SAP_REFRESH_TOKEN) {
197
+ lines.push("# Refresh Token expiry: Unable to determine (token may not be a standard JWT)");
198
+ }
199
+ lines.push("");
200
+ }
201
+
202
+ if (updates.SAP_AUTH_TYPE === "jwt") {
203
+ // jwt: write only relevant params
204
+ const jwtAllowed = [
205
+ "SAP_URL",
206
+ "SAP_CLIENT",
207
+ "SAP_LANGUAGE",
208
+ "TLS_REJECT_UNAUTHORIZED",
209
+ "SAP_AUTH_TYPE",
210
+ "SAP_JWT_TOKEN",
211
+ "SAP_REFRESH_TOKEN",
212
+ "SAP_UAA_URL",
213
+ "SAP_UAA_CLIENT_ID",
214
+ "SAP_UAA_CLIENT_SECRET",
215
+ ];
216
+ jwtAllowed.forEach((key) => {
217
+ if (updates[key]) lines.push(`${key}=${updates[key]}`);
218
+ });
219
+ lines.push("");
220
+ lines.push("# For JWT authentication");
221
+ lines.push("# SAP_USERNAME=your_username");
222
+ lines.push("# SAP_PASSWORD=your_password");
223
+ } else {
224
+ // basic: write only relevant params
225
+ const basicAllowed = [
226
+ "SAP_URL",
227
+ "SAP_CLIENT",
228
+ "SAP_LANGUAGE",
229
+ "TLS_REJECT_UNAUTHORIZED",
230
+ "SAP_AUTH_TYPE",
231
+ "SAP_USERNAME",
232
+ "SAP_PASSWORD",
233
+ ];
234
+ basicAllowed.forEach((key) => {
235
+ if (updates[key]) lines.push(`${key}=${updates[key]}`);
236
+ });
237
+ lines.push("");
238
+ lines.push("# For JWT authentication (not used for basic)");
239
+ lines.push("# SAP_JWT_TOKEN=your_jwt_token_here");
240
+ }
241
+ fs.writeFileSync(envFilePath, lines.join("\n") + "\n", "utf8");
242
+ console.log(".env file created successfully.");
243
+ } catch (error) {
244
+ console.error(`Error updating .env file: ${error.message}`);
245
+ process.exit(1);
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Builds the JWT (OAuth2) authentication URL
251
+ * @param {Object} serviceKey SAP BTP service key object
252
+ * @param {number} port Redirect URL port
253
+ * @returns {string} Authentication URL
254
+ */
255
+ function getJwtAuthorizationUrl(serviceKey, port = 3001) {
256
+ // Use serviceKey.uaa.url (OAuth endpoint) for OAuth2 authorization URL (correct for BTP ABAP)
257
+ const oauthUrl = serviceKey.uaa?.url;
258
+ const clientid = serviceKey.uaa?.clientid;
259
+ const redirectUri = `http://localhost:${port}/callback`;
260
+ return `${oauthUrl}/oauth/authorize?client_id=${encodeURIComponent(
261
+ clientid
262
+ )}&response_type=code&redirect_uri=${encodeURIComponent(redirectUri)}`;
263
+ }
264
+
265
+ /**
266
+ * Starts a local server to intercept the authentication response
267
+ * @param {Object} serviceKey SAP BTP service key object
268
+ * @param {string} browser Browser to open
269
+ * @param {string} flow Flow type: jwt (OAuth2)
270
+ * @returns {Promise<{accessToken: string, refreshToken?: string}>} Promise that resolves to tokens
271
+ */
272
+ async function startAuthServer(serviceKey, browser = undefined, flow = "jwt") {
273
+ return new Promise((resolve, reject) => {
274
+ const app = express();
275
+ const server = http.createServer(app);
276
+ const PORT = 3001;
277
+ let serverInstance = null;
278
+
279
+ // Choose the authorization URL
280
+ const authorizationUrl = getJwtAuthorizationUrl(serviceKey, PORT);
281
+
282
+ // JWT OAuth2 flow (get code, exchange for token)
283
+ app.get("/callback", async (req, res) => {
284
+ try {
285
+ const { code } = req.query;
286
+ if (!code) {
287
+ res.status(400).send("Error: Authorization code missing");
288
+ return reject(new Error("Authorization code missing"));
289
+ }
290
+ console.log("Authorization code received");
291
+ res.send(`<!DOCTYPE html>
292
+ <html lang="en">
293
+ <head>
294
+ <meta charset="UTF-8">
295
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
296
+ <title>SAP BTP Authentication</title>
297
+ <style>
298
+ body {
299
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
300
+ text-align: center;
301
+ margin: 0;
302
+ padding: 50px 20px;
303
+ background: linear-gradient(135deg, #0070f3 0%, #00d4ff 100%);
304
+ color: white;
305
+ min-height: 100vh;
306
+ display: flex;
307
+ flex-direction: column;
308
+ justify-content: center;
309
+ align-items: center;
310
+ }
311
+ .container {
312
+ background: rgba(255, 255, 255, 0.1);
313
+ border-radius: 20px;
314
+ padding: 40px;
315
+ backdrop-filter: blur(10px);
316
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
317
+ max-width: 500px;
318
+ width: 100%;
319
+ }
320
+ .success-icon {
321
+ font-size: 4rem;
322
+ margin-bottom: 20px;
323
+ color: #4ade80;
324
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
325
+ }
326
+ h1 {
327
+ margin: 0 0 20px 0;
328
+ font-size: 2rem;
329
+ font-weight: 300;
330
+ }
331
+ p {
332
+ margin: 0;
333
+ font-size: 1.1rem;
334
+ opacity: 0.9;
335
+ line-height: 1.5;
336
+ }
337
+ .sap-logo {
338
+ margin-top: 30px;
339
+ font-weight: bold;
340
+ opacity: 0.7;
341
+ font-size: 0.9rem;
342
+ }
343
+ </style>
344
+ </head>
345
+ <body>
346
+ <div class="container">
347
+ <div class="success-icon">✓</div>
348
+ <h1>Authentication Successful!</h1>
349
+ <p>You have successfully authenticated with SAP BTP.</p>
350
+ <p>You can now close this browser window.</p>
351
+ <div class="sap-logo">SAP Business Technology Platform</div>
352
+ </div>
353
+ </body>
354
+ </html>`);
355
+ try {
356
+ const tokens = await exchangeCodeForToken(serviceKey, code);
357
+ server.close(() => {
358
+ console.log("Authentication server stopped");
359
+ });
360
+ resolve(tokens);
361
+ } catch (error) {
362
+ reject(error);
363
+ }
364
+ } catch (error) {
365
+ console.error("Error handling callback:", error);
366
+ res.status(500).send("Error processing authentication");
367
+ reject(error);
368
+ }
369
+ });
370
+
371
+ serverInstance = server.listen(PORT, () => {
372
+ console.log(`Authentication server started on port ${PORT}`);
373
+
374
+ const browserApp = BROWSER_MAP[browser];
375
+ if (!browser || browser === "none" || browserApp === null) {
376
+ console.log(
377
+ "\nBrowser not specified. Please manually open the following URL:"
378
+ );
379
+ console.log("");
380
+ console.log(`🔗 ${authorizationUrl}`);
381
+ console.log("");
382
+ console.log(
383
+ "Copy and paste this URL into your browser to authenticate.\n"
384
+ );
385
+ } else {
386
+ console.log("Opening browser for authentication...");
387
+ if (browserApp) {
388
+ open(authorizationUrl, { app: { name: browserApp } });
389
+ } else {
390
+ open(authorizationUrl);
391
+ }
392
+ }
393
+ });
394
+
395
+ setTimeout(() => {
396
+ if (serverInstance) {
397
+ serverInstance.close();
398
+ reject(new Error("Authentication timeout. Process aborted."));
399
+ }
400
+ }, 5 * 60 * 1000);
401
+ });
402
+ }
403
+
404
+ /**
405
+ * Exchanges the authorization code for tokens
406
+ * @param {Object} serviceKey SAP BTP service key object
407
+ * @param {string} code Authorization code
408
+ * @returns {Promise<{accessToken: string, refreshToken?: string}>} Promise that resolves to tokens
409
+ */
410
+ async function exchangeCodeForToken(serviceKey, code) {
411
+ try {
412
+ const { url, clientid, clientsecret } = serviceKey.uaa;
413
+ const tokenUrl = `${url}/oauth/token`;
414
+ const redirectUri = "http://localhost:3001/callback";
415
+
416
+ const params = new URLSearchParams();
417
+ params.append("grant_type", "authorization_code");
418
+ params.append("code", code);
419
+ params.append("redirect_uri", redirectUri);
420
+
421
+ const authString = Buffer.from(`${clientid}:${clientsecret}`).toString(
422
+ "base64"
423
+ );
424
+
425
+ const response = await axios({
426
+ method: "post",
427
+ url: tokenUrl,
428
+ headers: {
429
+ Authorization: `Basic ${authString}`,
430
+ "Content-Type": "application/x-www-form-urlencoded",
431
+ },
432
+ data: params.toString(),
433
+ });
434
+
435
+ if (response.data && response.data.access_token) {
436
+ console.log("OAuth token received successfully.");
437
+ return {
438
+ accessToken: response.data.access_token,
439
+ refreshToken: response.data.refresh_token
440
+ };
441
+ } else {
442
+ throw new Error("Response does not contain access_token");
443
+ }
444
+ } catch (error) {
445
+ if (error.response) {
446
+ console.error(
447
+ `API error (${error.response.status}): ${JSON.stringify(
448
+ error.response.data
449
+ )}`
450
+ );
451
+ } else {
452
+ console.error(`Error obtaining OAuth token: ${error.message}`);
453
+ }
454
+ throw error;
455
+ }
456
+ }
457
+
458
+ /**
459
+ * Refreshes the access token using refresh token
460
+ * @param {Object} serviceKey SAP BTP service key object
461
+ * @param {string} refreshToken Refresh token
462
+ * @returns {Promise<{accessToken: string, refreshToken?: string}>} Promise that resolves to new tokens
463
+ */
464
+ async function refreshJwtToken(serviceKey, refreshToken) {
465
+ try {
466
+ const { url, clientid, clientsecret } = serviceKey.uaa;
467
+ const tokenUrl = `${url}/oauth/token`;
468
+
469
+ const params = new URLSearchParams();
470
+ params.append("grant_type", "refresh_token");
471
+ params.append("refresh_token", refreshToken);
472
+
473
+ const authString = Buffer.from(`${clientid}:${clientsecret}`).toString(
474
+ "base64"
475
+ );
476
+
477
+ const response = await axios({
478
+ method: "post",
479
+ url: tokenUrl,
480
+ headers: {
481
+ Authorization: `Basic ${authString}`,
482
+ "Content-Type": "application/x-www-form-urlencoded",
483
+ },
484
+ data: params.toString(),
485
+ });
486
+
487
+ if (response.data && response.data.access_token) {
488
+ console.log("Access token refreshed successfully.");
489
+ return {
490
+ accessToken: response.data.access_token,
491
+ refreshToken: response.data.refresh_token || refreshToken // Use new refresh token if provided, otherwise keep old one
492
+ };
493
+ } else {
494
+ throw new Error("Response does not contain access_token");
495
+ }
496
+ } catch (error) {
497
+ if (error.response) {
498
+ console.error(
499
+ `API error (${error.response.status}): ${JSON.stringify(
500
+ error.response.data
501
+ )}`
502
+ );
503
+ } else {
504
+ console.error(`Error refreshing OAuth token: ${error.message}`);
505
+ }
506
+ throw error;
507
+ }
508
+ }
509
+
510
+ /**
511
+ * Main program function
512
+ */
513
+ async function main() {
514
+ program
515
+ .name("sap-abap-auth")
516
+ .description(
517
+ "CLI utility for authentication in SAP BTP ABAP Environment (Steampunk) via browser. Creates .env file with connection configuration."
518
+ )
519
+ .version("0.1.0")
520
+ .helpOption("-h, --help", "Show help for all commands and options");
521
+
522
+ program
523
+ .command("auth")
524
+ .description(
525
+ "Authenticate in SAP BTP ABAP Environment (Steampunk) via browser and update .env file (JWT)"
526
+ )
527
+ .requiredOption(
528
+ "-k, --key <path>",
529
+ "Path to the service key file in JSON format"
530
+ )
531
+ .option(
532
+ "-b, --browser <browser>",
533
+ "Browser to open (chrome, edge, firefox, system, none). Use 'none' or omit to display URL for manual copy."
534
+ )
535
+ .option(
536
+ "-o, --output <path>",
537
+ "Path to output .env file (default: .env in current directory)"
538
+ )
539
+ .option(
540
+ "-f, --force",
541
+ "Force browser authentication even if valid tokens exist in .env"
542
+ )
543
+ .helpOption("-h, --help", "Show help for the auth command")
544
+ .action(async (options) => {
545
+ try {
546
+ if (!options.key) {
547
+ console.error(
548
+ "Service key file (--key) is required for authentication. Please provide a valid service key JSON file."
549
+ );
550
+ process.exit(1);
551
+ }
552
+ console.log("Starting authentication process...");
553
+ const serviceKey = readServiceKey(options.key);
554
+ console.log("Service key read successfully.");
555
+
556
+ // Validate required fields in service key
557
+ const abapUrl =
558
+ serviceKey.url || serviceKey.abap?.url || serviceKey.sap_url;
559
+ if (!abapUrl) {
560
+ console.error(
561
+ "SAP_URL is missing in the service key. Please check your service key JSON file."
562
+ );
563
+ process.exit(1);
564
+ }
565
+
566
+ let tokens = null;
567
+
568
+ // Try to refresh existing token if not forced
569
+ if (!options.force) {
570
+ const envFilePath = getEnvFilePath(options.output);
571
+ const existingEnv = readEnvFile(envFilePath);
572
+
573
+ // Check if we have all necessary data for token refresh
574
+ if (
575
+ existingEnv.SAP_REFRESH_TOKEN &&
576
+ existingEnv.SAP_UAA_URL &&
577
+ existingEnv.SAP_UAA_CLIENT_ID &&
578
+ existingEnv.SAP_UAA_CLIENT_SECRET
579
+ ) {
580
+ tokens = await tryRefreshToken(
581
+ existingEnv.SAP_REFRESH_TOKEN,
582
+ existingEnv.SAP_UAA_URL,
583
+ existingEnv.SAP_UAA_CLIENT_ID,
584
+ existingEnv.SAP_UAA_CLIENT_SECRET
585
+ );
586
+ } else if (existingEnv.SAP_REFRESH_TOKEN) {
587
+ console.log("⚠️ Refresh token found in .env but missing UAA credentials");
588
+ console.log("📝 Falling back to browser authentication...");
589
+ }
590
+ } else {
591
+ console.log("🔒 Force mode enabled - skipping token refresh");
592
+ }
593
+
594
+ // Fallback to browser authentication if refresh failed or was skipped
595
+ if (!tokens) {
596
+ console.log("🌐 Starting browser authentication...");
597
+ tokens = await startAuthServer(serviceKey, options.browser, "jwt");
598
+ if (!tokens || !tokens.accessToken) {
599
+ console.error("JWT token was not obtained. Authentication failed.");
600
+ process.exit(1);
601
+ }
602
+ }
603
+
604
+ // Collect all relevant parameters from service key
605
+ const envUpdates = {
606
+ SAP_URL: abapUrl,
607
+ TLS_REJECT_UNAUTHORIZED: "0",
608
+ SAP_AUTH_TYPE: "jwt",
609
+ SAP_JWT_TOKEN: tokens.accessToken,
610
+ };
611
+
612
+ // Add refresh token if available
613
+ if (tokens.refreshToken) {
614
+ envUpdates.SAP_REFRESH_TOKEN = tokens.refreshToken;
615
+ }
616
+
617
+ // Add UAA credentials for token refresh
618
+ if (serviceKey.uaa?.url) {
619
+ envUpdates.SAP_UAA_URL = serviceKey.uaa.url;
620
+ }
621
+ if (serviceKey.uaa?.clientid) {
622
+ envUpdates.SAP_UAA_CLIENT_ID = serviceKey.uaa.clientid;
623
+ }
624
+ if (serviceKey.uaa?.clientsecret) {
625
+ envUpdates.SAP_UAA_CLIENT_SECRET = serviceKey.uaa.clientsecret;
626
+ }
627
+ // Optional: client
628
+ const abapClient =
629
+ serviceKey.client || serviceKey.abap?.client || serviceKey.sap_client;
630
+ if (abapClient) {
631
+ envUpdates.SAP_CLIENT = abapClient;
632
+ }
633
+ // Optional: language
634
+ if (serviceKey.language) {
635
+ envUpdates.SAP_LANGUAGE = serviceKey.language;
636
+ } else if (serviceKey.abap && serviceKey.abap.language) {
637
+ envUpdates.SAP_LANGUAGE = serviceKey.abap.language;
638
+ }
639
+
640
+ // Use custom output path if provided
641
+ const envFilePath = getEnvFilePath(options.output);
642
+ updateEnvFile(envUpdates, envFilePath);
643
+ console.log("Authentication completed successfully!");
644
+ process.exit(0);
645
+ } catch (error) {
646
+ console.error(`Error during authentication: ${error.message}`);
647
+ process.exit(1);
648
+ }
649
+ });
650
+
651
+ // Parse and handle command-line arguments
652
+ program.parse(process.argv);
653
+
654
+ // If no arguments were provided, show help
655
+ if (process.argv.length === 2) {
656
+ program.help();
657
+ }
658
+ }
659
+
660
+ // Execute the main function
661
+ main().catch((error) => {
662
+ console.error(`Unexpected error: ${error.message}`);
663
+ process.exit(1);
664
+ });
665
+
@@ -0,0 +1,5 @@
1
+ import type { ISapConfig, SapAuthType, SapConnectionType } from '@babamba2/mcp-abap-adt-interfaces';
2
+ export type { SapAuthType, SapConnectionType };
3
+ export type SapConfig = ISapConfig;
4
+ export declare function sapConfigSignature(config: ISapConfig): string;
5
+ //# sourceMappingURL=sapConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sapConfig.d.ts","sourceRoot":"","sources":["../../src/config/sapConfig.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,UAAU,EACV,WAAW,EACX,iBAAiB,EAClB,MAAM,mCAAmC,CAAC;AAG3C,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,CAAC;AAC/C,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC;AAEnC,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAuB7D"}
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sapConfigSignature = sapConfigSignature;
4
+ function sapConfigSignature(config) {
5
+ // Include token preview (first 10 and last 10 chars) to detect token changes
6
+ // This allows connection recreation when token is updated via HTTP headers
7
+ const jwtTokenPreview = config.jwtToken
8
+ ? `${config.jwtToken.substring(0, 10)}...${config.jwtToken.substring(Math.max(0, config.jwtToken.length - 10))}`
9
+ : null;
10
+ const refreshTokenPreview = config.refreshToken
11
+ ? `${config.refreshToken.substring(0, 10)}...${config.refreshToken.substring(Math.max(0, config.refreshToken.length - 10))}`
12
+ : null;
13
+ const sessionCookiesPreview = config.sessionCookies
14
+ ? `${config.sessionCookies.substring(0, 10)}...${config.sessionCookies.substring(Math.max(0, config.sessionCookies.length - 10))}`
15
+ : null;
16
+ return JSON.stringify({
17
+ url: config.url,
18
+ client: config.client ?? null,
19
+ authType: config.authType,
20
+ username: config.username ?? null,
21
+ password: config.password ? 'set' : null,
22
+ jwtToken: jwtTokenPreview,
23
+ refreshToken: refreshTokenPreview,
24
+ sessionCookies: sessionCookiesPreview,
25
+ });
26
+ }
@@ -0,0 +1,4 @@
1
+ import type { IAbapConnection, IAbapRequestOptions } from '@babamba2/mcp-abap-adt-interfaces';
2
+ export type AbapRequestOptions = IAbapRequestOptions;
3
+ export type AbapConnection = IAbapConnection;
4
+ //# sourceMappingURL=AbapConnection.d.ts.map