zavadil-ts-common 1.1.48 → 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.
@@ -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
  }
@@ -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 = {
@@ -1,4 +1,4 @@
1
- import {AccessTokenPayload, IdTokenPayload, OAuthRestClient, RequestIdTokenFromLoginPayload} from "./OAuthRestClient";
1
+ import {AccessTokenPayload, IdTokenPayload, OAuthRestClient} from "./OAuthRestClient";
2
2
  import {EventManager} from "../component";
3
3
 
4
4
  /**
@@ -14,14 +14,15 @@ export class OAuthTokenManager {
14
14
 
15
15
  idToken?: IdTokenPayload;
16
16
 
17
- accessToken?: AccessTokenPayload;
17
+ accessTokens: Map<string, AccessTokenPayload>;
18
18
 
19
19
  constructor(oAuthServerBaseUrl: string, targetAudience: string) {
20
20
  this.audience = targetAudience;
21
21
  this.oAuthServer = new OAuthRestClient(oAuthServerBaseUrl);
22
+ this.accessTokens = new Map<string, AccessTokenPayload>();
22
23
  }
23
24
 
24
- addIdTokenChangedHandler(handler: () => any) {
25
+ addIdTokenChangedHandler(handler: (t: IdTokenPayload) => any) {
25
26
  this.eventManager.addEventListener('id-token-changed', handler);
26
27
  }
27
28
 
@@ -51,13 +52,13 @@ export class OAuthTokenManager {
51
52
  return accessToken !== undefined && !this.isTokenExpired(accessToken.expires);
52
53
  }
53
54
 
54
- hasValidAccessToken(): boolean {
55
- return this.isValidAccessToken(this.accessToken);
55
+ hasValidAccessToken(privilege: string): boolean {
56
+ return this.isValidAccessToken(this.accessTokens.get(privilege));
56
57
  }
57
58
 
58
59
  reset() {
59
60
  this.setIdToken(undefined);
60
- this.accessToken = undefined;
61
+ this.accessTokens.clear();
61
62
  }
62
63
 
63
64
  getIdToken(): Promise<string> {
@@ -82,7 +83,7 @@ export class OAuthTokenManager {
82
83
  throw new Error("Received ID token is not valid!");
83
84
  }
84
85
  this.idToken = token;
85
- this.eventManager.triggerEvent('id-token-changed');
86
+ this.eventManager.triggerEvent('id-token-changed', token);
86
87
  }
87
88
 
88
89
  verifyIdToken(token: string): Promise<boolean> {
@@ -101,23 +102,27 @@ export class OAuthTokenManager {
101
102
  })
102
103
  }
103
104
 
104
- getAccessToken(): Promise<string> {
105
- if (this.hasValidAccessToken()) {
106
- return Promise.resolve(String(this.accessToken?.accessToken));
107
- } else {
108
- return this.getIdToken()
109
- .then(
110
- (idToken: string) => this.oAuthServer
111
- .requestAccessToken({idToken: idToken, targetAudience: this.audience})
112
- .then((act: AccessTokenPayload) => {
113
- if (!this.isValidAccessToken(act)) {
114
- return Promise.reject("Received access token is not valid!");
115
- }
116
- this.accessToken = act;
117
- return act.accessToken;
118
- })
119
- );
120
- }
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);
121
126
  }
122
127
 
123
128
  }
@@ -1,6 +1,7 @@
1
1
  import {OAuthTokenManager} from "./OAuthTokenManager";
2
- import { RestClient, RestClientHeaders } from "../client";
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,39 +12,87 @@ export type ServerOAuthInfoPayload = {
11
12
 
12
13
  export class RestClientWithOAuth extends RestClient {
13
14
 
14
- private tokenManager?: OAuthTokenManager;
15
+ private insecureClient: RestClient;
15
16
 
16
- private serverInfo?: ServerOAuthInfoPayload;
17
+ private tokenManager: LazyAsync<OAuthTokenManager>;
17
18
 
18
- private insecureClient: RestClient;
19
+ private serverInfo: LazyAsync<ServerOAuthInfoPayload>;
19
20
 
20
- constructor(url: string) {
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);
29
+
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);
40
+ }
41
+ }
42
+
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;
67
+ }
68
+ localStorage.setItem('id-token', raw);
24
69
  }
25
70
 
26
71
  addIdTokenChangedHandler(handler: () => any) {
27
72
  this.getTokenManager().then((m) => m.addIdTokenChangedHandler(handler));
28
73
  }
29
74
 
75
+ private getServerInfoInternal(): Promise<ServerOAuthInfoPayload> {
76
+ return this.insecureClient.getJson('status/info');
77
+ }
78
+
30
79
  getServerInfo(): Promise<ServerOAuthInfoPayload> {
31
- if (this.serverInfo !== undefined) {
32
- return Promise.resolve(this.serverInfo);
33
- }
34
- return this.insecureClient.getJson('status/info')
35
- .then((si: ServerOAuthInfoPayload) => {
36
- this.serverInfo = si;
37
- return this.serverInfo;
38
- });
80
+ return this.serverInfo.get();
39
81
  }
40
82
 
41
- getTokenManager(): Promise<OAuthTokenManager> {
42
- if (this.tokenManager !== undefined) {
43
- return Promise.resolve(this.tokenManager);
44
- }
83
+ private getTokenManagerInternal(): Promise<OAuthTokenManager> {
45
84
  return this.getServerInfo()
46
- .then((info) => new OAuthTokenManager(info.oauthServerUrl, info.targetAudience));
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();
47
96
  }
48
97
 
49
98
  login(login: string, password: string): Promise<boolean> {
@@ -61,9 +110,9 @@ export class RestClientWithOAuth extends RestClient {
61
110
  .then(m => m.verifyIdToken(token))
62
111
  }
63
112
 
64
- getHeaders(): Promise<RestClientHeaders> {
113
+ getHeaders(url: string): Promise<RestClientHeaders> {
65
114
  return this.getTokenManager()
66
- .then(sm => sm.getAccessToken())
115
+ .then(tm => tm.getAccessToken(this.getPrivilege(url)))
67
116
  .then(
68
117
  (accessToken) => {
69
118
  return {