This commit is contained in:
Timothy Jaeryang Baek 2026-07-26 18:06:03 -04:00
parent e3cce68ef2
commit 1f0dc90abe
11 changed files with 287 additions and 82 deletions

View file

@ -1843,6 +1843,10 @@ USER_PERMISSIONS_CHAT_ALLOW_PUBLIC_SHARING = (
os.getenv('USER_PERMISSIONS_CHAT_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true'
)
USER_PERMISSIONS_CHAT_ALLOW_OPEN_SHARING = (
os.getenv('USER_PERMISSIONS_CHAT_ALLOW_OPEN_SHARING', 'False').lower() == 'true'
)
USER_PERMISSIONS_CHAT_EXPORT = os.getenv('USER_PERMISSIONS_CHAT_EXPORT', 'True').lower() == 'true'
USER_PERMISSIONS_CHAT_IMPORT = os.getenv('USER_PERMISSIONS_CHAT_IMPORT', 'True').lower() == 'true'
@ -1929,6 +1933,7 @@ DEFAULT_USER_PERMISSIONS = {
'public_notes': USER_PERMISSIONS_NOTES_ALLOW_PUBLIC_SHARING,
'folders': USER_PERMISSIONS_FOLDERS_ALLOW_SHARING,
'public_chats': USER_PERMISSIONS_CHAT_ALLOW_PUBLIC_SHARING,
'open_chats': USER_PERMISSIONS_CHAT_ALLOW_OPEN_SHARING,
'public_calendars': USER_PERMISSIONS_CALENDAR_ALLOW_PUBLIC_SHARING,
},
'access_grants': {

View file

@ -11,6 +11,11 @@ from sqlalchemy.ext.asyncio import AsyncSession
log = logging.getLogger(__name__)
PRINCIPAL_TYPE_ANYONE = 'anyone'
PRINCIPAL_TYPE_GROUP = 'group'
PRINCIPAL_TYPE_USER = 'user'
WILDCARD_PRINCIPAL_ID = '*'
####################
# AccessGrant DB Schema
@ -23,7 +28,7 @@ class AccessGrant(Base):
id = Column(Text, primary_key=True)
resource_type = Column(Text, nullable=False) # "knowledge", "model", "prompt", "tool", "note", "channel", "file"
resource_id = Column(Text, nullable=False)
principal_type = Column(Text, nullable=False) # "user" or "group"
principal_type = Column(Text, nullable=False) # "user", "group", or "anyone"
principal_id = Column(Text, nullable=False) # user_id, group_id, or "*" (wildcard for public)
permission = Column(Text, nullable=False) # "read" or "write"
created_at = Column(BigInteger, nullable=False)
@ -163,12 +168,16 @@ def normalize_access_grants(access_grants: Optional[list]) -> list[dict]:
principal_id = grant.get('principal_id')
permission = grant.get('permission')
if principal_type not in ('user', 'group'):
if principal_type not in (PRINCIPAL_TYPE_USER, PRINCIPAL_TYPE_GROUP, PRINCIPAL_TYPE_ANYONE):
continue
if permission not in ('read', 'write'):
continue
if not isinstance(principal_id, str) or not principal_id:
continue
if principal_type == PRINCIPAL_TYPE_ANYONE and (
principal_id != WILDCARD_PRINCIPAL_ID or permission != 'read'
):
continue
key = (principal_type, principal_id, permission)
deduped[key] = {
@ -186,7 +195,11 @@ def has_public_read_access_grant(access_grants: Optional[list]) -> bool:
Returns True when a direct grant list includes wildcard public-read.
"""
for grant in normalize_access_grants(access_grants):
if grant['principal_type'] == 'user' and grant['principal_id'] == '*' and grant['permission'] == 'read':
if (
grant['principal_type'] == PRINCIPAL_TYPE_USER
and grant['principal_id'] == WILDCARD_PRINCIPAL_ID
and grant['permission'] == 'read'
):
return True
return False
@ -196,7 +209,25 @@ def has_public_write_access_grant(access_grants: Optional[list]) -> bool:
Returns True when a direct grant list includes wildcard public-write.
"""
for grant in normalize_access_grants(access_grants):
if grant['principal_type'] == 'user' and grant['principal_id'] == '*' and grant['permission'] == 'write':
if (
grant['principal_type'] == PRINCIPAL_TYPE_USER
and grant['principal_id'] == WILDCARD_PRINCIPAL_ID
and grant['permission'] == 'write'
):
return True
return False
def has_anyone_read_access_grant(access_grants: Optional[list]) -> bool:
"""
Returns True when a direct grant list includes no-auth anyone-read.
"""
for grant in normalize_access_grants(access_grants):
if (
grant['principal_type'] == PRINCIPAL_TYPE_ANYONE
and grant['principal_id'] == WILDCARD_PRINCIPAL_ID
and grant['permission'] == 'read'
):
return True
return False
@ -206,7 +237,7 @@ def has_user_access_grant(access_grants: Optional[list]) -> bool:
Returns True when a direct grant list includes any non-wildcard user grant.
"""
for grant in normalize_access_grants(access_grants):
if grant['principal_type'] == 'user' and grant['principal_id'] != '*':
if grant['principal_type'] == PRINCIPAL_TYPE_USER and grant['principal_id'] != WILDCARD_PRINCIPAL_ID:
return True
return False
@ -223,12 +254,27 @@ def strip_user_access_grants(access_grants: Optional[list]) -> list:
for grant in access_grants
if not (
(grant.get('principal_type') if isinstance(grant, dict) else getattr(grant, 'principal_type', None))
== 'user'
and (grant.get('principal_id') if isinstance(grant, dict) else getattr(grant, 'principal_id', None)) != '*'
== PRINCIPAL_TYPE_USER
and (grant.get('principal_id') if isinstance(grant, dict) else getattr(grant, 'principal_id', None))
!= WILDCARD_PRINCIPAL_ID
)
]
def strip_anyone_access_grants(access_grants: Optional[list]) -> list:
"""
Remove no-auth anyone grants from the list.
"""
if not access_grants:
return []
return [
grant
for grant in access_grants
if (grant.get('principal_type') if isinstance(grant, dict) else getattr(grant, 'principal_type', None))
!= PRINCIPAL_TYPE_ANYONE
]
def grants_to_access_control(grants: list) -> Optional[dict]:
"""
Convert a list of grant objects (AccessGrantModel or AccessGrantResponse)
@ -493,6 +539,28 @@ class AccessGrantsTable:
result_dict[g.resource_id].append(AccessGrantModel.model_validate(g))
return result_dict
async def has_anyone_access(
self,
resource_type: str,
resource_id: str,
permission: str = 'read',
db: Optional[AsyncSession] = None,
) -> bool:
"""Check for a no-auth anyone:* grant. Callers must opt in explicitly."""
async with get_async_db_context(db) as db:
result = await db.execute(
select(AccessGrant)
.filter(
AccessGrant.resource_type == resource_type,
AccessGrant.resource_id == resource_id,
AccessGrant.principal_type == PRINCIPAL_TYPE_ANYONE,
AccessGrant.principal_id == WILDCARD_PRINCIPAL_ID,
AccessGrant.permission == permission,
)
.limit(1)
)
return result.scalars().first() is not None
async def has_access(
self,
user_id: str,

View file

@ -6,8 +6,9 @@ import logging
from typing import Optional
from uuid import uuid4
from fastapi import APIRouter, Depends, HTTPException, Request, status
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Request, Response, status
from fastapi.responses import StreamingResponse
from fastapi.security import HTTPAuthorizationCredentials
from open_webui.config import ENABLE_ADMIN_CHAT_ACCESS, ENABLE_ADMIN_EXPORT
from open_webui.constants import ERROR_MESSAGES
from open_webui.events import EVENTS, publish_event
@ -37,7 +38,7 @@ from open_webui.socket.main import get_event_emitter
from open_webui.tasks import has_active_tasks, stop_item_tasks
from open_webui.utils.access_control import filter_allowed_access_grants, has_permission
from open_webui.utils.access_control.folders import has_folder_access
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.auth import bearer_security, get_admin_user, get_current_user, get_verified_user
from open_webui.utils.chat_fork import build_fork_history
from open_webui.utils.context_compaction import compact_chat_branch, get_chat_context_usage
from open_webui.utils.misc import get_message_list
@ -60,6 +61,47 @@ CHAT_CONFIG_KEYS = {
}
async def get_optional_verified_user(
request: Request,
response: Response,
background_tasks: BackgroundTasks,
auth_token: HTTPAuthorizationCredentials | None = Depends(bearer_security),
):
try:
user = await get_current_user(request, response, background_tasks, auth_token)
except HTTPException:
return None
if user.role not in {'user', 'admin'}:
return None
return user
async def is_open_shared_chat(shared, db: AsyncSession) -> bool:
return await AccessGrants.has_anyone_access(
resource_type='shared_chat',
resource_id=shared.chat_id,
permission='read',
db=db,
)
async def can_read_shared_chat(user, shared, db: AsyncSession) -> bool:
if user.role == 'pending':
return False
if user.role == 'admin' and ENABLE_ADMIN_CHAT_ACCESS:
return True
if shared.user_id == user.id:
return True
return await AccessGrants.has_access(
user_id=user.id,
resource_type='shared_chat',
resource_id=shared.chat_id,
permission='read',
db=db,
)
async def add_active_state_to_chat_list(
request: Request, chat_list: list[ChatTitleIdResponse]
) -> list[ChatTitleIdResponse]:
@ -1082,38 +1124,30 @@ async def get_shared_session_user_chat_list(
@router.get('/share/{share_id}', response_model=ChatResponse | None)
async def get_shared_chat_by_id(
share_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)
share_id: str, user=Depends(get_optional_verified_user), db: AsyncSession = Depends(get_async_session)
):
if user.role == 'pending':
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND)
shared = await SharedChats.get_by_id(share_id, db=db)
if shared:
if await is_open_shared_chat(shared, db=db) or (
user is not None and await can_read_shared_chat(user, shared, db=db)
):
chat = await Chats.get_chat_by_share_id(share_id, db=db)
if chat:
return ChatResponse(**chat.model_dump())
chat = await Chats.get_chat_by_share_id(share_id, db=db)
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED if user else ERROR_MESSAGES.INVALID_TOKEN,
)
# Fallback: admins can also access any chat directly by chat ID
if not chat and user.role == 'admin' and ENABLE_ADMIN_CHAT_ACCESS:
chat = None
if user is not None and user.role == 'admin' and ENABLE_ADMIN_CHAT_ACCESS:
chat = await Chats.get_chat_by_id(share_id, db=db)
if chat:
return ChatResponse(**chat.model_dump())
if not chat:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND)
# Look up the original chat_id to check access grants (admins bypass)
if user.role != 'admin' or not ENABLE_ADMIN_CHAT_ACCESS:
shared = await SharedChats.get_by_id(share_id, db=db)
if shared and shared.user_id != user.id:
has_grant = await AccessGrants.has_access(
user_id=user.id,
resource_type='shared_chat',
resource_id=shared.chat_id,
permission='read',
db=db,
)
if not has_grant:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
return ChatResponse(**chat.model_dump())
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND)
############################
@ -1753,7 +1787,7 @@ async def clone_shared_chat_by_id(
# Enforce access grants (owner and admins bypass)
shared = await SharedChats.get_by_id(id, db=db)
if shared and user.role != 'admin' and shared.user_id != user.id:
has_grant = await AccessGrants.has_access(
has_grant = await is_open_shared_chat(shared, db=db) or await AccessGrants.has_access(
user_id=user.id,
resource_type='shared_chat',
resource_id=shared.chat_id,
@ -1948,6 +1982,8 @@ async def update_shared_chat_access_by_id(
user.role,
form_data.access_grants,
'sharing.public_chats',
'sharing.open_chats',
db=db,
)
await AccessGrants.set_access_grants('shared_chat', id, form_data.access_grants, db=db)

View file

@ -3,9 +3,11 @@ from typing import Any
from open_webui.config import DEFAULT_USER_PERMISSIONS
from open_webui.models.access_grants import (
has_anyone_read_access_grant,
has_public_read_access_grant,
has_public_write_access_grant,
has_user_access_grant,
strip_anyone_access_grants,
strip_user_access_grants,
)
from open_webui.models.groups import Groups
@ -215,13 +217,31 @@ async def filter_allowed_access_grants(
user_role: str,
access_grants: list,
public_permission_key: str,
anyone_permission_key: str | None = None,
db: AsyncSession | None = None,
) -> list:
"""
Checks if the user has the required permissions to grant access to a resource.
Returns the filtered list of access grants if permissions are missing.
"""
if user_role == 'admin' or not access_grants:
if not access_grants:
return access_grants
if has_anyone_read_access_grant(access_grants) and (
not anyone_permission_key
or (
user_role != 'admin'
and not await has_permission(
user_id,
anyone_permission_key,
default_permissions,
db=db,
)
)
):
access_grants = strip_anyone_access_grants(access_grants)
if user_role == 'admin':
return access_grants
# Check if user can share publicly

View file

@ -494,6 +494,25 @@
</div>
{/if}
</div>
<div class="flex flex-col w-full">
<div class="flex w-full justify-between my-1">
<div class=" self-center text-xs font-normal">
{$i18n.t('Chats Open Sharing')}
</div>
<Switch
bind:state={permissions.sharing.open_chats}
ariaLabel={$i18n.t('Chats Open Sharing')}
/>
</div>
{#if defaultPermissions?.sharing?.open_chats && !permissions.sharing.open_chats}
<div>
<div class="text-xs text-gray-500">
{$i18n.t('This is a default user permission and will remain enabled.')}
</div>
</div>
{/if}
</div>
{/if}
{#if permissions.features.calendar}

View file

@ -158,6 +158,7 @@
bind:accessGrants
accessRoles={['read']}
sharePublic={$user?.permissions?.sharing?.public_chats || $user?.role === 'admin'}
shareOpen={$user?.permissions?.sharing?.open_chats || $user?.role === 'admin'}
shareUsers={($user?.permissions?.access_grants?.allow_users ?? true) ||
$user?.role === 'admin'}
onChange={saveAccessGrants}

View file

@ -16,7 +16,7 @@
type AccessGrant = {
id?: string;
principal_type: 'user' | 'group';
principal_type: 'user' | 'group' | 'anyone';
principal_id: string;
permission: 'read' | 'write';
};
@ -34,6 +34,7 @@
export let share = true;
export let sharePublic = true;
export let shareOpen = false;
export let shareUsers = true;
export let allowGroups = true;
export let defaultPermission: 'read' | 'write' = 'read';
@ -162,6 +163,14 @@
grant.principal_type === 'user' && grant.principal_id === '*' && grant.permission === 'read'
);
const hasAnyoneReadGrant = (grants: AccessGrant[]): boolean =>
grants.some(
(grant) =>
grant.principal_type === 'anyone' &&
grant.principal_id === '*' &&
grant.permission === 'read'
);
const hasPublicWriteGrant = (grants: AccessGrant[]): boolean =>
grants.some(
(grant) =>
@ -170,16 +179,17 @@
grant.permission === 'write'
);
const currentGrants = (): AccessGrant[] =>
Array.isArray(accessGrants) ? (accessGrants as AccessGrant[]) : [];
const currentGrants = (grants: AccessGrant[] | any = accessGrants): AccessGrant[] =>
Array.isArray(grants) ? (grants as AccessGrant[]) : [];
const getPrincipalIdsByPermission = (
principalType: 'user' | 'group',
permission: 'read' | 'write'
permission: 'read' | 'write',
grants: AccessGrant[] | any = accessGrants
): string[] =>
Array.from(
new Set(
currentGrants()
currentGrants(grants)
.filter(
(grant) => grant.principal_type === principalType && grant.permission === permission
)
@ -188,7 +198,7 @@
);
const hasPrincipalGrant = (
principalType: 'user' | 'group',
principalType: 'user' | 'group' | 'anyone',
principalId: string,
permission: 'read' | 'write'
): boolean =>
@ -204,17 +214,32 @@
onChange(accessGrants);
};
const setPublic = (isPublic: boolean) => {
// Remove all user:* grants
const getVisibility = (grants: AccessGrant[]): 'private' | 'public' | 'open' => {
if (hasAnyoneReadGrant(grants)) return 'open';
if (hasPublicReadGrant(grants)) return 'public';
return 'private';
};
const setVisibility = (visibility: 'private' | 'public' | 'open') => {
const filtered = currentGrants().filter(
(grant) => !(grant.principal_type === 'user' && grant.principal_id === '*')
(grant) =>
!(
(grant.principal_type === 'user' || grant.principal_type === 'anyone') &&
grant.principal_id === '*'
)
);
if (isPublic) {
if (visibility === 'public') {
filtered.push({
principal_type: 'user',
principal_id: '*',
permission: 'read'
});
} else if (visibility === 'open') {
filtered.push({
principal_type: 'anyone',
principal_id: '*',
permission: 'read'
});
}
commitAccessGrants(filtered);
};
@ -237,7 +262,7 @@
};
const upsertPrincipalGrant = (
principalType: 'user' | 'group',
principalType: 'user' | 'group' | 'anyone',
principalId: string,
permission: 'read' | 'write',
grants: AccessGrant[]
@ -263,7 +288,7 @@
};
const removePrincipalGrant = (
principalType: 'user' | 'group',
principalType: 'user' | 'group' | 'anyone',
principalId: string,
permission: 'read' | 'write',
grants: AccessGrant[]
@ -277,14 +302,14 @@
)
);
const removePrincipal = (principalType: 'user' | 'group', principalId: string) => {
const removePrincipal = (principalType: 'user' | 'group' | 'anyone', principalId: string) => {
let next = [...currentGrants()];
next = removePrincipalGrant(principalType, principalId, 'read', next);
next = removePrincipalGrant(principalType, principalId, 'write', next);
commitAccessGrants(next);
};
const togglePrincipalWrite = (principalType: 'user' | 'group', principalId: string) => {
const togglePrincipalWrite = (principalType: 'user' | 'group' | 'anyone', principalId: string) => {
let next = [...currentGrants()];
const hasWrite = hasPrincipalGrant(principalType, principalId, 'write');
if (hasWrite) {
@ -379,12 +404,14 @@
$: if (readGroupIds.length > 0 || writeGroupIds.length > 0) {
void ensureGroupsByIds([...readGroupIds, ...writeGroupIds]);
}
$: readGroupIds = (accessGrants, getPrincipalIdsByPermission('group', 'read'));
$: writeGroupIds = (accessGrants, getPrincipalIdsByPermission('group', 'write'));
$: readUserIds =
(accessGrants, getPrincipalIdsByPermission('user', 'read').filter((id) => id !== '*'));
$: writeUserIds =
(accessGrants, getPrincipalIdsByPermission('user', 'write').filter((id) => id !== '*'));
$: readGroupIds = getPrincipalIdsByPermission('group', 'read', accessGrants);
$: writeGroupIds = getPrincipalIdsByPermission('group', 'write', accessGrants);
$: readUserIds = getPrincipalIdsByPermission('user', 'read', accessGrants).filter(
(id) => id !== '*'
);
$: writeUserIds = getPrincipalIdsByPermission('user', 'write', accessGrants).filter(
(id) => id !== '*'
);
$: selectedUserIds = Array.from(new Set([...readUserIds, ...writeUserIds]));
@ -450,7 +477,7 @@
<div class="flex gap-2 items-center">
<div>
<div class="p-2 bg-black/5 dark:bg-white/5 rounded-full">
{#if !hasPublicReadGrant(accessGrants ?? [])}
{#if getVisibility(accessGrants ?? []) === 'private'}
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
@ -486,36 +513,41 @@
<div>
<Tooltip
content={!(share && sharePublic) && !hasPublicReadGrant(accessGrants ?? [])
content={!(share && sharePublic) && getVisibility(accessGrants ?? []) === 'private'
? $i18n.t('You do not have permission to make this public')
: ''}
>
<select
id="models"
class="outline-none bg-transparent text-sm font-normal block w-fit pr-8 max-w-full placeholder-gray-400"
value={!hasPublicReadGrant(accessGrants ?? []) ? 'private' : 'public'}
value={getVisibility(accessGrants ?? [])}
on:change={(e) => {
setPublic((e.target as HTMLSelectElement).value === 'public');
setVisibility((e.target as HTMLSelectElement).value as 'private' | 'public' | 'open');
}}
>
<option class=" text-gray-700" value="private">{$i18n.t('Private')}</option>
{#if (share && sharePublic) || hasPublicReadGrant(accessGrants ?? [])}
<option class=" text-gray-700" value="public">{$i18n.t('Public')}</option>
{/if}
{#if (share && shareOpen) || hasAnyoneReadGrant(accessGrants ?? [])}
<option class=" text-gray-700" value="open">{$i18n.t('Open')}</option>
{/if}
</select>
</Tooltip>
<div class=" text-xs text-gray-400 font-normal">
{#if !hasPublicReadGrant(accessGrants ?? [])}
{#if getVisibility(accessGrants ?? []) === 'private'}
{$i18n.t('Only select users and groups with permission can access')}
{:else}
{:else if getVisibility(accessGrants ?? []) === 'public'}
{$i18n.t('Accessible to all users')}
{:else}
{$i18n.t('Anyone with the link can view')}
{/if}
</div>
</div>
</div>
{#if hasPublicReadGrant(accessGrants ?? []) && accessRoles.includes('write')}
{#if hasPublicReadGrant(accessGrants ?? []) && !hasAnyoneReadGrant(accessGrants ?? []) && accessRoles.includes('write')}
<div class="flex w-full justify-between mt-1.5 ml-0.5">
<div class="self-center text-xs">
{$i18n.t('Allow public write access')}
@ -660,7 +692,7 @@
{/each}
{/if}
{#if !hasPublicReadGrant(accessGrants ?? []) && accessGroups.length === 0 && selectedUsers.length === 0}
{#if getVisibility(accessGrants ?? []) === 'private' && accessGroups.length === 0 && selectedUsers.length === 0}
<div class="text-xs text-gray-500 text-center py-3">
{$i18n.t('No access grants. Private to you.')}
</div>

View file

@ -8,7 +8,7 @@
type AccessGrant = {
id?: string;
principal_type: 'user' | 'group';
principal_type: 'user' | 'group' | 'anyone';
principal_id: string;
permission: 'read' | 'write';
};
@ -20,6 +20,7 @@
export let share = true;
export let sharePublic = true;
export let shareOpen = false;
export let shareUsers = true;
export let onChange = () => {};
@ -49,6 +50,7 @@
{accessRoles}
{share}
{sharePublic}
{shareOpen}
{shareUsers}
/>
</div>

View file

@ -29,6 +29,7 @@ export const DEFAULT_PERMISSIONS = {
public_notes: false,
folders: false,
public_chats: false,
open_chats: false,
public_calendars: false
},
access_grants: {

View file

@ -223,6 +223,7 @@
"and create a new shared link.": "",
"Android": "",
"Anyone": "",
"Anyone with the link can view": "",
"API Auth String": "",
"API Base URL": "",
"API Key": "",
@ -432,6 +433,7 @@
"Chat unshared successfully.": "",
"chats": "",
"Chats": "",
"Chats Open Sharing": "",
"Chats Public Sharing": "",
"Check Again": "",
"Check for updates": "",
@ -1908,6 +1910,7 @@
"Open Modal To Manage Floating Quick Actions": "",
"Open Modal To Manage Image Compression": "",
"Open Model Selector": "",
"Open": "",
"Open note": "",
"Open Settings": "",
"Open Sidebar": "",

View file

@ -43,6 +43,8 @@
};
$: messages = createMessagesList(history, history.currentId);
$: canClone =
$sessionUser && ($sessionUser.role === 'admin' || ($sessionUser.permissions?.chat?.import ?? true));
$: if ($page.params.id) {
(async () => {
@ -60,10 +62,16 @@
//////////////////////////
const loadSharedChat = async () => {
const userSettings = await getUserSettings(localStorage.token).catch((error) => {
console.error(error);
return null;
});
const token = localStorage.token ?? '';
const shareId = $page.params.id;
if (!shareId) return null;
const userSettings = token
? await getUserSettings(token).catch((error) => {
console.error(error);
return null;
})
: null;
if (userSettings) {
settings.set(userSettings.ui);
@ -80,22 +88,31 @@
}
await models.set(
await getModels(
localStorage.token,
$config?.features?.enable_direct_connections && ($settings?.directConnections ?? null)
)
token
? await getModels(
token,
$config?.features?.enable_direct_connections
? ($settings?.directConnections ?? null)
: null
).catch((error) => {
console.error(error);
return [];
})
: []
);
await chatId.set($page.params.id);
chat = await getChatByShareId(localStorage.token, $chatId).catch(async (error) => {
await chatId.set(shareId);
chat = await getChatByShareId(token, shareId).catch(async (error) => {
await goto('/');
return null;
});
if (chat) {
user = await getUserInfoById(localStorage.token, chat.user_id).catch((error) => {
console.error(error);
return null;
});
user = token
? await getUserInfoById(token, chat.user_id).catch((error) => {
console.error(error);
return null;
})
: null;
const chatContent = chat.chat;
@ -128,7 +145,7 @@
};
const cloneSharedChat = async () => {
if (!($sessionUser?.role === 'admin' || ($sessionUser?.permissions?.chat?.import ?? true))) {
if (!canClone) {
toast.error($i18n.t('Access prohibited'));
return;
}
@ -152,6 +169,7 @@
? `${title.length > 30 ? `${title.slice(0, 30)}...` : title} / ${$WEBUI_NAME}`
: `${$WEBUI_NAME}`}
</title>
<meta name="robots" content="noindex,nofollow" />
</svelte:head>
{#if loaded}
@ -202,7 +220,7 @@
</div>
</div>
{#if $sessionUser?.role === 'admin' || ($sessionUser?.permissions?.chat?.import ?? true)}
{#if canClone}
<div
class="absolute bottom-0 right-0 left-0 flex justify-center w-full bg-linear-to-b from-transparent to-white dark:to-gray-900"
>