[FEAT] review the loggin model to be satble and efficient with user interactions

Signed-off-by: Edouard DUPIN <yui.heero@gmail.com>
This commit is contained in:
Edouard DUPIN 2025-03-23 14:11:30 +01:00
parent 83f8ec0e9b
commit 4caec9a54a
14 changed files with 357 additions and 219 deletions

View File

@ -35,6 +35,7 @@ public class WebLauncherLocal extends WebLauncher {
ConfigBaseVariable.dbPort = "3906";
ConfigBaseVariable.testMode = "true";
}
// Test fail of SSO: ConfigBaseVariable.ssoAdress = null;
try {
super.migrateDB();
} catch (final Exception e) {

View File

@ -1,6 +1,13 @@
import { useEffect, useRef, useState } from 'react';
import { Box, Flex, IconButton, SliderTrack, Text } from '@chakra-ui/react';
import {
Box,
Flex,
IconButton,
SliderTrack,
Text,
chakra,
} from '@chakra-ui/react';
import {
MdFastForward,
MdFastRewind,
@ -223,6 +230,7 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
paddingX="10px"
marginX="15px"
bottom={0}
//top="calc(100% - 150px)"
left={0}
right={0}
zIndex={1000}
@ -318,7 +326,9 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
marginLeft="auto"
variant="ghost"
>
<MdNavigateBefore style={{ width: '100%', height: '100%' }} />{' '}
<MdNavigateBefore
style={{ width: '100%', height: '100%' }}
/>{' '}
</IconButton>
<IconButton
{...configButton}
@ -357,7 +367,7 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
</Flex>
)}
<audio
<chakra.audio
src={mediaSource}
ref={audioRef}
//preload={true}

View File

