diff --git a/.gitignore b/.gitignore index 61dcb78..a32ab0c 100644 --- a/.gitignore +++ b/.gitignore @@ -59,4 +59,5 @@ tests/playwright-report/ # Dummy /dump +jwt-auth-inject.json platform/app/dist.zip diff --git a/extensions/cornerstone/src/Viewport/Overlays/studyDataForOverlayItem.ts b/extensions/cornerstone/src/Viewport/Overlays/studyDataForOverlayItem.ts index 71f3b87..e6c90f6 100644 --- a/extensions/cornerstone/src/Viewport/Overlays/studyDataForOverlayItem.ts +++ b/extensions/cornerstone/src/Viewport/Overlays/studyDataForOverlayItem.ts @@ -102,8 +102,20 @@ export const studyDataForOverlayItem = (studyInstanceUID: string) => { try { const qidoRootUrl = getQidoRootUrl(); + // Get the authentication token from session storage + const authToken = window.sessionStorage.getItem('ohif-auth-token'); + + // Create request headers with Authorization if token exists + const headers: HeadersInit = {}; + if (authToken) { + headers['Authorization'] = `Bearer ${authToken}`; + } + const response = await fetch( - `${qidoRootUrl}/studies?limit=101&offset=0&fuzzymatching=false&includefield=00080050,00081030,00101010,0010004&StudyInstanceUID=${studyInstanceUID}` + `${qidoRootUrl}/studies?limit=101&offset=0&fuzzymatching=false&includefield=00080050,00081030,00101010,0010004&StudyInstanceUID=${studyInstanceUID}`, + { + headers, + } ); if (!response.ok) { diff --git a/extensions/cornerstone/src/initWADOImageLoader.js b/extensions/cornerstone/src/initWADOImageLoader.js index 9f9922f..8b12a77 100644 --- a/extensions/cornerstone/src/initWADOImageLoader.js +++ b/extensions/cornerstone/src/initWADOImageLoader.js @@ -39,6 +39,15 @@ export default function initWADOImageLoader( Accept: acceptHeader, }; + // // Patch Mario: + const authToken = sessionStorage.getItem('ohif-auth-token'); + if (!authToken) { + window.location.href = '/login'; + return; + } + + xhrRequestHeaders.Authorization = `Bearer ${authToken}`; + if (headers) { Object.assign(xhrRequestHeaders, headers); } diff --git a/extensions/cornerstone/src/utils/CornerstoneViewportDownloadForm.tsx b/extensions/cornerstone/src/utils/CornerstoneViewportDownloadForm.tsx index 5e72d37..514855c 100644 --- a/extensions/cornerstone/src/utils/CornerstoneViewportDownloadForm.tsx +++ b/extensions/cornerstone/src/utils/CornerstoneViewportDownloadForm.tsx @@ -26,6 +26,15 @@ const CornerstoneViewportDownloadForm = ({ const activeViewportElement = enabledElement?.element; const activeViewportEnabledElement = getEnabledElement(activeViewportElement); + // console.log('cornerstoneViewportService', cornerstoneViewportService); + const viewportInfo = cornerstoneViewportService.getViewportInfo("default"); + // console.log('viewportInfo', viewportInfo); + + // Retrieve StudyInstanceUID from viewportInfo + const StudyInstanceUID = viewportInfo.getViewportData().data[0].StudyInstanceUID; + const SetInstanceUID = viewportInfo.getViewportData().data[0].displaySetInstanceUID; + // console.log('StudyInstanceUID', StudyInstanceUID); + // console.log('SetInstanceUID', SetInstanceUID); const { viewportId: activeViewportId, renderingEngineId, @@ -108,6 +117,10 @@ const CornerstoneViewportDownloadForm = ({ const downloadCanvas = getOrCreateCanvas(element); + // Log the canvas content before conversion + const context = downloadCanvas.getContext('2d'); + const imageData = context.getImageData(0, 0, downloadCanvas.width, downloadCanvas.height); + const type = 'image/' + fileType; const dataUrl = downloadCanvas.toDataURL(type, 1); @@ -214,6 +227,43 @@ const CornerstoneViewportDownloadForm = ({ }); }; + // New function to send annotation data + const sendAnnotationData = async (base64Image, activeViewportElement) => { + try { + // Get the SOPInstanceUID from the active viewport + const activeViewportEnabledElement = getEnabledElement(activeViewportElement); + const imageId = activeViewportEnabledElement?.viewport?.getCurrentImageId(); + + if (!base64Image || !StudyInstanceUID) { + throw new Error('Missing required data'); + } + + const payload = { + image: base64Image, + StudyInstanceUID: StudyInstanceUID + }; + + const response = await fetch('http://host:port/one-api/tools/annotation/store', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload) + }); + + if (!response.ok) { + throw new Error('Network response was not ok'); + } + + const data = await response.json(); + console.log('Annotation data sent successfully:', data); + return data; + } catch (error) { + console.error('Error sending annotation data:', error); + throw error; + } + }; + const downloadBlob = (filename, fileType) => { const file = `${filename}.${fileType}`; const divForDownloadViewport = document.querySelector( @@ -221,9 +271,16 @@ const CornerstoneViewportDownloadForm = ({ ); html2canvas(divForDownloadViewport).then(canvas => { + const dataUrl = canvas.toDataURL(fileType, 1.0); + const base64Image = dataUrl.split(',')[1]; // Remove prefix 'data:image/png;base64,' + + // Send annotation data + sendAnnotationData(base64Image, activeViewportElement) + .catch(error => console.error('Annotation data sending failed:', error)); + const link = document.createElement('a'); link.download = file; - link.href = canvas.toDataURL(fileType, 1.0); + link.href = dataUrl; link.click(); }); }; diff --git a/extensions/default/src/DicomWebDataSource/index.js b/extensions/default/src/DicomWebDataSource/index.js index 8f5f9ae..de3d528 100644 --- a/extensions/default/src/DicomWebDataSource/index.js +++ b/extensions/default/src/DicomWebDataSource/index.js @@ -85,6 +85,14 @@ function createDicomWebApi(dicomWebConfig, servicesManager) { if (authHeaders && authHeaders.Authorization) { xhrRequestHeaders.Authorization = authHeaders.Authorization; } + + const authToken = sessionStorage.getItem('ohif-auth-token'); + if (!authToken) { + window.location.href = '/login'; + return; + } + xhrRequestHeaders.Authorization = `Bearer ${authToken}`; + return xhrRequestHeaders; }; diff --git a/extensions/dicom-pdf/src/viewports/OHIFCornerstonePdfViewport.tsx b/extensions/dicom-pdf/src/viewports/OHIFCornerstonePdfViewport.tsx index a7d9df9..5e4651e 100644 --- a/extensions/dicom-pdf/src/viewports/OHIFCornerstonePdfViewport.tsx +++ b/extensions/dicom-pdf/src/viewports/OHIFCornerstonePdfViewport.tsx @@ -6,7 +6,8 @@ function OHIFCornerstonePdfViewport({ displaySets }) { var [url, setUrl] = useState(null); const sopInstanceUid = displaySets[0].SOPInstanceUID; - url = `http://128.199.154.150:8080/rid/IHERetrieveDocument?requestType=DOCUMENT&documentUID=${sopInstanceUid}&preferredContentType=application%2Fpdf`; + url = `http://${window.config.pacs_document_host}:${window.config.pacs_document_port}/rid/IHERetrieveDocument?requestType=DOCUMENT&documentUID=${sopInstanceUid}&preferredContentType=application%2Fpdf`; + console.log("URL PDF", url); useEffect(() => { document.body.addEventListener('drag', makePdfDropTarget); diff --git a/platform/app/.webpack/webpack.pwa.js b/platform/app/.webpack/webpack.pwa.js index cd64875..78c05e0 100644 --- a/platform/app/.webpack/webpack.pwa.js +++ b/platform/app/.webpack/webpack.pwa.js @@ -25,7 +25,7 @@ const PROXY_DOMAIN = process.env.PROXY_DOMAIN; const PROXY_PATH_REWRITE_FROM = process.env.PROXY_PATH_REWRITE_FROM; const PROXY_PATH_REWRITE_TO = process.env.PROXY_PATH_REWRITE_TO; -const OHIF_PORT = Number(process.env.OHIF_PORT || 3000); +const OHIF_PORT = Number(process.env.OHIF_PORT || 3030); const ENTRY_TARGET = process.env.ENTRY_TARGET || `${SRC_DIR}/index.js`; const Dotenv = require('dotenv-webpack'); const writePluginImportFile = require('./writePluginImportsFile.js'); diff --git a/platform/app/public/config/default.js b/platform/app/public/config/default.js index c145905..f85e728 100644 --- a/platform/app/public/config/default.js +++ b/platform/app/public/config/default.js @@ -1,6 +1,10 @@ /** @type {AppTypes.Config} */ - +function sas_get_token() { + //implement token here + return ''; +} window.config = { + sasGetToken: sas_get_token, routerBasename: '/', // whiteLabeling: {}, extensions: [], @@ -25,7 +29,7 @@ window.config = { }, expertise: false, //* Tambahan untuk enable expertise (CustomizableViewportOverlay) expertise_host: `https://devone.aplikasi.web.id/one-api/mockup/pacsmwl/Workorder/get_dummy_expertise`, //* Tambahan untuk fetch data Expertise) - pacs_document_host: `152.42.173.210`, + pacs_document_host: `${window.location.hostname}`, pacs_document_port: 8080, // filterQueryParam: false, // defaultDataSourceName: 'dicomweb', diff --git a/platform/app/public/config/google.js b/platform/app/public/config/google.js index cd3c6b2..5fa956c 100644 --- a/platform/app/public/config/google.js +++ b/platform/app/public/config/google.js @@ -1,7 +1,7 @@ /** @type {AppTypes.Config} */ window.config = { routerBasename: '/', - pacs_document_host: '152.42.173.210', + pacs_document_host: `${window.location.hostname}`, pacs_document_port: 8080, expertise: false, enableGoogleCloudAdapter: false, @@ -41,16 +41,16 @@ window.config = { configuration: { friendlyName: 'dcmjs DICOMWeb Server', name: 'GCP', - wadoUriRoot: `http://152.42.173.210:5555/dicomWeb`, - qidoRoot: `http://152.42.173.210:5555/dicomWeb`, - wadoRoot: `http://152.42.173.210:5555/dicomWeb`, + wadoUriRoot: `http://${window.location.hostname}:5555/dicomWeb`, + qidoRoot: `http://${window.location.hostname}:5555/dicomWeb`, + wadoRoot: `http://${window.location.hostname}:5555/dicomWeb`, qidoSupportsIncludeField: true, imageRendering: 'wadors', thumbnailRendering: 'wadors', enableStudyLazyLoad: true, - supportsFuzzyMatching: true, + supportsFuzzyMatching: false, supportsWildcard: true, - dicomUploadEnabled: true, + dicomUploadEnabled: false, omitQuotationForMultipartRequest: true, configurationAPI: 'ohif.dataSourceConfigurationAPI.google', // defaultDicomStoreConfiguredItems: { diff --git a/platform/app/src/App.tsx b/platform/app/src/App.tsx index 253080b..1436ded 100644 --- a/platform/app/src/App.tsx +++ b/platform/app/src/App.tsx @@ -36,6 +36,49 @@ 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; + + // 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 + return originalXHROpen.apply(this, arguments); + }; + + XMLHttpRequest.prototype.send = function (body) { + this.setRequestHeader('Authorization', `Bearer ${authToken}`); + + this.addEventListener('readystatechange', function () { + if (this.readyState === 4) { + // readyState 4 = DONE + try { + // 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, @@ -108,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 ( +