diff --git a/package-lock.json b/package-lock.json index 1ce7424f5..a3f57f265 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "open-webui", "version": "0.5.17", "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", @@ -134,6 +135,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.24.1", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.1.tgz", diff --git a/package.json b/package.json index a5db14e19..ed52f5fb5 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 d26469529..e14da6eae 100644 --- a/src/lib/utils/onedrive-file-picker.ts +++ b/src/lib/utils/onedrive-file-picker.ts @@ -1,7 +1,11 @@ -let CLIENT_ID = ''; +import { PublicClientApplication } from '@azure/msal-browser'; +import type { PopupRequest } from '@azure/msal-browser'; + +let CLIENT_ID = '521ada3e-6154-4a35-b9d3-51faac8ac944'; async function getCredentials() { if (CLIENT_ID) return; + const response = await fetch('/api/config'); if (!response.ok) { throw new Error('Failed to fetch OneDrive credentials'); @@ -13,63 +17,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; } @@ -95,6 +105,7 @@ const params = { } }; + // Download file from OneDrive async function downloadOneDriveFile(fileInfo: any): Promise { const accessToken = await getToken(); @@ -119,6 +130,8 @@ async function downloadOneDriveFile(fileInfo: any): Promise { return await downloadResponse.blob(); } +// OneDrive 피커 결과 인터페이스 정의 + // Open OneDrive file picker and return selected file metadata export async function openOneDrivePicker(): Promise { if (typeof window === 'undefined') { @@ -162,7 +175,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' }, @@ -187,7 +199,6 @@ export async function openOneDrivePicker(): Promise { break; } default: { - console.warn('Unsupported command:', command); channelPort?.postMessage({ result: 'error', error: { code: 'unsupportedCommand', message: command.command }, @@ -218,14 +229,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'); @@ -234,11 +248,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); } }; @@ -249,18 +267,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 };