@logto/client 1.1.1 → 2.0.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/adapter.d.ts +17 -0
- package/lib/errors.cjs +17 -0
- package/lib/errors.d.ts +13 -0
- package/lib/errors.js +15 -0
- package/lib/index.cjs +285 -0
- package/lib/index.d.ts +19 -56
- package/lib/index.js +142 -231
- package/lib/index.test.d.ts +1 -0
- package/lib/mock.d.ts +68 -0
- package/lib/types/index.cjs +26 -0
- package/lib/types/index.d.ts +21 -0
- package/lib/types/index.js +23 -0
- package/lib/utils/index.cjs +9 -0
- package/lib/utils/index.d.ts +3 -0
- package/lib/utils/index.js +6 -0
- package/lib/utils/index.test.d.ts +1 -0
- package/lib/utils/once.cjs +20 -0
- package/lib/utils/once.d.ts +3 -0
- package/lib/utils/once.js +18 -0
- package/lib/utils/requester.cjs +21 -0
- package/lib/utils/requester.d.ts +2 -0
- package/lib/utils/requester.js +19 -0
- package/lib/utils/requester.test.d.ts +1 -0
- package/package.json +32 -40
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/module.d.mts +0 -76
- package/lib/module.mjs +0 -309
- package/lib/module.mjs.map +0 -1
package/lib/index.js
CHANGED
|
@@ -1,285 +1,194 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
Object.defineProperty(dest, key, {
|
|
19
|
-
enumerable: true,
|
|
20
|
-
get: function get() {
|
|
21
|
-
return source[key];
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
return dest;
|
|
27
|
-
}
|
|
28
|
-
function $parcel$export(e, n, v, s) {
|
|
29
|
-
Object.defineProperty(e, n, {get: v, set: s, enumerable: true, configurable: true});
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
$parcel$defineInteropFlag(module.exports);
|
|
33
|
-
|
|
34
|
-
$parcel$export(module.exports, "default", () => $f73788ae50447ce9$export$2e2bcd8739ae039);
|
|
35
|
-
$parcel$export(module.exports, "LogtoError", () => $f73788ae50447ce9$re_export$LogtoError);
|
|
36
|
-
$parcel$export(module.exports, "OidcError", () => $f73788ae50447ce9$re_export$OidcError);
|
|
37
|
-
$parcel$export(module.exports, "Prompt", () => $4R6L3$logtojs.Prompt);
|
|
38
|
-
$parcel$export(module.exports, "LogtoRequestError", () => $f73788ae50447ce9$re_export$LogtoRequestError);
|
|
39
|
-
$parcel$export(module.exports, "ReservedScope", () => $f73788ae50447ce9$re_export$ReservedScope);
|
|
40
|
-
$parcel$export(module.exports, "UserScope", () => $f73788ae50447ce9$re_export$UserScope);
|
|
41
|
-
$parcel$export(module.exports, "createRequester", () => $b455f57f80fbf6bf$export$8d54726fdbf08e0a);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
var $9166104b36889c59$exports = {};
|
|
46
|
-
|
|
47
|
-
$parcel$export($9166104b36889c59$exports, "LogtoClientError", () => $9166104b36889c59$export$877962ca249b8fc8);
|
|
48
|
-
|
|
49
|
-
const $9166104b36889c59$var$logtoClientErrorCodes = Object.freeze({
|
|
50
|
-
sign_in_session: {
|
|
51
|
-
invalid: "Invalid sign-in session.",
|
|
52
|
-
not_found: "Sign-in session not found."
|
|
53
|
-
},
|
|
54
|
-
not_authenticated: "Not authenticated.",
|
|
55
|
-
fetch_user_info_failed: "Unable to fetch user info. The access token may be invalid."
|
|
56
|
-
});
|
|
57
|
-
const $9166104b36889c59$var$getMessageByErrorCode = (errorCode)=>{
|
|
58
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
59
|
-
const message = (0, ($parcel$interopDefault($4R6L3$lodashget)))($9166104b36889c59$var$logtoClientErrorCodes, errorCode);
|
|
60
|
-
if (typeof message === "string") return message;
|
|
61
|
-
return errorCode;
|
|
62
|
-
};
|
|
63
|
-
class $9166104b36889c59$export$877962ca249b8fc8 extends Error {
|
|
64
|
-
constructor(code, data){
|
|
65
|
-
super($9166104b36889c59$var$getMessageByErrorCode(code));
|
|
66
|
-
this.code = code;
|
|
67
|
-
this.data = data;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
var $6d3989f7f53311af$exports = {};
|
|
73
|
-
|
|
74
|
-
$parcel$export($6d3989f7f53311af$exports, "isLogtoSignInSessionItem", () => $6d3989f7f53311af$export$5d8adf6e063019de);
|
|
75
|
-
$parcel$export($6d3989f7f53311af$exports, "isLogtoAccessTokenMap", () => $6d3989f7f53311af$export$c12fab42a9a3e2a6);
|
|
76
|
-
|
|
77
|
-
const $6d3989f7f53311af$export$5d8adf6e063019de = (data)=>{
|
|
78
|
-
if (!(0, $4R6L3$logtojs.isArbitraryObject)(data)) return false;
|
|
79
|
-
return [
|
|
80
|
-
"redirectUri",
|
|
81
|
-
"codeVerifier",
|
|
82
|
-
"state"
|
|
83
|
-
].every((key)=>typeof data[key] === "string");
|
|
84
|
-
};
|
|
85
|
-
const $6d3989f7f53311af$export$c12fab42a9a3e2a6 = (data)=>{
|
|
86
|
-
if (!(0, $4R6L3$logtojs.isArbitraryObject)(data)) return false;
|
|
87
|
-
return Object.values(data).every((value)=>{
|
|
88
|
-
if (!(0, $4R6L3$logtojs.isArbitraryObject)(value)) return false;
|
|
89
|
-
return typeof value.token === "string" && typeof value.scope === "string" && typeof value.expiresAt === "number";
|
|
90
|
-
});
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const $b455f57f80fbf6bf$export$8d54726fdbf08e0a = (fetchFunction)=>{
|
|
97
|
-
return async (...args)=>{
|
|
98
|
-
const response = await fetchFunction(...args);
|
|
99
|
-
if (!response.ok) {
|
|
100
|
-
const responseJson = await response.json();
|
|
101
|
-
if (!(0, $4R6L3$logtojs.isLogtoRequestError)(responseJson)) throw new (0, $4R6L3$logtojs.LogtoError)("unexpected_response_error", responseJson);
|
|
102
|
-
// Expected request error from server
|
|
103
|
-
const { code: code , message: message } = responseJson;
|
|
104
|
-
throw new (0, $4R6L3$logtojs.LogtoRequestError)(code, message);
|
|
105
|
-
}
|
|
106
|
-
return response.json();
|
|
107
|
-
};
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const $e2aabdbdb3cc09f0$export$8f595bd2a47bcea6 = (resource = "", scopes = [])=>`${scopes.slice().sort().join(" ")}@${resource}`;
|
|
112
|
-
const $e2aabdbdb3cc09f0$export$5d9c34f69c80822b = (endpoint)=>new URL((0, $4R6L3$logtojs.discoveryPath), endpoint).toString();
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
class $f73788ae50447ce9$export$2e2bcd8739ae039 {
|
|
120
|
-
getOidcConfig = (0, ($parcel$interopDefault($4R6L3$lodashonce)))(this._getOidcConfig);
|
|
121
|
-
getJwtVerifyGetKey = (0, ($parcel$interopDefault($4R6L3$lodashonce)))(this._getJwtVerifyGetKey);
|
|
122
|
-
accessTokenMap = new Map();
|
|
123
|
-
constructor(logtoConfig, adapter){
|
|
1
|
+
import { Prompt, withDefaultScopes, decodeIdToken, fetchUserInfo, generateSignInUri, verifyAndParseCodeFromCallbackUri, fetchTokenByAuthorizationCode, revoke, generateSignOutUri, fetchTokenByRefreshToken, fetchOidcConfig, verifyIdToken } from '@logto/js';
|
|
2
|
+
export { LogtoError, LogtoRequestError, OidcError, Prompt, ReservedScope, UserScope } from '@logto/js';
|
|
3
|
+
import { createRemoteJWKSet } from 'jose';
|
|
4
|
+
import { LogtoClientError } from './errors.js';
|
|
5
|
+
import { isLogtoSignInSessionItem, isLogtoAccessTokenMap } from './types/index.js';
|
|
6
|
+
import { buildAccessTokenKey, getDiscoveryEndpoint } from './utils/index.js';
|
|
7
|
+
import { once } from './utils/once.js';
|
|
8
|
+
export { createRequester } from './utils/requester.js';
|
|
9
|
+
|
|
10
|
+
class LogtoClient {
|
|
11
|
+
constructor(logtoConfig, adapter) {
|
|
12
|
+
this.getOidcConfig = once(this._getOidcConfig);
|
|
13
|
+
this.getJwtVerifyGetKey = once(this._getJwtVerifyGetKey);
|
|
14
|
+
this.accessTokenMap = new Map();
|
|
124
15
|
this.logtoConfig = {
|
|
125
16
|
...logtoConfig,
|
|
126
|
-
prompt: logtoConfig.prompt ??
|
|
127
|
-
scopes:
|
|
17
|
+
prompt: logtoConfig.prompt ?? Prompt.Consent,
|
|
18
|
+
scopes: withDefaultScopes(logtoConfig.scopes).split(' '),
|
|
128
19
|
};
|
|
129
20
|
this.adapter = adapter;
|
|
130
|
-
this.loadAccessTokenMap();
|
|
21
|
+
void this.loadAccessTokenMap();
|
|
131
22
|
}
|
|
132
23
|
async isAuthenticated() {
|
|
133
24
|
return Boolean(await this.getIdToken());
|
|
134
25
|
}
|
|
135
26
|
async getRefreshToken() {
|
|
136
|
-
return this.adapter.storage.getItem(
|
|
27
|
+
return this.adapter.storage.getItem('refreshToken');
|
|
137
28
|
}
|
|
138
29
|
async getIdToken() {
|
|
139
|
-
return this.adapter.storage.getItem(
|
|
30
|
+
return this.adapter.storage.getItem('idToken');
|
|
140
31
|
}
|
|
141
32
|
async getAccessToken(resource) {
|
|
142
|
-
if (!await this.getIdToken())
|
|
143
|
-
|
|
33
|
+
if (!(await this.getIdToken())) {
|
|
34
|
+
throw new LogtoClientError('not_authenticated');
|
|
35
|
+
}
|
|
36
|
+
const accessTokenKey = buildAccessTokenKey(resource);
|
|
144
37
|
const accessToken = this.accessTokenMap.get(accessTokenKey);
|
|
145
|
-
if (accessToken && accessToken.expiresAt > Date.now() / 1000)
|
|
38
|
+
if (accessToken && accessToken.expiresAt > Date.now() / 1000) {
|
|
39
|
+
return accessToken.token;
|
|
40
|
+
}
|
|
146
41
|
// Since the access token has expired, delete it from the map.
|
|
147
|
-
if (accessToken)
|
|
42
|
+
if (accessToken) {
|
|
43
|
+
this.accessTokenMap.delete(accessTokenKey);
|
|
44
|
+
}
|
|
148
45
|
/**
|
|
149
|
-
|
|
150
|
-
|
|
46
|
+
* Need to fetch a new access token using refresh token.
|
|
47
|
+
*/
|
|
48
|
+
return this.getAccessTokenByRefreshToken(resource);
|
|
151
49
|
}
|
|
152
50
|
async getIdTokenClaims() {
|
|
153
51
|
const idToken = await this.getIdToken();
|
|
154
|
-
if (!idToken)
|
|
155
|
-
|
|
52
|
+
if (!idToken) {
|
|
53
|
+
throw new LogtoClientError('not_authenticated');
|
|
54
|
+
}
|
|
55
|
+
return decodeIdToken(idToken);
|
|
156
56
|
}
|
|
157
57
|
async fetchUserInfo() {
|
|
158
|
-
const { userinfoEndpoint
|
|
58
|
+
const { userinfoEndpoint } = await this.getOidcConfig();
|
|
159
59
|
const accessToken = await this.getAccessToken();
|
|
160
|
-
if (!accessToken)
|
|
161
|
-
|
|
60
|
+
if (!accessToken) {
|
|
61
|
+
throw new LogtoClientError('fetch_user_info_failed');
|
|
62
|
+
}
|
|
63
|
+
return fetchUserInfo(userinfoEndpoint, accessToken, this.adapter.requester);
|
|
162
64
|
}
|
|
163
65
|
async signIn(redirectUri, interactionMode) {
|
|
164
|
-
const { appId: clientId
|
|
165
|
-
const { authorizationEndpoint
|
|
66
|
+
const { appId: clientId, prompt, resources, scopes } = this.logtoConfig;
|
|
67
|
+
const { authorizationEndpoint } = await this.getOidcConfig();
|
|
166
68
|
const codeVerifier = this.adapter.generateCodeVerifier();
|
|
167
69
|
const codeChallenge = await this.adapter.generateCodeChallenge(codeVerifier);
|
|
168
70
|
const state = this.adapter.generateState();
|
|
169
|
-
const signInUri =
|
|
170
|
-
authorizationEndpoint
|
|
171
|
-
clientId
|
|
172
|
-
redirectUri
|
|
173
|
-
codeChallenge
|
|
174
|
-
state
|
|
175
|
-
scopes
|
|
176
|
-
resources
|
|
177
|
-
prompt
|
|
178
|
-
interactionMode
|
|
179
|
-
});
|
|
180
|
-
await this.setSignInSession({
|
|
181
|
-
redirectUri: redirectUri,
|
|
182
|
-
codeVerifier: codeVerifier,
|
|
183
|
-
state: state
|
|
71
|
+
const signInUri = generateSignInUri({
|
|
72
|
+
authorizationEndpoint,
|
|
73
|
+
clientId,
|
|
74
|
+
redirectUri,
|
|
75
|
+
codeChallenge,
|
|
76
|
+
state,
|
|
77
|
+
scopes,
|
|
78
|
+
resources,
|
|
79
|
+
prompt,
|
|
80
|
+
interactionMode,
|
|
184
81
|
});
|
|
82
|
+
await this.setSignInSession({ redirectUri, codeVerifier, state });
|
|
185
83
|
await this.setRefreshToken(null);
|
|
186
84
|
await this.setIdToken(null);
|
|
187
85
|
this.adapter.navigate(signInUri);
|
|
188
86
|
}
|
|
189
87
|
async isSignInRedirected(url) {
|
|
190
88
|
const signInSession = await this.getSignInSession();
|
|
191
|
-
if (!signInSession)
|
|
192
|
-
|
|
193
|
-
|
|
89
|
+
if (!signInSession) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
const { redirectUri } = signInSession;
|
|
93
|
+
const { origin, pathname } = new URL(url);
|
|
194
94
|
return `${origin}${pathname}` === redirectUri;
|
|
195
95
|
}
|
|
196
96
|
async handleSignInCallback(callbackUri) {
|
|
197
|
-
const { logtoConfig
|
|
198
|
-
const { requester
|
|
97
|
+
const { logtoConfig, adapter } = this;
|
|
98
|
+
const { requester } = adapter;
|
|
199
99
|
const signInSession = await this.getSignInSession();
|
|
200
|
-
if (!signInSession)
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const {
|
|
204
|
-
const
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
100
|
+
if (!signInSession) {
|
|
101
|
+
throw new LogtoClientError('sign_in_session.not_found');
|
|
102
|
+
}
|
|
103
|
+
const { redirectUri, state, codeVerifier } = signInSession;
|
|
104
|
+
const code = verifyAndParseCodeFromCallbackUri(callbackUri, redirectUri, state);
|
|
105
|
+
const { appId: clientId } = logtoConfig;
|
|
106
|
+
const { tokenEndpoint } = await this.getOidcConfig();
|
|
107
|
+
const codeTokenResponse = await fetchTokenByAuthorizationCode({
|
|
108
|
+
clientId,
|
|
109
|
+
tokenEndpoint,
|
|
110
|
+
redirectUri,
|
|
111
|
+
codeVerifier,
|
|
112
|
+
code,
|
|
211
113
|
}, requester);
|
|
212
114
|
await this.verifyIdToken(codeTokenResponse.idToken);
|
|
213
115
|
await this.saveCodeToken(codeTokenResponse);
|
|
214
116
|
await this.setSignInSession(null);
|
|
215
117
|
}
|
|
216
118
|
async signOut(postLogoutRedirectUri) {
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
const { appId: clientId } = this.logtoConfig;
|
|
220
|
-
const { endSessionEndpoint: endSessionEndpoint , revocationEndpoint: revocationEndpoint } = await this.getOidcConfig();
|
|
119
|
+
const { appId: clientId } = this.logtoConfig;
|
|
120
|
+
const { endSessionEndpoint, revocationEndpoint } = await this.getOidcConfig();
|
|
221
121
|
const refreshToken = await this.getRefreshToken();
|
|
222
|
-
if (refreshToken)
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
122
|
+
if (refreshToken) {
|
|
123
|
+
try {
|
|
124
|
+
await revoke(revocationEndpoint, clientId, refreshToken, this.adapter.requester);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// Do nothing at this point, as we don't want to break the sign-out flow even if the revocation is failed
|
|
128
|
+
}
|
|
226
129
|
}
|
|
227
|
-
const url =
|
|
228
|
-
endSessionEndpoint
|
|
229
|
-
postLogoutRedirectUri
|
|
230
|
-
clientId
|
|
130
|
+
const url = generateSignOutUri({
|
|
131
|
+
endSessionEndpoint,
|
|
132
|
+
postLogoutRedirectUri,
|
|
133
|
+
clientId,
|
|
231
134
|
});
|
|
232
135
|
this.accessTokenMap.clear();
|
|
233
136
|
await this.setRefreshToken(null);
|
|
234
137
|
await this.setIdToken(null);
|
|
235
|
-
await this.adapter.storage.removeItem(
|
|
138
|
+
await this.adapter.storage.removeItem('accessToken');
|
|
236
139
|
this.adapter.navigate(url);
|
|
237
140
|
}
|
|
238
141
|
async getSignInSession() {
|
|
239
|
-
const jsonItem = await this.adapter.storage.getItem(
|
|
240
|
-
if (!jsonItem)
|
|
142
|
+
const jsonItem = await this.adapter.storage.getItem('signInSession');
|
|
143
|
+
if (!jsonItem) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
241
146
|
const item = JSON.parse(jsonItem);
|
|
242
|
-
if (!(
|
|
147
|
+
if (!isLogtoSignInSessionItem(item)) {
|
|
148
|
+
throw new LogtoClientError('sign_in_session.invalid');
|
|
149
|
+
}
|
|
243
150
|
return item;
|
|
244
151
|
}
|
|
245
152
|
async setSignInSession(logtoSignInSessionItem) {
|
|
246
153
|
if (!logtoSignInSessionItem) {
|
|
247
|
-
await this.adapter.storage.removeItem(
|
|
154
|
+
await this.adapter.storage.removeItem('signInSession');
|
|
248
155
|
return;
|
|
249
156
|
}
|
|
250
157
|
const jsonItem = JSON.stringify(logtoSignInSessionItem);
|
|
251
|
-
await this.adapter.storage.setItem(
|
|
158
|
+
await this.adapter.storage.setItem('signInSession', jsonItem);
|
|
252
159
|
}
|
|
253
160
|
async setIdToken(idToken) {
|
|
254
161
|
if (!idToken) {
|
|
255
|
-
await this.adapter.storage.removeItem(
|
|
162
|
+
await this.adapter.storage.removeItem('idToken');
|
|
256
163
|
return;
|
|
257
164
|
}
|
|
258
|
-
await this.adapter.storage.setItem(
|
|
165
|
+
await this.adapter.storage.setItem('idToken', idToken);
|
|
259
166
|
}
|
|
260
167
|
async setRefreshToken(refreshToken) {
|
|
261
168
|
if (!refreshToken) {
|
|
262
|
-
await this.adapter.storage.removeItem(
|
|
169
|
+
await this.adapter.storage.removeItem('refreshToken');
|
|
263
170
|
return;
|
|
264
171
|
}
|
|
265
|
-
await this.adapter.storage.setItem(
|
|
172
|
+
await this.adapter.storage.setItem('refreshToken', refreshToken);
|
|
266
173
|
}
|
|
267
174
|
async getAccessTokenByRefreshToken(resource) {
|
|
268
175
|
const currentRefreshToken = await this.getRefreshToken();
|
|
269
|
-
if (!currentRefreshToken)
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
const
|
|
273
|
-
const {
|
|
274
|
-
|
|
275
|
-
|
|
176
|
+
if (!currentRefreshToken) {
|
|
177
|
+
throw new LogtoClientError('not_authenticated');
|
|
178
|
+
}
|
|
179
|
+
const accessTokenKey = buildAccessTokenKey(resource);
|
|
180
|
+
const { appId: clientId } = this.logtoConfig;
|
|
181
|
+
const { tokenEndpoint } = await this.getOidcConfig();
|
|
182
|
+
const { accessToken, refreshToken, idToken, scope, expiresIn } = await fetchTokenByRefreshToken({
|
|
183
|
+
clientId,
|
|
184
|
+
tokenEndpoint,
|
|
276
185
|
refreshToken: currentRefreshToken,
|
|
277
|
-
resource
|
|
186
|
+
resource,
|
|
278
187
|
}, this.adapter.requester);
|
|
279
188
|
this.accessTokenMap.set(accessTokenKey, {
|
|
280
189
|
token: accessToken,
|
|
281
|
-
scope
|
|
282
|
-
expiresAt: Math.round(Date.now() / 1000) + expiresIn
|
|
190
|
+
scope,
|
|
191
|
+
expiresAt: Math.round(Date.now() / 1000) + expiresIn,
|
|
283
192
|
});
|
|
284
193
|
await this.saveAccessTokenMap();
|
|
285
194
|
await this.setRefreshToken(refreshToken);
|
|
@@ -290,54 +199,56 @@ class $f73788ae50447ce9$export$2e2bcd8739ae039 {
|
|
|
290
199
|
return accessToken;
|
|
291
200
|
}
|
|
292
201
|
async _getOidcConfig() {
|
|
293
|
-
const { endpoint
|
|
294
|
-
const discoveryEndpoint = (
|
|
295
|
-
return
|
|
202
|
+
const { endpoint } = this.logtoConfig;
|
|
203
|
+
const discoveryEndpoint = getDiscoveryEndpoint(endpoint);
|
|
204
|
+
return fetchOidcConfig(discoveryEndpoint, this.adapter.requester);
|
|
296
205
|
}
|
|
297
206
|
async _getJwtVerifyGetKey() {
|
|
298
|
-
const { jwksUri
|
|
299
|
-
return
|
|
207
|
+
const { jwksUri } = await this.getOidcConfig();
|
|
208
|
+
return createRemoteJWKSet(new URL(jwksUri));
|
|
300
209
|
}
|
|
301
210
|
async verifyIdToken(idToken) {
|
|
302
|
-
const { appId
|
|
303
|
-
const { issuer
|
|
211
|
+
const { appId } = this.logtoConfig;
|
|
212
|
+
const { issuer } = await this.getOidcConfig();
|
|
304
213
|
const jwtVerifyGetKey = await this.getJwtVerifyGetKey();
|
|
305
|
-
await
|
|
214
|
+
await verifyIdToken(idToken, appId, issuer, jwtVerifyGetKey);
|
|
306
215
|
}
|
|
307
|
-
async saveCodeToken({ refreshToken
|
|
216
|
+
async saveCodeToken({ refreshToken, idToken, scope, accessToken, expiresIn, }) {
|
|
308
217
|
await this.setRefreshToken(refreshToken ?? null);
|
|
309
218
|
await this.setIdToken(idToken);
|
|
310
219
|
// NOTE: Will add scope to accessTokenKey when needed. (Linear issue LOG-1589)
|
|
311
|
-
const accessTokenKey = (
|
|
220
|
+
const accessTokenKey = buildAccessTokenKey();
|
|
312
221
|
const expiresAt = Date.now() / 1000 + expiresIn;
|
|
313
|
-
this.accessTokenMap.set(accessTokenKey, {
|
|
314
|
-
token: accessToken,
|
|
315
|
-
scope: scope,
|
|
316
|
-
expiresAt: expiresAt
|
|
317
|
-
});
|
|
222
|
+
this.accessTokenMap.set(accessTokenKey, { token: accessToken, scope, expiresAt });
|
|
318
223
|
await this.saveAccessTokenMap();
|
|
319
224
|
}
|
|
320
225
|
async saveAccessTokenMap() {
|
|
321
226
|
const data = {};
|
|
322
|
-
for (const [key, accessToken] of this.accessTokenMap.entries())
|
|
323
|
-
|
|
324
|
-
|
|
227
|
+
for (const [key, accessToken] of this.accessTokenMap.entries()) {
|
|
228
|
+
// eslint-disable-next-line @silverhand/fp/no-mutation
|
|
229
|
+
data[key] = accessToken;
|
|
230
|
+
}
|
|
231
|
+
await this.adapter.storage.setItem('accessToken', JSON.stringify(data));
|
|
325
232
|
}
|
|
326
233
|
async loadAccessTokenMap() {
|
|
327
|
-
const raw = await this.adapter.storage.getItem(
|
|
328
|
-
if (!raw)
|
|
234
|
+
const raw = await this.adapter.storage.getItem('accessToken');
|
|
235
|
+
if (!raw) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
329
238
|
try {
|
|
330
239
|
const json = JSON.parse(raw);
|
|
331
|
-
if (!(
|
|
240
|
+
if (!isLogtoAccessTokenMap(json)) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
332
243
|
this.accessTokenMap.clear();
|
|
333
|
-
for (const [key, accessToken] of Object.entries(json))
|
|
334
|
-
|
|
244
|
+
for (const [key, accessToken] of Object.entries(json)) {
|
|
245
|
+
this.accessTokenMap.set(key, accessToken);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
335
249
|
console.warn(error);
|
|
336
250
|
}
|
|
337
251
|
}
|
|
338
252
|
}
|
|
339
|
-
$parcel$exportWildcard(module.exports, $9166104b36889c59$exports);
|
|
340
|
-
$parcel$exportWildcard(module.exports, $6d3989f7f53311af$exports);
|
|
341
|
-
|
|
342
253
|
|
|
343
|
-
|
|
254
|
+
export { LogtoClientError, LogtoClient as default, isLogtoAccessTokenMap, isLogtoSignInSessionItem };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/lib/mock.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/// <reference types="jest" />
|
|
2
|
+
import { Prompt } from '@logto/js';
|
|
3
|
+
import type { Nullable } from '@silverhand/essentials';
|
|
4
|
+
import type { Storage } from './adapter.js';
|
|
5
|
+
import type { AccessToken, LogtoConfig, LogtoSignInSessionItem } from './index.js';
|
|
6
|
+
import LogtoClient from './index.js';
|
|
7
|
+
export declare const appId = "app_id_value";
|
|
8
|
+
export declare const endpoint = "https://logto.dev";
|
|
9
|
+
export declare class MockedStorage implements Storage {
|
|
10
|
+
private storage;
|
|
11
|
+
constructor(values?: Record<string, string>);
|
|
12
|
+
getItem(key: string): Promise<string | null>;
|
|
13
|
+
setItem(key: string, value: string): Promise<void>;
|
|
14
|
+
removeItem(key: string): Promise<void>;
|
|
15
|
+
reset(values: Record<string, string>): void;
|
|
16
|
+
}
|
|
17
|
+
export declare const authorizationEndpoint: string;
|
|
18
|
+
export declare const userinfoEndpoint: string;
|
|
19
|
+
export declare const tokenEndpoint: string;
|
|
20
|
+
export declare const endSessionEndpoint: string;
|
|
21
|
+
export declare const revocationEndpoint: string;
|
|
22
|
+
export declare const jwksUri: string;
|
|
23
|
+
export declare const issuer = "http://localhost:443/oidc";
|
|
24
|
+
export declare const redirectUri = "http://localhost:3000/callback";
|
|
25
|
+
export declare const postSignOutRedirectUri = "http://localhost:3000";
|
|
26
|
+
export declare const mockCodeChallenge = "code_challenge_value";
|
|
27
|
+
export declare const mockedCodeVerifier = "code_verifier_value";
|
|
28
|
+
export declare const mockedState = "state_value";
|
|
29
|
+
export declare const mockedSignInUri: string;
|
|
30
|
+
export declare const mockedSignInUriWithLoginPrompt: string;
|
|
31
|
+
export declare const mockedSignUpUri: string;
|
|
32
|
+
export declare const accessToken = "access_token_value";
|
|
33
|
+
export declare const refreshToken = "new_refresh_token_value";
|
|
34
|
+
export declare const idToken = "id_token_value";
|
|
35
|
+
export declare const currentUnixTimeStamp: number;
|
|
36
|
+
export declare const fetchOidcConfig: jest.Mock<Promise<{
|
|
37
|
+
authorizationEndpoint: string;
|
|
38
|
+
tokenEndpoint: string;
|
|
39
|
+
userinfoEndpoint: string;
|
|
40
|
+
endSessionEndpoint: string;
|
|
41
|
+
revocationEndpoint: string;
|
|
42
|
+
jwksUri: string;
|
|
43
|
+
issuer: string;
|
|
44
|
+
}>, [], any>;
|
|
45
|
+
export declare const requester: jest.Mock<any, any, any>;
|
|
46
|
+
export declare const failingRequester: jest.Mock<any, any, any>;
|
|
47
|
+
export declare const navigate: jest.Mock<any, any, any>;
|
|
48
|
+
export declare const generateCodeChallenge: jest.Mock<Promise<string>, [], any>;
|
|
49
|
+
export declare const generateCodeVerifier: jest.Mock<string, [], any>;
|
|
50
|
+
export declare const generateState: jest.Mock<string, [], any>;
|
|
51
|
+
export declare const createAdapters: () => {
|
|
52
|
+
requester: jest.Mock<any, any, any>;
|
|
53
|
+
storage: MockedStorage;
|
|
54
|
+
navigate: jest.Mock<any, any, any>;
|
|
55
|
+
generateCodeChallenge: jest.Mock<Promise<string>, [], any>;
|
|
56
|
+
generateCodeVerifier: jest.Mock<string, [], any>;
|
|
57
|
+
generateState: jest.Mock<string, [], any>;
|
|
58
|
+
};
|
|
59
|
+
export declare const createClient: (prompt?: Prompt, storage?: MockedStorage) => LogtoClient;
|
|
60
|
+
/**
|
|
61
|
+
* Make LogtoClient.signInSession accessible for test
|
|
62
|
+
*/
|
|
63
|
+
export declare class LogtoClientSignInSessionAccessor extends LogtoClient {
|
|
64
|
+
getLogtoConfig(): Nullable<LogtoConfig>;
|
|
65
|
+
getSignInSessionItem(): Promise<Nullable<LogtoSignInSessionItem>>;
|
|
66
|
+
setSignInSessionItem(item: Nullable<LogtoSignInSessionItem>): Promise<void>;
|
|
67
|
+
getAccessTokenMap(): Map<string, AccessToken>;
|
|
68
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var js = require('@logto/js');
|
|
4
|
+
|
|
5
|
+
const isLogtoSignInSessionItem = (data) => {
|
|
6
|
+
if (!js.isArbitraryObject(data)) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
return ['redirectUri', 'codeVerifier', 'state'].every((key) => typeof data[key] === 'string');
|
|
10
|
+
};
|
|
11
|
+
const isLogtoAccessTokenMap = (data) => {
|
|
12
|
+
if (!js.isArbitraryObject(data)) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
return Object.values(data).every((value) => {
|
|
16
|
+
if (!js.isArbitraryObject(value)) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
return (typeof value.token === 'string' &&
|
|
20
|
+
typeof value.scope === 'string' &&
|
|
21
|
+
typeof value.expiresAt === 'number');
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
exports.isLogtoAccessTokenMap = isLogtoAccessTokenMap;
|
|
26
|
+
exports.isLogtoSignInSessionItem = isLogtoSignInSessionItem;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Prompt } from '@logto/js';
|
|
2
|
+
export type LogtoConfig = {
|
|
3
|
+
endpoint: string;
|
|
4
|
+
appId: string;
|
|
5
|
+
appSecret?: string;
|
|
6
|
+
scopes?: string[];
|
|
7
|
+
resources?: string[];
|
|
8
|
+
prompt?: Prompt;
|
|
9
|
+
};
|
|
10
|
+
export type AccessToken = {
|
|
11
|
+
token: string;
|
|
12
|
+
scope: string;
|
|
13
|
+
expiresAt: number;
|
|
14
|
+
};
|
|
15
|
+
export declare const isLogtoSignInSessionItem: (data: unknown) => data is LogtoSignInSessionItem;
|
|
16
|
+
export declare const isLogtoAccessTokenMap: (data: unknown) => data is Record<string, AccessToken>;
|
|
17
|
+
export type LogtoSignInSessionItem = {
|
|
18
|
+
redirectUri: string;
|
|
19
|
+
codeVerifier: string;
|
|
20
|
+
state: string;
|
|
21
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { isArbitraryObject } from '@logto/js';
|
|
2
|
+
|
|
3
|
+
const isLogtoSignInSessionItem = (data) => {
|
|
4
|
+
if (!isArbitraryObject(data)) {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
return ['redirectUri', 'codeVerifier', 'state'].every((key) => typeof data[key] === 'string');
|
|
8
|
+
};
|
|
9
|
+
const isLogtoAccessTokenMap = (data) => {
|
|
10
|
+
if (!isArbitraryObject(data)) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
return Object.values(data).every((value) => {
|
|
14
|
+
if (!isArbitraryObject(value)) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
return (typeof value.token === 'string' &&
|
|
18
|
+
typeof value.scope === 'string' &&
|
|
19
|
+
typeof value.expiresAt === 'number');
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export { isLogtoAccessTokenMap, isLogtoSignInSessionItem };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var js = require('@logto/js');
|
|
4
|
+
|
|
5
|
+
const buildAccessTokenKey = (resource = '', scopes = []) => `${scopes.slice().sort().join(' ')}@${resource}`;
|
|
6
|
+
const getDiscoveryEndpoint = (endpoint) => new URL(js.discoveryPath, endpoint).toString();
|
|
7
|
+
|
|
8
|
+
exports.buildAccessTokenKey = buildAccessTokenKey;
|
|
9
|
+
exports.getDiscoveryEndpoint = getDiscoveryEndpoint;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { discoveryPath } from '@logto/js';
|
|
2
|
+
|
|
3
|
+
const buildAccessTokenKey = (resource = '', scopes = []) => `${scopes.slice().sort().join(' ')}@${resource}`;
|
|
4
|
+
const getDiscoveryEndpoint = (endpoint) => new URL(discoveryPath, endpoint).toString();
|
|
5
|
+
|
|
6
|
+
export { buildAccessTokenKey, getDiscoveryEndpoint };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|