From 979e6e5a79252d402d01a7d7aa380cdc8f78f5f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antti=20Pyykk=C3=B6nen?= Date: Tue, 19 Nov 2024 16:14:52 +0200 Subject: [PATCH 1/2] feat: support for configuring private api key use --- .../open_webui/apps/webui/routers/auths.py | 8 +- backend/open_webui/config.py | 4 + backend/open_webui/constants.py | 2 + backend/open_webui/main.py | 2 + backend/open_webui/utils/utils.py | 11 +- .../components/chat/Settings/Account.svelte | 164 +++++++++--------- src/lib/stores/index.ts | 1 + 7 files changed, 111 insertions(+), 81 deletions(-) diff --git a/backend/open_webui/apps/webui/routers/auths.py b/backend/open_webui/apps/webui/routers/auths.py index d3592f03b..ba0af2370 100644 --- a/backend/open_webui/apps/webui/routers/auths.py +++ b/backend/open_webui/apps/webui/routers/auths.py @@ -18,9 +18,10 @@ from open_webui.apps.webui.models.auths import ( UserResponse, ) from open_webui.apps.webui.models.users import Users -from open_webui.config import WEBUI_AUTH +from open_webui.config import ENABLE_API_KEY_AUTH from open_webui.constants import ERROR_MESSAGES, WEBHOOK_MESSAGES from open_webui.env import ( + WEBUI_AUTH, WEBUI_AUTH_TRUSTED_EMAIL_HEADER, WEBUI_AUTH_TRUSTED_NAME_HEADER, WEBUI_SESSION_COOKIE_SAME_SITE, @@ -734,6 +735,11 @@ async def update_ldap_config( # create api key @router.post("/api_key", response_model=ApiKey) async def create_api_key_(user=Depends(get_current_user)): + if not ENABLE_API_KEY_AUTH: + raise HTTPException( + status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.API_KEY_CREATION_NOT_ALLOWED + ) + api_key = create_api_key() success = Users.update_user_api_key_by_id(user.id, api_key) if success: diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index c33895396..0a36b8c7c 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -265,6 +265,10 @@ class AppConfig: # WEBUI_AUTH (Required for security) #################################### +ENABLE_API_KEY_AUTH = ( + os.environ.get("ENABLE_API_KEY_AUTH", "True").lower() == "true" +) + JWT_EXPIRES_IN = PersistentConfig( "JWT_EXPIRES_IN", "auth.jwt_expiry", os.environ.get("JWT_EXPIRES_IN", "-1") ) diff --git a/backend/open_webui/constants.py b/backend/open_webui/constants.py index d6f33af4a..9c7d6f9e9 100644 --- a/backend/open_webui/constants.py +++ b/backend/open_webui/constants.py @@ -62,6 +62,7 @@ class ERROR_MESSAGES(str, Enum): NOT_FOUND = "We could not find what you're looking for :/" USER_NOT_FOUND = "We could not find what you're looking for :/" API_KEY_NOT_FOUND = "Oops! It looks like there's a hiccup. The API key is missing. Please make sure to provide a valid API key to access this feature." + API_KEY_NOT_ALLOWED = "Use of API key is not enabled in the environment." MALICIOUS = "Unusual activities detected, please try again in a few minutes." @@ -75,6 +76,7 @@ class ERROR_MESSAGES(str, Enum): OPENAI_NOT_FOUND = lambda name="": "OpenAI API was not found" OLLAMA_NOT_FOUND = "WebUI could not connect to Ollama" CREATE_API_KEY_ERROR = "Oops! Something went wrong while creating your API key. Please try again later. If the issue persists, contact support for assistance." + API_KEY_CREATION_NOT_ALLOWED = "API key creation is not allowed in the environment." EMPTY_CONTENT = "The content provided is empty. Please ensure that there is text or data present before proceeding." diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index 04c86395a..94d766f65 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -74,6 +74,7 @@ from open_webui.config import ( ENABLE_ADMIN_EXPORT, ENABLE_OLLAMA_API, ENABLE_OPENAI_API, + ENABLE_API_KEY_AUTH, ENABLE_TAGS_GENERATION, ENV, FRONTEND_BUILD_DIR, @@ -2427,6 +2428,7 @@ async def get_app_config(request: Request): "auth": WEBUI_AUTH, "auth_trusted_header": bool(webui_app.state.AUTH_TRUSTED_EMAIL_HEADER), "enable_ldap": webui_app.state.config.ENABLE_LDAP, + "enable_api_key_auth": ENABLE_API_KEY_AUTH, "enable_signup": webui_app.state.config.ENABLE_SIGNUP, "enable_login_form": webui_app.state.config.ENABLE_LOGIN_FORM, **( diff --git a/backend/open_webui/utils/utils.py b/backend/open_webui/utils/utils.py index 1c2205ebf..ef8dba1c2 100644 --- a/backend/open_webui/utils/utils.py +++ b/backend/open_webui/utils/utils.py @@ -5,13 +5,11 @@ import jwt from datetime import UTC, datetime, timedelta from typing import Optional, Union, List, Dict - from open_webui.apps.webui.models.users import Users from open_webui.constants import ERROR_MESSAGES from open_webui.env import WEBUI_SECRET_KEY - from fastapi import Depends, HTTPException, Request, Response, status from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from passlib.context import CryptContext @@ -75,10 +73,15 @@ def get_http_authorization_cred(auth_header: str): except Exception: raise ValueError(ERROR_MESSAGES.INVALID_TOKEN) +def get_api_key_auth_config(): + from open_webui.config import ENABLE_API_KEY_AUTH + return ENABLE_API_KEY_AUTH + def get_current_user( request: Request, auth_token: HTTPAuthorizationCredentials = Depends(bearer_security), + api_key_auth_enabled: bool = Depends(get_api_key_auth_config) ): token = None @@ -93,6 +96,10 @@ def get_current_user( # auth by api key if token.startswith("sk-"): + if not api_key_auth_enabled: + raise HTTPException( + status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.API_KEY_NOT_ALLOWED + ) return get_current_user_by_api_key(token) # auth by jwt token diff --git a/src/lib/components/chat/Settings/Account.svelte b/src/lib/components/chat/Settings/Account.svelte index 5946744f9..5e3bef37b 100644 --- a/src/lib/components/chat/Settings/Account.svelte +++ b/src/lib/components/chat/Settings/Account.svelte @@ -2,7 +2,7 @@ import { toast } from 'svelte-sonner'; import { onMount, getContext } from 'svelte'; - import { user } from '$lib/stores'; + import { user, config } from '$lib/stores'; import { updateUserProfile, createAPIKey, getAPIKey } from '$lib/apis/auths'; import UpdatePassword from './Account/UpdatePassword.svelte'; @@ -27,6 +27,8 @@ let APIKey = ''; let APIKeyCopied = false; + $: enableApiKeyAuth = $config?.features.enable_api_key_auth ?? true; + let profileImageInputElement: HTMLInputElement; const submitHandler = async () => { @@ -306,90 +308,96 @@
{$i18n.t('API Key')}
-
- {#if APIKey} - + {#if !enableApiKeyAuth} +
+ {$i18n.t('Private API keys are disabled in this environment')} +
+ {:else} +
+ {#if APIKey} + - - - + + + + + {:else} + - - {:else} - - {/if} -
+ {$i18n.t('Create new secret key')} + {/if} +
+ {/if} {/if} diff --git a/src/lib/stores/index.ts b/src/lib/stores/index.ts index 0765c1c5c..8b2ea7b9d 100644 --- a/src/lib/stores/index.ts +++ b/src/lib/stores/index.ts @@ -172,6 +172,7 @@ type Config = { features: { auth: boolean; auth_trusted_header: boolean; + enable_api_key_auth: boolean; enable_signup: boolean; enable_login_form: boolean; enable_web_search?: boolean; From 7a585fbaf34a8864309d85350c0b8fda97dae8c5 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 19 Nov 2024 12:17:23 -0800 Subject: [PATCH 2/2] enh: option to disable api auth --- backend/open_webui/apps/webui/main.py | 3 +++ .../open_webui/apps/webui/routers/auths.py | 14 +++++++++---- backend/open_webui/config.py | 7 +++++-- backend/open_webui/main.py | 4 ++-- backend/open_webui/utils/utils.py | 8 +------- .../components/admin/Settings/General.svelte | 6 ++++++ .../components/chat/Settings/Account.svelte | 20 ++++++------------- src/lib/stores/index.ts | 2 +- 8 files changed, 34 insertions(+), 30 deletions(-) diff --git a/backend/open_webui/apps/webui/main.py b/backend/open_webui/apps/webui/main.py index 593dcb533..ce4945b69 100644 --- a/backend/open_webui/apps/webui/main.py +++ b/backend/open_webui/apps/webui/main.py @@ -35,6 +35,7 @@ from open_webui.config import ( ENABLE_LOGIN_FORM, ENABLE_MESSAGE_RATING, ENABLE_SIGNUP, + ENABLE_API_KEY, ENABLE_EVALUATION_ARENA_MODELS, EVALUATION_ARENA_MODELS, DEFAULT_ARENA_MODEL, @@ -98,6 +99,8 @@ app.state.config = AppConfig() app.state.config.ENABLE_SIGNUP = ENABLE_SIGNUP app.state.config.ENABLE_LOGIN_FORM = ENABLE_LOGIN_FORM +app.state.config.ENABLE_API_KEY = ENABLE_API_KEY + app.state.config.JWT_EXPIRES_IN = JWT_EXPIRES_IN app.state.AUTH_TRUSTED_EMAIL_HEADER = WEBUI_AUTH_TRUSTED_EMAIL_HEADER app.state.AUTH_TRUSTED_NAME_HEADER = WEBUI_AUTH_TRUSTED_NAME_HEADER diff --git a/backend/open_webui/apps/webui/routers/auths.py b/backend/open_webui/apps/webui/routers/auths.py index ba0af2370..630a8bcbf 100644 --- a/backend/open_webui/apps/webui/routers/auths.py +++ b/backend/open_webui/apps/webui/routers/auths.py @@ -18,7 +18,7 @@ from open_webui.apps.webui.models.auths import ( UserResponse, ) from open_webui.apps.webui.models.users import Users -from open_webui.config import ENABLE_API_KEY_AUTH + from open_webui.constants import ERROR_MESSAGES, WEBHOOK_MESSAGES from open_webui.env import ( WEBUI_AUTH, @@ -581,6 +581,7 @@ async def get_admin_config(request: Request, user=Depends(get_admin_user)): return { "SHOW_ADMIN_DETAILS": request.app.state.config.SHOW_ADMIN_DETAILS, "ENABLE_SIGNUP": request.app.state.config.ENABLE_SIGNUP, + "ENABLE_API_KEY": request.app.state.config.ENABLE_API_KEY, "DEFAULT_USER_ROLE": request.app.state.config.DEFAULT_USER_ROLE, "JWT_EXPIRES_IN": request.app.state.config.JWT_EXPIRES_IN, "ENABLE_COMMUNITY_SHARING": request.app.state.config.ENABLE_COMMUNITY_SHARING, @@ -591,6 +592,7 @@ async def get_admin_config(request: Request, user=Depends(get_admin_user)): class AdminConfig(BaseModel): SHOW_ADMIN_DETAILS: bool ENABLE_SIGNUP: bool + ENABLE_API_KEY: bool DEFAULT_USER_ROLE: str JWT_EXPIRES_IN: str ENABLE_COMMUNITY_SHARING: bool @@ -603,6 +605,7 @@ async def update_admin_config( ): request.app.state.config.SHOW_ADMIN_DETAILS = form_data.SHOW_ADMIN_DETAILS request.app.state.config.ENABLE_SIGNUP = form_data.ENABLE_SIGNUP + request.app.state.config.ENABLE_API_KEY = form_data.ENABLE_API_KEY if form_data.DEFAULT_USER_ROLE in ["pending", "user", "admin"]: request.app.state.config.DEFAULT_USER_ROLE = form_data.DEFAULT_USER_ROLE @@ -621,6 +624,7 @@ async def update_admin_config( return { "SHOW_ADMIN_DETAILS": request.app.state.config.SHOW_ADMIN_DETAILS, "ENABLE_SIGNUP": request.app.state.config.ENABLE_SIGNUP, + "ENABLE_API_KEY": request.app.state.config.ENABLE_API_KEY, "DEFAULT_USER_ROLE": request.app.state.config.DEFAULT_USER_ROLE, "JWT_EXPIRES_IN": request.app.state.config.JWT_EXPIRES_IN, "ENABLE_COMMUNITY_SHARING": request.app.state.config.ENABLE_COMMUNITY_SHARING, @@ -734,14 +738,16 @@ async def update_ldap_config( # create api key @router.post("/api_key", response_model=ApiKey) -async def create_api_key_(user=Depends(get_current_user)): - if not ENABLE_API_KEY_AUTH: +async def create_api_key(request: Request, user=Depends(get_current_user)): + if not request.app.config.state.ENABLE_API_KEY: raise HTTPException( - status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.API_KEY_CREATION_NOT_ALLOWED + status.HTTP_403_FORBIDDEN, + detail=ERROR_MESSAGES.API_KEY_CREATION_NOT_ALLOWED, ) api_key = create_api_key() success = Users.update_user_api_key_by_id(user.id, api_key) + if success: return { "api_key": api_key, diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index 0a36b8c7c..a5adbb0f1 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -265,10 +265,13 @@ class AppConfig: # WEBUI_AUTH (Required for security) #################################### -ENABLE_API_KEY_AUTH = ( - os.environ.get("ENABLE_API_KEY_AUTH", "True").lower() == "true" +ENABLE_API_KEY = PersistentConfig( + "ENABLE_API_KEY", + "auth.api_key.enable", + os.environ.get("ENABLE_API_KEY", "True").lower() == "true", ) + JWT_EXPIRES_IN = PersistentConfig( "JWT_EXPIRES_IN", "auth.jwt_expiry", os.environ.get("JWT_EXPIRES_IN", "-1") ) diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index 94d766f65..c145ca1b8 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -74,7 +74,6 @@ from open_webui.config import ( ENABLE_ADMIN_EXPORT, ENABLE_OLLAMA_API, ENABLE_OPENAI_API, - ENABLE_API_KEY_AUTH, ENABLE_TAGS_GENERATION, ENV, FRONTEND_BUILD_DIR, @@ -941,6 +940,7 @@ async def commit_session_after_request(request: Request, call_next): @app.middleware("http") async def check_url(request: Request, call_next): start_time = int(time.time()) + request.state.enable_api_key = webui_app.state.config.ENABLE_API_KEY response = await call_next(request) process_time = int(time.time()) - start_time response.headers["X-Process-Time"] = str(process_time) @@ -2428,7 +2428,7 @@ async def get_app_config(request: Request): "auth": WEBUI_AUTH, "auth_trusted_header": bool(webui_app.state.AUTH_TRUSTED_EMAIL_HEADER), "enable_ldap": webui_app.state.config.ENABLE_LDAP, - "enable_api_key_auth": ENABLE_API_KEY_AUTH, + "enable_api_key": webui_app.state.config.ENABLE_API_KEY, "enable_signup": webui_app.state.config.ENABLE_SIGNUP, "enable_login_form": webui_app.state.config.ENABLE_LOGIN_FORM, **( diff --git a/backend/open_webui/utils/utils.py b/backend/open_webui/utils/utils.py index ef8dba1c2..cde953102 100644 --- a/backend/open_webui/utils/utils.py +++ b/backend/open_webui/utils/utils.py @@ -73,15 +73,10 @@ def get_http_authorization_cred(auth_header: str): except Exception: raise ValueError(ERROR_MESSAGES.INVALID_TOKEN) -def get_api_key_auth_config(): - from open_webui.config import ENABLE_API_KEY_AUTH - return ENABLE_API_KEY_AUTH - def get_current_user( request: Request, auth_token: HTTPAuthorizationCredentials = Depends(bearer_security), - api_key_auth_enabled: bool = Depends(get_api_key_auth_config) ): token = None @@ -96,14 +91,13 @@ def get_current_user( # auth by api key if token.startswith("sk-"): - if not api_key_auth_enabled: + if not request.state.enable_api_key: raise HTTPException( status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.API_KEY_NOT_ALLOWED ) return get_current_user_by_api_key(token) # auth by jwt token - try: data = decode_token(token) except Exception as e: diff --git a/src/lib/components/admin/Settings/General.svelte b/src/lib/components/admin/Settings/General.svelte index d19d7a9da..8fabe5bce 100644 --- a/src/lib/components/admin/Settings/General.svelte +++ b/src/lib/components/admin/Settings/General.svelte @@ -112,6 +112,12 @@ +
+
{$i18n.t('Enable API Key Auth')}
+ + +
+
diff --git a/src/lib/components/chat/Settings/Account.svelte b/src/lib/components/chat/Settings/Account.svelte index 5e3bef37b..70c53977e 100644 --- a/src/lib/components/chat/Settings/Account.svelte +++ b/src/lib/components/chat/Settings/Account.svelte @@ -26,9 +26,6 @@ let APIKey = ''; let APIKeyCopied = false; - - $: enableApiKeyAuth = $config?.features.enable_api_key_auth ?? true; - let profileImageInputElement: HTMLInputElement; const submitHandler = async () => { @@ -303,16 +300,11 @@
-
-
-
{$i18n.t('API Key')}
-
- - {#if !enableApiKeyAuth} -
- {$i18n.t('Private API keys are disabled in this environment')} + {#if $config?.features?.enable_api_key ?? true} +
+
+
{$i18n.t('API Key')}
- {:else}
{#if APIKey} @@ -397,8 +389,8 @@ > {/if}
- {/if} -
+
+ {/if}
{/if} diff --git a/src/lib/stores/index.ts b/src/lib/stores/index.ts index 8b2ea7b9d..2e3976bf9 100644 --- a/src/lib/stores/index.ts +++ b/src/lib/stores/index.ts @@ -172,7 +172,7 @@ type Config = { features: { auth: boolean; auth_trusted_header: boolean; - enable_api_key_auth: boolean; + enable_api_key: boolean; enable_signup: boolean; enable_login_form: boolean; enable_web_search?: boolean;