@trimble-oss/trimble-id-react 1.0.0-rc.3 → 1.0.1

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/README.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @trimble-oss/trimble-id-react
2
2
 
3
+ > **Important Notice:**
4
+ >
5
+ > As of version 1.0.0, `PersistentOptions` have been removed. By default, the SDK now supports in-memory token storage.
6
+ >
7
+ > When you upgrade to version 1.x, storage options will no longer be available, resulting in a breaking change. For those using an older version of the SDK (i.e., <1.x), it is highly recommended to use the default in-memory storage to avoid any security issues.
8
+
3
9
  Trimble Identity SDK for React app.
4
10
 
5
11
  🚀 [Getting Started](#getting-started) - 📚 [Usage Reference](#usage-reference) - 💬 [Support](#support)
@@ -61,7 +67,7 @@ process related to the authentication for you. Configure the SDK by wrapping you
61
67
 
62
68
  Here TIDProvider can take two parameters :
63
69
  * **tidClient** : TID client instance. You can send an instance of the TID Client if you want to handle the initialization yourself
64
- * **onRedirectCallback** - When the redirect callback occur this function will be call once the user is login using the TIDClient. This could allow you to redirect the user into another page after the login happen.
70
+ * **onRedirectCallback** - When the redirect callback occur this function will be call once the user is login using the TIDClient. This function receives an `authState` parameter that contains a `redirectTo` property with the user's original location before authentication, allowing you to redirect the user back to their intended destination after login.
65
71
 
66
72
 
67
73
  After wrapping your app with the TIDProvider, you have to configure the TID credentials registered in TrimbleCloud console. There are two ways of doing this:
@@ -96,6 +102,15 @@ After wrapping your app with the TIDProvider, you have to configure the TID cred
96
102
  </TIDProvider>
97
103
  ```
98
104
 
105
+ ```tsx
106
+ const handleRedirect = (authState) => {
107
+ // Use redirectTo for automatic redirection to original location
108
+ const redirectTo = authState.redirectTo || '/dashboard'
109
+ // Navigate to the intended destination
110
+ navigate(redirectTo)
111
+ }
112
+ ```
113
+
99
114
  Below are the parameters of TIDClient.
100
115
  ### 1. TID Client configurations:
101
116
 
@@ -106,12 +121,6 @@ Production: https://id.trimble.com/.well-known/openid-configuration <br />
106
121
  * **logoutRedirectUrl** : The URL to which Trimble Identity should redirect after successfully logout a user
107
122
  * **scopes** : The type of credentials you want (openID, or application_name)
108
123
 
109
-
110
- > **_NOTE:_**
111
- >
112
- > As of version 1.0.0, PersistentOptions have been removed. By default, the SDK now supports in-memory token storage. Using localStorage and sessionStorage for storing sensitive information poses several security risks, including vulnerability to XSS attacks, lack of secure attributes and session hijacking.
113
-
114
- > When you upgrade to version 1.x, storage options will no longer be available, resulting in a breaking change. For those using an older version of the SDK (i.e., <1.x), it is highly recommended to use the default in-memory storage to avoid any security issues.
115
124
  ### useAuth
116
125
  Use the `useAuth` hook in your components to access authentication state (`isLoading`, `isAuthenticated`, `user`, `error`) and authentication methods (`loginWithRedirect` and `logout`):
117
126
 
@@ -131,6 +140,21 @@ const {logout}= useAuth()
131
140
  await logout()
132
141
  ```
133
142
 
143
+ ### handleCallback
144
+
145
+ Handle OAuth callback to complete user authentication and returns the auth state with redirect path.
146
+
147
+ ```tsx
148
+ const { handleCallback } = useAuth()
149
+
150
+ // Handle callback and get redirect information
151
+ const authState = await handleCallback()
152
+
153
+ // Use redirectTo for automatic redirection to original location
154
+ const redirectTo = authState.redirectTo || '/dashboard'
155
+ navigate(redirectTo)
156
+ ```
157
+
134
158
  ### isAuthenticated
135
159
 
136
160
  True if the user is authenticated.
@@ -206,5 +230,4 @@ To provide feedback or report a bug, please [raise an issue on our issue tracker
206
230
 
207
231
  ## <a name="support">Support</a>
208
232
 
209
- Send email to [cloudplatform_support@trimble.com](mailto:cloudplatform_support@trimble.com)
210
-
233
+ Send email to [cloudplatform_support@trimble.com](mailto:cloudplatform_support@trimble.com)
@@ -100,6 +100,10 @@ export declare class TIDClient {
100
100
  * @param {CacheManagerOptions} props - TID client configuration
101
101
  */
102
102
  constructor(props: TIDClientOptions);
103
+ /**
104
+ * Clean up expired state payloads to prevent storage bloat
105
+ */
106
+ private cleanupExpiredState;
103
107
  /**
104
108
  * Redirect the user to TID using the browser
105
109
  * @param {LoginWithRedirectOptions} options - Custom configuration for the redirection
@@ -107,6 +111,7 @@ export declare class TIDClient {
107
111
  * @example No configuration
108
112
  * loginWithRedirect()
109
113
  * // Automatically redirects the user to TID with all necessary parameters
114
+ * // After authentication, user will be redirected back to the current page
110
115
  * @example Custom redirect
111
116
  * loginWithRedirect({onRedirect: (url) => router.navigate(url)})
112
117
  * // Redirect calls onRedirect with the log-out url for TID
@@ -116,8 +121,9 @@ export declare class TIDClient {
116
121
  /**
117
122
  * Authenticated the user using the url callback params
118
123
  * @param {string} url - Custom configuration for the redirection
119
- * @return {Promise<AuthState>} Object contain the state returned from TID
124
+ * @return {Promise<AuthState>} Object contain the state returned from TID and redirect path
120
125
  * @throws {CodeVerifierNotFoundException} Will throw an exception if the session doesn't contain the code verifier
126
+ * @throws {Error} Will throw an exception if state validation fails or replay attack is detected
121
127
  * @example No configuration
122
128
  * handleCallback()
123
129
  * // Will automatically take the url from the browser and try to log in the user
@@ -133,6 +139,23 @@ export declare class TIDClient {
133
139
  */
134
140
  private generateToken;
135
141
  private reloadCodeVerifier;
142
+ /**
143
+ * Create and encode state payload for replay attack protection
144
+ * @param {string} redirectTo - Path to redirect to after authentication
145
+ * @return {string} - Base64 encoded state payload
146
+ */
147
+ private createStatePayload;
148
+ /**
149
+ * Generate a random nonce for state validation
150
+ * @return {string} - Random nonce
151
+ */
152
+ private generateNonce;
153
+ /**
154
+ * Validate state payload to prevent replay attacks
155
+ * @param {string} receivedState - State received from OAuth callback
156
+ * @return {StateValidationResult} - Validation result with redirect path if valid
157
+ */
158
+ private validateStatePayload;
136
159
  /**
137
160
  * Return the user stored in cache
138
161
  * @return {Promise<TIDUser | undefined>} User in cache
@@ -8,3 +8,9 @@ export declare const CLOCK_SKEW_TIME: number;
8
8
  * @type {Array<string>}
9
9
  */
10
10
  export declare const LIST_PARAMS_REQUIRED: Array<string>;
11
+ /**
12
+ * Maximum time allowed for state parameter to be valid (10 minutes)
13
+ * Used to prevent replay attacks
14
+ * @type {number}
15
+ */
16
+ export declare const STATE_EXPIRATION_TIME: number;
@@ -6,3 +6,6 @@ export declare class TokenNotFoundException extends Error {
6
6
  }
7
7
  export declare class CodeVerifierNotFoundException extends Error {
8
8
  }
9
+ /** Class representing an OAuth state validation exception for CSRF protection */
10
+ export declare class StateValidationException extends Error {
11
+ }
@@ -175,4 +175,15 @@ export interface TIDJWTUser {
175
175
  }
176
176
  export interface AuthState {
177
177
  authState: any;
178
+ returnTo?: string;
179
+ }
180
+ export interface StatePayload {
181
+ redirectTo: string;
182
+ timestamp: number;
183
+ nonce: string;
184
+ }
185
+ export interface StateValidationResult {
186
+ isValid: boolean;
187
+ redirectTo?: string;
188
+ error?: string;
178
189
  }
@@ -12,6 +12,11 @@ interface CookieValue {
12
12
  * @type {string}
13
13
  */
14
14
  code_verifier: string;
15
+ /**
16
+ * State information used for replay attack protection
17
+ * @type {string}
18
+ */
19
+ state_payload?: string;
15
20
  }
16
21
  /** Class to manage cookies */
17
22
  export declare class CookiesManager {
@@ -32,9 +37,18 @@ export declare class CookiesManager {
32
37
  constructor(options: CookiesManagerOptions);
33
38
  /**
34
39
  * Store cookies in the browser
35
- * @param {CookieValue} value - information to store
40
+ * @param {Partial<CookieValue>} value - information to store
41
+ */
42
+ save(value: Partial<CookieValue>): void;
43
+ /**
44
+ * Retrieve state payload from cookies
45
+ * @return {string | undefined} - State payload from cookies
46
+ */
47
+ getStatePayload(): string | undefined;
48
+ /**
49
+ * Clear state payload from cookies
36
50
  */
37
- save(value: CookieValue): void;
51
+ clearStatePayload(): void;
38
52
  /**
39
53
  * Retrieve cookies in the browser
40
54
  * @return {CookieValue | undefined} - Cookies from the browser
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { TIDClient } from './TIDClient';
2
2
  export { TIDContext, useAuth, TIDProvider } from './TIDProvider';
3
3
  export { AuthenticationGuard } from './AuthenticationGuard/AuthenticationGuard';
4
- export type { TokenResponse } from './TIDClient';
4
+ export type { TokenResponse, AuthState } from './TIDClient';
@@ -1,12 +1,12 @@
1
- var F = Object.defineProperty;
2
- var B = (i, e, t) => e in i ? F(i, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : i[e] = t;
3
- var c = (i, e, t) => (B(i, typeof e != "symbol" ? e + "" : e, t), t);
4
- import { OpenIdEndpointProvider as $, AuthorizationCodeGrantTokenProvider as R, AnalyticsHttpClient as H, BearerTokenHttpClientProvider as Q } from "@trimble-oss/trimble-id";
5
- import * as w from "es-cookie";
6
- import J from "jwt-decode";
7
- import { createContext as q, useContext as L, useState as z, useReducer as X, useRef as Y, useMemo as M, useEffect as D, useCallback as y } from "react";
8
- import { jsx as K, Fragment as Z } from "react/jsx-runtime";
9
- class j {
1
+ var $ = Object.defineProperty;
2
+ var B = (i, e, t) => e in i ? $(i, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : i[e] = t;
3
+ var l = (i, e, t) => (B(i, typeof e != "symbol" ? e + "" : e, t), t);
4
+ import { OpenIdEndpointProvider as J, AuthorizationCodeGrantTokenProvider as R, AnalyticsHttpClient as H, BearerTokenHttpClientProvider as Q } from "@trimble-oss/trimble-id";
5
+ import * as T from "es-cookie";
6
+ import X from "jwt-decode";
7
+ import { createContext as q, useContext as L, useState as z, useReducer as Y, useRef as Z, useMemo as M, useEffect as N, useCallback as m } from "react";
8
+ import { jsx as V, Fragment as j } from "react/jsx-runtime";
9
+ class ee {
10
10
  constructor() {
11
11
  /**
12
12
  * This function generate a encapsulation function to store
@@ -15,7 +15,7 @@ class j {
15
15
  * @see {https://medium.com/javascript-scene/encapsulation-in-javascript-26be60e325b4} Encapsulation
16
16
  * @return {CacheStorage} Key for the token
17
17
  */
18
- c(this, "generateCache", function() {
18
+ l(this, "generateCache", function() {
19
19
  const e = {
20
20
  token: void 0,
21
21
  user: void 0
@@ -62,14 +62,14 @@ class j {
62
62
  }());
63
63
  }
64
64
  }
65
- class ee {
65
+ class te {
66
66
  constructor() {
67
67
  /**
68
68
  * Cache option selected
69
69
  * @type {CacheStorage}
70
70
  */
71
- c(this, "cacheStorage");
72
- this.cacheStorage = new j().generateCache;
71
+ l(this, "cacheStorage");
72
+ this.cacheStorage = new ee().generateCache;
73
73
  }
74
74
  /**
75
75
  * Store token in cache
@@ -109,7 +109,7 @@ class ee {
109
109
  await this.cacheStorage.clear();
110
110
  }
111
111
  }
112
- const x = 5 * 6e4, te = ["code", "state"], A = (i) => i.split("?")[0], ie = (i) => i.split("?")[1], b = (i) => {
112
+ const x = 5 * 6e4, ie = ["code", "state"], A = 10 * 60 * 1e3, U = (i) => i.split("?")[0], re = (i) => i.split("?")[1], b = (i) => {
113
113
  let e = i;
114
114
  if (!i.startsWith("?"))
115
115
  try {
@@ -119,16 +119,16 @@ const x = 5 * 6e4, te = ["code", "state"], A = (i) => i.split("?")[0], ie = (i)
119
119
  return new URLSearchParams(e);
120
120
  }, ne = (i = window.location.href, e) => {
121
121
  if (e != null) {
122
- const n = A(e), r = A(i ?? "");
123
- if (n !== r)
122
+ const r = U(e), n = U(i ?? "");
123
+ if (r !== n)
124
124
  return !1;
125
125
  }
126
126
  const t = b(i);
127
- for (const n of te)
128
- if (!t.has(n))
127
+ for (const r of ie)
128
+ if (!t.has(r))
129
129
  return !1;
130
130
  return !0;
131
- }, re = (i) => ({
131
+ }, ae = (i) => ({
132
132
  id: i.sub,
133
133
  name: `${i.given_name} ${i.family_name}`,
134
134
  given_name: i.given_name,
@@ -136,14 +136,14 @@ const x = 5 * 6e4, te = ["code", "state"], A = (i) => i.split("?")[0], ie = (i)
136
136
  picture: i.picture,
137
137
  email: i.email,
138
138
  email_verified: i.email_verified
139
- }), se = "@TID_COOKIE";
140
- class oe {
139
+ }), oe = "@TID_COOKIE";
140
+ class se {
141
141
  /**
142
142
  * Retrieve a cookie from the browser
143
143
  * @param {string} key - Key to retrieve the cookies
144
144
  */
145
145
  get(e) {
146
- const t = w.get(e);
146
+ const t = T.get(e);
147
147
  if (t == null)
148
148
  throw new Error("Cookie not found");
149
149
  return JSON.parse(t);
@@ -158,12 +158,12 @@ class oe {
158
158
  * @example Save cookies with a custom domain
159
159
  * cookiesStorage.set('key',{data:{...}},{domain:'https://example.com/subpath'})
160
160
  */
161
- set(e, t, n) {
162
- let r = {};
163
- window.location.protocol === "https:" && (r = {
161
+ set(e, t, r) {
162
+ let n = {};
163
+ window.location.protocol === "https:" && (n = {
164
164
  secure: !0,
165
165
  sameSite: "none"
166
- }), n != null && n.expires && (r.expires = n.expires), n != null && n.domain && (r.domain = n.domain), w.set(e, JSON.stringify(t), r);
166
+ }), r != null && r.expires && (n.expires = r.expires), r != null && r.domain && (n.domain = r.domain), T.set(e, JSON.stringify(t), n);
167
167
  }
168
168
  /**
169
169
  * Remove a cookie from the browser
@@ -175,11 +175,11 @@ class oe {
175
175
  * cookiesStorage.remove('key',{domain:'https://example.com/subpath'})
176
176
  */
177
177
  remove(e, t) {
178
- const n = {};
179
- t != null && t.domain && (n.domain = t.domain), w.remove(e, n);
178
+ const r = {};
179
+ t != null && t.domain && (r.domain = t.domain), T.remove(e, r);
180
180
  }
181
181
  }
182
- class ae {
182
+ class ce {
183
183
  /**
184
184
  * Create a cookies manager to extract or save cookies in the browser
185
185
  * @param {CookiesManagerOptions} options - Configuration for the managing the cookies
@@ -189,23 +189,39 @@ class ae {
189
189
  * Cookie full key to store and retrieve the information from cookies
190
190
  * @type {string}
191
191
  */
192
- c(this, "cookieKey");
192
+ l(this, "cookieKey");
193
193
  /**
194
194
  * Cookie storage. This object contain all functions necessary to retrieve and store tokens using cookies
195
195
  * @type {CookiesStorage}
196
196
  */
197
- c(this, "cookiesStorage");
198
- this.cookieKey = `${se}.${e.clientId}`, this.cookiesStorage = new oe();
197
+ l(this, "cookiesStorage");
198
+ this.cookieKey = `${oe}.${e.clientId}`, this.cookiesStorage = new se();
199
199
  }
200
200
  /**
201
201
  * Store cookies in the browser
202
- * @param {CookieValue} value - information to store
202
+ * @param {Partial<CookieValue>} value - information to store
203
203
  */
204
204
  save(e) {
205
- this.cookiesStorage.set(this.cookieKey, e, {
205
+ const r = { ...this.get() || {}, ...e };
206
+ this.cookiesStorage.set(this.cookieKey, r, {
206
207
  expires: 1
207
208
  });
208
209
  }
210
+ /**
211
+ * Retrieve state payload from cookies
212
+ * @return {string | undefined} - State payload from cookies
213
+ */
214
+ getStatePayload() {
215
+ const e = this.get();
216
+ return e == null ? void 0 : e.state_payload;
217
+ }
218
+ /**
219
+ * Clear state payload from cookies
220
+ */
221
+ clearStatePayload() {
222
+ const e = this.get();
223
+ e && (delete e.state_payload, this.cookiesStorage.set(this.cookieKey, e, { expires: 1 }));
224
+ }
209
225
  /**
210
226
  * Retrieve cookies in the browser
211
227
  * @return {CookieValue | undefined} - Cookies from the browser
@@ -224,20 +240,20 @@ class ae {
224
240
  this.cookiesStorage.remove(this.cookieKey);
225
241
  }
226
242
  }
227
- class U extends Error {
228
- }
229
243
  class O extends Error {
230
244
  }
231
- class ce extends Error {
245
+ class D extends Error {
246
+ }
247
+ class le extends Error {
232
248
  }
233
- const d = "@trimble-oss/trimble-id-react", h = "1.0.0-rc.2", le = {
249
+ const u = "@trimble-oss/trimble-id-react", g = "1.0.1", de = {
234
250
  configurationEndpoint: "",
235
251
  clientId: "",
236
252
  redirectUrl: "",
237
253
  logoutRedirectUrl: "",
238
254
  scopes: []
239
255
  };
240
- class de {
256
+ class he {
241
257
  /**
242
258
  * Create a TID client to handle manage all user authentication functions and information
243
259
  * @param {CacheManagerOptions} props - TID client configuration
@@ -247,47 +263,61 @@ class de {
247
263
  * Token provider SDK. This object handles all necessary communication with TID
248
264
  * @type {AuthorizationCodeGrantTokenProvider}
249
265
  */
250
- c(this, "tokenProvider");
266
+ l(this, "tokenProvider");
251
267
  /**
252
268
  * This object manage all caching, and all configurations necessary
253
269
  * @type {CacheManager}
254
270
  */
255
- c(this, "cacheManager");
271
+ l(this, "cacheManager");
256
272
  /**
257
273
  * This object manage all cookies administration, and all configurations necessary
258
274
  * @type {CookiesManager}
259
275
  */
260
- c(this, "cookiesManager");
276
+ l(this, "cookiesManager");
261
277
  /**
262
278
  * Client id of the application created in trimble developer console
263
279
  * @type {string}
264
280
  */
265
- c(this, "clientId");
281
+ l(this, "clientId");
266
282
  /**
267
283
  * Callback url to redirect the user after the authentication is successful
268
284
  * @type {string}
269
285
  */
270
- c(this, "redirectUrl");
286
+ l(this, "redirectUrl");
271
287
  /**
272
288
  * AnalyticsHttpClient for sending events
273
289
  * @type {AnalyticsHttpClient}
274
290
  */
275
- c(this, "analyticshttpclient");
276
- const { config: t = le } = e;
291
+ l(this, "analyticshttpclient");
292
+ const { config: t = de } = e;
277
293
  if (this.redirectUrl = t.redirectUrl, t.configurationEndpoint == null || t.configurationEndpoint == "")
278
294
  throw new Error("Configuration endpoint not defined");
279
295
  if (t.clientId == null || t.clientId == "")
280
296
  throw new Error("Consumer key is not defined");
281
- this.cookiesManager = new ae({
297
+ this.cookiesManager = new ce({
282
298
  clientId: t.clientId
283
299
  });
284
- const n = this.cookiesManager.get(), r = new $(t.configurationEndpoint);
285
- let a = new R(
286
- r,
300
+ const r = this.cookiesManager.get(), n = new J(t.configurationEndpoint);
301
+ let s = new R(
302
+ n,
287
303
  t.clientId,
288
304
  t.redirectUrl
289
305
  ).WithScopes(t.scopes);
290
- t.logoutRedirectUrl && (a = a.WithLogoutRedirect(t.logoutRedirectUrl)), (n == null ? void 0 : n.code_verifier) != null && (a = a.WithProofKeyForCodeExchange(n.code_verifier)), this.tokenProvider = a, this.cacheManager = new ee(), this.clientId = t.clientId, this.analyticshttpclient = H, this.analyticshttpclient.sendInitEvent("TIDClient", this.clientId, d, h);
306
+ t.logoutRedirectUrl && (s = s.WithLogoutRedirect(t.logoutRedirectUrl)), (r == null ? void 0 : r.code_verifier) != null && (s = s.WithProofKeyForCodeExchange(r.code_verifier)), this.tokenProvider = s, this.cacheManager = new te(), this.clientId = t.clientId, this.analyticshttpclient = H, this.analyticshttpclient.sendInitEvent("TIDClient", this.clientId, u, g), this.cleanupExpiredState();
307
+ }
308
+ /**
309
+ * Clean up expired state payloads to prevent storage bloat
310
+ */
311
+ cleanupExpiredState() {
312
+ try {
313
+ const e = this.cookiesManager.getStatePayload();
314
+ if (e) {
315
+ const t = atob(e), r = JSON.parse(t);
316
+ Date.now() - r.timestamp > A && this.cookiesManager.clearStatePayload();
317
+ }
318
+ } catch {
319
+ this.cookiesManager.clearStatePayload();
320
+ }
291
321
  }
292
322
  /**
293
323
  * Redirect the user to TID using the browser
@@ -296,23 +326,27 @@ class de {
296
326
  * @example No configuration
297
327
  * loginWithRedirect()
298
328
  * // Automatically redirects the user to TID with all necessary parameters
329
+ * // After authentication, user will be redirected back to the current page
299
330
  * @example Custom redirect
300
331
  * loginWithRedirect({onRedirect: (url) => router.navigate(url)})
301
332
  * // Redirect calls onRedirect with the log-out url for TID
302
333
  * // So it can be handled by the developer
303
334
  */
304
335
  async loginWithRedirect(e) {
305
- this.analyticshttpclient.sendMethodEvent(this.loginWithRedirect.name, this.clientId, d, h);
306
- const { onRedirect: t } = e || {}, n = R.GenerateCodeVerifier();
307
- this.cookiesManager.save({ code_verifier: n }), this.tokenProvider = this.tokenProvider.WithProofKeyForCodeExchange(n);
308
- const r = await this.tokenProvider.GetOAuthRedirect("state");
309
- t != null ? t(r) : window.location.assign(r);
336
+ this.analyticshttpclient.sendMethodEvent(this.loginWithRedirect.name, this.clientId, u, g);
337
+ const { onRedirect: t } = e || {}, r = window.location.pathname + window.location.search, n = this.createStatePayload(r);
338
+ this.cookiesManager.save({ state_payload: n });
339
+ const s = R.GenerateCodeVerifier();
340
+ this.cookiesManager.save({ code_verifier: s }), this.tokenProvider = this.tokenProvider.WithProofKeyForCodeExchange(s);
341
+ const c = await this.tokenProvider.GetOAuthRedirect(n);
342
+ t != null ? t(c) : window.location.assign(c);
310
343
  }
311
344
  /**
312
345
  * Authenticated the user using the url callback params
313
346
  * @param {string} url - Custom configuration for the redirection
314
- * @return {Promise<AuthState>} Object contain the state returned from TID
347
+ * @return {Promise<AuthState>} Object contain the state returned from TID and redirect path
315
348
  * @throws {CodeVerifierNotFoundException} Will throw an exception if the session doesn't contain the code verifier
349
+ * @throws {Error} Will throw an exception if state validation fails or replay attack is detected
316
350
  * @example No configuration
317
351
  * handleCallback()
318
352
  * // Will automatically take the url from the browser and try to log in the user
@@ -323,10 +357,13 @@ class de {
323
357
  async handleCallback(e = window.location.href) {
324
358
  const t = this.cookiesManager.get();
325
359
  if (t == null || (t == null ? void 0 : t.code_verifier) == null)
326
- throw new ce("Code verifier not available");
327
- const n = ie(e), r = b(e), a = r.get("identity_provider") ?? "", g = r.get("state") ?? "";
328
- return await this.tokenProvider.ValidateQuery(n), await this.generateToken(a), {
329
- authState: g
360
+ throw new le("Code verifier not available");
361
+ const r = re(e), n = b(e), s = n.get("identity_provider") ?? "", c = n.get("state") ?? "", d = this.validateStatePayload(c);
362
+ if (!d.isValid)
363
+ throw new Error(`State validation failed: ${d.error}`);
364
+ return await this.tokenProvider.ValidateQuery(r), await this.generateToken(s), {
365
+ authState: c,
366
+ returnTo: d.redirectTo
330
367
  };
331
368
  }
332
369
  /**
@@ -335,32 +372,79 @@ class de {
335
372
  * @return {Promise<void>} Empty promise
336
373
  */
337
374
  async generateToken(e) {
338
- var s, f;
339
- const t = await this.tokenProvider.RetrieveToken(), n = await this.tokenProvider.RetrieveRefreshToken(), r = await this.tokenProvider.RetrieveIdToken(), a = await this.tokenProvider.RetrieveTokenExpiry(), k = new Date(a).getTime(), v = (f = (s = this.tokenProvider) == null ? void 0 : s._scopes) == null ? void 0 : f.join(" ");
375
+ var a, k;
376
+ const t = await this.tokenProvider.RetrieveToken(), r = await this.tokenProvider.RetrieveRefreshToken(), n = await this.tokenProvider.RetrieveIdToken(), s = await this.tokenProvider.RetrieveTokenExpiry(), d = new Date(s).getTime(), f = (k = (a = this.tokenProvider) == null ? void 0 : a._scopes) == null ? void 0 : k.join(" ");
340
377
  await this.cacheManager.setToken({
341
- scope: v,
378
+ scope: f,
342
379
  state: "",
343
380
  session_state: "",
344
381
  identity_provider: e,
345
382
  token_type: "bearer",
346
383
  access_token: t,
347
- refresh_token: n,
348
- id_token: r,
349
- expires_at: k
384
+ refresh_token: r,
385
+ id_token: n,
386
+ expires_at: d
350
387
  });
351
- const m = J(r), T = re(m);
352
- await this.cacheManager.setUser(T), await this.reloadCodeVerifier();
388
+ const v = X(n), p = ae(v);
389
+ await this.cacheManager.setUser(p), await this.reloadCodeVerifier();
353
390
  }
354
391
  async reloadCodeVerifier() {
355
392
  const e = await this.tokenProvider.RetrieveCodeVerifier();
356
393
  this.cookiesManager.save({ code_verifier: e }), this.tokenProvider = this.tokenProvider.WithProofKeyForCodeExchange(e);
357
394
  }
395
+ /**
396
+ * Create and encode state payload for replay attack protection
397
+ * @param {string} redirectTo - Path to redirect to after authentication
398
+ * @return {string} - Base64 encoded state payload
399
+ */
400
+ createStatePayload(e) {
401
+ const t = this.generateNonce(), r = Date.now();
402
+ return btoa(JSON.stringify({
403
+ redirectTo: e,
404
+ timestamp: r,
405
+ nonce: t
406
+ }));
407
+ }
408
+ /**
409
+ * Generate a random nonce for state validation
410
+ * @return {string} - Random nonce
411
+ */
412
+ generateNonce() {
413
+ const e = new Uint8Array(16);
414
+ return crypto.getRandomValues(e), Array.from(e, (t) => t.toString(16).padStart(2, "0")).join("");
415
+ }
416
+ /**
417
+ * Validate state payload to prevent replay attacks
418
+ * @param {string} receivedState - State received from OAuth callback
419
+ * @return {StateValidationResult} - Validation result with redirect path if valid
420
+ */
421
+ validateStatePayload(e) {
422
+ try {
423
+ if (!e || e.trim() === "")
424
+ return { isValid: !1, error: "Empty state parameter" };
425
+ const t = this.cookiesManager.getStatePayload();
426
+ if (!t)
427
+ return { isValid: !1, error: "No stored state found" };
428
+ const r = atob(e), n = JSON.parse(r), s = atob(t), c = JSON.parse(s);
429
+ if (!n.nonce || !n.timestamp || !n.redirectTo)
430
+ return { isValid: !1, error: "Invalid state payload structure" };
431
+ if (n.nonce !== c.nonce)
432
+ return { isValid: !1, error: "State nonce mismatch" };
433
+ const f = Date.now() - n.timestamp;
434
+ return f > A ? { isValid: !1, error: "State expired - possible replay attack" } : f < 0 ? { isValid: !1, error: "State timestamp is in the future - possible replay attack" } : (this.cookiesManager.clearStatePayload(), {
435
+ isValid: !0,
436
+ redirectTo: n.redirectTo
437
+ });
438
+ } catch {
439
+ return { isValid: !1, error: "Invalid state format" };
440
+ }
441
+ }
358
442
  /**
359
443
  * Return the user stored in cache
360
444
  * @return {Promise<TIDUser | undefined>} User in cache
361
445
  */
362
446
  async getUser() {
363
- return this.analyticshttpclient.sendMethodEvent(this.getUser.name, this.clientId, d, h), await this.cacheManager.getUser();
447
+ return this.analyticshttpclient.sendMethodEvent(this.getUser.name, this.clientId, u, g), await this.cacheManager.getUser();
364
448
  }
365
449
  /**
366
450
  * Gets the access token from cache. If the token already expired,
@@ -370,16 +454,16 @@ class de {
370
454
  * @throws {TokenExpiredException} Will throw an exception if the user token expired
371
455
  */
372
456
  async getAccessTokenSilently() {
373
- this.analyticshttpclient.sendMethodEvent(this.getAccessTokenSilently.name, this.clientId, d, h);
457
+ this.analyticshttpclient.sendMethodEvent(this.getAccessTokenSilently.name, this.clientId, u, g);
374
458
  let e = await this.cacheManager.getToken();
375
459
  if (e == null)
376
- throw this.analyticshttpclient.sendExceptionEvent(this.getAccessTokenSilently.name, "No token available", this.clientId, d, h), new O("No token available");
460
+ throw this.analyticshttpclient.sendExceptionEvent(this.getAccessTokenSilently.name, "No token available", this.clientId, u, g), new D("No token available");
377
461
  const t = new Date((/* @__PURE__ */ new Date()).getTime() + x);
378
462
  if ((e == null ? void 0 : e.expires_at) == null || (e == null ? void 0 : e.expires_at) < t.getTime()) {
379
463
  try {
380
464
  await this.tokenProvider.RetrieveToken();
381
- } catch (n) {
382
- throw this.analyticshttpclient.sendExceptionEvent(this.getAccessTokenSilently.name, n.message, this.clientId, d, h), new U(n.message);
465
+ } catch (r) {
466
+ throw this.analyticshttpclient.sendExceptionEvent(this.getAccessTokenSilently.name, r.message, this.clientId, u, g), new O(r.message);
383
467
  }
384
468
  await this.generateToken((e == null ? void 0 : e.identity_provider) ?? ""), e = await this.cacheManager.getToken();
385
469
  }
@@ -393,16 +477,16 @@ class de {
393
477
  * @throws {TokenExpiredException} Will throw an exception if the user token expired
394
478
  */
395
479
  async getTokens() {
396
- this.analyticshttpclient.sendMethodEvent(this.getTokens.name, this.clientId, d, h);
480
+ this.analyticshttpclient.sendMethodEvent(this.getTokens.name, this.clientId, u, g);
397
481
  let e = await this.cacheManager.getToken();
398
482
  if (e == null)
399
- throw this.analyticshttpclient.sendExceptionEvent(this.getTokens.name, "No token available", this.clientId, d, h), new O("No token available");
483
+ throw this.analyticshttpclient.sendExceptionEvent(this.getTokens.name, "No token available", this.clientId, u, g), new D("No token available");
400
484
  const t = new Date((/* @__PURE__ */ new Date()).getTime() + x);
401
485
  if ((e == null ? void 0 : e.expires_at) == null || (e == null ? void 0 : e.expires_at) < t.getTime()) {
402
486
  try {
403
487
  await this.tokenProvider.RetrieveToken();
404
- } catch (n) {
405
- throw this.analyticshttpclient.sendExceptionEvent(this.getTokens.name, n.message, this.clientId, d, h), new U(n.message);
488
+ } catch (r) {
489
+ throw this.analyticshttpclient.sendExceptionEvent(this.getTokens.name, r.message, this.clientId, u, g), new O(r.message);
406
490
  }
407
491
  await this.generateToken((e == null ? void 0 : e.identity_provider) ?? ""), e = await this.cacheManager.getToken();
408
492
  }
@@ -425,13 +509,13 @@ class de {
425
509
  * // So it can be handled by the developer
426
510
  */
427
511
  async logout(e) {
428
- this.analyticshttpclient.sendMethodEvent(this.logout.name, this.clientId, d, h);
429
- const { onRedirect: t, disabledAutoRedirect: n } = e || {};
512
+ this.analyticshttpclient.sendMethodEvent(this.logout.name, this.clientId, u, g);
513
+ const { onRedirect: t, disabledAutoRedirect: r } = e || {};
430
514
  this.cacheManager && await this.cacheManager.clear(), this.cookiesManager && this.cookiesManager.clear();
431
- const r = await this.tokenProvider.GetOAuthLogoutRedirect("state");
515
+ const n = await this.tokenProvider.GetOAuthLogoutRedirect("state");
432
516
  if (t != null)
433
- return t(r);
434
- n || window.location.assign(r);
517
+ return t(n);
518
+ r || window.location.assign(n);
435
519
  }
436
520
  /**
437
521
  * Check if the user still has a valid session
@@ -460,8 +544,8 @@ class de {
460
544
  const e = await this.cacheManager.getToken();
461
545
  if (e == null)
462
546
  return;
463
- const t = e.access_token, n = e.refresh_token || "", r = e.id_token, a = e.expires_at;
464
- this.tokenProvider = this.tokenProvider.WithAccessToken(t, a).WithRefreshToken(n).WithIdToken(r);
547
+ const t = e.access_token, r = e.refresh_token || "", n = e.id_token, s = e.expires_at;
548
+ this.tokenProvider = this.tokenProvider.WithAccessToken(t, s).WithRefreshToken(r).WithIdToken(n);
465
549
  }
466
550
  /**
467
551
  * Get a http bearer token client to use it for another SDK (Ex: Processing framework)
@@ -478,7 +562,7 @@ class de {
478
562
  return this.redirectUrl;
479
563
  }
480
564
  }
481
- const E = q(null), W = () => L(E) ?? {}, he = (i, e) => {
565
+ const w = q(null), K = () => L(w) ?? {}, ue = (i, e) => {
482
566
  switch (e.type) {
483
567
  case "INIT":
484
568
  return {
@@ -511,10 +595,10 @@ const E = q(null), W = () => L(E) ?? {}, he = (i, e) => {
511
595
  error: e.error
512
596
  };
513
597
  }
514
- }, ue = {
598
+ }, ge = {
515
599
  isLoading: !0,
516
600
  isAuthenticated: !1
517
- }, ge = (i) => {
601
+ }, fe = (i) => {
518
602
  if (i == null || Object.keys(i).length <= 0)
519
603
  return !0;
520
604
  const e = Object.keys(i);
@@ -522,114 +606,112 @@ const E = q(null), W = () => L(E) ?? {}, he = (i, e) => {
522
606
  if (i[t] != null && i[t] !== "")
523
607
  return !1;
524
608
  return !0;
525
- }, ke = (i) => !ge(i.config), fe = (i) => {
609
+ }, ye = (i) => !fe(i.config), ke = (i) => {
526
610
  const {
527
611
  children: e,
528
612
  configurationEndpoint: t,
529
- clientId: n,
530
- redirectUrl: r,
531
- logoutRedirectUrl: a,
532
- scopes: g,
533
- onRedirectCallback: k,
534
- checkRedirectUrlMatch: v
613
+ clientId: r,
614
+ redirectUrl: n,
615
+ logoutRedirectUrl: s,
616
+ scopes: c,
617
+ onRedirectCallback: d,
618
+ checkRedirectUrlMatch: f
535
619
  } = i;
536
- if (L(E) != null)
620
+ if (L(w) != null)
537
621
  throw new Error("TID Provider already defined");
538
- const T = {
622
+ const p = {
539
623
  config: {
540
624
  configurationEndpoint: t ?? "",
541
- clientId: n ?? "",
542
- redirectUrl: r ?? "",
543
- logoutRedirectUrl: a ?? "",
544
- scopes: g ?? [""]
625
+ clientId: r ?? "",
626
+ redirectUrl: n ?? "",
627
+ logoutRedirectUrl: s ?? "",
628
+ scopes: c ?? [""]
545
629
  }
546
- }, [s] = z(i.tidClient ?? new de(T)), [f, u] = X(he, ue), p = Y(!1), N = M(
547
- () => v ? s.getRedirectUrl() : void 0,
548
- [v, s]
630
+ }, [a] = z(i.tidClient ?? new he(p)), [k, y] = Y(ue, ge), E = Z(!1), W = M(
631
+ () => f ? a.getRedirectUrl() : void 0,
632
+ [f, a]
549
633
  );
550
- D(() => {
551
- p.current || (i.tidClient != null && ke({
634
+ N(() => {
635
+ E.current || (i.tidClient != null && ye({
552
636
  config: {
553
637
  configurationEndpoint: t,
554
- clientId: n,
555
- redirectUrl: r,
556
- logoutRedirectUrl: a,
557
- scopes: g
638
+ clientId: r,
639
+ redirectUrl: n,
640
+ logoutRedirectUrl: s,
641
+ scopes: c
558
642
  }
559
643
  }) && console.warn(
560
644
  "When TID client is pass as prop, any client configuration property sent directly to the TID Provider component will be ignored"
561
- ), p.current = !0, (async () => {
645
+ ), E.current = !0, (async () => {
562
646
  try {
563
647
  let o;
564
- if (ne(void 0, N)) {
565
- const { authState: l } = await s.handleCallback();
566
- o = await s.getUser(), k != null && k(l);
648
+ if (ne(void 0, W)) {
649
+ const h = await a.handleCallback();
650
+ o = await a.getUser(), d != null && d(h);
567
651
  } else
568
- await s.loadUserSession(), o = await s.getUser();
569
- u({ type: "INIT", user: o });
652
+ await a.loadUserSession(), o = await a.getUser();
653
+ y({ type: "INIT", user: o });
570
654
  } catch (o) {
571
- let l = o;
572
- typeof o == "string" && (l = new Error(o)), u({ type: "ERROR", error: l });
655
+ let h = o;
656
+ typeof o == "string" && (h = new Error(o)), y({ type: "ERROR", error: h });
573
657
  }
574
658
  })());
575
- }, [s, k]);
576
- const _ = y(async () => {
577
- const o = await s.getAccessTokenSilently(), l = await s.getUser();
578
- return u({
659
+ }, [a, d]);
660
+ const S = m(async () => {
661
+ const o = await a.getAccessTokenSilently(), h = await a.getUser();
662
+ return y({
579
663
  type: "GET_ACCESS_TOKEN_COMPLETE",
580
- user: l
664
+ user: h
581
665
  }), o;
582
- }, [s]), C = y(async () => {
583
- const o = await s.getTokens(), l = await s.getUser();
584
- return u({
666
+ }, [a]), P = m(async () => {
667
+ const o = await a.getTokens(), h = await a.getUser();
668
+ return y({
585
669
  type: "GET_TOKENS_COMPLETE",
586
- user: l
670
+ user: h
587
671
  }), o;
588
- }, [s]), S = y(
672
+ }, [a]), _ = m(
589
673
  async (o) => {
590
- await s.loginWithRedirect(o);
674
+ await a.loginWithRedirect(o);
591
675
  },
592
- [s]
593
- ), I = y(
676
+ [a]
677
+ ), C = m(
594
678
  async (o) => {
595
- await s.logout(o), (o == null ? void 0 : o.disabledAutoRedirect) != null && o.disabledAutoRedirect && u({
679
+ await a.logout(o), (o == null ? void 0 : o.disabledAutoRedirect) != null && o.disabledAutoRedirect && y({
596
680
  type: "LOGOUT"
597
681
  });
598
682
  },
599
- [s]
600
- ), P = y(
683
+ [a]
684
+ ), I = m(
601
685
  async (o) => {
602
- const { authState: l } = await s.handleCallback(o), V = await s.getUser();
603
- return u({
686
+ const h = await a.handleCallback(o), F = await a.getUser();
687
+ return y({
604
688
  type: "HANDLE_CALLBACK_COMPLETE",
605
- user: V
606
- }), {
607
- authState: l
608
- };
689
+ user: F
690
+ }), h;
609
691
  },
610
- [s]
692
+ [a]
611
693
  ), G = M(
612
694
  () => ({
613
- ...f,
614
- getAccessTokenSilently: _,
615
- getTokens: C,
616
- loginWithRedirect: S,
617
- handleCallback: P,
618
- logout: I
695
+ ...k,
696
+ getAccessTokenSilently: S,
697
+ getTokens: P,
698
+ loginWithRedirect: _,
699
+ handleCallback: I,
700
+ logout: C
619
701
  }),
620
- [f, _, C, S, P, I]
702
+ [k, S, P, _, I, C]
621
703
  );
622
- return /* @__PURE__ */ K(E.Provider, { value: G, children: e });
623
- }, me = W, pe = fe, _e = ({ renderComponent: i, loader: e }) => {
624
- const { isAuthenticated: t, isLoading: n, loginWithRedirect: r } = W();
625
- return D(() => {
626
- !n && !t && (async () => await r())();
627
- }, [n, t, r]), t ? i : e || /* @__PURE__ */ K(Z, {});
704
+ return /* @__PURE__ */ V(w.Provider, { value: G, children: e });
705
+ }, Ee = K, Se = ke, Pe = ({ renderComponent: i, loader: e }) => {
706
+ const { isAuthenticated: t, isLoading: r, loginWithRedirect: n } = K();
707
+ return N(() => {
708
+ !r && !t && (async () => await n())();
709
+ }, [r, t, n]), t ? i : e || /* @__PURE__ */ V(j, {});
628
710
  };
629
711
  export {
630
- _e as AuthenticationGuard,
631
- de as TIDClient,
632
- E as TIDContext,
633
- pe as TIDProvider,
634
- me as useAuth
712
+ Pe as AuthenticationGuard,
713
+ he as TIDClient,
714
+ w as TIDContext,
715
+ Se as TIDProvider,
716
+ Ee as useAuth
635
717
  };
@@ -1 +1 @@
1
- (function(o,c){typeof exports=="object"&&typeof module<"u"?c(exports,require("@trimble-oss/trimble-id"),require("es-cookie"),require("jwt-decode"),require("react"),require("react/jsx-runtime")):typeof define=="function"&&define.amd?define(["exports","@trimble-oss/trimble-id","es-cookie","jwt-decode","react","react/jsx-runtime"],c):(o=typeof globalThis<"u"?globalThis:o||self,c(o.ReactTID={},o.trimbleId,o.esCookie,o.jwt_decode,o.React,o.jsxRuntime))})(this,function(o,c,k,W,l,p){"use strict";var le=Object.defineProperty;var de=(o,c,k)=>c in o?le(o,c,{enumerable:!0,configurable:!0,writable:!0,value:k}):o[c]=k;var h=(o,c,k)=>(de(o,typeof c!="symbol"?c+"":c,k),k);function G(i){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(i){for(const t in i)if(t!=="default"){const n=Object.getOwnPropertyDescriptor(i,t);Object.defineProperty(e,t,n.get?n:{enumerable:!0,get:()=>i[t]})}}return e.default=i,Object.freeze(e)}const C=G(k);class V{constructor(){h(this,"generateCache",function(){const e={token:void 0,user:void 0};return{async getToken(){return e.token},async getUser(){return e.user},async storeToken(t){e.token=t},async storeUser(t){e.user=t},clear(){return e.token=void 0,e.user=void 0,Promise.resolve(void 0)}}}())}}class F{constructor(){h(this,"cacheStorage");this.cacheStorage=new V().generateCache}async setToken(e){await this.cacheStorage.storeToken(e)}async setUser(e){await this.cacheStorage.storeUser(e)}async getUser(){return this.cacheStorage.getUser()}async getToken(){return this.cacheStorage.getToken()}async clear(){await this.cacheStorage.clear()}}const S=5*6e4,j=["code","state"],P=i=>i.split("?")[0],q=i=>i.split("?")[1],I=i=>{let e=i;if(!i.startsWith("?"))try{e=new URL(i).search}catch{}return new URLSearchParams(e)},B=(i=window.location.href,e)=>{if(e!=null){const n=P(e),r=P(i??"");if(n!==r)return!1}const t=I(i);for(const n of j)if(!t.has(n))return!1;return!0},$=i=>({id:i.sub,name:`${i.given_name} ${i.family_name}`,given_name:i.given_name,family_name:i.family_name,picture:i.picture,email:i.email,email_verified:i.email_verified}),H="@TID_COOKIE";class z{get(e){const t=C.get(e);if(t==null)throw new Error("Cookie not found");return JSON.parse(t)}set(e,t,n){let r={};window.location.protocol==="https:"&&(r={secure:!0,sameSite:"none"}),n!=null&&n.expires&&(r.expires=n.expires),n!=null&&n.domain&&(r.domain=n.domain),C.set(e,JSON.stringify(t),r)}remove(e,t){const n={};t!=null&&t.domain&&(n.domain=t.domain),C.remove(e,n)}}class Q{constructor(e){h(this,"cookieKey");h(this,"cookiesStorage");this.cookieKey=`${H}.${e.clientId}`,this.cookiesStorage=new z}save(e){this.cookiesStorage.set(this.cookieKey,e,{expires:1})}get(){try{return this.cookiesStorage.get(this.cookieKey)}catch{return}}clear(){this.cookiesStorage.remove(this.cookieKey)}}class R extends Error{}class M extends Error{}class J extends Error{}const u="@trimble-oss/trimble-id-react",g="1.0.0-rc.2",X={configurationEndpoint:"",clientId:"",redirectUrl:"",logoutRedirectUrl:"",scopes:[]};class x{constructor(e){h(this,"tokenProvider");h(this,"cacheManager");h(this,"cookiesManager");h(this,"clientId");h(this,"redirectUrl");h(this,"analyticshttpclient");const{config:t=X}=e;if(this.redirectUrl=t.redirectUrl,t.configurationEndpoint==null||t.configurationEndpoint=="")throw new Error("Configuration endpoint not defined");if(t.clientId==null||t.clientId=="")throw new Error("Consumer key is not defined");this.cookiesManager=new Q({clientId:t.clientId});const n=this.cookiesManager.get(),r=new c.OpenIdEndpointProvider(t.configurationEndpoint);let d=new c.AuthorizationCodeGrantTokenProvider(r,t.clientId,t.redirectUrl).WithScopes(t.scopes);t.logoutRedirectUrl&&(d=d.WithLogoutRedirect(t.logoutRedirectUrl)),(n==null?void 0:n.code_verifier)!=null&&(d=d.WithProofKeyForCodeExchange(n.code_verifier)),this.tokenProvider=d,this.cacheManager=new F,this.clientId=t.clientId,this.analyticshttpclient=c.AnalyticsHttpClient,this.analyticshttpclient.sendInitEvent("TIDClient",this.clientId,u,g)}async loginWithRedirect(e){this.analyticshttpclient.sendMethodEvent(this.loginWithRedirect.name,this.clientId,u,g);const{onRedirect:t}=e||{},n=c.AuthorizationCodeGrantTokenProvider.GenerateCodeVerifier();this.cookiesManager.save({code_verifier:n}),this.tokenProvider=this.tokenProvider.WithProofKeyForCodeExchange(n);const r=await this.tokenProvider.GetOAuthRedirect("state");t!=null?t(r):window.location.assign(r)}async handleCallback(e=window.location.href){const t=this.cookiesManager.get();if(t==null||(t==null?void 0:t.code_verifier)==null)throw new J("Code verifier not available");const n=q(e),r=I(e),d=r.get("identity_provider")??"",T=r.get("state")??"";return await this.tokenProvider.ValidateQuery(n),await this.generateToken(d),{authState:T}}async generateToken(e){var s,w;const t=await this.tokenProvider.RetrieveToken(),n=await this.tokenProvider.RetrieveRefreshToken(),r=await this.tokenProvider.RetrieveIdToken(),d=await this.tokenProvider.RetrieveTokenExpiry(),v=new Date(d).getTime(),m=(w=(s=this.tokenProvider)==null?void 0:s._scopes)==null?void 0:w.join(" ");await this.cacheManager.setToken({scope:m,state:"",session_state:"",identity_provider:e,token_type:"bearer",access_token:t,refresh_token:n,id_token:r,expires_at:v});const U=W(r),_=$(U);await this.cacheManager.setUser(_),await this.reloadCodeVerifier()}async reloadCodeVerifier(){const e=await this.tokenProvider.RetrieveCodeVerifier();this.cookiesManager.save({code_verifier:e}),this.tokenProvider=this.tokenProvider.WithProofKeyForCodeExchange(e)}async getUser(){return this.analyticshttpclient.sendMethodEvent(this.getUser.name,this.clientId,u,g),await this.cacheManager.getUser()}async getAccessTokenSilently(){this.analyticshttpclient.sendMethodEvent(this.getAccessTokenSilently.name,this.clientId,u,g);let e=await this.cacheManager.getToken();if(e==null)throw this.analyticshttpclient.sendExceptionEvent(this.getAccessTokenSilently.name,"No token available",this.clientId,u,g),new M("No token available");const t=new Date(new Date().getTime()+S);if((e==null?void 0:e.expires_at)==null||(e==null?void 0:e.expires_at)<t.getTime()){try{await this.tokenProvider.RetrieveToken()}catch(n){throw this.analyticshttpclient.sendExceptionEvent(this.getAccessTokenSilently.name,n.message,this.clientId,u,g),new R(n.message)}await this.generateToken((e==null?void 0:e.identity_provider)??""),e=await this.cacheManager.getToken()}return(e==null?void 0:e.access_token)||""}async getTokens(){this.analyticshttpclient.sendMethodEvent(this.getTokens.name,this.clientId,u,g);let e=await this.cacheManager.getToken();if(e==null)throw this.analyticshttpclient.sendExceptionEvent(this.getTokens.name,"No token available",this.clientId,u,g),new M("No token available");const t=new Date(new Date().getTime()+S);if((e==null?void 0:e.expires_at)==null||(e==null?void 0:e.expires_at)<t.getTime()){try{await this.tokenProvider.RetrieveToken()}catch(n){throw this.analyticshttpclient.sendExceptionEvent(this.getTokens.name,n.message,this.clientId,u,g),new R(n.message)}await this.generateToken((e==null?void 0:e.identity_provider)??""),e=await this.cacheManager.getToken()}return{access_token:(e==null?void 0:e.access_token)||"",expires_at:(e==null?void 0:e.expires_at)||0,id_token:(e==null?void 0:e.id_token)||""}}async logout(e){this.analyticshttpclient.sendMethodEvent(this.logout.name,this.clientId,u,g);const{onRedirect:t,disabledAutoRedirect:n}=e||{};this.cacheManager&&await this.cacheManager.clear(),this.cookiesManager&&this.cookiesManager.clear();const r=await this.tokenProvider.GetOAuthLogoutRedirect("state");if(t!=null)return t(r);n||window.location.assign(r)}async checkSession(){try{await this.getAccessTokenSilently()}catch{return!1}return!0}async loadUserSession(){await this.loadCacheSessionIntoSDK(),await this.checkSession()||await this.cacheManager.clear()}async loadCacheSessionIntoSDK(){const e=await this.cacheManager.getToken();if(e==null)return;const t=e.access_token,n=e.refresh_token||"",r=e.id_token,d=e.expires_at;this.tokenProvider=this.tokenProvider.WithAccessToken(t,d).WithRefreshToken(n).WithIdToken(r)}getBearerTokenHttpClient(e){return new c.BearerTokenHttpClientProvider(this.tokenProvider,e)}getRedirectUrl(){return this.redirectUrl}}const E=l.createContext(null),A=()=>l.useContext(E)??{},Y=(i,e)=>{switch(e.type){case"INIT":return{...i,isLoading:!1,isAuthenticated:e.user!=null,user:e.user,error:void 0};case"GET_TOKENS_COMPLETE":case"HANDLE_CALLBACK_COMPLETE":case"GET_ACCESS_TOKEN_COMPLETE":return{...i,isLoading:!1,isAuthenticated:e.user!=null,user:e.user,error:void 0};case"LOGOUT":return{...i,isAuthenticated:!1,user:void 0};case"ERROR":return{...i,isLoading:!1,error:e.error}}},Z={isLoading:!0,isAuthenticated:!1},ee=i=>{if(i==null||Object.keys(i).length<=0)return!0;const e=Object.keys(i);for(const t of e)if(i[t]!=null&&i[t]!=="")return!1;return!0},te=i=>!ee(i.config),ie=i=>{const{children:e,configurationEndpoint:t,clientId:n,redirectUrl:r,logoutRedirectUrl:d,scopes:T,onRedirectCallback:v,checkRedirectUrlMatch:m}=i;if(l.useContext(E)!=null)throw new Error("TID Provider already defined");const _={config:{configurationEndpoint:t??"",clientId:n??"",redirectUrl:r??"",logoutRedirectUrl:d??"",scopes:T??[""]}},[s]=l.useState(i.tidClient??new x(_)),[w,y]=l.useReducer(Y,Z),O=l.useRef(!1),oe=l.useMemo(()=>m?s.getRedirectUrl():void 0,[m,s]);l.useEffect(()=>{O.current||(i.tidClient!=null&&te({config:{configurationEndpoint:t,clientId:n,redirectUrl:r,logoutRedirectUrl:d,scopes:T}})&&console.warn("When TID client is pass as prop, any client configuration property sent directly to the TID Provider component will be ignored"),O.current=!0,(async()=>{try{let a;if(B(void 0,oe)){const{authState:f}=await s.handleCallback();a=await s.getUser(),v!=null&&v(f)}else await s.loadUserSession(),a=await s.getUser();y({type:"INIT",user:a})}catch(a){let f=a;typeof a=="string"&&(f=new Error(a)),y({type:"ERROR",error:f})}})())},[s,v]);const D=l.useCallback(async()=>{const a=await s.getAccessTokenSilently(),f=await s.getUser();return y({type:"GET_ACCESS_TOKEN_COMPLETE",user:f}),a},[s]),b=l.useCallback(async()=>{const a=await s.getTokens(),f=await s.getUser();return y({type:"GET_TOKENS_COMPLETE",user:f}),a},[s]),L=l.useCallback(async a=>{await s.loginWithRedirect(a)},[s]),K=l.useCallback(async a=>{await s.logout(a),(a==null?void 0:a.disabledAutoRedirect)!=null&&a.disabledAutoRedirect&&y({type:"LOGOUT"})},[s]),N=l.useCallback(async a=>{const{authState:f}=await s.handleCallback(a),ce=await s.getUser();return y({type:"HANDLE_CALLBACK_COMPLETE",user:ce}),{authState:f}},[s]),ae=l.useMemo(()=>({...w,getAccessTokenSilently:D,getTokens:b,loginWithRedirect:L,handleCallback:N,logout:K}),[w,D,b,L,N,K]);return p.jsx(E.Provider,{value:ae,children:e})},ne=A,re=ie,se=({renderComponent:i,loader:e})=>{const{isAuthenticated:t,isLoading:n,loginWithRedirect:r}=A();return l.useEffect(()=>{!n&&!t&&(async()=>await r())()},[n,t,r]),t?i:e||p.jsx(p.Fragment,{})};o.AuthenticationGuard=se,o.TIDClient=x,o.TIDContext=E,o.TIDProvider=re,o.useAuth=ne,Object.defineProperty(o,Symbol.toStringTag,{value:"Module"})});
1
+ (function(o,l){typeof exports=="object"&&typeof module<"u"?l(exports,require("@trimble-oss/trimble-id"),require("es-cookie"),require("jwt-decode"),require("react"),require("react/jsx-runtime")):typeof define=="function"&&define.amd?define(["exports","@trimble-oss/trimble-id","es-cookie","jwt-decode","react","react/jsx-runtime"],l):(o=typeof globalThis<"u"?globalThis:o||self,l(o.ReactTID={},o.trimbleId,o.esCookie,o.jwt_decode,o.React,o.jsxRuntime))})(this,function(o,l,p,W,d,E){"use strict";var de=Object.defineProperty;var he=(o,l,p)=>l in o?de(o,l,{enumerable:!0,configurable:!0,writable:!0,value:p}):o[l]=p;var u=(o,l,p)=>(he(o,typeof l!="symbol"?l+"":l,p),p);function G(i){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(i){for(const t in i)if(t!=="default"){const n=Object.getOwnPropertyDescriptor(i,t);Object.defineProperty(e,t,n.get?n:{enumerable:!0,get:()=>i[t]})}}return e.default=i,Object.freeze(e)}const S=G(p);class F{constructor(){u(this,"generateCache",function(){const e={token:void 0,user:void 0};return{async getToken(){return e.token},async getUser(){return e.user},async storeToken(t){e.token=t},async storeUser(t){e.user=t},clear(){return e.token=void 0,e.user=void 0,Promise.resolve(void 0)}}}())}}class j{constructor(){u(this,"cacheStorage");this.cacheStorage=new F().generateCache}async setToken(e){await this.cacheStorage.storeToken(e)}async setUser(e){await this.cacheStorage.storeUser(e)}async getUser(){return this.cacheStorage.getUser()}async getToken(){return this.cacheStorage.getToken()}async clear(){await this.cacheStorage.clear()}}const C=5*6e4,$=["code","state"],_=10*60*1e3,I=i=>i.split("?")[0],q=i=>i.split("?")[1],M=i=>{let e=i;if(!i.startsWith("?"))try{e=new URL(i).search}catch{}return new URLSearchParams(e)},B=(i=window.location.href,e)=>{if(e!=null){const n=I(e),r=I(i??"");if(n!==r)return!1}const t=M(i);for(const n of $)if(!t.has(n))return!1;return!0},J=i=>({id:i.sub,name:`${i.given_name} ${i.family_name}`,given_name:i.given_name,family_name:i.family_name,picture:i.picture,email:i.email,email_verified:i.email_verified}),H="@TID_COOKIE";class z{get(e){const t=S.get(e);if(t==null)throw new Error("Cookie not found");return JSON.parse(t)}set(e,t,n){let r={};window.location.protocol==="https:"&&(r={secure:!0,sameSite:"none"}),n!=null&&n.expires&&(r.expires=n.expires),n!=null&&n.domain&&(r.domain=n.domain),S.set(e,JSON.stringify(t),r)}remove(e,t){const n={};t!=null&&t.domain&&(n.domain=t.domain),S.remove(e,n)}}class Q{constructor(e){u(this,"cookieKey");u(this,"cookiesStorage");this.cookieKey=`${H}.${e.clientId}`,this.cookiesStorage=new z}save(e){const n={...this.get()||{},...e};this.cookiesStorage.set(this.cookieKey,n,{expires:1})}getStatePayload(){const e=this.get();return e==null?void 0:e.state_payload}clearStatePayload(){const e=this.get();e&&(delete e.state_payload,this.cookiesStorage.set(this.cookieKey,e,{expires:1}))}get(){try{return this.cookiesStorage.get(this.cookieKey)}catch{return}}clear(){this.cookiesStorage.remove(this.cookieKey)}}class R extends Error{}class x extends Error{}class X extends Error{}const g="@trimble-oss/trimble-id-react",f="1.0.1",Y={configurationEndpoint:"",clientId:"",redirectUrl:"",logoutRedirectUrl:"",scopes:[]};class A{constructor(e){u(this,"tokenProvider");u(this,"cacheManager");u(this,"cookiesManager");u(this,"clientId");u(this,"redirectUrl");u(this,"analyticshttpclient");const{config:t=Y}=e;if(this.redirectUrl=t.redirectUrl,t.configurationEndpoint==null||t.configurationEndpoint=="")throw new Error("Configuration endpoint not defined");if(t.clientId==null||t.clientId=="")throw new Error("Consumer key is not defined");this.cookiesManager=new Q({clientId:t.clientId});const n=this.cookiesManager.get(),r=new l.OpenIdEndpointProvider(t.configurationEndpoint);let c=new l.AuthorizationCodeGrantTokenProvider(r,t.clientId,t.redirectUrl).WithScopes(t.scopes);t.logoutRedirectUrl&&(c=c.WithLogoutRedirect(t.logoutRedirectUrl)),(n==null?void 0:n.code_verifier)!=null&&(c=c.WithProofKeyForCodeExchange(n.code_verifier)),this.tokenProvider=c,this.cacheManager=new j,this.clientId=t.clientId,this.analyticshttpclient=l.AnalyticsHttpClient,this.analyticshttpclient.sendInitEvent("TIDClient",this.clientId,g,f),this.cleanupExpiredState()}cleanupExpiredState(){try{const e=this.cookiesManager.getStatePayload();if(e){const t=atob(e),n=JSON.parse(t);Date.now()-n.timestamp>_&&this.cookiesManager.clearStatePayload()}}catch{this.cookiesManager.clearStatePayload()}}async loginWithRedirect(e){this.analyticshttpclient.sendMethodEvent(this.loginWithRedirect.name,this.clientId,g,f);const{onRedirect:t}=e||{},n=window.location.pathname+window.location.search,r=this.createStatePayload(n);this.cookiesManager.save({state_payload:r});const c=l.AuthorizationCodeGrantTokenProvider.GenerateCodeVerifier();this.cookiesManager.save({code_verifier:c}),this.tokenProvider=this.tokenProvider.WithProofKeyForCodeExchange(c);const h=await this.tokenProvider.GetOAuthRedirect(r);t!=null?t(h):window.location.assign(h)}async handleCallback(e=window.location.href){const t=this.cookiesManager.get();if(t==null||(t==null?void 0:t.code_verifier)==null)throw new X("Code verifier not available");const n=q(e),r=M(e),c=r.get("identity_provider")??"",h=r.get("state")??"",y=this.validateStatePayload(h);if(!y.isValid)throw new Error(`State validation failed: ${y.error}`);return await this.tokenProvider.ValidateQuery(n),await this.generateToken(c),{authState:h,returnTo:y.redirectTo}}async generateToken(e){var a,v;const t=await this.tokenProvider.RetrieveToken(),n=await this.tokenProvider.RetrieveRefreshToken(),r=await this.tokenProvider.RetrieveIdToken(),c=await this.tokenProvider.RetrieveTokenExpiry(),y=new Date(c).getTime(),T=(v=(a=this.tokenProvider)==null?void 0:a._scopes)==null?void 0:v.join(" ");await this.cacheManager.setToken({scope:T,state:"",session_state:"",identity_provider:e,token_type:"bearer",access_token:t,refresh_token:n,id_token:r,expires_at:y});const U=W(r),P=J(U);await this.cacheManager.setUser(P),await this.reloadCodeVerifier()}async reloadCodeVerifier(){const e=await this.tokenProvider.RetrieveCodeVerifier();this.cookiesManager.save({code_verifier:e}),this.tokenProvider=this.tokenProvider.WithProofKeyForCodeExchange(e)}createStatePayload(e){const t=this.generateNonce(),n=Date.now();return btoa(JSON.stringify({redirectTo:e,timestamp:n,nonce:t}))}generateNonce(){const e=new Uint8Array(16);return crypto.getRandomValues(e),Array.from(e,t=>t.toString(16).padStart(2,"0")).join("")}validateStatePayload(e){try{if(!e||e.trim()==="")return{isValid:!1,error:"Empty state parameter"};const t=this.cookiesManager.getStatePayload();if(!t)return{isValid:!1,error:"No stored state found"};const n=atob(e),r=JSON.parse(n),c=atob(t),h=JSON.parse(c);if(!r.nonce||!r.timestamp||!r.redirectTo)return{isValid:!1,error:"Invalid state payload structure"};if(r.nonce!==h.nonce)return{isValid:!1,error:"State nonce mismatch"};const T=Date.now()-r.timestamp;return T>_?{isValid:!1,error:"State expired - possible replay attack"}:T<0?{isValid:!1,error:"State timestamp is in the future - possible replay attack"}:(this.cookiesManager.clearStatePayload(),{isValid:!0,redirectTo:r.redirectTo})}catch{return{isValid:!1,error:"Invalid state format"}}}async getUser(){return this.analyticshttpclient.sendMethodEvent(this.getUser.name,this.clientId,g,f),await this.cacheManager.getUser()}async getAccessTokenSilently(){this.analyticshttpclient.sendMethodEvent(this.getAccessTokenSilently.name,this.clientId,g,f);let e=await this.cacheManager.getToken();if(e==null)throw this.analyticshttpclient.sendExceptionEvent(this.getAccessTokenSilently.name,"No token available",this.clientId,g,f),new x("No token available");const t=new Date(new Date().getTime()+C);if((e==null?void 0:e.expires_at)==null||(e==null?void 0:e.expires_at)<t.getTime()){try{await this.tokenProvider.RetrieveToken()}catch(n){throw this.analyticshttpclient.sendExceptionEvent(this.getAccessTokenSilently.name,n.message,this.clientId,g,f),new R(n.message)}await this.generateToken((e==null?void 0:e.identity_provider)??""),e=await this.cacheManager.getToken()}return(e==null?void 0:e.access_token)||""}async getTokens(){this.analyticshttpclient.sendMethodEvent(this.getTokens.name,this.clientId,g,f);let e=await this.cacheManager.getToken();if(e==null)throw this.analyticshttpclient.sendExceptionEvent(this.getTokens.name,"No token available",this.clientId,g,f),new x("No token available");const t=new Date(new Date().getTime()+C);if((e==null?void 0:e.expires_at)==null||(e==null?void 0:e.expires_at)<t.getTime()){try{await this.tokenProvider.RetrieveToken()}catch(n){throw this.analyticshttpclient.sendExceptionEvent(this.getTokens.name,n.message,this.clientId,g,f),new R(n.message)}await this.generateToken((e==null?void 0:e.identity_provider)??""),e=await this.cacheManager.getToken()}return{access_token:(e==null?void 0:e.access_token)||"",expires_at:(e==null?void 0:e.expires_at)||0,id_token:(e==null?void 0:e.id_token)||""}}async logout(e){this.analyticshttpclient.sendMethodEvent(this.logout.name,this.clientId,g,f);const{onRedirect:t,disabledAutoRedirect:n}=e||{};this.cacheManager&&await this.cacheManager.clear(),this.cookiesManager&&this.cookiesManager.clear();const r=await this.tokenProvider.GetOAuthLogoutRedirect("state");if(t!=null)return t(r);n||window.location.assign(r)}async checkSession(){try{await this.getAccessTokenSilently()}catch{return!1}return!0}async loadUserSession(){await this.loadCacheSessionIntoSDK(),await this.checkSession()||await this.cacheManager.clear()}async loadCacheSessionIntoSDK(){const e=await this.cacheManager.getToken();if(e==null)return;const t=e.access_token,n=e.refresh_token||"",r=e.id_token,c=e.expires_at;this.tokenProvider=this.tokenProvider.WithAccessToken(t,c).WithRefreshToken(n).WithIdToken(r)}getBearerTokenHttpClient(e){return new l.BearerTokenHttpClientProvider(this.tokenProvider,e)}getRedirectUrl(){return this.redirectUrl}}const w=d.createContext(null),O=()=>d.useContext(w)??{},Z=(i,e)=>{switch(e.type){case"INIT":return{...i,isLoading:!1,isAuthenticated:e.user!=null,user:e.user,error:void 0};case"GET_TOKENS_COMPLETE":case"HANDLE_CALLBACK_COMPLETE":case"GET_ACCESS_TOKEN_COMPLETE":return{...i,isLoading:!1,isAuthenticated:e.user!=null,user:e.user,error:void 0};case"LOGOUT":return{...i,isAuthenticated:!1,user:void 0};case"ERROR":return{...i,isLoading:!1,error:e.error}}},ee={isLoading:!0,isAuthenticated:!1},te=i=>{if(i==null||Object.keys(i).length<=0)return!0;const e=Object.keys(i);for(const t of e)if(i[t]!=null&&i[t]!=="")return!1;return!0},ie=i=>!te(i.config),ne=i=>{const{children:e,configurationEndpoint:t,clientId:n,redirectUrl:r,logoutRedirectUrl:c,scopes:h,onRedirectCallback:y,checkRedirectUrlMatch:T}=i;if(d.useContext(w)!=null)throw new Error("TID Provider already defined");const P={config:{configurationEndpoint:t??"",clientId:n??"",redirectUrl:r??"",logoutRedirectUrl:c??"",scopes:h??[""]}},[a]=d.useState(i.tidClient??new A(P)),[v,m]=d.useReducer(Z,ee),D=d.useRef(!1),se=d.useMemo(()=>T?a.getRedirectUrl():void 0,[T,a]);d.useEffect(()=>{D.current||(i.tidClient!=null&&ie({config:{configurationEndpoint:t,clientId:n,redirectUrl:r,logoutRedirectUrl:c,scopes:h}})&&console.warn("When TID client is pass as prop, any client configuration property sent directly to the TID Provider component will be ignored"),D.current=!0,(async()=>{try{let s;if(B(void 0,se)){const k=await a.handleCallback();s=await a.getUser(),y!=null&&y(k)}else await a.loadUserSession(),s=await a.getUser();m({type:"INIT",user:s})}catch(s){let k=s;typeof s=="string"&&(k=new Error(s)),m({type:"ERROR",error:k})}})())},[a,y]);const b=d.useCallback(async()=>{const s=await a.getAccessTokenSilently(),k=await a.getUser();return m({type:"GET_ACCESS_TOKEN_COMPLETE",user:k}),s},[a]),L=d.useCallback(async()=>{const s=await a.getTokens(),k=await a.getUser();return m({type:"GET_TOKENS_COMPLETE",user:k}),s},[a]),N=d.useCallback(async s=>{await a.loginWithRedirect(s)},[a]),V=d.useCallback(async s=>{await a.logout(s),(s==null?void 0:s.disabledAutoRedirect)!=null&&s.disabledAutoRedirect&&m({type:"LOGOUT"})},[a]),K=d.useCallback(async s=>{const k=await a.handleCallback(s),le=await a.getUser();return m({type:"HANDLE_CALLBACK_COMPLETE",user:le}),k},[a]),ce=d.useMemo(()=>({...v,getAccessTokenSilently:b,getTokens:L,loginWithRedirect:N,handleCallback:K,logout:V}),[v,b,L,N,K,V]);return E.jsx(w.Provider,{value:ce,children:e})},re=O,ae=ne,oe=({renderComponent:i,loader:e})=>{const{isAuthenticated:t,isLoading:n,loginWithRedirect:r}=O();return d.useEffect(()=>{!n&&!t&&(async()=>await r())()},[n,t,r]),t?i:e||E.jsx(E.Fragment,{})};o.AuthenticationGuard=oe,o.TIDClient=A,o.TIDContext=w,o.TIDProvider=ae,o.useAuth=re,Object.defineProperty(o,Symbol.toStringTag,{value:"Module"})});
package/package.json CHANGED
@@ -1,8 +1,7 @@
1
-
2
1
  {
3
2
  "name": "@trimble-oss/trimble-id-react",
4
3
  "private": false,
5
- "version": "1.0.0-rc.3",
4
+ "version": "1.0.1",
6
5
  "homepage": "https://github.com/trimble-oss/trimble-id-sdk-docs-for-react",
7
6
  "author": "Trimble developers <developers@trimble.com>",
8
7
  "license": "MIT",