@mcp-abap-adt/auth-broker 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/README.md +59 -8
- package/dist/AuthBroker.d.ts.map +1 -1
- package/dist/AuthBroker.js +21 -5
- package/dist/bin/mcp-auth.js +85 -3
- package/dist/bin/mcp-sso.js +222 -40
- package/package.json +8 -3
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,26 @@ Thank you to all contributors! See [CONTRIBUTORS.md](CONTRIBUTORS.md) for the co
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [1.0.5] - 2026-02-12
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- `mcp-auth` subcommands: `auth-code`, `oidc`, `saml2-pure`, `saml2-bearer` (delegates to `mcp-sso`).
|
|
18
|
+
- `mcp-auth saml2-bearer` requires `--dev` and warns otherwise (in progress).
|
|
19
|
+
- `mcp-sso` subcommands: `oidc`, `saml2`, `bearer`.
|
|
20
|
+
- `mcp-sso`: `--saml-metadata` to resolve XSUAA token alias endpoint from SP metadata.
|
|
21
|
+
- `tests/sso-demo`: service key helper script with `--service/--key/--out` overrides.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- `mcp-sso`: allow `--token-endpoint` with `--service-key` for SAML bearer.
|
|
25
|
+
- Docs and test scripts updated for new subcommands.
|
|
26
|
+
- Bumped `@mcp-abap-adt/auth-providers` to `^1.0.5`.
|
|
27
|
+
|
|
28
|
+
## [1.0.4] - 2026-02-11
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
- `mcp-sso`: ensure auth config is seeded for broker and avoid writing placeholder secrets.
|
|
32
|
+
|
|
33
|
+
|
|
14
34
|
## [1.0.3] - 2026-02-11
|
|
15
35
|
|
|
16
36
|
### Fixed
|
package/README.md
CHANGED
|
@@ -644,6 +644,7 @@ const btpBrokerFull = new AuthBroker({
|
|
|
644
644
|
Generate or refresh `.env`/JSON output using AuthBroker + stores:
|
|
645
645
|
|
|
646
646
|
```bash
|
|
647
|
+
mcp-auth <auth-code|oidc|saml2-pure|saml2-bearer> [options]
|
|
647
648
|
mcp-auth --service-key <path> --output <path> [--env <path>] [--type abap|xsuaa] [--credential] [--browser auto|none|system|chrome|edge|firefox] [--format json|env]
|
|
648
649
|
```
|
|
649
650
|
|
|
@@ -660,6 +661,18 @@ mcp-auth --service-key <path> --output <path> [--env <path>] [--type abap|xsuaa]
|
|
|
660
661
|
|
|
661
662
|
**Examples:**
|
|
662
663
|
```bash
|
|
664
|
+
# Auth code (default via service key)
|
|
665
|
+
mcp-auth auth-code --service-key ./abap.json --output ./abap.env --type abap
|
|
666
|
+
|
|
667
|
+
# OIDC SSO (device flow example)
|
|
668
|
+
mcp-auth oidc --flow device --issuer https://issuer --client-id my-client --output ./sso.env --type xsuaa
|
|
669
|
+
|
|
670
|
+
# SAML2 pure (cookie)
|
|
671
|
+
mcp-auth saml2-pure --idp-sso-url https://idp/sso --sp-entity-id my-sp --output ./saml.env --type abap
|
|
672
|
+
|
|
673
|
+
# SAML2 bearer (in progress, requires --dev)
|
|
674
|
+
mcp-auth saml2-bearer --dev --service-key ./mcp.json --assertion <base64> --output ./sso.env --type xsuaa
|
|
675
|
+
|
|
663
676
|
# ABAP: authorization_code (default, opens browser)
|
|
664
677
|
mcp-auth --service-key ./abap.json --output ./abap.env --type abap
|
|
665
678
|
|
|
@@ -681,6 +694,7 @@ mcp-auth --env ./mcp.env --service-key ./mcp.json --output ./mcp.env --type xsua
|
|
|
681
694
|
Get tokens via SSO providers (OIDC/SAML) and generate `.env`/JSON output:
|
|
682
695
|
|
|
683
696
|
```bash
|
|
697
|
+
mcp-sso <oidc|saml2|bearer> [options]
|
|
684
698
|
mcp-sso --protocol <oidc|saml2> --flow <flow> --output <path> [--type abap|xsuaa] [--format env|json] [--env <path>] [--config <path>]
|
|
685
699
|
```
|
|
686
700
|
|
|
@@ -691,27 +705,64 @@ mcp-sso --protocol <oidc|saml2> --flow <flow> --output <path> [--type abap|xsuaa
|
|
|
691
705
|
**Examples:**
|
|
692
706
|
```bash
|
|
693
707
|
# OIDC browser flow
|
|
694
|
-
mcp-sso
|
|
708
|
+
mcp-sso oidc --flow browser --issuer https://issuer --client-id my-client --output ./sso.env --type xsuaa
|
|
695
709
|
|
|
696
710
|
# OIDC browser flow (manual code / OOB)
|
|
697
|
-
mcp-sso
|
|
711
|
+
mcp-sso oidc --flow browser --token-endpoint https://issuer/token --client-id my-client --code <auth_code> --redirect-uri urn:ietf:wg:oauth:2.0:oob --output ./sso.env --type xsuaa
|
|
698
712
|
|
|
699
713
|
# OIDC device flow
|
|
700
|
-
mcp-sso
|
|
714
|
+
mcp-sso oidc --flow device --issuer https://issuer --client-id my-client --output ./sso.env --type xsuaa
|
|
701
715
|
|
|
702
|
-
# OIDC password flow
|
|
703
|
-
mcp-sso
|
|
716
|
+
# OIDC password flow
|
|
717
|
+
mcp-sso oidc --flow password --token-endpoint https://issuer/oauth/token --client-id my-client --username user --password pass --output ./sso.env --type xsuaa
|
|
704
718
|
|
|
705
719
|
# OIDC token exchange
|
|
706
|
-
mcp-sso
|
|
720
|
+
mcp-sso oidc --flow token_exchange --issuer https://issuer --client-id my-client --subject-token <token> --output ./sso.env --type xsuaa
|
|
707
721
|
|
|
708
722
|
# SAML bearer flow (assertion -> token)
|
|
709
|
-
mcp-sso
|
|
723
|
+
mcp-sso bearer --idp-sso-url https://idp/sso --sp-entity-id my-sp --token-endpoint https://uaa.example/oauth/token --assertion <base64> --output ./sso.env --type xsuaa
|
|
710
724
|
|
|
711
725
|
# SAML pure flow (cookie)
|
|
712
|
-
mcp-sso
|
|
726
|
+
mcp-sso saml2 --flow pure --idp-sso-url https://idp/sso --sp-entity-id my-sp --assertion <base64> --cookie "SAP_SESSION=..." --output ./sso.env --type abap
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
**SAML token alias (XSUAA):**
|
|
730
|
+
If your IdP requires the token alias endpoint, pass SAML metadata XML:
|
|
731
|
+
|
|
732
|
+
```bash
|
|
733
|
+
mcp-sso bearer --saml-metadata ./saml-sp.xml --assertion <base64> --service-key ./service-key.json --output ./sso.env --type xsuaa
|
|
713
734
|
```
|
|
714
735
|
|
|
736
|
+
### Local Keycloak (OIDC + SAML Tests)
|
|
737
|
+
|
|
738
|
+
For local testing of `mcp-sso`, a ready-to-run Keycloak setup is included
|
|
739
|
+
(OIDC browser/password/device + SAML assertion capture).
|
|
740
|
+
|
|
741
|
+
```bash
|
|
742
|
+
cd tests/keycloak
|
|
743
|
+
docker compose up -d
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
Then use:
|
|
747
|
+
```bash
|
|
748
|
+
node dist/bin/mcp-sso.js \
|
|
749
|
+
oidc \
|
|
750
|
+
--flow browser \
|
|
751
|
+
--issuer http://localhost:8080/realms/mcp-sso \
|
|
752
|
+
--client-id mcp-sso-cli \
|
|
753
|
+
--scopes openid,profile,email \
|
|
754
|
+
--output /tmp/keycloak.env \
|
|
755
|
+
--type xsuaa
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
See `tests/keycloak/README.md` for device flow and SAML examples.
|
|
759
|
+
|
|
760
|
+
### XSUAA Demo (CAP)
|
|
761
|
+
|
|
762
|
+
A minimal CAP app for testing XSUAA flows is included at `tests/sso-demo`.
|
|
763
|
+
It enables `authorization_code` and `saml2-bearer` grant types and provides a
|
|
764
|
+
simple `CatalogService`. See `tests/sso-demo/readme.md` for deploy steps.
|
|
765
|
+
|
|
715
766
|
**Config file:**
|
|
716
767
|
You can pass a JSON file with provider config:
|
|
717
768
|
|
package/dist/AuthBroker.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthBroker.d.ts","sourceRoot":"","sources":["../src/AuthBroker.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,eAAe,EAGrB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EACV,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACd,MAAM,qBAAqB,CAAC;AA6C7B;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,mEAAmE;IACnE,YAAY,EAAE,aAAa,CAAC;IAC5B,uEAAuE;IACvE,eAAe,CAAC,EAAE,gBAAgB,CAAC;IACnC,4IAA4I;IAC5I,aAAa,EAAE,cAAc,CAAC;IAC9B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;GAEG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,MAAM,CAAU;IACxB,OAAO,CAAC,eAAe,CAA+B;IACtD,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,aAAa,CAAiB;IACtC,OAAO,CAAC,gBAAgB,CAAU;IAElC;;;;;;;;;;;OAWG;gBACS,MAAM,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO;IAsFxE;;OAEG;YACW,eAAe;IA0D7B;;OAEG;YACW,aAAa;IAoD3B;;OAEG;YACW,oCAAoC;IA4ClD;;OAEG;YACW,kBAAkB;
|
|
1
|
+
{"version":3,"file":"AuthBroker.d.ts","sourceRoot":"","sources":["../src/AuthBroker.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,eAAe,EAGrB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EACV,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACd,MAAM,qBAAqB,CAAC;AA6C7B;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,mEAAmE;IACnE,YAAY,EAAE,aAAa,CAAC;IAC5B,uEAAuE;IACvE,eAAe,CAAC,EAAE,gBAAgB,CAAC;IACnC,4IAA4I;IAC5I,aAAa,EAAE,cAAc,CAAC;IAC9B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;GAEG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,MAAM,CAAU;IACxB,OAAO,CAAC,eAAe,CAA+B;IACtD,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,aAAa,CAAiB;IACtC,OAAO,CAAC,gBAAgB,CAAU;IAElC;;;;;;;;;;;OAWG;gBACS,MAAM,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO;IAsFxE;;OAEG;YACW,eAAe;IA0D7B;;OAEG;YACW,aAAa;IAoD3B;;OAEG;YACW,oCAAoC;IA4ClD;;OAEG;YACW,kBAAkB;YA4ClB,aAAa;YA0Db,kBAAkB;IAkDhC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAuCG;IACG,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAwKpD;;;;;OAKG;IACG,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IASxD;;;;OAIG;IACG,sBAAsB,CAC1B,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAoEvC;;;;OAIG;IACG,mBAAmB,CACvB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAuEpC;;;;;;;;;;;;;;;;OAgBG;IACH,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,eAAe;CAqB3D"}
|
package/dist/AuthBroker.js
CHANGED
|
@@ -244,12 +244,19 @@ class AuthBroker {
|
|
|
244
244
|
this.logger?.error(`Failed to save connection config to session for ${destination}: ${getErrorMessage(error)}`);
|
|
245
245
|
throw new Error(`Failed to save connection config for destination "${destination}": ${getErrorMessage(error)}`);
|
|
246
246
|
}
|
|
247
|
-
|
|
248
|
-
|
|
247
|
+
if (authorizationConfig.uaaUrl &&
|
|
248
|
+
authorizationConfig.uaaClientId &&
|
|
249
|
+
authorizationConfig.uaaClientSecret) {
|
|
250
|
+
try {
|
|
251
|
+
await this.sessionStore.setAuthorizationConfig(destination, authorizationConfig);
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
this.logger?.error(`Failed to save authorization config to session for ${destination}: ${getErrorMessage(error)}`);
|
|
255
|
+
throw new Error(`Failed to save authorization config for destination "${destination}": ${getErrorMessage(error)}`);
|
|
256
|
+
}
|
|
249
257
|
}
|
|
250
|
-
|
|
251
|
-
this.logger?.
|
|
252
|
-
throw new Error(`Failed to save authorization config for destination "${destination}": ${getErrorMessage(error)}`);
|
|
258
|
+
else {
|
|
259
|
+
this.logger?.debug(`Skipping authorization config save for ${destination}: missing UAA fields`);
|
|
253
260
|
}
|
|
254
261
|
}
|
|
255
262
|
async requestTokens(destination, sourceLabel) {
|
|
@@ -433,6 +440,15 @@ class AuthBroker {
|
|
|
433
440
|
throw error;
|
|
434
441
|
}
|
|
435
442
|
if (!this.serviceKeyStore) {
|
|
443
|
+
const tokenType = this.tokenProvider?.tokenType;
|
|
444
|
+
if (tokenType === 'saml') {
|
|
445
|
+
const tokenResult = await this.requestTokens(destination, 'session');
|
|
446
|
+
await this.persistTokenResult(destination, serviceUrl, connConfig, authConfig || {}, tokenResult);
|
|
447
|
+
this.logger?.info(`[AuthBroker] Token retrieved for ${destination} (SAML without auth config)`, {
|
|
448
|
+
authorizationToken: (0, formatting_1.formatToken)(tokenResult.authorizationToken),
|
|
449
|
+
});
|
|
450
|
+
return tokenResult.authorizationToken;
|
|
451
|
+
}
|
|
436
452
|
if (lastError) {
|
|
437
453
|
throw lastError;
|
|
438
454
|
}
|
package/dist/bin/mcp-auth.js
CHANGED
|
@@ -53,6 +53,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
53
53
|
};
|
|
54
54
|
})();
|
|
55
55
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
56
|
+
const child_process_1 = require("child_process");
|
|
56
57
|
const fs = __importStar(require("fs"));
|
|
57
58
|
const path = __importStar(require("path"));
|
|
58
59
|
// Use require for CommonJS dist files with absolute path
|
|
@@ -86,6 +87,7 @@ function showHelp() {
|
|
|
86
87
|
console.log('MCP Auth - Get tokens and generate .env files from service keys');
|
|
87
88
|
console.log('');
|
|
88
89
|
console.log('Usage:');
|
|
90
|
+
console.log(' mcp-auth <auth-code|oidc|saml2-pure|saml2-bearer> [options]');
|
|
89
91
|
console.log(' mcp-auth --service-key <path> --output <path> [options]');
|
|
90
92
|
console.log('');
|
|
91
93
|
console.log('Required Options:');
|
|
@@ -97,6 +99,7 @@ function showHelp() {
|
|
|
97
99
|
console.log('');
|
|
98
100
|
console.log('Optional Options:');
|
|
99
101
|
console.log(' --type <type> Auth type: abap or xsuaa (default: abap)');
|
|
102
|
+
console.log(' --dev Enable in-progress commands (saml2-bearer)');
|
|
100
103
|
console.log(' --credential Use client_credentials flow (clientId/clientSecret, no browser)');
|
|
101
104
|
console.log(' By default uses authorization_code flow');
|
|
102
105
|
console.log(' --browser <browser> Browser for authorization_code flow (default: auto):');
|
|
@@ -111,6 +114,18 @@ function showHelp() {
|
|
|
111
114
|
console.log(' --help, -h Show this help message');
|
|
112
115
|
console.log('');
|
|
113
116
|
console.log('Examples:');
|
|
117
|
+
console.log(' # Auth code (default flow via service key)');
|
|
118
|
+
console.log(' mcp-auth auth-code --service-key ./service-key.json --output ./mcp.env --type xsuaa');
|
|
119
|
+
console.log('');
|
|
120
|
+
console.log(' # OIDC SSO (device/password/browser/token-exchange)');
|
|
121
|
+
console.log(' mcp-auth oidc --flow device --issuer https://issuer --client-id my-client --output ./sso.env --type xsuaa');
|
|
122
|
+
console.log('');
|
|
123
|
+
console.log(' # SAML2 pure (cookies)');
|
|
124
|
+
console.log(' mcp-auth saml2-pure --idp-sso-url https://idp/sso --sp-entity-id my-sp --output ./saml.env --type abap');
|
|
125
|
+
console.log('');
|
|
126
|
+
console.log(' # SAML2 bearer (in progress, requires --dev)');
|
|
127
|
+
console.log(' mcp-auth saml2-bearer --dev --service-key ./service-key.json --assertion <base64> --output ./sso.env --type xsuaa');
|
|
128
|
+
console.log('');
|
|
114
129
|
console.log(' # XSUAA with authorization_code (default, opens browser)');
|
|
115
130
|
console.log(' mcp-auth --service-key ./service-key.json --output ./mcp.env --type xsuaa');
|
|
116
131
|
console.log('');
|
|
@@ -151,8 +166,7 @@ function showHelp() {
|
|
|
151
166
|
console.log(' - For ABAP, serviceUrl (SAP URL) is required - can be provided via --service-url or service key');
|
|
152
167
|
console.log(' - SAP_URL/XSUAA_MCP_URL is written to .env (from --service-url, service key, or placeholder)');
|
|
153
168
|
}
|
|
154
|
-
function parseArgs() {
|
|
155
|
-
const args = process.argv.slice(2);
|
|
169
|
+
function parseArgs(args = process.argv.slice(2)) {
|
|
156
170
|
// Handle --version and --help first
|
|
157
171
|
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
|
|
158
172
|
showHelp();
|
|
@@ -348,8 +362,76 @@ function writeJsonFile(outputPath, token, refreshToken, serviceUrl, uaaUrl, uaaC
|
|
|
348
362
|
// Write to file
|
|
349
363
|
fs.writeFileSync(outputPath, JSON.stringify(outputData, null, 2), 'utf8');
|
|
350
364
|
}
|
|
365
|
+
function runMcpSso(args) {
|
|
366
|
+
const mcpSsoPath = path.resolve(__dirname, 'mcp-sso.js');
|
|
367
|
+
const result = (0, child_process_1.spawnSync)(process.execPath, [mcpSsoPath, ...args], {
|
|
368
|
+
stdio: 'inherit',
|
|
369
|
+
});
|
|
370
|
+
if (result.error) {
|
|
371
|
+
throw result.error;
|
|
372
|
+
}
|
|
373
|
+
process.exit(result.status ?? 1);
|
|
374
|
+
}
|
|
351
375
|
async function main() {
|
|
352
|
-
const
|
|
376
|
+
const rawArgs = process.argv.slice(2);
|
|
377
|
+
const subcommand = rawArgs[0];
|
|
378
|
+
const hasSubcommand = subcommand && !subcommand.startsWith('-') && subcommand.length > 0;
|
|
379
|
+
if (hasSubcommand) {
|
|
380
|
+
const remaining = rawArgs.slice(1);
|
|
381
|
+
const ensureNoProtocol = () => {
|
|
382
|
+
if (remaining.includes('--protocol')) {
|
|
383
|
+
console.error('❌ --protocol is not supported with subcommands.');
|
|
384
|
+
process.exit(1);
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
switch (subcommand) {
|
|
388
|
+
case 'auth-code': {
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
case 'oidc': {
|
|
392
|
+
ensureNoProtocol();
|
|
393
|
+
runMcpSso(['oidc', ...remaining]);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
case 'saml2-pure': {
|
|
397
|
+
ensureNoProtocol();
|
|
398
|
+
if (remaining.includes('--flow')) {
|
|
399
|
+
const idx = remaining.indexOf('--flow');
|
|
400
|
+
const flow = remaining[idx + 1];
|
|
401
|
+
if (flow && flow !== 'pure') {
|
|
402
|
+
console.error('❌ saml2-pure requires --flow pure.');
|
|
403
|
+
process.exit(1);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
remaining.unshift('pure');
|
|
408
|
+
remaining.unshift('--flow');
|
|
409
|
+
}
|
|
410
|
+
runMcpSso(['saml2', ...remaining]);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
case 'saml2-bearer': {
|
|
414
|
+
ensureNoProtocol();
|
|
415
|
+
if (!remaining.includes('--dev')) {
|
|
416
|
+
console.error('⚠️ saml2-bearer is in progress. Re-run with --dev to enable.');
|
|
417
|
+
process.exit(1);
|
|
418
|
+
}
|
|
419
|
+
const filtered = remaining.filter((arg) => arg !== '--dev');
|
|
420
|
+
if (filtered.includes('--flow')) {
|
|
421
|
+
console.error('❌ saml2-bearer does not accept --flow.');
|
|
422
|
+
process.exit(1);
|
|
423
|
+
}
|
|
424
|
+
runMcpSso(['bearer', ...filtered]);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
default: {
|
|
428
|
+
console.error(`Unknown command: ${subcommand}`);
|
|
429
|
+
showHelp();
|
|
430
|
+
process.exit(1);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
const options = parseArgs(hasSubcommand ? rawArgs.slice(1) : rawArgs);
|
|
353
435
|
if (!options) {
|
|
354
436
|
// Help or version was shown, exit already handled
|
|
355
437
|
return;
|
package/dist/bin/mcp-sso.js
CHANGED
|
@@ -4,26 +4,27 @@
|
|
|
4
4
|
* MCP SSO - Get tokens via SSO providers and generate .env files
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
|
+
* mcp-sso <oidc|saml2|bearer> [options]
|
|
7
8
|
* mcp-sso --protocol <oidc|saml2> --flow <flow> --output <path> [options]
|
|
8
9
|
*
|
|
9
10
|
* Examples:
|
|
10
11
|
* # OIDC browser flow (authorization code with local callback)
|
|
11
|
-
* mcp-sso
|
|
12
|
+
* mcp-sso oidc --flow browser --issuer https://issuer --client-id my-client --output ./sso.env --type xsuaa
|
|
12
13
|
*
|
|
13
14
|
* # OIDC device flow
|
|
14
|
-
* mcp-sso
|
|
15
|
+
* mcp-sso oidc --flow device --issuer https://issuer --client-id my-client --output ./sso.env --type xsuaa
|
|
15
16
|
*
|
|
16
|
-
* # OIDC password flow
|
|
17
|
-
* mcp-sso
|
|
17
|
+
* # OIDC password flow
|
|
18
|
+
* mcp-sso oidc --flow password --token-endpoint https://issuer/oauth/token --client-id my-client --username user --password pass --output ./sso.env --type xsuaa
|
|
18
19
|
*
|
|
19
20
|
* # OIDC token exchange
|
|
20
|
-
* mcp-sso
|
|
21
|
+
* mcp-sso oidc --flow token_exchange --issuer https://issuer --client-id my-client --subject-token <token> --output ./sso.env --type xsuaa
|
|
21
22
|
*
|
|
22
23
|
* # SAML bearer flow
|
|
23
|
-
* mcp-sso
|
|
24
|
+
* mcp-sso bearer --idp-sso-url https://idp/sso --sp-entity-id my-sp --token-endpoint https://uaa.example/oauth/token --assertion <base64> --output ./sso.env --type xsuaa
|
|
24
25
|
*
|
|
25
26
|
* # SAML pure flow (cookie)
|
|
26
|
-
* mcp-sso
|
|
27
|
+
* mcp-sso saml2 --flow pure --idp-sso-url https://idp/sso --sp-entity-id my-sp --assertion <base64> --cookie "SAP_SESSION=..." --output ./sso.env --type abap
|
|
27
28
|
*/
|
|
28
29
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
29
30
|
if (k2 === undefined) k2 = k;
|
|
@@ -58,17 +59,14 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
58
59
|
return result;
|
|
59
60
|
};
|
|
60
61
|
})();
|
|
61
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
62
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
63
|
-
};
|
|
64
62
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
65
63
|
const node_readline_1 = require("node:readline");
|
|
66
|
-
const axios_1 = __importDefault(require("axios"));
|
|
67
64
|
const fs = __importStar(require("fs"));
|
|
68
65
|
const path = __importStar(require("path"));
|
|
69
66
|
// Use require for CommonJS dist files with absolute path
|
|
70
67
|
const distPath = path.resolve(__dirname, '..', 'index.js');
|
|
71
68
|
const { AuthBroker } = require(distPath);
|
|
69
|
+
const logger_1 = require("@mcp-abap-adt/logger");
|
|
72
70
|
const auth_providers_1 = require("@mcp-abap-adt/auth-providers");
|
|
73
71
|
const auth_stores_1 = require("@mcp-abap-adt/auth-stores");
|
|
74
72
|
function getVersion() {
|
|
@@ -97,14 +95,16 @@ function showHelp() {
|
|
|
97
95
|
console.log('MCP SSO - Get tokens via SSO providers and generate .env files');
|
|
98
96
|
console.log('');
|
|
99
97
|
console.log('Usage:');
|
|
98
|
+
console.log(' mcp-sso <oidc|saml2|bearer> [options]');
|
|
100
99
|
console.log(' mcp-sso --protocol <oidc|saml2> --flow <flow> --output <path> [options]');
|
|
101
100
|
console.log('');
|
|
102
101
|
console.log('Required Options:');
|
|
103
102
|
console.log(' --output <path> Output file path');
|
|
104
|
-
console.log(' --protocol <oidc|saml2> Protocol');
|
|
105
|
-
console.log(' --flow <flow> Flow for protocol');
|
|
103
|
+
console.log(' --protocol <oidc|saml2> Protocol (if no subcommand)');
|
|
104
|
+
console.log(' --flow <flow> Flow for protocol (if no subcommand)');
|
|
106
105
|
console.log('');
|
|
107
106
|
console.log('Common Options:');
|
|
107
|
+
console.log(' --service-key <path> Service key JSON (XSUAA/ABAP)');
|
|
108
108
|
console.log(' --type <abap|xsuaa> Output type (default: abap)');
|
|
109
109
|
console.log(' --format <env|json> Output format (default: env)');
|
|
110
110
|
console.log(' --env <path> Optional existing env file (used for refresh)');
|
|
@@ -134,12 +134,12 @@ function showHelp() {
|
|
|
134
134
|
console.log(' --actor-token <token> Actor token for token exchange');
|
|
135
135
|
console.log(' --actor-token-type <type> Actor token type for token exchange');
|
|
136
136
|
console.log(' --uaa-url <url> UAA base URL (used to build token endpoint)');
|
|
137
|
-
console.log(' --cf-api <url> CF API URL (resolve login/uaa endpoints)');
|
|
138
137
|
console.log('');
|
|
139
138
|
console.log('SAML Options:');
|
|
140
139
|
console.log(' --idp-sso-url <url> IdP SSO URL');
|
|
141
140
|
console.log(' --sp-entity-id <id> SP Entity ID');
|
|
142
141
|
console.log(' --acs-url <url> ACS URL (default: http://localhost:<port>/callback)');
|
|
142
|
+
console.log(' --saml-metadata <path> SAML metadata XML (to resolve token alias)');
|
|
143
143
|
console.log(' --relay-state <value> RelayState (optional)');
|
|
144
144
|
console.log(' --assertion-flow <flow> browser|manual|assertion (default: browser)');
|
|
145
145
|
console.log(' --assertion <base64> SAMLResponse (base64)');
|
|
@@ -170,8 +170,47 @@ function readManualInput(prompt) {
|
|
|
170
170
|
});
|
|
171
171
|
});
|
|
172
172
|
}
|
|
173
|
+
function createCliLogger(prefix = 'SSO') {
|
|
174
|
+
const isEnabled = () => {
|
|
175
|
+
if (process.env.DEBUG_SSO === 'false' ||
|
|
176
|
+
process.env.DEBUG_AUTH_SSO === 'false') {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
if (process.env.DEBUG_SSO === 'true' ||
|
|
180
|
+
process.env.DEBUG_AUTH_SSO === 'true' ||
|
|
181
|
+
process.env.DEBUG === 'true' ||
|
|
182
|
+
process.env.DEBUG?.includes('sso') === true ||
|
|
183
|
+
process.env.DEBUG?.includes('auth-sso') === true) {
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
return false;
|
|
187
|
+
};
|
|
188
|
+
const baseLogger = new logger_1.DefaultLogger((0, logger_1.getLogLevel)());
|
|
189
|
+
return {
|
|
190
|
+
debug: (message, meta) => {
|
|
191
|
+
if (isEnabled()) {
|
|
192
|
+
baseLogger.debug(`[${prefix}] ${message}`, meta);
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
info: (message, meta) => {
|
|
196
|
+
if (isEnabled()) {
|
|
197
|
+
baseLogger.info(`[${prefix}] ${message}`, meta);
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
warn: (message, meta) => {
|
|
201
|
+
if (isEnabled()) {
|
|
202
|
+
baseLogger.warn(`[${prefix}] ${message}`, meta);
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
error: (message, meta) => {
|
|
206
|
+
if (isEnabled()) {
|
|
207
|
+
baseLogger.error(`[${prefix}] ${message}`, meta);
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
}
|
|
173
212
|
function parseArgs() {
|
|
174
|
-
|
|
213
|
+
let args = process.argv.slice(2);
|
|
175
214
|
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
|
|
176
215
|
showHelp();
|
|
177
216
|
process.exit(0);
|
|
@@ -183,6 +222,7 @@ function parseArgs() {
|
|
|
183
222
|
let outputFile;
|
|
184
223
|
let envFilePath;
|
|
185
224
|
let destination;
|
|
225
|
+
let serviceKeyPath;
|
|
186
226
|
let authType = 'abap';
|
|
187
227
|
let format = 'env';
|
|
188
228
|
let protocol;
|
|
@@ -217,7 +257,26 @@ function parseArgs() {
|
|
|
217
257
|
let assertion;
|
|
218
258
|
let cookie;
|
|
219
259
|
let uaaUrl;
|
|
220
|
-
let
|
|
260
|
+
let samlMetadataPath;
|
|
261
|
+
const firstArg = args[0];
|
|
262
|
+
if (firstArg && !firstArg.startsWith('-')) {
|
|
263
|
+
if (firstArg === 'oidc') {
|
|
264
|
+
protocol = 'oidc';
|
|
265
|
+
}
|
|
266
|
+
else if (firstArg === 'saml2') {
|
|
267
|
+
protocol = 'saml2';
|
|
268
|
+
}
|
|
269
|
+
else if (firstArg === 'bearer') {
|
|
270
|
+
protocol = 'saml2';
|
|
271
|
+
flow = 'bearer';
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
console.error(`Unknown command: ${firstArg}`);
|
|
275
|
+
showHelp();
|
|
276
|
+
process.exit(1);
|
|
277
|
+
}
|
|
278
|
+
args = args.slice(1);
|
|
279
|
+
}
|
|
221
280
|
for (let i = 0; i < args.length; i++) {
|
|
222
281
|
const arg = args[i];
|
|
223
282
|
const next = i + 1 < args.length ? args[i + 1] : undefined;
|
|
@@ -230,6 +289,10 @@ function parseArgs() {
|
|
|
230
289
|
envFilePath = next;
|
|
231
290
|
i++;
|
|
232
291
|
break;
|
|
292
|
+
case '--service-key':
|
|
293
|
+
serviceKeyPath = next;
|
|
294
|
+
i++;
|
|
295
|
+
break;
|
|
233
296
|
case '--destination':
|
|
234
297
|
destination = next;
|
|
235
298
|
i++;
|
|
@@ -402,8 +465,8 @@ function parseArgs() {
|
|
|
402
465
|
uaaUrl = next;
|
|
403
466
|
i++;
|
|
404
467
|
break;
|
|
405
|
-
case '--
|
|
406
|
-
|
|
468
|
+
case '--saml-metadata':
|
|
469
|
+
samlMetadataPath = next;
|
|
407
470
|
i++;
|
|
408
471
|
break;
|
|
409
472
|
default:
|
|
@@ -414,6 +477,7 @@ function parseArgs() {
|
|
|
414
477
|
outputFile,
|
|
415
478
|
envFilePath,
|
|
416
479
|
destination,
|
|
480
|
+
serviceKeyPath,
|
|
417
481
|
authType,
|
|
418
482
|
format,
|
|
419
483
|
protocol,
|
|
@@ -448,9 +512,14 @@ function parseArgs() {
|
|
|
448
512
|
assertion,
|
|
449
513
|
cookie,
|
|
450
514
|
uaaUrl,
|
|
451
|
-
|
|
515
|
+
samlMetadataPath,
|
|
452
516
|
};
|
|
453
517
|
}
|
|
518
|
+
function resolveSamlTokenAlias(metadataXml) {
|
|
519
|
+
const regex = /<md:AssertionConsumerService[^>]*Location="([^"]*\/oauth\/token\/alias\/[^"]+)"/i;
|
|
520
|
+
const match = metadataXml.match(regex);
|
|
521
|
+
return match?.[1];
|
|
522
|
+
}
|
|
454
523
|
function normalizeProviderConfig(raw) {
|
|
455
524
|
if (!raw || typeof raw !== 'object') {
|
|
456
525
|
return null;
|
|
@@ -520,10 +589,13 @@ function buildSamlConfig(options) {
|
|
|
520
589
|
: assertionFlow === 'assertion'
|
|
521
590
|
? async () => readManualInput('Paste SAMLResponse: ')
|
|
522
591
|
: undefined;
|
|
523
|
-
const cookieProvider = async () => {
|
|
592
|
+
const cookieProvider = async (samlResponse) => {
|
|
524
593
|
if (options.cookie) {
|
|
525
594
|
return options.cookie;
|
|
526
595
|
}
|
|
596
|
+
if (assertionFlow === 'assertion') {
|
|
597
|
+
return `SAMLResponse=${samlResponse}`;
|
|
598
|
+
}
|
|
527
599
|
return readManualInput('Paste session cookies: ');
|
|
528
600
|
};
|
|
529
601
|
return {
|
|
@@ -632,18 +704,12 @@ function buildProviderConfig(options, existingAuth, existingConn, fileConfig) {
|
|
|
632
704
|
}
|
|
633
705
|
return result;
|
|
634
706
|
}
|
|
635
|
-
async function resolveCfEndpoints(apiUrl) {
|
|
636
|
-
const response = await axios_1.default.get(apiUrl);
|
|
637
|
-
const data = response.data;
|
|
638
|
-
const loginUrl = data?.links?.login?.href;
|
|
639
|
-
const uaaUrl = data?.links?.uaa?.href;
|
|
640
|
-
return { loginUrl, uaaUrl };
|
|
641
|
-
}
|
|
642
707
|
async function main() {
|
|
643
708
|
const options = parseArgs();
|
|
644
709
|
if (!options) {
|
|
645
710
|
return;
|
|
646
711
|
}
|
|
712
|
+
const logger = createCliLogger();
|
|
647
713
|
if (!options.outputFile) {
|
|
648
714
|
console.error('❌ Missing required --output');
|
|
649
715
|
process.exit(1);
|
|
@@ -653,6 +719,19 @@ async function main() {
|
|
|
653
719
|
? path.resolve(options.envFilePath)
|
|
654
720
|
: undefined;
|
|
655
721
|
let destination = options.destination;
|
|
722
|
+
if (options.serviceKeyPath) {
|
|
723
|
+
const resolvedServiceKeyPath = path.resolve(options.serviceKeyPath);
|
|
724
|
+
if (!fs.existsSync(resolvedServiceKeyPath)) {
|
|
725
|
+
console.error(`❌ Service key file not found: ${resolvedServiceKeyPath}`);
|
|
726
|
+
process.exit(1);
|
|
727
|
+
}
|
|
728
|
+
const serviceKeyFileName = path.basename(resolvedServiceKeyPath, path.extname(resolvedServiceKeyPath));
|
|
729
|
+
if (destination && destination !== serviceKeyFileName) {
|
|
730
|
+
console.error(`❌ Destination mismatch: service key (${serviceKeyFileName}) vs output (${destination})`);
|
|
731
|
+
process.exit(1);
|
|
732
|
+
}
|
|
733
|
+
destination = serviceKeyFileName;
|
|
734
|
+
}
|
|
656
735
|
if (!destination) {
|
|
657
736
|
destination = path.basename(resolvedOutputPath, path.extname(resolvedOutputPath));
|
|
658
737
|
}
|
|
@@ -663,6 +742,24 @@ async function main() {
|
|
|
663
742
|
process.exit(1);
|
|
664
743
|
}
|
|
665
744
|
}
|
|
745
|
+
const allowTokenEndpointWithServiceKey = options.protocol === 'saml2' && options.flow === 'bearer';
|
|
746
|
+
const serviceKeyConflicts = options.serviceKeyPath &&
|
|
747
|
+
(options.configPath ||
|
|
748
|
+
options.issuerUrl ||
|
|
749
|
+
options.authorizationEndpoint ||
|
|
750
|
+
(!allowTokenEndpointWithServiceKey && options.tokenEndpoint) ||
|
|
751
|
+
options.deviceAuthorizationEndpoint ||
|
|
752
|
+
options.clientId ||
|
|
753
|
+
options.clientSecret ||
|
|
754
|
+
options.uaaUrl);
|
|
755
|
+
if (serviceKeyConflicts) {
|
|
756
|
+
console.error('❌ Use either --service-key or explicit OIDC/SAML parameters (issuer/token/client/uaa).');
|
|
757
|
+
process.exit(1);
|
|
758
|
+
}
|
|
759
|
+
if (options.serviceKeyPath && options.authType !== 'xsuaa') {
|
|
760
|
+
console.error('❌ --service-key is supported only for XSUAA flows.');
|
|
761
|
+
process.exit(1);
|
|
762
|
+
}
|
|
666
763
|
let providerConfigFromFile = null;
|
|
667
764
|
if (options.configPath) {
|
|
668
765
|
const resolvedConfigPath = path.resolve(options.configPath);
|
|
@@ -677,13 +774,50 @@ async function main() {
|
|
|
677
774
|
process.exit(1);
|
|
678
775
|
}
|
|
679
776
|
}
|
|
680
|
-
if (options.
|
|
681
|
-
const
|
|
682
|
-
|
|
683
|
-
|
|
777
|
+
if (options.serviceKeyPath) {
|
|
778
|
+
const resolvedServiceKeyPath = path.resolve(options.serviceKeyPath);
|
|
779
|
+
const serviceKeyDir = path.dirname(resolvedServiceKeyPath);
|
|
780
|
+
const serviceKeyStore = new auth_stores_1.XsuaaServiceKeyStore(serviceKeyDir);
|
|
781
|
+
const authConfig = await serviceKeyStore.getAuthorizationConfig(destination);
|
|
782
|
+
if (!authConfig) {
|
|
783
|
+
console.error(`❌ Authorization config not found for ${destination}. Service key must contain clientid, clientsecret, and url fields.`);
|
|
784
|
+
process.exit(1);
|
|
684
785
|
}
|
|
685
|
-
|
|
686
|
-
|
|
786
|
+
const uaaUrl = authConfig.uaaUrl;
|
|
787
|
+
if (!uaaUrl) {
|
|
788
|
+
console.error(`❌ Service key missing UAA URL for ${destination}.`);
|
|
789
|
+
process.exit(1);
|
|
790
|
+
}
|
|
791
|
+
options.uaaUrl = uaaUrl;
|
|
792
|
+
options.clientId = authConfig.uaaClientId;
|
|
793
|
+
options.clientSecret = authConfig.uaaClientSecret;
|
|
794
|
+
if (!options.issuerUrl) {
|
|
795
|
+
options.issuerUrl = uaaUrl;
|
|
796
|
+
}
|
|
797
|
+
if (!options.tokenEndpoint) {
|
|
798
|
+
options.tokenEndpoint = `${uaaUrl.replace(/\/+$/, '')}/oauth/token`;
|
|
799
|
+
}
|
|
800
|
+
if (!options.authorizationEndpoint) {
|
|
801
|
+
options.authorizationEndpoint = `${uaaUrl.replace(/\/+$/, '')}/oauth/authorize`;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
if (options.samlMetadataPath) {
|
|
805
|
+
const resolvedMetadataPath = path.resolve(options.samlMetadataPath);
|
|
806
|
+
if (!fs.existsSync(resolvedMetadataPath)) {
|
|
807
|
+
console.error(`❌ SAML metadata file not found: ${resolvedMetadataPath}`);
|
|
808
|
+
process.exit(1);
|
|
809
|
+
}
|
|
810
|
+
const metadataXml = fs.readFileSync(resolvedMetadataPath, 'utf8');
|
|
811
|
+
const aliasUrl = resolveSamlTokenAlias(metadataXml);
|
|
812
|
+
if (!aliasUrl) {
|
|
813
|
+
console.error('❌ SAML metadata does not contain token alias endpoint.');
|
|
814
|
+
process.exit(1);
|
|
815
|
+
}
|
|
816
|
+
options.tokenEndpoint = aliasUrl;
|
|
817
|
+
}
|
|
818
|
+
if (options.protocol === 'oidc' && options.flow === 'password') {
|
|
819
|
+
if (options.uaaUrl && !options.tokenEndpoint) {
|
|
820
|
+
options.tokenEndpoint = `${options.uaaUrl.replace(/\/+$/, '')}/oauth/token`;
|
|
687
821
|
}
|
|
688
822
|
}
|
|
689
823
|
if (options.protocol === 'oidc' && options.flow) {
|
|
@@ -713,10 +847,13 @@ async function main() {
|
|
|
713
847
|
const tempEnvPath = path.join(tempSessionDir, `${destination}.env`);
|
|
714
848
|
fs.copyFileSync(resolvedEnvPath, tempEnvPath);
|
|
715
849
|
}
|
|
716
|
-
const
|
|
850
|
+
const placeholderServiceUrl = '<SERVICE_URL>';
|
|
851
|
+
const defaultServiceUrl = options.authType === 'xsuaa'
|
|
852
|
+
? options.serviceUrl || placeholderServiceUrl
|
|
853
|
+
: options.serviceUrl || '';
|
|
717
854
|
const sessionStore = options.authType === 'xsuaa'
|
|
718
855
|
? new auth_stores_1.XsuaaSessionStore(tempSessionDir, defaultServiceUrl)
|
|
719
|
-
: new auth_stores_1.AbapSessionStore(tempSessionDir);
|
|
856
|
+
: new auth_stores_1.AbapSessionStore(tempSessionDir, undefined, options.serviceUrl);
|
|
720
857
|
const existingConn = await sessionStore.getConnectionConfig(destination);
|
|
721
858
|
const existingAuth = await sessionStore.getAuthorizationConfig(destination);
|
|
722
859
|
const serviceUrl = options.serviceUrl || existingConn?.serviceUrl;
|
|
@@ -730,18 +867,47 @@ async function main() {
|
|
|
730
867
|
console.error('❌ SAML pure flow is only supported for ABAP sessions (cookies)');
|
|
731
868
|
process.exit(1);
|
|
732
869
|
}
|
|
870
|
+
const isSamlPureAbap = options.authType === 'abap' &&
|
|
871
|
+
options.protocol === 'saml2' &&
|
|
872
|
+
options.flow === 'pure';
|
|
733
873
|
await sessionStore.setConnectionConfig(destination, {
|
|
734
874
|
serviceUrl: serviceUrl ||
|
|
735
875
|
(options.authType === 'xsuaa' ? defaultServiceUrl : undefined),
|
|
736
|
-
authorizationToken:
|
|
876
|
+
authorizationToken: isSamlPureAbap
|
|
877
|
+
? existingConn?.authorizationToken || '__init__'
|
|
878
|
+
: existingConn?.authorizationToken,
|
|
737
879
|
sessionCookies: existingConn?.sessionCookies,
|
|
738
880
|
});
|
|
881
|
+
let stripClientSecret = false;
|
|
882
|
+
const authUaaUrl = options.uaaUrl || options.tokenEndpoint || options.issuerUrl || undefined;
|
|
883
|
+
if (options.clientId && authUaaUrl) {
|
|
884
|
+
let clientSecret = options.clientSecret;
|
|
885
|
+
if (!clientSecret) {
|
|
886
|
+
clientSecret = '__public__';
|
|
887
|
+
stripClientSecret = true;
|
|
888
|
+
}
|
|
889
|
+
await sessionStore.setAuthorizationConfig(destination, {
|
|
890
|
+
uaaUrl: authUaaUrl,
|
|
891
|
+
uaaClientId: options.clientId,
|
|
892
|
+
uaaClientSecret: clientSecret,
|
|
893
|
+
refreshToken: existingAuth?.refreshToken,
|
|
894
|
+
});
|
|
895
|
+
}
|
|
739
896
|
const providerConfig = buildProviderConfig(options, existingAuth, existingConn, providerConfigFromFile);
|
|
740
|
-
const
|
|
897
|
+
const providerConfigWithLogger = providerConfig.config
|
|
898
|
+
? {
|
|
899
|
+
...providerConfig,
|
|
900
|
+
config: {
|
|
901
|
+
...providerConfig.config,
|
|
902
|
+
logger: providerConfig.config?.logger ?? logger,
|
|
903
|
+
},
|
|
904
|
+
}
|
|
905
|
+
: providerConfig;
|
|
906
|
+
const tokenProvider = auth_providers_1.SsoProviderFactory.create(providerConfigWithLogger);
|
|
741
907
|
const broker = new AuthBroker({
|
|
742
908
|
sessionStore,
|
|
743
909
|
tokenProvider,
|
|
744
|
-
}, options.browser);
|
|
910
|
+
}, options.browser, logger);
|
|
745
911
|
console.log(`🔐 Getting token for destination "${destination}"...`);
|
|
746
912
|
await broker.getToken(destination);
|
|
747
913
|
console.log(`✅ Token obtained successfully`);
|
|
@@ -766,7 +932,21 @@ async function main() {
|
|
|
766
932
|
if (!fs.existsSync(outputDir)) {
|
|
767
933
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
768
934
|
}
|
|
769
|
-
fs.
|
|
935
|
+
let envContent = fs.readFileSync(tempEnvPath, 'utf8');
|
|
936
|
+
if (options.authType === 'xsuaa' && !serviceUrl) {
|
|
937
|
+
const lines = envContent
|
|
938
|
+
.split('\n')
|
|
939
|
+
.filter((line) => !line.startsWith('XSUAA_MCP_URL='));
|
|
940
|
+
envContent = `${lines.join('\n')}\n`;
|
|
941
|
+
}
|
|
942
|
+
if (stripClientSecret) {
|
|
943
|
+
const lines = envContent
|
|
944
|
+
.split('\n')
|
|
945
|
+
.filter((line) => !line.startsWith('XSUAA_UAA_CLIENT_SECRET=') &&
|
|
946
|
+
!line.startsWith('SAP_UAA_CLIENT_SECRET='));
|
|
947
|
+
envContent = `${lines.join('\n')}\n`;
|
|
948
|
+
}
|
|
949
|
+
fs.writeFileSync(resolvedOutputPath, envContent, 'utf8');
|
|
770
950
|
console.log(`✅ .env file created: ${resolvedOutputPath}`);
|
|
771
951
|
}
|
|
772
952
|
else {
|
|
@@ -792,7 +972,9 @@ async function main() {
|
|
|
792
972
|
outputData.uaaClientId = authConfig.uaaClientId;
|
|
793
973
|
}
|
|
794
974
|
if (authConfig?.uaaClientSecret) {
|
|
795
|
-
|
|
975
|
+
if (!stripClientSecret) {
|
|
976
|
+
outputData.uaaClientSecret = authConfig.uaaClientSecret;
|
|
977
|
+
}
|
|
796
978
|
}
|
|
797
979
|
const outputDir = path.dirname(resolvedOutputPath);
|
|
798
980
|
if (!fs.existsSync(outputDir)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcp-abap-adt/auth-broker",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "JWT authentication broker for MCP ABAP ADT - manages tokens based on destination headers",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -45,6 +45,11 @@
|
|
|
45
45
|
"build:fast": "tsc -p tsconfig.json && tsc -p tsconfig.cli.json",
|
|
46
46
|
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
|
|
47
47
|
"test:check": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json",
|
|
48
|
+
"test:mcp-auth": "sh tests/sso-demo/run-mcp-auth.sh",
|
|
49
|
+
"test:mcp-sso": "sh tests/sso-demo/run-mcp-sso.sh",
|
|
50
|
+
"test:device-code": "sh tests/keycloak/run-oidc.sh",
|
|
51
|
+
"test:saml-pure": "sh tests/keycloak/run-saml.sh",
|
|
52
|
+
"test:sso": "sh tests/keycloak/run-interactive.sh",
|
|
48
53
|
"prepublishOnly": "npm run build",
|
|
49
54
|
"prepack": "npm run build",
|
|
50
55
|
"generate-env": "tsx bin/generate-env-from-service-key.ts"
|
|
@@ -57,8 +62,8 @@
|
|
|
57
62
|
"node": ">=18.0.0"
|
|
58
63
|
},
|
|
59
64
|
"dependencies": {
|
|
60
|
-
"@mcp-abap-adt/auth-providers": "^1.0.
|
|
61
|
-
"@mcp-abap-adt/auth-stores": "^1.0.
|
|
65
|
+
"@mcp-abap-adt/auth-providers": "^1.0.5",
|
|
66
|
+
"@mcp-abap-adt/auth-stores": "^1.0.2",
|
|
62
67
|
"@mcp-abap-adt/interfaces": "^2.3.0",
|
|
63
68
|
"axios": "^1.13.5",
|
|
64
69
|
"tsx": "^4.21.0"
|