@logto/client 2.3.0 → 2.3.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.
- package/lib/index.cjs +51 -46
- package/lib/index.d.ts +16 -20
- package/lib/index.js +52 -47
- package/lib/mock.d.ts +3 -11
- package/package.json +3 -3
package/lib/index.cjs
CHANGED
|
@@ -24,7 +24,11 @@ var requester = require('./utils/requester.cjs');
|
|
|
24
24
|
*/
|
|
25
25
|
class LogtoClient {
|
|
26
26
|
constructor(logtoConfig, adapter) {
|
|
27
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Get the OIDC configuration from the discovery endpoint. This method will
|
|
29
|
+
* only fetch the configuration once and cache the result.
|
|
30
|
+
*/
|
|
31
|
+
this.getOidcConfig = once.once(this.#getOidcConfig);
|
|
28
32
|
/**
|
|
29
33
|
* Get the access token from the storage with refresh strategy.
|
|
30
34
|
*
|
|
@@ -53,6 +57,16 @@ class LogtoClient {
|
|
|
53
57
|
* It uses the same refresh strategy as {@link getAccessToken}.
|
|
54
58
|
*/
|
|
55
59
|
this.getOrganizationToken = memoize.memoize(this.#getOrganizationToken);
|
|
60
|
+
/**
|
|
61
|
+
* Handle the sign-in callback by parsing the authorization code from the
|
|
62
|
+
* callback URI and exchanging it for the tokens.
|
|
63
|
+
*
|
|
64
|
+
* @param callbackUri The callback URI, including the search params, that the user is redirected to after the sign-in flow is completed.
|
|
65
|
+
* The origin and pathname of this URI must match the origin and pathname of the redirect URI specified in {@link signIn}.
|
|
66
|
+
* In many cases you'll probably end up passing `window.location.href` as the argument to this function.
|
|
67
|
+
* @throws LogtoClientError if the sign-in session is not found.
|
|
68
|
+
*/
|
|
69
|
+
this.handleSignInCallback = memoize.memoize(this.#handleSignInCallback);
|
|
56
70
|
this.getJwtVerifyGetKey = once.once(this.#getJwtVerifyGetKey);
|
|
57
71
|
this.accessTokenMap = new Map();
|
|
58
72
|
this.logtoConfig = index.normalizeLogtoConfig(logtoConfig);
|
|
@@ -183,50 +197,6 @@ class LogtoClient {
|
|
|
183
197
|
const { origin, pathname } = new URL(url);
|
|
184
198
|
return `${origin}${pathname}` === redirectUri;
|
|
185
199
|
}
|
|
186
|
-
/**
|
|
187
|
-
* Handle the sign-in callback by parsing the authorization code from the
|
|
188
|
-
* callback URI and exchanging it for the tokens.
|
|
189
|
-
*
|
|
190
|
-
* @param callbackUri The callback URI, including the search params, that the user is redirected to after the sign-in flow is completed.
|
|
191
|
-
* The origin and pathname of this URI must match the origin and pathname of the redirect URI specified in {@link signIn}.
|
|
192
|
-
* In many cases you'll probably end up passing `window.location.href` as the argument to this function.
|
|
193
|
-
* @throws LogtoClientError if the sign-in session is not found.
|
|
194
|
-
*/
|
|
195
|
-
async handleSignInCallback(callbackUri) {
|
|
196
|
-
const { requester } = this.adapter;
|
|
197
|
-
const signInSession = await this.getSignInSession();
|
|
198
|
-
if (!signInSession) {
|
|
199
|
-
throw new errors.LogtoClientError('sign_in_session.not_found');
|
|
200
|
-
}
|
|
201
|
-
const { redirectUri, state, codeVerifier } = signInSession;
|
|
202
|
-
const code = js.verifyAndParseCodeFromCallbackUri(callbackUri, redirectUri, state);
|
|
203
|
-
// NOTE: Will add scope to accessTokenKey when needed. (Linear issue LOG-1589)
|
|
204
|
-
const accessTokenKey = index$2.buildAccessTokenKey();
|
|
205
|
-
const { appId: clientId } = this.logtoConfig;
|
|
206
|
-
const { tokenEndpoint } = await this.getOidcConfig();
|
|
207
|
-
const requestedAt = Math.round(Date.now() / 1000);
|
|
208
|
-
const { idToken, refreshToken, accessToken, scope, expiresIn } = await js.fetchTokenByAuthorizationCode({
|
|
209
|
-
clientId,
|
|
210
|
-
tokenEndpoint,
|
|
211
|
-
redirectUri,
|
|
212
|
-
codeVerifier,
|
|
213
|
-
code,
|
|
214
|
-
}, requester);
|
|
215
|
-
await this.verifyIdToken(idToken);
|
|
216
|
-
await this.setRefreshToken(refreshToken ?? null);
|
|
217
|
-
await this.setIdToken(idToken);
|
|
218
|
-
this.accessTokenMap.set(accessTokenKey, {
|
|
219
|
-
token: accessToken,
|
|
220
|
-
scope,
|
|
221
|
-
/** The `expiresAt` variable provides an approximate estimation of the actual `exp` property
|
|
222
|
-
* in the token claims. It is utilized by the client to determine if the cached access token
|
|
223
|
-
* has expired and when a new access token should be requested.
|
|
224
|
-
*/
|
|
225
|
-
expiresAt: requestedAt + expiresIn,
|
|
226
|
-
});
|
|
227
|
-
await this.saveAccessTokenMap();
|
|
228
|
-
await this.setSignInSession(null);
|
|
229
|
-
}
|
|
230
200
|
/**
|
|
231
201
|
* Start the sign-out flow with the specified redirect URI. The URI must be
|
|
232
202
|
* registered in the Logto Console.
|
|
@@ -385,7 +355,42 @@ class LogtoClient {
|
|
|
385
355
|
if (!this.logtoConfig.scopes?.includes(js.UserScope.Organizations)) {
|
|
386
356
|
throw new errors.LogtoClientError('missing_scope_organizations');
|
|
387
357
|
}
|
|
388
|
-
return this
|
|
358
|
+
return this.getAccessToken(undefined, organizationId);
|
|
359
|
+
}
|
|
360
|
+
async #handleSignInCallback(callbackUri) {
|
|
361
|
+
const { requester } = this.adapter;
|
|
362
|
+
const signInSession = await this.getSignInSession();
|
|
363
|
+
if (!signInSession) {
|
|
364
|
+
throw new errors.LogtoClientError('sign_in_session.not_found');
|
|
365
|
+
}
|
|
366
|
+
const { redirectUri, state, codeVerifier } = signInSession;
|
|
367
|
+
const code = js.verifyAndParseCodeFromCallbackUri(callbackUri, redirectUri, state);
|
|
368
|
+
// NOTE: Will add scope to accessTokenKey when needed. (Linear issue LOG-1589)
|
|
369
|
+
const accessTokenKey = index$2.buildAccessTokenKey();
|
|
370
|
+
const { appId: clientId } = this.logtoConfig;
|
|
371
|
+
const { tokenEndpoint } = await this.getOidcConfig();
|
|
372
|
+
const requestedAt = Math.round(Date.now() / 1000);
|
|
373
|
+
const { idToken, refreshToken, accessToken, scope, expiresIn } = await js.fetchTokenByAuthorizationCode({
|
|
374
|
+
clientId,
|
|
375
|
+
tokenEndpoint,
|
|
376
|
+
redirectUri,
|
|
377
|
+
codeVerifier,
|
|
378
|
+
code,
|
|
379
|
+
}, requester);
|
|
380
|
+
await this.verifyIdToken(idToken);
|
|
381
|
+
await this.setRefreshToken(refreshToken ?? null);
|
|
382
|
+
await this.setIdToken(idToken);
|
|
383
|
+
this.accessTokenMap.set(accessTokenKey, {
|
|
384
|
+
token: accessToken,
|
|
385
|
+
scope,
|
|
386
|
+
/** The `expiresAt` variable provides an approximate estimation of the actual `exp` property
|
|
387
|
+
* in the token claims. It is utilized by the client to determine if the cached access token
|
|
388
|
+
* has expired and when a new access token should be requested.
|
|
389
|
+
*/
|
|
390
|
+
expiresAt: requestedAt + expiresIn,
|
|
391
|
+
});
|
|
392
|
+
await this.saveAccessTokenMap();
|
|
393
|
+
await this.setSignInSession(null);
|
|
389
394
|
}
|
|
390
395
|
}
|
|
391
396
|
/* eslint-enable max-lines */
|
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type IdTokenClaims, type UserInfoResponse, type InteractionMode, type AccessTokenClaims } from '@logto/js';
|
|
1
|
+
import { type IdTokenClaims, type UserInfoResponse, type InteractionMode, type AccessTokenClaims, type OidcConfigResponse } from '@logto/js';
|
|
2
2
|
import { type Nullable } from '@silverhand/essentials';
|
|
3
3
|
import { type JWTVerifyGetKey } from 'jose';
|
|
4
4
|
import { ClientAdapterInstance, type ClientAdapter } from './adapter/index.js';
|
|
@@ -20,15 +20,11 @@ export * from './types/index.js';
|
|
|
20
20
|
export default class LogtoClient {
|
|
21
21
|
#private;
|
|
22
22
|
readonly logtoConfig: LogtoConfig;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
revocation_endpoint: string;
|
|
29
|
-
jwks_uri: string;
|
|
30
|
-
issuer: string;
|
|
31
|
-
}>>;
|
|
23
|
+
/**
|
|
24
|
+
* Get the OIDC configuration from the discovery endpoint. This method will
|
|
25
|
+
* only fetch the configuration once and cache the result.
|
|
26
|
+
*/
|
|
27
|
+
readonly getOidcConfig: () => Promise<OidcConfigResponse>;
|
|
32
28
|
/**
|
|
33
29
|
* Get the access token from the storage with refresh strategy.
|
|
34
30
|
*
|
|
@@ -57,6 +53,16 @@ export default class LogtoClient {
|
|
|
57
53
|
* It uses the same refresh strategy as {@link getAccessToken}.
|
|
58
54
|
*/
|
|
59
55
|
readonly getOrganizationToken: (this: unknown, organizationId: string) => Promise<string>;
|
|
56
|
+
/**
|
|
57
|
+
* Handle the sign-in callback by parsing the authorization code from the
|
|
58
|
+
* callback URI and exchanging it for the tokens.
|
|
59
|
+
*
|
|
60
|
+
* @param callbackUri The callback URI, including the search params, that the user is redirected to after the sign-in flow is completed.
|
|
61
|
+
* The origin and pathname of this URI must match the origin and pathname of the redirect URI specified in {@link signIn}.
|
|
62
|
+
* In many cases you'll probably end up passing `window.location.href` as the argument to this function.
|
|
63
|
+
* @throws LogtoClientError if the sign-in session is not found.
|
|
64
|
+
*/
|
|
65
|
+
readonly handleSignInCallback: (this: unknown, callbackUri: string) => Promise<void>;
|
|
60
66
|
protected readonly getJwtVerifyGetKey: (...args: unknown[]) => Promise<JWTVerifyGetKey>;
|
|
61
67
|
protected readonly adapter: ClientAdapterInstance;
|
|
62
68
|
protected readonly accessTokenMap: Map<string, AccessToken>;
|
|
@@ -129,16 +135,6 @@ export default class LogtoClient {
|
|
|
129
135
|
* @param url The current URL.
|
|
130
136
|
*/
|
|
131
137
|
isSignInRedirected(url: string): Promise<boolean>;
|
|
132
|
-
/**
|
|
133
|
-
* Handle the sign-in callback by parsing the authorization code from the
|
|
134
|
-
* callback URI and exchanging it for the tokens.
|
|
135
|
-
*
|
|
136
|
-
* @param callbackUri The callback URI, including the search params, that the user is redirected to after the sign-in flow is completed.
|
|
137
|
-
* The origin and pathname of this URI must match the origin and pathname of the redirect URI specified in {@link signIn}.
|
|
138
|
-
* In many cases you'll probably end up passing `window.location.href` as the argument to this function.
|
|
139
|
-
* @throws LogtoClientError if the sign-in session is not found.
|
|
140
|
-
*/
|
|
141
|
-
handleSignInCallback(callbackUri: string): Promise<void>;
|
|
142
138
|
/**
|
|
143
139
|
* Start the sign-out flow with the specified redirect URI. The URI must be
|
|
144
140
|
* registered in the Logto Console.
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { decodeIdToken, decodeAccessToken, fetchUserInfo, generateSignInUri,
|
|
1
|
+
import { decodeIdToken, decodeAccessToken, fetchUserInfo, generateSignInUri, revoke, generateSignOutUri, fetchTokenByRefreshToken, verifyIdToken, fetchOidcConfig, UserScope, verifyAndParseCodeFromCallbackUri, fetchTokenByAuthorizationCode } from '@logto/js';
|
|
2
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';
|
|
@@ -21,7 +21,11 @@ export { createRequester } from './utils/requester.js';
|
|
|
21
21
|
*/
|
|
22
22
|
class LogtoClient {
|
|
23
23
|
constructor(logtoConfig, adapter) {
|
|
24
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Get the OIDC configuration from the discovery endpoint. This method will
|
|
26
|
+
* only fetch the configuration once and cache the result.
|
|
27
|
+
*/
|
|
28
|
+
this.getOidcConfig = once(this.#getOidcConfig);
|
|
25
29
|
/**
|
|
26
30
|
* Get the access token from the storage with refresh strategy.
|
|
27
31
|
*
|
|
@@ -50,6 +54,16 @@ class LogtoClient {
|
|
|
50
54
|
* It uses the same refresh strategy as {@link getAccessToken}.
|
|
51
55
|
*/
|
|
52
56
|
this.getOrganizationToken = memoize(this.#getOrganizationToken);
|
|
57
|
+
/**
|
|
58
|
+
* Handle the sign-in callback by parsing the authorization code from the
|
|
59
|
+
* callback URI and exchanging it for the tokens.
|
|
60
|
+
*
|
|
61
|
+
* @param callbackUri The callback URI, including the search params, that the user is redirected to after the sign-in flow is completed.
|
|
62
|
+
* The origin and pathname of this URI must match the origin and pathname of the redirect URI specified in {@link signIn}.
|
|
63
|
+
* In many cases you'll probably end up passing `window.location.href` as the argument to this function.
|
|
64
|
+
* @throws LogtoClientError if the sign-in session is not found.
|
|
65
|
+
*/
|
|
66
|
+
this.handleSignInCallback = memoize(this.#handleSignInCallback);
|
|
53
67
|
this.getJwtVerifyGetKey = once(this.#getJwtVerifyGetKey);
|
|
54
68
|
this.accessTokenMap = new Map();
|
|
55
69
|
this.logtoConfig = normalizeLogtoConfig(logtoConfig);
|
|
@@ -180,50 +194,6 @@ class LogtoClient {
|
|
|
180
194
|
const { origin, pathname } = new URL(url);
|
|
181
195
|
return `${origin}${pathname}` === redirectUri;
|
|
182
196
|
}
|
|
183
|
-
/**
|
|
184
|
-
* Handle the sign-in callback by parsing the authorization code from the
|
|
185
|
-
* callback URI and exchanging it for the tokens.
|
|
186
|
-
*
|
|
187
|
-
* @param callbackUri The callback URI, including the search params, that the user is redirected to after the sign-in flow is completed.
|
|
188
|
-
* The origin and pathname of this URI must match the origin and pathname of the redirect URI specified in {@link signIn}.
|
|
189
|
-
* In many cases you'll probably end up passing `window.location.href` as the argument to this function.
|
|
190
|
-
* @throws LogtoClientError if the sign-in session is not found.
|
|
191
|
-
*/
|
|
192
|
-
async handleSignInCallback(callbackUri) {
|
|
193
|
-
const { requester } = this.adapter;
|
|
194
|
-
const signInSession = await this.getSignInSession();
|
|
195
|
-
if (!signInSession) {
|
|
196
|
-
throw new LogtoClientError('sign_in_session.not_found');
|
|
197
|
-
}
|
|
198
|
-
const { redirectUri, state, codeVerifier } = signInSession;
|
|
199
|
-
const code = verifyAndParseCodeFromCallbackUri(callbackUri, redirectUri, state);
|
|
200
|
-
// NOTE: Will add scope to accessTokenKey when needed. (Linear issue LOG-1589)
|
|
201
|
-
const accessTokenKey = buildAccessTokenKey();
|
|
202
|
-
const { appId: clientId } = this.logtoConfig;
|
|
203
|
-
const { tokenEndpoint } = await this.getOidcConfig();
|
|
204
|
-
const requestedAt = Math.round(Date.now() / 1000);
|
|
205
|
-
const { idToken, refreshToken, accessToken, scope, expiresIn } = await fetchTokenByAuthorizationCode({
|
|
206
|
-
clientId,
|
|
207
|
-
tokenEndpoint,
|
|
208
|
-
redirectUri,
|
|
209
|
-
codeVerifier,
|
|
210
|
-
code,
|
|
211
|
-
}, requester);
|
|
212
|
-
await this.verifyIdToken(idToken);
|
|
213
|
-
await this.setRefreshToken(refreshToken ?? null);
|
|
214
|
-
await this.setIdToken(idToken);
|
|
215
|
-
this.accessTokenMap.set(accessTokenKey, {
|
|
216
|
-
token: accessToken,
|
|
217
|
-
scope,
|
|
218
|
-
/** The `expiresAt` variable provides an approximate estimation of the actual `exp` property
|
|
219
|
-
* in the token claims. It is utilized by the client to determine if the cached access token
|
|
220
|
-
* has expired and when a new access token should be requested.
|
|
221
|
-
*/
|
|
222
|
-
expiresAt: requestedAt + expiresIn,
|
|
223
|
-
});
|
|
224
|
-
await this.saveAccessTokenMap();
|
|
225
|
-
await this.setSignInSession(null);
|
|
226
|
-
}
|
|
227
197
|
/**
|
|
228
198
|
* Start the sign-out flow with the specified redirect URI. The URI must be
|
|
229
199
|
* registered in the Logto Console.
|
|
@@ -382,7 +352,42 @@ class LogtoClient {
|
|
|
382
352
|
if (!this.logtoConfig.scopes?.includes(UserScope.Organizations)) {
|
|
383
353
|
throw new LogtoClientError('missing_scope_organizations');
|
|
384
354
|
}
|
|
385
|
-
return this
|
|
355
|
+
return this.getAccessToken(undefined, organizationId);
|
|
356
|
+
}
|
|
357
|
+
async #handleSignInCallback(callbackUri) {
|
|
358
|
+
const { requester } = this.adapter;
|
|
359
|
+
const signInSession = await this.getSignInSession();
|
|
360
|
+
if (!signInSession) {
|
|
361
|
+
throw new LogtoClientError('sign_in_session.not_found');
|
|
362
|
+
}
|
|
363
|
+
const { redirectUri, state, codeVerifier } = signInSession;
|
|
364
|
+
const code = verifyAndParseCodeFromCallbackUri(callbackUri, redirectUri, state);
|
|
365
|
+
// NOTE: Will add scope to accessTokenKey when needed. (Linear issue LOG-1589)
|
|
366
|
+
const accessTokenKey = buildAccessTokenKey();
|
|
367
|
+
const { appId: clientId } = this.logtoConfig;
|
|
368
|
+
const { tokenEndpoint } = await this.getOidcConfig();
|
|
369
|
+
const requestedAt = Math.round(Date.now() / 1000);
|
|
370
|
+
const { idToken, refreshToken, accessToken, scope, expiresIn } = await fetchTokenByAuthorizationCode({
|
|
371
|
+
clientId,
|
|
372
|
+
tokenEndpoint,
|
|
373
|
+
redirectUri,
|
|
374
|
+
codeVerifier,
|
|
375
|
+
code,
|
|
376
|
+
}, requester);
|
|
377
|
+
await this.verifyIdToken(idToken);
|
|
378
|
+
await this.setRefreshToken(refreshToken ?? null);
|
|
379
|
+
await this.setIdToken(idToken);
|
|
380
|
+
this.accessTokenMap.set(accessTokenKey, {
|
|
381
|
+
token: accessToken,
|
|
382
|
+
scope,
|
|
383
|
+
/** The `expiresAt` variable provides an approximate estimation of the actual `exp` property
|
|
384
|
+
* in the token claims. It is utilized by the client to determine if the cached access token
|
|
385
|
+
* has expired and when a new access token should be requested.
|
|
386
|
+
*/
|
|
387
|
+
expiresAt: requestedAt + expiresIn,
|
|
388
|
+
});
|
|
389
|
+
await this.saveAccessTokenMap();
|
|
390
|
+
await this.setSignInSession(null);
|
|
386
391
|
}
|
|
387
392
|
}
|
|
388
393
|
/* eslint-enable max-lines */
|
package/lib/mock.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference types="jest" />
|
|
2
|
-
import { Prompt } from '@logto/js';
|
|
2
|
+
import { type OidcConfigResponse, Prompt } from '@logto/js';
|
|
3
3
|
import { type Nullable } from '@silverhand/essentials';
|
|
4
4
|
import type { Storage } from './adapter/index.js';
|
|
5
5
|
import type { AccessToken, LogtoConfig, LogtoSignInSessionItem } from './index.js';
|
|
@@ -71,18 +71,10 @@ export declare const createClient: (prompt?: Prompt, storage?: MockedStorage, wi
|
|
|
71
71
|
* Make protected fields accessible for test
|
|
72
72
|
*/
|
|
73
73
|
export declare class LogtoClientWithAccessors extends LogtoClient {
|
|
74
|
-
runGetOidcConfig(): Promise<
|
|
75
|
-
authorization_endpoint: string;
|
|
76
|
-
token_endpoint: string;
|
|
77
|
-
userinfo_endpoint: string;
|
|
78
|
-
end_session_endpoint: string;
|
|
79
|
-
revocation_endpoint: string;
|
|
80
|
-
jwks_uri: string;
|
|
81
|
-
issuer: string;
|
|
82
|
-
}>>;
|
|
74
|
+
runGetOidcConfig(): Promise<OidcConfigResponse>;
|
|
83
75
|
runGetJwtVerifyGetKey(): Promise<import("jose").JWTVerifyGetKey>;
|
|
84
76
|
getLogtoConfig(): Nullable<LogtoConfig>;
|
|
85
|
-
|
|
77
|
+
getSignInSession(): Promise<Nullable<LogtoSignInSessionItem>>;
|
|
86
78
|
setSignInSessionItem(item: Nullable<LogtoSignInSessionItem>): Promise<void>;
|
|
87
79
|
getAccessTokenMap(): Map<string, AccessToken>;
|
|
88
80
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logto/client",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./lib/index.cjs",
|
|
6
6
|
"module": "./lib/index.js",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"jose": "^5.0.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@silverhand/eslint-config": "^
|
|
31
|
-
"@silverhand/ts-config": "^
|
|
30
|
+
"@silverhand/eslint-config": "^5.0.0",
|
|
31
|
+
"@silverhand/ts-config": "^5.0.0",
|
|
32
32
|
"@swc/core": "^1.3.50",
|
|
33
33
|
"@swc/jest": "^0.2.24",
|
|
34
34
|
"@types/jest": "^29.5.0",
|