@ -29,6 +29,7 @@ import {
MdMore,
MdOutlinePlaylistPlay,
MdOutlineUploadFile,
MdRestartAlt,
MdSupervisedUserCircle,
} from 'react-icons/md';
import { useNavigate } from 'react-router-dom';
@ -47,7 +48,6 @@ import {
MenuTrigger,
} from '@/components/ui/menu';
import { useServiceContext } from '@/service/ServiceContext';
import { SessionState } from '@/service/SessionState';
import { useSessionService } from '@/service/session';
import { colors } from '@/theme/colors';
import {
@ -120,11 +120,16 @@ export const TopBar = ({
const navigate = useNavigate();
const { colorMode, toggleColorMode } = useColorMode();
const { session } = useServiceContext();
const { clearToken } = useSessionService();
const { clearToken, isConnected } = useSessionService();
const backColor = useColorModeValue('back.100', 'back.800');
const drawerDisclose = useDisclosure();
const onChangeTheme = () => {
drawerDisclose.onOpen();
const isVisible = useBreakpointValue({ base: false, md: true });
const onOpenLeftMenu = () => {
if (!isConnected) {
onForceReload();
} else {
drawerDisclose.onOpen();
}
};
const onSignIn = (): void => {
clearToken();
@ -141,7 +146,10 @@ export const TopBar = ({
const onKarso = (): void => {
requestOpenSite();
};
const isVisible = useBreakpointValue({ base: false, md: true });
const onForceReload = (): void => {
// @ts-expect-error
window.location.reload(true);
};
return (
<Flex
minWidth="320px"
@ -158,7 +166,7 @@ export const TopBar = ({
boxShadow={'0px 2px 4px ' + colors.back[900]}
zIndex={200}
>
<Button {...BUTTON_TOP_BAR_PROPERTY} onClick={onChangeTheme}>
<Button {...BUTTON_TOP_BAR_PROPERTY} onClick={onOpenLeftMenu}>
<HStack>
<LuAlignJustify />
{isVisible && (
@ -169,7 +177,7 @@ export const TopBar = ({
</HStack>
</Button>
{title && (
<Text
<Flex
truncate
fontSize="20px"
fontWeight="bold"
@ -183,11 +191,11 @@ export const TopBar = ({
{titleIcon}
{title}
</Flex>
</Text>
</Flex>
)}
{children}
<Flex right="0">
{session?.state !== SessionState.CONNECTED && (
{!session?.isConnected && (
<>
<Button {...BUTTON_TOP_BAR_PROPERTY} onClick={onSignIn}>
<LuLogIn />
@ -207,7 +215,7 @@ export const TopBar = ({
</Button>
</>
)}
{session?.state === SessionState.CONNECTED && (
{session?.isConnected && (
<MenuRoot>
<MenuTrigger asChild>
<IconButton {...BUTTON_TOP_BAR_PROPERTY} width={TOP_BAR_HEIGHT}>
@ -248,6 +256,13 @@ export const TopBar = ({
<MenuItem value="karso" valueText="Karso" onClick={onKarso}>
<LuKeySquare /> Karso (SSO)
</MenuItem>
<MenuItem
value="force_reload"
valueText="Karso"
onClick={onForceReload}
>
<MdRestartAlt /> force reload
</MenuItem>
{colorMode === 'light' ? (
<MenuItem
value="set-dark"
@ -269,50 +284,52 @@ export const TopBar = ({
</MenuRoot>
)}
</Flex>
<DrawerRoot
placement="start"
onOpenChange={drawerDisclose.onClose}
open={drawerDisclose.open}
data-testid="top-bar_drawer-root"
>
<DrawerContent data-testid="top-bar_drawer-content">
<DrawerHeader
paddingY="auto"
as="button"
onClick={drawerDisclose.onClose}
boxShadow={'0px 2px 4px ' + colors.back[900]}
backgroundColor={backColor}
color={useColorModeValue('brand.900', 'brand.50')}
textTransform="uppercase"
>
<HStack {...BUTTON_TOP_BAR_PROPERTY} cursor="pointer">
<LuArrowBigLeft />
<Span paddingLeft="3px">Karusic</Span>
</HStack>
</DrawerHeader>
<DrawerBody paddingX="0px">
<Box marginY="3" />
<ButtonMenuLeft
onClickEnd={drawerDisclose.onClose}
dest="/"
title="Home"
icon={<MdHome />}
/>
<ButtonMenuLeft
onClickEnd={drawerDisclose.onClose}
dest="/on-air"
title="On air"
icon={<MdOutlinePlaylistPlay />}
/>
<ButtonMenuLeft
onClickEnd={drawerDisclose.onClose}
dest="/add"
title="Add Media"
icon={<MdOutlineUploadFile />}
/>
</DrawerBody>
</DrawerContent>
</DrawerRoot>
{session?.isConnected && (
<DrawerRoot
placement="start"
onOpenChange={drawerDisclose.onClose}
open={drawerDisclose.open}
data-testid="top-bar_drawer-root"
>
<DrawerContent data-testid="top-bar_drawer-content">
<DrawerHeader
paddingY="auto"
as="button"
onClick={drawerDisclose.onClose}
boxShadow={'0px 2px 4px ' + colors.back[900]}
backgroundColor={backColor}
color={useColorModeValue('brand.900', 'brand.50')}
textTransform="uppercase"
>
<HStack {...BUTTON_TOP_BAR_PROPERTY} cursor="pointer">
<LuArrowBigLeft />
<Span paddingLeft="3px">Karusic</Span>
</HStack>
</DrawerHeader>
<DrawerBody paddingX="0px">
<Box marginY="3" />
<ButtonMenuLeft
onClickEnd={drawerDisclose.onClose}
dest="/"
title="Home"
icon={<MdHome />}
/>
<ButtonMenuLeft
onClickEnd={drawerDisclose.onClose}
dest="/on-air"
title="On air"
icon={<MdOutlinePlaylistPlay />}
/>
<ButtonMenuLeft
onClickEnd={drawerDisclose.onClose}
dest="/add"
title="Add Media"
icon={<MdOutlineUploadFile />}
/>
</DrawerBody>
</DrawerContent>
</DrawerRoot>
)}
</Flex>
);
};

View File

@ -21,7 +21,7 @@ import { SettingsPage } from './home/SettingsPage';
import { OnAirPage } from './onAir/OnAirPage';
export const AppRoutes = () => {
const { isReadable } = useHasRight('user');
const { isReadable } = useHasRight('USER');
return (
<HistoryRouter
// @ts-expect-error

View File

@ -1,48 +1,58 @@
import { useEffect } from 'react';
import { Center, Heading, Image, Text } from '@chakra-ui/react';
import { Center, Flex, Heading, Image, Text } from '@chakra-ui/react';
import { MdFactCheck, MdWarning } from 'react-icons/md';
import { useNavigate, useParams } from 'react-router-dom';
import { useEffectOnce } from 'react-use';
import avatar_generic from '@/assets/images/avatar_generic.svg';
import { Icon } from '@/components';
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
import { TopBar } from '@/components/TopBar/TopBar';
import { isDevelopmentEnvironment } from '@/environment';
import { SessionState } from '@/service/SessionState';
import { toaster } from '@/components/ui/toaster';
import { useSessionService } from '@/service/session';
import { b64_to_utf8 } from '@/utils/sso';
export const SSOPage = () => {
const { data, keepConnected, token } = useParams();
console.log(`- data: ${data}`);
console.log(`- keepConnected: ${keepConnected}`);
console.log(`- token: ${token}`);
//const { data, keepConnected, token } = useState<string|undefined>();
// console.log(`- data: ${data}`);
// console.log(`- keepConnected: ${keepConnected}`);
// console.log(`- token: ${token}`);
const navigate = useNavigate();
const { state, setToken, login, clearToken } = useSessionService();
useEffect(() => {
if (token) {
setToken(token);
} else {
clearToken();
const { isConnected, errorSession, setToken, login } = useSessionService();
useEffectOnce(() => {
if (token === undefined || token === '') {
return;
}
}, [token, setToken, clearToken]);
const delay = isDevelopmentEnvironment() ? 2000 : 2000;
try {
setToken(token);
} catch (e) {
toaster.create({
title: 'Connection Fail',
description: `invalid token data model.`,
type: 'error',
});
navigate('/');
}
});
const delay = 1000;
useEffect(() => {
if (state === SessionState.CONNECTED) {
const destination = data ? b64_to_utf8(data) : '/';
console.log(`program redirect to: ${destination} (${delay}ms)`);
if (isConnected) {
toaster.create({
title: 'Connection Succeed',
description: `Welcome back: ${login}.`,
type: 'success',
});
const destination = data ? b64_to_utf8(data) : '/home';
const destinationFinal = destination === '' ? '/home' : destination;
console.log(`program redirect to: '${destinationFinal}' (${delay}ms)`);
setTimeout(() => {
navigate(`/${destination}`);
// note: check if the "navigate" is in the dependence of the use effect!!!
navigate(`/${destinationFinal}`, { replace: true });
}, delay);
}
}, [state]);
/*
const [searchParams] = useSearchParams();
console.log(`data: ${searchParams.get('data')}`);
console.log(`keepConnected: ${searchParams.get('keepConnected')}`);
console.log(`token: ${searchParams.get('token')}`);
const dataFromParam = useGetCreateActionParams();
console.log(`data group: ${JSON.stringify(dataFromParam, null, 2)}`);
*/
}, [isConnected, login, navigate]);
return (
<>
<TopBar />
@ -54,31 +64,53 @@ export const SSOPage = () => {
<Center w="full">
<Image src={avatar_generic} boxSize="150px" borderRadius="full" />
</Center>
{token === '__CANCEL__' && (
<Text>
<b>ERROR: </b> Request cancel of connection !
</Text>
)}
{token === '__FAIL__' && (
<Text>
<b>ERROR: </b> Connection FAIL !
</Text>
)}
{token === '__LOGOUT__' && (
<Text>
<b>Dis-connected: </b> Redirect soon!{' '}
</Text>
)}
{!['__LOGOUT__', '__FAIL__', '__CANCEL__'].includes(token ?? '') && (
{!isConnected && errorSession !== undefined && (
<>
<Text>
<b>Connected: </b> Redirect soon!
</Text>
<Text>
<b>Welcome back: </b> {login}
</Text>
<Flex color="red.500" fontSize="25px" gap={2} marginX="auto">
<Icon sizeIcon="55px">
<MdWarning />
</Icon>
<Text marginY="auto">
<b>ERROR: </b> {errorSession.message}
</Text>
</Flex>
{errorSession.description && (
<Text
whiteSpace="pre-line"
fontSize="25px"
gap={2}
marginX="auto"
>
{errorSession.description}
</Text>
)}
</>
)}
{!isConnected && errorSession === undefined && (
<Flex color="red.500" fontSize="25px" gap={2} marginX="auto">
<Icon sizeIcon="55px">
<MdWarning />
</Icon>
<Text marginY="auto">
<b>ERROR: </b> Not connected !
</Text>
</Flex>
)}
{isConnected && (
<Flex direction="column">
<Flex color="green.500" fontSize="25px" gap={2} marginX="auto">
<MdFactCheck />
<Text marginY="auto">
<b>Connected: </b> Redirect soon!
</Text>
</Flex>
<Flex fontSize="25px" gap={2} marginX="auto">
<Text marginY="auto">
<b>Welcome back: </b> {login}
</Text>
</Flex>
</Flex>
)}
</PageLayoutInfoCenter>
</>
);

View File

@ -1,4 +1,4 @@
import { useEffect, useMemo, useState } from 'react';
import { useMemo } from 'react';
import { Album, AlbumResource } from '@/back-api';
import { useServiceContext } from '@/service/ServiceContext';
@ -23,14 +23,14 @@ export const useAlbumServiceWrapped = (
{
restApiName: 'ALBUM',
primaryKey: 'id',
available: session.token !== undefined,
available: session.isConnected,
getsCall: () => {
return AlbumResource.gets({
restConfig: session.getRestConfig(),
});
},
},
[session.token]
[session.isConnected]
);
return { store };

View File

@ -23,14 +23,14 @@ export const useArtistServiceWrapped = (
{
restApiName: 'ARTIST',
primaryKey: 'id',
available: session.token !== undefined,
available: session.isConnected,
getsCall: () => {
return ArtistResource.gets({
restConfig: session.getRestConfig(),
});
},
},
[session.token]
[session.isConnected]
);
return { store };

View File

@ -23,14 +23,14 @@ export const useGenderServiceWrapped = (
{
restApiName: 'GENDER',
primaryKey: 'id',
available: session.token !== undefined,
available: session.isConnected,
getsCall: () => {
return GenderResource.gets({
restConfig: session.getRestConfig(),
});
},
},
[session.token]
[session.isConnected]
);
return { store };

View File

@ -1,5 +1,6 @@
import { ReactNode, createContext, useContext, useMemo } from 'react';
import { Album, Artist, Gender, Track } from '@/back-api';
import {
ActivePlaylistServiceProps,
useActivePlaylistServiceWrapped,
@ -7,7 +8,6 @@ import {
import { AlbumServiceProps, useAlbumServiceWrapped } from '@/service/Album';
import { ArtistServiceProps, useArtistServiceWrapped } from '@/service/Artist';
import { useGenderServiceWrapped } from '@/service/Gender';
import { SessionState } from '@/service/SessionState';
import { TrackServiceProps, useTrackServiceWrapped } from '@/service/Track';
import {
RightPart,
@ -15,7 +15,6 @@ import {
getRestConfig,
useSessionServiceWrapped,
} from '@/service/session';
import { Album, Artist, Gender, Track } from '@/back-api';
import { DataStoreType } from '@/utils/data-store';
export type ServiceContextType = {
@ -27,7 +26,6 @@ export type ServiceContextType = {
activePlaylist: ActivePlaylistServiceProps;
};
function emptyStore<TYPE>(): DataStoreType<TYPE> {
return {
data: [] as TYPE[],
@ -43,22 +41,29 @@ function emptyStore<TYPE>(): DataStoreType<TYPE> {
error: undefined,
update: (_request: Promise<TYPE>, _key?: string) => {
console.error('!!! WTF !!!');
return new Promise((resolve, reject) => { reject("fail") });
return new Promise((resolve, reject) => {
reject('fail');
});
},
updateRaw: (_data: TYPE, _key?: string) => { },
remove: (_id: number | string, _request: Promise<void>, _key?: string): void => {
updateRaw: (_data: TYPE, _key?: string) => {},
remove: (
_id: number | string,
_request: Promise<void>,
_key?: string
): void => {
console.error('!!! WTF !!!');
}
},
};
}
export const ServiceContext = createContext<ServiceContextType>({
session: {
setToken: (token: string) => { },
clearToken: () => { },
isConnected: false,
errorSession: undefined,
setToken: (token: string) => {},
clearToken: () => {},
hasReadRight: (part: RightPart) => false,
hasWriteRight: (part: RightPart) => false,
state: SessionState.NO_USER,
getRestConfig: getRestConfig,
},
track: {

View File

@ -1,7 +0,0 @@
export enum SessionState {
NO_USER,
CONNECTING,
CONNECTION_FAIL,
CONNECTED,
DISCONNECT,
}

View File

@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useMemo } from 'react';
import { Track, TrackResource } from '@/back-api';
import { useServiceContext } from '@/service/ServiceContext';
@ -23,14 +23,14 @@ export const useTrackServiceWrapped = (
{
restApiName: 'TRACK',
primaryKey: 'id',
available: session.token !== undefined,
available: session.isConnected,
getsCall: () => {
return TrackResource.gets({
restConfig: session.getRestConfig(),
});
},
},
[session.token]
[session.isConnected]
);
return { store };

View File

@ -1,18 +1,18 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { createListCollection } from '@chakra-ui/react';
import { useEffectOnce } from 'react-use';
import {
JwtToken,
PartRight,
RESTConfig,
UserMe,
RestErrorResponse,
UserResource,
setErrorApiGlobalCallback,
} from '@/back-api';
import { toaster } from '@/components/ui/toaster';
import { environment, getApiUrl } from '@/environment';
import { useServiceContext } from '@/service/ServiceContext';
import { SessionState } from '@/service/SessionState';
import { isBrowser } from '@/utils/layout';
import { parseToken } from '@/utils/sso';
@ -37,7 +37,7 @@ export const USERS = {
export const getUserToken = () => {
return localStorage.getItem(TOKEN_KEY);
};
export type RightPart = 'admin' | 'user';
export type RightPart = 'ADMIN' | 'USER';
export function getRestConfig(): RESTConfig {
return {
@ -46,14 +46,48 @@ export function getRestConfig(): RESTConfig {
};
}
const getDeltaPrint = (date: Date): string => {
const now = new Date();
let deltaSeconds = Math.floor((date.getTime() - now.getTime()) / 1000);
const isPast = deltaSeconds < 0;
deltaSeconds = Math.abs(deltaSeconds);
const days = Math.floor(deltaSeconds / 86400);
deltaSeconds %= 86400;
const hours = Math.floor(deltaSeconds / 3600);
deltaSeconds %= 3600;
const minutes = Math.floor(deltaSeconds / 60);
const seconds = deltaSeconds % 60;
const result = [days > 0 ? `${days}j` : '', `${hours}h${minutes}:${seconds}s`]
.filter(Boolean)
.join(' ');
return `${result}${isPast ? ' ago' : ''}`;
};
const isInThePast = (date: Date): boolean => {
const now = new Date();
return date.getTime() < now.getTime();
};
export type SessionErrorType = {
message: string;
description?: string;
};
export type SessionServiceProps = {
token?: string;
isConnected: boolean;
errorSession?: SessionErrorType;
setToken: (token: string) => void;
clearToken: () => void;
login?: string;
hasReadRight: (part: RightPart) => boolean;
hasWriteRight: (part: RightPart) => boolean;
state: SessionState;
getRestConfig: () => RESTConfig;
};
@ -63,115 +97,161 @@ export const useSessionService = (): SessionServiceProps => {
};
export const useSessionServiceWrapped = (): SessionServiceProps => {
const [token, setToken] = useState<string | undefined>(
// Load the token from the system (init)
const [tokenStorage, setTokenStorage] = useState<string | undefined>(
isBrowser ? (localStorage.getItem(TOKEN_KEY) ?? undefined) : undefined
);
const [state, setState] = useState<SessionState>(SessionState.NO_USER);
const [config, setConfig] = useState<UserMe | undefined>(undefined);
// Token that is ready to use
const [tokenStr, setTokenStr] = useState<string>('');
const [errorSession, setErrorSession] = useState<
SessionErrorType | undefined
>(undefined);
const [token, setToken] = useState<JwtToken | undefined>(undefined);
useEffectOnce(() => {
setErrorApiGlobalCallback((response: Response) => {
if (response.status == 401) {
console.error('Detect 401 error ==> remove token');
clearToken();
}
});
});
const getRestConfigLocal = useCallback((): RESTConfig => {
return {
server: getApiUrl(),
token: tokenStr,
};
}, [tokenStr]);
const updateRight = useCallback(() => {
console.log('update right...');
const updateRight = useEffect(() => {
//console.log('Detect a new token...');
if (isBrowser) {
console.log('Detect a new token...');
if (token === undefined) {
console.log(` ==> No User`);
setState(SessionState.NO_USER);
setConfig(undefined);
//console.log('update internal property');
if (tokenStorage === undefined) {
//console.log(` ==> No User`);
setToken(undefined);
setTokenStr('');
localStorage.removeItem(TOKEN_KEY);
} else {
console.log(' ==> Login ... (try to keep right)');
setState(SessionState.CONNECTING);
localStorage.setItem(TOKEN_KEY, token);
//console.log(' ==> Login ... (try to keep right)');
let tokenParsed: JwtToken | undefined = undefined;
try {
tokenParsed = parseToken(tokenStorage);
} catch (e) {
setErrorSession({
message: 'Fail to parse the token',
});
return;
}
//console.log(`get new token: ${JSON.stringify(tokenParsed, null, 2)}`);
const exp = new Date(tokenParsed.payload.exp * 1000);
if (isInThePast(exp)) {
//console.log(`token expired at: exp: ${exp.toISOString()}: delta=${getDeltaPrint(exp)}`);
const iat = new Date(tokenParsed.payload.iat * 1000);
//console.log(`iat: ${iat.toISOString()}: delta=${getDeltaPrint(iat)}`);
setErrorSession({
message: 'The inserted token has expired',
description: `It expired at ${exp.toISOString()}\nSince: ${getDeltaPrint(exp)}`,
});
return;
}
// Validate token on the server:
UserResource.getMe({
restConfig: getRestConfig(),
restConfig: {
server: getApiUrl(),
token: tokenStorage,
},
})
.then((response: UserMe) => {
//console.log(` ==> New right arrived to '${response.login}'`);
setState(SessionState.CONNECTED);
setConfig(response);
.then((_response) => {
// if the login work well, then the token does not fail with authentication
//console.log('Authentication finished: ...');
setTokenStr(tokenStorage);
setToken(tokenParsed);
localStorage.setItem(TOKEN_KEY, tokenStorage);
})
.catch((error) => {
setState(SessionState.CONNECTION_FAIL);
setConfig(undefined);
//console.log(` ==> Fail to get right: '${error}'`);
.catch((error: RestErrorResponse) => {
setErrorSession({
message: 'The server reject the token.',
description: `name: ${error.name}\nmessage: "${error.message}"`,
});
setTokenStr('');
setToken(undefined);
localStorage.removeItem(TOKEN_KEY);
});
}
}
}, [localStorage, parseToken, token]);
}, [
localStorage,
parseToken,
setErrorSession,
setToken,
setTokenStr,
tokenStorage,
]);
const setTokenLocal = useCallback(
(token?: string) => {
if (token ? token.startsWith('__') : false) {
token = undefined;
}
setToken(token);
updateRight();
localStorage.removeItem(TOKEN_KEY);
setTokenStorage(token);
setTokenStr('');
setToken(undefined);
setErrorSession(undefined);
},
[updateRight, setToken]
);
const clearToken = useCallback(() => {
localStorage.removeItem(TOKEN_KEY);
setTokenLocal(undefined);
setToken(undefined);
updateRight();
}, [updateRight, setToken]);
useEffect(() => {
setErrorApiGlobalCallback((response: Response) => {
if (response.status == 401) {
toaster.create({
title: 'API request rejected',
description: `API Authentication rejected by server.`,
type: 'error',
});
clearToken();
}
});
}, [clearToken]);
const hasReadRight = useCallback(
(part: RightPart) => {
//console.log(`config = ${JSON.stringify(config, null, 2)}`);
const right = config?.rights[environment.applName];
//console.log(`right = ${JSON.stringify(token?.payload?.right?.[environment.applName], null, 2)}`);
const right = token?.payload?.right?.[environment.applName];
if (right === undefined) {
return false;
}
return [PartRight.READ, PartRight.READ_WRITE].includes(right[part]);
},
[config]
[token]
);
const hasWriteRight = useCallback(
(part: RightPart) => {
const right = config?.rights[environment.applName];
const right = token?.payload?.right?.[environment.applName];
if (right === undefined) {
return false;
}
return [PartRight.READ, PartRight.READ_WRITE].includes(right[part]);
},
[config]
[token]
);
const getRestConfig = useCallback((): RESTConfig => {
return {
server: getApiUrl(),
token: token ?? '',
};
}, [token]);
useEffect(() => {
updateRight();
}, [updateRight]);
return {
token,
token: tokenStr,
isConnected: token !== undefined,
errorSession,
setToken: setTokenLocal,
clearToken,
login: config?.login,
login: token?.payload?.login,
hasReadRight,
hasWriteRight,
state,
getRestConfig,
getRestConfig: getRestConfigLocal,
};
};
export const useHasRight = (part: RightPart) => {
const { token, hasReadRight, hasWriteRight } = useSessionService();
const isReadable = useMemo(() => {
console.log(`get is read for: ${part} ==> ${hasReadRight(part)}`);
//console.log(`get is read for: ${part} ==> ${hasReadRight(part)}`);
return hasReadRight(part);
}, [token, hasReadRight, part]);
const isWritable = useMemo(() => {

View File

@ -1,10 +0,0 @@
import { RESTConfig } from '@/back-api';
import { getApiUrl } from '@/environment';
import { getUserToken } from '@/service/session';
export function getRestConfig(): RESTConfig {
return {
server: getApiUrl(),
token: getUserToken() ?? '',
};
}

View File

@ -1,3 +1,4 @@
import { JwtToken, ZodJwtToken } from '@/back-api';
import { environment } from '@/environment';
import { getApplicationLocation } from '@/utils/applPath';
@ -72,19 +73,28 @@ export function unHashLocalData(data: string): string | undefined {
return undefined;
}
export function parseToken(token: string): object {
const cut = token.split('.');
const decoded = b64_to_utf8(cut[1]);
const jsonModel = JSON.parse(decoded);
if (jsonModel.right === undefined) {
return {};
const convertBase64asJson = (data: string) => {
try {
const jsonBuffer = b64_to_utf8(data);
return JSON.parse(jsonBuffer);
} catch (e) {
console.error(`FAil to convert base64 data: ${e}`);
}
if (jsonModel.right[environment.applName] === undefined) {
return {};
}
return jsonModel.right[environment.applName];
}
};
export function parseToken(token: string): JwtToken {
const parts = token.split('.');
if (parts.length !== 3) {
console.error(`Wrong token model ${token}`);
throw new Error('Token JWT invalid');
}
const result = {
header: convertBase64asJson(parts[0]),
payload: convertBase64asJson(parts[1]),
signature: parts[2],
};
return ZodJwtToken.parse(result);
}
/**
* Request Open SSO Global website
*/
@ -98,14 +108,14 @@ export function requestSignIn(name?: string): void {
console.log(
`Request sign-in: '${environment.ssoSignIn}' + '${hashLocalData(name)}'`
);
window.location.href = environment.ssoSignIn + hashLocalData(name) + "/";
window.location.href = environment.ssoSignIn + hashLocalData(name) + '/';
}
/**
* Request SSO Disconnect
*/
export function requestSignOut(name?: string): void {
const url = environment.ssoSignOut + hashLocalData(name);
console.log(`Request just to the SSO: ${url}`)
console.log(`Request just to the SSO: ${url}`);
// unlog from the SSO
window.location.href = url;
}
@ -114,7 +124,7 @@ export function requestSignOut(name?: string): void {
*/
export function requestSignUp(name?: string): void {
const url = environment.ssoSignUp + hashLocalData(name);
console.log(`Request just to the SSO: ${url}`)
console.log(`Request just to the SSO: ${url}`);
// unlog from the SSO
window.location.href = url;
}