@mcp-abap-adt/auth-providers 0.2.8 → 0.2.10
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 +56 -0
- package/README.md +24 -3
- package/dist/__tests__/helpers/testLogger.d.ts +1 -0
- package/dist/__tests__/helpers/testLogger.d.ts.map +1 -1
- package/dist/__tests__/helpers/testLogger.js +23 -22
- package/dist/auth/browserAuth.d.ts.map +1 -1
- package/dist/auth/browserAuth.js +41 -10
- package/dist/providers/AuthorizationCodeProvider.d.ts.map +1 -1
- package/dist/providers/AuthorizationCodeProvider.js +35 -8
- package/dist/providers/BaseTokenProvider.d.ts +10 -0
- package/dist/providers/BaseTokenProvider.d.ts.map +1 -1
- package/dist/providers/BaseTokenProvider.js +51 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,62 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.10] - 2025-12-25
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- **Logging Improvements**: Enhanced logging for better debugging and readability
|
|
14
|
+
- **Date Formatting**: Expiration dates now displayed in readable format (`YYYY-MM-DD HH:MM:SS UTC`) instead of ISO format (`2025-12-25T11:08:15.000Z`)
|
|
15
|
+
- **Token Formatting**: Tokens are logged in truncated format (start...end) for security and readability
|
|
16
|
+
- **Browser Information**: Added logging of browser type and authorization URL before starting browser authentication
|
|
17
|
+
- **Token Lifecycle**: Improved logging of token acquisition, validation, and refresh operations with formatted dates
|
|
18
|
+
- **Structured Logging**: Replaced `console.log/info/warn/error` with `DefaultLogger` from `@mcp-abap-adt/logger` in test helpers
|
|
19
|
+
- Proper formatting with icons and level prefixes (ℹ️, 🐛, ⚠️, ❌)
|
|
20
|
+
- Respects `LOG_LEVEL` or `AUTH_LOG_LEVEL` environment variable
|
|
21
|
+
- Consistent logging format across all packages
|
|
22
|
+
- **Environment Variable Names**: Added short names for debug flags (backward compatible)
|
|
23
|
+
- `DEBUG_PROVIDER=true` (short) or `DEBUG_AUTH_PROVIDERS=true` (long)
|
|
24
|
+
- Both names are supported for backward compatibility
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
- **Browser Auth Timeout**: Added 30-second timeout for browser-based authentication
|
|
28
|
+
- Prevents provider from blocking consumer indefinitely when user doesn't complete authentication
|
|
29
|
+
- Timeout error is thrown if authentication is not completed within 30 seconds
|
|
30
|
+
- Helps prevent hanging in automated tests and CI/CD environments
|
|
31
|
+
- **Logger Package Dependency**: Added `@mcp-abap-adt/logger` to devDependencies
|
|
32
|
+
- Required for `DefaultLogger` and `getLogLevel()` utilities in test helpers
|
|
33
|
+
- Added `pino` and `pino-pretty` to devDependencies to support PinoLogger initialization
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
- **Test Hanging Issues**: Fixed Jest tests hanging after completion
|
|
37
|
+
- Added `forceExit: true` to Jest configuration to force exit after tests complete
|
|
38
|
+
- Improved cleanup of HTTP server and timers to prevent open handles
|
|
39
|
+
- Added protection against double execution of server close handlers
|
|
40
|
+
- Proper cleanup of `finishTimeoutId` timer in all scenarios (success, error, timeout)
|
|
41
|
+
- Server now resolves promise only after fully closing to ensure Jest can exit cleanly
|
|
42
|
+
|
|
43
|
+
## [0.2.9] - 2025-12-25
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
- **Logging Improvements**: Enhanced logging for better debugging and readability
|
|
47
|
+
- **Date Formatting**: Expiration dates now displayed in readable format (`YYYY-MM-DD HH:MM:SS UTC`) instead of ISO format (`2025-12-25T11:08:15.000Z`)
|
|
48
|
+
- **Token Formatting**: Tokens are logged in truncated format (start...end) for security and readability
|
|
49
|
+
- **Browser Information**: Added logging of browser type and authorization URL before starting browser authentication
|
|
50
|
+
- **Token Lifecycle**: Improved logging of token acquisition, validation, and refresh operations with formatted dates
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
- **Browser Auth Timeout**: Added 30-second timeout for browser-based authentication
|
|
54
|
+
- Prevents provider from blocking consumer indefinitely when user doesn't complete authentication
|
|
55
|
+
- Timeout error is thrown if authentication is not completed within 30 seconds
|
|
56
|
+
- Helps prevent hanging in automated tests and CI/CD environments
|
|
57
|
+
|
|
58
|
+
### Fixed
|
|
59
|
+
- **Test Hanging Issues**: Fixed Jest tests hanging after completion
|
|
60
|
+
- Added `forceExit: true` to Jest configuration to force exit after tests complete
|
|
61
|
+
- Improved cleanup of HTTP server and timers to prevent open handles
|
|
62
|
+
- Added protection against double execution of server close handlers
|
|
63
|
+
- Proper cleanup of `finishTimeoutId` timer in all scenarios (success, error, timeout)
|
|
64
|
+
- Server now resolves promise only after fully closing to ensure Jest can exit cleanly
|
|
65
|
+
|
|
10
66
|
## [0.2.8] - 2025-12-24
|
|
11
67
|
|
|
12
68
|
### Changed
|
package/README.md
CHANGED
|
@@ -207,7 +207,9 @@ const result = await provider.getTokens();
|
|
|
207
207
|
// result.refreshToken is undefined (client_credentials doesn't provide refresh tokens)
|
|
208
208
|
```
|
|
209
209
|
|
|
210
|
-
**Note**: The `browserAuthPort` parameter (default: 3001) configures the OAuth callback server port. If the requested port is already in use, an error will be thrown. You must specify a different port or free the port before starting authentication. The server properly closes all connections and frees the port after authentication completes, ensuring no lingering port occupation.
|
|
210
|
+
**Note**: The `browserAuthPort` parameter (default: 3001) configures the OAuth callback server port. If the requested port is already in use, an error will be thrown. You must specify a different port or free the port before starting authentication. The server properly closes all connections and frees the port after authentication completes, ensuring no lingering port occupation.
|
|
211
|
+
|
|
212
|
+
**Timeout**: Browser authentication has a 30-second timeout to prevent blocking the consumer. If authentication is not completed within 30 seconds, the operation will fail with a timeout error. This prevents the provider from hanging indefinitely when the user doesn't complete authentication.
|
|
211
213
|
|
|
212
214
|
**Process Termination Handling**: The OAuth callback server registers cleanup handlers for `SIGTERM`, `SIGINT`, `SIGHUP`, and `exit` signals. This ensures ports are properly freed even when MCP clients (like Cline) terminate the process before authentication completes. This is especially important for stdio servers where the client may kill the process at any time. On Windows, the `SIGBREAK` signal (Ctrl+Break) is also handled.
|
|
213
215
|
|
|
@@ -368,9 +370,20 @@ Integration tests will skip if `test-config.yaml` is not configured or contains
|
|
|
368
370
|
To enable detailed logging during tests or runtime, set environment variables:
|
|
369
371
|
|
|
370
372
|
```bash
|
|
371
|
-
# Enable logging for auth providers
|
|
373
|
+
# Enable logging for auth providers (short name)
|
|
372
374
|
DEBUG_PROVIDER=true npm test
|
|
373
375
|
|
|
376
|
+
# Or use long name (backward compatibility)
|
|
377
|
+
DEBUG_AUTH_PROVIDERS=true npm test
|
|
378
|
+
|
|
379
|
+
# Or enable via general DEBUG variable
|
|
380
|
+
DEBUG=true npm test
|
|
381
|
+
|
|
382
|
+
# Or include in DEBUG list
|
|
383
|
+
DEBUG=provider npm test
|
|
384
|
+
# Or
|
|
385
|
+
DEBUG=auth-providers npm test
|
|
386
|
+
|
|
374
387
|
# Set log level (debug, info, warn, error)
|
|
375
388
|
LOG_LEVEL=debug npm test
|
|
376
389
|
```
|
|
@@ -385,9 +398,17 @@ Example output:
|
|
|
385
398
|
```
|
|
386
399
|
[INFO] ℹ️ [browserAuth] Exchanging code for token...
|
|
387
400
|
[INFO] ℹ️ Tokens received: accessToken(2263 chars), refreshToken(34 chars)
|
|
388
|
-
[DEBUG] 🐛 [BaseTokenProvider] Token validation check {"expiresAt":"2025-12-
|
|
401
|
+
[DEBUG] 🐛 [BaseTokenProvider] Token validation check {"expiresAt":"2025-12-25 11:08:15 UTC","isValid":true}
|
|
402
|
+
[INFO] ℹ️ [browserAuth] Authorization URL: https://.../oauth/authorize?...
|
|
403
|
+
[INFO] ℹ️ [browserAuth] Browser: system
|
|
389
404
|
```
|
|
390
405
|
|
|
406
|
+
**Logging Features**:
|
|
407
|
+
- **Token Formatting**: Tokens are logged in truncated format (start...end) for security
|
|
408
|
+
- **Date Formatting**: Expiration dates are displayed in readable format (YYYY-MM-DD HH:MM:SS UTC) instead of ISO format
|
|
409
|
+
- **Browser Information**: Logs browser type and authorization URL for debugging
|
|
410
|
+
- **Token Lifecycle**: Detailed logging of token acquisition, validation, and refresh operations
|
|
411
|
+
|
|
391
412
|
## Dependencies
|
|
392
413
|
|
|
393
414
|
- `@mcp-abap-adt/interfaces` (^0.2.2) - Interface definitions and error code constants
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testLogger.d.ts","sourceRoot":"","sources":["../../../src/__tests__/helpers/testLogger.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"testLogger.d.ts","sourceRoot":"","sources":["../../../src/__tests__/helpers/testLogger.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAGxD,wBAAgB,gBAAgB,CAAC,MAAM,GAAE,MAAe,GAAG,OAAO,CAwCjE"}
|
|
@@ -1,43 +1,44 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* Test logger with environment variable control
|
|
4
|
+
* Uses DefaultLogger from @mcp-abap-adt/logger for proper formatting
|
|
4
5
|
*/
|
|
5
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
7
|
exports.createTestLogger = createTestLogger;
|
|
7
|
-
|
|
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
|
-
}
|
|
8
|
+
const logger_1 = require("@mcp-abap-adt/logger");
|
|
17
9
|
function createTestLogger(prefix = 'TEST') {
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
process.env.
|
|
21
|
-
|
|
10
|
+
// Check if logging is enabled
|
|
11
|
+
const isEnabled = () => {
|
|
12
|
+
return (process.env.DEBUG_PROVIDER === 'true' ||
|
|
13
|
+
process.env.DEBUG_AUTH_PROVIDERS === 'true' ||
|
|
14
|
+
process.env.DEBUG_BROWSER_AUTH === 'true' ||
|
|
15
|
+
process.env.DEBUG === 'true' ||
|
|
16
|
+
process.env.DEBUG?.includes('provider') === true ||
|
|
17
|
+
process.env.DEBUG?.includes('auth-providers') === true);
|
|
18
|
+
};
|
|
19
|
+
// Create DefaultLogger with appropriate log level
|
|
20
|
+
// getLogLevel respects AUTH_LOG_LEVEL env var and defaults to INFO
|
|
21
|
+
const baseLogger = new logger_1.DefaultLogger((0, logger_1.getLogLevel)());
|
|
22
|
+
// Return wrapper that checks if logging is enabled
|
|
22
23
|
return {
|
|
23
24
|
debug: (message, meta) => {
|
|
24
|
-
if (
|
|
25
|
-
|
|
25
|
+
if (isEnabled()) {
|
|
26
|
+
baseLogger.debug(`[${prefix}] ${message}`, meta);
|
|
26
27
|
}
|
|
27
28
|
},
|
|
28
29
|
info: (message, meta) => {
|
|
29
|
-
if (
|
|
30
|
-
|
|
30
|
+
if (isEnabled()) {
|
|
31
|
+
baseLogger.info(`[${prefix}] ${message}`, meta);
|
|
31
32
|
}
|
|
32
33
|
},
|
|
33
34
|
warn: (message, meta) => {
|
|
34
|
-
if (
|
|
35
|
-
|
|
35
|
+
if (isEnabled()) {
|
|
36
|
+
baseLogger.warn(`[${prefix}] ${message}`, meta);
|
|
36
37
|
}
|
|
37
38
|
},
|
|
38
39
|
error: (message, meta) => {
|
|
39
|
-
if (
|
|
40
|
-
|
|
40
|
+
if (isEnabled()) {
|
|
41
|
+
baseLogger.error(`[${prefix}] ${message}`, meta);
|
|
41
42
|
}
|
|
42
43
|
},
|
|
43
44
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browserAuth.d.ts","sourceRoot":"","sources":["../../src/auth/browserAuth.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAI9E,KAAK,iBAAiB,GAAG,oBAAoB,GAAG;IAC9C,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AA8BF;;;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,CAgDzD;AA6BD;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,iBAAiB,EAC7B,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,
|
|
1
|
+
{"version":3,"file":"browserAuth.d.ts","sourceRoot":"","sources":["../../src/auth/browserAuth.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAI9E,KAAK,iBAAiB,GAAG,oBAAoB,GAAG;IAC9C,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AA8BF;;;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,CAgDzD;AA6BD;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,iBAAiB,EAC7B,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,CAqjBzD"}
|
package/dist/auth/browserAuth.js
CHANGED
|
@@ -146,7 +146,9 @@ async function startBrowserAuth(authConfig, browser = 'system', logger, port = 3
|
|
|
146
146
|
}
|
|
147
147
|
return new Promise((originalResolve, originalReject) => {
|
|
148
148
|
let timeoutId = null;
|
|
149
|
+
let finishTimeoutId = null;
|
|
149
150
|
let cleanupDone = false;
|
|
151
|
+
let resolved = false;
|
|
150
152
|
const app = (0, express_1.default)();
|
|
151
153
|
const server = http.createServer(app);
|
|
152
154
|
// Disable keep-alive to ensure connections close immediately
|
|
@@ -164,6 +166,10 @@ async function startBrowserAuth(authConfig, browser = 'system', logger, port = 3
|
|
|
164
166
|
clearTimeout(timeoutId);
|
|
165
167
|
timeoutId = null;
|
|
166
168
|
}
|
|
169
|
+
if (finishTimeoutId) {
|
|
170
|
+
clearTimeout(finishTimeoutId);
|
|
171
|
+
finishTimeoutId = null;
|
|
172
|
+
}
|
|
167
173
|
if (server) {
|
|
168
174
|
try {
|
|
169
175
|
if (typeof server.closeAllConnections === 'function') {
|
|
@@ -189,8 +195,17 @@ async function startBrowserAuth(authConfig, browser = 'system', logger, port = 3
|
|
|
189
195
|
}
|
|
190
196
|
};
|
|
191
197
|
const resolve = (value) => {
|
|
192
|
-
if (
|
|
198
|
+
if (resolved)
|
|
199
|
+
return; // Prevent double resolution
|
|
200
|
+
resolved = true;
|
|
201
|
+
if (timeoutId) {
|
|
193
202
|
clearTimeout(timeoutId);
|
|
203
|
+
timeoutId = null;
|
|
204
|
+
}
|
|
205
|
+
if (finishTimeoutId) {
|
|
206
|
+
clearTimeout(finishTimeoutId);
|
|
207
|
+
finishTimeoutId = null;
|
|
208
|
+
}
|
|
194
209
|
removeCleanupListeners();
|
|
195
210
|
originalResolve(value);
|
|
196
211
|
};
|
|
@@ -379,24 +394,40 @@ async function startBrowserAuth(authConfig, browser = 'system', logger, port = 3
|
|
|
379
394
|
log?.info(`[browserAuth] Starting token exchange...`);
|
|
380
395
|
const tokens = await exchangeCodeForToken(authConfig, code, PORT, log);
|
|
381
396
|
log?.info(`[browserAuth] Tokens received: accessToken(${tokens.accessToken?.length || 0} chars), refreshToken(${tokens.refreshToken?.length || 0} chars)`);
|
|
382
|
-
// Resolve promise FIRST - this allows test to continue immediately
|
|
383
|
-
log?.info(`[browserAuth] Resolving promise with tokens...`);
|
|
384
|
-
resolve(tokens);
|
|
385
|
-
log?.info(`[browserAuth] Promise resolved, sending response...`);
|
|
386
397
|
// Send success page (non-blocking, doesn't affect promise)
|
|
387
398
|
res.send(html);
|
|
388
399
|
log?.info(`[browserAuth] Response sent, waiting for finish...`);
|
|
389
400
|
// Close all connections and server after response is sent
|
|
390
|
-
|
|
401
|
+
// Resolve promise AFTER server is closed to prevent Jest from hanging
|
|
402
|
+
let serverClosing = false;
|
|
403
|
+
const closeServerAndResolve = () => {
|
|
404
|
+
if (serverClosing)
|
|
405
|
+
return; // Prevent double execution
|
|
406
|
+
serverClosing = true;
|
|
407
|
+
if (finishTimeoutId) {
|
|
408
|
+
clearTimeout(finishTimeoutId);
|
|
409
|
+
finishTimeoutId = null;
|
|
410
|
+
}
|
|
391
411
|
log?.info(`[browserAuth] Response finished, closing server...`);
|
|
392
412
|
if (typeof server.closeAllConnections === 'function') {
|
|
393
413
|
server.closeAllConnections();
|
|
394
414
|
}
|
|
415
|
+
// Wait for server to close before resolving to prevent Jest from hanging
|
|
395
416
|
server.close(() => {
|
|
396
417
|
// Server closed - port should be freed
|
|
397
418
|
log?.info(`[browserAuth] Server closed, port ${PORT} should be freed`);
|
|
419
|
+
// Resolve after server is fully closed
|
|
420
|
+
log?.info(`[browserAuth] Resolving promise with tokens...`);
|
|
421
|
+
resolve({
|
|
422
|
+
accessToken: tokens.accessToken,
|
|
423
|
+
refreshToken: tokens.refreshToken,
|
|
424
|
+
});
|
|
398
425
|
});
|
|
399
|
-
}
|
|
426
|
+
};
|
|
427
|
+
// Wait for response to finish, but add timeout to prevent hanging
|
|
428
|
+
res.once('finish', closeServerAndResolve);
|
|
429
|
+
// Fallback: if finish event doesn't fire within 1 second, close anyway
|
|
430
|
+
finishTimeoutId = setTimeout(closeServerAndResolve, 1000);
|
|
400
431
|
}
|
|
401
432
|
catch (error) {
|
|
402
433
|
if (typeof server.closeAllConnections === 'function') {
|
|
@@ -565,7 +596,7 @@ async function startBrowserAuth(authConfig, browser = 'system', logger, port = 3
|
|
|
565
596
|
}
|
|
566
597
|
}
|
|
567
598
|
});
|
|
568
|
-
// Timeout after
|
|
599
|
+
// Timeout after 30 seconds to prevent blocking consumer
|
|
569
600
|
timeoutId = setTimeout(() => {
|
|
570
601
|
if (serverInstance) {
|
|
571
602
|
if (typeof server.closeAllConnections === 'function') {
|
|
@@ -578,8 +609,8 @@ async function startBrowserAuth(authConfig, browser = 'system', logger, port = 3
|
|
|
578
609
|
log?.debug(`Server closed on timeout, port ${PORT} should be freed`);
|
|
579
610
|
});
|
|
580
611
|
}, 100);
|
|
581
|
-
reject(new Error('Authentication timeout.
|
|
612
|
+
reject(new Error('Authentication timeout after 30 seconds. Please try again.'));
|
|
582
613
|
}
|
|
583
|
-
},
|
|
614
|
+
}, 30 * 1000);
|
|
584
615
|
});
|
|
585
616
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthorizationCodeProvider.d.ts","sourceRoot":"","sources":["../../src/providers/AuthorizationCodeProvider.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAEV,OAAO,EACP,YAAY,EACZ,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAIlC,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,WAAW,+BAA+B;IAE9C,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IAGrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAG1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IAGtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IAGtB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;GAKG;AACH,qBAAa,yBAA0B,SAAQ,iBAAiB;IAC9D,OAAO,CAAC,MAAM,CAAkC;gBAEpC,MAAM,EAAE,+BAA+B;
|
|
1
|
+
{"version":3,"file":"AuthorizationCodeProvider.d.ts","sourceRoot":"","sources":["../../src/providers/AuthorizationCodeProvider.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAEV,OAAO,EACP,YAAY,EACZ,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAIlC,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,WAAW,+BAA+B;IAE9C,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IAGrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAG1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IAGtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IAGtB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;GAKG;AACH,qBAAa,yBAA0B,SAAQ,iBAAiB;IAC9D,OAAO,CAAC,MAAM,CAAkC;gBAEpC,MAAM,EAAE,+BAA+B;IA8D7C,SAAS,IAAI,OAAO,CAAC,YAAY,CAAC;IAIxC,SAAS,CAAC,WAAW,IAAI,eAAe;cAIxB,YAAY,IAAI,OAAO,CAAC,YAAY,CAAC;cA2ErC,cAAc,IAAI,OAAO,CAAC,YAAY,CAAC;CA0CxD"}
|
|
@@ -28,6 +28,8 @@ class AuthorizationCodeProvider extends BaseTokenProvider_1.BaseTokenProvider {
|
|
|
28
28
|
clientId: config.clientId,
|
|
29
29
|
hasAccessToken: !!config.accessToken,
|
|
30
30
|
hasRefreshToken: !!config.refreshToken,
|
|
31
|
+
accessToken: this.formatToken(config.accessToken),
|
|
32
|
+
refreshToken: this.formatToken(config.refreshToken),
|
|
31
33
|
browser: config.browser || 'none',
|
|
32
34
|
redirectPort: config.redirectPort || 3001,
|
|
33
35
|
});
|
|
@@ -53,15 +55,18 @@ class AuthorizationCodeProvider extends BaseTokenProvider_1.BaseTokenProvider {
|
|
|
53
55
|
// Parse expiration from JWT
|
|
54
56
|
this.expiresAt = this.parseExpirationFromJWT(config.accessToken);
|
|
55
57
|
this.logger?.info('[AuthorizationCodeProvider] Initialized with access token', {
|
|
58
|
+
accessToken: this.formatToken(config.accessToken),
|
|
56
59
|
hasExpiresAt: !!this.expiresAt,
|
|
57
60
|
expiresAt: this.expiresAt
|
|
58
|
-
?
|
|
61
|
+
? this.formatExpirationDate(this.expiresAt)
|
|
59
62
|
: undefined,
|
|
60
63
|
});
|
|
61
64
|
}
|
|
62
65
|
if (config.refreshToken) {
|
|
63
66
|
this.refreshToken = config.refreshToken;
|
|
64
|
-
this.logger?.info('[AuthorizationCodeProvider] Initialized with refresh token'
|
|
67
|
+
this.logger?.info('[AuthorizationCodeProvider] Initialized with refresh token', {
|
|
68
|
+
refreshToken: this.formatToken(config.refreshToken),
|
|
69
|
+
});
|
|
65
70
|
}
|
|
66
71
|
}
|
|
67
72
|
async getTokens() {
|
|
@@ -71,10 +76,6 @@ class AuthorizationCodeProvider extends BaseTokenProvider_1.BaseTokenProvider {
|
|
|
71
76
|
return interfaces_1.AUTH_TYPE_AUTHORIZATION_CODE;
|
|
72
77
|
}
|
|
73
78
|
async performLogin() {
|
|
74
|
-
this.logger?.info('[AuthorizationCodeProvider] Performing login via browser', {
|
|
75
|
-
redirectPort: this.config.redirectPort || 3001,
|
|
76
|
-
browser: this.config.browser || 'none',
|
|
77
|
-
});
|
|
78
79
|
// Build authorization config
|
|
79
80
|
// If authorizationUrl is provided, use it; otherwise startBrowserAuth will build it from uaaUrl + clientId
|
|
80
81
|
const authConfig = {
|
|
@@ -88,11 +89,34 @@ class AuthorizationCodeProvider extends BaseTokenProvider_1.BaseTokenProvider {
|
|
|
88
89
|
}
|
|
89
90
|
// Use provided browser or default to 'none' (prints URL to console)
|
|
90
91
|
const browser = this.config.browser || 'none';
|
|
91
|
-
const
|
|
92
|
-
|
|
92
|
+
const redirectPort = this.config.redirectPort || 3001;
|
|
93
|
+
// Build authorization URL for logging (same logic as in startBrowserAuth)
|
|
94
|
+
const authorizationUrl = authConfig.authorizationUrl ??
|
|
95
|
+
`${authConfig.uaaUrl}/oauth/authorize?client_id=${encodeURIComponent(authConfig.uaaClientId)}&redirect_uri=${encodeURIComponent(`http://localhost:${redirectPort}/callback`)}&response_type=code`;
|
|
96
|
+
this.logger?.info('[AuthorizationCodeProvider] Performing login via browser', {
|
|
97
|
+
browser,
|
|
98
|
+
redirectPort,
|
|
99
|
+
authorizationUrl,
|
|
100
|
+
uaaUrl: authConfig.uaaUrl,
|
|
101
|
+
clientId: authConfig.uaaClientId,
|
|
102
|
+
});
|
|
103
|
+
// Wrap startBrowserAuth with timeout
|
|
104
|
+
const timeoutMs = 30 * 1000; // 30 seconds
|
|
105
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
106
|
+
setTimeout(() => {
|
|
107
|
+
reject(new Error(`Authentication timeout after ${timeoutMs / 1000} seconds. Please try again.`));
|
|
108
|
+
}, timeoutMs);
|
|
109
|
+
});
|
|
110
|
+
const result = await Promise.race([
|
|
111
|
+
(0, browserAuth_1.startBrowserAuth)(authConfig, browser, this.logger || undefined, // Pass logger to browserAuth
|
|
112
|
+
redirectPort),
|
|
113
|
+
timeoutPromise,
|
|
114
|
+
]);
|
|
93
115
|
this.logger?.info('[AuthorizationCodeProvider] Login completed', {
|
|
94
116
|
hasAccessToken: !!result.accessToken,
|
|
95
117
|
hasRefreshToken: !!result.refreshToken,
|
|
118
|
+
accessToken: this.formatToken(result.accessToken),
|
|
119
|
+
refreshToken: this.formatToken(result.refreshToken),
|
|
96
120
|
accessTokenLength: result.accessToken?.length || 0,
|
|
97
121
|
});
|
|
98
122
|
// Parse expiration from JWT
|
|
@@ -115,6 +139,9 @@ class AuthorizationCodeProvider extends BaseTokenProvider_1.BaseTokenProvider {
|
|
|
115
139
|
this.logger?.info('[AuthorizationCodeProvider] Token refresh completed', {
|
|
116
140
|
hasAccessToken: !!result.accessToken,
|
|
117
141
|
hasRefreshToken: !!result.refreshToken,
|
|
142
|
+
newAccessToken: this.formatToken(result.accessToken),
|
|
143
|
+
newRefreshToken: this.formatToken(result.refreshToken),
|
|
144
|
+
oldRefreshToken: this.formatToken(this.refreshToken),
|
|
118
145
|
});
|
|
119
146
|
const expiresIn = this.calculateExpiresIn(result.accessToken);
|
|
120
147
|
return {
|
|
@@ -22,6 +22,16 @@ export declare abstract class BaseTokenProvider implements ITokenProvider {
|
|
|
22
22
|
protected refreshToken?: string;
|
|
23
23
|
protected expiresAt?: number;
|
|
24
24
|
protected logger?: ILogger;
|
|
25
|
+
/**
|
|
26
|
+
* Format timestamp to readable date/time string
|
|
27
|
+
* @param timestamp Timestamp in milliseconds
|
|
28
|
+
* @returns Formatted date string (e.g., "2025-12-25 19:21:27 UTC")
|
|
29
|
+
*/
|
|
30
|
+
protected formatExpirationDate(timestamp: number): string;
|
|
31
|
+
/**
|
|
32
|
+
* Format token for logging (start...end)
|
|
33
|
+
*/
|
|
34
|
+
protected formatToken(token?: string): string | undefined;
|
|
25
35
|
/**
|
|
26
36
|
* Check if current token is valid (not expired)
|
|
27
37
|
* @returns true if token exists and is not expired, false otherwise
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseTokenProvider.d.ts","sourceRoot":"","sources":["../../src/providers/BaseTokenProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EACV,OAAO,EACP,cAAc,EACd,YAAY,EACZ,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAElC;;;;;;;;GAQG;AACH,8BAAsB,iBAAkB,YAAW,cAAc;IAC/D,SAAS,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACtC,SAAS,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAChC,SAAS,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC7B,SAAS,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAE3B;;;OAGG;IACH,SAAS,CAAC,YAAY,IAAI,OAAO;IAyBjC;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,YAAY,IAAI,OAAO,CAAC,YAAY,CAAC;IAExD;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,cAAc,IAAI,OAAO,CAAC,YAAY,CAAC;IAE1D;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,WAAW,IAAI,eAAe;IAEjD;;;;;;;;OAQG;IACG,SAAS,IAAI,OAAO,CAAC,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"BaseTokenProvider.d.ts","sourceRoot":"","sources":["../../src/providers/BaseTokenProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EACV,OAAO,EACP,cAAc,EACd,YAAY,EACZ,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAElC;;;;;;;;GAQG;AACH,8BAAsB,iBAAkB,YAAW,cAAc;IAC/D,SAAS,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACtC,SAAS,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAChC,SAAS,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC7B,SAAS,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAE3B;;;;OAIG;IACH,SAAS,CAAC,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAWzD;;OAEG;IACH,SAAS,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAMzD;;;OAGG;IACH,SAAS,CAAC,YAAY,IAAI,OAAO;IAyBjC;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,YAAY,IAAI,OAAO,CAAC,YAAY,CAAC;IAExD;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,cAAc,IAAI,OAAO,CAAC,YAAY,CAAC;IAE1D;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,WAAW,IAAI,eAAe;IAEjD;;;;;;;;OAQG;IACG,SAAS,IAAI,OAAO,CAAC,YAAY,CAAC;IAuElC,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAmB3E;;;OAGG;IACH,SAAS,CAAC,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI;IAoBlD;;;;OAIG;IACH,SAAS,CAAC,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IA0BnE;;;;OAIG;IACH,SAAS,CAAC,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;CAShE"}
|
|
@@ -24,6 +24,31 @@ class BaseTokenProvider {
|
|
|
24
24
|
refreshToken;
|
|
25
25
|
expiresAt; // timestamp in milliseconds
|
|
26
26
|
logger;
|
|
27
|
+
/**
|
|
28
|
+
* Format timestamp to readable date/time string
|
|
29
|
+
* @param timestamp Timestamp in milliseconds
|
|
30
|
+
* @returns Formatted date string (e.g., "2025-12-25 19:21:27 UTC")
|
|
31
|
+
*/
|
|
32
|
+
formatExpirationDate(timestamp) {
|
|
33
|
+
const date = new Date(timestamp);
|
|
34
|
+
const year = date.getUTCFullYear();
|
|
35
|
+
const month = String(date.getUTCMonth() + 1).padStart(2, '0');
|
|
36
|
+
const day = String(date.getUTCDate()).padStart(2, '0');
|
|
37
|
+
const hours = String(date.getUTCHours()).padStart(2, '0');
|
|
38
|
+
const minutes = String(date.getUTCMinutes()).padStart(2, '0');
|
|
39
|
+
const seconds = String(date.getUTCSeconds()).padStart(2, '0');
|
|
40
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds} UTC`;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Format token for logging (start...end)
|
|
44
|
+
*/
|
|
45
|
+
formatToken(token) {
|
|
46
|
+
if (!token)
|
|
47
|
+
return undefined;
|
|
48
|
+
if (token.length <= 50)
|
|
49
|
+
return token;
|
|
50
|
+
return `${token.substring(0, 25)}...${token.substring(token.length - 25)}`;
|
|
51
|
+
}
|
|
27
52
|
/**
|
|
28
53
|
* Check if current token is valid (not expired)
|
|
29
54
|
* @returns true if token exists and is not expired, false otherwise
|
|
@@ -41,8 +66,8 @@ class BaseTokenProvider {
|
|
|
41
66
|
const now = Date.now();
|
|
42
67
|
const isValid = now < this.expiresAt - bufferMs;
|
|
43
68
|
this.logger?.debug('[BaseTokenProvider] Token validation check', {
|
|
44
|
-
now:
|
|
45
|
-
expiresAt:
|
|
69
|
+
now: this.formatExpirationDate(now),
|
|
70
|
+
expiresAt: this.formatExpirationDate(this.expiresAt),
|
|
46
71
|
expiresIn: Math.floor((this.expiresAt - now) / 1000),
|
|
47
72
|
isValid,
|
|
48
73
|
bufferMs,
|
|
@@ -63,6 +88,7 @@ class BaseTokenProvider {
|
|
|
63
88
|
hasToken: !!this.authorizationToken,
|
|
64
89
|
hasExpiresAt: !!this.expiresAt,
|
|
65
90
|
hasRefreshToken: !!this.refreshToken,
|
|
91
|
+
currentToken: this.formatToken(this.authorizationToken),
|
|
66
92
|
});
|
|
67
93
|
// If token is valid, return cached
|
|
68
94
|
const isValid = this.isTokenValid();
|
|
@@ -72,6 +98,7 @@ class BaseTokenProvider {
|
|
|
72
98
|
throw new Error('Authorization token is missing.');
|
|
73
99
|
}
|
|
74
100
|
this.logger?.info('[BaseTokenProvider] Returning cached valid token', {
|
|
101
|
+
token: this.formatToken(authorizationToken),
|
|
75
102
|
expiresIn: this.expiresAt
|
|
76
103
|
? Math.floor((this.expiresAt - Date.now()) / 1000)
|
|
77
104
|
: undefined,
|
|
@@ -87,11 +114,17 @@ class BaseTokenProvider {
|
|
|
87
114
|
}
|
|
88
115
|
// Try refresh if we have refresh token
|
|
89
116
|
if (this.refreshToken) {
|
|
90
|
-
this.logger?.info('[BaseTokenProvider] Token invalid, attempting refresh'
|
|
117
|
+
this.logger?.info('[BaseTokenProvider] Token invalid, attempting refresh', {
|
|
118
|
+
oldToken: this.formatToken(this.authorizationToken),
|
|
119
|
+
refreshToken: this.formatToken(this.refreshToken),
|
|
120
|
+
});
|
|
91
121
|
try {
|
|
92
122
|
const result = await this.performRefresh();
|
|
93
123
|
this.updateTokens(result);
|
|
94
|
-
this.logger?.info('[BaseTokenProvider] Token refreshed successfully'
|
|
124
|
+
this.logger?.info('[BaseTokenProvider] Token refreshed successfully', {
|
|
125
|
+
newToken: this.formatToken(result.authorizationToken),
|
|
126
|
+
newRefreshToken: this.formatToken(result.refreshToken),
|
|
127
|
+
});
|
|
95
128
|
return result;
|
|
96
129
|
}
|
|
97
130
|
catch (error) {
|
|
@@ -108,7 +141,10 @@ class BaseTokenProvider {
|
|
|
108
141
|
this.logger?.info('[BaseTokenProvider] Token invalid and no refresh token, performing login');
|
|
109
142
|
const result = await this.performLogin();
|
|
110
143
|
this.updateTokens(result);
|
|
111
|
-
this.logger?.info('[BaseTokenProvider] Login completed'
|
|
144
|
+
this.logger?.info('[BaseTokenProvider] Login completed', {
|
|
145
|
+
newToken: this.formatToken(result.authorizationToken),
|
|
146
|
+
newRefreshToken: this.formatToken(result.refreshToken),
|
|
147
|
+
});
|
|
112
148
|
return result;
|
|
113
149
|
}
|
|
114
150
|
async validateToken(_token, _serviceUrl) {
|
|
@@ -122,7 +158,7 @@ class BaseTokenProvider {
|
|
|
122
158
|
const isValid = Date.now() < expiresAt - bufferMs;
|
|
123
159
|
this.logger?.info('[BaseTokenProvider] Token validation result', {
|
|
124
160
|
isValid,
|
|
125
|
-
expiresAt:
|
|
161
|
+
expiresAt: this.formatExpirationDate(expiresAt),
|
|
126
162
|
expiresIn: Math.floor((expiresAt - Date.now()) / 1000),
|
|
127
163
|
});
|
|
128
164
|
return isValid;
|
|
@@ -132,6 +168,7 @@ class BaseTokenProvider {
|
|
|
132
168
|
* @param result Token result to cache
|
|
133
169
|
*/
|
|
134
170
|
updateTokens(result) {
|
|
171
|
+
const oldToken = this.formatToken(this.authorizationToken);
|
|
135
172
|
this.authorizationToken = result.authorizationToken;
|
|
136
173
|
this.refreshToken = result.refreshToken;
|
|
137
174
|
if (result.expiresIn) {
|
|
@@ -141,6 +178,14 @@ class BaseTokenProvider {
|
|
|
141
178
|
// Try to parse expiration from JWT if expiresIn not provided
|
|
142
179
|
this.expiresAt = this.parseExpirationFromJWT(result.authorizationToken);
|
|
143
180
|
}
|
|
181
|
+
this.logger?.info('[BaseTokenProvider] Tokens updated', {
|
|
182
|
+
oldToken,
|
|
183
|
+
newToken: this.formatToken(result.authorizationToken),
|
|
184
|
+
newRefreshToken: this.formatToken(result.refreshToken),
|
|
185
|
+
expiresAt: this.expiresAt
|
|
186
|
+
? this.formatExpirationDate(this.expiresAt)
|
|
187
|
+
: undefined,
|
|
188
|
+
});
|
|
144
189
|
}
|
|
145
190
|
/**
|
|
146
191
|
* Parse expiration time from JWT token
|