feat: Add logout for oidc (#2840)
* add oidc logout route, button in menu * removed old comments * use local storage to trigger signout in other tabs * refactored to make use useEffect * cleaned up comments * fix remove event listener * moved remove leftover storage variable to beginning of usemount Co-authored-by: Michael Andersen <Michael.Andersen@rmp.uhn.ca>
This commit is contained in:
parent
e401358887
commit
1d47f31738
@ -148,6 +148,16 @@ function ViewerLayout({
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (appConfig.oidc) {
|
||||||
|
menuOptions.push({
|
||||||
|
title: t('Header:Logout'),
|
||||||
|
icon: 'power-off',
|
||||||
|
onClick: async () => {
|
||||||
|
navigate(`/logout?redirect_uri=${encodeURIComponent(window.location.href)}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set body classes (tailwindcss) that don't allow vertical
|
* Set body classes (tailwindcss) that don't allow vertical
|
||||||
* or horizontal overflow (no scrolling). Also guarantee window
|
* or horizontal overflow (no scrolling). Also guarantee window
|
||||||
|
|||||||
@ -4,5 +4,6 @@
|
|||||||
"INVESTIGATIONAL USE ONLY": "INVESTIGATIONAL USE ONLY",
|
"INVESTIGATIONAL USE ONLY": "INVESTIGATIONAL USE ONLY",
|
||||||
"Options": "Options",
|
"Options": "Options",
|
||||||
"Preferences": "Preferences",
|
"Preferences": "Preferences",
|
||||||
"Study list": "Study list"
|
"Study list": "Study list",
|
||||||
|
"Logout": "Logout"
|
||||||
}
|
}
|
||||||
11
platform/ui/src/assets/icons/power-off.svg
Normal file
11
platform/ui/src/assets/icons/power-off.svg
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 28"
|
||||||
|
aria-labelledby="title"
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<title id="title">Power Off</title>
|
||||||
|
<path d="M24 14c0 6.609-5.391 12-12 12s-12-5.391-12-12c0-3.797 1.75-7.297 4.797-9.578 0.891-0.672 2.141-0.5 2.797 0.391 0.672 0.875 0.484 2.141-0.391 2.797-2.031 1.531-3.203 3.859-3.203 6.391 0 4.406 3.594 8 8 8s8-3.594 8-8c0-2.531-1.172-4.859-3.203-6.391-0.875-0.656-1.062-1.922-0.391-2.797 0.656-0.891 1.922-1.062 2.797-0.391 3.047 2.281 4.797 5.781 4.797 9.578zM14 2v10c0 1.094-0.906 2-2 2s-2-0.906-2-2v-10c0-1.094 0.906-2 2-2s2 0.906 2 2z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 637 B |
@ -28,6 +28,7 @@ import logoOhifSmall from './../../assets/icons/logo-ohif-small.svg';
|
|||||||
import magnifier from './../../assets/icons/magnifier.svg';
|
import magnifier from './../../assets/icons/magnifier.svg';
|
||||||
import notificationwarningDiamond from './../../assets/icons/notificationwarning-diamond.svg';
|
import notificationwarningDiamond from './../../assets/icons/notificationwarning-diamond.svg';
|
||||||
import pencil from './../../assets/icons/pencil.svg';
|
import pencil from './../../assets/icons/pencil.svg';
|
||||||
|
import powerOff from './../../assets/icons/power-off.svg';
|
||||||
import profile from './../../assets/icons/profile.svg';
|
import profile from './../../assets/icons/profile.svg';
|
||||||
import pushLeft from './../../assets/icons/push-left.svg';
|
import pushLeft from './../../assets/icons/push-left.svg';
|
||||||
import pushRight from './../../assets/icons/push-right.svg';
|
import pushRight from './../../assets/icons/push-right.svg';
|
||||||
@ -97,6 +98,7 @@ const ICONS = {
|
|||||||
exclamation: exclamation,
|
exclamation: exclamation,
|
||||||
'notificationwarning-diamond': notificationwarningDiamond,
|
'notificationwarning-diamond': notificationwarningDiamond,
|
||||||
pencil: pencil,
|
pencil: pencil,
|
||||||
|
'power-off': powerOff,
|
||||||
profile: profile,
|
profile: profile,
|
||||||
'push-left': pushLeft,
|
'push-left': pushLeft,
|
||||||
'push-right': pushRight,
|
'push-right': pushRight,
|
||||||
|
|||||||
@ -28,7 +28,6 @@ let commandsManager, extensionManager, servicesManager, hotkeysManager;
|
|||||||
|
|
||||||
function App({ config, defaultExtensions, defaultModes }) {
|
function App({ config, defaultExtensions, defaultModes }) {
|
||||||
const [init, setInit] = useState(null);
|
const [init, setInit] = useState(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const run = async () => {
|
const run = async () => {
|
||||||
appInit(config, defaultExtensions, defaultModes)
|
appInit(config, defaultExtensions, defaultModes)
|
||||||
|
|||||||
@ -322,13 +322,13 @@ function WorkList({
|
|||||||
seriesTableDataSource={
|
seriesTableDataSource={
|
||||||
seriesInStudiesMap.has(studyInstanceUid)
|
seriesInStudiesMap.has(studyInstanceUid)
|
||||||
? seriesInStudiesMap.get(studyInstanceUid).map(s => {
|
? seriesInStudiesMap.get(studyInstanceUid).map(s => {
|
||||||
return {
|
return {
|
||||||
description: s.description || '(empty)',
|
description: s.description || '(empty)',
|
||||||
seriesNumber: s.seriesNumber || '',
|
seriesNumber: s.seriesNumber || '',
|
||||||
modality: s.modality || '',
|
modality: s.modality || '',
|
||||||
instances: s.numSeriesInstances || '',
|
instances: s.numSeriesInstances || '',
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
: []
|
: []
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -346,7 +346,7 @@ function WorkList({
|
|||||||
<Link
|
<Link
|
||||||
key={i}
|
key={i}
|
||||||
to={`${mode.routeName}?StudyInstanceUIDs=${studyInstanceUid}`}
|
to={`${mode.routeName}?StudyInstanceUIDs=${studyInstanceUid}`}
|
||||||
// to={`${mode.routeName}/dicomweb?StudyInstanceUIDs=${studyInstanceUid}`}
|
// to={`${mode.routeName}/dicomweb?StudyInstanceUIDs=${studyInstanceUid}`}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
rounded="full"
|
rounded="full"
|
||||||
@ -354,7 +354,7 @@ function WorkList({
|
|||||||
disabled={!isValidMode}
|
disabled={!isValidMode}
|
||||||
endIcon={<Icon name="launch-arrow" />} // launch-arrow | launch-info
|
endIcon={<Icon name="launch-arrow" />} // launch-arrow | launch-info
|
||||||
className={classnames('font-bold', { 'ml-2': !isFirst })}
|
className={classnames('font-bold', { 'ml-2': !isFirst })}
|
||||||
onClick={() => {}}
|
onClick={() => { }}
|
||||||
>
|
>
|
||||||
{t(`Modes:${mode.displayName}`)}
|
{t(`Modes:${mode.displayName}`)}
|
||||||
</Button>
|
</Button>
|
||||||
@ -414,6 +414,17 @@ function WorkList({
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (appConfig.oidc) {
|
||||||
|
menuOptions.push({
|
||||||
|
icon: 'power-off',
|
||||||
|
title: t('Header:Logout'),
|
||||||
|
onClick: () => {
|
||||||
|
navigate(`/logout?redirect_uri=${encodeURIComponent(window.location.href)}`);
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classnames('bg-black h-full', {
|
className={classnames('bg-black h-full', {
|
||||||
|
|||||||
@ -41,10 +41,7 @@ const initUserManager = (oidc, routerBasename) => {
|
|||||||
|
|
||||||
const openIdConnectConfiguration = Object.assign({}, firstOpenIdClient, {
|
const openIdConnectConfiguration = Object.assign({}, firstOpenIdClient, {
|
||||||
redirect_uri: _makeAbsoluteIfNecessary(redirect_uri, baseUri),
|
redirect_uri: _makeAbsoluteIfNecessary(redirect_uri, baseUri),
|
||||||
silent_redirect_uri: _makeAbsoluteIfNecessary(
|
silent_redirect_uri: _makeAbsoluteIfNecessary(silent_redirect_uri, baseUri),
|
||||||
silent_redirect_uri,
|
|
||||||
baseUri
|
|
||||||
),
|
|
||||||
post_logout_redirect_uri: _makeAbsoluteIfNecessary(
|
post_logout_redirect_uri: _makeAbsoluteIfNecessary(
|
||||||
post_logout_redirect_uri,
|
post_logout_redirect_uri,
|
||||||
baseUri
|
baseUri
|
||||||
@ -52,6 +49,17 @@ const initUserManager = (oidc, routerBasename) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return getUserManagerForOpenIdConnectClient(openIdConnectConfiguration);
|
return getUserManagerForOpenIdConnectClient(openIdConnectConfiguration);
|
||||||
|
};
|
||||||
|
|
||||||
|
function LogoutComponent(props) {
|
||||||
|
const { userManager } = props;
|
||||||
|
localStorage.setItem('signoutEvent', 'true');
|
||||||
|
const location = useLocation();
|
||||||
|
const query = new URLSearchParams(location.search);
|
||||||
|
userManager.signoutRedirect({
|
||||||
|
post_logout_redirect_uri: query.get('redirect_uri'),
|
||||||
|
});
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function LoginComponent(userManager) {
|
function LoginComponent(userManager) {
|
||||||
@ -60,9 +68,7 @@ function LoginComponent(userManager) {
|
|||||||
const loginHint = queryParams.get('login_hint');
|
const loginHint = queryParams.get('login_hint');
|
||||||
const targetLinkUri = queryParams.get('target_link_uri');
|
const targetLinkUri = queryParams.get('target_link_uri');
|
||||||
if (iss !== oidcAuthority) {
|
if (iss !== oidcAuthority) {
|
||||||
console.error(
|
console.error('iss of /login does not match the oidc authority');
|
||||||
'iss of /login does not match the oidc authority'
|
|
||||||
);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,22 +102,22 @@ function LoginComponent(userManager) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function OpenIdConnectRoutes({
|
function OpenIdConnectRoutes({
|
||||||
oidc,
|
oidc,
|
||||||
routerBasename,
|
routerBasename,
|
||||||
UserAuthenticationService
|
UserAuthenticationService,
|
||||||
}) {
|
}) {
|
||||||
const userManager = initUserManager(oidc, routerBasename);
|
const userManager = initUserManager(oidc, routerBasename);
|
||||||
|
|
||||||
const getAuthorizationHeader = () => {
|
const getAuthorizationHeader = () => {
|
||||||
const user = UserAuthenticationService.getUser();
|
const user = UserAuthenticationService.getUser();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
Authorization: `Bearer ${user.access_token}`
|
Authorization: `Bearer ${user.access_token}`,
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleUnauthenticated = () => {
|
const handleUnauthenticated = () => {
|
||||||
userManager.signinRedirect()
|
userManager.signinRedirect();
|
||||||
|
|
||||||
// return null because this is used in a react component
|
// return null because this is used in a react component
|
||||||
return null;
|
return null;
|
||||||
@ -119,23 +125,45 @@ function OpenIdConnectRoutes({
|
|||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
//for multi-tab logout
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.removeItem('signoutEvent');
|
||||||
|
const storageEventListener = event => {
|
||||||
|
const signOutEvent = localStorage.getItem('signoutEvent');
|
||||||
|
if (signOutEvent) {
|
||||||
|
navigate(
|
||||||
|
`/logout?redirect_uri=${encodeURIComponent(window.location.href)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('storage', storageEventListener);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('storage', storageEventListener);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
UserAuthenticationService.set({ enabled: true });
|
UserAuthenticationService.set({ enabled: true });
|
||||||
|
|
||||||
UserAuthenticationService.setServiceImplementation({
|
UserAuthenticationService.setServiceImplementation({
|
||||||
getAuthorizationHeader,
|
getAuthorizationHeader,
|
||||||
handleUnauthenticated
|
handleUnauthenticated,
|
||||||
});
|
});
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const oidcAuthority = oidc[0].authority;
|
const oidcAuthority = oidc[0].authority;
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { pathname, search } = location;
|
const { pathname, search } = location;
|
||||||
|
|
||||||
const redirect_uri = new URL(userManager.settings._redirect_uri).pathname//.replace(routerBasename,'')
|
const redirect_uri = new URL(userManager.settings._redirect_uri).pathname; //.replace(routerBasename,'')
|
||||||
const silent_refresh_uri = new URL(userManager.settings._silent_redirect_uri).pathname//.replace(routerBasename,'')
|
const silent_refresh_uri = new URL(userManager.settings._silent_redirect_uri)
|
||||||
const post_logout_redirect_uri = new URL(userManager.settings._post_logout_redirect_uri).pathname//.replace(routerBasename,'');
|
.pathname; //.replace(routerBasename,'')
|
||||||
|
const post_logout_redirect_uri = new URL(
|
||||||
|
userManager.settings._post_logout_redirect_uri
|
||||||
|
).pathname; //.replace(routerBasename,'');
|
||||||
|
|
||||||
// const pathnameRelative = pathname.replace(routerBasename,'');
|
// const pathnameRelative = pathname.replace(routerBasename,'');
|
||||||
|
|
||||||
@ -148,10 +176,7 @@ function OpenIdConnectRoutes({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Routes basename={routerBasename}>
|
<Routes basename={routerBasename}>
|
||||||
<Route
|
<Route path={silent_refresh_uri} onEnter={window.location.reload} />
|
||||||
path={silent_refresh_uri}
|
|
||||||
onEnter={window.location.reload}
|
|
||||||
/>
|
|
||||||
<Route
|
<Route
|
||||||
path={post_logout_redirect_uri}
|
path={post_logout_redirect_uri}
|
||||||
element={
|
element={
|
||||||
@ -167,22 +192,36 @@ function OpenIdConnectRoutes({
|
|||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={redirect_uri}
|
path={redirect_uri}
|
||||||
element={<CallbackPage userManager={userManager} onRedirectSuccess={(user) => {
|
element={
|
||||||
const { pathname, search = '' } = JSON.parse(
|
<CallbackPage
|
||||||
sessionStorage.getItem('ohif-redirect-to')
|
userManager={userManager}
|
||||||
);
|
onRedirectSuccess={user => {
|
||||||
|
const { pathname, search = '' } = JSON.parse(
|
||||||
|
sessionStorage.getItem('ohif-redirect-to')
|
||||||
|
);
|
||||||
|
|
||||||
UserAuthenticationService.setUser(user);
|
UserAuthenticationService.setUser(user);
|
||||||
|
|
||||||
navigate({
|
navigate({
|
||||||
pathname,
|
pathname,
|
||||||
search
|
search,
|
||||||
})
|
});
|
||||||
}}/>}
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/login"
|
path="/login"
|
||||||
element={<LoginComponent userManager={userManager} oidcAuthority={oidcAuthority}/>}
|
element={
|
||||||
|
<LoginComponent
|
||||||
|
userManager={userManager}
|
||||||
|
oidcAuthority={oidcAuthority}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/logout"
|
||||||
|
element={<LogoutComponent userManager={userManager} />}
|
||||||
/>
|
/>
|
||||||
</Routes>
|
</Routes>
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user