arc-1 0.9.17 → 0.9.19
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/README.md +26 -26
- package/dist/adt/config.d.ts +1 -1
- package/dist/adt/config.d.ts.map +1 -1
- package/dist/adt/discovery.js +1 -1
- package/dist/adt/discovery.js.map +1 -1
- package/dist/adt/features.d.ts.map +1 -1
- package/dist/adt/features.js +8 -8
- package/dist/adt/features.js.map +1 -1
- package/dist/adt/http.d.ts +11 -1
- package/dist/adt/http.d.ts.map +1 -1
- package/dist/adt/http.js +6 -1
- package/dist/adt/http.js.map +1 -1
- package/dist/adt/source-diff.d.ts +19 -0
- package/dist/adt/source-diff.d.ts.map +1 -0
- package/dist/adt/source-diff.js +39 -0
- package/dist/adt/source-diff.js.map +1 -0
- package/dist/adt/version-diff.d.ts +28 -0
- package/dist/adt/version-diff.d.ts.map +1 -0
- package/dist/adt/version-diff.js +119 -0
- package/dist/adt/version-diff.js.map +1 -0
- package/dist/authz/policy.d.ts +6 -0
- package/dist/authz/policy.d.ts.map +1 -1
- package/dist/authz/policy.js +20 -0
- package/dist/authz/policy.js.map +1 -1
- package/dist/cli.js +21 -3
- package/dist/cli.js.map +1 -1
- package/dist/handlers/dispatch.d.ts +3 -0
- package/dist/handlers/dispatch.d.ts.map +1 -1
- package/dist/handlers/dispatch.js +71 -53
- package/dist/handlers/dispatch.js.map +1 -1
- package/dist/handlers/read.d.ts.map +1 -1
- package/dist/handlers/read.js +27 -0
- package/dist/handlers/read.js.map +1 -1
- package/dist/handlers/schemas.d.ts +16 -5
- package/dist/handlers/schemas.d.ts.map +1 -1
- package/dist/handlers/schemas.js +17 -0
- package/dist/handlers/schemas.js.map +1 -1
- package/dist/handlers/tools.d.ts.map +1 -1
- package/dist/handlers/tools.js +18 -1
- package/dist/handlers/tools.js.map +1 -1
- package/dist/handlers/transport.d.ts.map +1 -1
- package/dist/handlers/transport.js +27 -1
- package/dist/handlers/transport.js.map +1 -1
- package/dist/plugins/manifest-interpreter.d.ts +25 -0
- package/dist/plugins/manifest-interpreter.d.ts.map +1 -0
- package/dist/plugins/manifest-interpreter.js +124 -0
- package/dist/plugins/manifest-interpreter.js.map +1 -0
- package/dist/public/define-tool.d.ts +9 -0
- package/dist/public/define-tool.d.ts.map +1 -0
- package/dist/public/define-tool.js +25 -0
- package/dist/public/define-tool.js.map +1 -0
- package/dist/public/index.d.ts +9 -0
- package/dist/public/index.d.ts.map +1 -0
- package/dist/public/index.js +10 -0
- package/dist/public/index.js.map +1 -0
- package/dist/public/testing.d.ts +26 -0
- package/dist/public/testing.d.ts.map +1 -0
- package/dist/public/testing.js +39 -0
- package/dist/public/testing.js.map +1 -0
- package/dist/public/types.d.ts +87 -0
- package/dist/public/types.d.ts.map +1 -0
- package/dist/public/types.js +4 -0
- package/dist/public/types.js.map +1 -0
- package/dist/registry/tool-registry.d.ts +74 -0
- package/dist/registry/tool-registry.d.ts.map +1 -0
- package/dist/registry/tool-registry.js +59 -0
- package/dist/registry/tool-registry.js.map +1 -0
- package/dist/server/app-url.d.ts +31 -0
- package/dist/server/app-url.d.ts.map +1 -0
- package/dist/server/app-url.js +50 -0
- package/dist/server/app-url.js.map +1 -0
- package/dist/server/audit.d.ts +4 -0
- package/dist/server/audit.d.ts.map +1 -1
- package/dist/server/audit.js.map +1 -1
- package/dist/server/config.d.ts.map +1 -1
- package/dist/server/config.js +19 -0
- package/dist/server/config.js.map +1 -1
- package/dist/server/http.d.ts +15 -46
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.js +105 -375
- package/dist/server/http.js.map +1 -1
- package/dist/server/logger.d.ts +22 -0
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/logger.js +22 -0
- package/dist/server/logger.js.map +1 -1
- package/dist/server/plugin-loader.d.ts +19 -0
- package/dist/server/plugin-loader.d.ts.map +1 -0
- package/dist/server/plugin-loader.js +162 -0
- package/dist/server/plugin-loader.js.map +1 -0
- package/dist/server/safe-http-client.d.ts +38 -0
- package/dist/server/safe-http-client.d.ts.map +1 -0
- package/dist/server/safe-http-client.js +129 -0
- package/dist/server/safe-http-client.js.map +1 -0
- package/dist/server/server.d.ts +2 -2
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +36 -7
- package/dist/server/server.js.map +1 -1
- package/dist/server/types.d.ts +8 -0
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/types.js +2 -0
- package/dist/server/types.js.map +1 -1
- package/package.json +28 -9
- package/dist/adt/btp.d.ts +0 -140
- package/dist/adt/btp.d.ts.map +0 -1
- package/dist/adt/btp.js +0 -427
- package/dist/adt/btp.js.map +0 -1
- package/dist/server/oauth-state.d.ts +0 -92
- package/dist/server/oauth-state.d.ts.map +0 -1
- package/dist/server/oauth-state.js +0 -163
- package/dist/server/oauth-state.js.map +0 -1
- package/dist/server/stateless-client-store.d.ts +0 -173
- package/dist/server/stateless-client-store.d.ts.map +0 -1
- package/dist/server/stateless-client-store.js +0 -503
- package/dist/server/stateless-client-store.js.map +0 -1
- package/dist/server/xsuaa.d.ts +0 -188
- package/dist/server/xsuaa.d.ts.map +0 -1
- package/dist/server/xsuaa.js +0 -464
- package/dist/server/xsuaa.js.map +0 -1
package/dist/adt/btp.js
DELETED
|
@@ -1,427 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* BTP Destination Service integration for ARC-1.
|
|
3
|
-
*
|
|
4
|
-
* When running on SAP BTP Cloud Foundry, this module:
|
|
5
|
-
* 1. Parses VCAP_SERVICES to extract service binding credentials
|
|
6
|
-
* 2. Fetches SAP connection details from the BTP Destination Service
|
|
7
|
-
* 3. Configures an HTTP proxy through the Cloud Connector (Connectivity Service)
|
|
8
|
-
*
|
|
9
|
-
* Startup path (lookupDestination, resolveBTPDestination) uses direct fetch
|
|
10
|
-
* to the Destination Service API. Per-user principal propagation path
|
|
11
|
-
* (lookupDestinationWithUserToken) uses SAP Cloud SDK's getDestination()
|
|
12
|
-
* for automatic token management and per-user caching.
|
|
13
|
-
*
|
|
14
|
-
* Token caching: Connectivity proxy tokens are cached and refreshed 60 seconds
|
|
15
|
-
* before expiry. Per-user destination tokens are cached by the SDK.
|
|
16
|
-
*/
|
|
17
|
-
import { getDestination, } from '@sap-cloud-sdk/connectivity';
|
|
18
|
-
import { logger } from '../server/logger.js';
|
|
19
|
-
import { destinationPpHint } from './errors.js';
|
|
20
|
-
/**
|
|
21
|
-
* Parse VCAP_SERVICES environment variable to extract BTP service credentials.
|
|
22
|
-
* Returns null if not running on BTP (VCAP_SERVICES not set).
|
|
23
|
-
*/
|
|
24
|
-
export function parseVCAPServices() {
|
|
25
|
-
const vcapJson = process.env.VCAP_SERVICES;
|
|
26
|
-
if (!vcapJson)
|
|
27
|
-
return null;
|
|
28
|
-
const vcap = JSON.parse(vcapJson);
|
|
29
|
-
const config = {
|
|
30
|
-
xsuaaUrl: '',
|
|
31
|
-
xsuaaClientId: '',
|
|
32
|
-
xsuaaSecret: '',
|
|
33
|
-
destinationUrl: '',
|
|
34
|
-
destinationClientId: '',
|
|
35
|
-
destinationSecret: '',
|
|
36
|
-
destinationTokenUrl: '',
|
|
37
|
-
connectivityProxyHost: '',
|
|
38
|
-
connectivityProxyPort: '',
|
|
39
|
-
connectivityClientId: '',
|
|
40
|
-
connectivitySecret: '',
|
|
41
|
-
connectivityTokenUrl: '',
|
|
42
|
-
};
|
|
43
|
-
// XSUAA binding
|
|
44
|
-
if (vcap.xsuaa?.[0]?.credentials) {
|
|
45
|
-
const c = vcap.xsuaa[0].credentials;
|
|
46
|
-
config.xsuaaUrl = c.url || '';
|
|
47
|
-
config.xsuaaClientId = c.clientid || '';
|
|
48
|
-
config.xsuaaSecret = c.clientsecret || '';
|
|
49
|
-
}
|
|
50
|
-
// Destination binding
|
|
51
|
-
if (vcap.destination?.[0]?.credentials) {
|
|
52
|
-
const c = vcap.destination[0].credentials;
|
|
53
|
-
config.destinationUrl = c.uri || c.url || '';
|
|
54
|
-
config.destinationClientId = c.clientid || '';
|
|
55
|
-
config.destinationSecret = c.clientsecret || '';
|
|
56
|
-
config.destinationTokenUrl = c.token_service_url || '';
|
|
57
|
-
// Fallback: construct from URL
|
|
58
|
-
if (!config.destinationTokenUrl && c.url) {
|
|
59
|
-
config.destinationTokenUrl = `${c.url.replace(/\/$/, '')}/oauth/token`;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
// Connectivity binding
|
|
63
|
-
if (vcap.connectivity?.[0]?.credentials) {
|
|
64
|
-
const c = vcap.connectivity[0].credentials;
|
|
65
|
-
config.connectivityProxyHost = c.onpremise_proxy_host || '';
|
|
66
|
-
config.connectivityProxyPort = c.onpremise_proxy_http_port || '';
|
|
67
|
-
config.connectivityClientId = c.clientid || '';
|
|
68
|
-
config.connectivitySecret = c.clientsecret || '';
|
|
69
|
-
config.connectivityTokenUrl = c.token_service_url || '';
|
|
70
|
-
// Fallback + ensure /oauth/token suffix
|
|
71
|
-
if (!config.connectivityTokenUrl && c.url) {
|
|
72
|
-
config.connectivityTokenUrl = `${c.url.replace(/\/$/, '')}/oauth/token`;
|
|
73
|
-
}
|
|
74
|
-
else if (config.connectivityTokenUrl && !config.connectivityTokenUrl.endsWith('/oauth/token')) {
|
|
75
|
-
config.connectivityTokenUrl = `${config.connectivityTokenUrl.replace(/\/$/, '')}/oauth/token`;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
logger.info('BTP VCAP_SERVICES parsed', {
|
|
79
|
-
hasXsuaa: !!config.xsuaaUrl,
|
|
80
|
-
hasDestination: !!config.destinationUrl,
|
|
81
|
-
hasConnectivity: !!config.connectivityProxyHost,
|
|
82
|
-
});
|
|
83
|
-
return config;
|
|
84
|
-
}
|
|
85
|
-
// ─── Destination Service ─────────────────────────────────────────────
|
|
86
|
-
/**
|
|
87
|
-
* Fetch an OAuth2 client_credentials token.
|
|
88
|
-
* Used for both Destination Service and Connectivity Service tokens.
|
|
89
|
-
*/
|
|
90
|
-
async function fetchClientCredentialsToken(tokenUrl, clientId, clientSecret) {
|
|
91
|
-
const body = new URLSearchParams({
|
|
92
|
-
grant_type: 'client_credentials',
|
|
93
|
-
client_id: clientId,
|
|
94
|
-
client_secret: clientSecret,
|
|
95
|
-
});
|
|
96
|
-
const resp = await fetch(tokenUrl, {
|
|
97
|
-
method: 'POST',
|
|
98
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
99
|
-
body: body.toString(),
|
|
100
|
-
});
|
|
101
|
-
if (!resp.ok) {
|
|
102
|
-
const text = await resp.text();
|
|
103
|
-
throw new Error(`Token endpoint returned HTTP ${resp.status}: ${text.slice(0, 200)}`);
|
|
104
|
-
}
|
|
105
|
-
const data = (await resp.json());
|
|
106
|
-
return { accessToken: data.access_token, expiresIn: data.expires_in };
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Look up a destination from the BTP Destination Service.
|
|
110
|
-
* Returns SAP URL, credentials, and proxy type.
|
|
111
|
-
*/
|
|
112
|
-
export async function lookupDestination(btpConfig, destinationName) {
|
|
113
|
-
// Get token for Destination Service API
|
|
114
|
-
const tokenUrl = btpConfig.destinationTokenUrl || `${btpConfig.xsuaaUrl}/oauth/token`;
|
|
115
|
-
const { accessToken } = await fetchClientCredentialsToken(tokenUrl, btpConfig.destinationClientId, btpConfig.destinationSecret);
|
|
116
|
-
// Call Destination Service
|
|
117
|
-
const destUrl = `${btpConfig.destinationUrl.replace(/\/$/, '')}/destination-configuration/v1/destinations/${encodeURIComponent(destinationName)}`;
|
|
118
|
-
const resp = await fetch(destUrl, {
|
|
119
|
-
headers: { Authorization: `Bearer ${accessToken}` },
|
|
120
|
-
});
|
|
121
|
-
if (!resp.ok) {
|
|
122
|
-
const text = await resp.text();
|
|
123
|
-
throw new Error(`Destination Service returned HTTP ${resp.status}: ${text.slice(0, 200)}`);
|
|
124
|
-
}
|
|
125
|
-
const data = (await resp.json());
|
|
126
|
-
logger.info('BTP destination resolved', {
|
|
127
|
-
name: data.destinationConfiguration.Name,
|
|
128
|
-
url: data.destinationConfiguration.URL,
|
|
129
|
-
auth: data.destinationConfiguration.Authentication,
|
|
130
|
-
proxyType: data.destinationConfiguration.ProxyType,
|
|
131
|
-
locationId: data.destinationConfiguration.CloudConnectorLocationId,
|
|
132
|
-
});
|
|
133
|
-
return data.destinationConfiguration;
|
|
134
|
-
}
|
|
135
|
-
// ─── Connectivity Proxy ──────────────────────────────────────────────
|
|
136
|
-
/**
|
|
137
|
-
* Create a proxy configuration for routing through the Cloud Connector.
|
|
138
|
-
*
|
|
139
|
-
* Returns a BTPProxyConfig with a token getter that caches the connectivity
|
|
140
|
-
* JWT and auto-refreshes it 60 seconds before expiry.
|
|
141
|
-
*/
|
|
142
|
-
export function createConnectivityProxy(btpConfig, locationId) {
|
|
143
|
-
if (!btpConfig.connectivityProxyHost)
|
|
144
|
-
return null;
|
|
145
|
-
let cachedToken = '';
|
|
146
|
-
let expiresAt = 0;
|
|
147
|
-
return {
|
|
148
|
-
host: btpConfig.connectivityProxyHost,
|
|
149
|
-
port: Number.parseInt(btpConfig.connectivityProxyPort || '20003', 10),
|
|
150
|
-
protocol: 'http',
|
|
151
|
-
locationId,
|
|
152
|
-
getProxyToken: async () => {
|
|
153
|
-
// Return cached token if still valid (60s buffer)
|
|
154
|
-
if (cachedToken && Date.now() < expiresAt) {
|
|
155
|
-
return cachedToken;
|
|
156
|
-
}
|
|
157
|
-
const { accessToken, expiresIn } = await fetchClientCredentialsToken(btpConfig.connectivityTokenUrl, btpConfig.connectivityClientId, btpConfig.connectivitySecret);
|
|
158
|
-
cachedToken = accessToken;
|
|
159
|
-
expiresAt = Date.now() + (expiresIn - 60) * 1000;
|
|
160
|
-
return cachedToken;
|
|
161
|
-
},
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Look up a destination with the user's JWT token for principal propagation.
|
|
166
|
-
*
|
|
167
|
-
* Uses SAP Cloud SDK's `getDestination()` to resolve the destination with per-user
|
|
168
|
-
* JWT. The SDK handles service token acquisition, X-User-Token header injection,
|
|
169
|
-
* and per-user destination caching.
|
|
170
|
-
*
|
|
171
|
-
* This is the key API for per-user SAP authentication:
|
|
172
|
-
* 1. Caller passes the user's JWT (from XSUAA/OIDC)
|
|
173
|
-
* 2. SDK calls the Destination Service with X-User-Token header
|
|
174
|
-
* 3. For PrincipalPropagation destinations: returns SAP-Connectivity-Authentication header
|
|
175
|
-
* 4. For OAuth2SAMLBearerAssertion destinations: returns a Bearer token
|
|
176
|
-
*
|
|
177
|
-
* Includes a jwt-bearer fallback (Option 2) when the Destination Service returns
|
|
178
|
-
* no auth tokens — uses direct fetch to the Connectivity Service token URL.
|
|
179
|
-
*
|
|
180
|
-
* @param btpConfig - BTP service credentials (needed for jwt-bearer fallback only)
|
|
181
|
-
*/
|
|
182
|
-
export async function lookupDestinationWithUserToken(btpConfig, destinationName, userJwt) {
|
|
183
|
-
// Log JWT claims for PP debugging (decode payload without verification)
|
|
184
|
-
try {
|
|
185
|
-
const parts = userJwt.split('.');
|
|
186
|
-
if (parts.length === 3) {
|
|
187
|
-
const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
|
|
188
|
-
logger.debug('PP user JWT claims', {
|
|
189
|
-
destination: destinationName,
|
|
190
|
-
grantType: payload.grant_type,
|
|
191
|
-
sub: payload.sub,
|
|
192
|
-
email: payload.email,
|
|
193
|
-
userUuid: payload.user_uuid,
|
|
194
|
-
zid: payload.zid,
|
|
195
|
-
iss: payload.iss,
|
|
196
|
-
aud: Array.isArray(payload.aud) ? payload.aud.join(',') : payload.aud,
|
|
197
|
-
azp: payload.azp,
|
|
198
|
-
scope: payload.scope?.join?.(' ') ?? payload.scope,
|
|
199
|
-
origin: payload.origin,
|
|
200
|
-
exp: payload.exp,
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
catch {
|
|
205
|
-
logger.debug('PP user JWT: failed to decode claims');
|
|
206
|
-
}
|
|
207
|
-
// Use SAP Cloud SDK to resolve the destination with per-user JWT.
|
|
208
|
-
// The SDK handles: service token acquisition, X-User-Token header,
|
|
209
|
-
// and per-user destination caching (keyed by destinationName + jwt).
|
|
210
|
-
const sdkDest = await getDestination({
|
|
211
|
-
destinationName,
|
|
212
|
-
jwt: userJwt,
|
|
213
|
-
useCache: true,
|
|
214
|
-
});
|
|
215
|
-
if (!sdkDest) {
|
|
216
|
-
throw new Error(`Destination Service (per-user) returned no destination for '${destinationName}'`);
|
|
217
|
-
}
|
|
218
|
-
// Map SDK Destination → ARC-1 Destination type
|
|
219
|
-
const dest = {
|
|
220
|
-
Name: sdkDest.name ?? destinationName,
|
|
221
|
-
URL: sdkDest.url ?? '',
|
|
222
|
-
Authentication: sdkDest.authentication ?? '',
|
|
223
|
-
ProxyType: sdkDest.proxyType ?? '',
|
|
224
|
-
User: sdkDest.username ?? '',
|
|
225
|
-
Password: sdkDest.password ?? '',
|
|
226
|
-
'sap-client': sdkDest.sapClient ?? undefined,
|
|
227
|
-
CloudConnectorLocationId: sdkDest.cloudConnectorLocationId ?? undefined,
|
|
228
|
-
};
|
|
229
|
-
const tokens = {};
|
|
230
|
-
const sdkAuthTokens = sdkDest.authTokens;
|
|
231
|
-
// Log raw auth response for PP debugging.
|
|
232
|
-
// Field names avoid "token" substring to prevent logger redaction.
|
|
233
|
-
const rawEntries = sdkAuthTokens?.map((t) => ({
|
|
234
|
-
entryType: t.type,
|
|
235
|
-
httpHeaderKey: t.http_header?.key,
|
|
236
|
-
hasValue: !!t.value,
|
|
237
|
-
hasHttpHeaderValue: !!t.http_header?.value,
|
|
238
|
-
entryError: t.error,
|
|
239
|
-
}));
|
|
240
|
-
logger.debug('Destination Service PP response', {
|
|
241
|
-
destination: destinationName,
|
|
242
|
-
authentication: dest.Authentication,
|
|
243
|
-
proxyType: dest.ProxyType,
|
|
244
|
-
url: dest.URL,
|
|
245
|
-
ppEntryCount: sdkAuthTokens?.length ?? 0,
|
|
246
|
-
ppEntries: rawEntries ?? 'NONE',
|
|
247
|
-
});
|
|
248
|
-
// Extract auth tokens from the SDK response
|
|
249
|
-
if (sdkAuthTokens) {
|
|
250
|
-
for (const token of sdkAuthTokens) {
|
|
251
|
-
if (token.error) {
|
|
252
|
-
logger.error('Destination Service auth token error', {
|
|
253
|
-
destination: destinationName,
|
|
254
|
-
tokenType: token.type,
|
|
255
|
-
error: token.error,
|
|
256
|
-
});
|
|
257
|
-
const hint = destinationPpHint(token.error);
|
|
258
|
-
const hintSuffix = hint ? ` — ${hint}` : '';
|
|
259
|
-
throw new Error(`Destination Service auth token error for '${destinationName}': ${token.error}${hintSuffix}`);
|
|
260
|
-
}
|
|
261
|
-
// SAP-Connectivity-Authentication header (used by Cloud Connector for PP)
|
|
262
|
-
if (token.http_header?.key === 'SAP-Connectivity-Authentication') {
|
|
263
|
-
tokens.sapConnectivityAuth = token.http_header.value;
|
|
264
|
-
logger.debug('PP: SAP-Connectivity-Authentication header extracted', {
|
|
265
|
-
destination: destinationName,
|
|
266
|
-
headerValueLength: token.http_header.value.length,
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
// Bearer token (OAuth2UserTokenExchange / OAuth2SAMLBearerAssertion). The SAP Cloud SDK
|
|
270
|
-
// lowercases the type ("bearer") for OAuth2UserTokenExchange and exposes the token via
|
|
271
|
-
// `value` and/or an `Authorization` http_header. Match case-insensitively and fall back to
|
|
272
|
-
// the header value (stripping the "Bearer " prefix). Verified live against a BTP ABAP
|
|
273
|
-
// Environment (#301): a capital-B-only check silently dropped the token (hasBearer:false).
|
|
274
|
-
if (token.type?.toLowerCase() === 'bearer') {
|
|
275
|
-
tokens.bearerToken = token.value || token.http_header?.value?.replace(/^Bearer\s+/i, '');
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
else {
|
|
280
|
-
logger.warn('Destination Service returned no authTokens — trying jwt-bearer exchange fallback', {
|
|
281
|
-
destination: destinationName,
|
|
282
|
-
authentication: dest.Authentication,
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
// ─── PP jwt-bearer fallback (Option 2) ─────────────────────────────
|
|
286
|
-
//
|
|
287
|
-
// Background: The BTP Destination Service SHOULD return authTokens containing
|
|
288
|
-
// the SAP-Connectivity-Authentication header for PrincipalPropagation destinations.
|
|
289
|
-
// In practice, it often returns NO authTokens (empty response). This is a known
|
|
290
|
-
// issue — the Destination Service simply omits the field.
|
|
291
|
-
//
|
|
292
|
-
// Workaround: We perform a jwt-bearer token exchange with the Connectivity
|
|
293
|
-
// Service's XSUAA to verify the user JWT is valid. If the exchange succeeds,
|
|
294
|
-
// we send the ORIGINAL user JWT as SAP-Connectivity-Authentication (Option 2
|
|
295
|
-
// per SAP docs page 211). The Cloud Connector extracts the user identity from
|
|
296
|
-
// this header and generates the X.509 certificate.
|
|
297
|
-
//
|
|
298
|
-
// Why Option 2 and not Option 1?
|
|
299
|
-
// - Option 1 sends the EXCHANGED token as Proxy-Authorization
|
|
300
|
-
// - The CC couldn't extract the principal from the exchanged token
|
|
301
|
-
// (CC trace: "no principal available, injecting empty certificate")
|
|
302
|
-
// - Option 2 sends the ORIGINAL user JWT as SAP-Connectivity-Authentication
|
|
303
|
-
// + regular connectivity proxy token as Proxy-Authorization
|
|
304
|
-
// - The CC successfully extracts the user email from the original JWT
|
|
305
|
-
//
|
|
306
|
-
// Reference: SAP BTP Connectivity docs page 209-213
|
|
307
|
-
// https://help.sap.com/docs/connectivity/sap-btp-connectivity-cf/configure-principal-propagation-via-user-exchange-token
|
|
308
|
-
if (!tokens.sapConnectivityAuth && dest.Authentication === 'PrincipalPropagation' && btpConfig.connectivityClientId) {
|
|
309
|
-
logger.info('PP jwt-bearer exchange: attempting direct exchange with Connectivity Service', {
|
|
310
|
-
destination: destinationName,
|
|
311
|
-
connectivityUrl: btpConfig.connectivityTokenUrl,
|
|
312
|
-
});
|
|
313
|
-
try {
|
|
314
|
-
// Exchange user JWT via jwt-bearer grant type with Connectivity Service credentials.
|
|
315
|
-
// This validates the user JWT and proves we have a legitimate user token.
|
|
316
|
-
// The exchange itself isn't used for auth — we use the original JWT instead.
|
|
317
|
-
const exchangeResp = await fetch(btpConfig.connectivityTokenUrl, {
|
|
318
|
-
method: 'POST',
|
|
319
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
320
|
-
body: new URLSearchParams({
|
|
321
|
-
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
|
322
|
-
client_id: btpConfig.connectivityClientId,
|
|
323
|
-
client_secret: btpConfig.connectivitySecret,
|
|
324
|
-
assertion: userJwt,
|
|
325
|
-
token_format: 'jwt',
|
|
326
|
-
response_type: 'token',
|
|
327
|
-
}).toString(),
|
|
328
|
-
});
|
|
329
|
-
if (exchangeResp.ok) {
|
|
330
|
-
await exchangeResp.json(); // consume response body
|
|
331
|
-
// Option 2: Send the ORIGINAL user JWT as SAP-Connectivity-Authentication.
|
|
332
|
-
// The CC reads this header, extracts the user identity (email), and generates
|
|
333
|
-
// a short-lived X.509 certificate with CN=${email}. The regular connectivity
|
|
334
|
-
// proxy token (from btpProxy.getProxyToken()) is sent as Proxy-Authorization.
|
|
335
|
-
tokens.sapConnectivityAuth = `Bearer ${userJwt}`;
|
|
336
|
-
logger.info('PP: using Option 2 (SAP-Connectivity-Authentication with original JWT)', {
|
|
337
|
-
destination: destinationName,
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
else {
|
|
341
|
-
const errText = await exchangeResp.text();
|
|
342
|
-
logger.error('PP jwt-bearer exchange: failed', {
|
|
343
|
-
destination: destinationName,
|
|
344
|
-
status: exchangeResp.status,
|
|
345
|
-
error: errText.slice(0, 300),
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
catch (err) {
|
|
350
|
-
logger.error('PP jwt-bearer exchange: error', {
|
|
351
|
-
destination: destinationName,
|
|
352
|
-
error: err instanceof Error ? err.message : String(err),
|
|
353
|
-
});
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
logger.info('BTP destination resolved (per-user)', {
|
|
357
|
-
name: dest.Name,
|
|
358
|
-
url: dest.URL,
|
|
359
|
-
auth: dest.Authentication,
|
|
360
|
-
hasConnectivityAuth: !!tokens.sapConnectivityAuth,
|
|
361
|
-
hasBearer: !!tokens.bearerToken,
|
|
362
|
-
});
|
|
363
|
-
return { destination: dest, authTokens: tokens };
|
|
364
|
-
}
|
|
365
|
-
// ─── Top-Level Resolver ──────────────────────────────────────────────
|
|
366
|
-
/**
|
|
367
|
-
* Resolve BTP destination and connectivity proxy.
|
|
368
|
-
* Called on startup when SAP_BTP_DESTINATION env var is set.
|
|
369
|
-
*
|
|
370
|
-
* Returns the resolved SAP connection config to override defaults.
|
|
371
|
-
*/
|
|
372
|
-
export async function resolveBTPDestination(destinationName) {
|
|
373
|
-
const btpConfig = parseVCAPServices();
|
|
374
|
-
if (!btpConfig) {
|
|
375
|
-
throw new Error('SAP_BTP_DESTINATION is set but VCAP_SERVICES is not available. Are you running on BTP CF?');
|
|
376
|
-
}
|
|
377
|
-
// Use direct fetch for startup — works with BasicAuth destinations without JWT.
|
|
378
|
-
// The SDK's getDestination() fails for PrincipalPropagation destinations at startup
|
|
379
|
-
// because there's no user JWT available yet.
|
|
380
|
-
const dest = await lookupDestination(btpConfig, destinationName);
|
|
381
|
-
const proxy = dest.ProxyType === 'OnPremise' ? createConnectivityProxy(btpConfig, dest.CloudConnectorLocationId) : null;
|
|
382
|
-
return {
|
|
383
|
-
url: dest.URL,
|
|
384
|
-
username: dest.User,
|
|
385
|
-
password: dest.Password,
|
|
386
|
-
client: dest['sap-client'] || '100',
|
|
387
|
-
proxy,
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
/**
|
|
391
|
-
* Get the app's public URL.
|
|
392
|
-
*
|
|
393
|
-
* Priority:
|
|
394
|
-
* 1. ARC1_PUBLIC_URL env var — set this when the app is reached through a
|
|
395
|
-
* reverse proxy on a different hostname (e.g. SAP Integration Suite API
|
|
396
|
-
* Management). The value flows into every absolute URL the OAuth metadata
|
|
397
|
-
* endpoints emit (issuer, authorize, token, register, revoke, resource).
|
|
398
|
-
* May include a base-path component (e.g. https://api.example.com/arc1) —
|
|
399
|
-
* the path is preserved verbatim.
|
|
400
|
-
* 2. VCAP_APPLICATION.application_uris[0] — set automatically by CF, points
|
|
401
|
-
* to the app's CF route.
|
|
402
|
-
* 3. undefined — caller falls back to bind-host:port.
|
|
403
|
-
*
|
|
404
|
-
* The trailing slash, if present, is stripped so callers can do `${url}/path`
|
|
405
|
-
* consistently.
|
|
406
|
-
*/
|
|
407
|
-
export function getAppUrl() {
|
|
408
|
-
const override = process.env.ARC1_PUBLIC_URL?.trim();
|
|
409
|
-
if (override) {
|
|
410
|
-
return override.replace(/\/$/, '');
|
|
411
|
-
}
|
|
412
|
-
const vcapApp = process.env.VCAP_APPLICATION;
|
|
413
|
-
if (!vcapApp)
|
|
414
|
-
return undefined;
|
|
415
|
-
try {
|
|
416
|
-
const app = JSON.parse(vcapApp);
|
|
417
|
-
const uris = app.application_uris ?? app.uris;
|
|
418
|
-
if (Array.isArray(uris) && uris.length > 0) {
|
|
419
|
-
return `https://${uris[0]}`;
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
catch {
|
|
423
|
-
// Not valid JSON
|
|
424
|
-
}
|
|
425
|
-
return undefined;
|
|
426
|
-
}
|
|
427
|
-
//# sourceMappingURL=btp.js.map
|
package/dist/adt/btp.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"btp.js","sourceRoot":"","sources":["../../src/adt/btp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAEL,cAAc,GAEf,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AA8DhD;;;GAGG;AACH,MAAM,UAAU,iBAAiB;IAC/B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;IAC3C,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAE3B,MAAM,IAAI,GAAiB,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAChD,MAAM,MAAM,GAAc;QACxB,QAAQ,EAAE,EAAE;QACZ,aAAa,EAAE,EAAE;QACjB,WAAW,EAAE,EAAE;QACf,cAAc,EAAE,EAAE;QAClB,mBAAmB,EAAE,EAAE;QACvB,iBAAiB,EAAE,EAAE;QACrB,mBAAmB,EAAE,EAAE;QACvB,qBAAqB,EAAE,EAAE;QACzB,qBAAqB,EAAE,EAAE;QACzB,oBAAoB,EAAE,EAAE;QACxB,kBAAkB,EAAE,EAAE;QACtB,oBAAoB,EAAE,EAAE;KACzB,CAAC;IAEF,gBAAgB;IAChB,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;QACjC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;QACpC,MAAM,CAAC,QAAQ,GAAI,CAAC,CAAC,GAAc,IAAI,EAAE,CAAC;QAC1C,MAAM,CAAC,aAAa,GAAI,CAAC,CAAC,QAAmB,IAAI,EAAE,CAAC;QACpD,MAAM,CAAC,WAAW,GAAI,CAAC,CAAC,YAAuB,IAAI,EAAE,CAAC;IACxD,CAAC;IAED,sBAAsB;IACtB,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;QACvC,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;QAC1C,MAAM,CAAC,cAAc,GAAI,CAAC,CAAC,GAAc,IAAK,CAAC,CAAC,GAAc,IAAI,EAAE,CAAC;QACrE,MAAM,CAAC,mBAAmB,GAAI,CAAC,CAAC,QAAmB,IAAI,EAAE,CAAC;QAC1D,MAAM,CAAC,iBAAiB,GAAI,CAAC,CAAC,YAAuB,IAAI,EAAE,CAAC;QAC5D,MAAM,CAAC,mBAAmB,GAAI,CAAC,CAAC,iBAA4B,IAAI,EAAE,CAAC;QACnE,+BAA+B;QAC/B,IAAI,CAAC,MAAM,CAAC,mBAAmB,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC;YACzC,MAAM,CAAC,mBAAmB,GAAG,GAAI,CAAC,CAAC,GAAc,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,cAAc,CAAC;QACrF,CAAC;IACH,CAAC;IAED,uBAAuB;IACvB,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;QACxC,MAAM,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;QAC3C,MAAM,CAAC,qBAAqB,GAAI,CAAC,CAAC,oBAA+B,IAAI,EAAE,CAAC;QACxE,MAAM,CAAC,qBAAqB,GAAI,CAAC,CAAC,yBAAoC,IAAI,EAAE,CAAC;QAC7E,MAAM,CAAC,oBAAoB,GAAI,CAAC,CAAC,QAAmB,IAAI,EAAE,CAAC;QAC3D,MAAM,CAAC,kBAAkB,GAAI,CAAC,CAAC,YAAuB,IAAI,EAAE,CAAC;QAC7D,MAAM,CAAC,oBAAoB,GAAI,CAAC,CAAC,iBAA4B,IAAI,EAAE,CAAC;QACpE,wCAAwC;QACxC,IAAI,CAAC,MAAM,CAAC,oBAAoB,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC;YAC1C,MAAM,CAAC,oBAAoB,GAAG,GAAI,CAAC,CAAC,GAAc,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,cAAc,CAAC;QACtF,CAAC;aAAM,IAAI,MAAM,CAAC,oBAAoB,IAAI,CAAC,MAAM,CAAC,oBAAoB,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;YAChG,MAAM,CAAC,oBAAoB,GAAG,GAAG,MAAM,CAAC,oBAAoB,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,cAAc,CAAC;QAChG,CAAC;IACH,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,0BAA0B,EAAE;QACtC,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ;QAC3B,cAAc,EAAE,CAAC,CAAC,MAAM,CAAC,cAAc;QACvC,eAAe,EAAE,CAAC,CAAC,MAAM,CAAC,qBAAqB;KAChD,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,wEAAwE;AAExE;;;GAGG;AACH,KAAK,UAAU,2BAA2B,CACxC,QAAgB,EAChB,QAAgB,EAChB,YAAoB;IAEpB,MAAM,IAAI,GAAG,IAAI,eAAe,CAAC;QAC/B,UAAU,EAAE,oBAAoB;QAChC,SAAS,EAAE,QAAQ;QACnB,aAAa,EAAE,YAAY;KAC5B,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE;QACjC,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,mCAAmC,EAAE;QAChE,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE;KACtB,CAAC,CAAC;IAEH,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QACb,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,gCAAgC,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IACxF,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAiD,CAAC;IACjF,OAAO,EAAE,WAAW,EAAE,IAAI,CAAC,YAAY,EAAE,SAAS,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC;AACxE,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,SAAoB,EAAE,eAAuB;IACnF,wCAAwC;IACxC,MAAM,QAAQ,GAAG,SAAS,CAAC,mBAAmB,IAAI,GAAG,SAAS,CAAC,QAAQ,cAAc,CAAC;IACtF,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,2BAA2B,CACvD,QAAQ,EACR,SAAS,CAAC,mBAAmB,EAC7B,SAAS,CAAC,iBAAiB,CAC5B,CAAC;IAEF,2BAA2B;IAC3B,MAAM,OAAO,GAAG,GAAG,SAAS,CAAC,cAAc,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,8CAA8C,kBAAkB,CAAC,eAAe,CAAC,EAAE,CAAC;IAClJ,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,OAAO,EAAE;QAChC,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,WAAW,EAAE,EAAE;KACpD,CAAC,CAAC;IAEH,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QACb,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,qCAAqC,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IAC7F,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAA8C,CAAC;IAE9E,MAAM,CAAC,IAAI,CAAC,0BAA0B,EAAE;QACtC,IAAI,EAAE,IAAI,CAAC,wBAAwB,CAAC,IAAI;QACxC,GAAG,EAAE,IAAI,CAAC,wBAAwB,CAAC,GAAG;QACtC,IAAI,EAAE,IAAI,CAAC,wBAAwB,CAAC,cAAc;QAClD,SAAS,EAAE,IAAI,CAAC,wBAAwB,CAAC,SAAS;QAClD,UAAU,EAAE,IAAI,CAAC,wBAAwB,CAAC,wBAAwB;KACnE,CAAC,CAAC;IAEH,OAAO,IAAI,CAAC,wBAAwB,CAAC;AACvC,CAAC;AAED,wEAAwE;AAExE;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,SAAoB,EAAE,UAAmB;IAC/E,IAAI,CAAC,SAAS,CAAC,qBAAqB;QAAE,OAAO,IAAI,CAAC;IAElD,IAAI,WAAW,GAAG,EAAE,CAAC;IACrB,IAAI,SAAS,GAAG,CAAC,CAAC;IAElB,OAAO;QACL,IAAI,EAAE,SAAS,CAAC,qBAAqB;QACrC,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,qBAAqB,IAAI,OAAO,EAAE,EAAE,CAAC;QACrE,QAAQ,EAAE,MAAM;QAChB,UAAU;QACV,aAAa,EAAE,KAAK,IAAI,EAAE;YACxB,kDAAkD;YAClD,IAAI,WAAW,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,EAAE,CAAC;gBAC1C,OAAO,WAAW,CAAC;YACrB,CAAC;YAED,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,MAAM,2BAA2B,CAClE,SAAS,CAAC,oBAAoB,EAC9B,SAAS,CAAC,oBAAoB,EAC9B,SAAS,CAAC,kBAAkB,CAC7B,CAAC;YAEF,WAAW,GAAG,WAAW,CAAC;YAC1B,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,SAAS,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC;YACjD,OAAO,WAAW,CAAC;QACrB,CAAC;KACF,CAAC;AACJ,CAAC;AAqBD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAClD,SAAoB,EACpB,eAAuB,EACvB,OAAe;IAEf,wEAAwE;IACxE,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;YACvE,MAAM,CAAC,KAAK,CAAC,oBAAoB,EAAE;gBACjC,WAAW,EAAE,eAAe;gBAC5B,SAAS,EAAE,OAAO,CAAC,UAAU;gBAC7B,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,QAAQ,EAAE,OAAO,CAAC,SAAS;gBAC3B,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,GAAG,EAAE,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG;gBACrE,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,KAAK;gBAClD,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,GAAG,EAAE,OAAO,CAAC,GAAG;aACjB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;IACvD,CAAC;IAED,kEAAkE;IAClE,mEAAmE;IACnE,qEAAqE;IACrE,MAAM,OAAO,GAA0B,MAAM,cAAc,CAAC;QAC1D,eAAe;QACf,GAAG,EAAE,OAAO;QACZ,QAAQ,EAAE,IAAI;KACf,CAAC,CAAC;IAEH,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,+DAA+D,eAAe,GAAG,CAAC,CAAC;IACrG,CAAC;IAED,+CAA+C;IAC/C,MAAM,IAAI,GAAgB;QACxB,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,eAAe;QACrC,GAAG,EAAE,OAAO,CAAC,GAAG,IAAI,EAAE;QACtB,cAAc,EAAE,OAAO,CAAC,cAAc,IAAI,EAAE;QAC5C,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,EAAE;QAClC,IAAI,EAAE,OAAO,CAAC,QAAQ,IAAI,EAAE;QAC5B,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,EAAE;QAChC,YAAY,EAAE,OAAO,CAAC,SAAS,IAAI,SAAS;QAC5C,wBAAwB,EAAE,OAAO,CAAC,wBAAwB,IAAI,SAAS;KACxE,CAAC;IAEF,MAAM,MAAM,GAAsB,EAAE,CAAC;IACrC,MAAM,aAAa,GAA8C,OAAO,CAAC,UAAU,CAAC;IAEpF,0CAA0C;IAC1C,mEAAmE;IACnE,MAAM,UAAU,GAAG,aAAa,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC5C,SAAS,EAAE,CAAC,CAAC,IAAI;QACjB,aAAa,EAAE,CAAC,CAAC,WAAW,EAAE,GAAG;QACjC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK;QACnB,kBAAkB,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK;QAC1C,UAAU,EAAE,CAAC,CAAC,KAAK;KACpB,CAAC,CAAC,CAAC;IACJ,MAAM,CAAC,KAAK,CAAC,iCAAiC,EAAE;QAC9C,WAAW,EAAE,eAAe;QAC5B,cAAc,EAAE,IAAI,CAAC,cAAc;QACnC,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,YAAY,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;QACxC,SAAS,EAAE,UAAU,IAAI,MAAM;KAChC,CAAC,CAAC;IAEH,4CAA4C;IAC5C,IAAI,aAAa,EAAE,CAAC;QAClB,KAAK,MAAM,KAAK,IAAI,aAAa,EAAE,CAAC;YAClC,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;gBAChB,MAAM,CAAC,KAAK,CAAC,sCAAsC,EAAE;oBACnD,WAAW,EAAE,eAAe;oBAC5B,SAAS,EAAE,KAAK,CAAC,IAAI;oBACrB,KAAK,EAAE,KAAK,CAAC,KAAK;iBACnB,CAAC,CAAC;gBACH,MAAM,IAAI,GAAG,iBAAiB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;gBAC5C,MAAM,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC5C,MAAM,IAAI,KAAK,CAAC,6CAA6C,eAAe,MAAM,KAAK,CAAC,KAAK,GAAG,UAAU,EAAE,CAAC,CAAC;YAChH,CAAC;YAED,0EAA0E;YAC1E,IAAI,KAAK,CAAC,WAAW,EAAE,GAAG,KAAK,iCAAiC,EAAE,CAAC;gBACjE,MAAM,CAAC,mBAAmB,GAAG,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC;gBACrD,MAAM,CAAC,KAAK,CAAC,sDAAsD,EAAE;oBACnE,WAAW,EAAE,eAAe;oBAC5B,iBAAiB,EAAE,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,MAAM;iBAClD,CAAC,CAAC;YACL,CAAC;YAED,wFAAwF;YACxF,uFAAuF;YACvF,2FAA2F;YAC3F,sFAAsF;YACtF,2FAA2F;YAC3F,IAAI,KAAK,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,QAAQ,EAAE,CAAC;gBAC3C,MAAM,CAAC,WAAW,GAAG,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,WAAW,EAAE,KAAK,EAAE,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;YAC3F,CAAC;QACH,CAAC;IACH,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,IAAI,CAAC,kFAAkF,EAAE;YAC9F,WAAW,EAAE,eAAe;YAC5B,cAAc,EAAE,IAAI,CAAC,cAAc;SACpC,CAAC,CAAC;IACL,CAAC;IAED,sEAAsE;IACtE,EAAE;IACF,8EAA8E;IAC9E,oFAAoF;IACpF,gFAAgF;IAChF,0DAA0D;IAC1D,EAAE;IACF,2EAA2E;IAC3E,6EAA6E;IAC7E,6EAA6E;IAC7E,8EAA8E;IAC9E,mDAAmD;IACnD,EAAE;IACF,iCAAiC;IACjC,8DAA8D;IAC9D,mEAAmE;IACnE,sEAAsE;IACtE,4EAA4E;IAC5E,8DAA8D;IAC9D,sEAAsE;IACtE,EAAE;IACF,oDAAoD;IACpD,yHAAyH;IACzH,IAAI,CAAC,MAAM,CAAC,mBAAmB,IAAI,IAAI,CAAC,cAAc,KAAK,sBAAsB,IAAI,SAAS,CAAC,oBAAoB,EAAE,CAAC;QACpH,MAAM,CAAC,IAAI,CAAC,8EAA8E,EAAE;YAC1F,WAAW,EAAE,eAAe;YAC5B,eAAe,EAAE,SAAS,CAAC,oBAAoB;SAChD,CAAC,CAAC;QAEH,IAAI,CAAC;YACH,qFAAqF;YACrF,0EAA0E;YAC1E,6EAA6E;YAC7E,MAAM,YAAY,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,oBAAoB,EAAE;gBAC/D,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,mCAAmC,EAAE;gBAChE,IAAI,EAAE,IAAI,eAAe,CAAC;oBACxB,UAAU,EAAE,6CAA6C;oBACzD,SAAS,EAAE,SAAS,CAAC,oBAAoB;oBACzC,aAAa,EAAE,SAAS,CAAC,kBAAkB;oBAC3C,SAAS,EAAE,OAAO;oBAClB,YAAY,EAAE,KAAK;oBACnB,aAAa,EAAE,OAAO;iBACvB,CAAC,CAAC,QAAQ,EAAE;aACd,CAAC,CAAC;YAEH,IAAI,YAAY,CAAC,EAAE,EAAE,CAAC;gBACpB,MAAM,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC,wBAAwB;gBAEnD,2EAA2E;gBAC3E,8EAA8E;gBAC9E,6EAA6E;gBAC7E,8EAA8E;gBAC9E,MAAM,CAAC,mBAAmB,GAAG,UAAU,OAAO,EAAE,CAAC;gBAEjD,MAAM,CAAC,IAAI,CAAC,wEAAwE,EAAE;oBACpF,WAAW,EAAE,eAAe;iBAC7B,CAAC,CAAC;YACL,CAAC;iBAAM,CAAC;gBACN,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,IAAI,EAAE,CAAC;gBAC1C,MAAM,CAAC,KAAK,CAAC,gCAAgC,EAAE;oBAC7C,WAAW,EAAE,eAAe;oBAC5B,MAAM,EAAE,YAAY,CAAC,MAAM;oBAC3B,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;iBAC7B,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,KAAK,CAAC,+BAA+B,EAAE;gBAC5C,WAAW,EAAE,eAAe;gBAC5B,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;aACxD,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,qCAAqC,EAAE;QACjD,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,IAAI,EAAE,IAAI,CAAC,cAAc;QACzB,mBAAmB,EAAE,CAAC,CAAC,MAAM,CAAC,mBAAmB;QACjD,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,WAAW;KAChC,CAAC,CAAC;IAEH,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;AACnD,CAAC;AAED,wEAAwE;AAExE;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,eAAuB;IAOjE,MAAM,SAAS,GAAG,iBAAiB,EAAE,CAAC;IACtC,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,2FAA2F,CAAC,CAAC;IAC/G,CAAC;IAED,gFAAgF;IAChF,oFAAoF;IACpF,6CAA6C;IAC7C,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;IACjE,MAAM,KAAK,GACT,IAAI,CAAC,SAAS,KAAK,WAAW,CAAC,CAAC,CAAC,uBAAuB,CAAC,SAAS,EAAE,IAAI,CAAC,wBAAwB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAE5G,OAAO;QACL,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,QAAQ,EAAE,IAAI,CAAC,IAAI;QACnB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,KAAK;QACnC,KAAK;KACN,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,SAAS;IACvB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,EAAE,CAAC;IACrD,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACrC,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;IAC7C,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IAE/B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAChC,MAAM,IAAI,GAAG,GAAG,CAAC,gBAAgB,IAAI,GAAG,CAAC,IAAI,CAAC;QAC9C,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3C,OAAO,WAAW,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9B,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,iBAAiB;IACnB,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Stateless, signed OAuth `state` codec for the XSUAA callback proxy.
|
|
3
|
-
*
|
|
4
|
-
* ── Why this exists (issue #214) ──────────────────────────────────────
|
|
5
|
-
* XSUAA echoes a literal `+` (not `%2B`) for any `state` value that
|
|
6
|
-
* contains `+` when it redirects back to the OAuth client. Standard base64
|
|
7
|
-
* `state` values (e.g. VS Code generates `randomBytes(16).toString('base64')`)
|
|
8
|
-
* contain `+` ~50% of the time. The receiving client parses the callback
|
|
9
|
-
* query string with `application/x-www-form-urlencoded` semantics, where
|
|
10
|
-
* `+` decodes to a space, so the round-tripped `state` no longer matches the
|
|
11
|
-
* value the client generated → "State does not match" → login fails.
|
|
12
|
-
*
|
|
13
|
-
* ARC-1 cannot influence what XSUAA emits, and XSUAA redirects DIRECTLY to
|
|
14
|
-
* the client today (ARC-1 is not in the return path). The only fix is to
|
|
15
|
-
* insert ARC-1 into the return path: send XSUAA a `state` that ARC-1
|
|
16
|
-
* controls and that is immune to the `+` bug, then re-emit the client's
|
|
17
|
-
* ORIGINAL `state` correctly when redirecting back to the client.
|
|
18
|
-
*
|
|
19
|
-
* ── How this codec is immune to the `+` bug ───────────────────────────
|
|
20
|
-
* The token is `base64url(payload) + "." + base64url(sig)`. base64url uses
|
|
21
|
-
* the alphabet `A-Za-z0-9-_` — no `+`, no `/`. The `.` separator is an
|
|
22
|
-
* RFC 3986 unreserved character. So the entire token is URL-safe: XSUAA has
|
|
23
|
-
* no `+` to mangle, and Express's `+`→space query decoding is a no-op on it.
|
|
24
|
-
* The client's real `state` (which may contain `+`) rides INSIDE the opaque
|
|
25
|
-
* base64url payload, so it survives the XSUAA round-trip untouched.
|
|
26
|
-
*
|
|
27
|
-
* ── Why stateless (vs an in-memory map) ───────────────────────────────
|
|
28
|
-
* Mirrors the StatelessDcrClientStore design (PR #212): the token carries
|
|
29
|
-
* its own payload + HMAC signature, so any instance with the same signing
|
|
30
|
-
* key can validate it. No in-memory map → survives `cf restart`, cell
|
|
31
|
-
* moves, and horizontal scale-out. The signing key is derived (HKDF-style)
|
|
32
|
-
* from the same secret the DCR store uses, with a distinct domain-separation
|
|
33
|
-
* label so the two key spaces never overlap.
|
|
34
|
-
*
|
|
35
|
-
* ── Upstream tracking / when this whole module can be deleted ──────────
|
|
36
|
-
* This is a WORKAROUND for an XSUAA bug. It can be removed ONLY when XSUAA
|
|
37
|
-
* stops emitting a literal `+` (emits `%2B`) for `state` on the authorize
|
|
38
|
-
* redirect. Tracking:
|
|
39
|
-
* - arc-1 issue: https://github.com/marianfoo/arc-1/issues/214
|
|
40
|
-
* - XSUAA root cause: no public SAP Note as of 2026-06; the `+`→literal
|
|
41
|
-
* echo is the actual defect and the only thing whose
|
|
42
|
-
* fix makes this module removable.
|
|
43
|
-
* - VS Code (client): https://github.com/microsoft/vscode/issues/314715
|
|
44
|
-
* asks VS Code to use base64url `state`. If accepted it
|
|
45
|
-
* fixes the VS Code SYMPTOM only — other MCP clients
|
|
46
|
-
* (Cursor, claude.ai, Copilot Studio, …) still send
|
|
47
|
-
* base64 `state` containing `+`, so the callback proxy
|
|
48
|
-
* stays until the XSUAA-side fix lands. Do NOT delete
|
|
49
|
-
* this module just because vscode#314715 closes.
|
|
50
|
-
* To verify whether the XSUAA bug is gone, re-run the issue #214 spectrum
|
|
51
|
-
* reproducer (see the issue thread) against the target XSUAA tenant.
|
|
52
|
-
*/
|
|
53
|
-
export type DecodeResult = {
|
|
54
|
-
kind: 'ok';
|
|
55
|
-
clientState?: string;
|
|
56
|
-
clientRedirectUri: string;
|
|
57
|
-
clientId: string;
|
|
58
|
-
} | {
|
|
59
|
-
kind: 'error';
|
|
60
|
-
reason: 'malformed' | 'bad_signature' | 'invalid_payload' | 'expired';
|
|
61
|
-
};
|
|
62
|
-
/**
|
|
63
|
-
* Signs and verifies OAuth `state` tokens for the XSUAA callback proxy.
|
|
64
|
-
*/
|
|
65
|
-
export declare class OAuthStateCodec {
|
|
66
|
-
private readonly hmacKey;
|
|
67
|
-
private readonly ttlSeconds;
|
|
68
|
-
constructor(signingSecret: string, opts?: {
|
|
69
|
-
ttlSeconds?: number;
|
|
70
|
-
});
|
|
71
|
-
/**
|
|
72
|
-
* Encode a URL-safe, signed state token. The returned value is safe to put
|
|
73
|
-
* in a query string and round-trip through XSUAA (no `+`, no `/`).
|
|
74
|
-
*
|
|
75
|
-
* @param input.now Injectable clock (epoch ms) for deterministic tests.
|
|
76
|
-
*/
|
|
77
|
-
encode(input: {
|
|
78
|
-
clientState?: string;
|
|
79
|
-
clientRedirectUri: string;
|
|
80
|
-
clientId: string;
|
|
81
|
-
now?: number;
|
|
82
|
-
}): string;
|
|
83
|
-
/**
|
|
84
|
-
* Decode and verify a state token. Never throws — returns a typed result.
|
|
85
|
-
*
|
|
86
|
-
* @param now Injectable clock (epoch ms) for deterministic tests.
|
|
87
|
-
*/
|
|
88
|
-
decode(token: string, now?: number): DecodeResult;
|
|
89
|
-
private sign;
|
|
90
|
-
private verifySignature;
|
|
91
|
-
}
|
|
92
|
-
//# sourceMappingURL=oauth-state.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"oauth-state.d.ts","sourceRoot":"","sources":["../../src/server/oauth-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAsCH,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACjF;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,WAAW,GAAG,eAAe,GAAG,iBAAiB,GAAG,SAAS,CAAA;CAAE,CAAC;AAE7F;;GAEG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,aAAa,EAAE,MAAM,EAAE,IAAI,GAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAO;IAUrE;;;;;OAKG;IACH,MAAM,CAAC,KAAK,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,iBAAiB,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM;IAe1G;;;;OAIG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,GAAE,MAAmB,GAAG,YAAY;IA2B7D,OAAO,CAAC,IAAI;IAKZ,OAAO,CAAC,eAAe;CAQxB"}
|