@mcp-abap-adt/auth-providers 0.1.3 → 0.1.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,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.5] - 2025-12-13
11
+
12
+ ### Changed
13
+ - Dependency bump: `@mcp-abap-adt/interfaces` to `^0.1.16` to align with latest interfaces release
14
+
15
+ ## [0.1.4] - 2025-12-08
16
+
17
+ ### Added
18
+ - **Integration Tests for browserAuth**: Added real integration test that uses actual service keys and OAuth flow
19
+ - Test verifies token retrieval with real credentials from service keys
20
+ - Shows all authentication stages with logging when `DEBUG_AUTH_PROVIDERS=true` is set
21
+ - Tests both access token and refresh token retrieval
22
+ - **Test Logger Helper**: Added `createTestLogger` helper for tests with environment variable control
23
+ - Supports log levels (debug, info, warn, error) via `LOG_LEVEL` environment variable
24
+ - Only outputs logs when `DEBUG_AUTH_PROVIDERS=true` or `DEBUG_BROWSER_AUTH=true` is set
25
+ - Provides clean, controlled logging for test scenarios
26
+
27
+ ### Changed
28
+ - **Improved Logging in browserAuth**: Made logging more concise and informative
29
+ - All log messages are now single-line strings without verbose objects
30
+ - Logs show key information: what we send, what we receive, token lengths
31
+ - Example: `Tokens received: accessToken(2263 chars), refreshToken(34 chars)`
32
+ - Logging only works when logger is provided (no default console output)
33
+ - **exchangeCodeForToken Function**: Exported for testing purposes
34
+ - Function is marked as `@internal` but exported to enable unit testing
35
+ - Allows testing token exchange logic without full browser auth flow
36
+
37
+ ### Fixed
38
+ - **Test Error Logging**: Fixed error test to use mock logger without console output
39
+ - Error test no longer pollutes console with error messages
40
+ - Still verifies that error logging occurs via spy
41
+
10
42
  ## [0.1.3] - 2025-12-07
11
43
 
12
44
  ### Added
@@ -107,4 +139,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
107
139
  - `@mcp-abap-adt/auth-stores` ^0.1.2 - Store implementations
108
140
  - `@mcp-abap-adt/connection` ^0.1.13 - Connection utilities
109
141
  - `@mcp-abap-adt/logger` ^0.1.0 - Logging utilities
110
-
package/README.md CHANGED
@@ -244,6 +244,32 @@ Integration tests will skip if `test-config.yaml` is not configured or contains
244
244
  - ABAP integration tests use `abap.destination` and require `AbapServiceKeyStore`/`AbapSessionStore` (with `sapUrl`)
245
245
  - BTP/ABAP integration tests may open a browser for authentication if no refresh token is available. This is expected behavior.
246
246
 
247
+ ### Debug Logging
248
+
249
+ To enable detailed logging during tests or runtime, set environment variables:
250
+
251
+ ```bash
252
+ # Enable logging for auth providers
253
+ DEBUG_AUTH_PROVIDERS=true npm test
254
+
255
+ # Or enable browser auth specific logging
256
+ DEBUG_BROWSER_AUTH=true npm test
257
+
258
+ # Set log level (debug, info, warn, error)
259
+ LOG_LEVEL=debug npm test
260
+ ```
261
+
262
+ Logging shows:
263
+ - Token exchange stages (what we send, what we receive)
264
+ - Token information (lengths, previews)
265
+ - Errors with details
266
+
267
+ Example output:
268
+ ```
269
+ [INTEGRATION] Exchanging code for token: https://.../oauth/token
270
+ [INTEGRATION] Tokens received: accessToken(2263 chars), refreshToken(34 chars)
271
+ ```
272
+
247
273
  ## Dependencies
248
274
 
