@mcp-abap-adt/auth-broker 0.1.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 +94 -0
- package/CONTRIBUTORS.md +33 -0
- package/LICENSE +22 -0
- package/README.md +166 -0
- package/dist/AuthBroker.d.ts +62 -0
- package/dist/AuthBroker.d.ts.map +1 -0
- package/dist/AuthBroker.js +219 -0
- package/dist/__tests__/testHelpers.d.ts +44 -0
- package/dist/__tests__/testHelpers.d.ts.map +1 -0
- package/dist/__tests__/testHelpers.js +129 -0
- package/dist/browserAuth.d.ts +17 -0
- package/dist/browserAuth.d.ts.map +1 -0
- package/dist/browserAuth.js +301 -0
- package/dist/cache.d.ts +20 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +46 -0
- package/dist/envLoader.d.ts +12 -0
- package/dist/envLoader.d.ts.map +1 -0
- package/dist/envLoader.js +90 -0
- package/dist/getToken.d.ts +14 -0
- package/dist/getToken.d.ts.map +1 -0
- package/dist/getToken.js +62 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/logger.d.ts +29 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +131 -0
- package/dist/pathResolver.d.ts +21 -0
- package/dist/pathResolver.d.ts.map +1 -0
- package/dist/pathResolver.js +105 -0
- package/dist/refreshToken.d.ts +14 -0
- package/dist/refreshToken.d.ts.map +1 -0
- package/dist/refreshToken.js +71 -0
- package/dist/serviceKeyLoader.d.ts +12 -0
- package/dist/serviceKeyLoader.d.ts.map +1 -0
- package/dist/serviceKeyLoader.js +72 -0
- package/dist/tokenRefresher.d.ts +17 -0
- package/dist/tokenRefresher.d.ts.map +1 -0
- package/dist/tokenRefresher.js +53 -0
- package/dist/tokenStorage.d.ts +15 -0
- package/dist/tokenStorage.d.ts.map +1 -0
- package/dist/tokenStorage.js +107 -0
- package/dist/tokenValidator.d.ts +11 -0
- package/dist/tokenValidator.d.ts.map +1 -0
- package/dist/tokenValidator.js +108 -0
- package/dist/types.d.ts +37 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/package.json +64 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Token storage - saves tokens to .env files
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.saveTokenToEnv = saveTokenToEnv;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
/**
|
|
43
|
+
* Save token to {destination}.env file
|
|
44
|
+
* @param destination Destination name
|
|
45
|
+
* @param savePath Path where to save the file
|
|
46
|
+
* @param config Configuration to save
|
|
47
|
+
*/
|
|
48
|
+
async function saveTokenToEnv(destination, savePath, config) {
|
|
49
|
+
// Ensure directory exists
|
|
50
|
+
if (!fs.existsSync(savePath)) {
|
|
51
|
+
fs.mkdirSync(savePath, { recursive: true });
|
|
52
|
+
}
|
|
53
|
+
const envFilePath = path.join(savePath, `${destination}.env`);
|
|
54
|
+
const tempFilePath = `${envFilePath}.tmp`;
|
|
55
|
+
// Read existing .env file if it exists
|
|
56
|
+
let existingContent = '';
|
|
57
|
+
if (fs.existsSync(envFilePath)) {
|
|
58
|
+
existingContent = fs.readFileSync(envFilePath, 'utf8');
|
|
59
|
+
}
|
|
60
|
+
// Parse existing content to preserve other values
|
|
61
|
+
const lines = existingContent.split('\n');
|
|
62
|
+
const existingVars = new Map();
|
|
63
|
+
for (const line of lines) {
|
|
64
|
+
const trimmed = line.trim();
|
|
65
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const match = trimmed.match(/^([^=]+)=(.*)$/);
|
|
69
|
+
if (match) {
|
|
70
|
+
const key = match[1].trim();
|
|
71
|
+
const value = match[2].trim().replace(/^["']|["']$/g, ''); // Remove quotes
|
|
72
|
+
existingVars.set(key, value);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Update with new values
|
|
76
|
+
existingVars.set('SAP_URL', config.sapUrl);
|
|
77
|
+
existingVars.set('SAP_JWT_TOKEN', config.jwtToken);
|
|
78
|
+
if (config.sapClient) {
|
|
79
|
+
existingVars.set('SAP_CLIENT', config.sapClient);
|
|
80
|
+
}
|
|
81
|
+
if (config.refreshToken) {
|
|
82
|
+
existingVars.set('SAP_REFRESH_TOKEN', config.refreshToken);
|
|
83
|
+
}
|
|
84
|
+
if (config.uaaUrl) {
|
|
85
|
+
existingVars.set('SAP_UAA_URL', config.uaaUrl);
|
|
86
|
+
}
|
|
87
|
+
if (config.uaaClientId) {
|
|
88
|
+
existingVars.set('SAP_UAA_CLIENT_ID', config.uaaClientId);
|
|
89
|
+
}
|
|
90
|
+
if (config.uaaClientSecret) {
|
|
91
|
+
existingVars.set('SAP_UAA_CLIENT_SECRET', config.uaaClientSecret);
|
|
92
|
+
}
|
|
93
|
+
// Write to temporary file first (atomic write)
|
|
94
|
+
const envLines = [];
|
|
95
|
+
for (const [key, value] of existingVars.entries()) {
|
|
96
|
+
// Escape value if it contains spaces or special characters
|
|
97
|
+
const escapedValue = value.includes(' ') || value.includes('=') || value.includes('#')
|
|
98
|
+
? `"${value.replace(/"/g, '\\"')}"`
|
|
99
|
+
: value;
|
|
100
|
+
envLines.push(`${key}=${escapedValue}`);
|
|
101
|
+
}
|
|
102
|
+
const envContent = envLines.join('\n') + '\n';
|
|
103
|
+
// Write to temp file
|
|
104
|
+
fs.writeFileSync(tempFilePath, envContent, 'utf8');
|
|
105
|
+
// Atomic rename
|
|
106
|
+
fs.renameSync(tempFilePath, envFilePath);
|
|
107
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token validator - validates JWT tokens by testing connection
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Validate JWT token by making a test request to SAP system
|
|
6
|
+
* @param token JWT token to validate
|
|
7
|
+
* @param sapUrl SAP system URL
|
|
8
|
+
* @returns true if token is valid, false if expired/invalid
|
|
9
|
+
*/
|
|
10
|
+
export declare function validateToken(token: string, sapUrl: string): Promise<boolean>;
|
|
11
|
+
//# sourceMappingURL=tokenValidator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenValidator.d.ts","sourceRoot":"","sources":["../src/tokenValidator.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA0EnF"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Token validator - validates JWT tokens by testing connection
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.validateToken = validateToken;
|
|
40
|
+
const axios_1 = __importStar(require("axios"));
|
|
41
|
+
/**
|
|
42
|
+
* Validate JWT token by making a test request to SAP system
|
|
43
|
+
* @param token JWT token to validate
|
|
44
|
+
* @param sapUrl SAP system URL
|
|
45
|
+
* @returns true if token is valid, false if expired/invalid
|
|
46
|
+
*/
|
|
47
|
+
async function validateToken(token, sapUrl) {
|
|
48
|
+
try {
|
|
49
|
+
// Make a lightweight request to test token validity
|
|
50
|
+
// Using /sap/bc/adt/core/discovery as it's a simple endpoint
|
|
51
|
+
const testUrl = `${sapUrl}/sap/bc/adt/core/discovery`;
|
|
52
|
+
const response = await axios_1.default.get(testUrl, {
|
|
53
|
+
headers: {
|
|
54
|
+
'Authorization': `Bearer ${token}`,
|
|
55
|
+
'Accept': 'application/xml',
|
|
56
|
+
},
|
|
57
|
+
timeout: 10000, // 10 second timeout
|
|
58
|
+
validateStatus: (status) => status < 500, // Don't throw on 4xx errors
|
|
59
|
+
});
|
|
60
|
+
// 200-299: Token is valid
|
|
61
|
+
if (response.status >= 200 && response.status < 300) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
// 401/403: Token expired or invalid (but distinguish from permission errors)
|
|
65
|
+
if (response.status === 401 || response.status === 403) {
|
|
66
|
+
const responseText = typeof response.data === 'string'
|
|
67
|
+
? response.data
|
|
68
|
+
: JSON.stringify(response.data || '');
|
|
69
|
+
// Check if it's a permission error (not auth error)
|
|
70
|
+
if (responseText.includes('ExceptionResourceNoAccess') ||
|
|
71
|
+
responseText.includes('No authorization') ||
|
|
72
|
+
responseText.includes('Missing authorization')) {
|
|
73
|
+
// Permission error - token is valid but user doesn't have access
|
|
74
|
+
// We consider this as valid token
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
// Auth error - token expired or invalid
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
// Other errors - assume token is valid (might be network issues, etc.)
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (error instanceof axios_1.AxiosError) {
|
|
85
|
+
// Network errors or timeouts - assume token might be valid
|
|
86
|
+
// (we don't want to invalidate tokens due to network issues)
|
|
87
|
+
if (error.code === 'ECONNABORTED' || error.code === 'ENOTFOUND' || error.code === 'ECONNREFUSED') {
|
|
88
|
+
// Network issue - assume token is valid
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
// 401/403 errors
|
|
92
|
+
if (error.response?.status === 401 || error.response?.status === 403) {
|
|
93
|
+
const responseText = typeof error.response.data === 'string'
|
|
94
|
+
? error.response.data
|
|
95
|
+
: JSON.stringify(error.response.data || '');
|
|
96
|
+
// Check if it's a permission error
|
|
97
|
+
if (responseText.includes('ExceptionResourceNoAccess') ||
|
|
98
|
+
responseText.includes('No authorization') ||
|
|
99
|
+
responseText.includes('Missing authorization')) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Unknown error - assume token is valid to avoid false negatives
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for auth-broker package
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Environment configuration loaded from .env file
|
|
6
|
+
*/
|
|
7
|
+
export interface EnvConfig {
|
|
8
|
+
sapUrl: string;
|
|
9
|
+
sapClient?: string;
|
|
10
|
+
jwtToken: string;
|
|
11
|
+
refreshToken?: string;
|
|
12
|
+
uaaUrl?: string;
|
|
13
|
+
uaaClientId?: string;
|
|
14
|
+
uaaClientSecret?: string;
|
|
15
|
+
language?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Service key structure from JSON file
|
|
19
|
+
*/
|
|
20
|
+
export interface ServiceKey {
|
|
21
|
+
url?: string;
|
|
22
|
+
abap?: {
|
|
23
|
+
url?: string;
|
|
24
|
+
client?: string;
|
|
25
|
+
language?: string;
|
|
26
|
+
};
|
|
27
|
+
sap_url?: string;
|
|
28
|
+
client?: string;
|
|
29
|
+
sap_client?: string;
|
|
30
|
+
language?: string;
|
|
31
|
+
uaa: {
|
|
32
|
+
url: string;
|
|
33
|
+
clientid: string;
|
|
34
|
+
clientsecret: string;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE;QACL,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE;QACH,GAAG,EAAE,MAAM,CAAC;QACZ,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;CACH"}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mcp-abap-adt/auth-broker",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "JWT authentication broker for MCP ABAP ADT - manages tokens based on destination headers",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"README.md",
|
|
10
|
+
"CHANGELOG.md",
|
|
11
|
+
"CONTRIBUTORS.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"keywords": [
|
|
15
|
+
"abap",
|
|
16
|
+
"sap",
|
|
17
|
+
"adt",
|
|
18
|
+
"jwt",
|
|
19
|
+
"authentication",
|
|
20
|
+
"token",
|
|
21
|
+
"broker",
|
|
22
|
+
"mcp",
|
|
23
|
+
"abap-adt"
|
|
24
|
+
],
|
|
25
|
+
"author": "Oleksii Kyslytsia <oleksij.kyslytsja@gmail.com>",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"homepage": "https://github.com/fr0ster/mcp-abap-adt-auth-broker#readme",
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/fr0ster/mcp-abap-adt-auth-broker/issues"
|
|
30
|
+
},
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/fr0ster/mcp-abap-adt-auth-broker.git"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"clean": "rm -rf dist tsconfig.tsbuildinfo",
|
|
40
|
+
"build": "npm run clean --silent && npx tsc -p tsconfig.json",
|
|
41
|
+
"build:fast": "npx tsc -p tsconfig.json",
|
|
42
|
+
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
|
|
43
|
+
"test:check": "npx tsc --noEmit",
|
|
44
|
+
"prepublishOnly": "npm run build"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=18.0.0"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@mcp-abap-adt/connection": "^0.1.12",
|
|
51
|
+
"axios": "^1.11.0",
|
|
52
|
+
"dotenv": "^17.2.1",
|
|
53
|
+
"express": "^5.1.0",
|
|
54
|
+
"open": "^11.0.0"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/express": "^5.0.5",
|
|
58
|
+
"@types/jest": "^30.0.0",
|
|
59
|
+
"@types/node": "^24.2.1",
|
|
60
|
+
"jest": "^30.2.0",
|
|
61
|
+
"ts-jest": "^29.2.5",
|
|
62
|
+
"typescript": "^5.9.2"
|
|
63
|
+
}
|
|
64
|
+
}
|