@gandalan/weblibs 1.1.52 → 1.1.55
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/api/authUtils.js +60 -0
- package/api/fluentApi.js +211 -137
- package/api/fluentAuthBuilder.js +133 -0
- package/api/fluentRestClient.js +118 -0
- package/index.js +4 -1
- package/jsconfig.json +1 -0
- package/package.json +1 -1
package/api/authUtils.js
CHANGED
|
@@ -2,9 +2,36 @@
|
|
|
2
2
|
import { jwtDecode } from "jwt-decode";
|
|
3
3
|
import validator from "validator";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {Object} Settings
|
|
7
|
+
* @property {string} appToken - The application token.
|
|
8
|
+
* @property {string} mandantGuid - The mandant GUID.
|
|
9
|
+
* @property {string} apiBaseurl - The base URL for the API.
|
|
10
|
+
* @property {string} authUrl - The authentication URL.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* the current JWT token (encoded)
|
|
15
|
+
*
|
|
16
|
+
* @type {string}
|
|
17
|
+
*/
|
|
5
18
|
export let currentToken = undefined;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* the current refresh token (UUID v4)
|
|
22
|
+
*
|
|
23
|
+
* @type {string}
|
|
24
|
+
*/
|
|
6
25
|
export let currentRefreshToken = undefined;
|
|
7
26
|
|
|
27
|
+
/**
|
|
28
|
+
* initializes the API client with the given appToken
|
|
29
|
+
*
|
|
30
|
+
* @export
|
|
31
|
+
* @async
|
|
32
|
+
* @param {string} appToken, UUID v4 format
|
|
33
|
+
* @returns {Settings} settings object
|
|
34
|
+
*/
|
|
8
35
|
export async function initIDAS(appToken)
|
|
9
36
|
{
|
|
10
37
|
if (!validator.isUUID(appToken))
|
|
@@ -59,6 +86,13 @@ export async function initIDAS(appToken)
|
|
|
59
86
|
return settings;
|
|
60
87
|
}
|
|
61
88
|
|
|
89
|
+
/**
|
|
90
|
+
* sets up authentication
|
|
91
|
+
*
|
|
92
|
+
* @export
|
|
93
|
+
* @async
|
|
94
|
+
* @param {Settings} settings
|
|
95
|
+
*/
|
|
62
96
|
export async function setup(settings)
|
|
63
97
|
{
|
|
64
98
|
console.log("Setup IDAS");
|
|
@@ -97,6 +131,12 @@ export async function setup(settings)
|
|
|
97
131
|
console.log("Setup finished", settings);
|
|
98
132
|
}
|
|
99
133
|
|
|
134
|
+
/**
|
|
135
|
+
* starts a timer to refresh the JWT token before it expires
|
|
136
|
+
*
|
|
137
|
+
* @private
|
|
138
|
+
* @type {*}
|
|
139
|
+
*/
|
|
100
140
|
let timerRef = undefined;
|
|
101
141
|
function startRefreshTimer(settings)
|
|
102
142
|
{
|
|
@@ -119,6 +159,13 @@ function startRefreshTimer(settings)
|
|
|
119
159
|
}, 5000);
|
|
120
160
|
}
|
|
121
161
|
|
|
162
|
+
/**
|
|
163
|
+
* checks if the current JWT token is invalid
|
|
164
|
+
*
|
|
165
|
+
* @export
|
|
166
|
+
* @param {Settings} settings
|
|
167
|
+
* @returns {boolean}
|
|
168
|
+
*/
|
|
122
169
|
export function isInvalid(settings)
|
|
123
170
|
{
|
|
124
171
|
if (!currentToken)
|
|
@@ -136,6 +183,13 @@ export function isInvalid(settings)
|
|
|
136
183
|
return true;
|
|
137
184
|
}
|
|
138
185
|
|
|
186
|
+
/**
|
|
187
|
+
* tries to renew the JWT token
|
|
188
|
+
*
|
|
189
|
+
* @export
|
|
190
|
+
* @async
|
|
191
|
+
* @param {Settings} settings
|
|
192
|
+
*/
|
|
139
193
|
export async function tryRenew(settings)
|
|
140
194
|
{
|
|
141
195
|
console.log("Try to refresh");
|
|
@@ -173,6 +227,12 @@ export async function tryRenew(settings)
|
|
|
173
227
|
}
|
|
174
228
|
}
|
|
175
229
|
|
|
230
|
+
/**
|
|
231
|
+
* redirects to the login page
|
|
232
|
+
*
|
|
233
|
+
* @export
|
|
234
|
+
* @param {Settings} settings
|
|
235
|
+
*/
|
|
176
236
|
export function redirectToLogin(settings, authPath)
|
|
177
237
|
{
|
|
178
238
|
const authEndpoint = (new URL(window.location.href).origin) + authPath;
|
package/api/fluentApi.js
CHANGED
|
@@ -1,9 +1,42 @@
|
|
|
1
1
|
import { jwtDecode } from "jwt-decode";
|
|
2
|
-
|
|
2
|
+
import { restClient } from "./fluentRestClient";
|
|
3
|
+
import { authBuilder } from "./fluentAuthBuilder";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {Object} EnvironmentConfig
|
|
7
|
+
* @property {string} name - The environment name.
|
|
8
|
+
* @property {string} version - The version number.
|
|
9
|
+
* @property {string} cms - The CMS URL.
|
|
10
|
+
* @property {string} idas - The IDAS API URL.
|
|
11
|
+
* @property {string} store - The store API URL.
|
|
12
|
+
* @property {string} docs - The documentation URL.
|
|
13
|
+
* @property {string} notify - The notification service URL.
|
|
14
|
+
* @property {string} feedback - The feedback service URL.
|
|
15
|
+
* @property {string} helpcenter - The help center URL.
|
|
16
|
+
* @property {string} reports - The reports service URL.
|
|
17
|
+
* @property {string} webhookService - The webhook service URL.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* buffer for environment data
|
|
22
|
+
* @private
|
|
23
|
+
* @type {Object.<string, EnvironmentConfig>}
|
|
24
|
+
*/
|
|
3
25
|
const envs = {};
|
|
4
26
|
|
|
27
|
+
/**
|
|
28
|
+
* configure the time before token expiry to renew
|
|
29
|
+
* @type {number}
|
|
30
|
+
*/
|
|
5
31
|
const JWT_SAFE_RENEWAL = 30; // seconds before token expiry to renew
|
|
6
32
|
|
|
33
|
+
/**
|
|
34
|
+
* fetches the environment data from the hub
|
|
35
|
+
* @export
|
|
36
|
+
* @async
|
|
37
|
+
* @param {string} [env="", env="dev", env="staging", env="produktiv"]
|
|
38
|
+
* @returns {Promise<EnvironmentConfig>}
|
|
39
|
+
*/
|
|
7
40
|
export async function fetchEnv(env = "") {
|
|
8
41
|
if (!(env in envs)) {
|
|
9
42
|
const hubUrl = `https://connect.idas-cloudservices.net/api/Endpoints?env=${env}`;
|
|
@@ -15,17 +48,37 @@ export async function fetchEnv(env = "") {
|
|
|
15
48
|
return envs[env];
|
|
16
49
|
}
|
|
17
50
|
|
|
51
|
+
/**
|
|
52
|
+
* @typedef {Object} JwtTokenExt
|
|
53
|
+
* @property {string} id
|
|
54
|
+
* @property {string} refreshToken
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* decode the JWT token and return the refresh token
|
|
59
|
+
* @export
|
|
60
|
+
* @param {string} token
|
|
61
|
+
* @returns {string}
|
|
62
|
+
*/
|
|
18
63
|
export function getRefreshToken(token) {
|
|
19
|
-
const decoded = jwtDecode(token);
|
|
64
|
+
const decoded = /** @type {JwtTokenExt} */(jwtDecode(token));
|
|
20
65
|
return decoded.refreshToken;
|
|
21
66
|
}
|
|
22
67
|
|
|
68
|
+
/**
|
|
69
|
+
* check if the token is still valid
|
|
70
|
+
* - checks the expiry date and the JWT_SAFE_RENEWAL buffer
|
|
71
|
+
*
|
|
72
|
+
* @export
|
|
73
|
+
* @param {string} token
|
|
74
|
+
* @returns {boolean}
|
|
75
|
+
*/
|
|
23
76
|
export function isTokenValid(token)
|
|
24
77
|
{
|
|
25
78
|
try
|
|
26
79
|
{
|
|
27
80
|
const decoded = jwtDecode(token);
|
|
28
|
-
if (!decoded)
|
|
81
|
+
if (!decoded || !decoded.exp)
|
|
29
82
|
throw new Error("Invalid token");
|
|
30
83
|
return (decoded.exp - JWT_SAFE_RENEWAL > Date.now() / 1000);
|
|
31
84
|
}
|
|
@@ -34,133 +87,37 @@ export function isTokenValid(token)
|
|
|
34
87
|
}
|
|
35
88
|
}
|
|
36
89
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
this.refreshToken = getRefreshToken(this.token);
|
|
69
|
-
|
|
70
|
-
if (this.refreshToken) {
|
|
71
|
-
try {
|
|
72
|
-
const temptoken = await this.tryRefreshToken(this.refreshToken);
|
|
73
|
-
if (temptoken) {
|
|
74
|
-
this.token = temptoken;
|
|
75
|
-
this.refreshToken = getRefreshToken(temptoken);
|
|
76
|
-
}
|
|
77
|
-
} catch {
|
|
78
|
-
// if refresh failed, we'll return the current token
|
|
79
|
-
// - should still be valid for a while
|
|
80
|
-
}
|
|
81
|
-
return this.token;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (username && password) {
|
|
85
|
-
const payload = { "Email": username, "Password": password, "AppToken": this.appToken };
|
|
86
|
-
const res = await fetch(`${this.authUrl}/LoginJwt`,
|
|
87
|
-
{ method: "POST", body: JSON.stringify(payload), headers: { "Content-Type": "application/json" } });
|
|
88
|
-
const temptoken = await res.json();
|
|
89
|
-
if (temptoken) {
|
|
90
|
-
this.token = temptoken;
|
|
91
|
-
return this.token;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
throw new Error("not authenticated");
|
|
96
|
-
},
|
|
97
|
-
|
|
98
|
-
async tryRefreshToken(refreshToken = "") {
|
|
99
|
-
const payload = { "Token": refreshToken };
|
|
100
|
-
const res = await fetch(`${this.authUrl}/LoginJwt/Refresh`,
|
|
101
|
-
{
|
|
102
|
-
method: "PUT",
|
|
103
|
-
body: JSON.stringify(payload),
|
|
104
|
-
headers: { "Content-Type": "application/json" },
|
|
105
|
-
});
|
|
106
|
-
return res.ok ? await res.json() : null;
|
|
107
|
-
},
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export function restClient()
|
|
112
|
-
{
|
|
113
|
-
return {
|
|
114
|
-
baseUrl: "",
|
|
115
|
-
token: "",
|
|
116
|
-
|
|
117
|
-
useBaseUrl(url = "") {
|
|
118
|
-
this.baseUrl = url; return this;
|
|
119
|
-
},
|
|
120
|
-
|
|
121
|
-
useToken(jwtToken = "") {
|
|
122
|
-
this.token = jwtToken; return this;
|
|
123
|
-
},
|
|
124
|
-
|
|
125
|
-
async get(url = "", auth = true) {
|
|
126
|
-
const finalUrl = `${this.baseUrl}/${url}`;
|
|
127
|
-
const headers = this.token ? { "Authorization": `Bearer ${this.token}` } : {};
|
|
128
|
-
const res = await fetch(finalUrl, { method: "GET", headers });
|
|
129
|
-
if (res.ok)
|
|
130
|
-
return await res.json();
|
|
131
|
-
throw new Error(`GET ${finalUrl} failed: ${res.status} ${res.statusText}`);
|
|
132
|
-
},
|
|
133
|
-
|
|
134
|
-
async put(url = "", payload = {}) {
|
|
135
|
-
const finalUrl = `${this.baseUrl}/${url}`;
|
|
136
|
-
const headers = this.token ? { "Authorization": `Bearer ${this.token}`, "Content-Type": "application/json" } : {};
|
|
137
|
-
const res = await fetch(finalUrl, { method: "PUT", body: JSON.stringify(payload), headers });
|
|
138
|
-
if (res.ok)
|
|
139
|
-
return await res.json();
|
|
140
|
-
throw new Error(`PUT ${finalUrl} failed: ${res.status} ${res.statusText}`);
|
|
141
|
-
},
|
|
142
|
-
|
|
143
|
-
async post(url = "", payload = {}) {
|
|
144
|
-
const finalUrl = `${this.baseUrl}/${url}`;
|
|
145
|
-
const headers = this.token ? { "Authorization": `Bearer ${this.token}`, "Content-Type": "application/json" } : {};
|
|
146
|
-
const res = await fetch(finalUrl, { method: "POST", body: JSON.stringify(payload), headers });
|
|
147
|
-
if (res.ok)
|
|
148
|
-
return await res.json();
|
|
149
|
-
throw new Error(`POST ${finalUrl} failed: ${res.status} ${res.statusText}`);
|
|
150
|
-
},
|
|
151
|
-
|
|
152
|
-
async delete(url = "") {
|
|
153
|
-
const finalUrl = `${this.baseUrl}/${url}`;
|
|
154
|
-
const headers = this.token ? { "Authorization": `Bearer ${this.token}` } : {};
|
|
155
|
-
const res = await fetch(finalUrl, { method: "DELETE", headers });
|
|
156
|
-
if (res.ok)
|
|
157
|
-
return await res.json();
|
|
158
|
-
throw new Error(`DELETE ${finalUrl} failed: ${res.status} ${res.statusText}`);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export function api() {
|
|
90
|
+
/**
|
|
91
|
+
* @typedef {Object} FluentApi
|
|
92
|
+
* @property {string} baseUrl - The base URL for API requests.
|
|
93
|
+
* @property {string} authUrl - The authentication URL.
|
|
94
|
+
* @property {string} env - The environment setting.
|
|
95
|
+
* @property {string} appToken - The application token.
|
|
96
|
+
* @property {string} storageEntry - The storage entry.
|
|
97
|
+
* @property {string} token - The JWT token for authorization.
|
|
98
|
+
* @property {string} refreshToken - The refresh token.
|
|
99
|
+
* @property {function(string) : FluentApi} useEnvironment - Sets the environment and returns the FluentApi object.
|
|
100
|
+
* @property {function(string) : FluentApi} useAppToken - Sets the application token and returns the FluentApi object.
|
|
101
|
+
* @property {function(string) : FluentApi} useBaseUrl - Sets the base URL for API requests and returns the FluentApi object.
|
|
102
|
+
* @property {function(string) : FluentApi} useAuthUrl - Sets the authentication URL and returns the FluentApi object.
|
|
103
|
+
* @property {function(string) : FluentApi} useToken - Sets the JWT token for authorization and returns the FluentApi object.
|
|
104
|
+
* @property {function(string) : FluentApi} useRefreshToken - Sets the refresh token and returns the FluentApi object.
|
|
105
|
+
* @property {function() : FluentApi} useGlobalAuth - Uses global authentication tokens and returns the FluentApi object.
|
|
106
|
+
* @property {function(string) : object|Array<any>} get - Async function to perform GET requests.
|
|
107
|
+
* @property {function(string, object) : object|Array<any>} put - Async function to perform PUT requests with a payload.
|
|
108
|
+
* @property {function(string, object) : object|Array<any>} post - Async function to perform POST requests with a payload.
|
|
109
|
+
* @property {function(string) : object|Array<any>} delete - Async function to perform DELETE requests.
|
|
110
|
+
* @property {Function} ensureAuthenticated - Ensures the user is authenticated before making a request.
|
|
111
|
+
* @property {Function} ensureBaseUrlIsSet - Ensures the base URL is set before making a request.
|
|
112
|
+
* @property {Function} redirectToLogin - Redirects to the login page.
|
|
113
|
+
*/
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Builds a client to communicate with the IDAS api in a fluent syntax
|
|
117
|
+
*
|
|
118
|
+
* @return {FluentApi}
|
|
119
|
+
*/
|
|
120
|
+
export function createApi() {
|
|
164
121
|
return {
|
|
165
122
|
baseUrl: "",
|
|
166
123
|
authUrl: "",
|
|
@@ -170,59 +127,143 @@ export function api() {
|
|
|
170
127
|
token: "",
|
|
171
128
|
refreshToken: "",
|
|
172
129
|
|
|
130
|
+
/**
|
|
131
|
+
* set the environment to use
|
|
132
|
+
*
|
|
133
|
+
* @param {string} env
|
|
134
|
+
* @return {FluentApi}
|
|
135
|
+
*/
|
|
173
136
|
useEnvironment(env = "") {
|
|
174
137
|
this.env = env; return this;
|
|
175
138
|
},
|
|
176
139
|
|
|
140
|
+
/**
|
|
141
|
+
* set the app token to use
|
|
142
|
+
*
|
|
143
|
+
* @param {string} [newApptoken=""]
|
|
144
|
+
* @return {FluentApi}
|
|
145
|
+
*/
|
|
177
146
|
useAppToken(newApptoken = "") {
|
|
178
147
|
this.appToken = newApptoken; return this;
|
|
179
148
|
},
|
|
180
149
|
|
|
150
|
+
/**
|
|
151
|
+
* set the base URL for API requests
|
|
152
|
+
*
|
|
153
|
+
* @param {string} [url=""]
|
|
154
|
+
* @return {FluentApi}
|
|
155
|
+
*/
|
|
181
156
|
useBaseUrl(url = "") {
|
|
182
157
|
this.baseUrl = url; return this;
|
|
183
158
|
},
|
|
184
159
|
|
|
160
|
+
/**
|
|
161
|
+
* set the authentication URL
|
|
162
|
+
*
|
|
163
|
+
* @param {string} [url=""]
|
|
164
|
+
* @return {FluentApi}
|
|
165
|
+
*/
|
|
185
166
|
useAuthUrl(url = "") {
|
|
186
167
|
this.authUrl = url; return this;
|
|
187
168
|
},
|
|
188
169
|
|
|
170
|
+
/**
|
|
171
|
+
* set the JWT token for authorization
|
|
172
|
+
*
|
|
173
|
+
* @param {string} [jwtToken=""]
|
|
174
|
+
* @return {FluentApi}
|
|
175
|
+
*/
|
|
189
176
|
useToken(jwtToken = "") {
|
|
190
177
|
this.token = jwtToken; return this;
|
|
191
178
|
},
|
|
192
179
|
|
|
180
|
+
/**
|
|
181
|
+
* set the refresh token
|
|
182
|
+
*
|
|
183
|
+
* @param {string} [storedRefreshToken=""]
|
|
184
|
+
* @return {FluentApi}
|
|
185
|
+
*/
|
|
193
186
|
useRefreshToken(storedRefreshToken = "") {
|
|
194
187
|
this.refreshToken = storedRefreshToken; return this;
|
|
195
188
|
},
|
|
196
|
-
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* tell the client to use the global authentication tokens
|
|
192
|
+
*
|
|
193
|
+
* @return {FluentApi}
|
|
194
|
+
*/
|
|
197
195
|
useGlobalAuth() {
|
|
196
|
+
// eslint-disable-next-line no-undef
|
|
197
|
+
this.token = globalThis.idasTokens.token;
|
|
198
198
|
// eslint-disable-next-line no-undef
|
|
199
|
-
this.
|
|
199
|
+
this.refreshToken = globalThis.idasTokens.refreshToken;
|
|
200
|
+
return this;
|
|
200
201
|
},
|
|
201
|
-
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* GET request, ensure authenticated if needed
|
|
205
|
+
*
|
|
206
|
+
* @async
|
|
207
|
+
* @param {string} [url=""]
|
|
208
|
+
* @param {boolean} [auth=true]
|
|
209
|
+
* @returns {Promise<Object>}
|
|
210
|
+
*/
|
|
202
211
|
async get(url = "", auth = true) {
|
|
203
212
|
if (auth)
|
|
204
213
|
await this.ensureAuthenticated();
|
|
205
214
|
return restClient().useBaseUrl(this.baseUrl).useToken(this.token).get(url);
|
|
206
215
|
},
|
|
207
216
|
|
|
217
|
+
/**
|
|
218
|
+
* PUT request, ensure authenticated if needed
|
|
219
|
+
*
|
|
220
|
+
* @async
|
|
221
|
+
* @param {string} [url=""]
|
|
222
|
+
* @param {Object} [payload={}]
|
|
223
|
+
* @param {boolean} [auth=true]
|
|
224
|
+
* @returns {Promise<Object>}
|
|
225
|
+
*/
|
|
208
226
|
async put(url = "", payload = {}, auth = true) {
|
|
209
227
|
if (auth)
|
|
210
228
|
await this.ensureAuthenticated();
|
|
211
229
|
return restClient().useBaseUrl(this.baseUrl).useToken(this.token).put(url, payload);
|
|
212
230
|
},
|
|
213
231
|
|
|
232
|
+
/**
|
|
233
|
+
* POST request, ensure authenticated if needed
|
|
234
|
+
*
|
|
235
|
+
* @async
|
|
236
|
+
* @param {string} [url=""]
|
|
237
|
+
* @param {Object} [payload={}]
|
|
238
|
+
* @param {boolean} [auth=true]
|
|
239
|
+
* @returns {Promise<Object>}
|
|
240
|
+
*/
|
|
214
241
|
async post(url = "", payload = {}, auth = true) {
|
|
215
242
|
if (auth)
|
|
216
243
|
await this.ensureAuthenticated();
|
|
217
244
|
return restClient().useBaseUrl(this.baseUrl).useToken(this.token).post(url, payload);
|
|
218
245
|
},
|
|
219
|
-
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* DELETE request, ensure authenticated if needed
|
|
249
|
+
*
|
|
250
|
+
* @async
|
|
251
|
+
* @param {string} [url=""]
|
|
252
|
+
* @param {boolean} [auth=true]
|
|
253
|
+
* @returns {Promise<Object>}
|
|
254
|
+
*/
|
|
220
255
|
async delete(url = "", auth = true) {
|
|
221
256
|
if (auth)
|
|
222
257
|
await this.ensureAuthenticated();
|
|
223
258
|
return restClient().useBaseUrl(this.baseUrl).useToken(this.token).delete(url);
|
|
224
259
|
},
|
|
225
260
|
|
|
261
|
+
/**
|
|
262
|
+
* Ensure the user is authenticated before making a request
|
|
263
|
+
*
|
|
264
|
+
* @async
|
|
265
|
+
* @private
|
|
266
|
+
*/
|
|
226
267
|
async ensureAuthenticated() {
|
|
227
268
|
if (this.token && isTokenValid(this.token))
|
|
228
269
|
return;
|
|
@@ -252,7 +293,14 @@ export function api() {
|
|
|
252
293
|
this.redirectToLogin();
|
|
253
294
|
}
|
|
254
295
|
},
|
|
255
|
-
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Ensure the base URL is set before making a request. If not set,
|
|
299
|
+
* retrieve the environment data and set the base URL.
|
|
300
|
+
*
|
|
301
|
+
* @async
|
|
302
|
+
* @private
|
|
303
|
+
*/
|
|
256
304
|
async ensureBaseUrlIsSet() {
|
|
257
305
|
if (this.env && (!this.baseUrl || !this.authUrl)) {
|
|
258
306
|
const envInfo = await fetchEnv(this.env);
|
|
@@ -270,6 +318,12 @@ export function api() {
|
|
|
270
318
|
}
|
|
271
319
|
},
|
|
272
320
|
|
|
321
|
+
/**
|
|
322
|
+
* Redirect to the login page
|
|
323
|
+
*
|
|
324
|
+
* @param {string} [authPath=""]
|
|
325
|
+
* @private
|
|
326
|
+
*/
|
|
273
327
|
redirectToLogin(authPath = "") {
|
|
274
328
|
if (!window) {
|
|
275
329
|
return;
|
|
@@ -284,14 +338,34 @@ export function api() {
|
|
|
284
338
|
url.search = `?a=${this.appToken}&r=${encodeURIComponent(authUrlCallback)}`;
|
|
285
339
|
let loginUrl = url.toString();
|
|
286
340
|
|
|
287
|
-
window.location = loginUrl;
|
|
341
|
+
window.location.href = loginUrl;
|
|
288
342
|
},
|
|
289
343
|
};
|
|
290
344
|
}
|
|
291
345
|
|
|
346
|
+
/**
|
|
347
|
+
* Default setup for IDAS
|
|
348
|
+
*
|
|
349
|
+
* @export
|
|
350
|
+
* @param {string} [appToken=""]
|
|
351
|
+
* @return {FluentApi}
|
|
352
|
+
*/
|
|
292
353
|
export function idasApi(appToken = "") {
|
|
293
|
-
return
|
|
354
|
+
return createApi()
|
|
294
355
|
.useGlobalAuth()
|
|
295
356
|
.useAppToken(appToken)
|
|
296
357
|
.useEnvironment("dev");
|
|
297
358
|
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Default setup for local API
|
|
362
|
+
*
|
|
363
|
+
* @export
|
|
364
|
+
* @return {FluentApi}
|
|
365
|
+
*/
|
|
366
|
+
export function localApi() {
|
|
367
|
+
return createApi()
|
|
368
|
+
.useGlobalAuth()
|
|
369
|
+
.useBaseUrl("/api")
|
|
370
|
+
.useEnvironment("dev");
|
|
371
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { jwtDecode } from "jwt-decode";
|
|
2
|
+
import { isTokenValid, getRefreshToken } from "./fluentApi";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {Object} FluentAuth
|
|
6
|
+
* @property {string} authUrl - The authentication URL.
|
|
7
|
+
* @property {string} appToken - The application token.
|
|
8
|
+
* @property {string} token - The JWT token.
|
|
9
|
+
* @property {string} refreshToken - The refresh token.
|
|
10
|
+
* @property {function(string) : FluentApi} useAppToken - Sets the application token and returns the FluentApi object.
|
|
11
|
+
* @property {function(string) : FluentApi} useBaseUrl - Sets the base URL for authentication and returns the FluentApi object.
|
|
12
|
+
* @property {function(string) : FluentApi} useToken - Sets the JWT token and returns the FluentApi object.
|
|
13
|
+
* @property {function(string) : FluentApi} useRefreshToken - Sets the refresh token and returns the FluentApi object.
|
|
14
|
+
* @property {Function} authenticate - Authenticates the user with username and password, or refreshes the token.
|
|
15
|
+
* @property {Function} tryRefreshToken - Attempts to refresh the authentication token using the refresh token.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Builds an authentication object
|
|
20
|
+
*
|
|
21
|
+
* @export
|
|
22
|
+
* @returns {FluentAuth}
|
|
23
|
+
*/
|
|
24
|
+
export function authBuilder() {
|
|
25
|
+
return {
|
|
26
|
+
authUrl: "",
|
|
27
|
+
appToken: "",
|
|
28
|
+
token: "",
|
|
29
|
+
refreshToken: "",
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* app token to use for authentication
|
|
33
|
+
*
|
|
34
|
+
* @param {string} [appToken=""]
|
|
35
|
+
* @returns {FluentAuth}
|
|
36
|
+
*/
|
|
37
|
+
useAppToken(appToken = "") {
|
|
38
|
+
this.appToken = appToken; return this;
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* base URL for authentication
|
|
43
|
+
*
|
|
44
|
+
* @param {string} [authUrl=""]
|
|
45
|
+
* @returns {FluentAuth}
|
|
46
|
+
*/
|
|
47
|
+
useBaseUrl(authUrl = "") {
|
|
48
|
+
this.authUrl = authUrl; return this;
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* token to use for authentication
|
|
53
|
+
*
|
|
54
|
+
* @param {string} [jwtToken=""]
|
|
55
|
+
* @returns {FluentAuth}
|
|
56
|
+
*/
|
|
57
|
+
useToken(jwtToken = "") {
|
|
58
|
+
this.token = jwtToken; return this;
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* sets the refresh token to use
|
|
63
|
+
* @param {string} a refresh token (UUID v4 format)
|
|
64
|
+
* @returns {FluentAuth}
|
|
65
|
+
*/
|
|
66
|
+
useRefreshToken(storedRefreshToken = "") {
|
|
67
|
+
this.refreshToken = storedRefreshToken; return this;
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Authenticates the user with username and password, or refreshes the token.
|
|
72
|
+
*
|
|
73
|
+
* @param {string} username
|
|
74
|
+
* @param {string} password
|
|
75
|
+
* @returns
|
|
76
|
+
*/
|
|
77
|
+
async authenticate(username = "", password = "") {
|
|
78
|
+
console.log("authenticating:", this.token ? `token set, exp: ${jwtDecode(this.token).exp - (Date.now() / 1000)}` : "no token,", this.refreshToken);
|
|
79
|
+
|
|
80
|
+
if (this.token && isTokenValid(this.token))
|
|
81
|
+
return this.token;
|
|
82
|
+
|
|
83
|
+
if (this.token && !this.refreshToken)
|
|
84
|
+
this.refreshToken = getRefreshToken(this.token);
|
|
85
|
+
|
|
86
|
+
if (this.refreshToken) {
|
|
87
|
+
try {
|
|
88
|
+
const temptoken = await this.tryRefreshToken(this.refreshToken);
|
|
89
|
+
if (temptoken) {
|
|
90
|
+
this.token = temptoken;
|
|
91
|
+
this.refreshToken = getRefreshToken(temptoken);
|
|
92
|
+
}
|
|
93
|
+
} catch {
|
|
94
|
+
// if refresh failed, we'll return the current token
|
|
95
|
+
// - should still be valid for a while
|
|
96
|
+
}
|
|
97
|
+
return this.token;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (username && password) {
|
|
101
|
+
const payload = { "Email": username, "Password": password, "AppToken": this.appToken };
|
|
102
|
+
const res = await fetch(`${this.authUrl}/LoginJwt`,
|
|
103
|
+
{ method: "POST", body: JSON.stringify(payload), headers: { "Content-Type": "application/json" } });
|
|
104
|
+
const temptoken = await res.json();
|
|
105
|
+
if (temptoken) {
|
|
106
|
+
this.token = temptoken;
|
|
107
|
+
return this.token;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
throw new Error("not authenticated");
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* try to refresh the token using the refresh token
|
|
116
|
+
*
|
|
117
|
+
* @async
|
|
118
|
+
* @private
|
|
119
|
+
* @param {string} [refreshToken=""]
|
|
120
|
+
* @returns {unknown}
|
|
121
|
+
*/
|
|
122
|
+
async tryRefreshToken(refreshToken = "") {
|
|
123
|
+
const payload = { "Token": refreshToken };
|
|
124
|
+
const res = await fetch(`${this.authUrl}/LoginJwt/Refresh`,
|
|
125
|
+
{
|
|
126
|
+
method: "PUT",
|
|
127
|
+
body: JSON.stringify(payload),
|
|
128
|
+
headers: { "Content-Type": "application/json" },
|
|
129
|
+
});
|
|
130
|
+
return res.ok ? await res.json() : null;
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} FluentRESTClient
|
|
3
|
+
* @property {string} baseUrl - The base URL for API requests.
|
|
4
|
+
* @property {string} token - The JWT token for authorization.
|
|
5
|
+
* @property {function(string) : FluentRESTClient} useBaseUrl - Function to set the base URL and return the FluentApi object.
|
|
6
|
+
* @property {function(string) : FluentRESTClient} useToken - Function to set the JWT token and return the FluentApi object.
|
|
7
|
+
* @property {function(string) : object|Array<any>} get - Async function to perform GET requests.
|
|
8
|
+
* @property {function(string, object) : object|Array<any>} put - Async function to perform PUT requests with a payload.
|
|
9
|
+
* @property {function(string, object) : object|Array<any>} post - Async function to perform POST requests with a payload.
|
|
10
|
+
* @property {function(string) : object|Array<any>} delete - Async function to perform DELETE requests.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Creates a REST client object with fluent API for making HTTP requests.
|
|
15
|
+
* @returns {FluentRESTClient} The REST client object.
|
|
16
|
+
*/
|
|
17
|
+
export function restClient() {
|
|
18
|
+
return {
|
|
19
|
+
baseUrl: "",
|
|
20
|
+
token: "",
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* set the base URL for all requests
|
|
24
|
+
*
|
|
25
|
+
* @param {string} url to use as base URL
|
|
26
|
+
* @returns {FluentRESTClient}
|
|
27
|
+
*/
|
|
28
|
+
useBaseUrl(url = "") {
|
|
29
|
+
this.baseUrl = url; return this;
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* set the JWT token for authorization
|
|
34
|
+
*
|
|
35
|
+
* @param {string} [jwtToken=""]
|
|
36
|
+
* @returns {FluentRESTClient}
|
|
37
|
+
*/
|
|
38
|
+
useToken(jwtToken = "") {
|
|
39
|
+
this.token = jwtToken; return this;
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* GET request to the specified URL
|
|
44
|
+
*
|
|
45
|
+
* @async
|
|
46
|
+
* @param {string} [url=""]
|
|
47
|
+
* @param {boolean} [auth=true]
|
|
48
|
+
* @returns {Promise<any>}
|
|
49
|
+
*/
|
|
50
|
+
async get(url = "", auth = true) {
|
|
51
|
+
const finalUrl = `${this.baseUrl}/${url}`;
|
|
52
|
+
const headers = this.token ? { "Authorization": `Bearer ${this.token}` } : {};
|
|
53
|
+
const res = await fetch(finalUrl, { method: "GET", headers });
|
|
54
|
+
if (res.ok)
|
|
55
|
+
return await this._parseReponse(res);
|
|
56
|
+
throw new Error(`GET ${finalUrl} failed: ${res.status} ${res.statusText}`);
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* PUT request to the specified URL
|
|
61
|
+
*
|
|
62
|
+
* @async
|
|
63
|
+
* @param {string} [url=""]
|
|
64
|
+
* @param {Object} [payload={}]
|
|
65
|
+
* @returns {Promise<any>}
|
|
66
|
+
*/
|
|
67
|
+
async put(url = "", payload = {}) {
|
|
68
|
+
const finalUrl = `${this.baseUrl}/${url}`;
|
|
69
|
+
const headers = this.token ? { "Authorization": `Bearer ${this.token}`, "Content-Type": "application/json" } : {};
|
|
70
|
+
const res = await fetch(finalUrl, { method: "PUT", body: JSON.stringify(payload), headers });
|
|
71
|
+
if (res.ok)
|
|
72
|
+
return await this._parseReponse(res);
|
|
73
|
+
throw new Error(`PUT ${finalUrl} failed: ${res.status} ${res.statusText}`);
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* POST request to the specified URL
|
|
78
|
+
*
|
|
79
|
+
* @async
|
|
80
|
+
* @param {string} [url=""]
|
|
81
|
+
* @param {Object} [payload={}]
|
|
82
|
+
* @returns {Promise<any>}
|
|
83
|
+
*/
|
|
84
|
+
async post(url = "", payload = {}) {
|
|
85
|
+
const finalUrl = `${this.baseUrl}/${url}`;
|
|
86
|
+
const headers = this.token ? { "Authorization": `Bearer ${this.token}`, "Content-Type": "application/json" } : {};
|
|
87
|
+
const res = await fetch(finalUrl, { method: "POST", body: JSON.stringify(payload), headers });
|
|
88
|
+
if (res.ok)
|
|
89
|
+
return await this._parseReponse(res);
|
|
90
|
+
throw new Error(`POST ${finalUrl} failed: ${res.status} ${res.statusText}`);
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* DELETE request to the specified URL
|
|
95
|
+
*
|
|
96
|
+
* @async
|
|
97
|
+
* @param {string} [url=""]
|
|
98
|
+
* @returns {Promise<any>}
|
|
99
|
+
*/
|
|
100
|
+
async delete(url = "") {
|
|
101
|
+
const finalUrl = `${this.baseUrl}/${url}`;
|
|
102
|
+
const headers = this.token ? { "Authorization": `Bearer ${this.token}` } : {};
|
|
103
|
+
const res = await fetch(finalUrl, { method: "DELETE", headers });
|
|
104
|
+
if (res.ok)
|
|
105
|
+
return await this._parseReponse(res);
|
|
106
|
+
throw new Error(`DELETE ${finalUrl} failed: ${res.status} ${res.statusText}`);
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
async _parseReponse(res)
|
|
110
|
+
{
|
|
111
|
+
// check if repsonse is JSON, then return parsed JSON, otherwise return text
|
|
112
|
+
const contentType = res.headers.get("content-type");
|
|
113
|
+
if (contentType && contentType.includes("application/json"))
|
|
114
|
+
return await res.json();
|
|
115
|
+
return await res.text();
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
package/index.js
CHANGED
|
@@ -15,4 +15,7 @@ export {
|
|
|
15
15
|
export { IDASFactory } from "./api/IDAS";
|
|
16
16
|
export { RESTClient } from "./api/RESTClient";
|
|
17
17
|
export { initIDAS } from "./api/authUtils";
|
|
18
|
-
|
|
18
|
+
|
|
19
|
+
export { createApi as api, idasApi, fetchEnv, getRefreshToken } from "./api/fluentApi";
|
|
20
|
+
export { authBuilder } from "./api/fluentAuthBuilder";
|
|
21
|
+
export { restClient } from "./api/fluentRestClient";
|
package/jsconfig.json
CHANGED