mirror of
https://github.com/open-webui/open-webui
synced 2025-05-24 14:54:33 +00:00
feat: user permissions
This commit is contained in:
parent
057c957f5d
commit
cf2dcf1dc3
@ -17,7 +17,10 @@ from open_webui.constants import ERROR_MESSAGES
|
|||||||
from open_webui.env import SRC_LOG_LEVELS
|
from open_webui.env import SRC_LOG_LEVELS
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
from open_webui.utils.utils import get_admin_user, get_verified_user
|
from open_webui.utils.utils import get_admin_user, get_verified_user
|
||||||
|
from open_webui.utils.access_control import has_permission
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
log.setLevel(SRC_LOG_LEVELS["MODELS"])
|
log.setLevel(SRC_LOG_LEVELS["MODELS"])
|
||||||
@ -50,9 +53,10 @@ async def get_session_user_chat_list(
|
|||||||
|
|
||||||
@router.delete("/", response_model=bool)
|
@router.delete("/", response_model=bool)
|
||||||
async def delete_all_user_chats(request: Request, user=Depends(get_verified_user)):
|
async def delete_all_user_chats(request: Request, user=Depends(get_verified_user)):
|
||||||
if user.role == "user" and not request.app.state.config.USER_PERMISSIONS.get(
|
|
||||||
"chat", {}
|
if user.role == "user" and not has_permission(
|
||||||
).get("delete", {}):
|
user.id, "chat.delete", request.app.state.config.USER_PERMISSIONS
|
||||||
|
):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||||
@ -385,8 +389,8 @@ async def delete_chat_by_id(request: Request, id: str, user=Depends(get_verified
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
else:
|
else:
|
||||||
if not request.app.state.config.USER_PERMISSIONS.get("chat", {}).get(
|
if not has_permission(
|
||||||
"delete", {}
|
user.id, "chat.delete", request.app.state.config.USER_PERMISSIONS
|
||||||
):
|
):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
@ -1,214 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { getBackendConfig, getModelFilterConfig, updateModelFilterConfig } from '$lib/apis';
|
|
||||||
import { getSignUpEnabledStatus, toggleSignUpEnabledStatus } from '$lib/apis/auths';
|
|
||||||
import { getUserDefaultPermissions, updateUserDefaultPermissions } from '$lib/apis/users';
|
|
||||||
|
|
||||||
import { onMount, getContext } from 'svelte';
|
|
||||||
import { models, config } from '$lib/stores';
|
|
||||||
import Switch from '$lib/components/common/Switch.svelte';
|
|
||||||
import { setDefaultModels } from '$lib/apis/configs';
|
|
||||||
|
|
||||||
const i18n = getContext('i18n');
|
|
||||||
|
|
||||||
export let saveHandler: Function;
|
|
||||||
|
|
||||||
let defaultModelId = '';
|
|
||||||
|
|
||||||
let whitelistEnabled = false;
|
|
||||||
let whitelistModels = [''];
|
|
||||||
let permissions = {
|
|
||||||
chat: {
|
|
||||||
deletion: true,
|
|
||||||
edit: true,
|
|
||||||
temporary: true
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let chatDeletion = true;
|
|
||||||
let chatEdit = true;
|
|
||||||
let chatTemporary = true;
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
permissions = await getUserDefaultPermissions(localStorage.token);
|
|
||||||
|
|
||||||
chatDeletion = permissions?.chat?.deletion ?? true;
|
|
||||||
chatEdit = permissions?.chat?.editing ?? true;
|
|
||||||
chatTemporary = permissions?.chat?.temporary ?? true;
|
|
||||||
|
|
||||||
const res = await getModelFilterConfig(localStorage.token);
|
|
||||||
if (res) {
|
|
||||||
whitelistEnabled = res.enabled;
|
|
||||||
whitelistModels = res.models.length > 0 ? res.models : [''];
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultModelId = $config.default_models ? $config?.default_models.split(',')[0] : '';
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<form
|
|
||||||
class="flex flex-col h-full justify-between space-y-3 text-sm"
|
|
||||||
on:submit|preventDefault={async () => {
|
|
||||||
// console.log('submit');
|
|
||||||
|
|
||||||
await setDefaultModels(localStorage.token, defaultModelId);
|
|
||||||
await updateUserDefaultPermissions(localStorage.token, {
|
|
||||||
chat: {
|
|
||||||
deletion: chatDeletion,
|
|
||||||
editing: chatEdit,
|
|
||||||
temporary: chatTemporary
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await updateModelFilterConfig(localStorage.token, whitelistEnabled, whitelistModels);
|
|
||||||
saveHandler();
|
|
||||||
|
|
||||||
await config.set(await getBackendConfig());
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div class=" space-y-3 overflow-y-scroll max-h-full pr-1.5">
|
|
||||||
<div>
|
|
||||||
<div class=" mb-2 text-sm font-medium">{$i18n.t('User Permissions')}</div>
|
|
||||||
|
|
||||||
<div class=" flex w-full justify-between my-2 pr-2">
|
|
||||||
<div class=" self-center text-xs font-medium">{$i18n.t('Allow Chat Deletion')}</div>
|
|
||||||
|
|
||||||
<Switch bind:state={chatDeletion} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class=" flex w-full justify-between my-2 pr-2">
|
|
||||||
<div class=" self-center text-xs font-medium">{$i18n.t('Allow Chat Editing')}</div>
|
|
||||||
|
|
||||||
<Switch bind:state={chatEdit} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class=" flex w-full justify-between my-2 pr-2">
|
|
||||||
<div class=" self-center text-xs font-medium">{$i18n.t('Allow Temporary Chat')}</div>
|
|
||||||
|
|
||||||
<Switch bind:state={chatTemporary} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- <hr class=" border-gray-50 dark:border-gray-850 my-2" />
|
|
||||||
|
|
||||||
<div class="mt-2 space-y-3">
|
|
||||||
<div>
|
|
||||||
<div class="mb-2">
|
|
||||||
<div class="flex justify-between items-center text-xs">
|
|
||||||
<div class=" text-sm font-medium">{$i18n.t('Manage Models')}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class=" space-y-1 mb-3">
|
|
||||||
<div class="mb-2">
|
|
||||||
<div class="flex justify-between items-center text-xs">
|
|
||||||
<div class=" text-xs font-medium">{$i18n.t('Default Model')}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-1 mr-2">
|
|
||||||
<select
|
|
||||||
class="w-full bg-transparent outline-none py-0.5"
|
|
||||||
bind:value={defaultModelId}
|
|
||||||
placeholder="Select a model"
|
|
||||||
>
|
|
||||||
<option value="" disabled selected>{$i18n.t('Select a model')}</option>
|
|
||||||
{#each $models.filter((model) => model.id) as model}
|
|
||||||
<option value={model.id} class="bg-gray-100 dark:bg-gray-700">{model.name}</option>
|
|
||||||
{/each}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class=" space-y-1">
|
|
||||||
<div class="mb-2">
|
|
||||||
<div class="flex justify-between items-center text-xs my-3 pr-2">
|
|
||||||
<div class=" text-xs font-medium">{$i18n.t('Model Whitelisting')}</div>
|
|
||||||
|
|
||||||
<Switch bind:state={whitelistEnabled} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if whitelistEnabled}
|
|
||||||
<div>
|
|
||||||
<div class=" space-y-1.5">
|
|
||||||
{#each whitelistModels as modelId, modelIdx}
|
|
||||||
<div class="flex w-full">
|
|
||||||
<div class="flex-1 mr-2">
|
|
||||||
<select
|
|
||||||
class="w-full bg-transparent outline-none py-0.5"
|
|
||||||
bind:value={modelId}
|
|
||||||
placeholder="Select a model"
|
|
||||||
>
|
|
||||||
<option value="" disabled selected>{$i18n.t('Select a model')}</option>
|
|
||||||
{#each $models.filter((model) => model.id) as model}
|
|
||||||
<option value={model.id} class="bg-gray-100 dark:bg-gray-700"
|
|
||||||
>{model.name}</option
|
|
||||||
>
|
|
||||||
{/each}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if modelIdx === 0}
|
|
||||||
<button
|
|
||||||
class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-900 dark:text-white rounded-lg transition"
|
|
||||||
type="button"
|
|
||||||
on:click={() => {
|
|
||||||
if (whitelistModels.at(-1) !== '') {
|
|
||||||
whitelistModels = [...whitelistModels, ''];
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
fill="currentColor"
|
|
||||||
class="w-4 h-4"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
{:else}
|
|
||||||
<button
|
|
||||||
class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-900 dark:text-white rounded-lg transition"
|
|
||||||
type="button"
|
|
||||||
on:click={() => {
|
|
||||||
whitelistModels.splice(modelIdx, 1);
|
|
||||||
whitelistModels = whitelistModels;
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
fill="currentColor"
|
|
||||||
class="w-4 h-4"
|
|
||||||
>
|
|
||||||
<path d="M3.75 7.25a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5h-8.5Z" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex justify-end items-center text-xs mt-1.5 text-right">
|
|
||||||
<div class=" text-xs font-medium">
|
|
||||||
{whitelistModels.length}
|
|
||||||
{$i18n.t('Model(s) Whitelisted')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex justify-end pt-3 text-sm font-medium">
|
|
||||||
<button
|
|
||||||
class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
{$i18n.t('Save')}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
@ -92,6 +92,11 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const uploadFileHandler = async (file) => {
|
const uploadFileHandler = async (file) => {
|
||||||
|
if (!($user?.permissions?.chat?.file_upload ?? true)) {
|
||||||
|
toast.error($i18n.t('You do not have permission to upload files.'));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
console.log(file);
|
console.log(file);
|
||||||
|
|
||||||
const tempItemId = uuidv4();
|
const tempItemId = uuidv4();
|
||||||
|
@ -729,7 +729,7 @@
|
|||||||
|
|
||||||
{#if message.done}
|
{#if message.done}
|
||||||
{#if !readOnly}
|
{#if !readOnly}
|
||||||
{#if $user.role === 'user' ? ($config?.permissions?.chat?.editing ?? true) : true}
|
{#if $user.role === 'user' ? ($user?.permissions?.chat?.edit ?? true) : true}
|
||||||
<Tooltip content={$i18n.t('Edit')} placement="bottom">
|
<Tooltip content={$i18n.t('Edit')} placement="bottom">
|
||||||
<button
|
<button
|
||||||
class="{isLastMessage
|
class="{isLastMessage
|
||||||
|
@ -48,7 +48,7 @@
|
|||||||
model: model
|
model: model
|
||||||
}))}
|
}))}
|
||||||
showTemporaryChatControl={$user.role === 'user'
|
showTemporaryChatControl={$user.role === 'user'
|
||||||
? ($config?.permissions?.chat?.temporary ?? true)
|
? ($user?.permissions?.chat?.temporary ?? true)
|
||||||
: true}
|
: true}
|
||||||
bind:value={selectedModel}
|
bind:value={selectedModel}
|
||||||
/>
|
/>
|
||||||
|
Loading…
Reference in New Issue
Block a user