249
275
  - `@mcp-abap-adt/auth-broker` (^0.1.6) - Interface definitions
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Test logger with environment variable control
3
+ */
4
+ import type { ILogger } from '@mcp-abap-adt/interfaces';
5
+ export declare function createTestLogger(prefix?: string): ILogger;
6
+ //# sourceMappingURL=testLogger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testLogger.d.ts","sourceRoot":"","sources":["../../../src/__tests__/helpers/testLogger.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAgBxD,wBAAgB,gBAAgB,CAAC,MAAM,GAAE,MAAe,GAAG,OAAO,CA4BjE"}
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ /**
3
+ * Test logger with environment variable control
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createTestLogger = createTestLogger;
7
+ function getLogLevel() {
8
+ const level = process.env.LOG_LEVEL?.toLowerCase() || 'info';
9
+ const levels = ['debug', 'info', 'warn', 'error'];
10
+ return levels.includes(level) ? level : 'info';
11
+ }
12
+ function shouldLog(level) {
13
+ const currentLevel = getLogLevel();
14
+ const levels = ['debug', 'info', 'warn', 'error'];
15
+ return levels.indexOf(level) >= levels.indexOf(currentLevel);
16
+ }
17
+ function createTestLogger(prefix = 'TEST') {
18
+ const level = getLogLevel();
19
+ const enabled = process.env.DEBUG_AUTH_PROVIDERS === 'true' ||
20
+ process.env.DEBUG_BROWSER_AUTH === 'true' ||
21
+ process.env.DEBUG === 'true';
22
+ return {
23
+ debug: (message, meta) => {
24
+ if (enabled && shouldLog('debug')) {
25
+ console.debug(`[${prefix}] [DEBUG] ${message}`, meta || '');
26
+ }
27
+ },
28
+ info: (message, meta) => {
29
+ if (enabled && shouldLog('info')) {
30
+ console.info(`[${prefix}] ${message}`, meta || '');
31
+ }
32
+ },
33
+ warn: (message, meta) => {
34
+ if (enabled && shouldLog('warn')) {
35
+ console.warn(`[${prefix}] [WARN] ${message}`, meta || '');
36
+ }
37
+ },
38
+ error: (message, meta) => {
39
+ if (enabled && shouldLog('error')) {
40
+ console.error(`[${prefix}] [ERROR] ${message}`, meta || '');
41
+ }
42
+ },
43
+ };
44
+ }
@@ -2,6 +2,14 @@
2
2
  * Browser authentication - OAuth2 flow for obtaining tokens
3
3
  */
4
4
  import type { IAuthorizationConfig, ILogger } from '@mcp-abap-adt/interfaces';
5
+ /**
6
+ * Exchange authorization code for tokens
7
+ * @internal - Exported for testing
8
+ */
9
+ export declare function exchangeCodeForToken(authConfig: IAuthorizationConfig, code: string, port?: number, log?: ILogger | null): Promise<{
10
+ accessToken: string;
11
+ refreshToken?: string;
12
+ }>;
5
13
  /**
6
14
  * Start browser authentication flow
7
15
  * @param authConfig Authorization configuration with UAA credentials
@@ -1 +1 @@
1
- {"version":3,"file":"browserAuth.d.ts","sourceRoot":"","sources":["../../src/auth/browserAuth.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,KAAK,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAkF9E;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,oBAAoB,EAChC,OAAO,GAAE,MAAiB,EAC1B,MAAM,CAAC,EAAE,OAAO,EAChB,IAAI,GAAE,MAAa,GAClB,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAkSzD"}
1
+ {"version":3,"file":"browserAuth.d.ts","sourceRoot":"","sources":["../../src/auth/browserAuth.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,KAAK,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAyB9E;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,oBAAoB,EAChC,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,MAAa,EACnB,GAAG,CAAC,EAAE,OAAO,GAAG,IAAI,GACnB,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAsCzD;AAaD;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,oBAAoB,EAChC,OAAO,GAAE,MAAiB,EAC1B,MAAM,CAAC,EAAE,OAAO,EAChB,IAAI,GAAE,MAAa,GAClB,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAuSzD"}
@@ -39,6 +39,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
39
39
  return (mod && mod.__esModule) ? mod : { "default": mod };
40
40
  };
41
41
  Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.exchangeCodeForToken = exchangeCodeForToken;
42
43
  exports.startBrowserAuth = startBrowserAuth;
43
44
  const http = __importStar(require("http"));
44
45
  const child_process = __importStar(require("child_process"));
@@ -65,8 +66,9 @@ function getJwtAuthorizationUrl(authConfig, port = 3001) {
65
66
  }
66
67
  /**
67
68
  * Exchange authorization code for tokens
69
+ * @internal - Exported for testing
68
70
  */
