@mcp-abap-adt/auth-stores 0.2.3 → 0.2.6

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,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.6] - 2025-12-21
11
+
12
+ ### Added
13
+ - **EnvFileSessionStore**: New session store that reads from a specific `.env` file path
14
+ - Use case: `mcp-abap-adt --env /path/to/.env` CLI option
15
+ - Supports both basic auth (SAP_USERNAME/SAP_PASSWORD) and JWT auth (SAP_JWT_TOKEN)
16
+ - `getAuthType()` method to determine auth type from the file
17
+ - Read-only for file content; token updates stored in memory only
18
+ - Automatic detection of auth type based on file content
19
+
20
+ ## [0.2.5] - 2025-12-19
21
+
22
+ ### Fixed
23
+ - **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.
24
+
25
+ ## [0.2.4] - 2025-12-19
26
+
27
+ ### Added
28
+ - **Typed Error Classes**: Added typed error classes for better error handling in auth-broker
29
+ - `StoreError` - Base error class with error code
30
+ - `FileNotFoundError` - File not found errors (includes filePath)
31
+ - `ParseError` - JSON/YAML parsing errors (includes filePath and cause)
32
+ - `InvalidConfigError` - Missing required config fields (includes missingFields array)
33
+ - `StorageError` - File write/permission errors (includes operation and cause)
34
+
35
+ ### Changed
36
+ - **Service Key Stores**: Now throw typed errors instead of generic Error
37
+ - `FileNotFoundError` when service key file not found (returns null)
38
+ - `ParseError` when JSON parsing fails or format is invalid
39
+ - `InvalidConfigError` when required UAA fields are missing (returns null)
40
+ - **Dependency**: Updated `@mcp-abap-adt/interfaces` to `^0.2.3` for STORE_ERROR_CODES
41
+
10
42
  ## [0.2.3] - 2025-12-16
11
43
 
12
44
  ### Changed
package/README.md CHANGED
@@ -90,6 +90,9 @@ Session stores manage authentication tokens and configuration:
90
90
  - **`SafeAbapSessionStore`** - In-memory store for ABAP sessions
91
91
  - **`SafeXsuaaSessionStore`** - In-memory store for XSUAA sessions
92
92
 
93
+ **File-based single-file stores**:
94
+ - **`EnvFileSessionStore`** - Reads from a specific `.env` file path (e.g., `--env /path/to/.env`)
95
+
93
96
  ## Usage
94
97
 
95
98
  ### BTP Stores (base BTP without sapUrl)
