@mcp-abap-adt/auth-broker 0.1.11 → 0.2.1

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,211 +25,542 @@ 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');
40
- }
41
- if (!stores.serviceKeyStore) {
42
- throw new Error('AuthBroker: serviceKeyStore 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');
43
41
  }
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');
46
+ // Validate that stores and provider are correctly instantiated (have required methods)
47
+ const sessionStore = config.sessionStore;
48
+ const tokenProvider = config.tokenProvider;
49
+ const serviceKeyStore = config.serviceKeyStore;
50
+ // Check sessionStore methods
51
+ if (typeof sessionStore.getAuthorizationConfig !== 'function') {
52
+ throw new Error('AuthBroker: sessionStore.getAuthorizationConfig must be a function');
53
+ }
54
+ if (typeof sessionStore.getConnectionConfig !== 'function') {
55
+ throw new Error('AuthBroker: sessionStore.getConnectionConfig must be a function');
56
+ }
57
+ if (typeof sessionStore.setAuthorizationConfig !== 'function') {
58
+ throw new Error('AuthBroker: sessionStore.setAuthorizationConfig must be a function');
59
+ }
60
+ if (typeof sessionStore.setConnectionConfig !== 'function') {
61
+ throw new Error('AuthBroker: sessionStore.setConnectionConfig must be a function');
62
+ }
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
+ }
49
81
  }
50
- this.serviceKeyStore = stores.serviceKeyStore;
51
- this.sessionStore = stores.sessionStore;
52
- this.tokenProvider = stores.tokenProvider;
82
+ this.serviceKeyStore = serviceKeyStore;
83
+ this.sessionStore = sessionStore;
84
+ this.tokenProvider = tokenProvider;
53
85
  this.browser = browser || 'system';
54
86
  this.logger = logger || noOpLogger;
87
+ // Log successful initialization
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
+ }
55
181
  }
56
182
  /**
57
183
  * Get authentication token for destination.
58
- * Tries to load from session store, validates it, and refreshes if needed using a fallback chain.
184
+ * Implements a three-step flow: Step 0 (initialize), Step 1 (refresh), Step 2 (UAA).
59
185
  *
60
- * **Fallback Chain:**
61
- * 1. **Check session**: Load token from session store and validate it
62
- * - If token is valid, return it immediately
63
- * - If token is invalid or missing, 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
64
193
  *
65
- * 2. **Check service key**: Verify that service key exists
66
- * - If no service key found, throw error
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
67
201
  *
68
- * 3. **Try refresh token**: If refresh token is available in session, attempt to refresh using it (via tokenProvider)
69
- * - If successful, save new token to session and return it
70
- * - 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
71
208
  *
72
- * 4. **Try UAA (client_credentials)**: Attempt to get token using UAA credentials (via tokenProvider)
73
- * - If UAA parameters are available and authentication succeeds, save token to session and return it
74
- * - If failed or parameters missing, continue to next step
75
- *
76
- * 5. **Try browser authentication**: Attempt browser-based OAuth2 flow using service key (via tokenProvider)
77
- * - If successful, save token and refresh token to session and return it
78
- * - If failed, continue to next step
79
- *
80
- * 6. **Throw error**: If all authentication methods failed, throw comprehensive error with details
81
- *
82
- * **Note**: Token validation is performed only when checking existing session (step 1).
83
- * 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
84
215
  *
85
216
  * @param destination Destination name (e.g., "TRIAL")
86
217
  * @returns Promise that resolves to JWT token string
87
- * @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
88
219
  */
