"...git@developer.sourcefind.cn:chenpangpang/open-webui.git" did not exist on "4727e5cbb1fe43e3f39616ab525805a5eb41161a"
Unverified Commit d17dc592 authored by Timothy Jaeryang Baek's avatar Timothy Jaeryang Baek Committed by GitHub
Browse files

Merge pull request #2574 from cheahjs/feat/oauth

feat: experimental SSO support for Google, Microsoft, and OIDC
parents 09082a07 79f8620b
...@@ -126,6 +126,7 @@ ...@@ -126,6 +126,7 @@
"Content": "Садржај", "Content": "Садржај",
"Context Length": "Дужина контекста", "Context Length": "Дужина контекста",
"Continue Response": "Настави одговор", "Continue Response": "Настави одговор",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "Адреса дељеног ћаскања ископирана у оставу!", "Copied shared chat URL to clipboard!": "Адреса дељеног ћаскања ископирана у оставу!",
"Copy": "Копирај", "Copy": "Копирај",
"Copy last code block": "Копирај последњи блок кода", "Copy last code block": "Копирај последњи блок кода",
...@@ -378,6 +379,7 @@ ...@@ -378,6 +379,7 @@
"Notifications": "Обавештења", "Notifications": "Обавештења",
"November": "Новембар", "November": "Новембар",
"num_thread (Ollama)": "нум _тхреад (Оллама)", "num_thread (Ollama)": "нум _тхреад (Оллама)",
"OAuth ID": "",
"October": "Октобар", "October": "Октобар",
"Off": "Искључено", "Off": "Искључено",
"Okay, Let's Go!": "У реду, хајде да кренемо!", "Okay, Let's Go!": "У реду, хајде да кренемо!",
......
...@@ -126,6 +126,7 @@ ...@@ -126,6 +126,7 @@
"Content": "Innehåll", "Content": "Innehåll",
"Context Length": "Kontextlängd", "Context Length": "Kontextlängd",
"Continue Response": "Fortsätt svar", "Continue Response": "Fortsätt svar",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "Kopierad delad chatt-URL till urklipp!", "Copied shared chat URL to clipboard!": "Kopierad delad chatt-URL till urklipp!",
"Copy": "Kopiera", "Copy": "Kopiera",
"Copy last code block": "Kopiera sista kodblock", "Copy last code block": "Kopiera sista kodblock",
...@@ -378,6 +379,7 @@ ...@@ -378,6 +379,7 @@
"Notifications": "Notifikationer", "Notifications": "Notifikationer",
"November": "november", "November": "november",
"num_thread (Ollama)": "num_thread (Ollama)", "num_thread (Ollama)": "num_thread (Ollama)",
"OAuth ID": "",
"October": "oktober", "October": "oktober",
"Off": "Av", "Off": "Av",
"Okay, Let's Go!": "Okej, nu kör vi!", "Okay, Let's Go!": "Okej, nu kör vi!",
......
...@@ -126,6 +126,7 @@ ...@@ -126,6 +126,7 @@
"Content": "", "Content": "",
"Context Length": "", "Context Length": "",
"Continue Response": "", "Continue Response": "",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "", "Copied shared chat URL to clipboard!": "",
"Copy": "", "Copy": "",
"Copy last code block": "", "Copy last code block": "",
...@@ -378,6 +379,7 @@ ...@@ -378,6 +379,7 @@
"Notifications": "", "Notifications": "",
"November": "", "November": "",
"num_thread (Ollama)": "", "num_thread (Ollama)": "",
"OAuth ID": "",
"October": "", "October": "",
"Off": "", "Off": "",
"Okay, Let's Go!": "", "Okay, Let's Go!": "",
......
...@@ -126,6 +126,7 @@ ...@@ -126,6 +126,7 @@
"Content": "İçerik", "Content": "İçerik",
"Context Length": "Bağlam Uzunluğu", "Context Length": "Bağlam Uzunluğu",
"Continue Response": "Yanıta Devam Et", "Continue Response": "Yanıta Devam Et",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "Paylaşılan sohbet URL'si panoya kopyalandı!", "Copied shared chat URL to clipboard!": "Paylaşılan sohbet URL'si panoya kopyalandı!",
"Copy": "Kopyala", "Copy": "Kopyala",
"Copy last code block": "Son kod bloğunu kopyala", "Copy last code block": "Son kod bloğunu kopyala",
...@@ -378,6 +379,7 @@ ...@@ -378,6 +379,7 @@
"Notifications": "Bildirimler", "Notifications": "Bildirimler",
"November": "Kasım", "November": "Kasım",
"num_thread (Ollama)": "num_thread (Ollama)", "num_thread (Ollama)": "num_thread (Ollama)",
"OAuth ID": "",
"October": "Ekim", "October": "Ekim",
"Off": "Kapalı", "Off": "Kapalı",
"Okay, Let's Go!": "Tamam, Hadi Başlayalım!", "Okay, Let's Go!": "Tamam, Hadi Başlayalım!",
......
...@@ -126,6 +126,7 @@ ...@@ -126,6 +126,7 @@
"Content": "Зміст", "Content": "Зміст",
"Context Length": "Довжина контексту", "Context Length": "Довжина контексту",
"Continue Response": "Продовжити відповідь", "Continue Response": "Продовжити відповідь",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "Скопійовано URL-адресу спільного чату в буфер обміну!", "Copied shared chat URL to clipboard!": "Скопійовано URL-адресу спільного чату в буфер обміну!",
"Copy": "Копіювати", "Copy": "Копіювати",
"Copy last code block": "Копіювати останній блок коду", "Copy last code block": "Копіювати останній блок коду",
...@@ -378,6 +379,7 @@ ...@@ -378,6 +379,7 @@
"Notifications": "Сповіщення", "Notifications": "Сповіщення",
"November": "Листопад", "November": "Листопад",
"num_thread (Ollama)": "num_thread (Ollama)", "num_thread (Ollama)": "num_thread (Ollama)",
"OAuth ID": "",
"October": "Жовтень", "October": "Жовтень",
"Off": "Вимк", "Off": "Вимк",
"Okay, Let's Go!": "Гаразд, давайте почнемо!", "Okay, Let's Go!": "Гаразд, давайте почнемо!",
......
...@@ -126,6 +126,7 @@ ...@@ -126,6 +126,7 @@
"Content": "Nội dung", "Content": "Nội dung",
"Context Length": "Độ dài ngữ cảnh (Context Length)", "Context Length": "Độ dài ngữ cảnh (Context Length)",
"Continue Response": "Tiếp tục trả lời", "Continue Response": "Tiếp tục trả lời",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "Đã sao chép link chia sẻ trò chuyện vào clipboard!", "Copied shared chat URL to clipboard!": "Đã sao chép link chia sẻ trò chuyện vào clipboard!",
"Copy": "Sao chép", "Copy": "Sao chép",
"Copy last code block": "Sao chép khối mã cuối cùng", "Copy last code block": "Sao chép khối mã cuối cùng",
...@@ -378,6 +379,7 @@ ...@@ -378,6 +379,7 @@
"Notifications": "Thông báo trên máy tính (Notification)", "Notifications": "Thông báo trên máy tính (Notification)",
"November": "Tháng 11", "November": "Tháng 11",
"num_thread (Ollama)": "num_thread (Ollama)", "num_thread (Ollama)": "num_thread (Ollama)",
"OAuth ID": "",
"October": "Tháng 10", "October": "Tháng 10",
"Off": "Tắt", "Off": "Tắt",
"Okay, Let's Go!": "Được rồi, Bắt đầu thôi!", "Okay, Let's Go!": "Được rồi, Bắt đầu thôi!",
......
...@@ -126,6 +126,7 @@ ...@@ -126,6 +126,7 @@
"Content": "内容", "Content": "内容",
"Context Length": "上下文长度", "Context Length": "上下文长度",
"Continue Response": "继续生成", "Continue Response": "继续生成",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "已复制此对话分享链接至剪贴板!", "Copied shared chat URL to clipboard!": "已复制此对话分享链接至剪贴板!",
"Copy": "复制", "Copy": "复制",
"Copy last code block": "复制最后一个代码块中的代码", "Copy last code block": "复制最后一个代码块中的代码",
...@@ -378,6 +379,7 @@ ...@@ -378,6 +379,7 @@
"Notifications": "桌面通知", "Notifications": "桌面通知",
"November": "十一月", "November": "十一月",
"num_thread (Ollama)": "num_thread(Ollama)", "num_thread (Ollama)": "num_thread(Ollama)",
"OAuth ID": "",
"October": "十月", "October": "十月",
"Off": "关闭", "Off": "关闭",
"Okay, Let's Go!": "确认,开始使用!", "Okay, Let's Go!": "确认,开始使用!",
......
...@@ -126,6 +126,7 @@ ...@@ -126,6 +126,7 @@
"Content": "內容", "Content": "內容",
"Context Length": "上下文長度", "Context Length": "上下文長度",
"Continue Response": "繼續回答", "Continue Response": "繼續回答",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "已複製共享聊天連結到剪貼簿!", "Copied shared chat URL to clipboard!": "已複製共享聊天連結到剪貼簿!",
"Copy": "複製", "Copy": "複製",
"Copy last code block": "複製最後一個程式碼區塊", "Copy last code block": "複製最後一個程式碼區塊",
...@@ -378,6 +379,7 @@ ...@@ -378,6 +379,7 @@
"Notifications": "通知", "Notifications": "通知",
"November": "11 月", "November": "11 月",
"num_thread (Ollama)": "num_thread(Ollama)", "num_thread (Ollama)": "num_thread(Ollama)",
"OAuth ID": "",
"October": "10 月", "October": "10 月",
"Off": "關閉", "Off": "關閉",
"Okay, Let's Go!": "好的,啟動吧!", "Okay, Let's Go!": "好的,啟動吧!",
......
...@@ -149,6 +149,11 @@ type Config = { ...@@ -149,6 +149,11 @@ type Config = {
enable_admin_export: boolean; enable_admin_export: boolean;
enable_community_sharing: boolean; enable_community_sharing: boolean;
}; };
oauth: {
providers: {
[key: string]: string;
};
};
}; };
type PromptSuggestion = { type PromptSuggestion = {
......
...@@ -195,6 +195,18 @@ ...@@ -195,6 +195,18 @@
<span class="invisible">▲</span> <span class="invisible">▲</span>
{/if} {/if}
</th> </th>
<th
scope="col"
class="px-3 py-2 cursor-pointer select-none"
on:click={() => setSortKey('oauth_sub')}
>
{$i18n.t('OAuth ID')}
{#if sortKey === 'oauth_sub'}
{sortOrder === 'asc' ? '▲' : '▼'}
{:else}
<span class="invisible">▲</span>
{/if}
</th>
<th <th
scope="col" scope="col"
class="px-3 py-2 cursor-pointer select-none" class="px-3 py-2 cursor-pointer select-none"
...@@ -283,6 +295,8 @@ ...@@ -283,6 +295,8 @@
</td> </td>
<td class=" px-3 py-2"> {user.email} </td> <td class=" px-3 py-2"> {user.email} </td>
<td class=" px-3 py-2"> {user.oauth_sub ?? ''} </td>
<td class=" px-3 py-2"> <td class=" px-3 py-2">
{dayjs(user.last_active_at * 1000).fromNow()} {dayjs(user.last_active_at * 1000).fromNow()}
</td> </td>
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
USAGE_POOL USAGE_POOL
} from '$lib/stores'; } from '$lib/stores';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { Toaster, toast } from 'svelte-sonner'; import { Toaster, toast } from 'svelte-sonner';
import { getBackendConfig } from '$lib/apis'; import { getBackendConfig } from '$lib/apis';
...@@ -141,7 +142,11 @@ ...@@ -141,7 +142,11 @@
await goto('/auth'); await goto('/auth');
} }
} else { } else {
await goto('/auth'); // Don't redirect if we're already on the auth page
// Needed because we pass in tokens from OAuth logins via URL fragments
if ($page.url.pathname !== '/auth') {
await goto('/auth');
}
} }
} }
} else { } else {
......
<script> <script>
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { userSignIn, userSignUp } from '$lib/apis/auths'; import { getSessionUser, userSignIn, userSignUp } from '$lib/apis/auths';
import Spinner from '$lib/components/common/Spinner.svelte'; import Spinner from '$lib/components/common/Spinner.svelte';
import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants'; import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
import { WEBUI_NAME, config, user, socket } from '$lib/stores'; import { WEBUI_NAME, config, user, socket } from '$lib/stores';
import { onMount, getContext } from 'svelte'; import { onMount, getContext } from 'svelte';
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
import { generateInitialsImage, canvasPixelTest } from '$lib/utils'; import { generateInitialsImage, canvasPixelTest } from '$lib/utils';
import { page } from '$app/stores';
const i18n = getContext('i18n'); const i18n = getContext('i18n');
...@@ -21,7 +22,9 @@ ...@@ -21,7 +22,9 @@
if (sessionUser) { if (sessionUser) {
console.log(sessionUser); console.log(sessionUser);
toast.success($i18n.t(`You're now logged in.`)); toast.success($i18n.t(`You're now logged in.`));
localStorage.token = sessionUser.token; if (sessionUser.token) {
localStorage.token = sessionUser.token;
}
$socket.emit('user-join', { auth: { token: sessionUser.token } }); $socket.emit('user-join', { auth: { token: sessionUser.token } });
await user.set(sessionUser); await user.set(sessionUser);
...@@ -57,10 +60,35 @@ ...@@ -57,10 +60,35 @@
} }
}; };
const checkOauthCallback = async () => {
if (!$page.url.hash) {
return;
}
const hash = $page.url.hash.substring(1);
if (!hash) {
return;
}
const params = new URLSearchParams(hash);
const token = params.get('token');
if (!token) {
return;
}
const sessionUser = await getSessionUser(token).catch((error) => {
toast.error(error);
return null;
});
if (!sessionUser) {
return;
}
localStorage.token = token;
await setSessionUser(sessionUser);
};
onMount(async () => { onMount(async () => {
if ($user !== undefined) { if ($user !== undefined) {
await goto('/'); await goto('/');
} }
await checkOauthCallback();
loaded = true; loaded = true;
if (($config?.features.auth_trusted_header ?? false) || $config?.features.auth === false) { if (($config?.features.auth_trusted_header ?? false) || $config?.features.auth === false) {
await signInHandler(); await signInHandler();
...@@ -219,6 +247,97 @@ ...@@ -219,6 +247,97 @@
{/if} {/if}
</div> </div>
</form> </form>
{#if Object.keys($config?.oauth?.providers ?? {}).length > 0}
<div class="inline-flex items-center justify-center w-full">
<hr class="w-64 h-px my-8 bg-gray-200 border-0 dark:bg-gray-700" />
<span
class="absolute px-3 font-medium text-gray-900 -translate-x-1/2 bg-white left-1/2 dark:text-white dark:bg-gray-950"
>{$i18n.t('or')}</span
>
</div>
<div class="flex flex-col space-y-2">
{#if $config?.oauth?.providers?.google}
<button
class="flex items-center px-6 border-2 dark:border-gray-800 duration-300 dark:bg-gray-900 hover:bg-gray-100 dark:hover:bg-gray-800 w-full rounded-2xl dark:text-white text-sm py-3 transition"
on:click={() => {
window.location.href = `${WEBUI_BASE_URL}/oauth/google/login`;
}}
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" class="size-6 mr-3">
<path
fill="#EA4335"
d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"
/><path
fill="#4285F4"
d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"
/><path
fill="#FBBC05"
d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"
/><path
fill="#34A853"
d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"
/><path fill="none" d="M0 0h48v48H0z" />
</svg>
<span>{$i18n.t('Continue with {{provider}}', { provider: 'Google' })}</span>
</button>
{/if}
{#if $config?.oauth?.providers?.microsoft}
<button
class="flex items-center px-6 border-2 dark:border-gray-800 duration-300 dark:bg-gray-900 hover:bg-gray-100 dark:hover:bg-gray-800 w-full rounded-2xl dark:text-white text-sm py-3 transition"
on:click={() => {
window.location.href = `${WEBUI_BASE_URL}/oauth/microsoft/login`;
}}
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 21" class="size-6 mr-3">
<rect x="1" y="1" width="9" height="9" fill="#f25022" /><rect
x="1"
y="11"
width="9"
height="9"
fill="#00a4ef"
/><rect x="11" y="1" width="9" height="9" fill="#7fba00" /><rect
x="11"
y="11"
width="9"
height="9"
fill="#ffb900"
/>
</svg>
<span>{$i18n.t('Continue with {{provider}}', { provider: 'Microsoft' })}</span>
</button>
{/if}
{#if $config?.oauth?.providers?.oidc}
<button
class="flex items-center px-6 border-2 dark:border-gray-800 duration-300 dark:bg-gray-900 hover:bg-gray-100 dark:hover:bg-gray-800 w-full rounded-2xl dark:text-white text-sm py-3 transition"
on:click={() => {
window.location.href = `${WEBUI_BASE_URL}/oauth/oidc/login`;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-6 mr-3"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15.75 5.25a3 3 0 0 1 3 3m3 0a6 6 0 0 1-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1 1 21.75 8.25Z"
/>
</svg>
<span
>{$i18n.t('Continue with {{provider}}', {
provider: $config?.oauth?.providers?.oidc ?? 'SSO'
})}</span
>
</button>
{/if}
</div>
{/if}
</div> </div>
{/if} {/if}
</div> </div>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment