@mcp-abap-adt/connection 4.0.0 → 6.0.0

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.
Files changed (87) hide show
  1. package/CHANGELOG.md +407 -1
  2. package/README.md +239 -53
  3. package/dist/auth/providers.d.ts +124 -0
  4. package/dist/auth/providers.d.ts.map +1 -0
  5. package/dist/auth/providers.js +183 -0
  6. package/dist/connection/AbstractAbapConnection.d.ts +209 -57
  7. package/dist/connection/AbstractAbapConnection.d.ts.map +1 -1
  8. package/dist/connection/AbstractAbapConnection.js +459 -457
  9. package/dist/connection/AdtCloudConnector.d.ts +34 -0
  10. package/dist/connection/AdtCloudConnector.d.ts.map +1 -0
  11. package/dist/connection/AdtCloudConnector.js +27 -0
  12. package/dist/connection/AdtOnPremConnector.d.ts +43 -0
  13. package/dist/connection/AdtOnPremConnector.d.ts.map +1 -0
  14. package/dist/connection/AdtOnPremConnector.js +28 -0
  15. package/dist/connection/CloudHttpTransport.d.ts +37 -0
  16. package/dist/connection/CloudHttpTransport.d.ts.map +1 -0
  17. package/dist/connection/CloudHttpTransport.js +145 -0
  18. package/dist/connection/CredentialAbapConnection.d.ts +55 -0
  19. package/dist/connection/CredentialAbapConnection.d.ts.map +1 -0
  20. package/dist/connection/CredentialAbapConnection.js +128 -0
  21. package/dist/connection/HttpTransport.d.ts +178 -0
  22. package/dist/connection/HttpTransport.d.ts.map +1 -0
  23. package/dist/connection/HttpTransport.js +402 -0
  24. package/dist/connection/IAdtTransport.d.ts +232 -0
  25. package/dist/connection/IAdtTransport.d.ts.map +1 -0
  26. package/dist/connection/IAdtTransport.js +28 -0
  27. package/dist/connection/LegacyOnPremHttpTransport.d.ts +40 -0
  28. package/dist/connection/LegacyOnPremHttpTransport.d.ts.map +1 -0
  29. package/dist/connection/LegacyOnPremHttpTransport.js +57 -0
  30. package/dist/connection/OnPremHttpTransport.d.ts +45 -0
  31. package/dist/connection/OnPremHttpTransport.d.ts.map +1 -0
  32. package/dist/connection/OnPremHttpTransport.js +91 -0
  33. package/dist/connection/RfcTransport.d.ts +89 -0
  34. package/dist/connection/RfcTransport.d.ts.map +1 -0
  35. package/dist/connection/RfcTransport.js +256 -0
  36. package/dist/connection/rfcConversation.d.ts +44 -0
  37. package/dist/connection/rfcConversation.d.ts.map +1 -0
  38. package/dist/connection/rfcConversation.js +71 -0
  39. package/dist/index.d.ts +10 -7
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +30 -18
  42. package/dist/session/SessionLifecycle.d.ts +17 -2
  43. package/dist/session/SessionLifecycle.d.ts.map +1 -1
  44. package/dist/session/SessionLifecycle.js +17 -2
  45. package/dist/utils/cookies.d.ts +12 -0
  46. package/dist/utils/cookies.d.ts.map +1 -0
  47. package/dist/utils/cookies.js +24 -0
  48. package/dist/utils/timeouts.d.ts +6 -3
  49. package/dist/utils/timeouts.d.ts.map +1 -1
  50. package/dist/utils/timeouts.js +6 -3
  51. package/docs/INDEX.md +5 -2
  52. package/docs/INSTALLATION.md +28 -6
  53. package/docs/JWT_AUTH_TOOLS.md +20 -4
  54. package/docs/MIGRATION-2.0.md +1 -1
  55. package/docs/MIGRATION-5.0.md +116 -0
  56. package/docs/MIGRATION-6.0.md +359 -0
  57. package/docs/SCOPE.md +1 -1
  58. package/docs/STATEFUL_SESSION_GUIDE.md +155 -20
  59. package/docs/USAGE.md +322 -111
  60. package/examples/basic-connection.js +15 -3
  61. package/examples/jwt-with-token-refresh.js +15 -7
  62. package/examples/saml-connection.js +15 -2
  63. package/package.json +12 -10
  64. package/dist/__tests__/helpers/session.d.ts +0 -15
  65. package/dist/__tests__/helpers/session.d.ts.map +0 -1
  66. package/dist/__tests__/helpers/session.js +0 -19
  67. package/dist/connection/BaseAbapConnection.d.ts +0 -23
  68. package/dist/connection/BaseAbapConnection.d.ts.map +0 -1
  69. package/dist/connection/BaseAbapConnection.js +0 -75
  70. package/dist/connection/CertificateAbapConnection.d.ts +0 -25
  71. package/dist/connection/CertificateAbapConnection.d.ts.map +0 -1
  72. package/dist/connection/CertificateAbapConnection.js +0 -79
  73. package/dist/connection/JwtAbapConnection.d.ts +0 -115
  74. package/dist/connection/JwtAbapConnection.d.ts.map +0 -1
  75. package/dist/connection/JwtAbapConnection.js +0 -358
  76. package/dist/connection/KerberosAbapConnection.d.ts +0 -24
  77. package/dist/connection/KerberosAbapConnection.d.ts.map +0 -1
  78. package/dist/connection/KerberosAbapConnection.js +0 -120
  79. package/dist/connection/RfcAbapConnection.d.ts +0 -49
  80. package/dist/connection/RfcAbapConnection.d.ts.map +0 -1
  81. package/dist/connection/RfcAbapConnection.js +0 -331
  82. package/dist/connection/SamlAbapConnection.d.ts +0 -25
  83. package/dist/connection/SamlAbapConnection.d.ts.map +0 -1
  84. package/dist/connection/SamlAbapConnection.js +0 -75
  85. package/dist/connection/connectionFactory.d.ts +0 -9
  86. package/dist/connection/connectionFactory.d.ts.map +0 -1
  87. package/dist/connection/connectionFactory.js +0 -32
