diff --git a/backend/apps/web/main.py b/backend/apps/web/main.py index 8877df46e..400ddac0d 100644 --- a/backend/apps/web/main.py +++ b/backend/apps/web/main.py @@ -11,7 +11,15 @@ from apps.web.routers import ( configs, utils, ) -from config import WEBUI_VERSION, WEBUI_AUTH, DEFAULT_MODELS, DEFAULT_PROMPT_SUGGESTIONS, ENABLE_SIGNUP +from config import ( + WEBUI_VERSION, + WEBUI_AUTH, + DEFAULT_MODELS, + DEFAULT_PROMPT_SUGGESTIONS, + DEFAULT_USER_ROLE, + ENABLE_SIGNUP, + USER_PERMISSIONS, +) app = FastAPI() @@ -20,6 +28,9 @@ origins = ["*"] app.state.ENABLE_SIGNUP = ENABLE_SIGNUP app.state.DEFAULT_MODELS = DEFAULT_MODELS app.state.DEFAULT_PROMPT_SUGGESTIONS = DEFAULT_PROMPT_SUGGESTIONS +app.state.DEFAULT_USER_ROLE = DEFAULT_USER_ROLE +app.state.USER_PERMISSIONS = USER_PERMISSIONS + app.add_middleware( CORSMiddleware, diff --git a/backend/apps/web/routers/auths.py b/backend/apps/web/routers/auths.py index 58da3512a..7ccef6300 100644 --- a/backend/apps/web/routers/auths.py +++ b/backend/apps/web/routers/auths.py @@ -19,7 +19,12 @@ from apps.web.models.auths import ( ) from apps.web.models.users import Users -from utils.utils import get_password_hash, get_current_user, get_admin_user, create_token +from utils.utils import ( + get_password_hash, + get_current_user, + get_admin_user, + create_token, +) from utils.misc import get_gravatar_url, validate_email_format from constants import ERROR_MESSAGES @@ -116,16 +121,24 @@ async def signin(form_data: SigninForm): @router.post("/signup", response_model=SigninResponse) async def signup(request: Request, form_data: SignupForm): if not request.app.state.ENABLE_SIGNUP: - raise HTTPException(status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED) + raise HTTPException( + status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED + ) if not validate_email_format(form_data.email.lower()): - raise HTTPException(status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.INVALID_EMAIL_FORMAT) + raise HTTPException( + status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.INVALID_EMAIL_FORMAT + ) if Users.get_user_by_email(form_data.email.lower()): raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN) try: - role = "admin" if Users.get_num_users() == 0 else "pending" + role = ( + "admin" + if Users.get_num_users() == 0 + else request.app.state.DEFAULT_USER_ROLE + ) hashed = get_password_hash(form_data.password) user = Auths.insert_new_auth( form_data.email.lower(), hashed, form_data.name, role @@ -164,3 +177,26 @@ async def get_sign_up_status(request: Request, user=Depends(get_admin_user)): async def toggle_sign_up(request: Request, user=Depends(get_admin_user)): request.app.state.ENABLE_SIGNUP = not request.app.state.ENABLE_SIGNUP return request.app.state.ENABLE_SIGNUP + + +############################ +# Default User Role +############################ + + +@router.get("/signup/user/role") +async def get_default_user_role(request: Request, user=Depends(get_admin_user)): + return request.app.state.DEFAULT_USER_ROLE + + +class UpdateRoleForm(BaseModel): + role: str + + +@router.post("/signup/user/role") +async def update_default_user_role( + request: Request, form_data: UpdateRoleForm, user=Depends(get_admin_user) +): + if form_data.role in ["pending", "user", "admin"]: + request.app.state.DEFAULT_USER_ROLE = form_data.role + return request.app.state.DEFAULT_USER_ROLE diff --git a/backend/apps/web/routers/chats.py b/backend/apps/web/routers/chats.py index 1150234a4..00dcfb6ed 100644 --- a/backend/apps/web/routers/chats.py +++ b/backend/apps/web/routers/chats.py @@ -165,7 +165,17 @@ async def update_chat_by_id( @router.delete("/{id}", response_model=bool) -async def delete_chat_by_id(id: str, user=Depends(get_current_user)): +async def delete_chat_by_id(request: Request, id: str, user=Depends(get_current_user)): + + if ( + user.role == "user" + and not request.app.state.USER_PERMISSIONS["chat"]["deletion"] + ): + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=ERROR_MESSAGES.ACCESS_PROHIBITED, + ) + result = Chats.delete_chat_by_id_and_user_id(id, user.id) return result diff --git a/backend/apps/web/routers/users.py b/backend/apps/web/routers/users.py index 18c6c6202..b8e2732c8 100644 --- a/backend/apps/web/routers/users.py +++ b/backend/apps/web/routers/users.py @@ -1,4 +1,4 @@ -from fastapi import Response +from fastapi import Response, Request from fastapi import Depends, FastAPI, HTTPException, status from datetime import datetime, timedelta from typing import List, Union, Optional @@ -26,6 +26,24 @@ async def get_users(skip: int = 0, limit: int = 50, user=Depends(get_admin_user) return Users.get_users(skip, limit) +############################ +# User Permissions +############################ + + +@router.get("/permissions/user") +async def get_user_permissions(request: Request, user=Depends(get_admin_user)): + return request.app.state.USER_PERMISSIONS + + +@router.post("/permissions/user") +async def update_user_permissions( + request: Request, form_data: dict, user=Depends(get_admin_user) +): + request.app.state.USER_PERMISSIONS = form_data + return request.app.state.USER_PERMISSIONS + + ############################ # UpdateUserRole ############################ diff --git a/backend/config.py b/backend/config.py index 81b900840..f8fa68a70 100644 --- a/backend/config.py +++ b/backend/config.py @@ -93,12 +93,15 @@ DEFAULT_PROMPT_SUGGESTIONS = os.environ.get( }, ], ) +DEFAULT_USER_ROLE = "pending" +USER_PERMISSIONS = {"chat": {"deletion": True}} + #################################### # WEBUI_VERSION #################################### -WEBUI_VERSION = os.environ.get("WEBUI_VERSION", "v1.0.0-alpha.92") +WEBUI_VERSION = os.environ.get("WEBUI_VERSION", "v1.0.0-alpha.100") #################################### # WEBUI_AUTH (Required for security) diff --git a/src/lib/apis/auths/index.ts b/src/lib/apis/auths/index.ts index 5f16f83f5..078589984 100644 --- a/src/lib/apis/auths/index.ts +++ b/src/lib/apis/auths/index.ts @@ -178,6 +178,63 @@ export const getSignUpEnabledStatus = async (token: string) => { return res; }; +export const getDefaultUserRole = async (token: string) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/auths/signup/user/role`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + console.log(err); + error = err.detail; + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + +export const updateDefaultUserRole = async (token: string, role: string) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/auths/signup/user/role`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` + }, + body: JSON.stringify({ + role: role + }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + console.log(err); + error = err.detail; + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + export const toggleSignUpEnabledStatus = async (token: string) => { let error = null; diff --git a/src/lib/apis/chats/index.ts b/src/lib/apis/chats/index.ts index 58c14bb9e..aadf3769f 100644 --- a/src/lib/apis/chats/index.ts +++ b/src/lib/apis/chats/index.ts @@ -272,7 +272,7 @@ export const deleteChatById = async (token: string, id: string) => { return json; }) .catch((err) => { - error = err; + error = err.detail; console.log(err); return null; diff --git a/src/lib/apis/users/index.ts b/src/lib/apis/users/index.ts index 3faeb8c46..788e8820f 100644 --- a/src/lib/apis/users/index.ts +++ b/src/lib/apis/users/index.ts @@ -1,5 +1,62 @@ import { WEBUI_API_BASE_URL } from '$lib/constants'; +export const getUserPermissions = async (token: string) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/users/permissions/user`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + console.log(err); + error = err.detail; + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + +export const updateUserPermissions = async (token: string, permissions: object) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/users/permissions/user`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` + }, + body: JSON.stringify({ + ...permissions + }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + console.log(err); + error = err.detail; + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + export const updateUserRole = async (token: string, id: string, role: string) => { let error = null; diff --git a/src/lib/components/admin/Settings/General.svelte b/src/lib/components/admin/Settings/General.svelte new file mode 100644 index 000000000..48ed41e74 --- /dev/null +++ b/src/lib/components/admin/Settings/General.svelte @@ -0,0 +1,108 @@ + + +
diff --git a/src/lib/components/admin/Settings/Users.svelte b/src/lib/components/admin/Settings/Users.svelte new file mode 100644 index 000000000..8a442c519 --- /dev/null +++ b/src/lib/components/admin/Settings/Users.svelte @@ -0,0 +1,82 @@ + + + diff --git a/src/lib/components/admin/SettingsModal.svelte b/src/lib/components/admin/SettingsModal.svelte new file mode 100644 index 000000000..67f6be884 --- /dev/null +++ b/src/lib/components/admin/SettingsModal.svelte @@ -0,0 +1,107 @@ + + +