@@ -141,6 +144,54 @@ const sessionStore = new XsuaaSessionStore('/path/to/sessions', 'https://default
141
144
  const safeSessionStore = new SafeXsuaaSessionStore('https://default.mcp.com', logger);
142
145
  ```
143
146
 
147
+ ### EnvFileSessionStore (Single File)
148
+
149
+ `EnvFileSessionStore` reads connection configuration from a specific `.env` file path rather than a directory. This is useful for the `--env` CLI option.
150
+
151
+ ```typescript
152
+ import { EnvFileSessionStore } from '@mcp-abap-adt/auth-stores';
153
+
154
+ // Create store pointing to specific .env file
155
+ const store = new EnvFileSessionStore('/path/to/.env', logger);
156
+
157
+ // Check the auth type from the file
158
+ const authType = store.getAuthType(); // 'basic' | 'jwt' | null
159
+
160
+ // Load session (works like other session stores)
161
+ const config = await store.loadSession('default');
162
+ console.log(config?.serviceUrl, config?.authType);
163
+
164
+ // For basic auth
165
+ console.log(config?.username, config?.password);
166
+
167
+ // For JWT auth
168
+ console.log(config?.authorizationToken, config?.refreshToken);
169
+ ```
170
+
171
+ **Env file format:**
172
+ ```bash
173
+ # Connection
174
+ SAP_URL=https://your-sap-system.com
175
+ SAP_CLIENT=100
176
+
177
+ # Auth type: 'basic' or 'jwt' (defaults to 'basic')
178
+ SAP_AUTH_TYPE=basic
179
+
180
+ # Basic auth credentials
181
+ SAP_USERNAME=your-username
182
+ SAP_PASSWORD=your-password
183
+
184
+ # OR JWT auth
185
+ # SAP_AUTH_TYPE=jwt
186
+ # SAP_JWT_TOKEN=your-jwt-token
187
+ # SAP_REFRESH_TOKEN=your-refresh-token
188
+ # SAP_UAA_URL=https://uaa.example.com
189
+ # SAP_UAA_CLIENT_ID=client-id
190
+ # SAP_UAA_CLIENT_SECRET=client-secret
191
+ ```
192
+
193
+ **Important**: This store is **read-only** for the file. Token updates (e.g., refreshed JWT tokens) are stored in memory only and do not modify the original `.env` file.
194
+
144
195
  ### Directory Configuration
145
196
 
146
197
  All stores accept a single directory path in the constructor:
@@ -198,6 +249,58 @@ The `defaultServiceUrl` is used when creating new sessions via `setConnectionCon
198
249
 
199
250
  ## File Handlers
200
251
 
252
+ This package provides utility classes for safe file operations:
253
+
254
+ ### Error Handling
255
+
256
+ All service key stores throw typed errors for better error handling:
257
+
258
+ ```typescript
259
+ import {
260
+ BtpServiceKeyStore,
261
+ FileNotFoundError,
262
+ ParseError,
263
+ InvalidConfigError
264
+ } from '@mcp-abap-adt/auth-stores';
265
+ import { STORE_ERROR_CODES } from '@mcp-abap-adt/interfaces';
266
+
267
+ const serviceKeyStore = new BtpServiceKeyStore('/path/to/keys');
268
+
269
+ try {
270
+ const authConfig = await serviceKeyStore.getAuthorizationConfig('TRIAL');
271
+ console.log('Auth config loaded:', authConfig);
272
+ } catch (error: any) {
273
+ if (error.code === STORE_ERROR_CODES.FILE_NOT_FOUND) {
274
+ // File not found - returns null instead of throwing
275
+ console.error('Service key file not found:', error.filePath);
276
+ } else if (error.code === STORE_ERROR_CODES.PARSE_ERROR) {
277
+ // JSON parsing failed or invalid format
278
+ console.error('Failed to parse service key:', error.filePath);
279
+ console.error('Cause:', error.cause);
280
+ } else if (error.code === STORE_ERROR_CODES.INVALID_CONFIG) {
281
+ // Required UAA fields missing - returns null instead of throwing
282
+ console.error('Invalid config:', error.missingFields);
283
+ } else if (error.code === STORE_ERROR_CODES.STORAGE_ERROR) {
284
+ // File write/permission error
285
+ console.error('Storage operation failed:', error.operation);
286
+ console.error('Cause:', error.cause);
287
+ } else {
288
+ // Generic error
289
+ console.error('Unexpected error:', error.message);
290
+ }
291
+ }
292
+ ```
293
+
294
+ **Error Types:**
295
+ - **`FileNotFoundError`** - Service key file not found (includes `filePath`)
296
+ - **`ParseError`** - JSON parsing failed or invalid format (includes `filePath` and `cause`)
297
+ - **`InvalidConfigError`** - Required configuration fields missing (includes `missingFields` array)
298
+ - **`StorageError`** - File write or permission error (includes `operation` and `cause`)
299
+
300
+ **Note**: Most errors result in `null` return values rather than exceptions. Only fatal errors (like JSON parsing failures) throw exceptions.
301
+
302
+ ## File Handlers
303
+
201
304
  Utility classes for working with files:
202
305
 
203
306
  ### 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,8 @@ 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 { EnvFileSessionStore } from './stores/env/EnvFileSessionStore';
17
+ export { StoreError, FileNotFoundError, ParseError, InvalidConfigError, StorageError } from './errors/StoreErrors';
16
18
  export { resolveSearchPaths, findFileInPaths } from './utils/pathResolver';
17
19
  export { JsonFileHandler } from './utils/JsonFileHandler';
18
20
  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.EnvFileSessionStore = 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,16 @@ 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
+ // Env file stores (for --env=path scenarios)
32
+ var EnvFileSessionStore_1 = require("./stores/env/EnvFileSessionStore");
33
+ Object.defineProperty(exports, "EnvFileSessionStore", { enumerable: true, get: function () { return EnvFileSessionStore_1.EnvFileSessionStore; } });
34
+ // Error classes
35
+ var StoreErrors_1 = require("./errors/StoreErrors");
36
+ Object.defineProperty(exports, "StoreError", { enumerable: true, get: function () { return StoreErrors_1.StoreError; } });
37
+ Object.defineProperty(exports, "FileNotFoundError", { enumerable: true, get: function () { return StoreErrors_1.FileNotFoundError; } });
38
+ Object.defineProperty(exports, "ParseError", { enumerable: true, get: function () { return StoreErrors_1.ParseError; } });
39
+ Object.defineProperty(exports, "InvalidConfigError", { enumerable: true, get: function () { return StoreErrors_1.InvalidConfigError; } });
40
+ Object.defineProperty(exports, "StorageError", { enumerable: true, get: function () { return StoreErrors_1.StorageError; } });
31
41
  // Utils
32
42
  var pathResolver_1 = require("./utils/pathResolver");
33
43
  Object.defineProperty(exports, "resolveSearchPaths", { enumerable: true, get: function () { return pathResolver_1.resolveSearchPaths; } });
@@ -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
  /**
@@ -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
  /**
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Env File Session Store - reads session from a specific .env file
3
+ *
4
+ * This store reads connection configuration from a .env file at a specified path.
5
+ * Unlike other stores that work with directories, this one works with a single file.
6
+ *
7
+ * Use case: `mcp-abap-adt --env=/path/to/.env`
8
+ *
9
+ * The store is READ-ONLY for connection config (writes only update in-memory state).
10
+ * For token refresh scenarios (JWT), the in-memory state is used.
11
+ */
12
+ import type { ISessionStore, IConnectionConfig, IAuthorizationConfig, IConfig, ILogger } from '@mcp-abap-adt/interfaces';
13
+ /**
14
+ * Session store that reads from a specific .env file
15
+ */
16
+ export declare class EnvFileSessionStore implements ISessionStore {
17
+ private envFilePath;
18
+ private log?;
19
+ private loadedData;
20
+ private inMemoryUpdates;
21
+ /**
22
+ * Create a new EnvFileSessionStore
23
+ * @param envFilePath Absolute path to the .env file
24
+ * @param log Optional logger
25
+ */
26
+ constructor(envFilePath: string, log?: ILogger);
27
+ /**
28
+ * Get the auth type from the loaded .env file
29
+ */
30
+ getAuthType(): 'basic' | 'jwt' | null;
31
+ /**
32
+ * Parse .env file content into key-value pairs
33
+ */
34
+ private parseEnvContent;
35
+ /**
36
+ * Load and parse the .env file
37
+ */
38
+ private loadEnvFile;
39
+ /**
40
+ * Convert internal format to IConfig
41
+ */
42
+ private toIConfig;
43
+ loadSession(destination: string): Promise<IConfig | null>;
44
+ saveSession(destination: string, config: IConfig): Promise<void>;
45
+ getConnectionConfig(destination: string): Promise<IConnectionConfig | null>;
46
+ setConnectionConfig(destination: string, config: IConnectionConfig): Promise<void>;
47
+ getAuthorizationConfig(destination: string): Promise<IAuthorizationConfig | null>;
48
+ setAuthorizationConfig(destination: string, config: IAuthorizationConfig): Promise<void>;
49
+ getToken(destination: string): Promise<string | undefined>;
50
+ setToken(destination: string, token: string): Promise<void>;
51
+ getRefreshToken(destination: string): Promise<string | undefined>;
52
+ setRefreshToken(destination: string, refreshToken: string): Promise<void>;
53
+ /**
54
+ * Clear in-memory updates (useful for testing)
55
+ */
56
+ clear(): void;
57
+ }
@@ -0,0 +1,338 @@
1
+ "use strict";
2
+ /**
3
+ * Env File Session Store - reads session from a specific .env file
4
+ *
5
+ * This store reads connection configuration from a .env file at a specified path.
6
+ * Unlike other stores that work with directories, this one works with a single file.
7
+ *
8
+ * Use case: `mcp-abap-adt --env=/path/to/.env`
9
+ *
10
+ * The store is READ-ONLY for connection config (writes only update in-memory state).
11
+ * For token refresh scenarios (JWT), the in-memory state is used.
12
+ */
13
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ var desc = Object.getOwnPropertyDescriptor(m, k);
16
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
+ desc = { enumerable: true, get: function() { return m[k]; } };
18
+ }
19
+ Object.defineProperty(o, k2, desc);
20
+ }) : (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ o[k2] = m[k];
23
+ }));
24
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
26
+ }) : function(o, v) {
27
+ o["default"] = v;
28
+ });
29
+ var __importStar = (this && this.__importStar) || (function () {
30
+ var ownKeys = function(o) {
31
+ ownKeys = Object.getOwnPropertyNames || function (o) {
32
+ var ar = [];
33
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
34
+ return ar;
35
+ };
36
+ return ownKeys(o);
37
+ };
38
+ return function (mod) {
39
+ if (mod && mod.__esModule) return mod;
40
+ var result = {};
41
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
42
+ __setModuleDefault(result, mod);
43
+ return result;
44
+ };
45
+ })();
46
+ Object.defineProperty(exports, "__esModule", { value: true });
47
+ exports.EnvFileSessionStore = void 0;
48
+ const fs = __importStar(require("fs"));
49
+ const path = __importStar(require("path"));
50
+ /**
51
+ * Session store that reads from a specific .env file
52
+ */
53
+ class EnvFileSessionStore {
54
+ envFilePath;
55
+ log;
56
+ loadedData = null;
57
+ inMemoryUpdates = new Map();
58
+ /**
59
+ * Create a new EnvFileSessionStore
60
+ * @param envFilePath Absolute path to the .env file
61
+ * @param log Optional logger
62
+ */
63
+ constructor(envFilePath, log) {
64
+ this.envFilePath = path.resolve(envFilePath);
65
+ this.log = log;
66
+ }
67
+ /**
68
+ * Get the auth type from the loaded .env file
69
+ */
70
+ getAuthType() {
71
+ if (!this.loadedData) {
72
+ this.loadEnvFile();
73
+ }
74
+ return this.loadedData?.authType || null;
75
+ }
76
+ /**
77
+ * Parse .env file content into key-value pairs
78
+ */
79
+ parseEnvContent(content) {
80
+ const envVars = {};
81
+ for (const line of content.split(/\r?\n/)) {
82
+ const trimmed = line.trim();
83
+ if (!trimmed || trimmed.startsWith('#'))
84
+ continue;
85
+ const eqIndex = trimmed.indexOf('=');
86
+ if (eqIndex === -1)
87
+ continue;
88
+ const key = trimmed.substring(0, eqIndex).trim();
89
+ let value = trimmed.substring(eqIndex + 1);
90
+ // Remove inline comments (but be careful with URLs containing #)
91
+ // Only remove # comments that are preceded by whitespace
92
+ const commentMatch = value.match(/\s+#/);
93
+ if (commentMatch) {
94
+ value = value.substring(0, commentMatch.index).trim();
95
+ }
96
+ else {
97
+ value = value.trim();
98
+ }
99
+ // Remove surrounding quotes
100
+ value = value.replace(/^["']+|["']+$/g, '').trim();
101
+ if (key) {
102
+ envVars[key] = value;
103
+ }
104
+ }
105
+ return envVars;
106
+ }
107
+ /**
108
+ * Load and parse the .env file
109
+ */
110
+ loadEnvFile() {
111
+ if (this.loadedData) {
112
+ return this.loadedData;
113
+ }
114
+ if (!fs.existsSync(this.envFilePath)) {
115
+ this.log?.error(`EnvFileSessionStore: .env file not found: ${this.envFilePath}`);
116
+ return null;
117
+ }
118
+ try {
119
+ const content = fs.readFileSync(this.envFilePath, 'utf8');
120
+ const envVars = this.parseEnvContent(content);
121
+ // Validate required fields
122
+ if (!envVars.SAP_URL) {
123
+ this.log?.error(`EnvFileSessionStore: .env file missing SAP_URL`);
124
+ return null;
125
+ }
126
+ const authType = (envVars.SAP_AUTH_TYPE || 'basic');
127
+ const data = {
128
+ serviceUrl: envVars.SAP_URL,
129
+ sapClient: envVars.SAP_CLIENT,
130
+ authType,
131
+ };
132
+ if (authType === 'basic') {
133
+ if (!envVars.SAP_USERNAME || !envVars.SAP_PASSWORD) {
134
+ this.log?.error(`EnvFileSessionStore: .env file missing SAP_USERNAME or SAP_PASSWORD for basic auth`);
135
+ return null;
136
+ }
137
+ data.username = envVars.SAP_USERNAME;
138
+ data.password = envVars.SAP_PASSWORD;
139
+ }
140
+ else if (authType === 'jwt') {
141
+ if (!envVars.SAP_JWT_TOKEN) {
142
+ this.log?.error(`EnvFileSessionStore: .env file missing SAP_JWT_TOKEN for JWT auth`);
143
+ return null;
144
+ }
145
+ data.jwtToken = envVars.SAP_JWT_TOKEN;
146
+ data.refreshToken = envVars.SAP_REFRESH_TOKEN;
147
+ data.uaaUrl = envVars.SAP_UAA_URL;
148
+ data.uaaClientId = envVars.SAP_UAA_CLIENT_ID;
149
+ data.uaaClientSecret = envVars.SAP_UAA_CLIENT_SECRET;
150
+ }
151
+ this.loadedData = data;
152
+ this.log?.debug(`EnvFileSessionStore: loaded .env file`, {
153
+ serviceUrl: data.serviceUrl,
154
+ authType: data.authType
155
+ });
156
+ return data;
157
+ }
158
+ catch (error) {
159
+ this.log?.error(`EnvFileSessionStore: failed to read .env file`, {
160
+ error: error instanceof Error ? error.message : String(error)
161
+ });
162
+ return null;
163
+ }
164
+ }
165
+ /**
166
+ * Convert internal format to IConfig
167
+ */
168
+ toIConfig(data) {
169
+ const config = {
170
+ serviceUrl: data.serviceUrl,
171
+ sapClient: data.sapClient,
172
+ authType: data.authType,
173
+ };
174
+ if (data.authType === 'basic') {
175
+ config.username = data.username;
176
+ config.password = data.password;
177
+ }
178
+ else if (data.authType === 'jwt') {
179
+ config.authorizationToken = data.jwtToken;
180
+ config.refreshToken = data.refreshToken;
181
+ config.uaaUrl = data.uaaUrl;
182
+ config.uaaClientId = data.uaaClientId;
183
+ config.uaaClientSecret = data.uaaClientSecret;
184
+ }
185
+ return config;
186
+ }
187
+ // ============================================================================
188
+ // ISessionStore implementation
189
+ // ============================================================================
190
+ async loadSession(destination) {
191
+ // Check in-memory updates first (for token refresh)
192
+ const updated = this.inMemoryUpdates.get(destination);
193
+ if (updated) {
194
+ return this.toIConfig(updated);
195
+ }
196
+ // Load from .env file
197
+ const data = this.loadEnvFile();
198
+ if (!data)
199
+ return null;
200
+ return this.toIConfig(data);
201
+ }
202
+ async saveSession(destination, config) {
203
+ // Save to in-memory only (don't overwrite .env file)
204
+ const data = {
205
+ serviceUrl: config.serviceUrl || this.loadedData?.serviceUrl || '',
206
+ sapClient: config.sapClient || this.loadedData?.sapClient,
207
+ authType: config.authType || this.loadedData?.authType || 'basic',
208
+ username: config.username,
209
+ password: config.password,
210
+ jwtToken: config.authorizationToken,
211
+ refreshToken: config.refreshToken,
212
+ uaaUrl: config.uaaUrl,
213
+ uaaClientId: config.uaaClientId,
214
+ uaaClientSecret: config.uaaClientSecret,
215
+ };
216
+ this.inMemoryUpdates.set(destination, data);
217
+ this.log?.debug(`EnvFileSessionStore: saved session to memory`, { destination });
218
+ }
219
+ async getConnectionConfig(destination) {
220
+ // Check in-memory updates first
221
+ const updated = this.inMemoryUpdates.get(destination);
222
+ if (updated) {
223
+ return {
224
+ serviceUrl: updated.serviceUrl,
225
+ sapClient: updated.sapClient,
226
+ authType: updated.authType,
227
+ username: updated.username,
228
+ password: updated.password,
229
+ authorizationToken: updated.jwtToken,
230
+ };
231
+ }
232
+ const data = this.loadEnvFile();
233
+ if (!data)
234
+ return null;
235
+ return {
236
+ serviceUrl: data.serviceUrl,
237
+ sapClient: data.sapClient,
238
+ authType: data.authType,
239
+ username: data.username,
240
+ password: data.password,
241
+ authorizationToken: data.jwtToken,
242
+ };
243
+ }
244
+ async setConnectionConfig(destination, config) {
245
+ // Store in memory (merge with existing data)
246
+ const existing = this.inMemoryUpdates.get(destination) || this.loadEnvFile() || {};
247
+ const data = {
248
+ ...existing,
249
+ serviceUrl: config.serviceUrl || existing.serviceUrl,
250
+ sapClient: config.sapClient || existing.sapClient,
251
+ authType: config.authType || existing.authType || 'basic',
252
+ username: config.username || existing.username,
253
+ password: config.password || existing.password,
254
+ jwtToken: config.authorizationToken || existing.jwtToken,
255
+ };
256
+ this.inMemoryUpdates.set(destination, data);
257
+ this.log?.debug(`EnvFileSessionStore: set connection config`, { destination, serviceUrl: data.serviceUrl });
258
+ }
259
+ async getAuthorizationConfig(destination) {
260
+ // Check in-memory updates first
261
+ const updated = this.inMemoryUpdates.get(destination);
262
+ if (updated && updated.authType === 'jwt') {
263
+ return {
264
+ uaaUrl: updated.uaaUrl || '',
265
+ uaaClientId: updated.uaaClientId || '',
266
+ uaaClientSecret: updated.uaaClientSecret || '',
267
+ };
268
+ }
269
+ const data = this.loadEnvFile();
270
+ if (!data || data.authType !== 'jwt')
271
+ return null;
272
+ return {
273
+ uaaUrl: data.uaaUrl || '',
274
+ uaaClientId: data.uaaClientId || '',
275
+ uaaClientSecret: data.uaaClientSecret || '',
276
+ };
277
+ }
278
+ async setAuthorizationConfig(destination, config) {
279
+ const existing = this.inMemoryUpdates.get(destination) || this.loadEnvFile() || {};
280
+ const data = {
281
+ ...existing,
282
+ serviceUrl: existing.serviceUrl || '',
283
+ authType: existing.authType || 'jwt',
284
+ uaaUrl: config.uaaUrl,
285
+ uaaClientId: config.uaaClientId,
286
+ uaaClientSecret: config.uaaClientSecret,
287
+ };
288
+ this.inMemoryUpdates.set(destination, data);
289
+ this.log?.debug(`EnvFileSessionStore: set authorization config`, { destination });
290
+ }
291
+ async getToken(destination) {
292
+ // Check in-memory updates first (refreshed token)
293
+ const updated = this.inMemoryUpdates.get(destination);
294
+ if (updated?.jwtToken) {
295
+ return updated.jwtToken;
296
+ }
297
+ const data = this.loadEnvFile();
298
+ return data?.jwtToken;
299
+ }
300
+ async setToken(destination, token) {
301
+ const existing = this.inMemoryUpdates.get(destination) || this.loadEnvFile() || {};
302
+ const data = {
303
+ ...existing,
304
+ serviceUrl: existing.serviceUrl || '',
305
+ authType: 'jwt',
306
+ jwtToken: token,
307
+ };
308
+ this.inMemoryUpdates.set(destination, data);
309
+ this.log?.debug(`EnvFileSessionStore: set token`, { destination });
310
+ }
311
+ async getRefreshToken(destination) {
312
+ const updated = this.inMemoryUpdates.get(destination);
313
+ if (updated?.refreshToken) {
314
+ return updated.refreshToken;
315
+ }
316
+ const data = this.loadEnvFile();
317
+ return data?.refreshToken;
318
+ }
319
+ async setRefreshToken(destination, refreshToken) {
320
+ const existing = this.inMemoryUpdates.get(destination) || this.loadEnvFile() || {};
321
+ const data = {
322
+ ...existing,
323
+ serviceUrl: existing.serviceUrl || '',
324
+ authType: 'jwt',
325
+ refreshToken,
326
+ };
327
+ this.inMemoryUpdates.set(destination, data);
328
+ this.log?.debug(`EnvFileSessionStore: set refresh token`, { destination });
329
+ }
330
+ /**
331
+ * Clear in-memory updates (useful for testing)
332
+ */
333
+ clear() {
334
+ this.inMemoryUpdates.clear();
335
+ this.loadedData = null;
336
+ }
337
+ }
338
+ exports.EnvFileSessionStore = EnvFileSessionStore;
@@ -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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-abap-adt/auth-stores",
3
- "version": "0.2.3",
3
+ "version": "0.2.6",
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.17",
52
+ "@mcp-abap-adt/interfaces": "^0.2.3",
53
53
  "dotenv": "^17.2.1"
54
54
  },
55
55
  "devDependencies": {