@@ -4,7 +4,11 @@
4
4
  * Demonstrates simple connection to SAP system and making ADT request
5
5
  */
6
6
 
7
- const { createAbapConnection } = require('@mcp-abap-adt/connection');
7
+ const {
8
+ OnPremHttpTransport,
9
+ AdtOnPremConnector,
10
+ BasicAuthProvider,
11
+ } = require('@mcp-abap-adt/connection');
8
12
 
9
13
  async function main() {
10
14
  // Configuration from environment or hardcoded
@@ -18,8 +22,16 @@ async function main() {
18
22
 
19
23
  console.log('Creating connection to', config.url);
20
24
 
21
- // Create connection (factory auto-detects cloud vs on-premise)
22
- const connection = createAbapConnection(config, console);
25
+ // Which system (the class), which credential (the object). Neither is detected.
26
+ const connection = new AdtOnPremConnector(
27
+ config,
28
+ new BasicAuthProvider(config.username, config.password),
29
+ new OnPremHttpTransport(() => ({}), console, {
30
+ client: config.client,
31
+ baseUrl: config.url,
32
+ }),
33
+ console,
34
+ );
23
35
 
24
36
  try {
25
37
  // Connect and get CSRF token
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Example: JWT Connection with Automatic Token Refresh
3
3
  *
4
- * This example demonstrates how to create a JwtAbapConnection with
4
+ * This example demonstrates how to create a cloud connection with
5
5
  * automatic token refresh using ITokenRefresher from auth-broker.
6
6
  *
7
7
  * When a **401** occurs, the connection automatically:
@@ -16,7 +16,11 @@
16
16
  * change nothing.
17
17
  */
18
18
 
19
- const { JwtAbapConnection } = require('@mcp-abap-adt/connection');
19
+ const {
20
+ CloudHttpTransport,
21
+ AdtCloudConnector,
22
+ TokenAuthProvider,
23
+ } = require('@mcp-abap-adt/connection');
20
24
  // const { AuthBroker } = require('@mcp-abap-adt/auth-broker');
21
25
 
22
26
  // Simple logger
@@ -62,13 +66,17 @@ async function main() {
62
66
  jwtToken: initialToken,
63
67
  };
64
68
 
65
- // Create connection with token refresher
66
- // 4th parameter is the ITokenRefresher
67
- const connection = new JwtAbapConnection(
69
+ // The refresher IS the credential: `TokenAuthProvider` asks it for a token on
70
+ // every request, so a provider that renews on expiry renews without anyone
71
+ // here deciding to. A bare string would be a token with nothing behind it.
72
+ const connection = new AdtCloudConnector(
68
73
  config,
74
+ new TokenAuthProvider(tokenRefresher),
75
+ new CloudHttpTransport(() => ({}), console, {
76
+ client: config.client,
77
+ baseUrl: config.url,
78
+ }),
69
79
  logger,
70
- undefined,
71
- tokenRefresher,
72
80
  );
73
81
 
74
82
  try {
@@ -5,7 +5,11 @@
5
5
  * Session cookies are expected from prior login flow.
6
6
  */
7
7
 
8
- const { createAbapConnection } = require('@mcp-abap-adt/connection');
8
+ const {
9
+ OnPremHttpTransport,
10
+ AdtOnPremConnector,
11
+ SamlAuthProvider,
12
+ } = require('@mcp-abap-adt/connection');
9
13
 
10
14
  async function main() {
11
15
  const config = {
@@ -22,7 +26,16 @@ async function main() {
22
26
  }
23
27
 
24
28
  console.log('Creating SAML connection to', config.url);
25
- const connection = createAbapConnection(config, console);
29
+ // The cookies ARE the credential — there is no Authorization header at all.
30
+ const connection = new AdtOnPremConnector(
31
+ config,
32
+ new SamlAuthProvider(config.sessionCookies),
33
+ new OnPremHttpTransport(() => ({}), console, {
34
+ client: config.client,
35
+ baseUrl: config.url,
36
+ }),
37
+ console,
38
+ );
26
39
 
27
40
  try {
28
41
  console.log('Connecting using session cookies...');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-abap-adt/connection",
3
- "version": "4.0.0",
3
+ "version": "6.0.0",
4
4
  "description": "ABAP connection layer for MCP ABAP ADT server",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -35,22 +35,24 @@
35
35
  "url": "git+https://github.com/fr0ster/mcp-abap-connection.git"
36
36
  },
37
37
  "scripts": {
38
- "chrono": "./tools/version-stats.sh",
39
- "clean": "rm -rf dist tsconfig.tsbuildinfo",
40
- "lint": "npx biome check --write src",
41
- "lint:check": "npx biome check src",
42
- "format": "npx biome format --write src",
43
- "build": "npm run --silent clean && npx biome check src --diagnostic-level=error && npx tsc -p tsconfig.json",
38
+ "chrono": "node scripts/version-stats.mjs",
39
+ "clean": "node scripts/clean.mjs",
40
+ "lint": "npx biome check --write src scripts",
41
+ "lint:check": "npx biome check src scripts",
42
+ "format": "npx biome format --write src scripts",
43
+ "build": "npm run --silent clean && npx biome check src scripts --diagnostic-level=error && npx tsc -p tsconfig.json && npm run --silent typecheck:scripts",
44
44
  "build:fast": "npx tsc -p tsconfig.json",
45
45
  "test": "npm run --silent check:docs && jest",
46
- "prepublishOnly": "npm run build && npm run check:docs",
47
- "check:docs": "node scripts/check-docs.mjs"
46
+ "prepublishOnly": "npm run build && npm run check:docs && npm run --silent check:pack",
47
+ "check:docs": "node scripts/check-docs.mjs",
48
+ "typecheck:scripts": "npx tsc -p tsconfig.scripts.json",
49
+ "check:pack": "node scripts/check-pack.mjs"
48
50
  },
49
51
  "engines": {
50
52
  "node": ">=18.0.0"
51
53
  },
52
54
  "dependencies": {
53
- "@mcp-abap-adt/interfaces": "^12.0.0",
55
+ "@mcp-abap-adt/interfaces": "^21.0.0",
54
56
  "axios": "^1.16.0",
55
57
  "commander": "^14.0.3",
56
58
  "express": "^5.1.0",
@@ -1,15 +0,0 @@
1
- /**
2
- * Test precondition: put a connection into the connected state without a
3
- * network round trip.
4
- *
5
- * `connect()` is mandatory now, so a test whose subject is something else —
6
- * timeouts, headers, retry classification — needs the session to exist before
7
- * it can say anything. Establishing it for real would mean teaching every such
8
- * mock to answer a CSRF fetch, which tests the wrong thing and adds retry
9
- * delays to suites that are not about connecting.
10
- *
11
- * Tests that ARE about connecting use a real stub instead; see
12
- * `connection/sessionComposition.test.ts`.
13
- */
14
- export declare function markConnectedForTest(connection: unknown, fingerprint?: Map<string, string>): void;
15
- //# sourceMappingURL=session.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/__tests__/helpers/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,OAAO,EACnB,WAAW,GAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAA4C,GAC1E,IAAI,CAMN"}
@@ -1,19 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.markConnectedForTest = markConnectedForTest;
4
- /**
5
- * Test precondition: put a connection into the connected state without a
6
- * network round trip.
7
- *
8
- * `connect()` is mandatory now, so a test whose subject is something else —
9
- * timeouts, headers, retry classification — needs the session to exist before
10
- * it can say anything. Establishing it for real would mean teaching every such
11
- * mock to answer a CSRF fetch, which tests the wrong thing and adds retry
12
- * delays to suites that are not about connecting.
13
- *
14
- * Tests that ARE about connecting use a real stub instead; see
15
- * `connection/sessionComposition.test.ts`.
16
- */
17
- function markConnectedForTest(connection, fingerprint = new Map([['SAP_SESSIONID_T_100', 'S1']])) {
18
- connection.lifecycle.markConnected(fingerprint);
19
- }
@@ -1,23 +0,0 @@
1
- import type { SapConfig } from '../config/sapConfig.js';
2
- import type { ILogger } from '../logger.js';
3
- import { AbstractAbapConnection } from './AbstractAbapConnection.js';
4
- /**
5
- * Basic Authentication connection for on-premise SAP systems
6
- */
7
- export declare class BaseAbapConnection extends AbstractAbapConnection {
8
- constructor(config: SapConfig, logger?: ILogger | null, sessionId?: string, options?: {
9
- skipSessionType?: boolean;
10
- });
11
- /**
12
- * Connect to SAP system with Basic Auth
13
- * Fetches CSRF token which also establishes session cookies
14
- */
15
- /**
16
- * Establishes the session for this auth type. Called by
17
- * AbstractAbapConnection.connect(), which owns the lifecycle around it.
18
- */
19
- protected establishSession(): Promise<void>;
20
- protected buildAuthorizationHeader(): string;
21
- private static validateConfig;
22
- }
23
- //# sourceMappingURL=BaseAbapConnection.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"BaseAbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/BaseAbapConnection.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAErE;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,sBAAsB;gBAE1D,MAAM,EAAE,SAAS,EACjB,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,EACvB,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE;IAMzC;;;OAGG;IACH;;;OAGG;cACa,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IA6CjD,SAAS,CAAC,wBAAwB,IAAI,MAAM;IAU5C,OAAO,CAAC,MAAM,CAAC,cAAc;CAmB9B"}
@@ -1,75 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.BaseAbapConnection = void 0;
4
- const axios_1 = require("axios");
5
- const AbstractAbapConnection_js_1 = require("./AbstractAbapConnection.js");
6
- /**
7
- * Basic Authentication connection for on-premise SAP systems
8
- */
9
- class BaseAbapConnection extends AbstractAbapConnection_js_1.AbstractAbapConnection {
10
- constructor(config, logger, sessionId, options) {
11
- BaseAbapConnection.validateConfig(config);
12
- super(config, logger || null, sessionId, options);
13
- }
14
- /**
15
- * Connect to SAP system with Basic Auth
16
- * Fetches CSRF token which also establishes session cookies
17
- */
18
- /**
19
- * Establishes the session for this auth type. Called by
20
- * AbstractAbapConnection.connect(), which owns the lifecycle around it.
21
- */
22
- async establishSession() {
23
- const baseUrl = await this.getBaseUrl();
24
- const discoveryUrl = `${baseUrl}/sap/bc/adt/discovery`;
25
- this.logger?.debug(`[DEBUG] BaseAbapConnection - Connecting to SAP system: ${discoveryUrl}`);
26
- try {
27
- // Try to get CSRF token (this will also get cookies)
28
- const token = await this.fetchCsrfToken(discoveryUrl);
29
- this.setCsrfToken(token);
30
- this.logger?.debug('Successfully connected to SAP system', {
31
- hasCsrfToken: !!this.getCsrfToken(),
32
- hasCookies: !!this.getCookies(),
33
- cookieLength: this.getCookies()?.length || 0,
34
- });
35
- }
36
- catch (error) {
37
- // For Basic auth, log warning but don't fail
38
- // The retry logic in makeAdtRequest will handle transient errors automatically
39
- const errorMsg = error instanceof Error ? error.message : String(error);
40
- this.logger?.warn(`[WARN] BaseAbapConnection - Could not connect to SAP system upfront: ${errorMsg}. Will retry on first request.`);
41
- // Still try to extract cookies from error response if available
42
- if (error instanceof axios_1.AxiosError && error.response?.headers) {
43
- // updateCookiesFromResponse is private, but cookies are extracted in fetchCsrfToken
44
- if (this.getCookies()) {
45
- this.logger?.debug(`[DEBUG] BaseAbapConnection - Cookies extracted from error response during connect (first 100 chars): ${this.getCookies()?.substring(0, 100)}...`);
46
- }
47
- }
48
- // Rethrow: a resolved connect() must mean a usable session exists. This
49
- // used to swallow and resolve anyway, deferring establishment to the
50
- // first request — coherent only while that lazy path existed. Without it,
51
- // swallowing would leave a connection that reports success, holds
52
- // nothing, and refuses every request.
53
- throw error;
54
- }
55
- }
56
- buildAuthorizationHeader() {
57
- const { username, password } = this.getConfig();
58
- const safeUsername = username ?? '';
59
- const safePassword = password ?? '';
60
- const token = Buffer.from(`${safeUsername}:${safePassword}`).toString('base64');
61
- return `Basic ${token}`;
62
- }
63
- static validateConfig(config) {
64
- if (config.authType !== 'basic') {
65
- throw new Error(`Basic authentication connection expects authType "basic", got "${config.authType}"`);
66
- }
67
- if (!config.username || !config.password) {
68
- throw new Error('Basic authentication requires both username and password');
69
- }
70
- if (!config.client) {
71
- throw new Error('Basic authentication requires SAP_CLIENT to be provided');
72
- }
73
- }
74
- }
75
- exports.BaseAbapConnection = BaseAbapConnection;
@@ -1,25 +0,0 @@
1
- import type { AgentOptions } from 'node:https';
2
- import type { ICertificateMaterialLoader } from '@mcp-abap-adt/interfaces';
3
- import type { SapConfig } from '../config/sapConfig.js';
4
- import type { ILogger } from '../logger.js';
5
- import { AbstractAbapConnection } from './AbstractAbapConnection.js';
6
- /** Client-certificate (mTLS) authentication. Credential lives on the TLS agent. */
7
- export declare class CertificateAbapConnection extends AbstractAbapConnection {
8
- private loader;
9
- private material;
10
- constructor(config: SapConfig, logger?: ILogger | null, sessionId?: string, loader?: ICertificateMaterialLoader);
11
- private static validateConfig;
12
- protected ensureMaterial(): Promise<void>;
13
- /**
14
- * Loads certificate material and primes the session. MUST be called before the first
15
- * request — the TLS agent is built lazily and needs the client cert present.
16
- */
17
- /**
18
- * Establishes the session for this auth type. Called by
19
- * AbstractAbapConnection.connect(), which owns the lifecycle around it.
20
- */
21
- protected establishSession(): Promise<void>;
22
- protected getHttpsAgentOptions(): AgentOptions;
23
- protected buildAuthorizationHeader(): string;
24
- }
25
- //# sourceMappingURL=CertificateAbapConnection.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"CertificateAbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/CertificateAbapConnection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,KAAK,EAEV,0BAA0B,EAC3B,MAAM,0BAA0B,CAAC;AAGlC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAErE,mFAAmF;AACnF,qBAAa,yBAA0B,SAAQ,sBAAsB;IACnE,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,QAAQ,CAAqC;gBAGnD,MAAM,EAAE,SAAS,EACjB,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,EACvB,SAAS,CAAC,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,0BAA0B;IAOrC,OAAO,CAAC,MAAM,CAAC,cAAc;cAyBb,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAM/C;;;OAGG;IACH;;;OAGG;cACa,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IA6BjD,SAAS,CAAC,oBAAoB,IAAI,YAAY;IAU9C,SAAS,CAAC,wBAAwB,IAAI,MAAM;CAG7C"}
@@ -1,79 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CertificateAbapConnection = void 0;
4
- const axios_1 = require("axios");
5
- const FileCertificateMaterialLoader_js_1 = require("../auth/FileCertificateMaterialLoader.js");
6
- const AbstractAbapConnection_js_1 = require("./AbstractAbapConnection.js");
7
- /** Client-certificate (mTLS) authentication. Credential lives on the TLS agent. */
8
- class CertificateAbapConnection extends AbstractAbapConnection_js_1.AbstractAbapConnection {
9
- loader;
10
- material = null;
11
- constructor(config, logger, sessionId, loader) {
12
- CertificateAbapConnection.validateConfig(config);
13
- super(config, logger || null, sessionId);
14
- this.loader = loader ?? new FileCertificateMaterialLoader_js_1.FileCertificateMaterialLoader();
15
- }
16
- static validateConfig(config) {
17
- if (config.authType !== 'certificate') {
18
- throw new Error(`Certificate connection expects authType "certificate", got "${config.authType}"`);
19
- }
20
- if (config.connectionType === 'rfc') {
21
- throw new Error('Certificate auth is not supported with connectionType "rfc".');
22
- }
23
- const hasPem = !!(config.certPath && config.certKeyPath); // complete PEM pair present
24
- const hasPfx = !!config.certPfxPath;
25
- if (!hasPem && !hasPfx) {
26
- throw new Error('Certificate auth requires certPfxPath OR (certPath AND certKeyPath).');
27
- }
28
- if ((config.certPath || config.certKeyPath) && config.certPfxPath) {
29
- throw new Error('Certificate auth: provide either PEM pair OR PFX, not both.');
30
- }
31
- }
32
- async ensureMaterial() {
33
- if (!this.material) {
34
- this.material = await this.loader.load(this.getConfig());
35
- }
36
- }
37
- /**
38
- * Loads certificate material and primes the session. MUST be called before the first
39
- * request — the TLS agent is built lazily and needs the client cert present.
40
- */
41
- /**
42
- * Establishes the session for this auth type. Called by
43
- * AbstractAbapConnection.connect(), which owns the lifecycle around it.
44
- */
45
- async establishSession() {
46
- await this.ensureMaterial();
47
- const baseUrl = await this.getBaseUrl();
48
- const discoveryUrl = `${baseUrl}/sap/bc/adt/discovery`;
49
- try {
50
- const token = await this.fetchCsrfToken(discoveryUrl);
51
- this.setCsrfToken(token);
52
- }
53
- catch (error) {
54
- this.logger?.warn(`[WARN] CertificateAbapConnection - connect deferred: ${error instanceof Error ? error.message : String(error)}`);
55
- if (error instanceof axios_1.AxiosError && error.response?.headers) {
56
- if (this.getCookies()) {
57
- this.logger?.debug(`[DEBUG] CertificateAbapConnection - Cookies extracted from error response during connect (first 100 chars): ${this.getCookies()?.substring(0, 100)}...`);
58
- }
59
- }
60
- // Rethrow: a resolved connect() must mean a usable session exists. This
61
- // used to swallow and resolve anyway, deferring establishment to the
62
- // first request — coherent only while that lazy path existed. Without it,
63
- // swallowing would leave a connection that reports success, holds
64
- // nothing, and refuses every request.
65
- throw error;
66
- }
67
- }
68
- getHttpsAgentOptions() {
69
- if (!this.material) {
70
- throw new Error('CertificateAbapConnection: certificate material not loaded. Call connect() before making requests.');
71
- }
72
- const { cert, key, pfx, passphrase } = this.material;
73
- return { cert, key, pfx, passphrase };
74
- }
75
- buildAuthorizationHeader() {
76
- return '';
77
- }
78
- }
79
- exports.CertificateAbapConnection = CertificateAbapConnection;
@@ -1,115 +0,0 @@
1
- import { type IAdtResponse, type ITokenRefresher } from '@mcp-abap-adt/interfaces';
2
- import type { SapConfig } from '../config/sapConfig.js';
3
- import type { ILogger } from '../logger.js';
4
- import type { AbapRequestOptions } from './AbapConnection.js';
5
- import { AbstractAbapConnection } from './AbstractAbapConnection.js';
6
- /**
7
- * JWT Authentication connection for SAP BTP Cloud systems
8
- *
9
- * Supports automatic token refresh via ITokenRefresher injection:
10
- * - a **401** triggers a token refresh when a tokenRefresher is available;
11
- * - without one, or when the refresh does not help, the server's own error is
12
- * thrown unchanged — status and body intact;
13
- * - a **403** is never a token problem. It propagates as it arrived, because a
14
- * new token is the same caller and cannot change a permissions answer.
15
- */
16
- export declare class JwtAbapConnection extends AbstractAbapConnection {
17
- private tokenRefresher?;
18
- private currentToken;
19
- /** Bumped by any token refresh, token-only ones included. */
20
- private tokenGeneration;
21
- /** Single-flight over the token fetch alone. Touches no session state. */
22
- private tokenRefreshInFlight?;
23
- /**
24
- * The `tokenGeneration` a completed session recovery was built for. Only
25
- * `performRenewal` moves it, and only after `recoverSession` resolves.
26
- *
27
- * Separate from `tokenGeneration` because a token-only refresh — which
28
- * `fetchCsrfToken` does, from inside an establishment — bumps that counter
29
- * without rebuilding anything. Reading it as "the session is settled" made
30
- * the outer handler retry a request whose session was never rebuilt.
31
- */
32
- private recoveredGeneration;
33
- /**
34
- * Single-flight over the whole credential renewal, session included.
35
- *
36
- * Sharing only the token fetch leaves the expensive half racing: `recover`
37
- * and `cleanup` never join (`SessionLifecycle.transition`), so two concurrent
38
- * renewals queue as cleanup A → recover A → cleanup B → recover B, and A's
39
- * retry meets a session B has just torn down.
40
- */
41
- private renewalInFlight?;
42
- /**
43
- * Per connection, deliberately NOT static. `baseline` is compared against
44
- * `this.tokenGeneration`, which is instance state — a store shared between
45
- * instances would let one connection's operation hand its baseline to
46
- * another's, and the comparison would be between unrelated counters.
47
- */
48
- private readonly recoveryScope;
49
- constructor(config: SapConfig, logger?: ILogger | null, sessionId?: string, tokenRefresher?: ITokenRefresher);
50
- protected buildAuthorizationHeader(): string;
51
- /**
52
- * For the public entry point: this call is its own operation, always.
53
- *
54
- * A re-entrant `makeAdtRequest` — from a logger or a refresher callback the
55
- * connection itself invokes while a scope is live — is a new caller-visible
56
- * operation. Inheriting there would hand it a baseline from somebody else's
57
- * refresh, which reads as "already refreshed for me" and skips a refresh it
58
- * needs.
59
- */
60
- private inNewRecoveryScope;
61
- /** For the inner levels: join the operation in progress, or start one. */
62
- private inRecoveryScope;
63
- /**
64
- * The baseline this operation is reasoning from.
65
- *
66
- * The `active` check here and the one in `inRecoveryScope` OVERLAP: each
67
- * compensates for the other, and the stale-context test only fails when both
68
- * are removed. Kept as two because they answer different questions — "may I
69
- * join this scope" and "may I trust this baseline" — and a reader who finds
70
- * one redundant would be deleting half a guarantee.
71
- */
72
- private currentBaseline;
73
- /**
74
- * Fetch a new token, unless somebody already did for this operation.
75
- *
76
- * Single-flighted so two concurrent handlers — including two nested
77
- * `fetchCsrfToken` calls, which is a level `renewalInFlight` cannot reach —
78
- * share one network call instead of racing and leaving `currentToken` as
79
- * whichever settled last.
80
- *
81
- * @returns true when the caller may retry.
82
- */
83
- private refreshTokenOnce;
84
- private performTokenRefresh;
85
- /**
86
- * Renew the credential and the session it belongs to, once, shared.
87
- */
88
- private renewCredential;
89
- private performRenewal;
90
- /**
91
- * May this operation retry?
92
- *
93
- * The order of the checks is the design, not style.
94
- */
95
- private ensureRecovered;
96
- /**
97
- * Establishes the session for this auth type. Called by
98
- * AbstractAbapConnection.connect(), which owns the lifecycle around it.
99
- */
100
- protected establishSession(): Promise<void>;
101
- /**
102
- * Override makeAdtRequest to handle JWT auth errors with automatic token refresh
103
- */
104
- makeAdtRequest<T = any, D = any>(options: AbapRequestOptions): Promise<IAdtResponse<T, D>>;
105
- private attemptRequest;
106
- /**
107
- * Override fetchCsrfToken to handle JWT auth errors with automatic token refresh
108
- */
109
- protected fetchCsrfToken(url: string, retryCount?: number, retryDelay?: number,
110
- /** Fences the response effects; omitted during connect(), which has no lease. */
111
- generation?: number): Promise<string>;
112
- private attemptCsrfToken;
113
- private static validateConfig;
114
- }
115
- //# sourceMappingURL=JwtAbapConnection.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"JwtAbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/JwtAbapConnection.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,eAAe,EACrB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAuBrE;;;;;;;;;GASG;AACH,qBAAa,iBAAkB,SAAQ,sBAAsB;IAC3D,OAAO,CAAC,cAAc,CAAC,CAAkB;IACzC,OAAO,CAAC,YAAY,CAAS;IAE7B,6DAA6D;IAC7D,OAAO,CAAC,eAAe,CAAK;IAE5B,0EAA0E;IAC1E,OAAO,CAAC,oBAAoB,CAAC,CAAmB;IAEhD;;;;;;;;OAQG;IACH,OAAO,CAAC,mBAAmB,CAAK;IAEhC;;;;;;;OAOG;IACH,OAAO,CAAC,eAAe,CAAC,CAAmB;IAE3C;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA2C;gBAGvE,MAAM,EAAE,SAAS,EACjB,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,EACvB,SAAS,CAAC,EAAE,MAAM,EAClB,cAAc,CAAC,EAAE,eAAe;IAWlC,SAAS,CAAC,wBAAwB,IAAI,MAAM;IAW5C;;;;;;;;OAQG;IACH,OAAO,CAAC,kBAAkB;IAc1B,0EAA0E;IAC1E,OAAO,CAAC,eAAe;IASvB;;;;;;;;OAQG;IACH,OAAO,CAAC,eAAe;IAQvB;;;;;;;;;OASG;IACH,OAAO,CAAC,gBAAgB;YAsBV,mBAAmB;IAoBjC;;OAEG;IACH,OAAO,CAAC,eAAe;YAiBT,cAAc;IAwB5B;;;;OAIG;YACW,eAAe;IAyB7B;;;OAGG;cACa,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IA4BjD;;OAEG;IACG,cAAc,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG,EACnC,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAKhB,cAAc;IA8D5B;;OAEG;cACa,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,UAAU,GAAE,MAAgC,EAC5C,UAAU,GAAE,MAAgC;IAC5C,iFAAiF;IACjF,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,CAAC;YASJ,gBAAgB;IAkD9B,OAAO,CAAC,MAAM,CAAC,cAAc;CAa9B"}