@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 +32 -1
- package/README.md +26 -0
- package/dist/__tests__/helpers/testLogger.d.ts +6 -0
- package/dist/__tests__/helpers/testLogger.d.ts.map +1 -0
- package/dist/__tests__/helpers/testLogger.js +44 -0
- package/dist/auth/browserAuth.d.ts +8 -0
- package/dist/auth/browserAuth.d.ts.map +1 -1
- package/dist/auth/browserAuth.js +33 -24
- package/package.json +2 -2
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 @@
|
|
|
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;
|
|
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"}
|
package/dist/auth/browserAuth.js
CHANGED
|
@@ -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
|
|
90
|
-
refreshToken
|
|
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
|
-
*
|
|
105
|
+
* Check if debug logging is enabled for auth providers
|
|
99
106
|
*/
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
118
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
387
|
-
log
|
|
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
|
+
"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.
|
|
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"
|