@logto/client 3.0.0-alpha.2 → 3.0.4
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/README.md +2 -2
- package/lib/adapter/defaults.d.ts +4 -2
- package/lib/adapter/defaults.js +9 -10
- package/lib/adapter/types.d.ts +5 -4
- package/lib/client.d.ts +64 -9
- package/lib/client.js +60 -31
- package/lib/index.js +1 -1
- package/lib/mock.d.ts +24 -36
- package/lib/shim.d.ts +3 -3
- package/lib/shim.js +1 -1
- package/lib/types/index.d.ts +11 -2
- package/lib/types/index.js +7 -4
- package/lib/utils/memoize.js +1 -1
- package/lib/utils/requester.js +4 -3
- package/package.json +22 -33
- package/lib/adapter/defaults.cjs +0 -28
- package/lib/adapter/index.cjs +0 -63
- package/lib/adapter/types.cjs +0 -24
- package/lib/client.cjs +0 -383
- package/lib/errors.cjs +0 -22
- package/lib/index.cjs +0 -81
- package/lib/shim.cjs +0 -66
- package/lib/types/index.cjs +0 -48
- package/lib/utils/index.cjs +0 -10
- package/lib/utils/memoize.cjs +0 -25
- package/lib/utils/once.cjs +0 -20
- package/lib/utils/requester.cjs +0 -29
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://github.com/logto-io/js/actions/workflows/main.yml)
|
|
4
4
|
[](https://app.codecov.io/gh/logto-io/js?branch=master)
|
|
5
5
|
|
|
6
|
-
The Logto JavaScript Client SDK written in TypeScript.
|
|
6
|
+
The Logto JavaScript Client SDK written in TypeScript.
|
|
7
7
|
|
|
8
8
|
## Installation
|
|
9
9
|
|
|
@@ -42,7 +42,7 @@ To implement a platform-specific SDK, you should implement the following adapter
|
|
|
42
42
|
5. generateCodeVerifier: generate code verifier.
|
|
43
43
|
6. generateCodeChallenge: generate code challenge.
|
|
44
44
|
|
|
45
|
-
See the [adapters
|
|
45
|
+
See the [adapters](./src/adapter/index.ts) for more information.
|
|
46
46
|
|
|
47
47
|
## Resources
|
|
48
48
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { JWTVerifyGetKey } from 'jose';
|
|
2
2
|
import { type StandardLogtoClient } from '../client.js';
|
|
3
3
|
import { type JwtVerifier } from './types.js';
|
|
4
|
-
export declare const
|
|
4
|
+
export declare const defaultClockTolerance = 300;
|
|
5
|
+
export declare const verifyIdToken: (idToken: string, clientId: string, issuer: string, jwks: JWTVerifyGetKey, clockTolerance?: number) => Promise<void>;
|
|
5
6
|
export declare class DefaultJwtVerifier implements JwtVerifier {
|
|
6
7
|
protected client: StandardLogtoClient;
|
|
8
|
+
readonly clockTolerance: number;
|
|
7
9
|
protected getJwtVerifyGetKey?: JWTVerifyGetKey;
|
|
8
|
-
constructor(client: StandardLogtoClient);
|
|
10
|
+
constructor(client: StandardLogtoClient, clockTolerance?: number);
|
|
9
11
|
verifyIdToken(idToken: string): Promise<void>;
|
|
10
12
|
}
|
package/lib/adapter/defaults.js
CHANGED
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
import { LogtoError } from '@logto/js';
|
|
2
2
|
import { createRemoteJWKSet, jwtVerify } from 'jose';
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
const verifyIdToken = async (idToken, clientId, issuer, jwks) => {
|
|
6
|
-
const result = await jwtVerify(idToken, jwks, { audience: clientId, issuer });
|
|
7
|
-
if (Math.abs((result.payload.iat ?? 0) - Date.now() / 1000) >
|
|
4
|
+
const defaultClockTolerance = 300; // 5 minutes
|
|
5
|
+
const verifyIdToken = async (idToken, clientId, issuer, jwks, clockTolerance = defaultClockTolerance) => {
|
|
6
|
+
const result = await jwtVerify(idToken, jwks, { audience: clientId, issuer, clockTolerance });
|
|
7
|
+
if (Math.abs((result.payload.iat ?? 0) - Date.now() / 1000) > clockTolerance) {
|
|
8
8
|
throw new LogtoError('id_token.invalid_iat');
|
|
9
9
|
}
|
|
10
10
|
};
|
|
11
11
|
class DefaultJwtVerifier {
|
|
12
|
-
constructor(client) {
|
|
12
|
+
constructor(client, clockTolerance = defaultClockTolerance) {
|
|
13
13
|
this.client = client;
|
|
14
|
+
this.clockTolerance = clockTolerance;
|
|
14
15
|
}
|
|
15
16
|
async verifyIdToken(idToken) {
|
|
16
17
|
const { appId } = this.client.logtoConfig;
|
|
17
18
|
const { issuer, jwksUri } = await this.client.getOidcConfig();
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
await verifyIdToken(idToken, appId, issuer, this.getJwtVerifyGetKey);
|
|
19
|
+
this.getJwtVerifyGetKey ||= createRemoteJWKSet(new URL(jwksUri));
|
|
20
|
+
await verifyIdToken(idToken, appId, issuer, this.getJwtVerifyGetKey, this.clockTolerance);
|
|
22
21
|
}
|
|
23
22
|
}
|
|
24
23
|
|
|
25
|
-
export { DefaultJwtVerifier, verifyIdToken };
|
|
24
|
+
export { DefaultJwtVerifier, defaultClockTolerance, verifyIdToken };
|
package/lib/adapter/types.d.ts
CHANGED
|
@@ -39,16 +39,17 @@ export type InferStorageKey<S> = S extends Storage<infer Key> ? Key : never;
|
|
|
39
39
|
* @param url The URL to navigate to.
|
|
40
40
|
* @param parameters The parameters for the navigation.
|
|
41
41
|
* @param parameters.redirectUri The redirect URI that the user will be redirected to after the
|
|
42
|
-
* flow is completed. That is, the "redirect URI" for sign-in and "post-logout redirect URI" for
|
|
43
|
-
* sign-out.
|
|
44
|
-
* @param parameters.for The purpose of the navigation. It can be either "sign-in"
|
|
42
|
+
* flow is completed. That is, the "redirect URI" for "sign-in" and "post-logout redirect URI" for
|
|
43
|
+
* "sign-out". For the "post-sign-in" navigation, it should be ignored.
|
|
44
|
+
* @param parameters.for The purpose of the navigation. It can be either "sign-in", "sign-out", or
|
|
45
|
+
* "post-sign-in".
|
|
45
46
|
* @remarks Usually, the `redirectUri` parameter can be ignored unless the client needs to pass the
|
|
46
47
|
* redirect scheme or other parameters to the native app, such as `ASWebAuthenticationSession` in
|
|
47
48
|
* iOS.
|
|
48
49
|
*/
|
|
49
50
|
export type Navigate = (url: string, parameters: {
|
|
50
51
|
redirectUri?: string;
|
|
51
|
-
for: 'sign-in' | 'sign-out';
|
|
52
|
+
for: 'sign-in' | 'sign-out' | 'post-sign-in';
|
|
52
53
|
}) => void | Promise<void>;
|
|
53
54
|
export type JwtVerifier = {
|
|
54
55
|
verifyIdToken(idToken: string): Promise<void>;
|
package/lib/client.d.ts
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
1
|
-
import { type IdTokenClaims, type UserInfoResponse, type
|
|
1
|
+
import { type IdTokenClaims, type UserInfoResponse, type AccessTokenClaims, type OidcConfigResponse, type SignInUriParameters } from '@logto/js';
|
|
2
2
|
import { type Nullable } from '@silverhand/essentials';
|
|
3
3
|
import { ClientAdapterInstance, type ClientAdapter, type JwtVerifier } from './adapter/index.js';
|
|
4
4
|
import type { AccessToken, LogtoConfig, LogtoSignInSessionItem } from './types/index.js';
|
|
5
|
+
export type SignInOptions = {
|
|
6
|
+
/**
|
|
7
|
+
* The redirect URI that the user will be redirected to after the sign-in flow is completed.
|
|
8
|
+
*/
|
|
9
|
+
redirectUri: string | URL;
|
|
10
|
+
/**
|
|
11
|
+
* The URI that the user will be redirected to after `redirectUri` successfully handled the
|
|
12
|
+
* sign-in callback. If not specified, the user will stay on the `redirectUri` page.
|
|
13
|
+
*/
|
|
14
|
+
postRedirectUri?: string | URL;
|
|
15
|
+
/**
|
|
16
|
+
* The prompt parameter to be used for the authorization request.
|
|
17
|
+
* Note: If specified, it will override the prompt value in Logto configs.
|
|
18
|
+
*/
|
|
19
|
+
prompt?: SignInUriParameters['prompt'];
|
|
20
|
+
} & Pick<SignInUriParameters, 'interactionMode' | 'firstScreen' | 'identifiers' | 'loginHint' | 'directSignIn' | 'extraParams'>;
|
|
5
21
|
/**
|
|
6
22
|
* The Logto base client class that provides the essential methods for
|
|
7
23
|
* interacting with the Logto server.
|
|
@@ -50,6 +66,14 @@ export declare class StandardLogtoClient {
|
|
|
50
66
|
* It uses the same refresh strategy as {@link getAccessToken}.
|
|
51
67
|
*/
|
|
52
68
|
readonly getOrganizationToken: (this: unknown, organizationId: string) => Promise<string>;
|
|
69
|
+
/**
|
|
70
|
+
* Clear the access token from the cache storage.
|
|
71
|
+
*/
|
|
72
|
+
readonly clearAccessToken: (this: unknown) => Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* Clear all cached tokens from storage.
|
|
75
|
+
*/
|
|
76
|
+
readonly clearAllTokens: (this: unknown) => Promise<void>;
|
|
53
77
|
/**
|
|
54
78
|
* Handle the sign-in callback by parsing the authorization code from the
|
|
55
79
|
* callback URI and exchanging it for the tokens.
|
|
@@ -61,9 +85,15 @@ export declare class StandardLogtoClient {
|
|
|
61
85
|
*/
|
|
62
86
|
readonly handleSignInCallback: (this: unknown, callbackUri: string) => Promise<void>;
|
|
63
87
|
readonly adapter: ClientAdapterInstance;
|
|
64
|
-
|
|
88
|
+
protected jwtVerifierInstance: JwtVerifier;
|
|
65
89
|
protected readonly accessTokenMap: Map<string, AccessToken>;
|
|
90
|
+
get jwtVerifier(): JwtVerifier;
|
|
66
91
|
constructor(logtoConfig: LogtoConfig, adapter: ClientAdapter, buildJwtVerifier: (client: StandardLogtoClient) => JwtVerifier);
|
|
92
|
+
/**
|
|
93
|
+
* Set the JWT verifier for the client.
|
|
94
|
+
* @param buildJwtVerifier The JWT verifier instance or a function that returns the JWT verifier instance.
|
|
95
|
+
*/
|
|
96
|
+
setJwtVerifier(buildJwtVerifier: JwtVerifier | ((client: StandardLogtoClient) => JwtVerifier)): void;
|
|
67
97
|
/**
|
|
68
98
|
* Check if the user is authenticated by checking if the ID token exists.
|
|
69
99
|
*/
|
|
@@ -107,6 +137,33 @@ export declare class StandardLogtoClient {
|
|
|
107
137
|
*/
|
|
108
138
|
fetchUserInfo(): Promise<UserInfoResponse>;
|
|
109
139
|
/**
|
|
140
|
+
* Start the sign-in flow with the specified options.
|
|
141
|
+
*
|
|
142
|
+
* The redirect URI is required and it must be registered in the Logto Console.
|
|
143
|
+
*
|
|
144
|
+
* The user will be redirected to that URI after the sign-in flow is completed,
|
|
145
|
+
* and the client will be able to get the authorization code from the URI.
|
|
146
|
+
* To fetch the tokens from the authorization code, use {@link handleSignInCallback}
|
|
147
|
+
* after the user is redirected in the callback URI.
|
|
148
|
+
*
|
|
149
|
+
* @param options The options for the sign-in flow.
|
|
150
|
+
*/
|
|
151
|
+
signIn(options: SignInOptions): Promise<void>;
|
|
152
|
+
/**
|
|
153
|
+
* Start the sign-in flow with the specified options.
|
|
154
|
+
*
|
|
155
|
+
* The redirect URI is required and it must be registered in the Logto Console.
|
|
156
|
+
*
|
|
157
|
+
* The user will be redirected to that URI after the sign-in flow is completed,
|
|
158
|
+
* and the client will be able to get the authorization code from the URI.
|
|
159
|
+
* To fetch the tokens from the authorization code, use {@link handleSignInCallback}
|
|
160
|
+
* after the user is redirected in the callback URI.
|
|
161
|
+
*
|
|
162
|
+
* @param redirectUri See {@link SignInOptions.redirectUri}.
|
|
163
|
+
*/
|
|
164
|
+
signIn(redirectUri: SignInOptions['redirectUri']): Promise<void>;
|
|
165
|
+
/**
|
|
166
|
+
*
|
|
110
167
|
* Start the sign-in flow with the specified redirect URI. The URI must be
|
|
111
168
|
* registered in the Logto Console.
|
|
112
169
|
*
|
|
@@ -115,14 +172,12 @@ export declare class StandardLogtoClient {
|
|
|
115
172
|
* To fetch the tokens from the authorization code, use {@link handleSignInCallback}
|
|
116
173
|
* after the user is redirected in the callback URI.
|
|
117
174
|
*
|
|
118
|
-
* @
|
|
119
|
-
* @param
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
* @see {@link https://docs.logto.io/docs/recipes/integrate-logto/vanilla-js/#sign-in | Sign in} for more information.
|
|
123
|
-
* @see {@link InteractionMode}
|
|
175
|
+
* @deprecated Use the object parameter instead.
|
|
176
|
+
* @param redirectUri See {@link SignInOptions.redirectUri}.
|
|
177
|
+
* @param interactionMode See {@link SignInOptions.interactionMode}.
|
|
178
|
+
* @param loginHint See {@link SignInOptions.loginHint}.
|
|
124
179
|
*/
|
|
125
|
-
signIn(redirectUri:
|
|
180
|
+
signIn(redirectUri: SignInOptions['redirectUri'], interactionMode?: SignInOptions['interactionMode'], loginHint?: SignInOptions['loginHint']): Promise<void>;
|
|
126
181
|
/**
|
|
127
182
|
* Check if the user is redirected from the sign-in page by checking if the
|
|
128
183
|
* current URL matches the redirect URI in the sign-in session.
|
package/lib/client.js
CHANGED
|
@@ -7,6 +7,7 @@ import { memoize } from './utils/memoize.js';
|
|
|
7
7
|
import { once } from './utils/once.js';
|
|
8
8
|
import { PersistKey, CacheKey } from './adapter/types.js';
|
|
9
9
|
|
|
10
|
+
/* eslint-disable max-lines */
|
|
10
11
|
/**
|
|
11
12
|
* The Logto base client class that provides the essential methods for
|
|
12
13
|
* interacting with the Logto server.
|
|
@@ -20,6 +21,9 @@ import { PersistKey, CacheKey } from './adapter/types.js';
|
|
|
20
21
|
* can use `StandardLogtoClient` and provide your own JWT verifier.
|
|
21
22
|
*/
|
|
22
23
|
class StandardLogtoClient {
|
|
24
|
+
get jwtVerifier() {
|
|
25
|
+
return this.jwtVerifierInstance;
|
|
26
|
+
}
|
|
23
27
|
constructor(logtoConfig, adapter, buildJwtVerifier) {
|
|
24
28
|
/**
|
|
25
29
|
* Get the OIDC configuration from the discovery endpoint. This method will
|
|
@@ -54,6 +58,14 @@ class StandardLogtoClient {
|
|
|
54
58
|
* It uses the same refresh strategy as {@link getAccessToken}.
|
|
55
59
|
*/
|
|
56
60
|
this.getOrganizationToken = memoize(this.#getOrganizationToken);
|
|
61
|
+
/**
|
|
62
|
+
* Clear the access token from the cache storage.
|
|
63
|
+
*/
|
|
64
|
+
this.clearAccessToken = memoize(this.#clearAccessToken);
|
|
65
|
+
/**
|
|
66
|
+
* Clear all cached tokens from storage.
|
|
67
|
+
*/
|
|
68
|
+
this.clearAllTokens = memoize(this.#clearAllTokens);
|
|
57
69
|
/**
|
|
58
70
|
* Handle the sign-in callback by parsing the authorization code from the
|
|
59
71
|
* callback URI and exchanging it for the tokens.
|
|
@@ -67,9 +79,17 @@ class StandardLogtoClient {
|
|
|
67
79
|
this.accessTokenMap = new Map();
|
|
68
80
|
this.logtoConfig = normalizeLogtoConfig(logtoConfig);
|
|
69
81
|
this.adapter = new ClientAdapterInstance(adapter);
|
|
70
|
-
this.
|
|
82
|
+
this.jwtVerifierInstance = buildJwtVerifier(this);
|
|
71
83
|
void this.loadAccessTokenMap();
|
|
72
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Set the JWT verifier for the client.
|
|
87
|
+
* @param buildJwtVerifier The JWT verifier instance or a function that returns the JWT verifier instance.
|
|
88
|
+
*/
|
|
89
|
+
setJwtVerifier(buildJwtVerifier) {
|
|
90
|
+
this.jwtVerifierInstance =
|
|
91
|
+
typeof buildJwtVerifier === 'function' ? buildJwtVerifier(this) : buildJwtVerifier;
|
|
92
|
+
}
|
|
73
93
|
/**
|
|
74
94
|
* Check if the user is authenticated by checking if the ID token exists.
|
|
75
95
|
*/
|
|
@@ -137,24 +157,23 @@ class StandardLogtoClient {
|
|
|
137
157
|
}
|
|
138
158
|
return fetchUserInfo(userinfoEndpoint, accessToken, this.adapter.requester);
|
|
139
159
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const { appId: clientId, prompt, resources, scopes } = this.logtoConfig;
|
|
160
|
+
async signIn(options, mode, hint) {
|
|
161
|
+
const { redirectUri: redirectUriUrl, postRedirectUri: postRedirectUriUrl, firstScreen, identifiers, interactionMode, loginHint, directSignIn, extraParams, prompt, } = typeof options === 'string' || options instanceof URL
|
|
162
|
+
? {
|
|
163
|
+
redirectUri: options,
|
|
164
|
+
postRedirectUri: undefined,
|
|
165
|
+
firstScreen: undefined,
|
|
166
|
+
identifiers: undefined,
|
|
167
|
+
interactionMode: mode,
|
|
168
|
+
loginHint: hint,
|
|
169
|
+
directSignIn: undefined,
|
|
170
|
+
extraParams: undefined,
|
|
171
|
+
prompt: undefined,
|
|
172
|
+
}
|
|
173
|
+
: options;
|
|
174
|
+
const redirectUri = redirectUriUrl.toString();
|
|
175
|
+
const postRedirectUri = postRedirectUriUrl?.toString();
|
|
176
|
+
const { appId: clientId, prompt: promptViaConfig, resources, scopes } = this.logtoConfig;
|
|
158
177
|
const { authorizationEndpoint } = await this.getOidcConfig();
|
|
159
178
|
const [codeVerifier, state] = await Promise.all([
|
|
160
179
|
this.adapter.generateCodeVerifier(),
|
|
@@ -164,18 +183,22 @@ class StandardLogtoClient {
|
|
|
164
183
|
const signInUri = generateSignInUri({
|
|
165
184
|
authorizationEndpoint,
|
|
166
185
|
clientId,
|
|
167
|
-
redirectUri,
|
|
186
|
+
redirectUri: redirectUri.toString(),
|
|
168
187
|
codeChallenge,
|
|
169
188
|
state,
|
|
170
189
|
scopes,
|
|
171
190
|
resources,
|
|
172
|
-
prompt,
|
|
191
|
+
prompt: prompt ?? promptViaConfig,
|
|
192
|
+
firstScreen,
|
|
193
|
+
identifiers,
|
|
173
194
|
interactionMode,
|
|
195
|
+
loginHint,
|
|
196
|
+
directSignIn,
|
|
197
|
+
extraParams,
|
|
174
198
|
});
|
|
175
199
|
await Promise.all([
|
|
176
|
-
this.setSignInSession({ redirectUri, codeVerifier, state }),
|
|
177
|
-
this.
|
|
178
|
-
this.setIdToken(null),
|
|
200
|
+
this.setSignInSession({ redirectUri, postRedirectUri, codeVerifier, state }),
|
|
201
|
+
this.clearAllTokens(),
|
|
179
202
|
]);
|
|
180
203
|
await this.adapter.navigate(signInUri, { redirectUri, for: 'sign-in' });
|
|
181
204
|
}
|
|
@@ -223,12 +246,7 @@ class StandardLogtoClient {
|
|
|
223
246
|
postLogoutRedirectUri,
|
|
224
247
|
clientId,
|
|
225
248
|
});
|
|
226
|
-
this.
|
|
227
|
-
await Promise.all([
|
|
228
|
-
this.setRefreshToken(null),
|
|
229
|
-
this.setIdToken(null),
|
|
230
|
-
this.adapter.storage.removeItem('accessToken'),
|
|
231
|
-
]);
|
|
249
|
+
await this.clearAllTokens();
|
|
232
250
|
await this.adapter.navigate(url, { redirectUri: postLogoutRedirectUri, for: 'sign-out' });
|
|
233
251
|
}
|
|
234
252
|
async getSignInSession() {
|
|
@@ -342,12 +360,19 @@ class StandardLogtoClient {
|
|
|
342
360
|
}
|
|
343
361
|
return this.getAccessToken(undefined, organizationId);
|
|
344
362
|
}
|
|
363
|
+
async #clearAccessToken() {
|
|
364
|
+
this.accessTokenMap.clear();
|
|
365
|
+
await this.adapter.storage.removeItem('accessToken');
|
|
366
|
+
}
|
|
367
|
+
async #clearAllTokens() {
|
|
368
|
+
await Promise.all([this.setRefreshToken(null), this.setIdToken(null), this.clearAccessToken()]);
|
|
369
|
+
}
|
|
345
370
|
async #handleSignInCallback(callbackUri) {
|
|
346
371
|
const signInSession = await this.getSignInSession();
|
|
347
372
|
if (!signInSession) {
|
|
348
373
|
throw new LogtoClientError('sign_in_session.not_found');
|
|
349
374
|
}
|
|
350
|
-
const { redirectUri, state, codeVerifier } = signInSession;
|
|
375
|
+
const { redirectUri, postRedirectUri, state, codeVerifier } = signInSession;
|
|
351
376
|
const code = verifyAndParseCodeFromCallbackUri(callbackUri, redirectUri, state);
|
|
352
377
|
// NOTE: Will add scope to accessTokenKey when needed. (Linear issue LOG-1589)
|
|
353
378
|
const accessTokenKey = buildAccessTokenKey();
|
|
@@ -375,7 +400,11 @@ class StandardLogtoClient {
|
|
|
375
400
|
});
|
|
376
401
|
await this.saveAccessTokenMap();
|
|
377
402
|
await this.setSignInSession(null);
|
|
403
|
+
if (postRedirectUri) {
|
|
404
|
+
await this.adapter.navigate(postRedirectUri, { for: 'post-sign-in' });
|
|
405
|
+
}
|
|
378
406
|
}
|
|
379
407
|
}
|
|
408
|
+
/* eslint-enable max-lines */
|
|
380
409
|
|
|
381
410
|
export { StandardLogtoClient };
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DefaultJwtVerifier } from './adapter/defaults.js';
|
|
2
2
|
import { StandardLogtoClient } from './client.js';
|
|
3
|
-
export { LogtoError, LogtoRequestError, OidcError, Prompt, ReservedResource, ReservedScope, UserScope, buildOrganizationUrn, getOrganizationIdFromUrn, organizationUrnPrefix } from '@logto/js';
|
|
3
|
+
export { LogtoError, LogtoRequestError, OidcError, Prompt, ReservedResource, ReservedScope, UserScope, buildOrganizationUrn, getOrganizationIdFromUrn, isLogtoRequestError, organizationUrnPrefix } from '@logto/js';
|
|
4
4
|
export { LogtoClientError } from './errors.js';
|
|
5
5
|
import '@silverhand/essentials';
|
|
6
6
|
export { CacheKey, PersistKey } from './adapter/types.js';
|
package/lib/mock.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
/// <reference types="jest" />
|
|
2
1
|
import { type OidcConfigResponse, Prompt } from '@logto/js';
|
|
3
2
|
import { type Nullable } from '@silverhand/essentials';
|
|
3
|
+
import nock from 'nock';
|
|
4
|
+
import { type Mock } from 'vitest';
|
|
4
5
|
import type { Storage } from './adapter/index.js';
|
|
5
6
|
import type { AccessToken, LogtoConfig, LogtoSignInSessionItem } from './index.js';
|
|
6
7
|
import LogtoClient from './index.js';
|
|
@@ -14,57 +15,43 @@ export declare class MockedStorage implements Storage<string> {
|
|
|
14
15
|
removeItem(key: string): Promise<void>;
|
|
15
16
|
reset(values: Record<string, string>): void;
|
|
16
17
|
}
|
|
17
|
-
export declare const authorizationEndpoint
|
|
18
|
-
export declare const userinfoEndpoint
|
|
19
|
-
export declare const tokenEndpoint
|
|
20
|
-
export declare const endSessionEndpoint
|
|
21
|
-
export declare const revocationEndpoint
|
|
22
|
-
export declare const jwksUri
|
|
18
|
+
export declare const authorizationEndpoint = "https://logto.dev/oidc/auth";
|
|
19
|
+
export declare const userinfoEndpoint = "https://logto.dev/oidc/me";
|
|
20
|
+
export declare const tokenEndpoint = "https://logto.dev/oidc/token";
|
|
21
|
+
export declare const endSessionEndpoint = "https://logto.dev/oidc/session/end";
|
|
22
|
+
export declare const revocationEndpoint = "https://logto.dev/oidc/token/revocation";
|
|
23
|
+
export declare const jwksUri = "https://logto.dev/oidc/jwks";
|
|
23
24
|
export declare const issuer = "http://localhost:443/oidc";
|
|
24
25
|
export declare const redirectUri = "http://localhost:3000/callback";
|
|
25
26
|
export declare const postSignOutRedirectUri = "http://localhost:3000";
|
|
26
27
|
export declare const mockCodeChallenge = "code_challenge_value";
|
|
27
28
|
export declare const mockedCodeVerifier = "code_verifier_value";
|
|
28
29
|
export declare const mockedState = "state_value";
|
|
30
|
+
export declare const mockedUserHint = "johndoe@example.com";
|
|
29
31
|
export declare const mockedSignInUri: string;
|
|
30
32
|
export declare const mockedSignInUriWithLoginPrompt: string;
|
|
31
33
|
export declare const mockedSignUpUri: string;
|
|
34
|
+
export declare const mockedSignInUriWithLoginHint: string;
|
|
32
35
|
export declare const accessToken = "access_token_value";
|
|
33
36
|
export declare const refreshToken = "new_refresh_token_value";
|
|
34
37
|
export declare const idToken = "id_token_value";
|
|
35
38
|
export declare const currentUnixTimeStamp: number;
|
|
36
|
-
export declare const mockFetchOidcConfig: (delay?: number) =>
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}>, [], any>;
|
|
45
|
-
export declare const fetchOidcConfig: jest.Mock<Promise<{
|
|
46
|
-
authorizationEndpoint: string;
|
|
47
|
-
tokenEndpoint: string;
|
|
48
|
-
userinfoEndpoint: string;
|
|
49
|
-
endSessionEndpoint: string;
|
|
50
|
-
revocationEndpoint: string;
|
|
51
|
-
jwksUri: string;
|
|
52
|
-
issuer: string;
|
|
53
|
-
}>, [], any>;
|
|
54
|
-
export declare const requester: jest.Mock<any, any, any>;
|
|
55
|
-
export declare const failingRequester: jest.Mock<any, any, any>;
|
|
56
|
-
export declare const navigate: jest.Mock<any, any, any>;
|
|
57
|
-
export declare const generateCodeChallenge: jest.Mock<Promise<string>, [], any>;
|
|
58
|
-
export declare const generateCodeVerifier: jest.Mock<string, [], any>;
|
|
59
|
-
export declare const generateState: jest.Mock<string, [], any>;
|
|
39
|
+
export declare const mockFetchOidcConfig: (delay?: number) => Mock<() => Promise<OidcConfigResponse>>;
|
|
40
|
+
export declare const fetchOidcConfig: Mock<() => Promise<OidcConfigResponse>>;
|
|
41
|
+
export declare const requester: Mock<(...args: any[]) => any>;
|
|
42
|
+
export declare const failingRequester: Mock<(...args: any[]) => any>;
|
|
43
|
+
export declare const navigate: Mock<(...args: any[]) => any>;
|
|
44
|
+
export declare const generateCodeChallenge: Mock<() => Promise<string>>;
|
|
45
|
+
export declare const generateCodeVerifier: Mock<() => string>;
|
|
46
|
+
export declare const generateState: Mock<() => string>;
|
|
60
47
|
export declare const createAdapters: (withCache?: boolean) => {
|
|
61
|
-
requester:
|
|
48
|
+
requester: Mock<(...args: any[]) => any>;
|
|
62
49
|
storage: MockedStorage;
|
|
63
50
|
unstable_cache: import("@silverhand/essentials").Optional<MockedStorage>;
|
|
64
|
-
navigate:
|
|
65
|
-
generateCodeChallenge:
|
|
66
|
-
generateCodeVerifier:
|
|
67
|
-
generateState:
|
|
51
|
+
navigate: Mock<(...args: any[]) => any>;
|
|
52
|
+
generateCodeChallenge: Mock<() => Promise<string>>;
|
|
53
|
+
generateCodeVerifier: Mock<() => string>;
|
|
54
|
+
generateState: Mock<() => string>;
|
|
68
55
|
};
|
|
69
56
|
export declare const createClient: (prompt?: Prompt, storage?: MockedStorage, withCache?: boolean, scopes?: string[]) => LogtoClientWithAccessors;
|
|
70
57
|
/**
|
|
@@ -77,3 +64,4 @@ export declare class LogtoClientWithAccessors extends LogtoClient {
|
|
|
77
64
|
setSignInSessionItem(item: Nullable<LogtoSignInSessionItem>): Promise<void>;
|
|
78
65
|
getAccessTokenMap(): Map<string, AccessToken>;
|
|
79
66
|
}
|
|
67
|
+
export declare const nocked: nock.Scope;
|
package/lib/shim.d.ts
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* JWT verifier. It can avoid the use of `jose` package which is useful for certain environments
|
|
4
4
|
* that don't support native modules like `crypto`. (e.g. React Native)
|
|
5
5
|
*/
|
|
6
|
-
export type { IdTokenClaims, LogtoErrorCode, UserInfoResponse, InteractionMode } from '@logto/js';
|
|
7
|
-
export { LogtoError, LogtoRequestError, OidcError, Prompt, ReservedScope, ReservedResource, UserScope, organizationUrnPrefix, buildOrganizationUrn, getOrganizationIdFromUrn, } from '@logto/js';
|
|
6
|
+
export type { AccessTokenClaims, IdTokenClaims, LogtoErrorCode, UserInfoResponse, InteractionMode, } from '@logto/js';
|
|
7
|
+
export { LogtoError, LogtoRequestError, OidcError, Prompt, ReservedScope, ReservedResource, UserScope, organizationUrnPrefix, buildOrganizationUrn, getOrganizationIdFromUrn, isLogtoRequestError, } from '@logto/js';
|
|
8
8
|
export * from './errors.js';
|
|
9
|
-
export type { Storage, StorageKey, ClientAdapter } from './adapter/index.js';
|
|
9
|
+
export type { Storage, StorageKey, ClientAdapter, JwtVerifier } from './adapter/index.js';
|
|
10
10
|
export { PersistKey, CacheKey } from './adapter/index.js';
|
|
11
11
|
export { createRequester } from './utils/index.js';
|
|
12
12
|
export * from './types/index.js';
|
package/lib/shim.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { LogtoError, LogtoRequestError, OidcError, Prompt, ReservedResource, ReservedScope, UserScope, buildOrganizationUrn, getOrganizationIdFromUrn, organizationUrnPrefix } from '@logto/js';
|
|
1
|
+
export { LogtoError, LogtoRequestError, OidcError, Prompt, ReservedResource, ReservedScope, UserScope, buildOrganizationUrn, getOrganizationIdFromUrn, isLogtoRequestError, organizationUrnPrefix } from '@logto/js';
|
|
2
2
|
export { LogtoClientError } from './errors.js';
|
|
3
3
|
import '@silverhand/essentials';
|
|
4
4
|
export { CacheKey, PersistKey } from './adapter/types.js';
|
package/lib/types/index.d.ts
CHANGED
|
@@ -39,12 +39,20 @@ export type LogtoConfig = {
|
|
|
39
39
|
* The prompt parameter to be used for the authorization request.
|
|
40
40
|
*/
|
|
41
41
|
prompt?: Prompt | Prompt[];
|
|
42
|
+
/**
|
|
43
|
+
* Whether to include reserved scopes (`openid`, `offline_access` and `profile`) in the scopes.
|
|
44
|
+
*
|
|
45
|
+
* @default true
|
|
46
|
+
*/
|
|
47
|
+
includeReservedScopes?: boolean;
|
|
42
48
|
};
|
|
43
49
|
/**
|
|
44
50
|
* Normalize the Logto client configuration per the following rules:
|
|
45
51
|
*
|
|
46
|
-
* - Add default scopes (`openid`, `offline_access` and `profile`) if not provided
|
|
47
|
-
*
|
|
52
|
+
* - Add default scopes (`openid`, `offline_access` and `profile`) if not provided and
|
|
53
|
+
* `includeReservedScopes` is `true`.
|
|
54
|
+
* - Add {@link ReservedResource.Organization} to resources if {@link UserScope.Organizations} is
|
|
55
|
+
* included in scopes.
|
|
48
56
|
*
|
|
49
57
|
* @param config The Logto client configuration to be normalized.
|
|
50
58
|
* @returns The normalized Logto client configuration.
|
|
@@ -62,6 +70,7 @@ export declare const isLogtoSignInSessionItem: (data: unknown) => data is LogtoS
|
|
|
62
70
|
export declare const isLogtoAccessTokenMap: (data: unknown) => data is Record<string, AccessToken>;
|
|
63
71
|
export type LogtoSignInSessionItem = {
|
|
64
72
|
redirectUri: string;
|
|
73
|
+
postRedirectUri?: string;
|
|
65
74
|
codeVerifier: string;
|
|
66
75
|
state: string;
|
|
67
76
|
};
|
package/lib/types/index.js
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
|
-
import { Prompt,
|
|
1
|
+
import { Prompt, withReservedScopes, UserScope, ReservedResource, isArbitraryObject } from '@logto/js';
|
|
2
2
|
import { deduplicate } from '@silverhand/essentials';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Normalize the Logto client configuration per the following rules:
|
|
6
6
|
*
|
|
7
|
-
* - Add default scopes (`openid`, `offline_access` and `profile`) if not provided
|
|
8
|
-
*
|
|
7
|
+
* - Add default scopes (`openid`, `offline_access` and `profile`) if not provided and
|
|
8
|
+
* `includeReservedScopes` is `true`.
|
|
9
|
+
* - Add {@link ReservedResource.Organization} to resources if {@link UserScope.Organizations} is
|
|
10
|
+
* included in scopes.
|
|
9
11
|
*
|
|
10
12
|
* @param config The Logto client configuration to be normalized.
|
|
11
13
|
* @returns The normalized Logto client configuration.
|
|
12
14
|
*/
|
|
13
15
|
const normalizeLogtoConfig = (config) => {
|
|
14
16
|
const { prompt = Prompt.Consent, scopes = [], resources, ...rest } = config;
|
|
17
|
+
const includeReservedScopes = config.includeReservedScopes ?? true;
|
|
15
18
|
return {
|
|
16
19
|
...rest,
|
|
17
20
|
prompt,
|
|
18
|
-
scopes:
|
|
21
|
+
scopes: includeReservedScopes ? withReservedScopes(scopes).split(' ') : scopes,
|
|
19
22
|
resources: scopes.includes(UserScope.Organizations)
|
|
20
23
|
? deduplicate([...(resources ?? []), ReservedResource.Organization])
|
|
21
24
|
: resources,
|
package/lib/utils/memoize.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
function memoize(run) {
|
|
2
2
|
const promiseCache = new Map();
|
|
3
3
|
const memoized = async function (...args) {
|
|
4
|
-
const promiseKey = args
|
|
4
|
+
const promiseKey = JSON.stringify(args);
|
|
5
5
|
const cachedPromise = promiseCache.get(promiseKey);
|
|
6
6
|
if (cachedPromise) {
|
|
7
7
|
return cachedPromise;
|
package/lib/utils/requester.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isLogtoRequestErrorJson, LogtoError, LogtoRequestError } from '@logto/js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* A factory function that creates a requester by accepting a `fetch`-like function.
|
|
@@ -11,14 +11,15 @@ const createRequester = (fetchFunction) => {
|
|
|
11
11
|
return async (...args) => {
|
|
12
12
|
const response = await fetchFunction(...args);
|
|
13
13
|
if (!response.ok) {
|
|
14
|
+
const cloned = response.clone();
|
|
14
15
|
const responseJson = await response.json();
|
|
15
16
|
console.error(`Logto requester error: [status=${response.status}]`, responseJson);
|
|
16
|
-
if (!
|
|
17
|
+
if (!isLogtoRequestErrorJson(responseJson)) {
|
|
17
18
|
throw new LogtoError('unexpected_response_error', responseJson);
|
|
18
19
|
}
|
|
19
20
|
// Expected request error from server
|
|
20
21
|
const { code, message } = responseJson;
|
|
21
|
-
throw new LogtoRequestError(code, message);
|
|
22
|
+
throw new LogtoRequestError(code, message, cloned);
|
|
22
23
|
}
|
|
23
24
|
return response.json();
|
|
24
25
|
};
|