@mcp-abap-adt/auth-stores 0.2.1 → 0.2.5

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 CHANGED
@@ -7,6 +7,52 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.5] - 2025-12-19
11
+
12
+ ### Fixed
13
+ - **Version Correction**: This release corrects the version numbering issue. The typed error classes and service key store updates that were documented in 0.2.4 were released after 0.2.4 was already published. This version properly documents those changes.
14
+
15
+ ## [0.2.4] - 2025-12-19
16
+
17
+ ### Added
18
+ - **Typed Error Classes**: Added typed error classes for better error handling in auth-broker
19
+ - `StoreError` - Base error class with error code
20
+ - `FileNotFoundError` - File not found errors (includes filePath)
21
+ - `ParseError` - JSON/YAML parsing errors (includes filePath and cause)
22
+ - `InvalidConfigError` - Missing required config fields (includes missingFields array)
23
+ - `StorageError` - File write/permission errors (includes operation and cause)
24
+
25
+ ### Changed
26
+ - **Service Key Stores**: Now throw typed errors instead of generic Error
27
+ - `FileNotFoundError` when service key file not found (returns null)
28
+ - `ParseError` when JSON parsing fails or format is invalid
29
+ - `InvalidConfigError` when required UAA fields are missing (returns null)
30
+ - **Dependency**: Updated `@mcp-abap-adt/interfaces` to `^0.2.3` for STORE_ERROR_CODES
31
+
32
+ ## [0.2.3] - 2025-12-16
33
+
34
+ ### Changed
35
+ - Dependency bump: `@mcp-abap-adt/interfaces` to `^0.1.17` for basic auth support
36
+
37
+ ### Added
38
+ - **Basic Authentication Support for On-Premise Systems**: Added support for basic auth (username/password) in addition to JWT tokens
39
+ - **envLoader.ts**: Now loads `SAP_USERNAME` and `SAP_PASSWORD` from `.env` files
40
+ - Automatically detects auth type: if username/password present and no JWT token, uses basic auth
41
+ - If JWT token present, uses JWT auth
42
+ - **AbapSessionStore.getConnectionConfig()**: Returns basic auth config when username/password are present
43
+ - Returns `IConnectionConfig` with `username`, `password`, and `authType: 'basic'` for on-premise systems
44
+ - Returns `IConnectionConfig` with `authorizationToken` and `authType: 'jwt'` for cloud systems
45
+ - **tokenStorage.ts**: Now saves `SAP_USERNAME` and `SAP_PASSWORD` to `.env` files
46
+ - Handles both JWT and basic auth configurations
47
+ - Clears username/password when JWT auth is used, and vice versa
48
+ - **constants.ts**: Added `USERNAME: 'SAP_USERNAME'` and `PASSWORD: 'SAP_PASSWORD'` to `ABAP_CONNECTION_VARS`
49
+ - This enables on-premise systems to use `--mcp` parameter with basic auth instead of requiring JWT tokens
50
+
51
+ ## [0.2.2] - 2025-12-13
52
+
53
+ ### Changed
54
+ - Dependency bump: `@mcp-abap-adt/interfaces` to `^0.1.16` for alignment with latest interfaces docs
55
+
10
56
  ## [0.2.1] - 2025-12-12
11
57
 
12
58
  ### Changed
