dauth-context-react 0.2.120 → 0.2.122

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,68 +1,291 @@
1
- # IMPORTANT
2
- This package
1
+ # dauth-context-react
3
2
 
4
- # Dauth React Context
5
- This is a simple example of how to use a custom `DauthProvider`, and how to utilize the `Dauth` authentication context in your React application.
3
+ React context provider and hook for token-based authentication with the [DAuth](https://dauth.ovh) service. Provides `DauthProvider` and `useDauth()` hook for seamless integration of DAuth tenant authentication into React applications.
6
4
 
7
5
  ## Installation
8
- You can install this package via npm. Make sure you have Node.js installed on your machine. Then, in your project directory, run the following command:
6
+
9
7
  ```bash
10
8
  npm install dauth-context-react
9
+ # or
10
+ yarn add dauth-context-react
11
11
  ```
12
12
 
13
- ## Code Example
14
- Here's a simplified example of how to use Dauth Provider:
15
- ```
13
+ ### Peer Dependencies
14
+
15
+ - `react` >= 16
16
+
17
+ ## Quick Start
18
+
19
+ ### 1. Wrap your app with `DauthProvider`
20
+
21
+ ```tsx
16
22
  import React from 'react';
17
- import ReactDOM from 'react-dom';
23
+ import ReactDOM from 'react-dom/client';
18
24
  import { RouterProvider } from 'react-router-dom';
25
+ import { DauthProvider } from 'dauth-context-react';
19
26
  import router from './router/router';
20
- import { DauthProvider } from 'dauth-context-react';
21
27
 
22
- ReactDOM.render(
28
+ ReactDOM.createRoot(document.getElementById('root')!).render(
23
29
  <DauthProvider
24
- domainName={domainName}
25
- sid={sid}
26
- ssid={ssid}
30
+ domainName="your-domain-name"
31
+ tsk="your-tenant-secret-key"
27
32
  >
28
- <RouterProvider
29
- router={router}
30
- fallbackElement={<></>}
31
- />
32
- </DauthProvider>,
33
- document.getElementById('root')
33
+ <RouterProvider router={router} fallbackElement={<></>} />
34
+ </DauthProvider>
34
35
  );
35
36
  ```
36
37
 
37
- ## Example: Using Authentication Context
38
- Here's an example of how to use the authentication context (dauth-context-react) in your components:
38
+ ### 2. Use the `useDauth()` hook in your components
39
+
40
+ ```tsx
41
+ import { useDauth } from 'dauth-context-react';
42
+
43
+ function MyComponent() {
44
+ const {
45
+ isAuthenticated,
46
+ isLoading,
47
+ user,
48
+ loginWithRedirect,
49
+ logout,
50
+ getAccessToken,
51
+ } = useDauth();
52
+
53
+ if (isLoading) return <div>Loading...</div>;
54
+
55
+ if (isAuthenticated) {
56
+ return (
57
+ <div>
58
+ <p>Hello, {user.name}!</p>
59
+ <button onClick={logout}>Logout</button>
60
+ </div>
61
+ );
62
+ }
63
+
64
+ return <button onClick={loginWithRedirect}>Login</button>;
65
+ }
66
+ ```
67
+
68
+ ## API
69
+
70
+ ### `<DauthProvider>`
71
+
72
+ | Prop | Type | Description |
73
+ |---|---|---|
74
+ | `domainName` | `string` | Your DAuth domain name (used for API routing and tenant resolution) |
75
+ | `tsk` | `string` | Tenant Secret Key for JWT verification |
76
+ | `children` | `React.ReactNode` | Child components |
77
+
78
+ ### `useDauth()` Hook
79
+
80
+ Returns the current authentication state and action methods:
81
+
82
+ | Property | Type | Description |
83
+ |---|---|---|
84
+ | `user` | `IDauthUser` | Authenticated user object |
85
+ | `domain` | `IDauthDomainState` | Domain configuration (name, loginRedirect, allowedOrigins) |
86
+ | `isLoading` | `boolean` | `true` while initial auth check is in progress |
87
+ | `isAuthenticated` | `boolean` | `true` if user is authenticated |
88
+ | `loginWithRedirect` | `() => void` | Redirects to the DAuth tenant sign-in page |
89
+ | `logout` | `() => void` | Clears token and resets auth state |
90
+ | `getAccessToken` | `() => Promise<string>` | Returns a fresh access token (refreshes if needed) |
91
+ | `updateUser` | `(fields: Partial<IDauthUser>) => Promise<boolean>` | Updates user profile fields |
92
+ | `updateUserWithRedirect` | `() => void` | Redirects to the DAuth user update page |
93
+ | `sendEmailVerification` | `() => Promise<boolean>` | Sends a verification email to the user |
94
+ | `sendEmailVerificationStatus` | `{ status: IActionStatus, isLoading: boolean }` | Status of the email verification request |
95
+
96
+ ### `IDauthUser`
97
+
98
+ ```typescript
99
+ interface IDauthUser {
100
+ _id: string;
101
+ name: string;
102
+ lastname: string;
103
+ nickname: string;
104
+ email: string;
105
+ isVerified: boolean;
106
+ language: string;
107
+ avatar: { id: string; url: string };
108
+ role: string;
109
+ telPrefix: string;
110
+ telSuffix: string;
111
+ createdAt: Date;
112
+ updatedAt: Date;
113
+ lastLogin: Date;
114
+ }
115
+ ```
116
+
117
+ ## Real-World Integration Example
118
+
119
+ This is the pattern used in `easymediacloud-frontend-react`, which delegates all user authentication to dauth.
120
+
121
+ ### 1. Provider hierarchy in `App.tsx`
122
+
123
+ ```tsx
124
+ // src/App.tsx
125
+ import { DauthProvider } from 'dauth-context-react';
126
+ import { LicensesProvider } from './context/licenses/LicensesProvider';
127
+ import { RouterProvider } from 'react-router-dom';
128
+ import router from './router/router';
129
+ import config from './config/config';
130
+
131
+ function App() {
132
+ return (
133
+ <DauthProvider
134
+ domainName={config.services.dauth.DOMAIN_NAME as string}
135
+ tsk={config.services.dauth.TSK as string}
136
+ >
137
+ <LicensesProvider>
138
+ <RouterProvider router={router} fallbackElement={<></>} />
139
+ </LicensesProvider>
140
+ </DauthProvider>
141
+ );
142
+ }
143
+ ```
144
+
145
+ Environment variables (`.env.development`):
146
+ ```
147
+ REACT_APP_DAUTH_DOMAIN_NAME=your-domain-name
148
+ REACT_APP_DAUTH_TSK=your-tenant-secret-key
39
149
  ```
40
- import React, { useContext } from 'react';
150
+
151
+ ### 2. Route protection with `EnsureAuthenticated`
152
+
153
+ ```tsx
154
+ // src/router/middlewares.tsx
155
+ import { useEffect } from 'react';
156
+ import { useNavigate } from 'react-router-dom';
41
157
  import { useDauth } from 'dauth-context-react';
158
+ import { routes } from './routes';
159
+
160
+ export function EnsureAuthenticated({ children }: { children: React.ReactNode }) {
161
+ const { isAuthenticated, isLoading } = useDauth();
162
+ const navigate = useNavigate();
42
163
 
43
- function SomeComponent() {
44
- const { isAuthenticated, isLoading, user, loginWithRedirect, logout, getAccessToken } = useDauth();
164
+ useEffect(() => {
165
+ if (!isAuthenticated && !isLoading) {
166
+ navigate(routes.home);
167
+ }
168
+ }, [isAuthenticated, isLoading, navigate]);
169
+
170
+ return <>{children}</>;
171
+ }
172
+
173
+ // In router config:
174
+ {
175
+ path: '/license/:id',
176
+ element: (
177
+ <EnsureAuthenticated>
178
+ <LicenseDetail />
179
+ </EnsureAuthenticated>
180
+ ),
181
+ }
182
+ ```
183
+
184
+ ### 3. Passing tokens to API calls from a context provider
185
+
186
+ ```tsx
187
+ // src/context/licenses/LicensesProvider.tsx
188
+ import { useCallback, useReducer } from 'react';
189
+ import { useDauth } from 'dauth-context-react';
190
+
191
+ function LicensesProvider({ children }: { children: React.ReactNode }) {
192
+ const [state, dispatch] = useReducer(licenseReducer, initialState);
193
+ const { getAccessToken } = useDauth();
194
+
195
+ const getLicenses = useCallback(async () => {
196
+ const token = await getAccessToken();
197
+ const { data } = await fetchLicenses(token);
198
+ dispatch({ type: 'SET_LICENSES', payload: data });
199
+ }, [getAccessToken]);
200
+
201
+ const createLicense = useCallback(async (name: string) => {
202
+ const token = await getAccessToken();
203
+ await postLicense({ name, token });
204
+ }, [getAccessToken]);
205
+
206
+ return (
207
+ <LicensesContext.Provider value={{ ...state, getLicenses, createLicense }}>
208
+ {children}
209
+ </LicensesContext.Provider>
210
+ );
211
+ }
212
+ ```
213
+
214
+ ### 4. Using `useDauth()` in components
215
+
216
+ ```tsx
217
+ // src/views/components/ProfileIcon.tsx
218
+ import { useDauth } from 'dauth-context-react';
219
+
220
+ function ProfileIcon() {
221
+ const { user, isAuthenticated, loginWithRedirect, logout, updateUserWithRedirect } = useDauth();
45
222
 
46
223
  return (
47
- isLoading ? <div>Loading...</div> :
48
- isAuthenticated ? (
49
- <div>
50
- Hello {user.name}
51
- <button onClick={() => logout()}>Logout</button>
52
- </div>
224
+ <Popover title={user.email}>
225
+ {isAuthenticated ? (
226
+ <>
227
+ <Avatar src={user.avatar?.url} />
228
+ <button onClick={updateUserWithRedirect}>My Profile</button>
229
+ <button onClick={logout}>Logout</button>
230
+ </>
53
231
  ) : (
54
- <div>
55
- <button onClick={() => loginWithRedirect()}>Login</button>
56
- </div>
57
- )
232
+ <button onClick={loginWithRedirect}>Login</button>
233
+ )}
234
+ </Popover>
58
235
  );
59
236
  }
237
+ ```
238
+
239
+ ### 5. Language sync from dauth user
240
+
241
+ ```tsx
242
+ // src/views/layouts/LayoutBasic.tsx
243
+ import { useDauth } from 'dauth-context-react';
244
+ import i18n from 'i18next';
245
+
246
+ function LayoutMain() {
247
+ const { user } = useDauth();
60
248
 
61
- export default SomeComponent;
249
+ useEffect(() => {
250
+ i18n.changeLanguage(user.language);
251
+ }, [user.language]);
252
+
253
+ return <Outlet />;
254
+ }
62
255
  ```
63
256
 
64
- ## About
65
- This project is maintained by David T. Pizarro Frick.
257
+ ## How It Works
258
+
259
+ 1. **On mount:** Checks the URL for a redirect token (`?dauth_state=...`), then attempts auto-login from localStorage.
260
+ 2. **Token verification:** Runs a 5-minute interval to verify the stored token is still valid against the DAuth backend.
261
+ 3. **Localhost detection:** Automatically routes API calls to `localhost:4012` during development (detects `localhost`, `127.0.0.1`, `[::1]`, and `192.168.x.x`) and to `https://<domainName>.dauth.ovh` in production.
262
+ 4. **Token storage:** Tokens are stored in localStorage under the key `dauth_state`.
263
+
264
+ ## Development
265
+
266
+ ```bash
267
+ npm start # Watch mode (tsdx watch)
268
+ npm run build # Production build (CJS + ESM)
269
+ npm test # Run Jest tests
270
+ npm run lint # ESLint via tsdx
271
+ npm run size # Check bundle size (10KB budget per entry)
272
+ npm run analyze # Bundle size analysis with visualization
273
+ ```
274
+
275
+ ### Bundle Outputs
276
+
277
+ - **CJS:** `dist/index.js` (with `.development.js` and `.production.min.js` variants)
278
+ - **ESM:** `dist/dauth-context-react.esm.js`
279
+ - **Types:** `dist/index.d.ts`
280
+
281
+ ### CI
282
+
283
+ GitHub Actions runs lint, tests with coverage, and build across Node 20.x on Ubuntu, Windows, and macOS.
284
+
285
+ ## Author
286
+
287
+ David T. Pizarro Frick
66
288
 
67
289
  ## License
68
- This project is licensed under the MIT License.
290
+
291
+ MIT
@@ -355,7 +355,7 @@ function _extends() {
355
355
 
356
356
  var initialDauthState = {
357
357
  user: {
358
- language: /*#__PURE__*/window.document.documentElement.getAttribute('lang') || 'es'
358
+ language: (typeof window !== 'undefined' ? /*#__PURE__*/window.document.documentElement.getAttribute('lang') : null) || 'es'
359
359
  },
360
360
  domain: {},
361
361
  isLoading: true,
@@ -447,14 +447,19 @@ var routes = {
447
447
  tenantGetUser: 't-get-user',
448
448
  tenantResendEmailVerification: 't-resend-email-verification',
449
449
  tenantRefreshAccessToken: 't-refresh-access-token',
450
- tenantVerifyToken: "t-verify-token"
450
+ tenantVerifyToken: 't-verify-token'
451
451
  };
452
452
 
453
- var isLocalhost = /*#__PURE__*/Boolean(window.location.hostname === 'localhost' || window.location.hostname === '[::1]' || /*#__PURE__*/window.location.hostname.match(/(192)\.(168)\.(1)\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/gm) || /*#__PURE__*/window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/));
454
453
  var apiVersion = 'v1';
455
454
  var serverDomain = 'dauth.ovh';
455
+ function checkIsLocalhost() {
456
+ if (typeof window === 'undefined') return false;
457
+ var hostname = window.location.hostname;
458
+ return Boolean(hostname === 'localhost' || hostname === '[::1]' || hostname.match(/(192)\.(168)\.(1)\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/gm) || hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/));
459
+ }
456
460
  function getServerBasePath(_ref) {
457
461
  var domainName = _ref.domainName;
462
+ var isLocalhost = checkIsLocalhost();
458
463
  var serverPort = 4012;
459
464
  var serverLocalUrl = window.location.protocol + "//" + window.location.hostname + ":" + serverPort + "/api/" + apiVersion;
460
465
  var serverProdUrl = "https://" + domainName + "." + serverDomain + "/api/" + apiVersion;
@@ -463,6 +468,7 @@ function getServerBasePath(_ref) {
463
468
  }
464
469
  function getClientBasePath(_ref2) {
465
470
  var domainName = _ref2.domainName;
471
+ var isLocalhost = checkIsLocalhost();
466
472
  var clientPort = 5185;
467
473
  var clientLocalUrl = window.location.protocol + "//" + window.location.hostname + ":" + clientPort;
468
474
  var clientProdUrl = "https://" + domainName + "." + serverDomain;
@@ -706,7 +712,7 @@ function _setDauthStateAction() {
706
712
  case 15:
707
713
  _context.prev = 15;
708
714
  _context.t0 = _context["catch"](2);
709
- console.log(_context.t0);
715
+ console.error(_context.t0);
710
716
  return _context.abrupt("return", resetUser(dispatch));
711
717
  case 19:
712
718
  _context.prev = 19;
@@ -730,7 +736,7 @@ function setAutoLoginAction(_x2) {
730
736
  }
731
737
  function _setAutoLoginAction() {
732
738
  _setAutoLoginAction = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(_ref2) {
733
- var dispatch, token_ls, domainName, refreshAccessTokenFetch, getUserFetch;
739
+ var dispatch, token_ls, domainName, refreshAccessTokenFetch, newToken, getUserFetch;
734
740
  return _regeneratorRuntime().wrap(function _callee2$(_context2) {
735
741
  while (1) switch (_context2.prev = _context2.next) {
736
742
  case 0:
@@ -747,15 +753,16 @@ function _setAutoLoginAction() {
747
753
  case 5:
748
754
  refreshAccessTokenFetch = _context2.sent;
749
755
  if (!(refreshAccessTokenFetch.response.status === 200)) {
750
- _context2.next = 20;
756
+ _context2.next = 21;
751
757
  break;
752
758
  }
753
- _context2.next = 9;
754
- return getUserAPI(domainName, token_ls);
755
- case 9:
759
+ newToken = refreshAccessTokenFetch.data.accessToken || token_ls;
760
+ _context2.next = 10;
761
+ return getUserAPI(domainName, newToken);
762
+ case 10:
756
763
  getUserFetch = _context2.sent;
757
764
  if (!(getUserFetch.response.status === 200)) {
758
- _context2.next = 16;
765
+ _context2.next = 17;
759
766
  break;
760
767
  }
761
768
  dispatch({
@@ -768,41 +775,41 @@ function _setAutoLoginAction() {
768
775
  });
769
776
  localStorage.setItem(TOKEN_LS, refreshAccessTokenFetch.data.accessToken);
770
777
  return _context2.abrupt("return");
771
- case 16:
778
+ case 17:
772
779
  window.location.replace(getClientBasePath({
773
780
  domainName: domainName
774
781
  }) + "/" + routes.tenantSignin + "/" + domainName);
775
782
  return _context2.abrupt("return", resetUser(dispatch));
776
- case 18:
777
- _context2.next = 22;
783
+ case 19:
784
+ _context2.next = 23;
778
785
  break;
779
- case 20:
786
+ case 21:
780
787
  window.location.replace(getClientBasePath({
781
788
  domainName: domainName
782
789
  }) + "/" + routes.tenantSignin + "/" + domainName);
783
790
  return _context2.abrupt("return", resetUser(dispatch));
784
- case 22:
785
- _context2.next = 28;
791
+ case 23:
792
+ _context2.next = 29;
786
793
  break;
787
- case 24:
788
- _context2.prev = 24;
794
+ case 25:
795
+ _context2.prev = 25;
789
796
  _context2.t0 = _context2["catch"](2);
790
- console.log(_context2.t0);
797
+ console.error(_context2.t0);
791
798
  return _context2.abrupt("return", resetUser(dispatch));
792
- case 28:
793
- _context2.prev = 28;
799
+ case 29:
800
+ _context2.prev = 29;
794
801
  dispatch({
795
802
  type: SET_IS_LOADING,
796
803
  payload: {
797
804
  isLoading: false
798
805
  }
799
806
  });
800
- return _context2.finish(28);
801
- case 31:
807
+ return _context2.finish(29);
808
+ case 32:
802
809
  case "end":
803
810
  return _context2.stop();
804
811
  }
805
- }, _callee2, null, [[2, 24, 28, 31]]);
812
+ }, _callee2, null, [[2, 25, 29, 32]]);
806
813
  }));
807
814
  return _setAutoLoginAction.apply(this, arguments);
808
815
  }
@@ -870,7 +877,7 @@ function _setUpdateUserAction() {
870
877
  });
871
878
  return _context3.abrupt("return", true);
872
879
  case 14:
873
- console.log('Update user error', getUserFetch.data.message);
880
+ console.error('Update user error', getUserFetch.data.message);
874
881
  return _context3.abrupt("return", false);
875
882
  case 16:
876
883
  _context3.next = 22;
@@ -878,7 +885,7 @@ function _setUpdateUserAction() {
878
885
  case 18:
879
886
  _context3.prev = 18;
880
887
  _context3.t0 = _context3["catch"](5);
881
- console.log('Update user error', _context3.t0);
888
+ console.error('Update user error', _context3.t0);
882
889
  return _context3.abrupt("return", false);
883
890
  case 22:
884
891
  case "end":
@@ -985,28 +992,31 @@ function _checkTokenAction() {
985
992
  case 4:
986
993
  refreshAccessTokenFetch = _context5.sent;
987
994
  if (!(refreshAccessTokenFetch.response.status === 200)) {
988
- _context5.next = 9;
995
+ _context5.next = 10;
989
996
  break;
990
997
  }
998
+ if (refreshAccessTokenFetch.data.accessToken) {
999
+ localStorage.setItem(TOKEN_LS, refreshAccessTokenFetch.data.accessToken);
1000
+ }
991
1001
  return _context5.abrupt("return");
992
- case 9:
1002
+ case 10:
993
1003
  window.location.replace(getClientBasePath({
994
1004
  domainName: domainName
995
1005
  }) + "/" + routes.tenantSignin + "/" + domainName);
996
1006
  return _context5.abrupt("return", resetUser(dispatch));
997
- case 11:
998
- _context5.next = 17;
1007
+ case 12:
1008
+ _context5.next = 18;
999
1009
  break;
1000
- case 13:
1001
- _context5.prev = 13;
1010
+ case 14:
1011
+ _context5.prev = 14;
1002
1012
  _context5.t0 = _context5["catch"](1);
1003
1013
  resetUser(dispatch);
1004
1014
  throw _context5.t0;
1005
- case 17:
1015
+ case 18:
1006
1016
  case "end":
1007
1017
  return _context5.stop();
1008
1018
  }
1009
- }, _callee5, null, [[1, 13]]);
1019
+ }, _callee5, null, [[1, 14]]);
1010
1020
  }));