69
- async function exchangeCodeForToken(authConfig, code, port = 3001) {
71
+ async function exchangeCodeForToken(authConfig, code, port = 3001, log) {
70
72
  const { uaaUrl: url, uaaClientId: clientid, uaaClientSecret: clientsecret } = authConfig;
71
73
  const tokenUrl = `${url}/oauth/token`;
72
74
  const redirectUri = `http://localhost:${port}/callback`;
@@ -75,6 +77,7 @@ async function exchangeCodeForToken(authConfig, code, port = 3001) {
75
77
  params.append('code', code);
76
78
  params.append('redirect_uri', redirectUri);
77
79
  const authString = Buffer.from(`${clientid}:${clientsecret}`).toString('base64');
80
+ log?.info(`Exchanging code for token: ${tokenUrl}`);
78
81
  const response = await (0, axios_1.default)({
79
82
  method: 'post',
80
83
  url: tokenUrl,
@@ -85,25 +88,29 @@ async function exchangeCodeForToken(authConfig, code, port = 3001) {
85
88
  data: params.toString(),
86
89
  });
87
90
  if (response.data && response.data.access_token) {
91
+ const accessToken = response.data.access_token;
92
+ const refreshToken = response.data.refresh_token;
93
+ log?.info(`Tokens received: accessToken(${accessToken.length} chars), refreshToken(${refreshToken?.length || 0} chars)`);
88
94
  return {
89
- accessToken: response.data.access_token,
90
- refreshToken: response.data.refresh_token,
95
+ accessToken,
96
+ refreshToken,
91
97
  };
92
98
  }
93
99
  else {
100
+ log?.error(`Token exchange failed: status ${response.status}, error: ${response.data?.error || 'unknown'}`);
94
101
  throw new Error('Response does not contain access_token');
95
102
  }
96
103
  }
97
104
  /**
98
- * Default logger implementation
105
+ * Check if debug logging is enabled for auth providers
99
106
  */
100
- const defaultLogger = {
101
- info: (msg) => console.info(msg),
102
- debug: (msg) => console.debug(`[DEBUG] ${msg}`),
103
- error: (msg) => console.error(msg),
104
- browserUrl: (url) => console.info(`🔗 Open in browser: ${url}`),
105
- browserOpening: () => console.debug(`🌐 Opening browser for authentication...`),
106
- };
107
+ function isDebugEnabled() {
108
+ return process.env.DEBUG_AUTH_PROVIDERS === 'true' ||
109
+ process.env.DEBUG_BROWSER_AUTH === 'true' ||
110
+ process.env.DEBUG === 'true' ||
111
+ process.env.DEBUG?.includes('auth-providers') === true ||
112
+ process.env.DEBUG?.includes('browser-auth') === true;
113
+ }
107
114
  /**
108
115
  * Start browser authentication flow
109
116
  * @param authConfig Authorization configuration with UAA credentials
@@ -114,13 +121,8 @@ const defaultLogger = {
114
121
  * @internal - Internal function, not exported from package
115
122
  */
116
123
  async function startBrowserAuth(authConfig, browser = 'system', logger, port = 3001) {
117
- const log = logger ? {
118
- info: (msg) => logger.info(msg),
119
- debug: (msg) => logger.debug(msg),
120
- error: (msg) => logger.error(msg),
121
- browserUrl: (url) => logger.info(`🔗 Open in browser: ${url}`),
122
- browserOpening: () => logger.debug('🌐 Opening browser for authentication...'),
123
- } : defaultLogger;
124
+ // Use logger if provided, otherwise null (no logging)
125
+ const log = logger || null;
124
126
  return new Promise((originalResolve, originalReject) => {
125
127
  let timeoutId = null;
126
128
  const resolve = (value) => {
@@ -141,9 +143,12 @@ async function startBrowserAuth(authConfig, browser = 'system', logger, port = 3
141
143
  // OAuth2 callback handler
142
144
  app.get('/callback', async (req, res) => {
143
145
  try {
146
+ log?.info(`Callback received: ${req.url}`);
147
+ log?.debug(`Callback query: ${JSON.stringify(req.query)}`);
144
148
  // Check for OAuth2 error parameters
145
149
  const { error, error_description, error_uri } = req.query;
146
150
  if (error) {
151
+ log?.error(`Callback error: ${error}${error_description ? ` - ${error_description}` : ''}`);
147
152
  const errorMsg = error_description
148
153
  ? `${error}: ${error_description}`
149
154
  : String(error);
@@ -214,10 +219,13 @@ async function startBrowserAuth(authConfig, browser = 'system', logger, port = 3
214
219
  return reject(new Error(`OAuth2 authentication failed: ${errorMsg}${error_uri ? ` (${error_uri})` : ''}`));
215
220
  }
216
221
  const { code } = req.query;
222
+ log?.debug(`Callback code received: ${code ? 'yes' : 'no'}`);
217
223
  if (!code || typeof code !== 'string') {
224
+ log?.error(`Callback code missing`);
218
225
  res.status(400).send('Error: Authorization code missing');
219
226
  return reject(new Error('Authorization code missing'));
220
227
  }
228
+ log?.debug(`Exchanging code for token`);
221
229
  // Send success page
222
230
  const html = `<!DOCTYPE html>
223
231
  <html lang="en">
@@ -280,7 +288,8 @@ async function startBrowserAuth(authConfig, browser = 'system', logger, port = 3
280
288
  res.send(html);
281
289
  // Exchange code for tokens and close server
282
290
  try {
283
- const tokens = await exchangeCodeForToken(authConfig, code, PORT);
291
+ const tokens = await exchangeCodeForToken(authConfig, code, PORT, log);
292
+ log?.info(`Tokens received: accessToken(${tokens.accessToken?.length || 0} chars), refreshToken(${tokens.refreshToken?.length || 0} chars)`);
284
293
  // Close all connections and server immediately after getting tokens
285
294
  if (typeof server.closeAllConnections === 'function') {
286
295
  server.closeAllConnections();
@@ -311,7 +320,7 @@ async function startBrowserAuth(authConfig, browser = 'system', logger, port = 3
311
320
  serverInstance = server.listen(PORT, async () => {
312
321
  const browserApp = BROWSER_MAP[browser];
313
322
  if (!browser || browser === 'none' || browserApp === null) {
314
- log.browserUrl(authorizationUrl);
323
+ log?.info(`🔗 Open in browser: ${authorizationUrl}`, { url: authorizationUrl });
315
324
  // For 'none' browser, don't wait for callback - throw error immediately
316
325
  // User must open browser manually and we can't wait for callback in automated tests
317
326
  if (serverInstance) {
@@ -323,7 +332,7 @@ async function startBrowserAuth(authConfig, browser = 'system', logger, port = 3
323
332
  return;
324
333
  }
325
334
  else {
326
- log.browserOpening();
335
+ log?.debug('🌐 Opening browser for authentication...');
327
336
  try {
328
337
  // Try dynamic import first (for ES modules)
329
338
  let open;
@@ -368,7 +377,7 @@ async function startBrowserAuth(authConfig, browser = 'system', logger, port = 3
368
377
  // Use child_process as fallback (non-blocking)
369
378
  child_process.exec(`${command} "${authorizationUrl}"`, (error) => {
370
379
  if (error) {
371
- log.error(`❌ Failed to open browser: ${error.message}. Please open manually: ${authorizationUrl}`);
380
+ log?.error(`❌ Failed to open browser: ${error.message}. Please open manually: ${authorizationUrl}`, { error: error.message, url: authorizationUrl });
372
381
  }
373
382
  });
374
383
  return; // Exit early since we're using child_process (non-blocking)
@@ -383,8 +392,8 @@ async function startBrowserAuth(authConfig, browser = 'system', logger, port = 3
383
392
  }
384
393
  catch (error) {
385
394
  // If browser cannot be opened, show URL and throw error for consumer to catch
386
- log.error(`❌ Failed to open browser: ${error?.message || String(error)}. Please open manually: ${authorizationUrl}`);
387
- log.browserUrl(authorizationUrl);
395
+ log?.error(`❌ Failed to open browser: ${error?.message || String(error)}. Please open manually: ${authorizationUrl}`, { error: error?.message || String(error), url: authorizationUrl });
396
+ log?.info(`🔗 Open in browser: ${authorizationUrl}`, { url: authorizationUrl });
388
397
  // Throw error so consumer can distinguish this from "service key missing" error
389
398
  reject(new Error(`Browser opening failed for destination authentication. Please open manually: ${authorizationUrl}`));
390
399
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-abap-adt/auth-providers",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Token providers for MCP ABAP ADT auth-broker - XSUAA and BTP token providers",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -48,7 +48,7 @@
48
48
  "node": ">=18.0.0"
49
49
  },
50
50
  "dependencies": {
51
- "@mcp-abap-adt/interfaces": "^0.1.1",
51
+ "@mcp-abap-adt/interfaces": "^0.1.16",
52
52
  "axios": "^1.11.0",
53
53
  "express": "^5.1.0",
54
54
  "open": "^11.0.0"