package/README.md CHANGED
@@ -198,6 +198,58 @@ The `defaultServiceUrl` is used when creating new sessions via `setConnectionCon
198
198
 
199
199
  ## File Handlers
200
200
 
201
+ This package provides utility classes for safe file operations:
202
+
203
+ ### Error Handling
204
+
205
+ All service key stores throw typed errors for better error handling:
206
+
207
+ ```typescript
208
+ import {
209
+ BtpServiceKeyStore,
210
+ FileNotFoundError,
211
+ ParseError,
212
+ InvalidConfigError
213
+ } from '@mcp-abap-adt/auth-stores';
214
+ import { STORE_ERROR_CODES } from '@mcp-abap-adt/interfaces';
215
+
216
+ const serviceKeyStore = new BtpServiceKeyStore('/path/to/keys');
217
+
218
+ try {
219
+ const authConfig = await serviceKeyStore.getAuthorizationConfig('TRIAL');
220
+ console.log('Auth config loaded:', authConfig);
221
+ } catch (error: any) {
222
+ if (error.code === STORE_ERROR_CODES.FILE_NOT_FOUND) {
223
+ // File not found - returns null instead of throwing
224
+ console.error('Service key file not found:', error.filePath);
225
+ } else if (error.code === STORE_ERROR_CODES.PARSE_ERROR) {
226
+ // JSON parsing failed or invalid format
227
+ console.error('Failed to parse service key:', error.filePath);
228
+ console.error('Cause:', error.cause);
229
+ } else if (error.code === STORE_ERROR_CODES.INVALID_CONFIG) {
230
+ // Required UAA fields missing - returns null instead of throwing
231
+ console.error('Invalid config:', error.missingFields);
232
+ } else if (error.code === STORE_ERROR_CODES.STORAGE_ERROR) {
233
+ // File write/permission error
234
+ console.error('Storage operation failed:', error.operation);
235
+ console.error('Cause:', error.cause);
236
+ } else {
237
+ // Generic error
238
+ console.error('Unexpected error:', error.message);
239
+ }
240
+ }
241
+ ```
242
+
243
+ **Error Types:**
244
+ - **`FileNotFoundError`** - Service key file not found (includes `filePath`)
245
+ - **`ParseError`** - JSON parsing failed or invalid format (includes `filePath` and `cause`)
246
+ - **`InvalidConfigError`** - Required configuration fields missing (includes `missingFields` array)
247
+ - **`StorageError`** - File write or permission error (includes `operation` and `cause`)
248
+
249
+ **Note**: Most errors result in `null` return values rather than exceptions. Only fatal errors (like JSON parsing failures) throw exceptions.
250
+
251
+ ## File Handlers
252
+
201
253
  Utility classes for working with files:
202
254
 
203
255
  ### JsonFileHandler
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Store error codes and typed error classes
3
+ */
4
+ import { type StoreErrorCode } from '@mcp-abap-adt/interfaces';
5
+ /**
6
+ * Base error class for all store errors
7
+ */
8
+ export declare class StoreError extends Error {
9
+ readonly code: StoreErrorCode;
10
+ constructor(message: string, code: StoreErrorCode);
11
+ }
12
+ /**
13
+ * Error thrown when a file is not found
14
+ */
15
+ export declare class FileNotFoundError extends StoreError {
16
+ readonly filePath: string;
17
+ constructor(filePath: string, message?: string);
18
+ }
19
+ /**
20
+ * Error thrown when a file cannot be parsed (invalid JSON, YAML, etc.)
21
+ */
22
+ export declare class ParseError extends StoreError {
23
+ readonly filePath?: string;
24
+ readonly cause?: Error;
25
+ constructor(message: string, filePath?: string, cause?: Error);
26
+ }
27
+ /**
28
+ * Error thrown when required configuration fields are missing
29
+ */
30
+ export declare class InvalidConfigError extends StoreError {
31
+ readonly missingFields: string[];
32
+ constructor(message: string, missingFields?: string[]);
33
+ }
34
+ /**
35
+ * Error thrown when storage operations fail (file write, permission denied, etc.)
36
+ */
37
+ export declare class StorageError extends StoreError {
38
+ readonly operation: string;
39
+ readonly cause?: Error;
40
+ constructor(operation: string, message: string, cause?: Error);
41
+ }
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ /**
3
+ * Store error codes and typed error classes
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.StorageError = exports.InvalidConfigError = exports.ParseError = exports.FileNotFoundError = exports.StoreError = void 0;
7
+ const interfaces_1 = require("@mcp-abap-adt/interfaces");
8
+ /**
9
+ * Base error class for all store errors
10
+ */
11
+ class StoreError extends Error {
12
+ code;
13
+ constructor(message, code) {
14
+ super(message);
15
+ this.name = 'StoreError';
16
+ this.code = code;
17
+ Object.setPrototypeOf(this, StoreError.prototype);
18
+ }
19
+ }
20
+ exports.StoreError = StoreError;
21
+ /**
22
+ * Error thrown when a file is not found
23
+ */
24
+ class FileNotFoundError extends StoreError {
25
+ filePath;
26
+ constructor(filePath, message) {
27
+ super(message || `File not found: ${filePath}`, interfaces_1.STORE_ERROR_CODES.FILE_NOT_FOUND);
28
+ this.name = 'FileNotFoundError';
29
+ this.filePath = filePath;
30
+ Object.setPrototypeOf(this, FileNotFoundError.prototype);
31
+ }
32
+ }
33
+ exports.FileNotFoundError = FileNotFoundError;
34
+ /**
35
+ * Error thrown when a file cannot be parsed (invalid JSON, YAML, etc.)
36
+ */
37
+ class ParseError extends StoreError {
38
+ filePath;
39
+ cause;
40
+ constructor(message, filePath, cause) {
41
+ super(message, interfaces_1.STORE_ERROR_CODES.PARSE_ERROR);
42
+ this.name = 'ParseError';
43
+ this.filePath = filePath;
44
+ this.cause = cause;
45
+ Object.setPrototypeOf(this, ParseError.prototype);
46
+ }
47
+ }
48
+ exports.ParseError = ParseError;
49
+ /**
50
+ * Error thrown when required configuration fields are missing
51
+ */
52
+ class InvalidConfigError extends StoreError {
53
+ missingFields;
54
+ constructor(message, missingFields = []) {
55
+ super(message, interfaces_1.STORE_ERROR_CODES.INVALID_CONFIG);
56
+ this.name = 'InvalidConfigError';
57
+ this.missingFields = missingFields;
58
+ Object.setPrototypeOf(this, InvalidConfigError.prototype);
59
+ }
60
+ }
61
+ exports.InvalidConfigError = InvalidConfigError;
62
+ /**
63
+ * Error thrown when storage operations fail (file write, permission denied, etc.)
64
+ */
65
+ class StorageError extends StoreError {
66
+ operation;
67
+ cause;
68
+ constructor(operation, message, cause) {
69
+ super(message, interfaces_1.STORE_ERROR_CODES.STORAGE_ERROR);
70
+ this.name = 'StorageError';
71
+ this.operation = operation;
72
+ this.cause = cause;
73
+ Object.setPrototypeOf(this, StorageError.prototype);
74
+ }
75
+ }
76
+ exports.StorageError = StorageError;
package/dist/index.d.ts CHANGED
@@ -13,6 +13,7 @@ export { AbapServiceKeyStore } from './stores/abap/AbapServiceKeyStore';
13
13
  export { XsuaaSessionStore } from './stores/xsuaa/XsuaaSessionStore';
14
14
  export { SafeXsuaaSessionStore } from './stores/xsuaa/SafeXsuaaSessionStore';
15
15
  export { XsuaaServiceKeyStore } from './stores/xsuaa/XsuaaServiceKeyStore';
16
+ export { StoreError, FileNotFoundError, ParseError, InvalidConfigError, StorageError } from './errors/StoreErrors';
16
17
  export { resolveSearchPaths, findFileInPaths } from './utils/pathResolver';
17
18
  export { JsonFileHandler } from './utils/JsonFileHandler';
18
19
  export { EnvFileHandler } from './utils/EnvFileHandler';
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * Provides BTP, ABAP, and XSUAA store implementations
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.loadXSUAAServiceKey = exports.loadServiceKey = exports.XSUAA_CONNECTION_VARS = exports.XSUAA_AUTHORIZATION_VARS = exports.BTP_CONNECTION_VARS = exports.BTP_AUTHORIZATION_VARS = exports.ABAP_CONNECTION_VARS = exports.ABAP_AUTHORIZATION_VARS = exports.EnvFileHandler = exports.JsonFileHandler = exports.findFileInPaths = exports.resolveSearchPaths = exports.XsuaaServiceKeyStore = exports.SafeXsuaaSessionStore = exports.XsuaaSessionStore = exports.AbapServiceKeyStore = exports.SafeAbapSessionStore = exports.AbapSessionStore = exports.BtpServiceKeyStore = exports.SafeBtpSessionStore = exports.BtpSessionStore = void 0;
9
+ exports.loadXSUAAServiceKey = exports.loadServiceKey = exports.XSUAA_CONNECTION_VARS = exports.XSUAA_AUTHORIZATION_VARS = exports.BTP_CONNECTION_VARS = exports.BTP_AUTHORIZATION_VARS = exports.ABAP_CONNECTION_VARS = exports.ABAP_AUTHORIZATION_VARS = exports.EnvFileHandler = exports.JsonFileHandler = exports.findFileInPaths = exports.resolveSearchPaths = exports.StorageError = exports.InvalidConfigError = exports.ParseError = exports.FileNotFoundError = exports.StoreError = exports.XsuaaServiceKeyStore = exports.SafeXsuaaSessionStore = exports.XsuaaSessionStore = exports.AbapServiceKeyStore = exports.SafeAbapSessionStore = exports.AbapSessionStore = exports.BtpServiceKeyStore = exports.SafeBtpSessionStore = exports.BtpSessionStore = void 0;
10
10
  // BTP stores (base BTP without sapUrl)
11
11
  var BtpSessionStore_1 = require("./stores/btp/BtpSessionStore");
12
12
  Object.defineProperty(exports, "BtpSessionStore", { enumerable: true, get: function () { return BtpSessionStore_1.BtpSessionStore; } });
@@ -28,6 +28,13 @@ var SafeXsuaaSessionStore_1 = require("./stores/xsuaa/SafeXsuaaSessionStore");
28
28
  Object.defineProperty(exports, "SafeXsuaaSessionStore", { enumerable: true, get: function () { return SafeXsuaaSessionStore_1.SafeXsuaaSessionStore; } });
29
29
  var XsuaaServiceKeyStore_1 = require("./stores/xsuaa/XsuaaServiceKeyStore");
30
30
  Object.defineProperty(exports, "XsuaaServiceKeyStore", { enumerable: true, get: function () { return XsuaaServiceKeyStore_1.XsuaaServiceKeyStore; } });
31
+ // Error classes
32
+ var StoreErrors_1 = require("./errors/StoreErrors");
33
+ Object.defineProperty(exports, "StoreError", { enumerable: true, get: function () { return StoreErrors_1.StoreError; } });
34
+ Object.defineProperty(exports, "FileNotFoundError", { enumerable: true, get: function () { return StoreErrors_1.FileNotFoundError; } });
35
+ Object.defineProperty(exports, "ParseError", { enumerable: true, get: function () { return StoreErrors_1.ParseError; } });
36
+ Object.defineProperty(exports, "InvalidConfigError", { enumerable: true, get: function () { return StoreErrors_1.InvalidConfigError; } });
37
+ Object.defineProperty(exports, "StorageError", { enumerable: true, get: function () { return StoreErrors_1.StorageError; } });
31
38
  // Utils
32
39
  var pathResolver_1 = require("./utils/pathResolver");
33
40
  Object.defineProperty(exports, "resolveSearchPaths", { enumerable: true, get: function () { return pathResolver_1.resolveSearchPaths; } });
@@ -5,7 +5,10 @@ import type { ILogger } from '@mcp-abap-adt/interfaces';
5
5
  interface EnvConfig {
6
6
  sapUrl: string;
7
7
  sapClient?: string;
8
- jwtToken: string;
8
+ jwtToken?: string;
9
+ username?: string;
10
+ password?: string;
11
+ authType?: 'basic' | 'jwt';
9
12
  refreshToken?: string;
10
13
  uaaUrl?: string;
11
14
  uaaClientId?: string;
@@ -65,16 +65,38 @@ async function loadEnvFile(destination, directory, log) {
65
65
  // Extract required fields
66
66
  const sapUrl = parsed[constants_1.ABAP_CONNECTION_VARS.SERVICE_URL];
67
67
  const jwtToken = parsed[constants_1.ABAP_CONNECTION_VARS.AUTHORIZATION_TOKEN];
68
- log?.debug(`Extracted fields: hasSapUrl(${!!sapUrl}), hasJwtToken(${jwtToken !== undefined && jwtToken !== null})`);
69
- // sapUrl is required, jwtToken can be empty string (for authorization-only sessions)
70
- if (!sapUrl || jwtToken === undefined || jwtToken === null) {
71
- log?.warn(`Env file missing required fields: sapUrl(${!!sapUrl}), jwtToken(${jwtToken !== undefined && jwtToken !== null})`);
68
+ const username = parsed[constants_1.ABAP_CONNECTION_VARS.USERNAME];
69
+ const password = parsed[constants_1.ABAP_CONNECTION_VARS.PASSWORD];
70
+ log?.debug(`Extracted fields: hasSapUrl(${!!sapUrl}), hasJwtToken(${jwtToken !== undefined && jwtToken !== null}), hasUsername(${!!username}), hasPassword(${!!password})`);
71
+ // sapUrl is always required
72
+ if (!sapUrl) {
73
+ log?.warn(`Env file missing required field: sapUrl`);
72
74
  return null;
73
75
  }
76
+ // Determine auth type: if username/password present and no jwtToken, use basic auth
77
+ // If jwtToken present, use JWT auth
78
+ // If neither is present, it's OK - auth can be set later (e.g., via setAuthorizationConfig)
79
+ const isBasicAuth = !!(username && password) && (!jwtToken || jwtToken.trim() === '');
80
+ const isJwtAuth = !!(jwtToken && jwtToken.trim() !== '');
81
+ const hasNoAuth = !isBasicAuth && !isJwtAuth;
74
82
  const config = {
75
83
  sapUrl: sapUrl.trim(),
76
- jwtToken: jwtToken.trim(), // Can be empty string for authorization-only sessions
77
84
  };
85
+ // Set authentication fields based on type
86
+ if (isBasicAuth) {
87
+ config.username = username.trim();
88
+ config.password = password.trim();
89
+ config.authType = 'basic';
90
+ }
91
+ else if (isJwtAuth) {
92
+ config.jwtToken = jwtToken.trim();
93
+ config.authType = 'jwt';
94
+ }
95
+ else {
96
+ // No auth yet - will be set later (e.g., via setAuthorizationConfig)
97
+ config.jwtToken = jwtToken || '';
98
+ config.authType = undefined;
99
+ }
78
100
  // Optional fields
79
101
  if (parsed[constants_1.ABAP_CONNECTION_VARS.SAP_CLIENT]) {
80
102
  config.sapClient = parsed[constants_1.ABAP_CONNECTION_VARS.SAP_CLIENT].trim();
@@ -94,7 +116,11 @@ async function loadEnvFile(destination, directory, log) {
94
116
  if (parsed[constants_1.ABAP_CONNECTION_VARS.SAP_LANGUAGE]) {
95
117
  config.language = parsed[constants_1.ABAP_CONNECTION_VARS.SAP_LANGUAGE].trim();
96
118
  }
97
- log?.info(`Env config loaded from ${envFilePath}: sapUrl(${config.sapUrl.substring(0, 50)}...), token(${config.jwtToken.length} chars), hasRefreshToken(${!!config.refreshToken}), hasUaaUrl(${!!config.uaaUrl})`);
119
+ const tokenLength = config.jwtToken?.length || 0;
120
+ const authInfo = config.authType === 'basic'
121
+ ? `basic auth (username: ${config.username})`
122
+ : `JWT token(${tokenLength} chars)`;
123
+ log?.info(`Env config loaded from ${envFilePath}: sapUrl(${config.sapUrl.substring(0, 50)}...), ${authInfo}, hasRefreshToken(${!!config.refreshToken}), hasUaaUrl(${!!config.uaaUrl})`);
98
124
  return config;
99
125
  }
100
126
  catch (error) {
@@ -5,7 +5,10 @@ import type { ILogger } from '@mcp-abap-adt/interfaces';
5
5
  interface EnvConfig {
6
6
  sapUrl: string;
7
7
  sapClient?: string;
8
- jwtToken: string;
8
+ jwtToken?: string;
9
+ username?: string;
10
+ password?: string;
11
+ authType?: 'basic' | 'jwt';
9
12
  refreshToken?: string;
10
13
  uaaUrl?: string;
11
14
  uaaClientId?: string;
@@ -20,7 +23,7 @@ interface EnvConfig {
20
23
  * @param log Optional logger for logging operations
21
24
  */
22
25
  export declare function saveTokenToEnv(destination: string, savePath: string, config: Partial<EnvConfig> & {
23
- sapUrl?: string;
24
- jwtToken: string;
26
+ sapUrl: string;
27
+ jwtToken?: string;
25
28
  }, log?: ILogger): Promise<void>;
26
29
  export {};
@@ -51,7 +51,9 @@ async function saveTokenToEnv(destination, savePath, config, log) {
51
51
  const envFilePath = path.join(savePath, `${destination}.env`);
52
52
  const tempFilePath = `${envFilePath}.tmp`;
53
53
  log?.debug(`Saving token to env file: ${envFilePath}`);
54
- log?.debug(`Config to save: hasSapUrl(${!!config.sapUrl}), token(${config.jwtToken.length} chars), hasRefreshToken(${!!config.refreshToken}), hasUaaUrl(${!!config.uaaUrl})`);
54
+ const tokenLength = config.jwtToken?.length || 0;
55
+ const hasBasicAuth = !!(config.username && config.password);
56
+ log?.debug(`Config to save: hasSapUrl(${!!config.sapUrl}), token(${tokenLength} chars), hasBasicAuth(${hasBasicAuth}), hasRefreshToken(${!!config.refreshToken}), hasUaaUrl(${!!config.uaaUrl})`);
55
57
  // Ensure directory exists
56
58
  if (!fs.existsSync(savePath)) {
57
59
  log?.debug(`Creating directory: ${savePath}`);
@@ -83,10 +85,25 @@ async function saveTokenToEnv(destination, savePath, config, log) {
83
85
  }
84
86
  log?.debug(`Preserved ${existingVars.size} existing variables from env file`);
85
87
  // Update with new values
86
- if (config.sapUrl) {
87
- existingVars.set(constants_1.ABAP_CONNECTION_VARS.SERVICE_URL, config.sapUrl);
88
+ // sapUrl is required - always save it
89
+ existingVars.set(constants_1.ABAP_CONNECTION_VARS.SERVICE_URL, config.sapUrl);
90
+ // Handle authentication: JWT or basic auth
91
+ if (config.username && config.password) {
92
+ // Basic auth - save username/password
93
+ existingVars.set(constants_1.ABAP_CONNECTION_VARS.USERNAME, config.username);
94
+ existingVars.set(constants_1.ABAP_CONNECTION_VARS.PASSWORD, config.password);
95
+ // Clear JWT token if basic auth is used
96
+ if (config.jwtToken) {
97
+ existingVars.set(constants_1.ABAP_CONNECTION_VARS.AUTHORIZATION_TOKEN, '');
98
+ }
99
+ }
100
+ else if (config.jwtToken) {
101
+ // JWT auth - save token
102
+ existingVars.set(constants_1.ABAP_CONNECTION_VARS.AUTHORIZATION_TOKEN, config.jwtToken);
103
+ // Clear username/password if JWT auth is used
104
+ existingVars.delete(constants_1.ABAP_CONNECTION_VARS.USERNAME);
105
+ existingVars.delete(constants_1.ABAP_CONNECTION_VARS.PASSWORD);
88
106
  }
89
- existingVars.set(constants_1.ABAP_CONNECTION_VARS.AUTHORIZATION_TOKEN, config.jwtToken);
90
107
  if (config.sapClient) {
91
108
  existingVars.set(constants_1.ABAP_CONNECTION_VARS.SAP_CLIENT, config.sapClient);
92
109
  }
@@ -120,5 +137,8 @@ async function saveTokenToEnv(destination, savePath, config, log) {
120
137
  fs.writeFileSync(tempFilePath, envContent, 'utf8');
121
138
  // Atomic rename
122
139
  fs.renameSync(tempFilePath, envFilePath);
123
- log?.info(`Token saved to ${envFilePath}: token(${config.jwtToken.length} chars), sapUrl(${config.sapUrl ? config.sapUrl.substring(0, 50) + '...' : 'none'}), variables(${envLines.length})`);
140
+ const authInfo = hasBasicAuth
141
+ ? `basic auth (username: ${config.username})`
142
+ : `JWT token(${tokenLength} chars)`;
143
+ log?.info(`Token saved to ${envFilePath}: ${authInfo}, sapUrl(${config.sapUrl ? config.sapUrl.substring(0, 50) + '...' : 'none'}), variables(${envLines.length})`);
124
144
  }
@@ -39,6 +39,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.AbapServiceKeyStore = void 0;
40
40
  const JsonFileHandler_1 = require("../../utils/JsonFileHandler");
41
41
  const AbapServiceKeyParser_1 = require("../../parsers/abap/AbapServiceKeyParser");
42
+ const StoreErrors_1 = require("../../errors/StoreErrors");
42
43
  const path = __importStar(require("path"));
43
44
  /**
44
45
  * ABAP Service key store implementation
@@ -112,7 +113,7 @@ class AbapServiceKeyStore {
112
113
  }
113
114
  catch (error) {
114
115
  this.log?.error(`Failed to parse service key from ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
