update usermanagement to legacy mode

This commit is contained in:
mengstabketemaw
2026-06-18 11:25:26 +03:00
parent 3a528c9515
commit 2feaaf1ee4
13 changed files with 158 additions and 57 deletions

View File

@@ -1,3 +1,4 @@
export * from './lib/base-api';
export * from './lib/query-and-mutation';
export * from './lib/session';
export { baseQueryWithReauth, configureTokenRefresh } from './lib/base-api/base-query-with-reauth';

View File

@@ -0,0 +1,53 @@
import { fetchBaseQuery, type BaseQueryFn } from '@reduxjs/toolkit/query/react';
import type { FetchArgs, FetchBaseQueryError } from '@reduxjs/toolkit/query';
import { resolveSessionContext } from '../session';
const BASE_API_URL =
(import.meta as { env?: Record<string, string> }).env?.['VITE_BASE_API_URL'] ??
'http://localhost:3001/api';
let _onTokenExpired: (() => Promise<string>) | null = null;
let _onAuthFailure: (() => void) | null = null;
export function configureTokenRefresh(config: {
onTokenExpired: () => Promise<string>;
onAuthFailure: () => void;
}) {
_onTokenExpired = config.onTokenExpired;
_onAuthFailure = config.onAuthFailure;
}
export const baseQueryWithReauth: BaseQueryFn<
string | FetchArgs,
unknown,
FetchBaseQueryError
> = async (args, api, extraOptions) => {
const baseQuery = fetchBaseQuery({
baseUrl: BASE_API_URL,
prepareHeaders: (headers) => {
const { token, sessionHeaders } = resolveSessionContext(
api.getState() as { auth?: { token?: string } },
);
if (token) headers.set('Authorization', `Bearer ${token}`);
Object.entries(sessionHeaders).forEach(([k, v]) => headers.set(k, v));
return headers;
},
});
let result = await baseQuery(args, api, extraOptions);
if (result.error?.status === 401) {
if (_onTokenExpired) {
try {
await _onTokenExpired();
result = await baseQuery(args, api, extraOptions);
} catch {
_onAuthFailure?.();
}
} else {
_onAuthFailure?.();
}
}
return result;
};

View File

@@ -1,23 +1,9 @@
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import { resolveSessionContext } from '../session';
const BASE_API_URL =
(import.meta as { env?: Record<string, string> }).env?.['VITE_BASE_API_URL'] ??
'http://localhost:3001/api';
import { createApi } from '@reduxjs/toolkit/query/react';
import { baseQueryWithReauth } from './base-query-with-reauth';
export const baseApi = createApi({
reducerPath: 'baseApi',
baseQuery: fetchBaseQuery({
baseUrl: BASE_API_URL,
prepareHeaders: (headers, { getState }) => {
const { token, sessionHeaders } = resolveSessionContext(
getState() as { auth?: { token?: string } },
);
if (token) headers.set('Authorization', `Bearer ${token}`);
Object.entries(sessionHeaders).forEach(([k, v]) => headers.set(k, v));
return headers;
},
}),
baseQuery: baseQueryWithReauth,
tagTypes: ['Api'],
endpoints: () => ({}),
});

View File

@@ -1,6 +1,7 @@
export { AuthConfigProvider, useAuthConfig } from './lib/AuthConfig';
export type { AuthConfigValue } from './lib/AuthConfig';
export { AuthShell, BrandMark } from './lib/components/AuthShell';
export { ProtectedRoute } from './lib/components/ProtectedRoute';
export { LoginPage } from './lib/pages/LoginPage';
export { SignupPage } from './lib/pages/SignupPage';
export { ForgotPasswordPage } from './lib/pages/ForgotPasswordPage';
@@ -8,4 +9,5 @@ export { OTPVerificationPage } from './lib/pages/OTPVerificationPage';
export { authReducer, loginSuccess, setUser, logout, hydrateAuth } from './lib/store/auth.slice';
export { signupReducer, setSignupData, setSignupStep, resetSignup } from './lib/store/signup.slice';
export { configureAuthStorage, authStorage } from './lib/utils/auth-storage';
export { refreshAccessToken } from './lib/utils/refresh-token';
export type { AuthUser, AuthState, LoginPayload } from './lib/types/auth.types';

View File

@@ -0,0 +1,24 @@
import { Navigate, Outlet, useLocation } from 'react-router-dom';
import type { ReactNode } from 'react';
import { authStorage } from '../utils/auth-storage';
interface ProtectedRouteProps {
children?: ReactNode;
loginPath?: string;
}
function getTokenFromCookie(): string | undefined {
const match = document.cookie.match(/(?:^|;\s*)auth-token=([^;]*)/);
return match ? decodeURIComponent(match[1]) : undefined;
}
export function ProtectedRoute({ children, loginPath = '/login' }: ProtectedRouteProps) {
const location = useLocation();
const token = authStorage.getToken() ?? getTokenFromCookie();
if (!token) {
return <Navigate to={loginPath} state={{ from: location }} replace />;
}
return children ? <>{children}</> : <Outlet />;
}

View File

@@ -25,5 +25,7 @@ export const authStorage = {
[key('auth-token'), key('refresh-token'), key('auth-user')].forEach((k) =>
localStorage.removeItem(k),
);
document.cookie =
'auth-token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC; SameSite=Lax';
},
};

View File

@@ -0,0 +1,31 @@
import { authStorage } from './auth-storage';
const BASE_API_URL =
(import.meta as { env?: Record<string, string> }).env?.['VITE_BASE_API_URL'] ??
'http://localhost:3001/api';
interface RefreshResponse {
token: string;
refreshToken: string;
}
export async function refreshAccessToken(): Promise<string> {
const refreshToken = authStorage.getRefreshToken();
if (!refreshToken) throw new Error('No refresh token available');
const response = await fetch(`${BASE_API_URL}/auth/refresh`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refreshToken }),
});
if (!response.ok) {
authStorage.clear();
throw new Error('Token refresh failed');
}
const data: RefreshResponse = await response.json();
authStorage.setToken(data.token);
if (data.refreshToken) authStorage.setRefreshToken(data.refreshToken);
return data.token;
}