@mcp-abap-adt/auth-broker 0.1.12 → 0.2.2

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.
@@ -2,8 +2,12 @@
2
2
  /**
3
3
  * Main AuthBroker class for managing JWT tokens based on destinations
4
4
  */
5
+ var __importDefault = (this && this.__importDefault) || function (mod) {
6
+ return (mod && mod.__esModule) ? mod : { "default": mod };
7
+ };
5
8
  Object.defineProperty(exports, "__esModule", { value: true });
6
9
  exports.AuthBroker = void 0;
10
+ const axios_1 = __importDefault(require("axios"));
7
11
  /**
8
12
  * No-op logger implementation for default fallback when logger is not provided
9
13
  */
@@ -13,9 +17,6 @@ const noOpLogger = {
13
17
  warn: () => { },
14
18
  debug: () => { },
15
19
  };
16
- /**
17
- * AuthBroker manages JWT authentication tokens for destinations
18
- */
19
20
  class AuthBroker {
20
21
  browser;
21
22
  logger;
@@ -24,43 +25,28 @@ class AuthBroker {
24
25
  tokenProvider;
25
26
  /**
26
27
  * Create a new AuthBroker instance
27
- * @param stores Object with stores and token provider
28
- * - serviceKeyStore: Store for service keys
29
- * - sessionStore: Store for session data
30
- * - tokenProvider: Token provider implementing ITokenProvider interface
28
+ * @param config Configuration object with stores and token provider
29
+ * - sessionStore: Store for session data (required)
30
+ * - serviceKeyStore: Store for service keys (optional)
31
+ * - tokenProvider: Token provider implementing ITokenProvider interface (optional). If not provided, direct UAA HTTP requests will be used when UAA credentials are available
31
32
  * @param browser Optional browser name for authentication (chrome, edge, firefox, system, none).
32
33
  * Default: 'system' (system default browser).
33
34
  * Use 'none' to print URL instead of opening browser.
34
35
  * @param logger Optional logger instance implementing ILogger interface. If not provided, uses no-op logger.
35
36
  */
36
- constructor(stores, browser, logger) {
37
- // Validate that stores and provider are provided and not null/undefined
38
- if (!stores) {
39
- throw new Error('AuthBroker: stores parameter is required');
37
+ constructor(config, browser, logger) {
38
+ // Validate that config is provided
39
+ if (!config) {
40
+ throw new Error('AuthBroker: config parameter is required');
40
41
  }
41
- if (!stores.serviceKeyStore) {
42
- throw new Error('AuthBroker: serviceKeyStore is required');
43
- }
44
- if (!stores.sessionStore) {
42
+ // Validate required sessionStore
43
+ if (!config.sessionStore) {
45
44
  throw new Error('AuthBroker: sessionStore is required');
46
45
  }
47
- if (!stores.tokenProvider) {
48
- throw new Error('AuthBroker: tokenProvider is required');
49
- }
50
46
  // Validate that stores and provider are correctly instantiated (have required methods)
51
- const serviceKeyStore = stores.serviceKeyStore;
52
- const sessionStore = stores.sessionStore;
53
- const tokenProvider = stores.tokenProvider;
54
- // Check serviceKeyStore methods
55
- if (typeof serviceKeyStore.getServiceKey !== 'function') {
56
- throw new Error('AuthBroker: serviceKeyStore.getServiceKey must be a function');
57
- }
58
- if (typeof serviceKeyStore.getAuthorizationConfig !== 'function') {
59
- throw new Error('AuthBroker: serviceKeyStore.getAuthorizationConfig must be a function');
60
- }
61
- if (typeof serviceKeyStore.getConnectionConfig !== 'function') {
62
- throw new Error('AuthBroker: serviceKeyStore.getConnectionConfig must be a function');
63
- }
47
+ const sessionStore = config.sessionStore;
48
+ const tokenProvider = config.tokenProvider;
49
+ const serviceKeyStore = config.serviceKeyStore;
64
50
  // Check sessionStore methods
65
51
  if (typeof sessionStore.getAuthorizationConfig !== 'function') {
66
52
  throw new Error('AuthBroker: sessionStore.getAuthorizationConfig must be a function');
@@ -74,242 +60,504 @@ class AuthBroker {
74
60
  if (typeof sessionStore.setConnectionConfig !== 'function') {
75
61
  throw new Error('AuthBroker: sessionStore.setConnectionConfig must be a function');
76
62
  }
77
- // Check tokenProvider methods
78
- if (typeof tokenProvider.getConnectionConfig !== 'function') {
79
- throw new Error('AuthBroker: tokenProvider.getConnectionConfig must be a function');
63
+ // Check tokenProvider methods (if provided)
64
+ if (tokenProvider) {
65
+ if (typeof tokenProvider.getConnectionConfig !== 'function') {
66
+ throw new Error('AuthBroker: tokenProvider.getConnectionConfig must be a function');
67
+ }
68
+ // validateToken is optional, so we don't check it
69
+ }
70
+ // Check serviceKeyStore methods (if provided)
71
+ if (serviceKeyStore) {
72
+ if (typeof serviceKeyStore.getServiceKey !== 'function') {
73
+ throw new Error('AuthBroker: serviceKeyStore.getServiceKey must be a function');
74
+ }
75
+ if (typeof serviceKeyStore.getAuthorizationConfig !== 'function') {
76
+ throw new Error('AuthBroker: serviceKeyStore.getAuthorizationConfig must be a function');
77
+ }
78
+ if (typeof serviceKeyStore.getConnectionConfig !== 'function') {
79
+ throw new Error('AuthBroker: serviceKeyStore.getConnectionConfig must be a function');
80
+ }
80
81
  }
81
- // validateToken is optional, so we don't check it
82
82
  this.serviceKeyStore = serviceKeyStore;
83
83
  this.sessionStore = sessionStore;
84
84
  this.tokenProvider = tokenProvider;
85
85
  this.browser = browser || 'system';
86
86
  this.logger = logger || noOpLogger;
87
87
  // Log successful initialization
88
- this.logger?.debug('AuthBroker initialized: serviceKeyStore(ok), sessionStore(ok), tokenProvider(ok)');
88
+ const hasServiceKeyStore = !!this.serviceKeyStore;
89
+ const hasTokenProvider = !!this.tokenProvider;
90
+ this.logger?.debug(`AuthBroker initialized: sessionStore(ok), serviceKeyStore(${hasServiceKeyStore ? 'ok' : 'none'}), tokenProvider(${hasTokenProvider ? 'ok' : 'none'})`);
91
+ }
92
+ /**
93
+ * Refresh token using refresh_token grant type (direct UAA HTTP request)
94
+ * @param refreshToken Refresh token
95
+ * @param authConfig UAA authorization configuration
96
+ * @returns Promise that resolves to new tokens
97
+ */
98
+ async refreshTokenDirect(refreshToken, authConfig) {
99
+ if (!authConfig.uaaUrl || !authConfig.uaaClientId || !authConfig.uaaClientSecret) {
100
+ throw new Error('UAA credentials incomplete: uaaUrl, uaaClientId, and uaaClientSecret are required');
101
+ }
102
+ const tokenUrl = `${authConfig.uaaUrl}/oauth/token`;
103
+ const params = new URLSearchParams();
104
+ params.append('grant_type', 'refresh_token');
105
+ params.append('refresh_token', refreshToken);
106
+ const authString = Buffer.from(`${authConfig.uaaClientId}:${authConfig.uaaClientSecret}`).toString('base64');
107
+ try {
108
+ const response = await (0, axios_1.default)({
109
+ method: 'post',
110
+ url: tokenUrl,
111
+ headers: {
112
+ Authorization: `Basic ${authString}`,
113
+ 'Content-Type': 'application/x-www-form-urlencoded',
114
+ },
115
+ data: params.toString(),
116
+ timeout: 30000,
117
+ });
118
+ if (response.data && response.data.access_token) {
119
+ return {
120
+ accessToken: response.data.access_token,
121
+ refreshToken: response.data.refresh_token || refreshToken,
122
+ expiresIn: response.data.expires_in,
123
+ };
124
+ }
125
+ else {
126
+ throw new Error('Response does not contain access_token');
127
+ }
128
+ }
129
+ catch (error) {
130
+ if (error.response) {
131
+ throw new Error(`Token refresh failed (${error.response.status}): ${JSON.stringify(error.response.data)}`);
132
+ }
133
+ else {
134
+ throw new Error(`Token refresh failed: ${error.message}`);
135
+ }
136
+ }
137
+ }
138
+ /**
139
+ * Get token using client_credentials grant type (direct UAA HTTP request)
140
+ * @param authConfig UAA authorization configuration
141
+ * @returns Promise that resolves to access token
142
+ */
143
+ async getTokenWithClientCredentials(authConfig) {
144
+ if (!authConfig.uaaUrl || !authConfig.uaaClientId || !authConfig.uaaClientSecret) {
145
+ throw new Error('UAA credentials incomplete: uaaUrl, uaaClientId, and uaaClientSecret are required');
146
+ }
147
+ const tokenUrl = `${authConfig.uaaUrl}/oauth/token`;
148
+ const params = new URLSearchParams();
149
+ params.append('grant_type', 'client_credentials');
150
+ params.append('client_id', authConfig.uaaClientId);
151
+ params.append('client_secret', authConfig.uaaClientSecret);
152
+ try {
153
+ const response = await (0, axios_1.default)({
154
+ method: 'post',
155
+ url: tokenUrl,
156
+ headers: {
157
+ 'Content-Type': 'application/x-www-form-urlencoded',
158
+ },
159
+ data: params.toString(),
160
+ timeout: 30000,
161
+ });
162
+ if (response.data && response.data.access_token) {
163
+ return {
164
+ accessToken: response.data.access_token,
165
+ refreshToken: response.data.refresh_token,
166
+ expiresIn: response.data.expires_in,
167
+ };
168
+ }
169
+ else {
170
+ throw new Error('Response does not contain access_token');
171
+ }
172
+ }
173
+ catch (error) {
174
+ if (error.response) {
175
+ throw new Error(`Client credentials authentication failed (${error.response.status}): ${JSON.stringify(error.response.data)}`);
176
+ }
177
+ else {
178
+ throw new Error(`Client credentials authentication failed: ${error.message}`);
179
+ }
180
+ }
89
181
  }
90
182
  /**
91
183
  * Get authentication token for destination.
92
- * Tries to load from session store, validates it, and refreshes if needed using a fallback chain.
93
- *
94
- * **Fallback Chain:**
95
- * 1. **Check session**: Load token from session store and validate it
96
- * - If token is valid, return it immediately
97
- * - If token is invalid or missing, continue to next step
98
- *
99
- * 2. **Check service key**: Verify that service key exists
100
- * - If no service key found, throw error
184
+ * Implements a three-step flow: Step 0 (initialize), Step 1 (refresh), Step 2 (UAA).
101
185
  *
102
- * 3. **Try refresh token**: If refresh token is available in session, attempt to refresh using it (via tokenProvider)
103
- * - If successful, save new token to session and return it
104
- * - If failed, continue to next step
186
+ * **Flow:**
187
+ * **Step 0: Initialize Session with Token (if needed)**
188
+ * - Check if session has `authorizationToken` AND UAA credentials
189
+ * - If both are empty AND serviceKeyStore is available:
190
+ * - Try direct UAA request from service key (if UAA credentials available)
191
+ * - If failed and tokenProvider available → use provider
192
+ * - If session has token OR UAA credentials → proceed to Step 1
105
193
  *
106
- * 4. **Try UAA (client_credentials)**: Attempt to get token using UAA credentials (via tokenProvider)
107
- * - If UAA parameters are available and authentication succeeds, save token to session and return it
108
- * - If failed or parameters missing, continue to next step
194
+ * **Step 1: Refresh Token Flow**
195
+ * - Check if refresh token exists in session
196
+ * - If refresh token exists:
197
+ * - Try direct UAA refresh (if UAA credentials in session)
198
+ * - If failed and tokenProvider available → use provider
199
+ * - If successful → return new token
200
+ * - Otherwise → proceed to Step 2
109
201
  *
110
- * 5. **Try browser authentication**: Attempt browser-based OAuth2 flow using service key (via tokenProvider)
111
- * - If successful, save token and refresh token to session and return it
112
- * - If failed, continue to next step
202
+ * **Step 2: UAA Credentials Flow**
203
+ * - Check if UAA credentials exist in session or service key
204
+ * - Try direct UAA client_credentials request (if UAA credentials available)
205
+ * - If failed and tokenProvider available → use provider
206
+ * - If successful → return new token
207
+ * - If all failed → return error
113
208
  *
114
- * 6. **Throw error**: If all authentication methods failed, throw comprehensive error with details
115
- *
116
- * **Note**: Token validation is performed only when checking existing session (step 1).
117
- * Tokens obtained through refresh/UAA/browser authentication are not validated before being saved.
209
+ * **Important Notes:**
210
+ * - If sessionStore contains valid UAA credentials, neither serviceKeyStore nor tokenProvider are required.
211
+ * Direct UAA HTTP requests will be used automatically.
212
+ * - tokenProvider is only needed when:
213
+ * - Initializing session from service key via browser authentication (Step 0)
214
+ * - Direct UAA requests fail and fallback to provider is needed
118
215
  *
119
216
  * @param destination Destination name (e.g., "TRIAL")
120
217
  * @returns Promise that resolves to JWT token string
121
- * @throws Error if neither session data nor service key found, or if all authentication methods failed
218
+ * @throws Error if session initialization fails or all authentication methods failed
122
219
  */
123
220
  async getToken(destination) {
124
221
  this.logger?.debug(`Getting token for destination: ${destination}`);
125
- // Step 1: Check if session exists and token is valid
222
+ // Step 0: Initialize Session with Token (if needed)
126
223
  const connConfig = await this.sessionStore.getConnectionConfig(destination);
127
- if (connConfig?.authorizationToken) {
128
- this.logger?.debug(`Session found: token(${connConfig.authorizationToken.length} chars), serviceUrl(${connConfig.serviceUrl ? 'yes' : 'no'})`);
224
+ const authConfig = await this.sessionStore.getAuthorizationConfig(destination);
225
+ // Check if session has serviceUrl (required)
226
+ // If not in session, try to get it from serviceKeyStore
227
+ let serviceUrl = connConfig?.serviceUrl;
228
+ if (!serviceUrl && this.serviceKeyStore) {
229
+ const serviceKeyConnConfig = await this.serviceKeyStore.getConnectionConfig(destination);
230
+ serviceUrl = serviceKeyConnConfig?.serviceUrl;
231
+ if (serviceUrl) {
232
+ this.logger?.debug(`serviceUrl not in session for ${destination}, found in serviceKeyStore`);
233
+ }
234
+ }
235
+ if (!serviceUrl) {
236
+ this.logger?.error(`Session for destination "${destination}" is missing required field 'serviceUrl'. SessionStore must contain initial session with serviceUrl${this.serviceKeyStore ? ' or serviceKeyStore must contain serviceUrl' : ''}.`);
237
+ throw new Error(`Session for destination "${destination}" is missing required field 'serviceUrl'. ` +
238
+ `SessionStore must contain initial session with serviceUrl${this.serviceKeyStore ? ' or serviceKeyStore must contain serviceUrl' : ''}.`);
239
+ }
240
+ // Check if we have token or UAA credentials
241
+ const hasToken = !!connConfig?.authorizationToken;
242
+ const hasUaaCredentials = !!(authConfig?.uaaUrl && authConfig?.uaaClientId && authConfig?.uaaClientSecret);
243
+ this.logger?.debug(`Step 0: Session check for ${destination}: hasToken(${hasToken}), hasUaaCredentials(${hasUaaCredentials}), serviceUrl(${serviceUrl ? 'yes' : 'no'})`);
244
+ // If token is empty AND UAA fields are empty, try to initialize from service key
245
+ if (!hasToken && !hasUaaCredentials) {
246
+ this.logger?.debug(`Step 0: Token and UAA credentials are empty for ${destination}, attempting initialization from service key`);
247
+ if (!this.serviceKeyStore) {
248
+ this.logger?.error(`Step 0: Cannot initialize session for ${destination}: authorizationToken is empty, UAA credentials are empty, and serviceKeyStore is not available`);
249
+ throw new Error(`Cannot initialize session for destination "${destination}": authorizationToken is empty, UAA credentials are empty, and serviceKeyStore is not available. ` +
250
+ `Provide serviceKeyStore to initialize from service key.`);
251
+ }
252
+ try {
253
+ // Get UAA credentials from service key
254
+ const serviceKeyAuthConfig = await this.serviceKeyStore.getAuthorizationConfig(destination);
255
+ if (!serviceKeyAuthConfig || !serviceKeyAuthConfig.uaaUrl || !serviceKeyAuthConfig.uaaClientId || !serviceKeyAuthConfig.uaaClientSecret) {
256
+ this.logger?.error(`Step 0: Service key for ${destination} missing UAA credentials`);
257
+ throw new Error(`Service key for destination "${destination}" does not contain UAA credentials`);
258
+ }
259
+ // Try direct UAA request first if UAA credentials are available in service key
260
+ let tokenResult;
261
+ try {
262
+ // Use direct UAA HTTP request (preferred when UAA credentials are available)
263
+ this.logger?.debug(`Step 0: Authenticating via direct UAA request for ${destination} using service key UAA credentials`);
264
+ const uaaResult = await this.getTokenWithClientCredentials(serviceKeyAuthConfig);
265
+ tokenResult = {
266
+ connectionConfig: {
267
+ authorizationToken: uaaResult.accessToken,
268
+ },
269
+ refreshToken: uaaResult.refreshToken,
270
+ };
271
+ }
272
+ catch (directError) {
273
+ this.logger?.debug(`Step 0: Direct UAA request failed for ${destination}: ${directError.message}, trying provider`);
274
+ // If direct UAA failed and we have provider, try provider
275
+ if (this.tokenProvider) {
276
+ this.logger?.debug(`Step 0: Authenticating via provider for ${destination} using service key UAA credentials`);
277
+ tokenResult = await this.tokenProvider.getConnectionConfig(serviceKeyAuthConfig, {
278
+ browser: this.browser,
279
+ logger: this.logger,
280
+ });
281
+ }
282
+ else {
283
+ throw directError; // No provider, re-throw direct error
284
+ }
285
+ }
286
+ const tokenLength = tokenResult.connectionConfig.authorizationToken?.length || 0;
287
+ this.logger?.info(`Step 0: Token initialized for ${destination}: token(${tokenLength} chars), hasRefreshToken(${!!tokenResult.refreshToken})`);
288
+ // Get serviceUrl from service key store if not in connectionConfig
289
+ const serviceKeyConnConfig = await this.serviceKeyStore.getConnectionConfig(destination);
290
+ const connectionConfigWithServiceUrl = {
291
+ ...tokenResult.connectionConfig,
292
+ serviceUrl: tokenResult.connectionConfig.serviceUrl || serviceKeyConnConfig?.serviceUrl || serviceUrl,
293
+ };
294
+ // Save token and UAA credentials to session
295
+ await this.sessionStore.setConnectionConfig(destination, connectionConfigWithServiceUrl);
296
+ await this.sessionStore.setAuthorizationConfig(destination, {
297
+ ...serviceKeyAuthConfig,
298
+ refreshToken: tokenResult.refreshToken || serviceKeyAuthConfig.refreshToken,
299
+ });
300
+ return tokenResult.connectionConfig.authorizationToken;
301
+ }
302
+ catch (error) {
303
+ this.logger?.error(`Step 0: Failed to initialize session for ${destination}: ${error.message}`);
304
+ const errorMessage = `Cannot initialize session for destination "${destination}": ${error.message}. ` +
305
+ `Ensure serviceKeyStore contains valid service key with UAA credentials${this.tokenProvider ? ' or provide tokenProvider for alternative authentication' : ''}.`;
306
+ throw new Error(errorMessage);
307
+ }
308
+ }
309
+ // If we have a token, validate it first
310
+ if (hasToken && connConfig.authorizationToken) {
311
+ this.logger?.debug(`Step 0: Token found for ${destination}, validating`);
129
312
  // Validate token if provider supports validation and we have service URL
130
- if (this.tokenProvider.validateToken && connConfig.serviceUrl) {
131
- this.logger?.debug(`Validating token for ${destination}`);
132
- const isValid = await this.tokenProvider.validateToken(connConfig.authorizationToken, connConfig.serviceUrl);
313
+ if (this.tokenProvider?.validateToken && serviceUrl) {
314
+ const isValid = await this.tokenProvider.validateToken(connConfig.authorizationToken, serviceUrl);
133
315
  if (isValid) {
134
- this.logger?.info(`Token valid for ${destination}: token(${connConfig.authorizationToken.length} chars)`);
316
+ this.logger?.info(`Step 0: Token valid for ${destination}: token(${connConfig.authorizationToken.length} chars)`);
135
317
  return connConfig.authorizationToken;
136
318
  }
137
- this.logger?.debug(`Token invalid for ${destination}, continuing to refresh`);
319
+ this.logger?.debug(`Step 0: Token invalid for ${destination}, continuing to refresh`);
138
320
  }
139
321
  else {
140
322
  // No service URL or provider doesn't support validation - just return token
141
- this.logger?.info(`Token found for ${destination} (no validation): token(${connConfig.authorizationToken.length} chars)`);
323
+ this.logger?.info(`Step 0: Token found for ${destination} (no validation): token(${connConfig.authorizationToken.length} chars)`);
142
324
  return connConfig.authorizationToken;
143
325
  }
144
326
  }
145
- else {
146
- this.logger?.debug(`No session found for ${destination}`);
147
- }
148
- // Step 2: No valid session, check if we have service key
149
- this.logger?.debug(`Checking service key for ${destination}`);
150
- const serviceKey = await this.serviceKeyStore.getServiceKey(destination);
151
- if (!serviceKey) {
152
- this.logger?.error(`No service key found for ${destination}`);
153
- throw new Error(`No authentication found for destination "${destination}". ` +
154
- `No session data and no service key found.`);
155
- }
156
- // Get authorization config from service key
157
- const authConfig = await this.serviceKeyStore.getAuthorizationConfig(destination);
158
- if (!authConfig) {
159
- this.logger?.error(`Service key for ${destination} missing UAA credentials`);
160
- throw new Error(`Service key for destination "${destination}" does not contain UAA credentials`);
161
- }
162
- this.logger?.debug(`Service key loaded for ${destination}: uaaUrl(${authConfig.uaaUrl.substring(0, 40)}...)`);
163
- // Get refresh token from session (if exists)
164
- const sessionAuthConfig = await this.sessionStore.getAuthorizationConfig(destination);
165
- const refreshToken = sessionAuthConfig?.refreshToken || authConfig.refreshToken;
166
- this.logger?.debug(`Refresh token check for ${destination}: hasRefreshToken(${!!refreshToken})`);
167
- let tokenResult;
168
- let lastError = null;
169
- // Step 3: Try to refresh using refresh token (if available) via tokenProvider
327
+ // Step 1: Refresh Token Flow
328
+ this.logger?.debug(`Step 1: Checking refresh token for ${destination}`);
329
+ const refreshToken = authConfig?.refreshToken;
170
330
  if (refreshToken) {
171
331
  try {
172
- this.logger?.debug(`Trying refresh token flow for ${destination}`);
173
- const authConfigWithRefresh = { ...authConfig, refreshToken };
174
- tokenResult = await this.tokenProvider.getConnectionConfig(authConfigWithRefresh, {
175
- browser: this.browser,
176
- logger: this.logger,
177
- });
332
+ this.logger?.debug(`Step 1: Trying refresh token flow for ${destination}`);
333
+ // Get UAA credentials from session or service key
334
+ const uaaCredentials = authConfig || (this.serviceKeyStore ? await this.serviceKeyStore.getAuthorizationConfig(destination) : null);
335
+ if (!uaaCredentials || !uaaCredentials.uaaUrl || !uaaCredentials.uaaClientId || !uaaCredentials.uaaClientSecret) {
336
+ throw new Error('UAA credentials not found in session and serviceKeyStore not available');
337
+ }
338
+ let tokenResult;
339
+ // Try direct UAA request if UAA credentials are available
340
+ if (uaaCredentials.uaaUrl && uaaCredentials.uaaClientId && uaaCredentials.uaaClientSecret) {
341
+ try {
342
+ this.logger?.debug(`Step 1: Trying direct UAA refresh for ${destination}`);
343
+ const uaaResult = await this.refreshTokenDirect(refreshToken, uaaCredentials);
344
+ tokenResult = {
345
+ connectionConfig: {
346
+ authorizationToken: uaaResult.accessToken,
347
+ },
348
+ refreshToken: uaaResult.refreshToken,
349
+ };
350
+ this.logger?.debug(`Step 1: Direct UAA refresh succeeded for ${destination}`);
351
+ }
352
+ catch (directError) {
353
+ this.logger?.debug(`Step 1: Direct UAA refresh failed for ${destination}: ${directError.message}, trying provider`);
354
+ // If direct UAA failed and we have provider, try provider
355
+ if (this.tokenProvider) {
356
+ const authConfigWithRefresh = { ...uaaCredentials, refreshToken };
357
+ tokenResult = await this.tokenProvider.getConnectionConfig(authConfigWithRefresh, {
358
+ browser: this.browser,
359
+ logger: this.logger,
360
+ });
361
+ }
362
+ else {
363
+ throw directError; // No provider, re-throw direct error
364
+ }
365
+ }
366
+ }
367
+ else if (this.tokenProvider) {
368
+ // No UAA credentials but have provider
369
+ const authConfigWithRefresh = { ...uaaCredentials, refreshToken };
370
+ tokenResult = await this.tokenProvider.getConnectionConfig(authConfigWithRefresh, {
371
+ browser: this.browser,
372
+ logger: this.logger,
373
+ });
374
+ }
375
+ else {
376
+ throw new Error('UAA credentials incomplete and tokenProvider not available');
377
+ }
178
378
  const tokenLength = tokenResult.connectionConfig.authorizationToken?.length || 0;
179
- this.logger?.info(`Token refreshed for ${destination}: token(${tokenLength} chars), hasRefreshToken(${!!tokenResult.refreshToken})`);
180
- // Get serviceUrl from service key store if not in connectionConfig (required for ABAP stores)
181
- // For XSUAA service keys, serviceUrl may not exist, which is fine for BTP/XSUAA stores
182
- const serviceKeyConnConfig = await this.serviceKeyStore.getConnectionConfig(destination);
379
+ this.logger?.info(`Step 1: Token refreshed for ${destination}: token(${tokenLength} chars), hasRefreshToken(${!!tokenResult.refreshToken})`);
380
+ // Get serviceUrl from session or service key (use the one we already have from the beginning of the method)
381
+ const finalServiceUrl = tokenResult.connectionConfig.serviceUrl ||
382
+ serviceUrl ||
383
+ (this.serviceKeyStore ? (await this.serviceKeyStore.getConnectionConfig(destination))?.serviceUrl : undefined);
183
384
  const connectionConfigWithServiceUrl = {
184
385
  ...tokenResult.connectionConfig,
185
- serviceUrl: tokenResult.connectionConfig.serviceUrl || serviceKeyConnConfig?.serviceUrl,
386
+ serviceUrl: finalServiceUrl,
186
387
  };
187
- // Update or create session with new token (stores handle creation if session doesn't exist)
388
+ // Update session with new token
188
389
  await this.sessionStore.setConnectionConfig(destination, connectionConfigWithServiceUrl);
189
390
  if (tokenResult.refreshToken) {
190
391
  await this.sessionStore.setAuthorizationConfig(destination, {
191
- ...authConfig,
392
+ ...uaaCredentials,
192
393
  refreshToken: tokenResult.refreshToken,
193
394
  });
194
395
  }
195
396
  return tokenResult.connectionConfig.authorizationToken;
196
397
  }
197
398
  catch (error) {
198
- lastError = error;
199
- this.logger?.debug(`Refresh token flow failed for ${destination}: ${error.message}, trying UAA`);
200
- // Continue to next step
399
+ this.logger?.debug(`Step 1: Refresh token flow failed for ${destination}: ${error.message}, trying Step 2`);
400
+ // Continue to Step 2
201
401
  }
202
402
  }
203
- // Step 4: Try UAA (client_credentials) via tokenProvider (without refresh token)
204
- // TokenProvider should try client_credentials if refresh token is not provided
205
- try {
206
- this.logger?.debug(`Trying UAA (client_credentials) flow for ${destination}`);
207
- const authConfigWithoutRefresh = { ...authConfig, refreshToken: undefined };
208
- tokenResult = await this.tokenProvider.getConnectionConfig(authConfigWithoutRefresh, {
209
- browser: this.browser,
210
- logger: this.logger,
211
- });
212
- const tokenLength = tokenResult.connectionConfig.authorizationToken?.length || 0;
213
- this.logger?.info(`Token obtained via UAA for ${destination}: token(${tokenLength} chars), hasRefreshToken(${!!tokenResult.refreshToken})`);
214
- // Get serviceUrl from service key store if not in connectionConfig (required for ABAP stores)
215
- // For XSUAA service keys, serviceUrl may not exist, which is fine for BTP/XSUAA stores
216
- const serviceKeyConnConfig = await this.serviceKeyStore.getConnectionConfig(destination);
217
- const connectionConfigWithServiceUrl = {
218
- ...tokenResult.connectionConfig,
219
- serviceUrl: tokenResult.connectionConfig.serviceUrl || serviceKeyConnConfig?.serviceUrl,
220
- };
221
- // Update or create session with new token (stores handle creation if session doesn't exist)
222
- await this.sessionStore.setConnectionConfig(destination, connectionConfigWithServiceUrl);
223
- if (tokenResult.refreshToken) {
224
- await this.sessionStore.setAuthorizationConfig(destination, {
225
- ...authConfig,
226
- refreshToken: tokenResult.refreshToken,
227
- });
228
- }
229
- return tokenResult.connectionConfig.authorizationToken;
403
+ else {
404
+ this.logger?.debug(`Step 1: No refresh token found for ${destination}, proceeding to Step 2`);
230
405
  }
231
- catch (error) {
232
- lastError = error;
233
- this.logger?.debug(`UAA flow failed for ${destination}: ${error.message}, trying browser`);
234
- // Continue to next step
406
+ // Step 2: UAA Credentials Flow
407
+ this.logger?.debug(`Step 2: Checking UAA credentials for ${destination}`);
408
+ // Get UAA credentials from session or service key
409
+ const uaaCredentials = authConfig || (this.serviceKeyStore ? await this.serviceKeyStore.getAuthorizationConfig(destination) : null);
410
+ if (!uaaCredentials || !uaaCredentials.uaaUrl || !uaaCredentials.uaaClientId || !uaaCredentials.uaaClientSecret) {
411
+ const errorMessage = `Step 2: UAA credentials not found for ${destination}. ` +
412
+ `Session has no UAA credentials${this.serviceKeyStore ? ' and serviceKeyStore has no UAA credentials' : ' and serviceKeyStore is not available'}.`;
413
+ this.logger?.error(errorMessage);
414
+ throw new Error(errorMessage);
235
415
  }
236
- // Step 5: Try browser authentication via tokenProvider (should be last resort)
237
- // TokenProvider should use browser auth if refresh token and client_credentials don't work
238
416
  try {
239
- this.logger?.debug(`Trying browser authentication flow for ${destination}`);
240
- const authConfigForBrowser = { ...authConfig, refreshToken: undefined };
241
- tokenResult = await this.tokenProvider.getConnectionConfig(authConfigForBrowser, {
242
- browser: this.browser,
243
- logger: this.logger,
244
- });
417
+ this.logger?.debug(`Step 2: Trying UAA (client_credentials) flow for ${destination}`);
418
+ let tokenResult;
419
+ // Try direct UAA request first if UAA credentials are available
420
+ if (uaaCredentials.uaaUrl && uaaCredentials.uaaClientId && uaaCredentials.uaaClientSecret) {
421
+ try {
422
+ this.logger?.debug(`Step 2: Trying direct UAA client_credentials for ${destination}`);
423
+ const uaaResult = await this.getTokenWithClientCredentials(uaaCredentials);
424
+ tokenResult = {
425
+ connectionConfig: {
426
+ authorizationToken: uaaResult.accessToken,
427
+ },
428
+ refreshToken: uaaResult.refreshToken,
429
+ };
430
+ this.logger?.debug(`Step 2: Direct UAA client_credentials succeeded for ${destination}`);
431
+ }
432
+ catch (directError) {
433
+ this.logger?.debug(`Step 2: Direct UAA client_credentials failed for ${destination}: ${directError.message}, trying provider`);
434
+ // If direct UAA failed and we have provider, try provider
435
+ if (this.tokenProvider) {
436
+ const authConfigWithoutRefresh = { ...uaaCredentials, refreshToken: undefined };
437
+ tokenResult = await this.tokenProvider.getConnectionConfig(authConfigWithoutRefresh, {
438
+ browser: this.browser,
439
+ logger: this.logger,
440
+ });
441
+ }
442
+ else {
443
+ throw directError; // No provider, re-throw direct error
444
+ }
445
+ }
446
+ }
447
+ else if (this.tokenProvider) {
448
+ // No UAA credentials but have provider
449
+ const authConfigWithoutRefresh = { ...uaaCredentials, refreshToken: undefined };
450
+ tokenResult = await this.tokenProvider.getConnectionConfig(authConfigWithoutRefresh, {
451
+ browser: this.browser,
452
+ logger: this.logger,
453
+ });
454
+ }
455
+ else {
456
+ throw new Error('UAA credentials incomplete and tokenProvider not available');
457
+ }
245
458
  const tokenLength = tokenResult.connectionConfig.authorizationToken?.length || 0;
246
- this.logger?.info(`Token obtained via browser for ${destination}: token(${tokenLength} chars), hasRefreshToken(${!!tokenResult.refreshToken})`);
247
- // Get serviceUrl from service key store if not in connectionConfig (required for ABAP stores)
248
- // For XSUAA service keys, serviceUrl may not exist, which is fine for BTP/XSUAA stores
249
- const serviceKeyConnConfig = await this.serviceKeyStore.getConnectionConfig(destination);
459
+ this.logger?.info(`Step 2: Token obtained via UAA for ${destination}: token(${tokenLength} chars), hasRefreshToken(${!!tokenResult.refreshToken})`);
460
+ // Get serviceUrl from session or service key (use the one we already have from the beginning of the method)
461
+ const finalServiceUrl = tokenResult.connectionConfig.serviceUrl ||
462
+ serviceUrl ||
463
+ (this.serviceKeyStore ? (await this.serviceKeyStore.getConnectionConfig(destination))?.serviceUrl : undefined);
250
464
  const connectionConfigWithServiceUrl = {
251
465
  ...tokenResult.connectionConfig,
252
- serviceUrl: tokenResult.connectionConfig.serviceUrl || serviceKeyConnConfig?.serviceUrl,
466
+ serviceUrl: finalServiceUrl,
253
467
  };
254
- // Update or create session with new token (stores handle creation if session doesn't exist)
468
+ // Update session with new token
255
469
  await this.sessionStore.setConnectionConfig(destination, connectionConfigWithServiceUrl);
256
470
  if (tokenResult.refreshToken) {
257
471
  await this.sessionStore.setAuthorizationConfig(destination, {
258
- ...authConfig,
472
+ ...uaaCredentials,
259
473
  refreshToken: tokenResult.refreshToken,
260
474
  });
261
475
  }
262
476
  return tokenResult.connectionConfig.authorizationToken;
263
477
  }
264
478
  catch (error) {
265
- lastError = error;
266
- // Step 6: All methods failed - throw error
479
+ this.logger?.error(`Step 2: UAA flow failed for ${destination}: ${error.message}`);
480
+ // If we have serviceKeyStore, we already tried it, so throw error
267
481
  const errorMessage = `All authentication methods failed for destination "${destination}". ` +
268
- `Refresh token: ${refreshToken ? 'failed' : 'not available'}. ` +
269
- `UAA: ${authConfig.uaaUrl && authConfig.uaaClientId && authConfig.uaaClientSecret ? 'failed' : 'parameters missing'}. ` +
270
- `Browser authentication: failed (${error.message})`;
271
- this.logger?.error(`All auth methods failed for ${destination}: refreshToken(${refreshToken ? 'failed' : 'none'}), UAA(${authConfig.uaaUrl && authConfig.uaaClientId && authConfig.uaaClientSecret ? 'failed' : 'missing'}), browser(failed: ${error.message})`);
482
+ `Step 1 (refresh token): ${refreshToken ? 'failed' : 'not available'}. ` +
483
+ `Step 2 (UAA credentials): failed (${error.message}).`;
484
+ this.logger?.error(errorMessage);
272
485
  throw new Error(errorMessage);
273
486
  }
274
487
  }
275
488
  /**
276
- * Force refresh token for destination using service key.
277
- * If no refresh token exists, starts browser authentication flow.
489
+ * Force refresh token for destination.
490
+ * Uses refresh token from session if available, otherwise uses UAA credentials from session or service key.
278
491
  * @param destination Destination name (e.g., "TRIAL")
279
492
  * @returns Promise that resolves to new JWT token string
280
493
  */
281
494
  async refreshToken(destination) {
282
495
  this.logger?.debug(`Force refreshing token for destination: ${destination}`);
283
- // Load service key
284
- const serviceKey = await this.serviceKeyStore.getServiceKey(destination);
285
- if (!serviceKey) {
286
- this.logger?.error(`Service key not found for ${destination}`);
287
- throw new Error(`Service key not found for destination "${destination}".`);
288
- }
289
- // Get authorization config from service key
290
- const authConfig = await this.serviceKeyStore.getAuthorizationConfig(destination);
496
+ // Get authorization config from session or service key
497
+ const sessionAuthConfig = await this.sessionStore.getAuthorizationConfig(destination);
498
+ const serviceKeyAuthConfig = this.serviceKeyStore
499
+ ? await this.serviceKeyStore.getAuthorizationConfig(destination)
500
+ : null;
501
+ const authConfig = sessionAuthConfig || serviceKeyAuthConfig;
291
502
  if (!authConfig) {
292
- this.logger?.error(`Service key for ${destination} missing UAA credentials`);
293
- throw new Error(`Service key for destination "${destination}" does not contain UAA credentials`);
503
+ this.logger?.error(`Authorization config not found for ${destination}`);
504
+ throw new Error(`Authorization config not found for destination "${destination}". ` +
505
+ `Session has no UAA credentials${this.serviceKeyStore ? ' and serviceKeyStore has no UAA credentials' : ' and serviceKeyStore is not available'}.`);
294
506
  }
295
- // Get refresh token from session
296
- const sessionAuthConfig = await this.sessionStore.getAuthorizationConfig(destination);
507
+ // Get refresh token from session or service key
297
508
  const refreshToken = sessionAuthConfig?.refreshToken || authConfig.refreshToken;
298
509
  this.logger?.debug(`Refresh token check for ${destination}: hasRefreshToken(${!!refreshToken})`);
299
- const authConfigWithRefresh = { ...authConfig, refreshToken };
300
- // Get connection config with token from provider
301
- const tokenResult = await this.tokenProvider.getConnectionConfig(authConfigWithRefresh, {
302
- browser: this.browser,
303
- logger: this.logger,
304
- });
510
+ let tokenResult;
511
+ // Try direct UAA request if UAA credentials are available
512
+ if (authConfig.uaaUrl && authConfig.uaaClientId && authConfig.uaaClientSecret && refreshToken) {
513
+ try {
514
+ this.logger?.debug(`Trying direct UAA refresh for ${destination}`);
515
+ const uaaResult = await this.refreshTokenDirect(refreshToken, authConfig);
516
+ tokenResult = {
517
+ connectionConfig: {
518
+ authorizationToken: uaaResult.accessToken,
519
+ },
520
+ refreshToken: uaaResult.refreshToken,
521
+ };
522
+ }
523
+ catch (directError) {
524
+ this.logger?.debug(`Direct UAA refresh failed for ${destination}: ${directError.message}, trying provider`);
525
+ // If direct UAA failed and we have provider, try provider
526
+ if (this.tokenProvider) {
527
+ const authConfigWithRefresh = { ...authConfig, refreshToken };
528
+ tokenResult = await this.tokenProvider.getConnectionConfig(authConfigWithRefresh, {
529
+ browser: this.browser,
530
+ logger: this.logger,
531
+ });
532
+ }
533
+ else {
534
+ throw directError; // No provider, re-throw direct error
535
+ }
536
+ }
537
+ }
538
+ else if (this.tokenProvider) {
539
+ // No UAA credentials or refresh token, but have provider
540
+ const authConfigWithRefresh = { ...authConfig, refreshToken };
541
+ tokenResult = await this.tokenProvider.getConnectionConfig(authConfigWithRefresh, {
542
+ browser: this.browser,
543
+ logger: this.logger,
544
+ });
545
+ }
546
+ else {
547
+ throw new Error('UAA credentials incomplete and tokenProvider not available');
548
+ }
305
549
  const tokenLength = tokenResult.connectionConfig.authorizationToken?.length || 0;
306
550
  this.logger?.info(`Token refreshed for ${destination}: token(${tokenLength} chars), hasRefreshToken(${!!tokenResult.refreshToken})`);
307
- // Get serviceUrl from service key store if not in connectionConfig (required for ABAP stores)
308
- // For XSUAA service keys, serviceUrl may not exist, which is fine for BTP/XSUAA stores
309
- const serviceKeyConnConfig = await this.serviceKeyStore.getConnectionConfig(destination);
551
+ // Get serviceUrl from session or service key
552
+ const connConfig = await this.sessionStore.getConnectionConfig(destination);
553
+ const serviceKeyConnConfig = this.serviceKeyStore
554
+ ? await this.serviceKeyStore.getConnectionConfig(destination)
555
+ : null;
310
556
  const connectionConfigWithServiceUrl = {
311
557
  ...tokenResult.connectionConfig,
312
- serviceUrl: tokenResult.connectionConfig.serviceUrl || serviceKeyConnConfig?.serviceUrl,
558
+ serviceUrl: tokenResult.connectionConfig.serviceUrl ||
559
+ connConfig?.serviceUrl ||
560
+ serviceKeyConnConfig?.serviceUrl,
313
561
  };
314
562
  // Update or create session with new token (stores handle creation if session doesn't exist)
315
563
  await this.sessionStore.setConnectionConfig(destination, connectionConfigWithServiceUrl);
@@ -335,16 +583,20 @@ class AuthBroker {
335
583
  this.logger?.debug(`Authorization config from session for ${destination}: hasUaaUrl(${!!sessionAuthConfig.uaaUrl}), hasRefreshToken(${!!sessionAuthConfig.refreshToken})`);
336
584
  return sessionAuthConfig;
337
585
  }
338
- // Fall back to service key store (has UAA credentials)
339
- this.logger?.debug(`Checking service key store for authorization config: ${destination}`);
340
- const serviceKeyAuthConfig = await this.serviceKeyStore.getAuthorizationConfig(destination);
341
- if (serviceKeyAuthConfig) {
342
- this.logger?.debug(`Authorization config from service key for ${destination}: hasUaaUrl(${!!serviceKeyAuthConfig.uaaUrl})`);
586
+ // Fall back to service key store (has UAA credentials) if available
587
+ if (this.serviceKeyStore) {
588
+ this.logger?.debug(`Checking service key store for authorization config: ${destination}`);
589
+ const serviceKeyAuthConfig = await this.serviceKeyStore.getAuthorizationConfig(destination);
590
+ if (serviceKeyAuthConfig) {
591
+ this.logger?.debug(`Authorization config from service key for ${destination}: hasUaaUrl(${!!serviceKeyAuthConfig.uaaUrl})`);
592
+ return serviceKeyAuthConfig;
593
+ }
343
594
  }
344
595
  else {
345
- this.logger?.debug(`No authorization config found for ${destination}`);
596
+ this.logger?.debug(`Service key store not available for ${destination}`);
346
597
  }
347
- return serviceKeyAuthConfig;
598
+ this.logger?.debug(`No authorization config found for ${destination}`);
599
+ return null;
348
600
  }
349
601
  /**
350
602
  * Get connection configuration for destination
@@ -359,15 +611,19 @@ class AuthBroker {
359
611
  this.logger?.debug(`Connection config from session for ${destination}: token(${sessionConnConfig.authorizationToken?.length || 0} chars), serviceUrl(${sessionConnConfig.serviceUrl ? 'yes' : 'no'})`);
360
612
  return sessionConnConfig;
361
613
  }
362
- // Fall back to service key store (has URLs but no tokens)
363
- const serviceKeyConnConfig = await this.serviceKeyStore.getConnectionConfig(destination);
364
- if (serviceKeyConnConfig) {
365
- this.logger?.debug(`Connection config from service key for ${destination}: serviceUrl(${serviceKeyConnConfig.serviceUrl ? 'yes' : 'no'}), token(none)`);
614
+ // Fall back to service key store (has URLs but no tokens) if available
615
+ if (this.serviceKeyStore) {
616
+ const serviceKeyConnConfig = await this.serviceKeyStore.getConnectionConfig(destination);
617
+ if (serviceKeyConnConfig) {
618
+ this.logger?.debug(`Connection config from service key for ${destination}: serviceUrl(${serviceKeyConnConfig.serviceUrl ? 'yes' : 'no'}), token(none)`);
619
+ return serviceKeyConnConfig;
620
+ }
366
621
  }
367
622
  else {
368
- this.logger?.debug(`No connection config found for ${destination}`);
623
+ this.logger?.debug(`Service key store not available for ${destination}`);
369
624
  }
370
- return serviceKeyConnConfig;
625
+ this.logger?.debug(`No connection config found for ${destination}`);
626
+ return null;
371
627
  }
372
628
  }
373
629
  exports.AuthBroker = AuthBroker;