89
220
  async getToken(destination) {
90
- // Step 1: Check if session exists and token is valid
221
+ this.logger?.debug(`Getting token for destination: ${destination}`);
222
+ // Step 0: Initialize Session with Token (if needed)
91
223
  const connConfig = await this.sessionStore.getConnectionConfig(destination);
92
- if (connConfig?.authorizationToken) {
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`);
93
312
  // Validate token if provider supports validation and we have service URL
94
- if (this.tokenProvider.validateToken && connConfig.serviceUrl) {
95
- 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);
96
315
  if (isValid) {
316
+ this.logger?.info(`Step 0: Token valid for ${destination}: token(${connConfig.authorizationToken.length} chars)`);
97
317
  return connConfig.authorizationToken;
98
318
  }
319
+ this.logger?.debug(`Step 0: Token invalid for ${destination}, continuing to refresh`);
99
320
  }
100
321
  else {
101
322
  // No service URL or provider doesn't support validation - just return token
323
+ this.logger?.info(`Step 0: Token found for ${destination} (no validation): token(${connConfig.authorizationToken.length} chars)`);
102
324
  return connConfig.authorizationToken;
103
325
  }
104
326
  }
105
- // Step 2: No valid session, check if we have service key
106
- const serviceKey = await this.serviceKeyStore.getServiceKey(destination);
107
- if (!serviceKey) {
108
- // No service key and no valid token
109
- throw new Error(`No authentication found for destination "${destination}". ` +
110
- `No session data and no service key found.`);
111
- }
112
- // Get authorization config from service key
113
- const authConfig = await this.serviceKeyStore.getAuthorizationConfig(destination);
114
- if (!authConfig) {
115
- throw new Error(`Service key for destination "${destination}" does not contain UAA credentials`);
116
- }
117
- // Get refresh token from session (if exists)
118
- const sessionAuthConfig = await this.sessionStore.getAuthorizationConfig(destination);
119
- const refreshToken = sessionAuthConfig?.refreshToken || authConfig.refreshToken;
120
- let tokenResult;
121
- let lastError = null;
122
- // 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;
123
330
  if (refreshToken) {
124
331
  try {
125
- this.logger.debug(`Attempting to refresh token using refresh token for destination "${destination}"...`);
126
- const authConfigWithRefresh = { ...authConfig, refreshToken };
127
- tokenResult = await this.tokenProvider.getConnectionConfig(authConfigWithRefresh, {
128
- browser: this.browser,
129
- logger: this.logger,
130
- });
131
- this.logger.debug(`Token refreshed successfully using refresh token for destination "${destination}"`);
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
+ }
378
+ const tokenLength = tokenResult.connectionConfig.authorizationToken?.length || 0;
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);
384
+ const connectionConfigWithServiceUrl = {
385
+ ...tokenResult.connectionConfig,
386
+ serviceUrl: finalServiceUrl,
387
+ };
132
388
  // Update session with new token
133
- await this.sessionStore.setConnectionConfig(destination, tokenResult.connectionConfig);
389
+ await this.sessionStore.setConnectionConfig(destination, connectionConfigWithServiceUrl);
134
390
  if (tokenResult.refreshToken) {
135
391
  await this.sessionStore.setAuthorizationConfig(destination, {
136
- ...authConfig,
392
+ ...uaaCredentials,
137
393
  refreshToken: tokenResult.refreshToken,
138
394
  });
139
395
  }
140
396
  return tokenResult.connectionConfig.authorizationToken;
141
397
  }
142
398
  catch (error) {
143
- lastError = error;
144
- this.logger.debug(`Token refresh failed for destination "${destination}": ${error.message}. Trying without refresh token...`);
145
- // 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
146
401
  }
147
402
  }
148
- // Step 4: Try UAA (client_credentials) via tokenProvider (without refresh token)
149
- // TokenProvider should try client_credentials if refresh token is not provided
150
- try {
151
- this.logger.debug(`Attempting to get token using UAA (client_credentials) for destination "${destination}"...`);
152
- const authConfigWithoutRefresh = { ...authConfig, refreshToken: undefined };
153
- tokenResult = await this.tokenProvider.getConnectionConfig(authConfigWithoutRefresh, {
154
- browser: this.browser,
155
- logger: this.logger,
156
- });
157
- this.logger.debug(`Token obtained successfully using UAA for destination "${destination}"`);
158
- // Update session with new token
159
- await this.sessionStore.setConnectionConfig(destination, tokenResult.connectionConfig);
160
- if (tokenResult.refreshToken) {
161
- await this.sessionStore.setAuthorizationConfig(destination, {
162
- ...authConfig,
163
- refreshToken: tokenResult.refreshToken,
164
- });
165
- }
166
- return tokenResult.connectionConfig.authorizationToken;
403
+ else {
404
+ this.logger?.debug(`Step 1: No refresh token found for ${destination}, proceeding to Step 2`);
167
405
  }
168
- catch (error) {
169
- lastError = error;
170
- this.logger.debug(`UAA authentication failed for destination "${destination}": ${error.message}. Trying browser authentication...`);
171
- // 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);
172
415
  }
173
- // Step 5: Try browser authentication via tokenProvider (should be last resort)
174
- // TokenProvider should use browser auth if refresh token and client_credentials don't work
175
416
  try {
176
- this.logger.debug(`Starting browser authentication flow for destination "${destination}"...`);
177
- const authConfigForBrowser = { ...authConfig, refreshToken: undefined };
178
- tokenResult = await this.tokenProvider.getConnectionConfig(authConfigForBrowser, {
179
- browser: this.browser,
180
- logger: this.logger,
181
- });
182
- this.logger.debug(`Token obtained successfully using browser authentication for destination "${destination}"`);
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
+ }
458
+ const tokenLength = tokenResult.connectionConfig.authorizationToken?.length || 0;
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);
464
+ const connectionConfigWithServiceUrl = {
465
+ ...tokenResult.connectionConfig,
466
+ serviceUrl: finalServiceUrl,
467
+ };
183
468
  // Update session with new token
184
- await this.sessionStore.setConnectionConfig(destination, tokenResult.connectionConfig);
469
+ await this.sessionStore.setConnectionConfig(destination, connectionConfigWithServiceUrl);
185
470
  if (tokenResult.refreshToken) {
186
471
  await this.sessionStore.setAuthorizationConfig(destination, {
187
- ...authConfig,
472
+ ...uaaCredentials,
188
473
  refreshToken: tokenResult.refreshToken,
189
474
  });
190
475
  }
191
476
  return tokenResult.connectionConfig.authorizationToken;
192
477
  }
193
478
  catch (error) {
194
- lastError = error;
195
- // 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
196
481
  const errorMessage = `All authentication methods failed for destination "${destination}". ` +
197
- `Refresh token: ${refreshToken ? 'failed' : 'not available'}. ` +
198
- `UAA: ${authConfig.uaaUrl && authConfig.uaaClientId && authConfig.uaaClientSecret ? 'failed' : 'parameters missing'}. ` +
199
- `Browser authentication: failed (${error.message})`;
200
- this.logger.error(errorMessage);
482
+ `Step 1 (refresh token): ${refreshToken ? 'failed' : 'not available'}. ` +
483
+ `Step 2 (UAA credentials): failed (${error.message}).`;
484
+ this.logger?.error(errorMessage);
201
485
  throw new Error(errorMessage);
202
486
  }
203
487
  }
204
488
  /**
205
- * Force refresh token for destination using service key.
206
- * 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.
207
491
  * @param destination Destination name (e.g., "TRIAL")
208
492
  * @returns Promise that resolves to new JWT token string
209
493
  */
210
494
  async refreshToken(destination) {
211
- // Load service key
212
- const serviceKey = await this.serviceKeyStore.getServiceKey(destination);
213
- if (!serviceKey) {
214
- throw new Error(`Service key not found for destination "${destination}".`);
215
- }
216
- // Get authorization config from service key
217
- const authConfig = await this.serviceKeyStore.getAuthorizationConfig(destination);
495
+ this.logger?.debug(`Force refreshing token for destination: ${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;
218
502
  if (!authConfig) {
219
- 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'}.`);
220
506
  }
221
- // Get refresh token from session
222
- const sessionAuthConfig = await this.sessionStore.getAuthorizationConfig(destination);
223
- const authConfigWithRefresh = { ...authConfig, refreshToken: sessionAuthConfig?.refreshToken || authConfig.refreshToken };
224
- // Get connection config with token from provider
225
- const tokenResult = await this.tokenProvider.getConnectionConfig(authConfigWithRefresh, {
226
- browser: this.browser,
227
- logger: this.logger,
228
- });
229
- // Update session with new token
230
- await this.sessionStore.setConnectionConfig(destination, tokenResult.connectionConfig);
231
- // Update authorization config with new refresh token if available
507
+ // Get refresh token from session or service key
508
+ const refreshToken = sessionAuthConfig?.refreshToken || authConfig.refreshToken;
509
+ this.logger?.debug(`Refresh token check for ${destination}: hasRefreshToken(${!!refreshToken})`);
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
+ }
549
+ const tokenLength = tokenResult.connectionConfig.authorizationToken?.length || 0;
550
+ this.logger?.info(`Token refreshed for ${destination}: token(${tokenLength} chars), hasRefreshToken(${!!tokenResult.refreshToken})`);
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;
556
+ const connectionConfigWithServiceUrl = {
557
+ ...tokenResult.connectionConfig,
558
+ serviceUrl: tokenResult.connectionConfig.serviceUrl ||
559
+ connConfig?.serviceUrl ||
560
+ serviceKeyConnConfig?.serviceUrl,
561
+ };
562
+ // Update or create session with new token (stores handle creation if session doesn't exist)
563
+ await this.sessionStore.setConnectionConfig(destination, connectionConfigWithServiceUrl);
232
564
  if (tokenResult.refreshToken) {
233
565
  await this.sessionStore.setAuthorizationConfig(destination, {
234
566
  ...authConfig,
@@ -243,13 +575,28 @@ class AuthBroker {
243
575
  * @returns Promise that resolves to IAuthorizationConfig or null if not found
244
576
  */
245
577
  async getAuthorizationConfig(destination) {
578
+ this.logger?.debug(`Getting authorization config for ${destination}`);
246
579
  // Try session store first (has tokens)
580
+ this.logger?.debug(`Checking session store for authorization config: ${destination}`);
247
581
  const sessionAuthConfig = await this.sessionStore.getAuthorizationConfig(destination);
248
582
  if (sessionAuthConfig) {
583
+ this.logger?.debug(`Authorization config from session for ${destination}: hasUaaUrl(${!!sessionAuthConfig.uaaUrl}), hasRefreshToken(${!!sessionAuthConfig.refreshToken})`);
249
584
  return sessionAuthConfig;
250
585
  }
251
- // Fall back to service key store (has UAA credentials)
252
- return await this.serviceKeyStore.getAuthorizationConfig(destination);
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
+ }
594
+ }
595
+ else {
596
+ this.logger?.debug(`Service key store not available for ${destination}`);
597
+ }
598
+ this.logger?.debug(`No authorization config found for ${destination}`);
599
+ return null;
253
600
  }
254
601
  /**
255
602
  * Get connection configuration for destination
@@ -257,13 +604,26 @@ class AuthBroker {
257
604
  * @returns Promise that resolves to IConnectionConfig or null if not found
258
605
  */
259
606
  async getConnectionConfig(destination) {
607
+ this.logger?.debug(`Getting connection config for ${destination}`);
260
608
  // Try session store first (has tokens and URLs)
261
609
  const sessionConnConfig = await this.sessionStore.getConnectionConfig(destination);
262
610
  if (sessionConnConfig) {
611
+ this.logger?.debug(`Connection config from session for ${destination}: token(${sessionConnConfig.authorizationToken?.length || 0} chars), serviceUrl(${sessionConnConfig.serviceUrl ? 'yes' : 'no'})`);
263
612
  return sessionConnConfig;
264
613
  }
265
- // Fall back to service key store (has URLs but no tokens)
266
- return await this.serviceKeyStore.getConnectionConfig(destination);
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
+ }
621
+ }
622
+ else {
623
+ this.logger?.debug(`Service key store not available for ${destination}`);
624
+ }
625
+ this.logger?.debug(`No connection config found for ${destination}`);
626
+ return null;
267
627
  }
268
628
  }
269
629
  exports.AuthBroker = AuthBroker;