diff --git a/extensions/default/src/ViewerLayout/index.tsx b/extensions/default/src/ViewerLayout/index.tsx index e9e7df98b..4326ece4f 100644 --- a/extensions/default/src/ViewerLayout/index.tsx +++ b/extensions/default/src/ViewerLayout/index.tsx @@ -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 * or horizontal overflow (no scrolling). Also guarantee window diff --git a/platform/i18n/src/locales/en-US/Header.json b/platform/i18n/src/locales/en-US/Header.json index 1347dae45..e3210b37b 100644 --- a/platform/i18n/src/locales/en-US/Header.json +++ b/platform/i18n/src/locales/en-US/Header.json @@ -4,5 +4,6 @@ "INVESTIGATIONAL USE ONLY": "INVESTIGATIONAL USE ONLY", "Options": "Options", "Preferences": "Preferences", - "Study list": "Study list" -} \ No newline at end of file + "Study list": "Study list", + "Logout": "Logout" +} diff --git a/platform/ui/src/assets/icons/power-off.svg b/platform/ui/src/assets/icons/power-off.svg new file mode 100644 index 000000000..3dd5dcf20 --- /dev/null +++ b/platform/ui/src/assets/icons/power-off.svg @@ -0,0 +1,11 @@ + + Power Off + + diff --git a/platform/ui/src/components/Icon/getIcon.js b/platform/ui/src/components/Icon/getIcon.js index c9af36fb6..08760e47e 100644 --- a/platform/ui/src/components/Icon/getIcon.js +++ b/platform/ui/src/components/Icon/getIcon.js @@ -28,6 +28,7 @@ import logoOhifSmall from './../../assets/icons/logo-ohif-small.svg'; import magnifier from './../../assets/icons/magnifier.svg'; import notificationwarningDiamond from './../../assets/icons/notificationwarning-diamond.svg'; import pencil from './../../assets/icons/pencil.svg'; +import powerOff from './../../assets/icons/power-off.svg'; import profile from './../../assets/icons/profile.svg'; import pushLeft from './../../assets/icons/push-left.svg'; import pushRight from './../../assets/icons/push-right.svg'; @@ -97,6 +98,7 @@ const ICONS = { exclamation: exclamation, 'notificationwarning-diamond': notificationwarningDiamond, pencil: pencil, + 'power-off': powerOff, profile: profile, 'push-left': pushLeft, 'push-right': pushRight, diff --git a/platform/viewer/src/App.tsx b/platform/viewer/src/App.tsx index dd754780d..758984d19 100644 --- a/platform/viewer/src/App.tsx +++ b/platform/viewer/src/App.tsx @@ -28,7 +28,6 @@ let commandsManager, extensionManager, servicesManager, hotkeysManager; function App({ config, defaultExtensions, defaultModes }) { const [init, setInit] = useState(null); - useEffect(() => { const run = async () => { appInit(config, defaultExtensions, defaultModes) diff --git a/platform/viewer/src/routes/WorkList/WorkList.tsx b/platform/viewer/src/routes/WorkList/WorkList.tsx index 14815050e..59e82ad17 100644 --- a/platform/viewer/src/routes/WorkList/WorkList.tsx +++ b/platform/viewer/src/routes/WorkList/WorkList.tsx @@ -322,13 +322,13 @@ function WorkList({ seriesTableDataSource={ seriesInStudiesMap.has(studyInstanceUid) ? seriesInStudiesMap.get(studyInstanceUid).map(s => { - return { - description: s.description || '(empty)', - seriesNumber: s.seriesNumber || '', - modality: s.modality || '', - instances: s.numSeriesInstances || '', - }; - }) + return { + description: s.description || '(empty)', + seriesNumber: s.seriesNumber || '', + modality: s.modality || '', + instances: s.numSeriesInstances || '', + }; + }) : [] } > @@ -346,7 +346,7 @@ function WorkList({ @@ -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 (
{ const openIdConnectConfiguration = Object.assign({}, firstOpenIdClient, { redirect_uri: _makeAbsoluteIfNecessary(redirect_uri, baseUri), - silent_redirect_uri: _makeAbsoluteIfNecessary( - silent_redirect_uri, - baseUri - ), + silent_redirect_uri: _makeAbsoluteIfNecessary(silent_redirect_uri, baseUri), post_logout_redirect_uri: _makeAbsoluteIfNecessary( post_logout_redirect_uri, baseUri @@ -52,6 +49,17 @@ const initUserManager = (oidc, routerBasename) => { }); 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) { @@ -60,9 +68,7 @@ function LoginComponent(userManager) { const loginHint = queryParams.get('login_hint'); const targetLinkUri = queryParams.get('target_link_uri'); if (iss !== oidcAuthority) { - console.error( - 'iss of /login does not match the oidc authority' - ); + console.error('iss of /login does not match the oidc authority'); return null; } @@ -96,22 +102,22 @@ function LoginComponent(userManager) { } function OpenIdConnectRoutes({ - oidc, - routerBasename, - UserAuthenticationService - }) { + oidc, + routerBasename, + UserAuthenticationService, +}) { const userManager = initUserManager(oidc, routerBasename); const getAuthorizationHeader = () => { const user = UserAuthenticationService.getUser(); return { - Authorization: `Bearer ${user.access_token}` + Authorization: `Bearer ${user.access_token}`, }; - } + }; const handleUnauthenticated = () => { - userManager.signinRedirect() + userManager.signinRedirect(); // return null because this is used in a react component return null; @@ -119,23 +125,45 @@ function OpenIdConnectRoutes({ 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(() => { UserAuthenticationService.set({ enabled: true }); UserAuthenticationService.setServiceImplementation({ getAuthorizationHeader, - handleUnauthenticated + handleUnauthenticated, }); - }, []) + }, []); const oidcAuthority = oidc[0].authority; const location = useLocation(); const { pathname, search } = location; - 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 post_logout_redirect_uri = new URL(userManager.settings._post_logout_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 post_logout_redirect_uri = new URL( + userManager.settings._post_logout_redirect_uri + ).pathname; //.replace(routerBasename,''); // const pathnameRelative = pathname.replace(routerBasename,''); @@ -148,10 +176,7 @@ function OpenIdConnectRoutes({ return ( - + { - const { pathname, search = '' } = JSON.parse( - sessionStorage.getItem('ohif-redirect-to') - ); + element={ + { + const { pathname, search = '' } = JSON.parse( + sessionStorage.getItem('ohif-redirect-to') + ); - UserAuthenticationService.setUser(user); + UserAuthenticationService.setUser(user); - navigate({ - pathname, - search - }) - }}/>} + navigate({ + pathname, + search, + }); + }} + /> + } /> } + element={ + + } + /> + } /> );