From 7f4548e18c3ad2baf08d22e798a05242a4b4397d Mon Sep 17 00:00:00 2001 From: mario Date: Tue, 13 May 2025 08:51:38 +0700 Subject: [PATCH] add: Login page and route --- platform/app/src/App.tsx | 33 +++-- platform/app/src/routes/Login.tsx | 129 ++++++++++++++++++ platform/app/src/routes/index.tsx | 6 + .../utils/initUserAuthenticationService.js | 60 ++++++++ 4 files changed, 218 insertions(+), 10 deletions(-) create mode 100644 platform/app/src/routes/Login.tsx create mode 100644 platform/app/src/utils/initUserAuthenticationService.js diff --git a/platform/app/src/App.tsx b/platform/app/src/App.tsx index 980863d..1436ded 100644 --- a/platform/app/src/App.tsx +++ b/platform/app/src/App.tsx @@ -36,15 +36,17 @@ import createRoutes from './routes'; import appInit from './appInit.js'; import OpenIdConnectRoutes from './utils/OpenIdConnectRoutes'; import { ShepherdJourneyProvider } from 'react-shepherd'; +import { initializeCustomAuth } from './utils/initUserAuthenticationService'; function injectAuth() { console.log('---> Inject Auth'); const originalXHROpen = XMLHttpRequest.prototype.open; const originalXHRSend = XMLHttpRequest.prototype.send; - //take from local storage for the token - // let authToken = '--kris-auth-token-check--'; - let authToken = window.config.sasGetToken(); + // Kalau ingin disable study list (Role Patient) + // window.config.showStudyList = false; + + const authToken = sessionStorage.getItem('ohif-auth-token'); XMLHttpRequest.prototype.open = function (method, url, async, user, password) { this._url = url; // Save URL if you want conditional logic @@ -53,23 +55,31 @@ function injectAuth() { XMLHttpRequest.prototype.send = function (body) { this.setRequestHeader('Authorization', `Bearer ${authToken}`); + this.addEventListener('readystatechange', function () { if (this.readyState === 4) { - // DONE + // readyState 4 = DONE try { - //check responseType ie json, and then check the auth response status - //redirect to custom login page if needed - console.log('response type :', this.responseType); - console.log('response :', this.response); - console.log('responseText :', this.responseText); - } catch (e) {} + // Check for auth errors (401/403) and redirect to login if needed + if (this.status === 401 || this.status === 403) { + window.sessionStorage.removeItem('ohif-auth-token'); + window.location.href = '/login'; + } + } catch (e) { + console.error('Error handling auth response:', e); + } } }); return originalXHRSend.apply(this, arguments); }; } +// Setup token access function +window.config.sasGetToken = () => window.sessionStorage.getItem('ohif-auth-token'); + +// Enable auth token injection // injectAuth(); + let commandsManager: CommandsManager, extensionManager: ExtensionManager, servicesManager: AppTypes.ServicesManager, @@ -141,6 +151,9 @@ function App({ customizationService, } = servicesManager.services; + // Initialize our custom authentication service + initializeCustomAuth(userAuthenticationService); + const providers = [ [AppConfigProvider, { value: appConfigState }], [UserAuthenticationProvider, { service: userAuthenticationService }], diff --git a/platform/app/src/routes/Login.tsx b/platform/app/src/routes/Login.tsx new file mode 100644 index 0000000..11ea2fc --- /dev/null +++ b/platform/app/src/routes/Login.tsx @@ -0,0 +1,129 @@ +import React, { useState, useEffect } from 'react'; +import { useNavigate, useLocation } from 'react-router-dom'; +import { useUserAuthentication } from '@ohif/ui'; + +const Login = () => { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [error, setError] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const navigate = useNavigate(); + const location = useLocation(); + const [, authContext] = useUserAuthentication(); + + // Get the intended destination from URL query params or default to home + const searchParams = new URLSearchParams(location.search); + const redirectPath = searchParams.get('redirect') || '/'; + + // Check if already authenticated + useEffect(() => { + const token = window.sessionStorage.getItem('ohif-auth-token'); + if (token) { + // Already logged in, redirect to destination + navigate(redirectPath, { replace: true }); + } + }, [redirectPath, navigate]); + + const handleLogin = async e => { + e.preventDefault(); + setError(''); + setIsLoading(true); + + try { + // Use window.config.goProxyHost for authentication endpoint + const proxyHost = window.config?.goProxyHost || `http://${window.location.hostname}:5555`; + const authEndpoint = `${proxyHost}/auth/login`; + + // Call go-ohif-proxy login endpoint + const response = await fetch(authEndpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ email, password }), + }); + + if (!response.ok) { + throw new Error('Login failed. Please check your credentials.'); + } + + const data = await response.json(); + + // Store token in sessionStorage + window.sessionStorage.setItem('ohif-auth-token', data.access_token); + + // Decode token to extract role and user information + let userInfo = data.user; + + // Update the auth context + authContext.setUser({ + ...userInfo, + token: data.access_token, + }); + + // Set window.config.sasGetToken for the injectAuth function + if (window.config) { + window.config.sasGetToken = () => window.sessionStorage.getItem('ohif-auth-token'); + } + + // Handle role-specific redirects if specified in response + if (data.redirect_url) { + navigate(data.redirect_url, { replace: true }); + } else { + // Redirect to the original destination + navigate(redirectPath, { replace: true }); + } + } catch (error) { + console.error('Login error:', error); + setError(error.message || 'Failed to log in. Please try again.'); + } finally { + setIsLoading(false); + } + }; + + return ( +
+
+

Login to OHIF Viewer

+ + {error &&
{error}
} + +
+
+ + setEmail(e.target.value)} + required + /> +
+ +
+ + setPassword(e.target.value)} + required + /> +
+ +
+ +
+
+
+
+ ); +}; + +export default Login; diff --git a/platform/app/src/routes/index.tsx b/platform/app/src/routes/index.tsx index 78ad5db..8c5ef8d 100644 --- a/platform/app/src/routes/index.tsx +++ b/platform/app/src/routes/index.tsx @@ -12,6 +12,7 @@ import buildModeRoutes from './buildModeRoutes'; import PrivateRoute from './PrivateRoute'; import PropTypes from 'prop-types'; import { Link } from 'react-router-dom'; +import Login from './Login'; const NotFoundServer = ({ message = 'Unable to query for studies at this time. Check your data source configuration or network connection', @@ -74,6 +75,11 @@ const bakedInRoutes = [ path: '/localbasic', children: Local.bind(null, { modePath: 'viewer/dicomlocal' }), }, + // * Custom Patch untuk Login go-ohif-proxy + { + path: '/login', + children: Login, + }, ]; // NOT FOUND (404) diff --git a/platform/app/src/utils/initUserAuthenticationService.js b/platform/app/src/utils/initUserAuthenticationService.js new file mode 100644 index 0000000..4b1d7fe --- /dev/null +++ b/platform/app/src/utils/initUserAuthenticationService.js @@ -0,0 +1,60 @@ +/** + * Initializes the custom authentication service for OHIF Viewer + * to work with go-ohif-proxy authentication + */ +export function initializeCustomAuth(userAuthenticationService) { + // Set up the authentication service with custom implementation + userAuthenticationService.setServiceImplementation({ + // Custom implementation to handle unauthenticated users + handleUnauthenticated: () => { + // Get the current path for redirect after login + const currentPath = window.location.pathname + window.location.search; + + // Clear any existing tokens + window.sessionStorage.removeItem('ohif-auth-token'); + + // Redirect to login page with the redirect URL in query params + window.location.href = `/login?redirect=${encodeURIComponent(currentPath)}`; + + // Return null to prevent rendering while redirecting + return null; + }, + + // Custom implementation to get the authorization header + // getAuthorizationHeader: () => { + // const token = window.sessionStorage.getItem('ohif-auth-token'); + // return token ? `Bearer ${token}` : undefined; + // }, + }); + + // Set authentication as enabled + userAuthenticationService.set({ enabled: true }); + + // Check if we already have a token and set the user if we do + const token = window.sessionStorage.getItem('ohif-auth-token'); + if (!token) { + return; + } + const decodedToken = decodeToken(token); + + // Check jika 'role' = 'patient' tapi akses '/' return ke viewer + if (decodedToken && decodedToken.role === 'patient') { + const currentPath = window.location.pathname + window.location.search; + if (currentPath === '/') { + console.log('User is a patient and trying to access the root path. Redirecting to /patient.'); + window.location.href = `${decodedToken.home_url}`; + } + } + + function decodeToken(token) { + try { + const payload = token.split('.')[1]; + if (payload) { + return JSON.parse(atob(payload)); + } + } catch (e) { + console.error('Error parsing JWT token', e); + } + return null; + } +}