@logto/client 2.2.4 → 2.3.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.
package/lib/errors.cjs CHANGED
@@ -1,11 +1,14 @@
1
1
  'use strict';
2
2
 
3
+ var js = require('@logto/js');
4
+
3
5
  const logtoClientErrorCodes = Object.freeze({
4
6
  'sign_in_session.invalid': 'Invalid sign-in session.',
5
7
  'sign_in_session.not_found': 'Sign-in session not found.',
6
8
  not_authenticated: 'Not authenticated.',
7
9
  fetch_user_info_failed: 'Unable to fetch user info. The access token may be invalid.',
8
10
  user_cancelled: 'The user cancelled the action.',
11
+ missing_scope_organizations: `The \`${js.UserScope.Organizations}\` scope is required`,
9
12
  });
10
13
  class LogtoClientError extends Error {
11
14
  constructor(code, data) {
package/lib/errors.d.ts CHANGED
@@ -4,6 +4,7 @@ declare const logtoClientErrorCodes: Readonly<{
4
4
  not_authenticated: "Not authenticated.";
5
5
  fetch_user_info_failed: "Unable to fetch user info. The access token may be invalid.";
6
6
  user_cancelled: "The user cancelled the action.";
7
+ missing_scope_organizations: "The `urn:logto:scope:organizations` scope is required";
7
8
  }>;
8
9
  export type LogtoClientErrorCode = keyof typeof logtoClientErrorCodes;
9
10
  export declare class LogtoClientError extends Error {
package/lib/errors.js CHANGED
@@ -1,9 +1,12 @@
1
+ import { UserScope } from '@logto/js';
2
+
1
3
  const logtoClientErrorCodes = Object.freeze({
2
4
  'sign_in_session.invalid': 'Invalid sign-in session.',
3
5
  'sign_in_session.not_found': 'Sign-in session not found.',
4
6
  not_authenticated: 'Not authenticated.',
5
7
  fetch_user_info_failed: 'Unable to fetch user info. The access token may be invalid.',
6
8
  user_cancelled: 'The user cancelled the action.',
9
+ missing_scope_organizations: `The \`${UserScope.Organizations}\` scope is required`,
7
10
  });
8
11
  class LogtoClientError extends Error {
9
12
  constructor(code, data) {
package/lib/index.cjs CHANGED
@@ -14,6 +14,7 @@ var once = require('./utils/once.cjs');
14
14
  var types = require('./adapter/types.cjs');
15
15
  var requester = require('./utils/requester.cjs');
16
16
 
17
+ /* eslint-disable max-lines */
17
18
  /**
18
19
  * The Logto base client class that provides the essential methods for
19
20
  * interacting with the Logto server.
@@ -25,7 +26,7 @@ class LogtoClient {
25
26
  constructor(logtoConfig, adapter) {
26
27
  this.getOidcConfig = memoize.memoize(this.#getOidcConfig);
27
28
  /**
28
- * Get the access token from the storage.
29
+ * Get the access token from the storage with refresh strategy.
29
30
  *
30
31
  * - If the access token has expired, it will try to fetch a new one using the Refresh Token.
31
32
  * - If there's an ongoing Promise to fetch the access token, it will return the Promise.
@@ -39,13 +40,22 @@ class LogtoClient {
39
40
  * @throws LogtoClientError if the user is not authenticated.
40
41
  */
41
42
  this.getAccessToken = memoize.memoize(this.#getAccessToken);
43
+ /**
44
+ * Get the access token for the specified organization from the storage with refresh strategy.
45
+ *
46
+ * Scope {@link UserScope.Organizations} is required in the config to use organization-related
47
+ * methods.
48
+ *
49
+ * @param organizationId The ID of the organization that the access token is granted for.
50
+ * @returns The access token string.
51
+ * @throws LogtoClientError if the user is not authenticated.
52
+ * @remarks
53
+ * It uses the same refresh strategy as {@link getAccessToken}.
54
+ */
55
+ this.getOrganizationToken = memoize.memoize(this.#getOrganizationToken);
42
56
  this.getJwtVerifyGetKey = once.once(this.#getJwtVerifyGetKey);
43
57
  this.accessTokenMap = new Map();
44
- this.logtoConfig = {
45
- ...logtoConfig,
46
- prompt: logtoConfig.prompt ?? js.Prompt.Consent,
47
- scopes: js.withDefaultScopes(logtoConfig.scopes).split(' '),
48
- };
58
+ this.logtoConfig = index.normalizeLogtoConfig(logtoConfig);
49
59
  this.adapter = new index$1.ClientAdapterInstance(adapter);
50
60
  void this.loadAccessTokenMap();
51
61
  }
@@ -89,6 +99,15 @@ class LogtoClient {
89
99
  const accessToken = await this.getAccessToken(resource);
90
100
  return js.decodeAccessToken(accessToken);
91
101
  }
102
+ /**
103
+ * Get the organization token claims for the specified organization.
104
+ *
105
+ * @param organizationId The ID of the organization that the access token is granted for.
106
+ */
107
+ async getOrganizationTokenClaims(organizationId) {
108
+ const accessToken = await this.getOrganizationToken(organizationId);
109
+ return js.decodeAccessToken(accessToken);
110
+ }
92
111
  /**
93
112
  * Get the user information from the Userinfo Endpoint.
94
113
  *
@@ -263,12 +282,12 @@ class LogtoClient {
263
282
  async setRefreshToken(value) {
264
283
  return this.adapter.setStorageItem(types.PersistKey.RefreshToken, value);
265
284
  }
266
- async getAccessTokenByRefreshToken(resource) {
285
+ async getAccessTokenByRefreshToken(resource, organizationId) {
267
286
  const currentRefreshToken = await this.getRefreshToken();
268
287
  if (!currentRefreshToken) {
269
288
  throw new errors.LogtoClientError('not_authenticated');
270
289
  }
271
- const accessTokenKey = index$2.buildAccessTokenKey(resource);
290
+ const accessTokenKey = index$2.buildAccessTokenKey(resource, organizationId);
272
291
  const { appId: clientId } = this.logtoConfig;
273
292
  const { tokenEndpoint } = await this.getOidcConfig();
274
293
  const requestedAt = Math.round(Date.now() / 1000);
@@ -277,6 +296,7 @@ class LogtoClient {
277
296
  tokenEndpoint,
278
297
  refreshToken: currentRefreshToken,
279
298
  resource,
299
+ organizationId,
280
300
  }, this.adapter.requester);
281
301
  this.accessTokenMap.set(accessTokenKey, {
282
302
  token: accessToken,
@@ -343,11 +363,11 @@ class LogtoClient {
343
363
  const cachedJwkSet = new remoteJwkSet.CachedRemoteJwkSet(new URL(jwksUri), this.adapter);
344
364
  return async (...args) => cachedJwkSet.getKey(...args);
345
365
  }
346
- async #getAccessToken(resource) {
347
- if (!(await this.getIdToken())) {
366
+ async #getAccessToken(resource, organizationId) {
367
+ if (!(await this.isAuthenticated())) {
348
368
  throw new errors.LogtoClientError('not_authenticated');
349
369
  }
350
- const accessTokenKey = index$2.buildAccessTokenKey(resource);
370
+ const accessTokenKey = index$2.buildAccessTokenKey(resource, organizationId);
351
371
  const accessToken = this.accessTokenMap.get(accessTokenKey);
352
372
  if (accessToken && accessToken.expiresAt > Date.now() / 1000) {
353
373
  return accessToken.token;
@@ -359,9 +379,16 @@ class LogtoClient {
359
379
  /**
360
380
  * Need to fetch a new access token using refresh token.
361
381
  */
362
- return this.getAccessTokenByRefreshToken(resource);
382
+ return this.getAccessTokenByRefreshToken(resource, organizationId);
383
+ }
384
+ async #getOrganizationToken(organizationId) {
385
+ if (!this.logtoConfig.scopes?.includes(js.UserScope.Organizations)) {
386
+ throw new errors.LogtoClientError('missing_scope_organizations');
387
+ }
388
+ return this.#getAccessToken(undefined, organizationId);
363
389
  }
364
390
  }
391
+ /* eslint-enable max-lines */
365
392
 
366
393
  Object.defineProperty(exports, 'LogtoError', {
367
394
  enumerable: true,
@@ -379,6 +406,10 @@ Object.defineProperty(exports, 'Prompt', {
379
406
  enumerable: true,
380
407
  get: function () { return js.Prompt; }
381
408
  });
409
+ Object.defineProperty(exports, 'ReservedResource', {
410
+ enumerable: true,
411
+ get: function () { return js.ReservedResource; }
412
+ });
382
413
  Object.defineProperty(exports, 'ReservedScope', {
383
414
  enumerable: true,
384
415
  get: function () { return js.ReservedScope; }
@@ -387,9 +418,22 @@ Object.defineProperty(exports, 'UserScope', {
387
418
  enumerable: true,
388
419
  get: function () { return js.UserScope; }
389
420
  });
421
+ Object.defineProperty(exports, 'buildOrganizationUrn', {
422
+ enumerable: true,
423
+ get: function () { return js.buildOrganizationUrn; }
424
+ });
425
+ Object.defineProperty(exports, 'getOrganizationIdFromUrn', {
426
+ enumerable: true,
427
+ get: function () { return js.getOrganizationIdFromUrn; }
428
+ });
429
+ Object.defineProperty(exports, 'organizationUrnPrefix', {
430
+ enumerable: true,
431
+ get: function () { return js.organizationUrnPrefix; }
432
+ });
390
433
  exports.LogtoClientError = errors.LogtoClientError;
391
434
  exports.isLogtoAccessTokenMap = index.isLogtoAccessTokenMap;
392
435
  exports.isLogtoSignInSessionItem = index.isLogtoSignInSessionItem;
436
+ exports.normalizeLogtoConfig = index.normalizeLogtoConfig;
393
437
  Object.defineProperty(exports, 'CacheKey', {
394
438
  enumerable: true,
395
439
  get: function () { return types.CacheKey; }
package/lib/index.d.ts CHANGED
@@ -4,7 +4,7 @@ import { type JWTVerifyGetKey } from 'jose';
4
4
  import { ClientAdapterInstance, type ClientAdapter } from './adapter/index.js';
5
5
  import type { AccessToken, LogtoConfig, LogtoSignInSessionItem } from './types/index.js';
6
6
  export type { IdTokenClaims, LogtoErrorCode, UserInfoResponse, InteractionMode } from '@logto/js';
7
- export { LogtoError, LogtoRequestError, OidcError, Prompt, ReservedScope, UserScope, } from '@logto/js';
7
+ export { LogtoError, LogtoRequestError, OidcError, Prompt, ReservedScope, ReservedResource, UserScope, organizationUrnPrefix, buildOrganizationUrn, getOrganizationIdFromUrn, } from '@logto/js';
8
8
  export * from './errors.js';
9
9
  export type { Storage, StorageKey, ClientAdapter } from './adapter/index.js';
10
10
  export { PersistKey, CacheKey } from './adapter/index.js';
@@ -30,7 +30,7 @@ export default class LogtoClient {
30
30
  issuer: string;
31
31
  }>>;
32
32
  /**
33
- * Get the access token from the storage.
33
+ * Get the access token from the storage with refresh strategy.
34
34
  *
35
35
  * - If the access token has expired, it will try to fetch a new one using the Refresh Token.
36
36
  * - If there's an ongoing Promise to fetch the access token, it will return the Promise.
@@ -43,7 +43,20 @@ export default class LogtoClient {
43
43
  * @returns The access token string.
44
44
  * @throws LogtoClientError if the user is not authenticated.
45
45
  */
46
- readonly getAccessToken: (this: unknown, resource?: string | undefined) => Promise<string>;
46
+ readonly getAccessToken: (this: unknown, resource?: string | undefined, organizationId?: string | undefined) => Promise<string>;
47
+ /**
48
+ * Get the access token for the specified organization from the storage with refresh strategy.
49
+ *
50
+ * Scope {@link UserScope.Organizations} is required in the config to use organization-related
51
+ * methods.
52
+ *
53
+ * @param organizationId The ID of the organization that the access token is granted for.
54
+ * @returns The access token string.
55
+ * @throws LogtoClientError if the user is not authenticated.
56
+ * @remarks
57
+ * It uses the same refresh strategy as {@link getAccessToken}.
58
+ */
59
+ readonly getOrganizationToken: (this: unknown, organizationId: string) => Promise<string>;
47
60
  protected readonly getJwtVerifyGetKey: (...args: unknown[]) => Promise<JWTVerifyGetKey>;
48
61
  protected readonly adapter: ClientAdapterInstance;
49
62
  protected readonly accessTokenMap: Map<string, AccessToken>;
@@ -73,6 +86,12 @@ export default class LogtoClient {
73
86
  * resource, as specified in the Logto Console.
74
87
  */
75
88
  getAccessTokenClaims(resource?: string): Promise<AccessTokenClaims>;
89
+ /**
90
+ * Get the organization token claims for the specified organization.
91
+ *
92
+ * @param organizationId The ID of the organization that the access token is granted for.
93
+ */
94
+ getOrganizationTokenClaims(organizationId: string): Promise<AccessTokenClaims>;
76
95
  /**
77
96
  * Get the user information from the Userinfo Endpoint.
78
97
  *
package/lib/index.js CHANGED
@@ -1,16 +1,17 @@
1
- import { Prompt, withDefaultScopes, decodeIdToken, decodeAccessToken, fetchUserInfo, generateSignInUri, verifyAndParseCodeFromCallbackUri, fetchTokenByAuthorizationCode, revoke, generateSignOutUri, fetchTokenByRefreshToken, verifyIdToken, fetchOidcConfig } from '@logto/js';
2
- export { LogtoError, LogtoRequestError, OidcError, Prompt, ReservedScope, UserScope } from '@logto/js';
1
+ import { decodeIdToken, decodeAccessToken, fetchUserInfo, generateSignInUri, verifyAndParseCodeFromCallbackUri, fetchTokenByAuthorizationCode, revoke, generateSignOutUri, fetchTokenByRefreshToken, verifyIdToken, fetchOidcConfig, UserScope } from '@logto/js';
2
+ export { LogtoError, LogtoRequestError, OidcError, Prompt, ReservedResource, ReservedScope, UserScope, buildOrganizationUrn, getOrganizationIdFromUrn, organizationUrnPrefix } from '@logto/js';
3
3
  import { createRemoteJWKSet } from 'jose';
4
4
  import { ClientAdapterInstance } from './adapter/index.js';
5
5
  import { LogtoClientError } from './errors.js';
6
6
  import { CachedRemoteJwkSet } from './remote-jwk-set.js';
7
- import { isLogtoSignInSessionItem, isLogtoAccessTokenMap } from './types/index.js';
7
+ import { normalizeLogtoConfig, isLogtoSignInSessionItem, isLogtoAccessTokenMap } from './types/index.js';
8
8
  import { buildAccessTokenKey, getDiscoveryEndpoint } from './utils/index.js';
9
9
  import { memoize } from './utils/memoize.js';
10
10
  import { once } from './utils/once.js';
11
11
  import { PersistKey, CacheKey } from './adapter/types.js';
12
12
  export { createRequester } from './utils/requester.js';
13
13
 
14
+ /* eslint-disable max-lines */
14
15
  /**
15
16
  * The Logto base client class that provides the essential methods for
16
17
  * interacting with the Logto server.
@@ -22,7 +23,7 @@ class LogtoClient {
22
23
  constructor(logtoConfig, adapter) {
23
24
  this.getOidcConfig = memoize(this.#getOidcConfig);
24
25
  /**
25
- * Get the access token from the storage.
26
+ * Get the access token from the storage with refresh strategy.
26
27
  *
27
28
  * - If the access token has expired, it will try to fetch a new one using the Refresh Token.
28
29
  * - If there's an ongoing Promise to fetch the access token, it will return the Promise.
@@ -36,13 +37,22 @@ class LogtoClient {
36
37
  * @throws LogtoClientError if the user is not authenticated.
37
38
  */
38
39
  this.getAccessToken = memoize(this.#getAccessToken);
40
+ /**
41
+ * Get the access token for the specified organization from the storage with refresh strategy.
42
+ *
43
+ * Scope {@link UserScope.Organizations} is required in the config to use organization-related
44
+ * methods.
45
+ *
46
+ * @param organizationId The ID of the organization that the access token is granted for.
47
+ * @returns The access token string.
48
+ * @throws LogtoClientError if the user is not authenticated.
49
+ * @remarks
50
+ * It uses the same refresh strategy as {@link getAccessToken}.
51
+ */
52
+ this.getOrganizationToken = memoize(this.#getOrganizationToken);
39
53
  this.getJwtVerifyGetKey = once(this.#getJwtVerifyGetKey);
40
54
  this.accessTokenMap = new Map();
41
- this.logtoConfig = {
42
- ...logtoConfig,
43
- prompt: logtoConfig.prompt ?? Prompt.Consent,
44
- scopes: withDefaultScopes(logtoConfig.scopes).split(' '),
45
- };
55
+ this.logtoConfig = normalizeLogtoConfig(logtoConfig);
46
56
  this.adapter = new ClientAdapterInstance(adapter);
47
57
  void this.loadAccessTokenMap();
48
58
  }
@@ -86,6 +96,15 @@ class LogtoClient {
86
96
  const accessToken = await this.getAccessToken(resource);
87
97
  return decodeAccessToken(accessToken);
88
98
  }
99
+ /**
100
+ * Get the organization token claims for the specified organization.
101
+ *
102
+ * @param organizationId The ID of the organization that the access token is granted for.
103
+ */
104
+ async getOrganizationTokenClaims(organizationId) {
105
+ const accessToken = await this.getOrganizationToken(organizationId);
106
+ return decodeAccessToken(accessToken);
107
+ }
89
108
  /**
90
109
  * Get the user information from the Userinfo Endpoint.
91
110
  *
@@ -260,12 +279,12 @@ class LogtoClient {
260
279
  async setRefreshToken(value) {
261
280
  return this.adapter.setStorageItem(PersistKey.RefreshToken, value);
262
281
  }
263
- async getAccessTokenByRefreshToken(resource) {
282
+ async getAccessTokenByRefreshToken(resource, organizationId) {
264
283
  const currentRefreshToken = await this.getRefreshToken();
265
284
  if (!currentRefreshToken) {
266
285
  throw new LogtoClientError('not_authenticated');
267
286
  }
268
- const accessTokenKey = buildAccessTokenKey(resource);
287
+ const accessTokenKey = buildAccessTokenKey(resource, organizationId);
269
288
  const { appId: clientId } = this.logtoConfig;
270
289
  const { tokenEndpoint } = await this.getOidcConfig();
271
290
  const requestedAt = Math.round(Date.now() / 1000);
@@ -274,6 +293,7 @@ class LogtoClient {
274
293
  tokenEndpoint,
275
294
  refreshToken: currentRefreshToken,
276
295
  resource,
296
+ organizationId,
277
297
  }, this.adapter.requester);
278
298
  this.accessTokenMap.set(accessTokenKey, {
279
299
  token: accessToken,
@@ -340,11 +360,11 @@ class LogtoClient {
340
360
  const cachedJwkSet = new CachedRemoteJwkSet(new URL(jwksUri), this.adapter);
341
361
  return async (...args) => cachedJwkSet.getKey(...args);
342
362
  }
343
- async #getAccessToken(resource) {
344
- if (!(await this.getIdToken())) {
363
+ async #getAccessToken(resource, organizationId) {
364
+ if (!(await this.isAuthenticated())) {
345
365
  throw new LogtoClientError('not_authenticated');
346
366
  }
347
- const accessTokenKey = buildAccessTokenKey(resource);
367
+ const accessTokenKey = buildAccessTokenKey(resource, organizationId);
348
368
  const accessToken = this.accessTokenMap.get(accessTokenKey);
349
369
  if (accessToken && accessToken.expiresAt > Date.now() / 1000) {
350
370
  return accessToken.token;
@@ -356,8 +376,15 @@ class LogtoClient {
356
376
  /**
357
377
  * Need to fetch a new access token using refresh token.
358
378
  */
359
- return this.getAccessTokenByRefreshToken(resource);
379
+ return this.getAccessTokenByRefreshToken(resource, organizationId);
380
+ }
381
+ async #getOrganizationToken(organizationId) {
382
+ if (!this.logtoConfig.scopes?.includes(UserScope.Organizations)) {
383
+ throw new LogtoClientError('missing_scope_organizations');
384
+ }
385
+ return this.#getAccessToken(undefined, organizationId);
360
386
  }
361
387
  }
388
+ /* eslint-enable max-lines */
362
389
 
363
- export { CacheKey, LogtoClientError, PersistKey, LogtoClient as default, isLogtoAccessTokenMap, isLogtoSignInSessionItem };
390
+ export { CacheKey, LogtoClientError, PersistKey, LogtoClient as default, isLogtoAccessTokenMap, isLogtoSignInSessionItem, normalizeLogtoConfig };
package/lib/mock.d.ts CHANGED
@@ -66,7 +66,7 @@ export declare const createAdapters: (withCache?: boolean) => {
66
66
  generateCodeVerifier: jest.Mock<string, [], any>;
67
67
  generateState: jest.Mock<string, [], any>;
68
68
  };
69
- export declare const createClient: (prompt?: Prompt, storage?: MockedStorage, withCache?: boolean) => LogtoClientWithAccessors;
69
+ export declare const createClient: (prompt?: Prompt, storage?: MockedStorage, withCache?: boolean, scopes?: string[]) => LogtoClientWithAccessors;
70
70
  /**
71
71
  * Make protected fields accessible for test
72
72
  */
@@ -1,7 +1,28 @@
1
1
  'use strict';
2
2
 
3
3
  var js = require('@logto/js');
4
+ var essentials = require('@silverhand/essentials');
4
5
 
6
+ /**
7
+ * Normalize the Logto client configuration per the following rules:
8
+ *
9
+ * - Add default scopes (`openid`, `offline_access` and `profile`) if not provided.
10
+ * - Add {@link ReservedResource.Organization} to resources if {@link UserScope.Organizations} is included in scopes.
11
+ *
12
+ * @param config The Logto client configuration to be normalized.
13
+ * @returns The normalized Logto client configuration.
14
+ */
15
+ const normalizeLogtoConfig = (config) => {
16
+ const { prompt = js.Prompt.Consent, scopes = [], resources, ...rest } = config;
17
+ return {
18
+ ...rest,
19
+ prompt,
20
+ scopes: js.withDefaultScopes(scopes).split(' '),
21
+ resources: scopes.includes(js.UserScope.Organizations)
22
+ ? essentials.deduplicate([...(resources ?? []), js.ReservedResource.Organization])
23
+ : resources,
24
+ };
25
+ };
5
26
  const isLogtoSignInSessionItem = (data) => {
6
27
  if (!js.isArbitraryObject(data)) {
7
28
  return false;
@@ -24,3 +45,4 @@ const isLogtoAccessTokenMap = (data) => {
24
45
 
25
46
  exports.isLogtoAccessTokenMap = isLogtoAccessTokenMap;
26
47
  exports.isLogtoSignInSessionItem = isLogtoSignInSessionItem;
48
+ exports.normalizeLogtoConfig = normalizeLogtoConfig;
@@ -1,4 +1,4 @@
1
- import type { Prompt } from '@logto/js';
1
+ import { Prompt } from '@logto/js';
2
2
  /** The configuration object for the Logto client. */
3
3
  export type LogtoConfig = {
4
4
  /**
@@ -40,6 +40,16 @@ export type LogtoConfig = {
40
40
  */
41
41
  prompt?: Prompt;
42
42
  };
43
+ /**
44
+ * Normalize the Logto client configuration per the following rules:
45
+ *
46
+ * - Add default scopes (`openid`, `offline_access` and `profile`) if not provided.
47
+ * - Add {@link ReservedResource.Organization} to resources if {@link UserScope.Organizations} is included in scopes.
48
+ *
49
+ * @param config The Logto client configuration to be normalized.
50
+ * @returns The normalized Logto client configuration.
51
+ */
52
+ export declare const normalizeLogtoConfig: (config: LogtoConfig) => LogtoConfig;
43
53
  export type AccessToken = {
44
54
  /** The access token string. */
45
55
  token: string;
@@ -1,5 +1,26 @@
1
- import { isArbitraryObject } from '@logto/js';
1
+ import { Prompt, withDefaultScopes, UserScope, ReservedResource, isArbitraryObject } from '@logto/js';
2
+ import { deduplicate } from '@silverhand/essentials';
2
3
 
4
+ /**
5
+ * Normalize the Logto client configuration per the following rules:
6
+ *
7
+ * - Add default scopes (`openid`, `offline_access` and `profile`) if not provided.
8
+ * - Add {@link ReservedResource.Organization} to resources if {@link UserScope.Organizations} is included in scopes.
9
+ *
10
+ * @param config The Logto client configuration to be normalized.
11
+ * @returns The normalized Logto client configuration.
12
+ */
13
+ const normalizeLogtoConfig = (config) => {
14
+ const { prompt = Prompt.Consent, scopes = [], resources, ...rest } = config;
15
+ return {
16
+ ...rest,
17
+ prompt,
18
+ scopes: withDefaultScopes(scopes).split(' '),
19
+ resources: scopes.includes(UserScope.Organizations)
20
+ ? deduplicate([...(resources ?? []), ReservedResource.Organization])
21
+ : resources,
22
+ };
23
+ };
3
24
  const isLogtoSignInSessionItem = (data) => {
4
25
  if (!isArbitraryObject(data)) {
5
26
  return false;
@@ -20,4 +41,4 @@ const isLogtoAccessTokenMap = (data) => {
20
41
  });
21
42
  };
22
43
 
23
- export { isLogtoAccessTokenMap, isLogtoSignInSessionItem };
44
+ export { isLogtoAccessTokenMap, isLogtoSignInSessionItem, normalizeLogtoConfig };
@@ -0,0 +1 @@
1
+ export {};
@@ -1,8 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  var js = require('@logto/js');
4
+ var essentials = require('@silverhand/essentials');
4
5
 
5
- const buildAccessTokenKey = (resource = '', scopes = []) => `${scopes.slice().sort().join(' ')}@${resource}`;
6
+ const buildAccessTokenKey = (resource = '', organizationId, scopes = []) => `${scopes.slice().sort().join(' ')}@${resource}${essentials.conditionalString(organizationId && `#${organizationId}`)}`;
6
7
  const getDiscoveryEndpoint = (endpoint) => new URL(js.discoveryPath, endpoint).toString();
7
8
 
8
9
  exports.buildAccessTokenKey = buildAccessTokenKey;
@@ -1,3 +1,3 @@
1
1
  export * from './requester.js';
2
- export declare const buildAccessTokenKey: (resource?: string, scopes?: string[]) => string;
2
+ export declare const buildAccessTokenKey: (resource?: string, organizationId?: string, scopes?: string[]) => string;
3
3
  export declare const getDiscoveryEndpoint: (endpoint: string) => string;
@@ -1,6 +1,7 @@
1
1
  import { discoveryPath } from '@logto/js';
2
+ import { conditionalString } from '@silverhand/essentials';
2
3
 
3
- const buildAccessTokenKey = (resource = '', scopes = []) => `${scopes.slice().sort().join(' ')}@${resource}`;
4
+ const buildAccessTokenKey = (resource = '', organizationId, scopes = []) => `${scopes.slice().sort().join(' ')}@${resource}${conditionalString(organizationId && `#${organizationId}`)}`;
4
5
  const getDiscoveryEndpoint = (endpoint) => new URL(discoveryPath, endpoint).toString();
5
6
 
6
7
  export { buildAccessTokenKey, getDiscoveryEndpoint };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logto/client",
3
- "version": "2.2.4",
3
+ "version": "2.3.0",
4
4
  "type": "module",
5
5
  "main": "./lib/index.cjs",
6
6
  "module": "./lib/index.js",
@@ -21,10 +21,10 @@
21
21
  "directory": "packages/client"
22
22
  },
23
23
  "dependencies": {
24
- "@logto/js": "^2.1.3",
24
+ "@logto/js": "^3.0.0",
25
25
  "@silverhand/essentials": "^2.6.2",
26
26
  "camelcase-keys": "^7.0.1",
27
- "jose": "^4.13.2"
27
+ "jose": "^5.0.0"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@silverhand/eslint-config": "^4.0.1",