115
- throw new Error(`Failed to parse service key for destination "${destination}": ${error instanceof Error ? error.message : String(error)}`);
116
+ throw new StoreErrors_1.ParseError(`Failed to parse service key for destination "${destination}"`, filePath, error instanceof Error ? error : undefined);
116
117
  }
117
118
  }
118
119
  /**
@@ -94,9 +94,8 @@ class AbapSessionStore {
94
94
  convertToInternalFormat(config) {
95
95
  const obj = config;
96
96
  // Convert IConfig format (serviceUrl, authorizationToken) to internal format (sapUrl, jwtToken)
97
- return {
97
+ const result = {
98
98
  sapUrl: (obj.serviceUrl || obj.sapUrl),
99
- jwtToken: (obj.authorizationToken || obj.jwtToken || ''), // Ensure jwtToken is always a string
100
99
  refreshToken: obj.refreshToken,
101
100
  uaaUrl: obj.uaaUrl,
102
101
  uaaClientId: obj.uaaClientId,
@@ -104,6 +103,20 @@ class AbapSessionStore {
104
103
  sapClient: obj.sapClient,
105
104
  language: obj.language,
106
105
  };
106
+ // Handle authentication: JWT or basic auth
107
+ if (obj.username && obj.password) {
108
+ // Basic auth
109
+ result.username = obj.username;
110
+ result.password = obj.password;
111
+ result.authType = 'basic';
112
+ result.jwtToken = obj.authorizationToken || obj.jwtToken || '';
113
+ }
114
+ else {
115
+ // JWT auth
116
+ result.jwtToken = (obj.authorizationToken || obj.jwtToken || '');
117
+ result.authType = 'jwt';
118
+ }
119
+ return result;
107
120
  }
108
121
  /**
109
122
  * Save session to ENV file
@@ -125,6 +138,9 @@ class AbapSessionStore {
125
138
  await (0, tokenStorage_1.saveTokenToEnv)(destination, savePath, {
126
139
  sapUrl: abapConfig.sapUrl,
127
140
  jwtToken: abapConfig.jwtToken,
141
+ username: abapConfig.username,
142
+ password: abapConfig.password,
143
+ authType: abapConfig.authType,
128
144
  refreshToken: abapConfig.refreshToken,
129
145
  uaaUrl: abapConfig.uaaUrl,
130
146
  uaaClientId: abapConfig.uaaClientId,
@@ -193,6 +209,16 @@ class AbapSessionStore {
193
209
  if (rawSession.jwtToken !== undefined) {
194
210
  result.authorizationToken = rawSession.jwtToken;
195
211
  }
212
+ // Basic auth fields (if present)
213
+ if (rawSession.username) {
214
+ result.username = rawSession.username;
215
+ }
216
+ if (rawSession.password) {
217
+ result.password = rawSession.password;
218
+ }
219
+ if (rawSession.authType) {
220
+ result.authType = rawSession.authType;
221
+ }
196
222
  if (rawSession.sapClient) {
197
223
  result.sapClient = rawSession.sapClient;
198
224
  }
@@ -230,7 +256,7 @@ class AbapSessionStore {
230
256
  try {
231
257
  const raw = await this.loadFromFile(sessionPath);
232
258
  if (!raw || !isEnvConfig(raw)) {
233
- this.log?.debug(`Invalid session format for ${destination}: missing required fields (sapUrl, jwtToken)`);
259
+ this.log?.debug(`Invalid session format for ${destination}: missing required field (sapUrl)`);
234
260
  return null;
235
261
  }
236
262
  return raw;
@@ -276,14 +302,38 @@ class AbapSessionStore {
276
302
  this.log?.debug(`Connection config not found for ${destination}`);
277
303
  return null;
278
304
  }
279
- if (!sessionConfig.jwtToken || !sessionConfig.sapUrl) {
280
- this.log?.warn(`Connection config for ${destination} missing required fields: jwtToken(${!!sessionConfig.jwtToken}), sapUrl(${!!sessionConfig.sapUrl})`);
305
+ if (!sessionConfig.sapUrl) {
306
+ this.log?.warn(`Connection config for ${destination} missing required field: sapUrl`);
281
307
  return null;
282
308
  }
283
- this.log?.debug(`Connection config loaded for ${destination}: token(${sessionConfig.jwtToken.length} chars), sapUrl(${sessionConfig.sapUrl.substring(0, 40)}...)`);
309
+ // Check for basic auth: if username/password present and no jwtToken, use basic auth
310
+ const isBasicAuth = sessionConfig.authType === 'basic' ||
311
+ (!sessionConfig.jwtToken && sessionConfig.username && sessionConfig.password);
312
+ if (isBasicAuth) {
313
+ if (!sessionConfig.username || !sessionConfig.password) {
314
+ this.log?.warn(`Connection config for ${destination} missing required fields for basic auth: username(${!!sessionConfig.username}), password(${!!sessionConfig.password})`);
315
+ return null;
316
+ }
317
+ this.log?.debug(`Connection config loaded for ${destination} (basic auth): username(${sessionConfig.username}), sapUrl(${sessionConfig.sapUrl.substring(0, 40)}...)`);
318
+ return {
319
+ serviceUrl: sessionConfig.sapUrl,
320
+ username: sessionConfig.username,
321
+ password: sessionConfig.password,
322
+ authType: 'basic',
323
+ sapClient: sessionConfig.sapClient,
324
+ language: sessionConfig.language,
325
+ };
326
+ }
327
+ // JWT auth: check jwtToken
328
+ if (!sessionConfig.jwtToken) {
329
+ this.log?.warn(`Connection config for ${destination} missing required field for JWT auth: jwtToken`);
330
+ return null;
331
+ }
332
+ this.log?.debug(`Connection config loaded for ${destination} (JWT auth): token(${sessionConfig.jwtToken.length} chars), sapUrl(${sessionConfig.sapUrl.substring(0, 40)}...)`);
284
333
  return {
285
334
  serviceUrl: sessionConfig.sapUrl,
286
335
  authorizationToken: sessionConfig.jwtToken,
336
+ authType: 'jwt',
287
337
  sapClient: sessionConfig.sapClient,
288
338
  language: sessionConfig.language,
289
339
  };
@@ -298,10 +348,27 @@ class AbapSessionStore {
298
348
  async setAuthorizationConfig(destination, config) {
299
349
  const current = await this.loadRawSession(destination);
300
350
  if (!current) {
301
- // Session doesn't exist - try to get serviceUrl from connection config or use defaultServiceUrl
351
+ // Session doesn't exist - try to get serviceUrl from existing session file or use defaultServiceUrl
302
352
  // For ABAP, we need sapUrl to create session
303
- const connConfig = await this.getConnectionConfig(destination);
304
- const sapUrl = connConfig?.serviceUrl || this.defaultServiceUrl;
353
+ let sapUrl = this.defaultServiceUrl;
354
+ let existingAuthToken = '';
355
+ let existingUsername;
356
+ let existingPassword;
357
+ let existingAuthType;
358
+ // Try to load existing session file to get serviceUrl and auth info
359
+ try {
360
+ const existingSession = await this.loadSession(destination);
361
+ if (existingSession?.serviceUrl) {
362
+ sapUrl = existingSession.serviceUrl;
363
+ existingAuthToken = existingSession.authorizationToken || '';
364
+ existingUsername = existingSession?.username;
365
+ existingPassword = existingSession?.password;
366
+ existingAuthType = existingSession?.authType;
367
+ }
368
+ }
369
+ catch {
370
+ // Ignore errors when loading session - will use defaultServiceUrl
371
+ }
305
372
  if (!sapUrl) {
306
373
  this.log?.error(`Cannot set authorization config for ${destination}: session does not exist and serviceUrl is required. Missing defaultServiceUrl in constructor.`);
307
374
  throw new Error(`Cannot set authorization config for destination "${destination}": session does not exist and serviceUrl is required for ABAP sessions. Call setConnectionConfig first or provide defaultServiceUrl in constructor.`);
@@ -309,7 +376,10 @@ class AbapSessionStore {
309
376
  this.log?.debug(`Creating new session for ${destination} via setAuthorizationConfig: sapUrl(${sapUrl.substring(0, 40)}...)`);
310
377
  const newSession = {
311
378
  serviceUrl: sapUrl,
312
- authorizationToken: connConfig?.authorizationToken || '', // Use token from connection config if available
379
+ authorizationToken: existingAuthToken,
380
+ username: existingUsername,
381
+ password: existingPassword,
382
+ authType: existingAuthType,
313
383
  uaaUrl: config.uaaUrl,
314
384
  uaaClientId: config.uaaClientId,
315
385
  uaaClientSecret: config.uaaClientSecret,
@@ -387,5 +457,6 @@ function isEnvConfig(config) {
387
457
  if (!config || typeof config !== 'object')
388
458
  return false;
389
459
  const obj = config;
390
- return 'sapUrl' in obj && 'jwtToken' in obj;
460
+ // Must have sapUrl (authentication fields are optional - can be set later)
461
+ return 'sapUrl' in obj;
391
462
  }
@@ -55,6 +55,9 @@ class SafeAbapSessionStore {
55
55
  const internal = {
56
56
  sapUrl: (obj.serviceUrl || obj.sapUrl),
57
57
  jwtToken: (obj.authorizationToken || obj.jwtToken),
58
+ username: obj.username,
59
+ password: obj.password,
60
+ authType: obj.authType,
58
61
  refreshToken: obj.refreshToken,
59
62
  uaaUrl: obj.uaaUrl,
60
63
  uaaClientId: obj.uaaClientId,
@@ -130,14 +133,38 @@ class SafeAbapSessionStore {
130
133
  this.log?.debug(`Connection config not found for ${destination}`);
131
134
  return null;
132
135
  }
133
- if (!sessionConfig.jwtToken || !sessionConfig.sapUrl) {
134
- this.log?.warn(`Connection config for ${destination} missing required fields: jwtToken(${!!sessionConfig.jwtToken}), sapUrl(${!!sessionConfig.sapUrl})`);
136
+ if (!sessionConfig.sapUrl) {
137
+ this.log?.warn(`Connection config for ${destination} missing required field: sapUrl`);
135
138
  return null;
136
139
  }
137
- this.log?.debug(`Connection config loaded for ${destination}: token(${sessionConfig.jwtToken.length} chars), sapUrl(${sessionConfig.sapUrl.substring(0, 40)}...)`);
140
+ // Check for basic auth: if username/password present and no jwtToken, use basic auth
141
+ const isBasicAuth = sessionConfig.authType === 'basic' ||
142
+ (!sessionConfig.jwtToken && sessionConfig.username && sessionConfig.password);
143
+ if (isBasicAuth) {
144
+ if (!sessionConfig.username || !sessionConfig.password) {
145
+ this.log?.warn(`Connection config for ${destination} missing required fields for basic auth: username(${!!sessionConfig.username}), password(${!!sessionConfig.password})`);
146
+ return null;
147
+ }
148
+ this.log?.debug(`Connection config loaded for ${destination} (basic auth): username(${sessionConfig.username}), sapUrl(${sessionConfig.sapUrl.substring(0, 40)}...)`);
149
+ return {
150
+ serviceUrl: sessionConfig.sapUrl,
151
+ username: sessionConfig.username,
152
+ password: sessionConfig.password,
153
+ authType: 'basic',
154
+ sapClient: sessionConfig.sapClient,
155
+ language: sessionConfig.language,
156
+ };
157
+ }
158
+ // JWT auth: check jwtToken
159
+ if (!sessionConfig.jwtToken) {
160
+ this.log?.warn(`Connection config for ${destination} missing required field for JWT auth: jwtToken`);
161
+ return null;
162
+ }
163
+ this.log?.debug(`Connection config loaded for ${destination} (JWT auth): token(${sessionConfig.jwtToken.length} chars), sapUrl(${sessionConfig.sapUrl.substring(0, 40)}...)`);
138
164
  return {
139
165
  serviceUrl: sessionConfig.sapUrl,
140
166
  authorizationToken: sessionConfig.jwtToken,
167
+ authType: 'jwt',
141
168
  sapClient: sessionConfig.sapClient,
142
169
  language: sessionConfig.language,
143
170
  };
@@ -155,7 +182,10 @@ class SafeAbapSessionStore {
155
182
  this.log?.debug(`Creating new session for ${destination} via setConnectionConfig: serviceUrl(${serviceUrl.substring(0, 40)}...), token(${config.authorizationToken?.length || 0} chars)`);
156
183
  const newSession = {
157
184
  sapUrl: serviceUrl,
158
- jwtToken: config.authorizationToken || '',
185
+ jwtToken: config.authorizationToken,
186
+ username: config.username,
187
+ password: config.password,
188
+ authType: config.authType,
159
189
  sapClient: config.sapClient,
160
190
  language: config.language,
161
191
  };
@@ -168,7 +198,10 @@ class SafeAbapSessionStore {
168
198
  const updated = {
169
199
  ...current,
170
200
  sapUrl: config.serviceUrl || current.sapUrl,
171
- jwtToken: config.authorizationToken,
201
+ jwtToken: config.authorizationToken || current.jwtToken || '',
202
+ username: config.username !== undefined ? config.username : current.username,
203
+ password: config.password !== undefined ? config.password : current.password,
204
+ authType: config.authType !== undefined ? config.authType : current.authType,
172
205
  sapClient: config.sapClient !== undefined ? config.sapClient : current.sapClient,
173
206
  language: config.language !== undefined ? config.language : current.language,
174
207
  };
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.BtpServiceKeyStore = void 0;
7
7
  const JsonFileHandler_1 = require("../../utils/JsonFileHandler");
8
8
  const XsuaaServiceKeyParser_1 = require("../../parsers/xsuaa/XsuaaServiceKeyParser");
9
+ const StoreErrors_1 = require("../../errors/StoreErrors");
9
10
  /**
10
11
  * Base BTP Service key store implementation
11
12
  *
@@ -73,7 +74,7 @@ class BtpServiceKeyStore {
73
74
  }
74
75
  catch (error) {
75
76
  this.log?.error(`Failed to parse service key for ${destination}: ${error instanceof Error ? error.message : String(error)}`);
76
- throw new Error(`Failed to parse service key for destination "${destination}": ${error instanceof Error ? error.message : String(error)}`);
77
+ throw new StoreErrors_1.ParseError(`Failed to parse service key for destination "${destination}"`, `${destination}.json`, error instanceof Error ? error : undefined);
77
78
  }
78
79
  }
79
80
  /**
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.XsuaaServiceKeyStore = void 0;
7
7
  const JsonFileHandler_1 = require("../../utils/JsonFileHandler");
8
8
  const XsuaaServiceKeyParser_1 = require("../../parsers/xsuaa/XsuaaServiceKeyParser");
9
+ const StoreErrors_1 = require("../../errors/StoreErrors");
9
10
  /**
10
11
  * XSUAA Service key store implementation
11
12
  *
@@ -73,7 +74,7 @@ class XsuaaServiceKeyStore {
73
74
  }
74
75
  catch (error) {
75
76
  this.log?.error(`Failed to parse service key for ${destination}: ${error instanceof Error ? error.message : String(error)}`);
76
- throw new Error(`Failed to parse service key for destination "${destination}": ${error instanceof Error ? error.message : String(error)}`);
77
+ throw new StoreErrors_1.ParseError(`Failed to parse service key for destination "${destination}"`, `${destination}.json`, error instanceof Error ? error : undefined);
77
78
  }
78
79
  }
79
80
  /**
@@ -107,7 +108,7 @@ class XsuaaServiceKeyStore {
107
108
  }
108
109
  catch (error) {
109
110
  this.log?.error(`Failed to parse service key for ${destination}: ${error instanceof Error ? error.message : String(error)}`);
110
- throw new Error(`Failed to parse service key for destination "${destination}": ${error instanceof Error ? error.message : String(error)}`);
111
+ throw new StoreErrors_1.ParseError(`Failed to parse service key for destination "${destination}"`, `${destination}.json`, error instanceof Error ? error : undefined);
111
112
  }
112
113
  }
113
114
  }
@@ -48,6 +48,10 @@ export declare const ABAP_CONNECTION_VARS: {
48
48
  readonly SERVICE_URL: "SAP_URL";
49
49
  /** Authorization token (JWT token) */