1011
1021
  return _checkTokenAction.apply(this, arguments);
1012
1022
  }
@@ -1210,7 +1220,7 @@ var DauthProvider = function DauthProvider(props) {
1210
1220
  case 0:
1211
1221
  token_ls = localStorage.getItem(TOKEN_LS);
1212
1222
  if (!(token_ls && !dauthState.isAuthenticated)) {
1213
- _context4.next = 11;
1223
+ _context4.next = 13;
1214
1224
  break;
1215
1225
  }
1216
1226
  _context4.next = 4;
@@ -1232,6 +1242,16 @@ var DauthProvider = function DauthProvider(props) {
1232
1242
  });
1233
1243
  throw new Error('Ask value in DauthProvider is not valid');
1234
1244
  case 11:
1245
+ _context4.next = 14;
1246
+ break;
1247
+ case 13:
1248
+ return _context4.abrupt("return", dispatch({
1249
+ type: SET_IS_LOADING,
1250
+ payload: {
1251
+ isLoading: false
1252
+ }
1253
+ }));
1254
+ case 14:
1235
1255
  case "end":
1236
1256
  return _context4.stop();
1237
1257
  }
@@ -1242,7 +1262,7 @@ var DauthProvider = function DauthProvider(props) {
1242
1262
  return window.location.replace(getClientBasePath({
1243
1263
  domainName: domainName
1244
1264
  }) + "/" + routes.tenantSignin + "/" + domainName);
1245
- }, [domainName, domainName]);
1265
+ }, [domainName]);
1246
1266
  var logout = React.useCallback(function () {
1247
1267
  return setLogoutAction({
1248
1268
  dispatch: dispatch
@@ -1266,7 +1286,7 @@ var DauthProvider = function DauthProvider(props) {
1266
1286
  return _context5.stop();
1267
1287
  }
1268
1288
  }, _callee5);
1269
- })), []);
1289
+ })), [domainName]);
1270
1290
  var updateUser = React.useCallback( /*#__PURE__*/function () {
1271
1291
  var _ref7 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee6(_ref6) {
1272
1292
  var name, lastname, nickname, telPrefix, telSuffix, language, avatar, token_ls, user;
@@ -1309,7 +1329,7 @@ var DauthProvider = function DauthProvider(props) {
1309
1329
  return window.location.replace(getClientBasePath({
1310
1330
  domainName: domainName
1311
1331
  }) + "/" + routes.tenantUpdateUser + "/" + domainName + "/" + token_ls);
1312
- }, [domainName, domainName]);
1332
+ }, [domainName]);
1313
1333
  var sendEmailVerification = React.useCallback( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee7() {
1314
1334
  var token_ls;
1315
1335
  return _regeneratorRuntime().wrap(function _callee7$(_context7) {