Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions backend/open_webui/routers/openai.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,15 @@ async def get_allowed_model_ids(
log.warning(
f"list_endpoints request error: {response.status} {list_endpoints_url}"
)
if response.status == 401:
raise HTTPException(
status_code=401,
detail="list_endpoints_unauthorized",
)
return []
data = json.loads(await response.text())
except HTTPException:
raise
except Exception as e:
log.warning(f"list_endpoints fetch error: {e}")
return []
Expand Down
1 change: 1 addition & 0 deletions backend/open_webui/utils/oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -1429,6 +1429,7 @@ async def handle_login(self, request, provider):
# [ADDITION BEGINS] Add extra parameters for Globus to include the policy
if provider == "globus" and GLOBUS_HIGH_ASSURANCE_POLICY.value:
kwargs["session_required_policies"] = GLOBUS_HIGH_ASSURANCE_POLICY.value
kwargs["prompt"] = "login"
# [ADDITION ENDS]

return await client.authorize_redirect(request, redirect_uri, **kwargs)
Expand Down
11 changes: 10 additions & 1 deletion src/lib/apis/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,16 @@ export const getModels = async (
}
)
.then(async (res) => {
if (!res.ok) throw await res.json();
// [MODIFICATION BEGINS]
// Original: if (!res.ok) throw await res.json();
// This adds res.status to the error object
// This allows the frontend to know when the user should be redirected
// to the logout URL (when the Globus token expires or loses the session)
if (!res.ok) {
const body = await res.json();
throw { ...body, status: res.status };
}
// [MODIFICATION ENDS]
return res.json();
})
.catch((err) => {
Expand Down
56 changes: 42 additions & 14 deletions src/lib/components/common/ConfirmDialog.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

export let cancelLabel = $i18n.t('Cancel');
export let confirmLabel = $i18n.t('Confirm');
// [ADDITION BEGINS] - Add cancel button
export let showCancel = true;
// [ADDITION ENDS]

export let onConfirm = () => {};

Expand All @@ -39,7 +42,10 @@
};

const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
// [MODIFICATION BEGINS] - Add cancel button
// Oritinal: if (event.key === 'Escape') {
if (event.key === 'Escape' && showCancel) {
// [MODIFICATION ENDS]
console.log('Escape');
show = false;
}
Expand Down Expand Up @@ -98,7 +104,10 @@
class=" fixed top-0 right-0 left-0 bottom-0 bg-black/60 w-full h-screen max-h-[100dvh] flex justify-center z-99999999 overflow-hidden overscroll-contain"
in:fade={{ duration: 10 }}
on:mousedown={() => {
show = false;
// [MODIFICATION BEGINS] - Add cancel button
// Oritinal: if (showCancel) show = false;
if (showCancel) show = false;
// [MODIFICATION ENDS]
}}
>
<div
Expand Down Expand Up @@ -138,18 +147,37 @@
</div>
</slot>

<div class="mt-6 flex justify-between gap-1.5">
<button
class="text-sm bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-white font-medium w-full py-2 rounded-3xl transition"
on:click={() => {
show = false;
dispatch('cancel');
}}
type="button"
>
{cancelLabel}
</button>
<button
<!-- [MODIFICATION BEGINS] - Add cancel button
Original:
<div class="mt-6 flex justify-between gap-1.5">
<button
class="text-sm bg-gray-100 hover:bg-gray-200 text-gray-800
dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-white font-medium w-full py-2 rounded-3xl transition"
on:click={() => {
show = false;
dispatch('cancel');
}}
type="button"
>
{cancelLabel}
</button>
<button
-->
<div class="mt-6 flex justify-between gap-1.5">
{#if showCancel}
<button
class="text-sm bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-white font-medium w-full py-2 rounded-3xl transition"
on:click={() => {
show = false;
dispatch('cancel');
}}
type="button"
>
{cancelLabel}
</button>
{/if}
<!-- [MODIFICATION ENDS] -->
<button
class="text-sm bg-gray-900 hover:bg-gray-850 text-gray-100 dark:bg-gray-100 dark:hover:bg-white dark:text-gray-800 font-medium w-full py-2 rounded-3xl transition"
on:click={() => {
confirmHandler();
Expand Down
70 changes: 64 additions & 6 deletions src/routes/(app)/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@
import { getBanners } from '$lib/apis/configs';
import { getUserSettings } from '$lib/apis/users';

// [ADDITION BEGINS]
// This is to redirect user to the logout URL
// when the Globus token expires or loses the session
import { userSignOut } from '$lib/apis/auths';
import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
// [MODIFICATION ENDS]

import { WEBUI_VERSION } from '$lib/constants';
import { compareVersion } from '$lib/utils';

Expand Down Expand Up @@ -53,6 +60,24 @@

let version;

// [ADDITION BEGINS] - Logout procedure when Globus token expires
let showSessionExpiredModal = false;

const handleSessionExpiredLogout = async () => {
await userSignOut();
user.set(undefined);
localStorage.removeItem('token');

const globusClientId = $config?.oauth?.client_ids?.globus;
const redirectUri = encodeURIComponent(window.location.origin + '/auth');
const globusLogoutUrl = globusClientId
? `https://auth.globus.org/v2/web/logout?client_id=${globusClientId}&redirect_uri=${redirectUri}&redirect_name=ALCF+Inference`
: 'https://auth.globus.org/v2/web/logout';

location.href = globusLogoutUrl;
};
// [ADDITION ENDS]

const clearChatInputStorage = () => {
const chatInputKeys = Object.keys(localStorage).filter((key) => key.startsWith('chat-input'));
if (chatInputKeys.length > 0) {
Expand Down Expand Up @@ -107,12 +132,32 @@
};

const setModels = async () => {
models.set(
await getModels(
localStorage.token,
$config?.features?.enable_direct_connections ? ($settings?.directConnections ?? null) : null
)
);
// [MODIFICATION BEGINS]
// Original:
// models.set(
// await getModels(
// localStorage.token,
// $config?.features?.enable_direct_connections ? ($settings?.directConnections ?? null) : null
// )
// );
// This adds a check to see if the Globus token has expired or lost the session
// and redirects the user to the logout URL if needed
try {
models.set(
await getModels(
localStorage.token,
$config?.features?.enable_direct_connections
? ($settings?.directConnections ?? null)
: null
)
);
} catch (err) {
if (err?.status === 401 && err?.detail === 'list_endpoints_unauthorized') {
console.warn('Globus token expired');
showSessionExpiredModal = true;
}
}
// [MODIFICATION ENDS]
};

const setToolServers = async () => {
Expand Down Expand Up @@ -304,6 +349,19 @@
<SettingsModal bind:show={$showSettings} />
<ChangelogModal bind:show={$showChangelog} />

<!-- [ADDITION BEGINS] - Session expired warning box -->
<ConfirmDialog
bind:show={showSessionExpiredModal}
title={$i18n.t('Session Expired')}
message={$i18n.t(
'Your session has expired. Please log out and log in again to continue.'
)}
confirmLabel={$i18n.t('Log Out')}
showCancel={false}
onConfirm={handleSessionExpiredLogout}
/>
<!-- [ADDITION ENDS] -->

{#if version && compareVersion(version.latest, version.current) && ($settings?.showUpdateToast ?? true)}
<div class=" absolute bottom-8 right-8 z-50" in:fade={{ duration: 100 }}>
<UpdateInfoToast
Expand Down