50
50
  readonly AUTHORIZATION_TOKEN: "SAP_JWT_TOKEN";
51
+ /** Username for basic authentication (on-premise systems) */
52
+ readonly USERNAME: "SAP_USERNAME";
53
+ /** Password for basic authentication (on-premise systems) */
54
+ readonly PASSWORD: "SAP_PASSWORD";
51
55
  /** SAP client number (optional) */
52
56
  readonly SAP_CLIENT: "SAP_CLIENT";
53
57
  /** Language (optional) */
@@ -51,6 +51,10 @@ exports.ABAP_CONNECTION_VARS = {
51
51
  SERVICE_URL: 'SAP_URL',
52
52
  /** Authorization token (JWT token) */
53
53
  AUTHORIZATION_TOKEN: 'SAP_JWT_TOKEN',
54
+ /** Username for basic authentication (on-premise systems) */
55
+ USERNAME: 'SAP_USERNAME',
56
+ /** Password for basic authentication (on-premise systems) */
57
+ PASSWORD: 'SAP_PASSWORD',
54
58
  /** SAP client number (optional) */
55
59
  SAP_CLIENT: 'SAP_CLIENT',
56
60
  /** Language (optional) */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-abap-adt/auth-stores",
3
- "version": "0.2.1",
3
+ "version": "0.2.5",
4
4
  "description": "Stores for MCP ABAP ADT auth-broker - BTP, ABAP, and XSUAA implementations",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -49,7 +49,7 @@
49
49
  "node": ">=18.0.0"
50
50
  },
51
51
  "dependencies": {
52
- "@mcp-abap-adt/interfaces": "^0.1.6",
52
+ "@mcp-abap-adt/interfaces": "^0.2.3",
53
53
  "dotenv": "^17.2.1"
54
54
  },
55
55
  "devDependencies": {