updates on the address while creasating the orders and security updates as well
This commit is contained in:
@@ -2,29 +2,32 @@ import PropTypes from 'prop-types';
|
||||
import { useEffect } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
// project import
|
||||
import useAuth from 'hooks/useAuth';
|
||||
import { isSessionValid } from './authState';
|
||||
|
||||
// ==============================|| AUTH GUARD ||============================== //
|
||||
|
||||
// Blocks a protected route when there is no valid session.
|
||||
//
|
||||
// Previously this used useAuth(), which throws unless JWTProvider wraps the
|
||||
// tree -- and that provider is commented out in App.js. That is why the guard
|
||||
// was disabled in the route files rather than fixed, leaving every route
|
||||
// publicly reachable. It now reads the same localStorage session the rest of
|
||||
// the app uses.
|
||||
//
|
||||
// Children are withheld while unauthenticated so a protected page cannot
|
||||
// paint (and fire its data queries) during the redirect.
|
||||
const AuthGuard = ({ children }) => {
|
||||
const { isLoggedIn } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const ok = isSessionValid();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoggedIn) {
|
||||
navigate('login', {
|
||||
state: {
|
||||
from: location.pathname
|
||||
},
|
||||
replace: true
|
||||
});
|
||||
navigate('login', { replace: true });
|
||||
if (!ok) {
|
||||
navigate('/login', { state: { from: location.pathname }, replace: true });
|
||||
}
|
||||
}, [isLoggedIn, navigate, location]);
|
||||
}, [ok, navigate, location.pathname]);
|
||||
|
||||
return children;
|
||||
return ok ? children : null;
|
||||
};
|
||||
|
||||
AuthGuard.propTypes = {
|
||||
|
||||
@@ -2,28 +2,30 @@ import PropTypes from 'prop-types';
|
||||
import { useEffect } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
// project import
|
||||
import { APP_DEFAULT_PATH } from 'config';
|
||||
import useAuth from 'hooks/useAuth';
|
||||
import { isSessionValid } from './authState';
|
||||
|
||||
// ==============================|| GUEST GUARD ||============================== //
|
||||
|
||||
// Keeps an already-signed-in user off the login page, sending them back to
|
||||
// wherever AuthGuard bounced them from (or the default landing path).
|
||||
//
|
||||
// Reads the same localStorage session as AuthGuard -- see authState.js for why
|
||||
// this no longer uses useAuth().
|
||||
const GuestGuard = ({ children }) => {
|
||||
const { isLoggedIn } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const ok = isSessionValid();
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoggedIn) {
|
||||
navigate(location?.state?.from ? location?.state?.from : APP_DEFAULT_PATH, {
|
||||
state: {
|
||||
from: ''
|
||||
},
|
||||
replace: true
|
||||
});
|
||||
if (ok) {
|
||||
navigate(location?.state?.from || APP_DEFAULT_PATH, { state: { from: '' }, replace: true });
|
||||
}
|
||||
}, [isLoggedIn, navigate, location]);
|
||||
}, [ok, navigate, location]);
|
||||
|
||||
// Unlike AuthGuard, render children regardless: a signed-in user seeing the
|
||||
// login form for one frame is harmless, whereas blanking it would leave a
|
||||
// signed-out user staring at nothing if the redirect ever failed.
|
||||
return children;
|
||||
};
|
||||
|
||||
|
||||
25
src/utils/route-guard/authState.js
Normal file
25
src/utils/route-guard/authState.js
Normal file
@@ -0,0 +1,25 @@
|
||||
// Single source of truth for "is this session still valid".
|
||||
//
|
||||
// The app authenticates via localStorage (login.js writes authname/tenantid/
|
||||
// applocationid/...), NOT via the JWTContext this template shipped with --
|
||||
// JWTProvider is commented out in App.js, so useAuth() throws. The guards
|
||||
// therefore read localStorage directly, using the same 1-hour window App.js
|
||||
// already enforces on its interval, so both agree on when a session is over.
|
||||
|
||||
export const SESSION_MAX_AGE_MS = 60 * 60 * 1000;
|
||||
|
||||
export const isSessionValid = () => {
|
||||
if (typeof window === 'undefined') return false;
|
||||
try {
|
||||
if (!localStorage.getItem('authname')) return false;
|
||||
const started = Number(localStorage.getItem('sessionStartTime'));
|
||||
// A missing/unparseable start time means a session written before this
|
||||
// check existed. Treat it as valid rather than logging the user out --
|
||||
// App.js stamps one on mount.
|
||||
if (!Number.isFinite(started) || started <= 0) return true;
|
||||
return Date.now() - started < SESSION_MAX_AGE_MS;
|
||||
} catch (e) {
|
||||
// Private mode or blocked storage: fail closed.
|
||||
return false;
|
||||
}
|
||||
};
|
||||
70
src/utils/route-guard/authState.test.js
Normal file
70
src/utils/route-guard/authState.test.js
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Session-validity tests. This function now gates every protected route, so
|
||||
* a wrong answer either locks out a legitimate operator or leaves the console
|
||||
* publicly reachable.
|
||||
*/
|
||||
|
||||
import { isSessionValid, SESSION_MAX_AGE_MS } from './authState';
|
||||
|
||||
const setSession = (authname, startedMsAgo) => {
|
||||
localStorage.clear();
|
||||
if (authname !== null) localStorage.setItem('authname', authname);
|
||||
if (startedMsAgo !== undefined) localStorage.setItem('sessionStartTime', String(Date.now() - startedMsAgo));
|
||||
};
|
||||
|
||||
beforeEach(() => localStorage.clear());
|
||||
|
||||
describe('isSessionValid', () => {
|
||||
it('rejects when nothing is stored', () => {
|
||||
expect(isSessionValid()).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects when authname is absent even if a start time exists', () => {
|
||||
localStorage.setItem('sessionStartTime', String(Date.now()));
|
||||
expect(isSessionValid()).toBe(false);
|
||||
});
|
||||
|
||||
it('accepts a fresh session', () => {
|
||||
setSession('operator', 1000);
|
||||
expect(isSessionValid()).toBe(true);
|
||||
});
|
||||
|
||||
it('accepts a session just inside the window', () => {
|
||||
setSession('operator', SESSION_MAX_AGE_MS - 5000);
|
||||
expect(isSessionValid()).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects a session past the window', () => {
|
||||
setSession('operator', SESSION_MAX_AGE_MS + 1000);
|
||||
expect(isSessionValid()).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects exactly at the boundary', () => {
|
||||
setSession('operator', SESSION_MAX_AGE_MS);
|
||||
expect(isSessionValid()).toBe(false);
|
||||
});
|
||||
|
||||
it('accepts when the start time is missing, rather than logging the user out', () => {
|
||||
// A session written before this check existed. App.js stamps one on mount.
|
||||
localStorage.setItem('authname', 'operator');
|
||||
expect(isSessionValid()).toBe(true);
|
||||
});
|
||||
|
||||
it.each([['not-a-number'], [''], ['0'], ['-1']])('accepts an unparseable start time (%p)', (value) => {
|
||||
localStorage.setItem('authname', 'operator');
|
||||
localStorage.setItem('sessionStartTime', value);
|
||||
expect(isSessionValid()).toBe(true);
|
||||
});
|
||||
|
||||
it('fails closed when storage throws', () => {
|
||||
const spy = jest.spyOn(Storage.prototype, 'getItem').mockImplementation(() => {
|
||||
throw new Error('SecurityError: storage blocked');
|
||||
});
|
||||
expect(isSessionValid()).toBe(false);
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it('matches the 1-hour window App.js enforces', () => {
|
||||
expect(SESSION_MAX_AGE_MS).toBe(60 * 60 * 1000);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user