dauth-context-react 4.0.4 → 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.
@@ -1,118 +1,87 @@
1
- import { getServerBasePath } from './utils/config';
2
1
  import { IDauthUser } from '../interfaces';
3
2
  import {
4
- IdeleteAccountAPIResponse,
5
- IExchangeCodeAPIResponse,
6
- IgetUserAPIResponse,
7
- IrefreshTokenAPIResponse,
8
- IupdateUserAPIResponse,
3
+ IExchangeCodeResponse,
4
+ ISessionResponse,
5
+ IUpdateUserResponse,
6
+ IDeleteAccountResponse,
9
7
  } from './interfaces/dauth.api.responses';
10
8
 
11
- export const getUserAPI = async (
12
- domainName: string,
13
- token: string
14
- ): Promise<IgetUserAPIResponse> => {
15
- const params = {
16
- method: 'GET',
17
- headers: {
18
- Authorization: token,
19
- 'Content-Type': 'application/json',
20
- },
21
- };
22
- const response = await fetch(
23
- `${getServerBasePath()}/app/${domainName}/user`,
24
- params
9
+ function getCsrfToken(): string {
10
+ const match = document.cookie.match(
11
+ /(?:^|;\s*)(?:__Host-csrf|csrf-token)=([^;]*)/
25
12
  );
13
+ if (!match?.[1]) return '';
14
+ try {
15
+ return decodeURIComponent(match[1]);
16
+ } catch {
17
+ return match[1];
18
+ }
19
+ }
20
+
21
+ export async function exchangeCodeAPI(
22
+ basePath: string,
23
+ code: string
24
+ ): Promise<IExchangeCodeResponse> {
25
+ const response = await fetch(`${basePath}/exchange-code`, {
26
+ method: 'POST',
27
+ headers: { 'Content-Type': 'application/json' },
28
+ credentials: 'include',
29
+ body: JSON.stringify({ code }),
30
+ });
26
31
  const data = await response.json();
27
32
  return { response, data };
28
- };
33
+ }
29
34
 
30
- export const updateUserAPI = async (
31
- domainName: string,
32
- user: Partial<IDauthUser>,
33
- token: string
34
- ): Promise<IupdateUserAPIResponse> => {
35
- const params = {
36
- method: 'PATCH',
37
- headers: {
38
- Authorization: token,
39
- 'Content-Type': 'application/json',
40
- },
41
- body: JSON.stringify(user),
42
- };
43
- const response = await fetch(
44
- `${getServerBasePath()}/app/${domainName}/user`,
45
- params
46
- );
35
+ export async function getSessionAPI(
36
+ basePath: string
37
+ ): Promise<ISessionResponse> {
38
+ const response = await fetch(`${basePath}/session`, {
39
+ method: 'GET',
40
+ credentials: 'include',
41
+ });
47
42
  const data = await response.json();
48
43
  return { response, data };
49
- };
44
+ }
50
45
 
51
- export const refreshTokenAPI = async (
52
- domainName: string,
53
- refreshToken: string
54
- ): Promise<IrefreshTokenAPIResponse> => {
55
- const params = {
46
+ export async function logoutAPI(
47
+ basePath: string
48
+ ): Promise<{ response: Response }> {
49
+ const response = await fetch(`${basePath}/logout`, {
56
50
  method: 'POST',
57
- headers: { 'Content-Type': 'application/json' },
58
- body: JSON.stringify({ refreshToken }),
59
- };
60
- const response = await fetch(
61
- `${getServerBasePath()}/app/${domainName}/refresh-token`,
62
- params
63
- );
64
- const data = await response.json();
65
- return { response, data };
66
- };
51
+ headers: {
52
+ 'Content-Type': 'application/json',
53
+ 'X-CSRF-Token': getCsrfToken(),
54
+ },
55
+ credentials: 'include',
56
+ });
57
+ return { response };
58
+ }
67
59
 
68
- export const deleteAccountAPI = async (
69
- domainName: string,
70
- token: string
71
- ): Promise<IdeleteAccountAPIResponse> => {
72
- const params = {
73
- method: 'DELETE',
60
+ export async function updateUserAPI(
61
+ basePath: string,
62
+ user: Partial<IDauthUser>
63
+ ): Promise<IUpdateUserResponse> {
64
+ const response = await fetch(`${basePath}/user`, {
65
+ method: 'PATCH',
74
66
  headers: {
75
- Authorization: token,
76
67
  'Content-Type': 'application/json',
68
+ 'X-CSRF-Token': getCsrfToken(),
77
69
  },
78
- };
79
- const response = await fetch(
80
- `${getServerBasePath()}/app/${domainName}/user`,
81
- params
82
- );
70
+ credentials: 'include',
71
+ body: JSON.stringify(user),
72
+ });
83
73
  const data = await response.json();
84
74
  return { response, data };
85
- };
75
+ }
86
76
 
87
- export const exchangeCodeAPI = async (
88
- domainName: string,
89
- code: string
90
- ): Promise<IExchangeCodeAPIResponse> => {
91
- const params = {
92
- method: 'POST',
93
- headers: { 'Content-Type': 'application/json' },
94
- body: JSON.stringify({ code }),
95
- };
96
- const response = await fetch(
97
- `${getServerBasePath()}/app/${domainName}/exchange-code`,
98
- params
99
- );
77
+ export async function deleteAccountAPI(
78
+ basePath: string
79
+ ): Promise<IDeleteAccountResponse> {
80
+ const response = await fetch(`${basePath}/user`, {
81
+ method: 'DELETE',
82
+ headers: { 'X-CSRF-Token': getCsrfToken() },
83
+ credentials: 'include',
84
+ });
100
85
  const data = await response.json();
101
86
  return { response, data };
102
- };
103
-
104
- export const logoutAPI = async (
105
- domainName: string,
106
- refreshToken: string
107
- ): Promise<{ response: Response }> => {
108
- const params = {
109
- method: 'POST',
110
- headers: { 'Content-Type': 'application/json' },
111
- body: JSON.stringify({ refreshToken }),
112
- };
113
- const response = await fetch(
114
- `${getServerBasePath()}/app/${domainName}/logout`,
115
- params
116
- );
117
- return { response };
118
- };
87
+ }
@@ -1,23 +1,23 @@
1
1
  import { IDauthDomainState, IDauthUser } from '../../interfaces';
2
2
 
3
- export interface IdeleteAccountAPIResponse {
3
+ export interface IExchangeCodeResponse {
4
4
  response: Response;
5
5
  data: {
6
- status: string;
7
- message: string;
6
+ user: IDauthUser;
7
+ domain: IDauthDomainState;
8
+ isNewUser: boolean;
8
9
  };
9
10
  }
10
11
 
11
- export interface IgetUserAPIResponse {
12
+ export interface ISessionResponse {
12
13
  response: Response;
13
14
  data: {
14
- status: string;
15
15
  user: IDauthUser;
16
16
  domain: IDauthDomainState;
17
17
  };
18
18
  }
19
19
 
20
- export interface IupdateUserAPIResponse {
20
+ export interface IUpdateUserResponse {
21
21
  response: Response;
22
22
  data: {
23
23
  status: string;
@@ -26,19 +26,10 @@ export interface IupdateUserAPIResponse {
26
26
  };
27
27
  }
28
28
 
29
- export interface IrefreshTokenAPIResponse {
30
- response: Response;
31
- data: {
32
- accessToken: string;
33
- refreshToken: string;
34
- };
35
- }
36
-
37
- export interface IExchangeCodeAPIResponse {
29
+ export interface IDeleteAccountResponse {
38
30
  response: Response;
39
31
  data: {
40
- accessToken: string;
41
- refreshToken: string;
42
- isNewUser?: boolean;
32
+ status: string;
33
+ message: string;
43
34
  };
44
35
  }
@@ -1,4 +1,3 @@
1
- export const apiVersion = 'v1';
2
1
  export const serverDomain = 'dauth.ovh';
3
2
 
4
3
  let _dauthUrl: string | undefined;
@@ -10,25 +9,21 @@ export function setDauthUrl(url: string | undefined) {
10
9
  function checkIsLocalhost(): boolean {
11
10
  if (typeof window === 'undefined') return false;
12
11
  const hostname = window.location.hostname;
13
- return Boolean(
12
+ return (
14
13
  hostname === 'localhost' ||
15
14
  hostname === '[::1]' ||
16
- hostname.match(
17
- /(192)\.(168)\.(1)\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/gm
15
+ /^127(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d\d?)){3}$/.test(
16
+ hostname
18
17
  ) ||
19
- hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)
18
+ /^192\.168(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d\d?)){2}$/.test(
19
+ hostname
20
+ ) ||
21
+ /^10(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d\d?)){3}$/.test(
22
+ hostname
23
+ )
20
24
  );
21
25
  }
22
26
 
23
- export function getServerBasePath() {
24
- if (_dauthUrl) return `${_dauthUrl}/api/${apiVersion}`;
25
- const isLocalhost = checkIsLocalhost();
26
- const serverPort = 4012;
27
- const serverLocalUrl = `${window.location.protocol}//${window.location.hostname}:${serverPort}/api/${apiVersion}`;
28
- const serverProdUrl = `https://${serverDomain}/api/${apiVersion}`;
29
- return isLocalhost ? serverLocalUrl : serverProdUrl;
30
- }
31
-
32
27
  export function getClientBasePath() {
33
28
  if (_dauthUrl) return _dauthUrl;
34
29
  const isLocalhost = checkIsLocalhost();
@@ -1,4 +1,3 @@
1
1
  export const routes = {
2
2
  signin: 'signin',
3
- updateUser: 'update-user',
4
3
  };
package/src/constants.ts CHANGED
@@ -1,3 +1 @@
1
- export const TOKEN_LS = 'dauth_state';
2
- export const REFRESH_TOKEN_LS = 'dauth_refresh_token';
3
1
  export const AUTH_CODE_PARAM = 'code';
package/src/index.tsx CHANGED
@@ -5,126 +5,69 @@ import React, {
5
5
  useCallback,
6
6
  createContext,
7
7
  useContext,
8
- useRef,
9
8
  } from 'react';
10
9
  import initialDauthState from './initialDauthState';
11
10
  import userReducer from './reducer/dauth.reducer';
12
11
  import * as action from './reducer/dauth.actions';
13
12
  import { getClientBasePath, setDauthUrl } from './api/utils/config';
14
- import { TOKEN_LS, REFRESH_TOKEN_LS, AUTH_CODE_PARAM } from './constants';
13
+ import { AUTH_CODE_PARAM } from './constants';
15
14
  import { routes } from './api/utils/routes';
16
15
  import type {
17
16
  IDauthProviderProps,
18
- IDauthStorageKeys,
19
- IDauthUser,
20
17
  IDauthAuthMethods,
18
+ IDauthUser,
19
+ IFormField,
20
+ DauthProfileModalProps,
21
21
  } from './interfaces';
22
- import { SET_IS_LOADING } from './reducer/dauth.types';
23
22
 
24
- export type { IDauthProviderProps, IDauthAuthMethods };
23
+ export { DauthProfileModal } from './DauthProfileModal';
24
+ export type {
25
+ IDauthProviderProps,
26
+ IDauthAuthMethods,
27
+ IFormField,
28
+ DauthProfileModalProps,
29
+ };
25
30
 
26
31
  const defaultOnError = (error: Error) => console.error(error);
27
32
 
28
33
  export const DauthProvider: React.FC<IDauthProviderProps> = (
29
34
  props: IDauthProviderProps
30
35
  ) => {
31
- const { domainName, children, storageKey, onError, env, dauthUrl } = props;
36
+ const {
37
+ domainName,
38
+ children,
39
+ authProxyPath = '/api/auth',
40
+ onError,
41
+ env,
42
+ dauthUrl,
43
+ } = props;
32
44
  const [dauthState, dispatch] = useReducer(userReducer, initialDauthState);
33
- const refreshTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
34
45
 
35
46
  // Configure custom dauth URL before any API calls
36
47
  useEffect(() => {
37
48
  setDauthUrl(dauthUrl);
38
49
  }, [dauthUrl]);
39
50
 
40
- const storageKeys: IDauthStorageKeys = useMemo(
41
- () => ({
42
- accessToken: storageKey?.accessToken ?? TOKEN_LS,
43
- refreshToken: storageKey?.refreshToken ?? REFRESH_TOKEN_LS,
44
- }),
45
- [storageKey?.accessToken, storageKey?.refreshToken]
46
- );
47
-
48
51
  const handleError = useCallback(
49
52
  (error: Error) => (onError ?? defaultOnError)(error),
50
53
  [onError]
51
54
  );
52
55
 
53
- // Build action context
54
56
  const ctx = useMemo(
55
- () => ({ dispatch, domainName, storageKeys, onError: handleError }),
56
- [domainName, storageKeys, handleError]
57
+ () => ({ dispatch, authProxyPath, onError: handleError }),
58
+ [authProxyPath, handleError]
57
59
  );
58
60
 
59
- // Schedule next proactive refresh based on access token expiry
60
- const scheduleRefresh = useCallback(() => {
61
- if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
62
- const token = localStorage.getItem(storageKeys.accessToken);
63
- if (!token) return;
64
- try {
65
- const payloadB64 = token.split('.')[1];
66
- if (!payloadB64) return;
67
- const payload = JSON.parse(atob(payloadB64));
68
- const expiresIn = (payload.exp || 0) * 1000 - Date.now();
69
- // Refresh 5 minutes before expiry, minimum 10 seconds
70
- const refreshIn = Math.max(expiresIn - 5 * 60 * 1000, 10_000);
71
- refreshTimerRef.current = setTimeout(async () => {
72
- await action.refreshSessionAction(ctx);
73
- scheduleRefresh();
74
- }, refreshIn);
75
- } catch (_) {
76
- // If decode fails, retry in 5 minutes
77
- refreshTimerRef.current = setTimeout(
78
- async () => {
79
- await action.refreshSessionAction(ctx);
80
- scheduleRefresh();
81
- },
82
- 5 * 60 * 1000
83
- );
84
- }
85
- }, [ctx, storageKeys.accessToken]);
86
-
87
- // Catch login redirect — exchange authorization code for tokens
88
- useEffect(() => {
89
- (async () => {
90
- const queryString = window.location.search;
91
- if (!queryString) return;
92
- const urlParams = new URLSearchParams(queryString);
93
- const code = urlParams.get(AUTH_CODE_PARAM);
94
- if (code && !dauthState.isAuthenticated) {
95
- return action.exchangeCodeAction({ ...ctx, code });
96
- }
97
- })();
98
- }, []);
99
-
100
- // Auto Login
61
+ // On mount: exchange code or auto-login via session cookie
101
62
  useEffect(() => {
102
- (async () => {
103
- // Skip if code exchange is in progress — that effect handles isLoading
104
- const urlParams = new URLSearchParams(window.location.search);
105
- if (urlParams.get(AUTH_CODE_PARAM)) return;
106
-
107
- const refreshToken = localStorage.getItem(storageKeys.refreshToken);
108
- if (refreshToken && !dauthState.isAuthenticated) {
109
- return action.setAutoLoginAction(ctx);
110
- } else {
111
- return dispatch({
112
- type: SET_IS_LOADING,
113
- payload: { isLoading: false },
114
- });
115
- }
116
- })();
117
- }, []);
118
-
119
- // Schedule proactive refresh when authenticated
120
- useEffect(() => {
121
- if (dauthState.isAuthenticated) {
122
- scheduleRefresh();
63
+ const params = new URLSearchParams(window.location.search);
64
+ const code = params.get(AUTH_CODE_PARAM);
65
+ if (code) {
66
+ action.exchangeCodeAction(ctx, code);
67
+ } else {
68
+ action.autoLoginAction(ctx);
123
69
  }
124
- return () => {
125
- if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
126
- };
127
- }, [dauthState.isAuthenticated, scheduleRefresh]);
70
+ }, []);
128
71
 
129
72
  const loginWithRedirect = useCallback(() => {
130
73
  const base = `${getClientBasePath()}/${domainName}/${routes.signin}`;
@@ -132,23 +75,10 @@ export const DauthProvider: React.FC<IDauthProviderProps> = (
132
75
  return window.location.replace(url);
133
76
  }, [domainName, env]);
134
77
 
135
- const logout = useCallback(() => {
136
- if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
137
- return action.setLogoutAction({
138
- dispatch,
139
- domainName,
140
- storageKeys,
141
- });
142
- }, [domainName, storageKeys]);
143
-
144
- const getAccessToken = useCallback(async () => {
145
- const token = await action.getAccessTokenAction(ctx);
146
- return token as string;
147
- }, [ctx]);
78
+ const logout = useCallback(() => action.logoutAction(ctx), [ctx]);
148
79
 
149
80
  const updateUser = useCallback(
150
81
  async (fields: Partial<IDauthUser>) => {
151
- const token_ls = localStorage.getItem(storageKeys.accessToken);
152
82
  const {
153
83
  name,
154
84
  lastname,
@@ -173,52 +103,25 @@ export const DauthProvider: React.FC<IDauthProviderProps> = (
173
103
  country,
174
104
  metadata,
175
105
  } as Partial<IDauthUser>;
176
- return (await action.setUpdateUserAction({
177
- ...ctx,
178
- user,
179
- token: token_ls,
180
- })) as boolean;
106
+ return action.updateUserAction(ctx, user);
181
107
  },
182
- [ctx, storageKeys.accessToken]
108
+ [ctx]
183
109
  );
184
110
 
185
- const updateUserWithRedirect = useCallback(() => {
186
- const token_ls = localStorage.getItem(storageKeys.accessToken);
187
- if (!token_ls) return;
188
- return window.location.replace(
189
- `${getClientBasePath()}/${domainName}/${routes.updateUser}/${token_ls}`
190
- );
191
- }, [domainName, storageKeys.accessToken]);
192
-
193
- const deleteAccount = useCallback(async () => {
194
- const token_ls = localStorage.getItem(storageKeys.accessToken);
195
- if (!token_ls) return false;
196
- if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
197
- return (await action.deleteAccountAction({
198
- ...ctx,
199
- token: token_ls,
200
- })) as boolean;
201
- }, [ctx, storageKeys.accessToken]);
111
+ const deleteAccount = useCallback(
112
+ () => action.deleteAccountAction(ctx),
113
+ [ctx]
114
+ );
202
115
 
203
116
  const memoProvider = useMemo(
204
117
  () => ({
205
118
  ...dauthState,
206
119
  loginWithRedirect,
207
120
  logout,
208
- getAccessToken,
209
121
  updateUser,
210
- updateUserWithRedirect,
211
122
  deleteAccount,
212
123
  }),
213
- [
214
- dauthState,
215
- loginWithRedirect,
216
- logout,
217
- getAccessToken,
218
- updateUser,
219
- updateUserWithRedirect,
220
- deleteAccount,
221
- ]
124
+ [dauthState, loginWithRedirect, logout, updateUser, deleteAccount]
222
125
  );
223
126
 
224
127
  return (
@@ -12,9 +12,7 @@ const initialDauthState: IDauthState = {
12
12
  isAuthenticated: false,
13
13
  loginWithRedirect: () => {},
14
14
  logout: () => {},
15
- getAccessToken: () => Promise.resolve(''),
16
15
  updateUser: () => Promise.resolve(false),
17
- updateUserWithRedirect: () => {},
18
16
  deleteAccount: () => Promise.resolve(false),
19
17
  };
20
18
 
package/src/interfaces.ts CHANGED
@@ -31,12 +31,18 @@ export interface IDauthAuthMethods {
31
31
  passkey: boolean;
32
32
  }
33
33
 
34
+ export interface IFormField {
35
+ field: string;
36
+ required: boolean;
37
+ }
38
+
34
39
  export interface IDauthDomainState {
35
40
  name: string;
36
41
  environments?: IDauthDomainEnvironment[];
37
42
  loginRedirect: string;
38
43
  allowedOrigins: string[];
39
44
  authMethods?: IDauthAuthMethods;
45
+ formFields?: IFormField[];
40
46
  }
41
47
 
42
48
  export interface IDauthState {
@@ -46,32 +52,28 @@ export interface IDauthState {
46
52
  isAuthenticated: boolean;
47
53
  loginWithRedirect: () => void;
48
54
  logout: () => void;
49
- getAccessToken: () => Promise<string>;
50
55
  updateUser: (fields: Partial<IDauthUser>) => Promise<boolean>;
51
- updateUserWithRedirect: () => void;
52
56
  deleteAccount: () => Promise<boolean>;
53
57
  }
54
58
 
59
+ export interface DauthProfileModalProps {
60
+ open: boolean;
61
+ onClose: () => void;
62
+ }
63
+
55
64
  export interface IActionStatus {
56
65
  type: TStatusTypes;
57
66
  message: string;
58
67
  }
59
68
  export type TStatusTypes = 'success' | 'error' | 'info' | 'warning';
60
69
 
61
- export interface IDauthStorageKeys {
62
- accessToken: string;
63
- refreshToken: string;
64
- }
65
-
66
70
  export interface IDauthProviderProps {
67
71
  domainName: string;
68
72
  children: React.ReactNode;
69
- storageKey?: {
70
- accessToken?: string;
71
- refreshToken?: string;
72
- };
73
+ /** Base path of the auth proxy on the consumer backend. Default: '/api/auth'. */
74
+ authProxyPath?: string;
73
75
  onError?: (error: Error) => void;
74
76
  env?: string;
75
- /** Override the dauth server URL (e.g. 'https://dev.dauth.ovh' for staging) */
77
+ /** Override the dauth frontend URL for loginWithRedirect (e.g. 'https://dev.dauth.ovh' for staging) */
76
78
  dauthUrl?: string;
77
79
  }