zavadil-ts-common 1.1.47 → 1.1.49
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/dist/cache/CacheAsync.d.ts +1 -1
- package/dist/cache/HashCacheAsync.d.ts +4 -3
- package/dist/cache/LazyAsync.d.ts +1 -0
- package/dist/client/RestClient.d.ts +2 -2
- package/dist/index.d.ts +64 -45
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/oauth/OAuthRestClient.d.ts +4 -3
- package/dist/oauth/OAuthTokenManager.d.ts +8 -4
- package/dist/oauth/RestClientWithOAuth.d.ts +17 -4
- package/package.json +1 -1
- package/src/cache/CacheAsync.ts +3 -2
- package/src/cache/HashCacheAsync.ts +4 -4
- package/src/cache/LazyAsync.ts +11 -4
- package/src/client/RestClient.ts +10 -10
- package/src/oauth/OAuthRestClient.ts +7 -3
- package/src/oauth/OAuthTokenManager.ts +44 -28
- package/src/oauth/RestClientWithOAuth.ts +78 -20
package/src/client/RestClient.ts
CHANGED
@@ -18,7 +18,7 @@ export class RestClient {
|
|
18
18
|
/**
|
19
19
|
* Override this to customize http headers.
|
20
20
|
*/
|
21
|
-
getHeaders(): Promise<RestClientHeaders> {
|
21
|
+
getHeaders(url: string): Promise<RestClientHeaders> {
|
22
22
|
return Promise.resolve(
|
23
23
|
{
|
24
24
|
'Content-Type': 'application/json'
|
@@ -46,8 +46,8 @@ export class RestClient {
|
|
46
46
|
return url;
|
47
47
|
}
|
48
48
|
|
49
|
-
getRequestOptions(method: string = 'GET', data: object | null = null): Promise<RequestInit> {
|
50
|
-
return this.getHeaders()
|
49
|
+
getRequestOptions(url: string, method: string = 'GET', data: object | null = null): Promise<RequestInit> {
|
50
|
+
return this.getHeaders(url)
|
51
51
|
.then(
|
52
52
|
(headers) => {
|
53
53
|
return {
|
@@ -116,31 +116,31 @@ export class RestClient {
|
|
116
116
|
if (params) {
|
117
117
|
url = `${url}${this.paramsToQueryString(params)}`;
|
118
118
|
}
|
119
|
-
return this.getRequestOptions().then(o => this.processRequestJson(url, o));
|
119
|
+
return this.getRequestOptions(url).then(o => this.processRequestJson(url, o));
|
120
120
|
}
|
121
121
|
|
122
122
|
postJson(url: string, data: object | null = null): Promise<any> {
|
123
|
-
return this.getRequestOptions('POST', data).then(o => this.processRequestJson(url, o));
|
123
|
+
return this.getRequestOptions(url, 'POST', data).then(o => this.processRequestJson(url, o));
|
124
124
|
}
|
125
125
|
|
126
126
|
putJson(url: string, data: object | null = null): Promise<any> {
|
127
|
-
return this.getRequestOptions('PUT', data).then(o => this.processRequestJson(url, o));
|
127
|
+
return this.getRequestOptions(url, 'PUT', data).then(o => this.processRequestJson(url, o));
|
128
128
|
}
|
129
129
|
|
130
130
|
get(url: string): Promise<Response> {
|
131
|
-
return this.getRequestOptions().then(o => this.processRequest(url, o));
|
131
|
+
return this.getRequestOptions(url).then(o => this.processRequest(url, o));
|
132
132
|
}
|
133
133
|
|
134
134
|
del(url: string): Promise<Response> {
|
135
|
-
return this.getRequestOptions('DELETE').then(o => this.processRequest(url, o));
|
135
|
+
return this.getRequestOptions(url, 'DELETE').then(o => this.processRequest(url, o));
|
136
136
|
}
|
137
137
|
|
138
138
|
post(url: string, data: object | null = null): Promise<Response> {
|
139
|
-
return this.getRequestOptions('POST', data).then(o => this.processRequest(url, o));
|
139
|
+
return this.getRequestOptions(url, 'POST', data).then(o => this.processRequest(url, o));
|
140
140
|
}
|
141
141
|
|
142
142
|
put(url: string, data: object | null = null): Promise<Response> {
|
143
|
-
return this.getRequestOptions('PUT', data).then(o => this.processRequest(url, o));
|
143
|
+
return this.getRequestOptions(url, 'PUT', data).then(o => this.processRequest(url, o));
|
144
144
|
}
|
145
145
|
|
146
146
|
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import {RestClient} from "../client
|
1
|
+
import {RestClient} from "../client";
|
2
2
|
import {StringUtil} from "../util";
|
3
3
|
|
4
4
|
export type TokenRequestPayloadBase = {
|
@@ -7,6 +7,7 @@ export type TokenRequestPayloadBase = {
|
|
7
7
|
|
8
8
|
export type RequestAccessTokenPayload = TokenRequestPayloadBase & {
|
9
9
|
idToken: string;
|
10
|
+
privilege: string;
|
10
11
|
}
|
11
12
|
|
12
13
|
export type RequestIdTokenFromPrevTokenPayload = {
|
@@ -29,7 +30,6 @@ export type IdTokenPayload = TokenResponsePayloadBase & {
|
|
29
30
|
|
30
31
|
export type AccessTokenPayload = TokenResponsePayloadBase & {
|
31
32
|
accessToken: string;
|
32
|
-
refreshToken: string;
|
33
33
|
}
|
34
34
|
|
35
35
|
export type JwKeyPayload = {
|
@@ -45,7 +45,7 @@ export type JwksPayload = {
|
|
45
45
|
|
46
46
|
/**
|
47
47
|
* This implements rest client for OAuth server - https://github.com/lotcz/oauth-server
|
48
|
-
* Provide basic url, /api/oauth prefix will be added automatically
|
48
|
+
* Provide basic url, /api/oauth path prefix will be added automatically
|
49
49
|
*/
|
50
50
|
export class OAuthRestClient extends RestClient {
|
51
51
|
|
@@ -57,6 +57,10 @@ export class OAuthRestClient extends RestClient {
|
|
57
57
|
return this.getJson('jwks.json');
|
58
58
|
}
|
59
59
|
|
60
|
+
verifyIdToken(idToken: string): Promise<IdTokenPayload> {
|
61
|
+
return this.getJson(`id-tokens/verify/${idToken}`);
|
62
|
+
}
|
63
|
+
|
60
64
|
requestIdTokenFromLogin(request: RequestIdTokenFromLoginPayload): Promise<IdTokenPayload> {
|
61
65
|
return this.postJson('id-tokens/from-login', request);
|
62
66
|
}
|
@@ -1,21 +1,29 @@
|
|
1
|
-
import {AccessTokenPayload, IdTokenPayload, OAuthRestClient
|
1
|
+
import {AccessTokenPayload, IdTokenPayload, OAuthRestClient} from "./OAuthRestClient";
|
2
|
+
import {EventManager} from "../component";
|
2
3
|
|
3
4
|
/**
|
4
5
|
* Manages refresh of id and access tokens.
|
5
6
|
*/
|
6
7
|
export class OAuthTokenManager {
|
7
8
|
|
9
|
+
private eventManager: EventManager = new EventManager();
|
10
|
+
|
8
11
|
oAuthServer: OAuthRestClient;
|
9
12
|
|
10
13
|
audience: string;
|
11
14
|
|
12
15
|
idToken?: IdTokenPayload;
|
13
16
|
|
14
|
-
|
17
|
+
accessTokens: Map<string, AccessTokenPayload>;
|
15
18
|
|
16
19
|
constructor(oAuthServerBaseUrl: string, targetAudience: string) {
|
17
20
|
this.audience = targetAudience;
|
18
21
|
this.oAuthServer = new OAuthRestClient(oAuthServerBaseUrl);
|
22
|
+
this.accessTokens = new Map<string, AccessTokenPayload>();
|
23
|
+
}
|
24
|
+
|
25
|
+
addIdTokenChangedHandler(handler: (t: IdTokenPayload) => any) {
|
26
|
+
this.eventManager.addEventListener('id-token-changed', handler);
|
19
27
|
}
|
20
28
|
|
21
29
|
isTokenExpired(expires?: Date | null): boolean {
|
@@ -44,13 +52,13 @@ export class OAuthTokenManager {
|
|
44
52
|
return accessToken !== undefined && !this.isTokenExpired(accessToken.expires);
|
45
53
|
}
|
46
54
|
|
47
|
-
hasValidAccessToken(): boolean {
|
48
|
-
return this.isValidAccessToken(this.
|
55
|
+
hasValidAccessToken(privilege: string): boolean {
|
56
|
+
return this.isValidAccessToken(this.accessTokens.get(privilege));
|
49
57
|
}
|
50
58
|
|
51
59
|
reset() {
|
52
|
-
this.
|
53
|
-
this.
|
60
|
+
this.setIdToken(undefined);
|
61
|
+
this.accessTokens.clear();
|
54
62
|
}
|
55
63
|
|
56
64
|
getIdToken(): Promise<string> {
|
@@ -62,10 +70,7 @@ export class OAuthTokenManager {
|
|
62
70
|
.refreshIdToken({idToken: this.idToken.idToken})
|
63
71
|
.then(
|
64
72
|
(t) => {
|
65
|
-
|
66
|
-
return Promise.reject("Received ID token is not valid!");
|
67
|
-
}
|
68
|
-
this.idToken = t;
|
73
|
+
this.setIdToken(t);
|
69
74
|
return t.idToken;
|
70
75
|
}
|
71
76
|
)
|
@@ -73,11 +78,18 @@ export class OAuthTokenManager {
|
|
73
78
|
return Promise.resolve(this.idToken.idToken);
|
74
79
|
}
|
75
80
|
|
76
|
-
setIdToken(token
|
81
|
+
setIdToken(token?: IdTokenPayload) {
|
77
82
|
if (!this.isValidIdToken(token)) {
|
78
83
|
throw new Error("Received ID token is not valid!");
|
79
84
|
}
|
80
85
|
this.idToken = token;
|
86
|
+
this.eventManager.triggerEvent('id-token-changed', token);
|
87
|
+
}
|
88
|
+
|
89
|
+
verifyIdToken(token: string): Promise<boolean> {
|
90
|
+
return this.oAuthServer.verifyIdToken(token)
|
91
|
+
.then((t) => this.setIdToken(t))
|
92
|
+
.then(() => true)
|
81
93
|
}
|
82
94
|
|
83
95
|
login(login: string, password: string): Promise<boolean> {
|
@@ -90,23 +102,27 @@ export class OAuthTokenManager {
|
|
90
102
|
})
|
91
103
|
}
|
92
104
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
105
|
+
private getAccessTokenInternal(privilege: string): Promise<AccessTokenPayload> {
|
106
|
+
return this.getIdToken()
|
107
|
+
.then(
|
108
|
+
(idToken: string) => this.oAuthServer
|
109
|
+
.requestAccessToken({idToken: idToken, targetAudience: this.audience, privilege: privilege})
|
110
|
+
.then((act: AccessTokenPayload) => {
|
111
|
+
if (!this.isValidAccessToken(act)) {
|
112
|
+
return Promise.reject("Received access token is not valid!");
|
113
|
+
}
|
114
|
+
this.accessTokens.set(privilege, act);
|
115
|
+
return act;
|
116
|
+
})
|
117
|
+
);
|
118
|
+
}
|
119
|
+
|
120
|
+
getAccessToken(privilege: string): Promise<string> {
|
121
|
+
const existing = this.accessTokens.get(privilege);
|
122
|
+
if (existing === undefined || !this.isValidAccessToken(existing)) return this.getAccessTokenInternal(privilege).then((t) => t.accessToken);
|
123
|
+
// preload access token if it is going to expire soon
|
124
|
+
if (this.isTokenReadyForRefresh(existing.issuedAt, existing.expires)) this.getAccessTokenInternal(privilege);
|
125
|
+
return Promise.resolve(existing.accessToken);
|
110
126
|
}
|
111
127
|
|
112
128
|
}
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import {OAuthTokenManager} from "./OAuthTokenManager";
|
2
|
-
import {
|
2
|
+
import {RestClient, RestClientHeaders} from "../client";
|
3
3
|
import {IdTokenPayload} from "./OAuthRestClient";
|
4
|
+
import {LazyAsync} from "../cache";
|
4
5
|
|
5
6
|
export type ServerOAuthInfoPayload = {
|
6
7
|
debugMode?: boolean;
|
@@ -11,35 +12,87 @@ export type ServerOAuthInfoPayload = {
|
|
11
12
|
|
12
13
|
export class RestClientWithOAuth extends RestClient {
|
13
14
|
|
14
|
-
private
|
15
|
+
private insecureClient: RestClient;
|
15
16
|
|
16
|
-
private
|
17
|
+
private tokenManager: LazyAsync<OAuthTokenManager>;
|
17
18
|
|
18
|
-
private
|
19
|
+
private serverInfo: LazyAsync<ServerOAuthInfoPayload>;
|
19
20
|
|
20
|
-
|
21
|
+
private defaultPrivilege: string;
|
22
|
+
|
23
|
+
constructor(url: string, defaultPrivilege: string = '*') {
|
21
24
|
super(url);
|
25
|
+
this.defaultPrivilege = defaultPrivilege;
|
26
|
+
|
22
27
|
// rest client without OAuth headers
|
23
28
|
this.insecureClient = new RestClient(url);
|
24
|
-
}
|
25
29
|
|
26
|
-
|
27
|
-
|
28
|
-
|
30
|
+
this.serverInfo = new LazyAsync<ServerOAuthInfoPayload>(() => this.getServerInfoInternal());
|
31
|
+
this.tokenManager = new LazyAsync<OAuthTokenManager>(() => this.getTokenManagerInternal());
|
32
|
+
|
33
|
+
// initialize
|
34
|
+
const urlToken = this.getIdTokenFromUrl();
|
35
|
+
if (urlToken !== null) {
|
36
|
+
this.setIdTokenRaw(urlToken);
|
37
|
+
} else {
|
38
|
+
const storageToken = this.getIdTokenFromLocalStorage();
|
39
|
+
if (storageToken) this.setIdToken(storageToken);
|
29
40
|
}
|
30
|
-
return this.insecureClient.getJson('status/info')
|
31
|
-
.then((si: ServerOAuthInfoPayload) => {
|
32
|
-
this.serverInfo = si;
|
33
|
-
return this.serverInfo;
|
34
|
-
});
|
35
41
|
}
|
36
42
|
|
37
|
-
|
38
|
-
|
39
|
-
|
43
|
+
/**
|
44
|
+
* Override this if a different privilege is needed for different endpoints
|
45
|
+
* @param url
|
46
|
+
*/
|
47
|
+
getPrivilege(url: string): string {
|
48
|
+
return this.defaultPrivilege;
|
49
|
+
}
|
50
|
+
|
51
|
+
getIdTokenFromUrl(): string | null {
|
52
|
+
const up = new URLSearchParams(document.location.search);
|
53
|
+
return up.get('token');
|
54
|
+
}
|
55
|
+
|
56
|
+
getIdTokenFromLocalStorage(): IdTokenPayload | null {
|
57
|
+
const raw = localStorage.getItem('id-token');
|
58
|
+
if (!raw) return null;
|
59
|
+
return JSON.parse(raw);
|
60
|
+
}
|
61
|
+
|
62
|
+
saveIdTokenToLocalStorage(token: IdTokenPayload | null) {
|
63
|
+
const raw = token ? JSON.stringify(token) : null;
|
64
|
+
if (raw === null) {
|
65
|
+
localStorage.removeItem('id-token');
|
66
|
+
return;
|
40
67
|
}
|
68
|
+
localStorage.setItem('id-token', raw);
|
69
|
+
}
|
70
|
+
|
71
|
+
addIdTokenChangedHandler(handler: () => any) {
|
72
|
+
this.getTokenManager().then((m) => m.addIdTokenChangedHandler(handler));
|
73
|
+
}
|
74
|
+
|
75
|
+
private getServerInfoInternal(): Promise<ServerOAuthInfoPayload> {
|
76
|
+
return this.insecureClient.getJson('status/info');
|
77
|
+
}
|
78
|
+
|
79
|
+
getServerInfo(): Promise<ServerOAuthInfoPayload> {
|
80
|
+
return this.serverInfo.get();
|
81
|
+
}
|
82
|
+
|
83
|
+
private getTokenManagerInternal(): Promise<OAuthTokenManager> {
|
41
84
|
return this.getServerInfo()
|
42
|
-
.then(
|
85
|
+
.then(
|
86
|
+
(info) => {
|
87
|
+
const tm = new OAuthTokenManager(info.oauthServerUrl, info.targetAudience);
|
88
|
+
tm.addIdTokenChangedHandler((t: IdTokenPayload) => this.saveIdTokenToLocalStorage(t));
|
89
|
+
return tm;
|
90
|
+
}
|
91
|
+
);
|
92
|
+
}
|
93
|
+
|
94
|
+
getTokenManager(): Promise<OAuthTokenManager> {
|
95
|
+
return this.tokenManager.get();
|
43
96
|
}
|
44
97
|
|
45
98
|
login(login: string, password: string): Promise<boolean> {
|
@@ -52,9 +105,14 @@ export class RestClientWithOAuth extends RestClient {
|
|
52
105
|
.then(() => true)
|
53
106
|
}
|
54
107
|
|
55
|
-
|
108
|
+
setIdTokenRaw(token: string): Promise<boolean> {
|
109
|
+
return this.getTokenManager()
|
110
|
+
.then(m => m.verifyIdToken(token))
|
111
|
+
}
|
112
|
+
|
113
|
+
getHeaders(url: string): Promise<RestClientHeaders> {
|
56
114
|
return this.getTokenManager()
|
57
|
-
.then(
|
115
|
+
.then(tm => tm.getAccessToken(this.getPrivilege(url)))
|
58
116
|
.then(
|
59
117
|
(accessToken) => {
|
60
118
|
return {
|