diff --git a/package-lock.json b/package-lock.json index 4b69377b2..719e8718d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "open-webui", "version": "0.5.19", "dependencies": { + "@azure/msal-browser": "^4.5.0", "@codemirror/lang-javascript": "^6.2.2", "@codemirror/lang-python": "^6.1.6", "@codemirror/language-data": "^6.5.1", @@ -135,6 +136,27 @@ "node": ">=6.0.0" } }, + "node_modules/@azure/msal-browser": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-4.5.0.tgz", + "integrity": "sha512-H7mWmu8yI0n0XxhJobrgncXI6IU5h8DKMiWDHL5y+Dc58cdg26GbmaMUehbUkdKAQV2OTiFa4FUa6Fdu/wIxBg==", + "license": "MIT", + "dependencies": { + "@azure/msal-common": "15.2.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-common": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-15.2.0.tgz", + "integrity": "sha512-HiYfGAKthisUYqHG1nImCf/uzcyS31wng3o+CycWLIM9chnYJ9Lk6jZ30Y6YiYYpTQ9+z/FGUpiKKekd3Arc0A==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/@babel/runtime": { "version": "7.26.9", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.9.tgz", diff --git a/package.json b/package.json index 26700bdc5..63d7a49c9 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ }, "type": "module", "dependencies": { + "@azure/msal-browser": "^4.5.0", "@codemirror/lang-javascript": "^6.2.2", "@codemirror/lang-python": "^6.1.6", "@codemirror/language-data": "^6.5.1", diff --git a/src/lib/utils/onedrive-file-picker.ts b/src/lib/utils/onedrive-file-picker.ts index 7abf52204..37b14045a 100644 --- a/src/lib/utils/onedrive-file-picker.ts +++ b/src/lib/utils/onedrive-file-picker.ts @@ -1,9 +1,12 @@ +import { PublicClientApplication } from '@azure/msal-browser'; +import type { PopupRequest } from '@azure/msal-browser'; import { v4 as uuidv4 } from 'uuid'; let CLIENT_ID = ''; async function getCredentials() { if (CLIENT_ID) return; + const response = await fetch('/api/config'); if (!response.ok) { throw new Error('Failed to fetch OneDrive credentials'); @@ -15,63 +18,69 @@ async function getCredentials() { } } -function loadMsalScript(): Promise { - return new Promise((resolve, reject) => { - const win = window; - if (win.msal) { - resolve(); - return; - } - const script = document.createElement('script'); - script.src = 'https://alcdn.msauth.net/browser/2.19.0/js/msal-browser.min.js'; - script.async = true; - script.onload = () => resolve(); - script.onerror = () => reject(new Error('Failed to load MSAL script')); - document.head.appendChild(script); - }); -} -let msalInstance: any; +let msalInstance: PublicClientApplication | null = null; // Initialize MSAL authentication async function initializeMsal() { - if (!CLIENT_ID) { - await getCredentials(); - } - const msalParams = { - auth: { - authority: 'https://login.microsoftonline.com/consumers', - clientId: CLIENT_ID - } - }; try { - await loadMsalScript(); - const win = window; - msalInstance = new win.msal.PublicClientApplication(msalParams); - if (msalInstance.initialize) { - await msalInstance.initialize(); + if (!CLIENT_ID) { + await getCredentials(); } + + const msalParams = { + auth: { + authority: 'https://login.microsoftonline.com/consumers', + clientId: CLIENT_ID + } + }; + + if (!msalInstance) { + msalInstance = new PublicClientApplication(msalParams); + if (msalInstance.initialize) { + await msalInstance.initialize(); + } + } + + return msalInstance; } catch (error) { - console.error('MSAL initialization error:', error); + throw new Error('MSAL initialization failed: ' + (error instanceof Error ? error.message : String(error))); } } // Retrieve OneDrive access token async function getToken(): Promise { - const authParams = { scopes: ['OneDrive.ReadWrite'] }; + const authParams: PopupRequest = { scopes: ['OneDrive.ReadWrite'] }; let accessToken = ''; try { - await initializeMsal(); + msalInstance = await initializeMsal(); + if (!msalInstance) { + throw new Error('MSAL not initialized'); + } + const resp = await msalInstance.acquireTokenSilent(authParams); accessToken = resp.accessToken; } catch (err) { - const resp = await msalInstance.loginPopup(authParams); - msalInstance.setActiveAccount(resp.account); - if (resp.idToken) { - const resp2 = await msalInstance.acquireTokenSilent(authParams); - accessToken = resp2.accessToken; + if (!msalInstance) { + throw new Error('MSAL not initialized'); + } + + try { + const resp = await msalInstance.loginPopup(authParams); + msalInstance.setActiveAccount(resp.account); + if (resp.idToken) { + const resp2 = await msalInstance.acquireTokenSilent(authParams); + accessToken = resp2.accessToken; + } + } catch (popupError) { + throw new Error('Failed to login: ' + (popupError instanceof Error ? popupError.message : String(popupError))); } } + + if (!accessToken) { + throw new Error('Failed to acquire access token'); + } + return accessToken; } @@ -97,6 +106,7 @@ const params = { } }; + // Download file from OneDrive async function downloadOneDriveFile(fileInfo: any): Promise { const accessToken = await getToken(); @@ -164,7 +174,6 @@ export async function openOneDrivePicker(): Promise { throw new Error('Could not retrieve auth token'); } } catch (err) { - console.error(err); channelPort?.postMessage({ result: 'error', error: { code: 'tokenError', message: 'Failed to get token' }, @@ -189,7 +198,6 @@ export async function openOneDrivePicker(): Promise { break; } default: { - console.warn('Unsupported command:', command); channelPort?.postMessage({ result: 'error', error: { code: 'unsupportedCommand', message: command.command }, @@ -220,14 +228,17 @@ export async function openOneDrivePicker(): Promise { if (!authToken) { return reject(new Error('Failed to acquire access token')); } + pickerWindow = window.open('', 'OneDrivePicker', 'width=800,height=600'); if (!pickerWindow) { return reject(new Error('Failed to open OneDrive picker window')); } + const queryString = new URLSearchParams({ filePicker: JSON.stringify(params) }); const url = `${baseUrl}?${queryString.toString()}`; + const form = pickerWindow.document.createElement('form'); form.setAttribute('action', url); form.setAttribute('method', 'POST'); @@ -236,11 +247,15 @@ export async function openOneDrivePicker(): Promise { input.setAttribute('name', 'access_token'); input.setAttribute('value', authToken); form.appendChild(input); + pickerWindow.document.body.appendChild(form); form.submit(); + window.addEventListener('message', handleWindowMessage); } catch (err) { - if (pickerWindow) pickerWindow.close(); + if (pickerWindow) { + pickerWindow.close(); + } reject(err); } }; @@ -251,18 +266,16 @@ export async function openOneDrivePicker(): Promise { // Pick and download file from OneDrive export async function pickAndDownloadFile(): Promise<{ blob: Blob; name: string } | null> { - try { - const pickerResult = await openOneDrivePicker(); - if (!pickerResult || !pickerResult.items || pickerResult.items.length === 0) { - return null; - } - const selectedFile = pickerResult.items[0]; - const blob = await downloadOneDriveFile(selectedFile); - return { blob, name: selectedFile.name }; - } catch (error) { - console.error('Error occurred during OneDrive file pick/download:', error); - throw error; + const pickerResult = await openOneDrivePicker(); + + if (!pickerResult || !pickerResult.items || pickerResult.items.length === 0) { + return null; } + + const selectedFile = pickerResult.items[0]; + const blob = await downloadOneDriveFile(selectedFile); + + return { blob, name: selectedFile.name }; } export { downloadOneDriveFile };