mirror of
https://github.com/open-webui/open-webui.git
synced 2026-11-15 09:31:21 +00:00
refac
This commit is contained in:
parent
360d81dfe8
commit
fdaa240d44
4 changed files with 118 additions and 14 deletions
|
|
@ -16,7 +16,7 @@ from open_webui.constants import ERROR_MESSAGES
|
|||
from open_webui.events import EVENTS, publish_event
|
||||
from open_webui.env import ENABLE_PROFILE_IMAGE_URL_FORWARDING, PROFILE_IMAGE_ALLOWED_MIME_TYPES, STATIC_DIR
|
||||
from open_webui.internal.db import get_async_session
|
||||
from open_webui.models.auths import Auths
|
||||
from open_webui.models.auths import ApiKey, Auths
|
||||
from open_webui.models.config import Config
|
||||
from open_webui.models.chat_messages import ChatMessages
|
||||
from open_webui.models.chats import Chats
|
||||
|
|
@ -41,6 +41,7 @@ from open_webui.models.models import Models
|
|||
from open_webui.models.tools import Tools
|
||||
from open_webui.utils.access_control import fill_missing_permissions, get_permissions, has_permission
|
||||
from open_webui.utils.auth import (
|
||||
create_api_key,
|
||||
get_admin_user,
|
||||
get_password_hash,
|
||||
get_verified_user,
|
||||
|
|
@ -925,6 +926,42 @@ async def revoke_user_sessions(request: Request, user_id: str, session_user=Depe
|
|||
return True
|
||||
|
||||
|
||||
@router.post('/{user_id}/api_key/rotate', response_model=ApiKey)
|
||||
async def rotate_user_api_key_by_id(
|
||||
request: Request,
|
||||
response: Response,
|
||||
user_id: str,
|
||||
session_user: UserModel = Depends(get_admin_user),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
target_user = await Users.get_user_by_id(user_id, db=db)
|
||||
if target_user is None:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.USER_NOT_FOUND)
|
||||
|
||||
first_user = await Users.get_first_user(db=db)
|
||||
if first_user and first_user.id == user_id and session_user.id != user_id:
|
||||
raise HTTPException(status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACTION_PROHIBITED)
|
||||
|
||||
if not await Config.get('auth.enable_api_keys') or (
|
||||
target_user.role != 'admin'
|
||||
and not await has_permission(target_user.id, 'features.api_keys', await Config.get('user.permissions'), db=db)
|
||||
):
|
||||
raise HTTPException(status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.API_KEY_CREATION_NOT_ALLOWED)
|
||||
|
||||
api_key = create_api_key()
|
||||
await Users.update_user_api_key_by_id(user_id, api_key, db=db)
|
||||
await publish_event(
|
||||
request,
|
||||
EVENTS.AUTH_API_KEY_CREATED,
|
||||
actor=session_user,
|
||||
subject_id=user_id,
|
||||
subject_type='user',
|
||||
source='admin',
|
||||
)
|
||||
response.headers['Cache-Control'] = 'no-store'
|
||||
return {'api_key': api_key}
|
||||
|
||||
|
||||
@router.delete('/{user_id}/api_key', response_model=bool)
|
||||
async def delete_user_api_key_by_id(
|
||||
request: Request,
|
||||
|
|
|
|||
|
|
@ -758,6 +758,16 @@ export const revokeUserSessions = async (token: string, userId: string) => {
|
|||
return result;
|
||||
};
|
||||
|
||||
export const rotateUserAPIKeyById = async (token: string, userId: string): Promise<string> => {
|
||||
const response = await fetch(`${WEBUI_API_BASE_URL}/users/${userId}/api_key/rotate`, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
});
|
||||
const result = await response.json();
|
||||
if (!response.ok) throw new Error(result.detail || 'Failed to rotate API key');
|
||||
return result.api_key;
|
||||
};
|
||||
|
||||
export const deleteUserAPIKeyById = async (token: string, userId: string) => {
|
||||
const response = await fetch(`${WEBUI_API_BASE_URL}/users/${userId}/api_key`, {
|
||||
method: 'DELETE',
|
||||
|
|
|
|||
|
|
@ -10,14 +10,17 @@
|
|||
updateUserById,
|
||||
getUserGroupsById,
|
||||
revokeUserSessions,
|
||||
rotateUserAPIKeyById,
|
||||
deleteUserAPIKeyById
|
||||
} from '$lib/apis/users';
|
||||
import { copyToClipboard } from '$lib/utils';
|
||||
|
||||
import Modal from '$lib/components/common/Modal.svelte';
|
||||
import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
|
||||
let showRevokeConfirmation = false;
|
||||
let showDeleteAPIKeyConfirmation = false;
|
||||
let deletingAPIKey = false;
|
||||
let showRotateAPIKeyConfirmation = false;
|
||||
let rotatedAPIKey = '';
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
import XMark from '$lib/components/icons/XMark.svelte';
|
||||
import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
|
||||
|
|
@ -31,11 +34,14 @@
|
|||
export let selectedUser;
|
||||
export let sessionUser;
|
||||
|
||||
$: if (show) {
|
||||
$: if (show && selectedUser) {
|
||||
init();
|
||||
} else {
|
||||
rotatedAPIKey = '';
|
||||
}
|
||||
|
||||
const init = () => {
|
||||
rotatedAPIKey = '';
|
||||
if (selectedUser) {
|
||||
_user = { ...selectedUser, password: '' };
|
||||
loadUserGroups();
|
||||
|
|
@ -74,20 +80,38 @@
|
|||
};
|
||||
</script>
|
||||
|
||||
<ConfirmDialog
|
||||
bind:show={showRotateAPIKeyConfirmation}
|
||||
title={$i18n.t('Rotate API Key?')}
|
||||
message={$i18n.t(
|
||||
'This will replace the current API key. Integrations using the old key will stop working.'
|
||||
)}
|
||||
confirmLabel={$i18n.t('Rotate')}
|
||||
on:confirm={async () => {
|
||||
const target = _user;
|
||||
try {
|
||||
const key = await rotateUserAPIKeyById(localStorage.token, selectedUser.id);
|
||||
if (show && target === _user) rotatedAPIKey = key;
|
||||
toast.success($i18n.t('API Key created.'));
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<ConfirmDialog
|
||||
bind:show={showDeleteAPIKeyConfirmation}
|
||||
title={$i18n.t('Delete API Key?')}
|
||||
message={$i18n.t('This will revoke the current API key.')}
|
||||
confirmLabel={$i18n.t('Delete')}
|
||||
on:confirm={async () => {
|
||||
deletingAPIKey = true;
|
||||
const target = _user;
|
||||
try {
|
||||
await deleteUserAPIKeyById(localStorage.token, selectedUser.id);
|
||||
if (target === _user) rotatedAPIKey = '';
|
||||
toast.success($i18n.t('API Key deleted.'));
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : String(error));
|
||||
} finally {
|
||||
deletingAPIKey = false;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
|
@ -258,15 +282,44 @@
|
|||
</div>
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<div class="text-xs text-gray-500">{$i18n.t('API Key')}</div>
|
||||
<button
|
||||
type="button"
|
||||
class="text-xs text-gray-500 transition-colors hover:text-gray-700 disabled:opacity-50 dark:text-gray-400 dark:hover:text-gray-200"
|
||||
disabled={deletingAPIKey}
|
||||
on:click={() => {
|
||||
showDeleteAPIKeyConfirmation = true;
|
||||
}}>{$i18n.t('Delete')}</button
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
class="text-xs text-gray-500 transition-colors hover:text-gray-700 disabled:opacity-50 dark:text-gray-400 dark:hover:text-gray-200"
|
||||
on:click={() => {
|
||||
showRotateAPIKeyConfirmation = true;
|
||||
}}>{$i18n.t('Rotate')}</button
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="text-xs text-gray-500 transition-colors hover:text-gray-700 disabled:opacity-50 dark:text-gray-400 dark:hover:text-gray-200"
|
||||
on:click={() => {
|
||||
showDeleteAPIKeyConfirmation = true;
|
||||
}}>{$i18n.t('Delete')}</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{#if rotatedAPIKey}
|
||||
<div class="space-y-1.5">
|
||||
<div class="flex items-center gap-2">
|
||||
<SensitiveInput variant="settings" value={rotatedAPIKey} readOnly={true} />
|
||||
<button
|
||||
type="button"
|
||||
class="shrink-0 text-xs text-gray-500 hover:text-gray-700 dark:hover:text-gray-200"
|
||||
aria-label={$i18n.t('Copy API Key')}
|
||||
on:click={async () => {
|
||||
if (await copyToClipboard(rotatedAPIKey))
|
||||
toast.success($i18n.t('Copied to clipboard'));
|
||||
}}>{$i18n.t('Copy')}</button
|
||||
>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500">
|
||||
{$i18n.t(
|
||||
'Copy this key now. It will not be shown here again after you close this dialog.'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -660,6 +660,7 @@
|
|||
"Copy Prompt": "",
|
||||
"Copy proposed revision to clipboard": "",
|
||||
"Copy Share Link": "",
|
||||
"Copy this key now. It will not be shown here again after you close this dialog.": "",
|
||||
"Copy to clipboard": "",
|
||||
"Copy Token": "",
|
||||
"Copy URL": "",
|
||||
|
|
@ -2328,6 +2329,8 @@
|
|||
"Revised": "",
|
||||
"Rewrite the selected text.": "",
|
||||
"Role": "",
|
||||
"Rotate": "",
|
||||
"Rotate API Key?": "",
|
||||
"RTL": "",
|
||||
"Run": "",
|
||||
"Run AI on your own terms. Connect any model, extend with code, and protect what matters without compromise. Your models, your data, your machine, wherever you open it.": "",
|
||||
|
|
@ -3887,6 +3890,7 @@
|
|||
"This will delete the cached model and cannot be undone.": "",
|
||||
"This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "",
|
||||
"This will remove all files and directories from this knowledge base. This action cannot be undone.": "",
|
||||
"This will replace the current API key. Integrations using the old key will stop working.": "",
|
||||
"This will reset the saved settings for this base model to their defaults. The model will remain available.": "",
|
||||
"This will revoke the current API key.": "",
|
||||
"Thorough explanation": "",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue