@logto/browser 0.1.2-rc.1 → 0.1.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/lib/index.d.ts +22 -10
- package/lib/index.js +60 -31
- package/package.json +10 -3
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IdTokenClaims,
|
|
1
|
+
import { IdTokenClaims, Requester, UserInfoResponse } from '@logto/js';
|
|
2
2
|
import { Nullable } from '@silverhand/essentials';
|
|
3
3
|
import { Infer } from 'superstruct';
|
|
4
4
|
export type { IdTokenClaims, UserInfoResponse } from '@logto/js';
|
|
@@ -26,29 +26,41 @@ export declare const LogtoSignInSessionItemSchema: import("superstruct").Struct<
|
|
|
26
26
|
}>;
|
|
27
27
|
export declare type LogtoSignInSessionItem = Infer<typeof LogtoSignInSessionItemSchema>;
|
|
28
28
|
export default class LogtoClient {
|
|
29
|
-
protected logtoConfig: LogtoConfig;
|
|
30
|
-
protected
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
protected readonly logtoConfig: LogtoConfig;
|
|
30
|
+
protected readonly getOidcConfig: () => Promise<import("@silverhand/essentials").KeysToCamelCase<{
|
|
31
|
+
authorization_endpoint: string;
|
|
32
|
+
token_endpoint: string;
|
|
33
|
+
userinfo_endpoint: string;
|
|
34
|
+
end_session_endpoint: string;
|
|
35
|
+
revocation_endpoint: string;
|
|
36
|
+
jwks_uri: string;
|
|
37
|
+
issuer: string;
|
|
38
|
+
}>>;
|
|
39
|
+
protected readonly getJwtVerifyGetKey: () => Promise<import("jose/dist/types/types").GetKeyFunction<import("jose").JWSHeaderParameters, import("jose").FlattenedJWSInput>>;
|
|
40
|
+
protected readonly logtoStorageKey: string;
|
|
41
|
+
protected readonly requester: Requester;
|
|
42
|
+
protected readonly accessTokenMap: Map<string, AccessToken>;
|
|
43
|
+
private readonly getAccessTokenPromiseMap;
|
|
34
44
|
private _refreshToken;
|
|
35
45
|
private _idToken;
|
|
36
46
|
constructor(logtoConfig: LogtoConfig, requester?: <T>(input: RequestInfo, init?: RequestInit | undefined) => Promise<T>);
|
|
37
47
|
get isAuthenticated(): boolean;
|
|
38
48
|
protected get signInSession(): Nullable<LogtoSignInSessionItem>;
|
|
39
49
|
protected set signInSession(logtoSignInSessionItem: Nullable<LogtoSignInSessionItem>);
|
|
40
|
-
|
|
50
|
+
get refreshToken(): Nullable<string>;
|
|
41
51
|
private set refreshToken(value);
|
|
42
|
-
|
|
52
|
+
get idToken(): Nullable<string>;
|
|
43
53
|
private set idToken(value);
|
|
44
|
-
getAccessToken(resource?: string): Promise<
|
|
54
|
+
getAccessToken(resource?: string): Promise<string>;
|
|
45
55
|
getIdTokenClaims(): IdTokenClaims;
|
|
46
56
|
fetchUserInfo(): Promise<UserInfoResponse>;
|
|
47
57
|
signIn(redirectUri: string): Promise<void>;
|
|
48
58
|
isSignInRedirected(url: string): boolean;
|
|
49
59
|
handleSignInCallback(callbackUri: string): Promise<void>;
|
|
50
60
|
signOut(postLogoutRedirectUri?: string): Promise<void>;
|
|
51
|
-
private
|
|
61
|
+
private getAccessTokenByRefreshToken;
|
|
62
|
+
private _getOidcConfig;
|
|
63
|
+
private _getJwtVerifyGetKey;
|
|
52
64
|
private verifyIdToken;
|
|
53
65
|
private saveCodeToken;
|
|
54
66
|
}
|
package/lib/index.js
CHANGED
|
@@ -9,10 +9,14 @@ var __createBinding = (this && this.__createBinding) || (Object.create ? (functi
|
|
|
9
9
|
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
10
10
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
11
11
|
};
|
|
12
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
13
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
14
|
+
};
|
|
12
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
16
|
exports.LogtoSignInSessionItemSchema = void 0;
|
|
14
17
|
const js_1 = require("@logto/js");
|
|
15
18
|
const jose_1 = require("jose");
|
|
19
|
+
const lodash_once_1 = __importDefault(require("lodash.once"));
|
|
16
20
|
const superstruct_1 = require("superstruct");
|
|
17
21
|
const errors_1 = require("./errors");
|
|
18
22
|
const utils_1 = require("./utils");
|
|
@@ -24,7 +28,10 @@ exports.LogtoSignInSessionItemSchema = (0, superstruct_1.type)({
|
|
|
24
28
|
});
|
|
25
29
|
class LogtoClient {
|
|
26
30
|
constructor(logtoConfig, requester = (0, js_1.createRequester)()) {
|
|
31
|
+
this.getOidcConfig = (0, lodash_once_1.default)(this._getOidcConfig);
|
|
32
|
+
this.getJwtVerifyGetKey = (0, lodash_once_1.default)(this._getJwtVerifyGetKey);
|
|
27
33
|
this.accessTokenMap = new Map();
|
|
34
|
+
this.getAccessTokenPromiseMap = new Map();
|
|
28
35
|
this.logtoConfig = logtoConfig;
|
|
29
36
|
this.logtoStorageKey = (0, utils_1.buildLogtoKey)(logtoConfig.clientId);
|
|
30
37
|
this.requester = requester;
|
|
@@ -89,33 +96,28 @@ class LogtoClient {
|
|
|
89
96
|
if (accessToken && accessToken.expiresAt > Date.now() / 1000) {
|
|
90
97
|
return accessToken.token;
|
|
91
98
|
}
|
|
92
|
-
//
|
|
99
|
+
// Since the access token has expired, delete it from the map.
|
|
93
100
|
if (accessToken) {
|
|
94
101
|
this.accessTokenMap.delete(accessTokenKey);
|
|
95
102
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const { accessToken, refreshToken, idToken, scope, expiresIn } = await (0, js_1.fetchTokenByRefreshToken)({ clientId, tokenEndpoint, refreshToken: this.refreshToken, resource }, this.requester);
|
|
104
|
-
this.accessTokenMap.set(accessTokenKey, {
|
|
105
|
-
token: accessToken,
|
|
106
|
-
scope,
|
|
107
|
-
expiresAt: Math.round(Date.now() / 1000) + expiresIn,
|
|
108
|
-
});
|
|
109
|
-
this.refreshToken = refreshToken;
|
|
110
|
-
if (idToken) {
|
|
111
|
-
await this.verifyIdToken(idToken);
|
|
112
|
-
this.idToken = idToken;
|
|
113
|
-
}
|
|
114
|
-
return accessToken;
|
|
115
|
-
}
|
|
116
|
-
catch (error) {
|
|
117
|
-
throw new errors_1.LogtoClientError('get_access_token_by_refresh_token_failed', error);
|
|
103
|
+
/**
|
|
104
|
+
* Need to fetch a new access token using refresh token.
|
|
105
|
+
* Reuse the cached promise if exists.
|
|
106
|
+
*/
|
|
107
|
+
const cachedPromise = this.getAccessTokenPromiseMap.get(accessTokenKey);
|
|
108
|
+
if (cachedPromise) {
|
|
109
|
+
return cachedPromise;
|
|
118
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Create a new promise and cache in map to avoid race condition.
|
|
113
|
+
* Since we enable "refresh token rotation" by default,
|
|
114
|
+
* it will be problematic when calling multiple `getAccessToken()` closely.
|
|
115
|
+
*/
|
|
116
|
+
const promise = this.getAccessTokenByRefreshToken(resource);
|
|
117
|
+
this.getAccessTokenPromiseMap.set(accessTokenKey, promise);
|
|
118
|
+
const token = await promise;
|
|
119
|
+
this.getAccessTokenPromiseMap.delete(accessTokenKey);
|
|
120
|
+
return token;
|
|
119
121
|
}
|
|
120
122
|
getIdTokenClaims() {
|
|
121
123
|
if (!this.idToken) {
|
|
@@ -202,19 +204,46 @@ class LogtoClient {
|
|
|
202
204
|
this.idToken = null;
|
|
203
205
|
window.location.assign(url);
|
|
204
206
|
}
|
|
205
|
-
async
|
|
206
|
-
if (!this.
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
207
|
+
async getAccessTokenByRefreshToken(resource) {
|
|
208
|
+
if (!this.refreshToken) {
|
|
209
|
+
throw new errors_1.LogtoClientError('not_authenticated');
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
const accessTokenKey = (0, utils_1.buildAccessTokenKey)(resource);
|
|
213
|
+
const { clientId } = this.logtoConfig;
|
|
214
|
+
const { tokenEndpoint } = await this.getOidcConfig();
|
|
215
|
+
const { accessToken, refreshToken, idToken, scope, expiresIn } = await (0, js_1.fetchTokenByRefreshToken)({ clientId, tokenEndpoint, refreshToken: this.refreshToken, resource }, this.requester);
|
|
216
|
+
this.accessTokenMap.set(accessTokenKey, {
|
|
217
|
+
token: accessToken,
|
|
218
|
+
scope,
|
|
219
|
+
expiresAt: Math.round(Date.now() / 1000) + expiresIn,
|
|
220
|
+
});
|
|
221
|
+
this.refreshToken = refreshToken;
|
|
222
|
+
if (idToken) {
|
|
223
|
+
await this.verifyIdToken(idToken);
|
|
224
|
+
this.idToken = idToken;
|
|
225
|
+
}
|
|
226
|
+
return accessToken;
|
|
210
227
|
}
|
|
211
|
-
|
|
228
|
+
catch (error) {
|
|
229
|
+
throw new errors_1.LogtoClientError('get_access_token_by_refresh_token_failed', error);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
async _getOidcConfig() {
|
|
233
|
+
const { endpoint } = this.logtoConfig;
|
|
234
|
+
const discoveryEndpoint = (0, utils_1.getDiscoveryEndpoint)(endpoint);
|
|
235
|
+
return (0, js_1.fetchOidcConfig)(discoveryEndpoint, this.requester);
|
|
236
|
+
}
|
|
237
|
+
async _getJwtVerifyGetKey() {
|
|
238
|
+
const { jwksUri } = await this.getOidcConfig();
|
|
239
|
+
return (0, jose_1.createRemoteJWKSet)(new URL(jwksUri));
|
|
212
240
|
}
|
|
213
241
|
async verifyIdToken(idToken) {
|
|
214
242
|
const { clientId } = this.logtoConfig;
|
|
215
|
-
const { issuer
|
|
243
|
+
const { issuer } = await this.getOidcConfig();
|
|
244
|
+
const jwtVerifyGetKey = await this.getJwtVerifyGetKey();
|
|
216
245
|
try {
|
|
217
|
-
await (0, js_1.verifyIdToken)(idToken, clientId, issuer,
|
|
246
|
+
await (0, js_1.verifyIdToken)(idToken, clientId, issuer, jwtVerifyGetKey);
|
|
218
247
|
}
|
|
219
248
|
catch (error) {
|
|
220
249
|
throw new errors_1.LogtoClientError('invalid_id_token', error);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logto/browser",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"main": "./lib/index.js",
|
|
5
5
|
"exports": "./lib/index.js",
|
|
6
6
|
"typings": "./lib/index.d.ts",
|
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
"lib"
|
|
9
9
|
],
|
|
10
10
|
"license": "MIT",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/logto-io/js.git",
|
|
14
|
+
"directory": "packages/browser"
|
|
15
|
+
},
|
|
11
16
|
"scripts": {
|
|
12
17
|
"dev:tsc": "tsc -p tsconfig.build.json -w --preserveWatchOutput",
|
|
13
18
|
"preinstall": "npx only-allow pnpm",
|
|
@@ -19,10 +24,11 @@
|
|
|
19
24
|
"prepack": "pnpm test && pnpm build"
|
|
20
25
|
},
|
|
21
26
|
"dependencies": {
|
|
22
|
-
"@logto/js": "^0.1.
|
|
27
|
+
"@logto/js": "^0.1.3",
|
|
23
28
|
"@silverhand/essentials": "^1.1.6",
|
|
24
29
|
"jose": "^4.5.0",
|
|
25
30
|
"lodash.get": "^4.4.2",
|
|
31
|
+
"lodash.once": "^4.1.1",
|
|
26
32
|
"superstruct": "^0.15.3"
|
|
27
33
|
},
|
|
28
34
|
"devDependencies": {
|
|
@@ -31,6 +37,7 @@
|
|
|
31
37
|
"@silverhand/ts-config": "^0.9.1",
|
|
32
38
|
"@types/jest": "^27.4.0",
|
|
33
39
|
"@types/lodash.get": "^4.4.6",
|
|
40
|
+
"@types/lodash.once": "^4.1.6",
|
|
34
41
|
"eslint": "^8.9.0",
|
|
35
42
|
"jest": "^27.5.1",
|
|
36
43
|
"jest-location-mock": "^1.0.9",
|
|
@@ -48,5 +55,5 @@
|
|
|
48
55
|
"publishConfig": {
|
|
49
56
|
"access": "public"
|
|
50
57
|
},
|
|
51
|
-
"gitHead": "
|
|
58
|
+
"gitHead": "a5eed81a3e3db3184a53f04f843db8ac707dd3ad"
|
|
52
59
|
}
|