@openeo/js-client 2.9.0 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -88,15 +88,23 @@ class AuthProvider {
88
88
  *
89
89
  * Returns `null` if no access token has been set yet (i.e. not authenticated any longer).
90
90
  *
91
+ * Checks whether the server supports the JWT conformance class.
92
+ *
91
93
  * @returns {string | null}
92
94
  */
93
95
  getToken() {
96
+ //check conformance
97
+ const isJWT = this.connection.capabilities().hasConformance(
98
+ "https://api.openeo.org/*/authentication/jwt"
99
+ );
94
100
  if (typeof this.token === 'string') {
95
- return this.getType() + "/" + this.getProviderId() + "/" + this.token;
96
- }
97
- else {
98
- return null;
101
+ if(isJWT) {
102
+ return this.token; // JWT since API v1.3.0
103
+ } else {
104
+ return this.getType() + "/" + this.getProviderId() + "/" + this.token; // legacy
105
+ }
99
106
  }
107
+ return null;
100
108
  }
101
109
 
102
110
  /**
@@ -119,6 +127,16 @@ class AuthProvider {
119
127
  }
120
128
  }
121
129
 
130
+ /**
131
+ * Tries to resume an existing session.
132
+ *
133
+ * @param {...any} args
134
+ * @returns {boolean} `true` if the session could be resumed, `false` otherwise
135
+ */
136
+ async resume(...args) { // eslint-disable-line no-unused-vars
137
+ return false;
138
+ }
139
+
122
140
  /**
123
141
  * Abstract method that extending classes implement the login process with.
124
142
  *
package/src/connection.js CHANGED
@@ -745,7 +745,7 @@ class Connection {
745
745
  const response = await this._post('/validation', this._normalizeUserProcess(process).process);
746
746
  if (Array.isArray(response.data.errors)) {
747
747
  const errors = response.data.errors;
748
- errors['federation:backends'] = Array.isArray(response.data['federation:missing']) ? response.data['federation:missing'] : [];
748
+ errors['federation:backends'] = Array.isArray(response.data['federation:backends']) ? response.data['federation:backends'] : [];
749
749
  return errors;
750
750
  }
751
751
  else {
@@ -53,6 +53,7 @@ class OidcProvider extends AuthProvider {
53
53
  }
54
54
  let providerOptions = provider.getOptions(options);
55
55
  let oidc = new Oidc.UserManager(providerOptions);
56
+ await oidc.clearStaleState();
56
57
  return await oidc.signinCallback(url);
57
58
  }
58
59
 
@@ -82,10 +83,19 @@ class OidcProvider extends AuthProvider {
82
83
  */
83
84
  this.clientId = null;
84
85
 
86
+ /**
87
+ * The client secret to use for authentication.
88
+ *
89
+ * Only used for the `client_credentials` grant type.
90
+ *
91
+ * @type {string | null}
92
+ */
93
+ this.clientSecret = null;
94
+
85
95
  /**
86
96
  * The grant type (flow) to use for this provider.
87
97
  *
88
- * Either "authorization_code+pkce" (default) or "implicit"
98
+ * Either "authorization_code+pkce" (default), "implicit" or "client_credentials"
89
99
  *
90
100
  * @type {string}
91
101
  */
@@ -127,12 +137,29 @@ class OidcProvider extends AuthProvider {
127
137
  */
128
138
  this.defaultClients = Array.isArray(options.default_clients) ? options.default_clients : [];
129
139
 
140
+ /**
141
+ * Additional parameters to include in authorization requests.
142
+ *
143
+ * As defined by the API, these parameters MUST be included when
144
+ * requesting the authorization endpoint.
145
+ *
146
+ * @type {object.<string, *>}
147
+ */
148
+ this.authorizationParameters = Utils.isObject(options.authorization_parameters) ? options.authorization_parameters : {};
149
+
130
150
  /**
131
151
  * The detected default Client.
132
152
  *
133
153
  * @type {OidcClient}
134
154
  */
135
155
  this.defaultClient = this.detectDefaultClient();
156
+
157
+ /**
158
+ * The cached OpenID Connect well-known configuration document.
159
+ *
160
+ * @type {object.<string, *> | null}
161
+ */
162
+ this.wellKnownDocument = null;
136
163
  }
137
164
 
138
165
  /**
@@ -166,7 +193,8 @@ class OidcProvider extends AuthProvider {
166
193
  /**
167
194
  * Authenticate with OpenID Connect (OIDC).
168
195
  *
169
- * Supported only in Browser environments.
196
+ * Supported in Browser environments for `authorization_code+pkce` and `implicit` grants.
197
+ * The `client_credentials` grant is supported in all environments.
170
198
  *
171
199
  * @async
172
200
  * @param {object.<string, *>} [options={}] - Object with authentication options.
@@ -181,6 +209,10 @@ class OidcProvider extends AuthProvider {
181
209
  throw new Error("No Issuer URL available for OpenID Connect");
182
210
  }
183
211
 
212
+ if (this.grant === 'client_credentials') {
213
+ return await this.loginClientCredentials();
214
+ }
215
+
184
216
  this.manager = new Oidc.UserManager(this.getOptions(options, requestRefreshToken));
185
217
  this.addListener('UserLoaded', async () => this.setUser(await this.manager.getUser()), 'js-client');
186
218
  this.addListener('AccessTokenExpired', () => this.setUser(null), 'js-client');
@@ -192,12 +224,143 @@ class OidcProvider extends AuthProvider {
192
224
  }
193
225
  }
194
226
 
227
+ /**
228
+ * Authenticate using the OIDC Client Credentials grant.
229
+ *
230
+ * Requires `clientId` and `clientSecret` to be set.
231
+ * This flow does not use the oidc-client library and works in all environments.
232
+ *
233
+ * @async
234
+ * @protected
235
+ * @returns {Promise<void>}
236
+ * @throws {Error}
237
+ */
238
+ async loginClientCredentials() {
239
+ if (!this.clientId || !this.clientSecret) {
240
+ throw new Error("Client ID and Client Secret are required for the client credentials flow");
241
+ }
242
+
243
+ let tokenEndpoint = await this.getTokenEndpoint();
244
+
245
+ let params = new URLSearchParams();
246
+ params.append('grant_type', 'client_credentials');
247
+ params.append('client_id', this.clientId);
248
+ params.append('client_secret', this.clientSecret);
249
+ params.append('scope', this.scopes.join(' '));
250
+
251
+ let response = await Environment.axios.post(tokenEndpoint, params.toString(), {
252
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
253
+ });
254
+
255
+ let data = response.data;
256
+ let user = new Oidc.User({
257
+ access_token: data.access_token,
258
+ token_type: data.token_type || 'Bearer',
259
+ scope: data.scope || this.scopes.join(' '),
260
+ expires_at: data.expires_in ? Math.floor(Date.now() / 1000) + data.expires_in : undefined,
261
+ profile: {}
262
+ });
263
+ this.setUser(user);
264
+ }
265
+
266
+ /**
267
+ * Retrieves the OpenID Connect well-known configuration document.
268
+ *
269
+ * @async
270
+ * @returns {Promise<object.<str, *>> | null} The well-known configuration document, or `null` if the issuer URL is not set.
271
+ */
272
+ async getWellKnownDocument() {
273
+ if (!this.issuer || typeof this.issuer !== 'string') {
274
+ return null;
275
+ }
276
+ if (this.wellKnownDocument === null) {
277
+ const authority = this.issuer.replace('/.well-known/openid-configuration', '');
278
+ const discoveryUrl = authority + '/.well-known/openid-configuration';
279
+ const response = await Environment.axios.get(discoveryUrl);
280
+ this.wellKnownDocument = response.data;
281
+ }
282
+ return this.wellKnownDocument;
283
+ }
284
+
285
+ /**
286
+ * Discovers the token endpoint from the OpenID Connect issuer.
287
+ *
288
+ * @async
289
+ * @protected
290
+ * @returns {Promise<string>} The token endpoint URL.
291
+ * @throws {Error}
292
+ */
293
+ async getTokenEndpoint() {
294
+ const wellKnown = await this.getWellKnownDocument();
295
+ if (!Utils.isObject(wellKnown) || !wellKnown.token_endpoint) {
296
+ throw new Error("Unable to discover token endpoint from issuer");
297
+ }
298
+ return wellKnown.token_endpoint;
299
+ }
300
+
301
+ /**
302
+ * Checks whether the OpenID Connect provider supports the Client Credentials grant.
303
+ *
304
+ * @async
305
+ * @returns {Promise<boolean|null>} `true` if the Client Credentials grant is supported, `false` otherwise. `null` if unknown.
306
+ */
307
+ async supportsClientCredentials() {
308
+ try {
309
+ const wellKnown = await this.getWellKnownDocument();
310
+ if (!Utils.isObject(wellKnown) || !Array.isArray(wellKnown.grant_types_supported)) {
311
+ return null;
312
+ }
313
+ return wellKnown.grant_types_supported.includes('client_credentials');
314
+ } catch (error) {
315
+ return null;
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Restores a previously established OIDC session from storage.
321
+ *
322
+ * Not supported for the `client_credentials` grant as credentials
323
+ * are not persisted. Use `login()` to re-authenticate instead.
324
+ *
325
+ * @async
326
+ * @param {object.<string, *>} [options={}] - Additional options passed to the OIDC UserManager.
327
+ * @returns {Promise<boolean>} `true` if the session could be resumed, `false` otherwise.
328
+ * @see https://github.com/IdentityModel/oidc-client-js/wiki#usermanager
329
+ */
330
+ async resume(options = {}) {
331
+ if (this.grant === 'client_credentials') {
332
+ return false;
333
+ }
334
+
335
+ this.manager = new Oidc.UserManager(this.getOptions(options));
336
+ this.addListener('UserLoaded', async () => this.setUser(await this.manager.getUser()), 'js-client');
337
+ this.addListener('AccessTokenExpired', () => this.setUser(null), 'js-client');
338
+
339
+ let user = await this.manager.getUser();
340
+ if (user && user.expired && user.refresh_token) {
341
+ user = await this.manager.signinSilent();
342
+ }
343
+
344
+ if (user && !user.expired) {
345
+ this.setUser(user);
346
+ return true;
347
+ }
348
+
349
+ return false;
350
+ }
351
+
195
352
  /**
196
353
  * Logout from the established session.
197
354
  *
198
355
  * @async
199
356
  */
200
357
  async logout() {
358
+ if (this.grant === 'client_credentials') {
359
+ super.logout();
360
+ this.setUser(null);
361
+ return;
362
+ }
363
+
201
364
  if (this.manager !== null) {
202
365
  try {
203
366
  if (OidcProvider.uiMethod === 'popup') {
@@ -244,7 +407,8 @@ class OidcProvider extends AuthProvider {
244
407
  scope: scope.join(' '),
245
408
  validateSubOnSilentRenew: true,
246
409
  response_type,
247
- response_mode: response_type.includes('code') ? 'query' : 'fragment'
410
+ response_mode: response_type.includes('code') ? 'query' : 'fragment',
411
+ extraQueryParams: this.authorizationParameters
248
412
  }, options);
249
413
  }
250
414
 
@@ -261,6 +425,8 @@ class OidcProvider extends AuthProvider {
261
425
  return 'code';
262
426
  case 'implicit':
263
427
  return 'token id_token';
428
+ case 'client_credentials':
429
+ return null;
264
430
  default:
265
431
  throw new Error('Grant Type not supported');
266
432
  }
@@ -276,6 +442,7 @@ class OidcProvider extends AuthProvider {
276
442
  switch(grant) {
277
443
  case 'authorization_code+pkce':
278
444
  case 'implicit':
445
+ case 'client_credentials':
279
446
  this.grant = grant;
280
447
  break;
281
448
  default:
@@ -294,6 +461,17 @@ class OidcProvider extends AuthProvider {
294
461
  this.clientId = clientId;
295
462
  }
296
463
 
464
+ /**
465
+ * Sets the Client Secret for OIDC authentication.
466
+ *
467
+ * Only used for the `client_credentials` grant type.
468
+ *
469
+ * @param {string | null} clientSecret
470
+ */
471
+ setClientSecret(clientSecret) {
472
+ this.clientSecret = clientSecret;
473
+ }
474
+
297
475
  /**
298
476
  * Sets the OIDC User.
299
477
  *
@@ -314,12 +492,21 @@ class OidcProvider extends AuthProvider {
314
492
  /**
315
493
  * Returns a display name for the authenticated user.
316
494
  *
495
+ * For the `client_credentials` grant, returns a name based on the client ID.
496
+ *
317
497
  * @returns {string?} Name of the user or `null`
318
498
  */
319
499
  getDisplayName() {
320
500
  if (this.user && Utils.isObject(this.user.profile)) {
321
501
  return this.user.profile.name || this.user.profile.preferred_username || this.user.profile.email || null;
322
502
  }
503
+ if (this.grant === 'client_credentials' && this.clientId) {
504
+ let id = this.clientId;
505
+ if (id.length > 15) {
506
+ id = id.slice(0, 5) + '\u2026' + id.slice(-5);
507
+ }
508
+ return `Client ${id}`;
509
+ }
323
510
  return null;
324
511
  }
325
512
 
@@ -381,7 +568,8 @@ OidcProvider.redirectUrl = Environment.getUrl().split('#')[0].split('?')[0].repl
381
568
  */
382
569
  OidcProvider.grants = [
383
570
  'authorization_code+pkce',
384
- 'implicit'
571
+ 'implicit',
572
+ 'client_credentials'
385
573
  ];
386
574
 
387
575
  module.exports = OidcProvider;
package/src/openeo.js CHANGED
@@ -109,7 +109,7 @@ class OpenEO {
109
109
  * @returns {string} Version number (according to SemVer).
110
110
  */
111
111
  static clientVersion() {
112
- return "2.9.0";
112
+ return "2.11.0";
113
113
  }
114
114
 
115
115
  }