@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.
- package/CHANGELOG.md +407 -1
- package/README.md +239 -53
- package/dist/auth/providers.d.ts +124 -0
- package/dist/auth/providers.d.ts.map +1 -0
- package/dist/auth/providers.js +183 -0
- package/dist/connection/AbstractAbapConnection.d.ts +209 -57
- package/dist/connection/AbstractAbapConnection.d.ts.map +1 -1
- package/dist/connection/AbstractAbapConnection.js +459 -457
- package/dist/connection/AdtCloudConnector.d.ts +34 -0
- package/dist/connection/AdtCloudConnector.d.ts.map +1 -0
- package/dist/connection/AdtCloudConnector.js +27 -0
- package/dist/connection/AdtOnPremConnector.d.ts +43 -0
- package/dist/connection/AdtOnPremConnector.d.ts.map +1 -0
- package/dist/connection/AdtOnPremConnector.js +28 -0
- package/dist/connection/CloudHttpTransport.d.ts +37 -0
- package/dist/connection/CloudHttpTransport.d.ts.map +1 -0
- package/dist/connection/CloudHttpTransport.js +145 -0
- package/dist/connection/CredentialAbapConnection.d.ts +55 -0
- package/dist/connection/CredentialAbapConnection.d.ts.map +1 -0
- package/dist/connection/CredentialAbapConnection.js +128 -0
- package/dist/connection/HttpTransport.d.ts +178 -0
- package/dist/connection/HttpTransport.d.ts.map +1 -0
- package/dist/connection/HttpTransport.js +402 -0
- package/dist/connection/IAdtTransport.d.ts +232 -0
- package/dist/connection/IAdtTransport.d.ts.map +1 -0
- package/dist/connection/IAdtTransport.js +28 -0
- package/dist/connection/LegacyOnPremHttpTransport.d.ts +40 -0
- package/dist/connection/LegacyOnPremHttpTransport.d.ts.map +1 -0
- package/dist/connection/LegacyOnPremHttpTransport.js +57 -0
- package/dist/connection/OnPremHttpTransport.d.ts +45 -0
- package/dist/connection/OnPremHttpTransport.d.ts.map +1 -0
- package/dist/connection/OnPremHttpTransport.js +91 -0
- package/dist/connection/RfcTransport.d.ts +89 -0
- package/dist/connection/RfcTransport.d.ts.map +1 -0
- package/dist/connection/RfcTransport.js +256 -0
- package/dist/connection/rfcConversation.d.ts +44 -0
- package/dist/connection/rfcConversation.d.ts.map +1 -0
- package/dist/connection/rfcConversation.js +71 -0
- package/dist/index.d.ts +10 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -18
- package/dist/session/SessionLifecycle.d.ts +17 -2
- package/dist/session/SessionLifecycle.d.ts.map +1 -1
- package/dist/session/SessionLifecycle.js +17 -2
- package/dist/utils/cookies.d.ts +12 -0
- package/dist/utils/cookies.d.ts.map +1 -0
- package/dist/utils/cookies.js +24 -0
- package/dist/utils/timeouts.d.ts +6 -3
- package/dist/utils/timeouts.d.ts.map +1 -1
- package/dist/utils/timeouts.js +6 -3
- package/docs/INDEX.md +5 -2
- package/docs/INSTALLATION.md +28 -6
- package/docs/JWT_AUTH_TOOLS.md +20 -4
- package/docs/MIGRATION-2.0.md +1 -1
- package/docs/MIGRATION-5.0.md +116 -0
- package/docs/MIGRATION-6.0.md +359 -0
- package/docs/SCOPE.md +1 -1
- package/docs/STATEFUL_SESSION_GUIDE.md +155 -20
- package/docs/USAGE.md +322 -111
- package/examples/basic-connection.js +15 -3
- package/examples/jwt-with-token-refresh.js +15 -7
- package/examples/saml-connection.js +15 -2
- package/package.json +12 -10
- package/dist/__tests__/helpers/session.d.ts +0 -15
- package/dist/__tests__/helpers/session.d.ts.map +0 -1
- package/dist/__tests__/helpers/session.js +0 -19
- package/dist/connection/BaseAbapConnection.d.ts +0 -23
- package/dist/connection/BaseAbapConnection.d.ts.map +0 -1
- package/dist/connection/BaseAbapConnection.js +0 -75
- package/dist/connection/CertificateAbapConnection.d.ts +0 -25
- package/dist/connection/CertificateAbapConnection.d.ts.map +0 -1
- package/dist/connection/CertificateAbapConnection.js +0 -79
- package/dist/connection/JwtAbapConnection.d.ts +0 -115
- package/dist/connection/JwtAbapConnection.d.ts.map +0 -1
- package/dist/connection/JwtAbapConnection.js +0 -358
- package/dist/connection/KerberosAbapConnection.d.ts +0 -24
- package/dist/connection/KerberosAbapConnection.d.ts.map +0 -1
- package/dist/connection/KerberosAbapConnection.js +0 -120
- package/dist/connection/RfcAbapConnection.d.ts +0 -49
- package/dist/connection/RfcAbapConnection.d.ts.map +0 -1
- package/dist/connection/RfcAbapConnection.js +0 -331
- package/dist/connection/SamlAbapConnection.d.ts +0 -25
- package/dist/connection/SamlAbapConnection.d.ts.map +0 -1
- package/dist/connection/SamlAbapConnection.js +0 -75
- package/dist/connection/connectionFactory.d.ts +0 -9
- package/dist/connection/connectionFactory.d.ts.map +0 -1
- 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 {
|
|
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
|
-
//
|
|
22
|
-
const connection =
|
|
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
|
|
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 {
|
|
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
|
-
//
|
|
66
|
-
//
|
|
67
|
-
|
|
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 {
|
|
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
|
-
|
|
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": "
|
|
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": "
|
|
39
|
-
"clean": "
|
|
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": "^
|
|
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"}
|