dauth-context-react 5.0.0 → 6.0.0

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,54 +1,52 @@
1
1
  # dauth-context-react
2
2
 
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.
3
+ React context provider and hook for passwordless authentication with the [dauth](https://dauth.ovh) service. Uses the BFF (Backend-for-Frontend) pattern with httpOnly cookies -- no tokens are exposed to the browser. Provides `DauthProvider` and `useDauth()` for seamless integration of dauth tenant authentication into React applications.
4
4
 
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
8
  npm install dauth-context-react
9
9
  # or
10
- yarn add dauth-context-react
10
+ pnpm add dauth-context-react
11
11
  ```
12
12
 
13
- ### Peer Dependencies
13
+ Peer dependency: `react` >= 16.
14
14
 
15
- - `react` >= 16
15
+ ## Tech Stack
16
16
 
17
- ## Quick Start
17
+ | Technology | Version | Purpose |
18
+ |---|---|---|
19
+ | React | >= 16 | Peer dependency |
20
+ | TypeScript | 5.9 | Type safety |
21
+ | tsup | 8 | Build tool (CJS + ESM bundles) |
22
+ | vitest | 4 | Testing framework |
23
+ | size-limit | 12 | Bundle size budget (10KB per entry) |
24
+
25
+ ## Usage
18
26
 
19
27
  ### 1. Wrap your app with `DauthProvider`
20
28
 
21
29
  ```tsx
22
- import React from 'react';
23
- import ReactDOM from 'react-dom/client';
24
- import { RouterProvider } from 'react-router-dom';
25
30
  import { DauthProvider } from 'dauth-context-react';
31
+ import { RouterProvider } from 'react-router-dom';
26
32
  import router from './router/router';
27
33
 
28
- ReactDOM.createRoot(document.getElementById('root')!).render(
29
- <DauthProvider
30
- domainName="your-domain-name"
31
- tsk="your-tenant-secret-key"
32
- >
33
- <RouterProvider router={router} fallbackElement={<></>} />
34
- </DauthProvider>
35
- );
34
+ function App() {
35
+ return (
36
+ <DauthProvider domainName="your-domain-name">
37
+ <RouterProvider router={router} fallbackElement={<></>} />
38
+ </DauthProvider>
39
+ );
40
+ }
36
41
  ```
37
42
 
38
- ### 2. Use the `useDauth()` hook in your components
43
+ ### 2. Use the `useDauth()` hook
39
44
 
40
45
  ```tsx
41
46
  import { useDauth } from 'dauth-context-react';
42
47
 
43
48
  function MyComponent() {
44
- const {
45
- isAuthenticated,
46
- isLoading,
47
- user,
48
- loginWithRedirect,
49
- logout,
50
- getAccessToken,
51
- } = useDauth();
49
+ const { isAuthenticated, isLoading, user, loginWithRedirect, logout } = useDauth();
52
50
 
53
51
  if (isLoading) return <div>Loading...</div>;
54
52
 
@@ -65,230 +63,105 @@ function MyComponent() {
65
63
  }
66
64
  ```
67
65
 
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
- birthDate?: Date;
112
- country?: string;
113
- metadata?: any;
114
- createdAt: Date;
115
- updatedAt: Date;
116
- lastLogin: Date;
117
- }
118
- ```
119
-
120
- ## Real-World Integration Example
121
-
122
- This is the pattern used in `easymediacloud-frontend-react`, which delegates all user authentication to Dauth.
123
-
124
- ### 1. Provider hierarchy in `App.tsx`
125
-
126
- ```tsx
127
- // src/App.tsx
128
- import { DauthProvider } from 'dauth-context-react';
129
- import { LicensesProvider } from './context/licenses/LicensesProvider';
130
- import { RouterProvider } from 'react-router-dom';
131
- import router from './router/router';
132
- import config from './config/config';
133
-
134
- function App() {
135
- return (
136
- <DauthProvider
137
- domainName={config.services.dauth.DOMAIN_NAME as string}
138
- tsk={config.services.dauth.TSK as string}
139
- >
140
- <LicensesProvider>
141
- <RouterProvider router={router} fallbackElement={<></>} />
142
- </LicensesProvider>
143
- </DauthProvider>
144
- );
145
- }
146
- ```
147
-
148
- Environment variables (`.env.development`):
149
- ```
150
- REACT_APP_DAUTH_DOMAIN_NAME=your-domain-name
151
- REACT_APP_DAUTH_TSK=your-tenant-secret-key
152
- ```
153
-
154
- ### 2. Route protection with `EnsureAuthenticated`
155
-
156
- ```tsx
157
- // src/router/middlewares.tsx
158
- import { useEffect } from 'react';
159
- import { useNavigate } from 'react-router-dom';
160
- import { useDauth } from 'dauth-context-react';
161
- import { routes } from './routes';
162
-
163
- export function EnsureAuthenticated({ children }: { children: React.ReactNode }) {
164
- const { isAuthenticated, isLoading } = useDauth();
165
- const navigate = useNavigate();
166
-
167
- useEffect(() => {
168
- if (!isAuthenticated && !isLoading) {
169
- navigate(routes.home);
170
- }
171
- }, [isAuthenticated, isLoading, navigate]);
172
-
173
- return <>{children}</>;
174
- }
175
-
176
- // In router config:
177
- {
178
- path: '/license/:id',
179
- element: (
180
- <EnsureAuthenticated>
181
- <LicenseDetail />
182
- </EnsureAuthenticated>
183
- ),
184
- }
185
- ```
186
-
187
- ### 3. Passing tokens to API calls from a context provider
188
-
189
- ```tsx
190
- // src/context/licenses/LicensesProvider.tsx
191
- import { useCallback, useReducer } from 'react';
192
- import { useDauth } from 'dauth-context-react';
66
+ ### 3. Implement the backend proxy
193
67
 
194
- function LicensesProvider({ children }: { children: React.ReactNode }) {
195
- const [state, dispatch] = useReducer(licenseReducer, initialState);
196
- const { getAccessToken } = useDauth();
68
+ Your backend must implement 6 endpoints at `authProxyPath` (default `/api/auth`) that proxy requests to the dauth backend and manage httpOnly session cookies. The [`dauth-md-node`](https://www.npmjs.com/package/dauth-md-node) package provides a ready-made Express router for this.
197
69
 
198
- const getLicenses = useCallback(async () => {
199
- const token = await getAccessToken();
200
- const { data } = await fetchLicenses(token);
201
- dispatch({ type: 'SET_LICENSES', payload: data });
202
- }, [getAccessToken]);
70
+ ### Authentication flow
203
71
 
204
- const createLicense = useCallback(async (name: string) => {
205
- const token = await getAccessToken();
206
- await postLicense({ name, token });
207
- }, [getAccessToken]);
208
-
209
- return (
210
- <LicensesContext.Provider value={{ ...state, getLicenses, createLicense }}>
211
- {children}
212
- </LicensesContext.Provider>
213
- );
214
- }
215
- ```
72
+ 1. **On mount**: checks for `?code=` in the URL. If present, exchanges the code for a session via `POST {authProxyPath}/exchange-code`. If absent, checks for an existing session via `GET {authProxyPath}/session`.
73
+ 2. **Login**: `loginWithRedirect()` navigates to `{dauthUrl}/{domainName}/signin`. After authentication, dauth redirects back with `?code=`.
74
+ 3. **Logout**: calls `POST {authProxyPath}/logout` and resets context state.
75
+ 4. **CSRF**: mutating requests read the CSRF token from cookie and send it as `X-CSRF-Token` header.
76
+ 5. **Localhost detection**: auto-routes to `localhost:5185` in development, `https://dauth.ovh` in production. Override with `dauthUrl` prop.
216
77
 
217
- ### 4. Using `useDauth()` in components
78
+ ## API Reference
218
79
 
219
- ```tsx
220
- // src/views/components/ProfileIcon.tsx
221
- import { useDauth } from 'dauth-context-react';
80
+ ### `<DauthProvider>` props
222
81
 
223
- function ProfileIcon() {
224
- const { user, isAuthenticated, loginWithRedirect, logout, updateUserWithRedirect } = useDauth();
82
+ | Prop | Type | Required | Default | Description |
83
+ |---|---|---|---|---|
84
+ | `domainName` | `string` | yes | - | Tenant identifier used in the signin redirect URL |
85
+ | `children` | `React.ReactNode` | yes | - | Child components |
86
+ | `authProxyPath` | `string` | no | `'/api/auth'` | Base path of the auth proxy on the consumer's backend |
87
+ | `onError` | `(error: Error) => void` | no | `console.error` | Error callback for failed auth operations |
88
+ | `env` | `string` | no | - | Environment name appended as `?env=` to the signin redirect URL |
89
+ | `dauthUrl` | `string` | no | auto-detected | Override the dauth frontend URL (e.g. `'https://dev.dauth.ovh'` for staging) |
225
90
 
226
- return (
227
- <Popover title={user.email}>
228
- {isAuthenticated ? (
229
- <>
230
- <Avatar src={user.avatar?.url} />
231
- <button onClick={updateUserWithRedirect}>My Profile</button>
232
- <button onClick={logout}>Logout</button>
233
- </>
234
- ) : (
235
- <button onClick={loginWithRedirect}>Login</button>
236
- )}
237
- </Popover>
238
- );
239
- }
240
- ```
91
+ ### `useDauth()` return value
241
92
 
242
- ### 5. Language sync from dauth user
93
+ | Property | Type | Description |
94
+ |---|---|---|
95
+ | `user` | `IDauthUser` | Authenticated user object |
96
+ | `domain` | `IDauthDomainState` | Domain configuration (name, loginRedirect, allowedOrigins, authMethods) |
97
+ | `isLoading` | `boolean` | `true` while the initial auth check is in progress |
98
+ | `isAuthenticated` | `boolean` | `true` if the user has a valid session |
99
+ | `loginWithRedirect` | `() => void` | Redirects to the dauth tenant sign-in page |
100
+ | `logout` | `() => void` | Clears the session (server-side + context reset) |
101
+ | `updateUser` | `(fields: Partial<IDauthUser>) => Promise<boolean>` | Updates user profile fields via the proxy |
102
+ | `updateUserWithRedirect` | `() => void` | Redirects to the dauth profile editing page |
103
+ | `deleteAccount` | `() => Promise<boolean>` | Deletes the user account |
243
104
 
244
- ```tsx
245
- // src/views/layouts/LayoutBasic.tsx
246
- import { useDauth } from 'dauth-context-react';
247
- import i18n from 'i18next';
105
+ ### Backend proxy endpoints
248
106
 
249
- function LayoutMain() {
250
- const { user } = useDauth();
107
+ Your backend must implement these at `authProxyPath`:
251
108
 
252
- useEffect(() => {
253
- i18n.changeLanguage(user.language);
254
- }, [user.language]);
109
+ | Method | Path | Purpose |
110
+ |---|---|---|
111
+ | `POST` | `/exchange-code` | Exchange authorization code for tokens, set httpOnly cookies |
112
+ | `GET` | `/session` | Verify session cookie, return user + domain data |
113
+ | `POST` | `/logout` | Clear session cookies |
114
+ | `PATCH` | `/user` | Forward user profile updates to dauth |
115
+ | `DELETE` | `/user` | Forward account deletion to dauth |
116
+ | `GET` | `/profile-redirect` | Return redirect URL to the dauth profile page |
117
+
118
+ ### Exported types
119
+
120
+ - `IDauthUser` -- user object (name, email, avatar, role, language, etc.)
121
+ - `IDauthDomainState` -- domain config (name, environments, loginRedirect, allowedOrigins, authMethods)
122
+ - `IDauthState` -- full context value
123
+ - `IDauthProviderProps` -- provider props
124
+ - `IDauthAuthMethods` -- auth methods config (`magicLink`, `passkey`)
125
+ - `IActionStatus` -- status object (type, message)
126
+
127
+ ## Scripts
128
+
129
+ | Script | Description |
130
+ |---|---|
131
+ | `pnpm start` | Watch mode (tsup --watch) |
132
+ | `pnpm build` | Production build (CJS + ESM + types) |
133
+ | `pnpm test` | Run vitest tests (80 tests) |
134
+ | `pnpm test:coverage` | Coverage report (v8, 70% threshold) |
135
+ | `pnpm typecheck` | TypeScript type checking |
136
+ | `pnpm size` | Check bundle size (10KB budget per entry) |
137
+ | `pnpm format` | Prettier formatting |
138
+
139
+ ## Testing
140
+
141
+ 80 tests across 6 files using vitest 4 with @testing-library/react (jsdom environment).
255
142
 
256
- return <Outlet />;
257
- }
143
+ ```bash
144
+ pnpm test
145
+ pnpm test:coverage
258
146
  ```
259
147
 
260
- ## How It Works
148
+ ## Publishing
261
149
 
262
- 1. **On mount:** Checks the URL for a redirect token (`?dauth_state=...`), then attempts auto-login from localStorage.
263
- 2. **Token refresh:** Proactively refreshes the access token before expiry (5 minutes before). Falls back to periodic refresh every 5 minutes if decode fails.
264
- 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://dauth.ovh` in production.
265
- 4. **Token storage:** Access token stored in localStorage under `dauth_state`, refresh token under `dauth_refresh_token`.
266
-
267
- ## Development
150
+ Automated via GitHub Actions. Push a `v*` tag to trigger build, test, and `npm publish`.
268
151
 
269
152
  ```bash
270
- pnpm start # Watch mode (tsdx watch)
271
- pnpm build # Production build (CJS + ESM)
272
- pnpm test # Run Jest tests
273
- pnpm lint # ESLint via tsdx
274
- pnpm size # Check bundle size (10KB budget per entry)
275
- pnpm analyze # Bundle size analysis with visualization
153
+ # 1. Bump version in package.json
154
+ # 2. Commit and tag
155
+ git tag v5.0.1
156
+ git push origin main --tags
157
+ # 3. GitHub Actions publishes to npm automatically
276
158
  ```
277
159
 
278
- ### Bundle Outputs
279
-
280
- - **CJS:** `dist/index.js` (with `.development.js` and `.production.min.js` variants)
281
- - **ESM:** `dist/dauth-context-react.esm.js`
282
- - **Types:** `dist/index.d.ts`
283
-
284
- ### CI/CD
285
-
286
- - **CI**: GitHub Actions runs lint, test, and build on push to `main` and PRs. Self-hosted runner, Node 22.
287
- - **Publish**: Automated npm publish on `v*` tags via GitHub Actions.
160
+ Requires `NPM_TOKEN` secret configured in the GitHub repo.
288
161
 
289
- ## Author
162
+ ## Code Style
290
163
 
291
- David T. Pizarro Frick
164
+ Prettier: 80 char width, single quotes, semicolons, trailing commas (es5), 2-space indent.
292
165
 
293
166
  ## License
294
167
 
package/dist/index.d.mts CHANGED
@@ -30,12 +30,17 @@ interface IDauthAuthMethods {
30
30
  magicLink: boolean;
31
31
  passkey: boolean;
32
32
  }
33
+ interface IFormField {
34
+ field: string;
35
+ required: boolean;
36
+ }
33
37
  interface IDauthDomainState {
34
38
  name: string;
35
39
  environments?: IDauthDomainEnvironment[];
36
40
  loginRedirect: string;
37
41
  allowedOrigins: string[];
38
42
  authMethods?: IDauthAuthMethods;
43
+ formFields?: IFormField[];
39
44
  }
40
45
  interface IDauthState {
41
46
  user: IDauthUser;
@@ -45,9 +50,12 @@ interface IDauthState {
45
50
  loginWithRedirect: () => void;
46
51
  logout: () => void;
47
52
  updateUser: (fields: Partial<IDauthUser>) => Promise<boolean>;
48
- updateUserWithRedirect: () => void;
49
53
  deleteAccount: () => Promise<boolean>;
50
54
  }
55
+ interface DauthProfileModalProps {
56
+ open: boolean;
57
+ onClose: () => void;
58
+ }
51
59
  interface IDauthProviderProps {
52
60
  domainName: string;
53
61
  children: React.ReactNode;
@@ -59,7 +67,9 @@ interface IDauthProviderProps {
59
67
  dauthUrl?: string;
60
68
  }
61
69
 
70
+ declare function DauthProfileModal({ open, onClose }: DauthProfileModalProps): React$1.ReactPortal | null;
71
+
62
72
  declare const DauthProvider: React$1.FC<IDauthProviderProps>;
63
73
  declare const useDauth: () => IDauthState;
64
74
 
65
- export { DauthProvider, type IDauthAuthMethods, type IDauthProviderProps, useDauth };
75
+ export { DauthProfileModal, type DauthProfileModalProps, DauthProvider, type IDauthAuthMethods, type IDauthProviderProps, type IFormField, useDauth };
package/dist/index.d.ts CHANGED
@@ -30,12 +30,17 @@ interface IDauthAuthMethods {
30
30
  magicLink: boolean;
31
31
  passkey: boolean;
32
32
  }
33
+ interface IFormField {
34
+ field: string;
35
+ required: boolean;
36
+ }
33
37
  interface IDauthDomainState {
34
38
  name: string;
35
39
  environments?: IDauthDomainEnvironment[];
36
40
  loginRedirect: string;
37
41
  allowedOrigins: string[];
38
42
  authMethods?: IDauthAuthMethods;
43
+ formFields?: IFormField[];
39
44
  }
40
45
  interface IDauthState {
41
46
  user: IDauthUser;
@@ -45,9 +50,12 @@ interface IDauthState {
45
50
  loginWithRedirect: () => void;
46
51
  logout: () => void;
47
52
  updateUser: (fields: Partial<IDauthUser>) => Promise<boolean>;
48
- updateUserWithRedirect: () => void;
49
53
  deleteAccount: () => Promise<boolean>;
50
54
  }
55
+ interface DauthProfileModalProps {
56
+ open: boolean;
57
+ onClose: () => void;
58
+ }
51
59
  interface IDauthProviderProps {
52
60
  domainName: string;
53
61
  children: React.ReactNode;
@@ -59,7 +67,9 @@ interface IDauthProviderProps {
59
67
  dauthUrl?: string;
60
68
  }
61
69
 
70
+ declare function DauthProfileModal({ open, onClose }: DauthProfileModalProps): React$1.ReactPortal | null;
71
+
62
72
  declare const DauthProvider: React$1.FC<IDauthProviderProps>;
63
73
  declare const useDauth: () => IDauthState;
64
74
 
65
- export { DauthProvider, type IDauthAuthMethods, type IDauthProviderProps, useDauth };
75
+ export { DauthProfileModal, type DauthProfileModalProps, DauthProvider, type IDauthAuthMethods, type IDauthProviderProps, type IFormField, useDauth };