mirror of
https://github.com/open-webui/open-webui
synced 2025-06-23 02:16:52 +00:00
enh: exp token check
This commit is contained in:
parent
91e758f3ec
commit
28ec3069de
@ -34,14 +34,17 @@ from fastapi import APIRouter, Depends, HTTPException, Request, status
|
|||||||
from fastapi.responses import RedirectResponse, Response
|
from fastapi.responses import RedirectResponse, Response
|
||||||
from open_webui.config import OPENID_PROVIDER_URL, ENABLE_OAUTH_SIGNUP, ENABLE_LDAP
|
from open_webui.config import OPENID_PROVIDER_URL, ENABLE_OAUTH_SIGNUP, ENABLE_LDAP
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from open_webui.utils.misc import parse_duration, validate_email_format
|
from open_webui.utils.misc import parse_duration, validate_email_format
|
||||||
from open_webui.utils.auth import (
|
from open_webui.utils.auth import (
|
||||||
|
decode_token,
|
||||||
create_api_key,
|
create_api_key,
|
||||||
create_token,
|
create_token,
|
||||||
get_admin_user,
|
get_admin_user,
|
||||||
get_verified_user,
|
get_verified_user,
|
||||||
get_current_user,
|
get_current_user,
|
||||||
get_password_hash,
|
get_password_hash,
|
||||||
|
get_http_authorization_cred,
|
||||||
)
|
)
|
||||||
from open_webui.utils.webhook import post_webhook
|
from open_webui.utils.webhook import post_webhook
|
||||||
from open_webui.utils.access_control import get_permissions
|
from open_webui.utils.access_control import get_permissions
|
||||||
@ -73,31 +76,13 @@ class SessionUserResponse(Token, UserResponse):
|
|||||||
async def get_session_user(
|
async def get_session_user(
|
||||||
request: Request, response: Response, user=Depends(get_current_user)
|
request: Request, response: Response, user=Depends(get_current_user)
|
||||||
):
|
):
|
||||||
expires_delta = parse_duration(request.app.state.config.JWT_EXPIRES_IN)
|
|
||||||
expires_at = None
|
|
||||||
if expires_delta:
|
|
||||||
expires_at = int(time.time()) + int(expires_delta.total_seconds())
|
|
||||||
|
|
||||||
token = create_token(
|
auth_header = request.headers.get("Authorization")
|
||||||
data={"id": user.id},
|
auth_token = get_http_authorization_cred(auth_header)
|
||||||
expires_delta=expires_delta,
|
token = auth_token.credentials
|
||||||
)
|
|
||||||
|
|
||||||
datetime_expires_at = (
|
data = decode_token(token)
|
||||||
datetime.datetime.fromtimestamp(expires_at, datetime.timezone.utc)
|
expires_at = data.get("exp")
|
||||||
if expires_at
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
|
|
||||||
# Set the cookie token
|
|
||||||
response.set_cookie(
|
|
||||||
key="token",
|
|
||||||
value=token,
|
|
||||||
expires=datetime_expires_at,
|
|
||||||
httponly=True, # Ensures the cookie is not accessible via JavaScript
|
|
||||||
samesite=WEBUI_AUTH_COOKIE_SAME_SITE,
|
|
||||||
secure=WEBUI_AUTH_COOKIE_SECURE,
|
|
||||||
)
|
|
||||||
|
|
||||||
user_permissions = get_permissions(
|
user_permissions = get_permissions(
|
||||||
user.id, request.app.state.config.USER_PERMISSIONS
|
user.id, request.app.state.config.USER_PERMISSIONS
|
||||||
@ -289,11 +274,14 @@ async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
|
|||||||
user = Auths.authenticate_user_by_trusted_header(email)
|
user = Auths.authenticate_user_by_trusted_header(email)
|
||||||
|
|
||||||
if user:
|
if user:
|
||||||
|
expires_delta = parse_duration(request.app.state.config.JWT_EXPIRES_IN)
|
||||||
|
expires_at = None
|
||||||
|
if expires_delta:
|
||||||
|
expires_at = int(time.time()) + int(expires_delta.total_seconds())
|
||||||
|
|
||||||
token = create_token(
|
token = create_token(
|
||||||
data={"id": user.id},
|
data={"id": user.id},
|
||||||
expires_delta=parse_duration(
|
expires_delta=expires_delta,
|
||||||
request.app.state.config.JWT_EXPIRES_IN
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set the cookie token
|
# Set the cookie token
|
||||||
@ -301,6 +289,8 @@ async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
|
|||||||
key="token",
|
key="token",
|
||||||
value=token,
|
value=token,
|
||||||
httponly=True, # Ensures the cookie is not accessible via JavaScript
|
httponly=True, # Ensures the cookie is not accessible via JavaScript
|
||||||
|
samesite=WEBUI_AUTH_COOKIE_SAME_SITE,
|
||||||
|
secure=WEBUI_AUTH_COOKIE_SECURE,
|
||||||
)
|
)
|
||||||
|
|
||||||
user_permissions = get_permissions(
|
user_permissions = get_permissions(
|
||||||
@ -310,6 +300,7 @@ async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
|
|||||||
return {
|
return {
|
||||||
"token": token,
|
"token": token,
|
||||||
"token_type": "Bearer",
|
"token_type": "Bearer",
|
||||||
|
"expires_at": expires_at,
|
||||||
"id": user.id,
|
"id": user.id,
|
||||||
"email": user.email,
|
"email": user.email,
|
||||||
"name": user.name,
|
"name": user.name,
|
||||||
|
@ -251,77 +251,79 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="app relative">
|
{#if $user}
|
||||||
<div
|
<div class="app relative">
|
||||||
class=" text-gray-700 dark:text-gray-100 bg-white dark:bg-gray-900 h-screen max-h-[100dvh] overflow-auto flex flex-row justify-end"
|
<div
|
||||||
>
|
class=" text-gray-700 dark:text-gray-100 bg-white dark:bg-gray-900 h-screen max-h-[100dvh] overflow-auto flex flex-row justify-end"
|
||||||
{#if !['user', 'admin'].includes($user?.role)}
|
>
|
||||||
<AccountPending />
|
{#if !['user', 'admin'].includes($user?.role)}
|
||||||
{:else if localDBChats.length > 0}
|
<AccountPending />
|
||||||
<div class="fixed w-full h-full flex z-50">
|
{:else if localDBChats.length > 0}
|
||||||
<div
|
<div class="fixed w-full h-full flex z-50">
|
||||||
class="absolute w-full h-full backdrop-blur-md bg-white/20 dark:bg-gray-900/50 flex justify-center"
|
<div
|
||||||
>
|
class="absolute w-full h-full backdrop-blur-md bg-white/20 dark:bg-gray-900/50 flex justify-center"
|
||||||
<div class="m-auto pb-44 flex flex-col justify-center">
|
>
|
||||||
<div class="max-w-md">
|
<div class="m-auto pb-44 flex flex-col justify-center">
|
||||||
<div class="text-center dark:text-white text-2xl font-medium z-50">
|
<div class="max-w-md">
|
||||||
Important Update<br /> Action Required for Chat Log Storage
|
<div class="text-center dark:text-white text-2xl font-medium z-50">
|
||||||
</div>
|
Important Update<br /> Action Required for Chat Log Storage
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class=" mt-4 text-center text-sm dark:text-gray-200 w-full">
|
<div class=" mt-4 text-center text-sm dark:text-gray-200 w-full">
|
||||||
{$i18n.t(
|
{$i18n.t(
|
||||||
"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through"
|
"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through"
|
||||||
)}
|
)}
|
||||||
<span class="font-semibold dark:text-white"
|
<span class="font-semibold dark:text-white"
|
||||||
>{$i18n.t('Settings')} > {$i18n.t('Chats')} > {$i18n.t('Import Chats')}</span
|
>{$i18n.t('Settings')} > {$i18n.t('Chats')} > {$i18n.t('Import Chats')}</span
|
||||||
>. {$i18n.t(
|
>. {$i18n.t(
|
||||||
'This ensures that your valuable conversations are securely saved to your backend database. Thank you!'
|
'This ensures that your valuable conversations are securely saved to your backend database. Thank you!'
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class=" mt-6 mx-auto relative group w-fit">
|
<div class=" mt-6 mx-auto relative group w-fit">
|
||||||
<button
|
<button
|
||||||
class="relative z-20 flex px-5 py-2 rounded-full bg-white border border-gray-100 dark:border-none hover:bg-gray-100 transition font-medium text-sm"
|
class="relative z-20 flex px-5 py-2 rounded-full bg-white border border-gray-100 dark:border-none hover:bg-gray-100 transition font-medium text-sm"
|
||||||
on:click={async () => {
|
on:click={async () => {
|
||||||
let blob = new Blob([JSON.stringify(localDBChats)], {
|
let blob = new Blob([JSON.stringify(localDBChats)], {
|
||||||
type: 'application/json'
|
type: 'application/json'
|
||||||
});
|
});
|
||||||
saveAs(blob, `chat-export-${Date.now()}.json`);
|
saveAs(blob, `chat-export-${Date.now()}.json`);
|
||||||
|
|
||||||
const tx = DB.transaction('chats', 'readwrite');
|
const tx = DB.transaction('chats', 'readwrite');
|
||||||
await Promise.all([tx.store.clear(), tx.done]);
|
await Promise.all([tx.store.clear(), tx.done]);
|
||||||
await deleteDB('Chats');
|
await deleteDB('Chats');
|
||||||
|
|
||||||
localDBChats = [];
|
localDBChats = [];
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Download & Delete
|
Download & Delete
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="text-xs text-center w-full mt-2 text-gray-400 underline"
|
class="text-xs text-center w-full mt-2 text-gray-400 underline"
|
||||||
on:click={async () => {
|
on:click={async () => {
|
||||||
localDBChats = [];
|
localDBChats = [];
|
||||||
}}>{$i18n.t('Close')}</button
|
}}>{$i18n.t('Close')}</button
|
||||||
>
|
>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
{/if}
|
|
||||||
|
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
|
|
||||||
{#if loaded}
|
{#if loaded}
|
||||||
<slot />
|
<slot />
|
||||||
{:else}
|
{:else}
|
||||||
<div class="w-full flex-1 h-full flex items-center justify-center">
|
<div class="w-full flex-1 h-full flex items-center justify-center">
|
||||||
<Spinner />
|
<Spinner />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.loading {
|
.loading {
|
||||||
|
@ -54,6 +54,7 @@
|
|||||||
const bc = new BroadcastChannel('active-tab-channel');
|
const bc = new BroadcastChannel('active-tab-channel');
|
||||||
|
|
||||||
let loaded = false;
|
let loaded = false;
|
||||||
|
let tokenTimer = null;
|
||||||
|
|
||||||
const BREAKPOINT = 768;
|
const BREAKPOINT = 768;
|
||||||
|
|
||||||
@ -443,6 +444,24 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const checkTokenExpiry = () => {
|
||||||
|
const exp = $user?.expires_at; // token expiry time in unix timestamp
|
||||||
|
const now = Math.floor(Date.now() / 1000); // current time in unix timestamp
|
||||||
|
|
||||||
|
if (!exp) {
|
||||||
|
// If no expiry time is set, do nothing
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (now >= exp) {
|
||||||
|
localStorage.removeItem('token');
|
||||||
|
// redirect to auth page
|
||||||
|
if ($page.url.pathname !== '/auth') {
|
||||||
|
goto(`/auth`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (typeof window !== 'undefined' && window.applyTheme) {
|
if (typeof window !== 'undefined' && window.applyTheme) {
|
||||||
window.applyTheme();
|
window.applyTheme();
|
||||||
@ -560,6 +579,12 @@
|
|||||||
|
|
||||||
await user.set(sessionUser);
|
await user.set(sessionUser);
|
||||||
await config.set(await getBackendConfig());
|
await config.set(await getBackendConfig());
|
||||||
|
|
||||||
|
// Set up the token expiry check
|
||||||
|
if (tokenTimer) {
|
||||||
|
clearInterval(tokenTimer);
|
||||||
|
}
|
||||||
|
tokenTimer = setInterval(checkTokenExpiry, 1000);
|
||||||
} else {
|
} else {
|
||||||
// Redirect Invalid Session User to /auth Page
|
// Redirect Invalid Session User to /auth Page
|
||||||
localStorage.removeItem('token');
|
localStorage.removeItem('token');
|
||||||
|
Loading…
Reference in New Issue
Block a user