diff --git a/backend/apps/ollama/main.py b/backend/apps/ollama/main.py index b639d949c..023a10861 100644 --- a/backend/apps/ollama/main.py +++ b/backend/apps/ollama/main.py @@ -8,7 +8,7 @@ import json from apps.web.models.users import Users from constants import ERROR_MESSAGES -from utils import extract_token_from_auth_header +from utils.utils import extract_token_from_auth_header from config import OLLAMA_API_BASE_URL, OLLAMA_WEBUI_AUTH app = Flask(__name__) @@ -25,24 +25,37 @@ TARGET_SERVER_URL = OLLAMA_API_BASE_URL def proxy(path): # Combine the base URL of the target server with the requested path target_url = f"{TARGET_SERVER_URL}/{path}" - print(target_url) + print(path) # Get data from the original request data = request.get_data() headers = dict(request.headers) + # Basic RBAC support if OLLAMA_WEBUI_AUTH: if "Authorization" in headers: token = extract_token_from_auth_header(headers["Authorization"]) user = Users.get_user_by_token(token) if user: - print(user) - pass + # Only user and admin roles can access + if user.role in ["user", "admin"]: + if path in ["pull", "delete", "push", "copy", "create"]: + # Only admin role can perform actions above + if user.role == "admin": + pass + else: + return ( + jsonify({"detail": ERROR_MESSAGES.ACCESS_PROHIBITED}), + 401, + ) + else: + pass + else: + return jsonify({"detail": ERROR_MESSAGES.ACCESS_PROHIBITED}), 401 else: return jsonify({"detail": ERROR_MESSAGES.UNAUTHORIZED}), 401 else: return jsonify({"detail": ERROR_MESSAGES.UNAUTHORIZED}), 401 - else: pass diff --git a/backend/apps/web/models/auths.py b/backend/apps/web/models/auths.py index f00a09cb8..41c82efdb 100644 --- a/backend/apps/web/models/auths.py +++ b/backend/apps/web/models/auths.py @@ -5,7 +5,7 @@ import uuid from apps.web.models.users import UserModel, Users -from utils import ( +from utils.utils import ( verify_password, get_password_hash, bearer_scheme, @@ -43,6 +43,7 @@ class UserResponse(BaseModel): email: str name: str role: str + profile_image_url: str class SigninResponse(Token, UserResponse): @@ -66,7 +67,7 @@ class AuthsTable: self.table = db.auths def insert_new_auth( - self, email: str, password: str, name: str, role: str = "user" + self, email: str, password: str, name: str, role: str = "pending" ) -> Optional[UserModel]: print("insert_new_auth") diff --git a/backend/apps/web/models/users.py b/backend/apps/web/models/users.py index 0ea682140..83fbbb885 100644 --- a/backend/apps/web/models/users.py +++ b/backend/apps/web/models/users.py @@ -3,7 +3,9 @@ from typing import List, Union, Optional from pymongo import ReturnDocument import time -from utils import decode_token +from utils.utils import decode_token +from utils.misc import get_gravatar_url + from config import DB #################### @@ -15,7 +17,8 @@ class UserModel(BaseModel): id: str name: str email: str - role: str = "user" + role: str = "pending" + profile_image_url: str = "/user.png" created_at: int # timestamp in epoch @@ -30,7 +33,7 @@ class UsersTable: self.table = db.users def insert_new_user( - self, id: str, name: str, email: str, role: str = "user" + self, id: str, name: str, email: str, role: str = "pending" ) -> Optional[UserModel]: user = UserModel( **{ @@ -38,6 +41,7 @@ class UsersTable: "name": name, "email": email, "role": role, + "profile_image_url": get_gravatar_url(email), "created_at": int(time.time()), } ) diff --git a/backend/apps/web/routers/auths.py b/backend/apps/web/routers/auths.py index 9ed7b187d..5d4f6182b 100644 --- a/backend/apps/web/routers/auths.py +++ b/backend/apps/web/routers/auths.py @@ -9,12 +9,14 @@ import time import uuid from constants import ERROR_MESSAGES -from utils import ( +from utils.utils import ( get_password_hash, bearer_scheme, create_token, ) +from utils.misc import get_gravatar_url + from apps.web.models.auths import ( SigninForm, SignupForm, @@ -45,10 +47,12 @@ async def get_session_user(cred=Depends(bearer_scheme)): "email": user.email, "name": user.name, "role": user.role, + "profile_image_url": user.profile_image_url, } else: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, + detail=ERROR_MESSAGES.INVALID_TOKEN, ) @@ -70,9 +74,10 @@ async def signin(form_data: SigninForm): "email": user.email, "name": user.name, "role": user.role, + "profile_image_url": user.profile_image_url, } else: - raise HTTPException(400, detail=ERROR_MESSAGES.DEFAULT()) + raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED) ############################ @@ -98,6 +103,7 @@ async def signup(form_data: SignupForm): "email": user.email, "name": user.name, "role": user.role, + "profile_image_url": user.profile_image_url, } else: raise HTTPException(500, detail=ERROR_MESSAGES.DEFAULT(err)) diff --git a/backend/constants.py b/backend/constants.py index 62103c305..8729f7a5b 100644 --- a/backend/constants.py +++ b/backend/constants.py @@ -7,7 +7,12 @@ class MESSAGES(str, Enum): class ERROR_MESSAGES(str, Enum): DEFAULT = lambda err="": f"Something went wrong :/\n{err if err else ''}" + INVALID_TOKEN = ( + "Your session has expired or the token is invalid. Please sign in again." + ) + INVALID_CRED = "The email or password provided is incorrect. Please check for typos and try logging in again." UNAUTHORIZED = "401 Unauthorized" + ACCESS_PROHIBITED = "You do not have permission to access this resource. Please contact your administrator for assistance." NOT_FOUND = "We could not find what you're looking for :/" USER_NOT_FOUND = "We could not find what you're looking for :/" MALICIOUS = "Unusual activities detected, please try again in a few minutes." diff --git a/backend/utils/misc.py b/backend/utils/misc.py new file mode 100644 index 000000000..934c9e60d --- /dev/null +++ b/backend/utils/misc.py @@ -0,0 +1,15 @@ +import hashlib + + +def get_gravatar_url(email): + # Trim leading and trailing whitespace from + # an email address and force all characters + # to lower case + address = str(email).strip().lower() + + # Create a SHA256 hash of the final string + hash_object = hashlib.sha256(address.encode()) + hash_hex = hash_object.hexdigest() + + # Grab the actual image URL + return f"https://www.gravatar.com/avatar/{hash_hex}" diff --git a/backend/utils.py b/backend/utils/utils.py similarity index 100% rename from backend/utils.py rename to backend/utils/utils.py diff --git a/src/lib/components/chat/SettingsModal.svelte b/src/lib/components/chat/SettingsModal.svelte index 61916e8e2..07fe96ea2 100644 --- a/src/lib/components/chat/SettingsModal.svelte +++ b/src/lib/components/chat/SettingsModal.svelte @@ -149,6 +149,10 @@ if (data.error) { throw data.error; } + + if (data.detail) { + throw data.detail; + } if (data.status) { if (!data.status.includes('downloading')) { toast.success(data.status); @@ -206,6 +210,10 @@ if (data.error) { throw data.error; } + if (data.detail) { + throw data.detail; + } + if (data.status) { } } else { diff --git a/src/lib/components/layout/Navbar.svelte b/src/lib/components/layout/Navbar.svelte index b7a6f4f07..389b7eba8 100644 --- a/src/lib/components/layout/Navbar.svelte +++ b/src/lib/components/layout/Navbar.svelte @@ -388,17 +388,17 @@ {#if $user !== undefined} @@ -406,7 +406,7 @@ {#if showDropdown} + + --> + + + + +