Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
chenpangpang
open-webui
Commits
8547b780
"vscode:/vscode.git/clone" did not exist on "5300c69fa5ae5a8d4fc83a55f4f5658e985d44c9"
Commit
8547b780
authored
Nov 18, 2023
by
Timothy J. Baek
Browse files
feat: basic RBAC support
parent
921eef03
Changes
13
Show whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
266 additions
and
44 deletions
+266
-44
backend/apps/ollama/main.py
backend/apps/ollama/main.py
+18
-5
backend/apps/web/models/auths.py
backend/apps/web/models/auths.py
+3
-2
backend/apps/web/models/users.py
backend/apps/web/models/users.py
+7
-3
backend/apps/web/routers/auths.py
backend/apps/web/routers/auths.py
+8
-2
backend/constants.py
backend/constants.py
+5
-0
backend/utils/misc.py
backend/utils/misc.py
+15
-0
backend/utils/utils.py
backend/utils/utils.py
+0
-0
src/lib/components/chat/SettingsModal.svelte
src/lib/components/chat/SettingsModal.svelte
+8
-0
src/lib/components/layout/Navbar.svelte
src/lib/components/layout/Navbar.svelte
+7
-7
src/routes/(app)/+layout.svelte
src/routes/(app)/+layout.svelte
+11
-4
src/routes/(app)/+page.svelte
src/routes/(app)/+page.svelte
+22
-9
src/routes/+layout.svelte
src/routes/+layout.svelte
+12
-7
src/routes/auth/+page.svelte
src/routes/auth/+page.svelte
+150
-5
No files found.
backend/apps/ollama/main.py
View file @
8547b780
...
...
@@ -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
)
# 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
...
...
backend/apps/web/models/auths.py
View file @
8547b780
...
...
@@ -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"
)
...
...
backend/apps/web/models/users.py
View file @
8547b780
...
...
@@ -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
()),
}
)
...
...
backend/apps/web/routers/auths.py
View file @
8547b780
...
...
@@ -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
))
...
...
backend/constants.py
View file @
8547b780
...
...
@@ -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."
backend/utils/misc.py
0 → 100644
View file @
8547b780
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
}
"
backend/utils.py
→
backend/utils
/utils
.py
View file @
8547b780
File moved
src/lib/components/chat/SettingsModal.svelte
View file @
8547b780
...
...
@@ -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 {
...
...
src/lib/components/layout/Navbar.svelte
View file @
8547b780
...
...
@@ -388,17 +388,17 @@
{#if $user !== undefined}
<button
class=" flex rounded-md py-3 px-3.5 w-full hover:bg-gray-900 transition"
on:
focus
={() => {
showDropdown =
true
;
on:
click
={() => {
showDropdown =
!showDropdown
;
}}
on:focusout={() => {
setTimeout(() => {
showDropdown = false;
}, 1
0
0);
}, 1
5
0);
}}
>
<div class=" self-center mr-3">
<img src=
"/
user.p
ng"
class=" max-w-[30px] object-cover rounded-full" />
<img src=
{$
user.p
rofile_image_url}
class=" max-w-[30px] object-cover rounded-full" />
</div>
<div class=" self-center font-semibold">{$user.name}</div>
</button>
...
...
@@ -406,7 +406,7 @@
{#if showDropdown}
<div
id="dropdownDots"
class="absolute z-10 bottom-[4.5rem
]
rounded-lg shadow w-[240px] bg-gray-900"
class="absolute z-10 bottom-[
70px]
4.5rem rounded-lg shadow w-[240px] bg-gray-900"
>
<div class="py-2 w-full">
<button
...
...
@@ -440,14 +440,14 @@
</button>
</div>
<hr class="
dark:
border-gray-700 m-0 p-0" />
<hr class=" border-gray-700 m-0 p-0" />
<div class="py-2 w-full">
<button
class="flex py-2.5 px-3.5 w-full hover:bg-gray-800 transition"
on:click={() => {
localStorage.removeItem('token');
location.href = '/';
location.href = '/
auth
';
}}
>
<div class=" self-center mr-3">
...
...
src/routes/(app)/+layout.svelte
View file @
8547b780
<script>
import { config, user } from '$lib/stores';
import { goto } from '$app/navigation';
import { onMount, tick } from 'svelte';
let loaded = false;
onMount(async () => {
if ($config && $config.auth && $user === undefined) {
goto('/auth');
await
goto('/auth');
}
await tick();
loaded = true;
});
</script>
{#if
$config !== undefin
ed}
{#if
load
ed}
<slot />
{/if}
src/routes/(app)/+page.svelte
View file @
8547b780
...
...
@@ -16,7 +16,7 @@
import
Navbar
from
'
$lib/components/layout/Navbar.svelte
'
;
import
SettingsModal
from
'
$lib/components/chat/SettingsModal.svelte
'
;
import
Suggestions
from
'
$lib/components/chat/Suggestions.svelte
'
;
import
{
user
}
from
'
$lib/stores
'
;
import
{
config
,
user
}
from
'
$lib/stores
'
;
let
API_BASE_URL
=
BUILD_TIME_API_BASE_URL
;
let
db
;
...
...
@@ -1224,14 +1224,27 @@
<div
class=
"flex justify-between px-5 mb-3 max-w-3xl mx-auto rounded-lg group"
>
<div
class=
" flex w-full"
>
<div
class=
" mr-4"
>
{#if message.role === 'user'}
{#if $config === null}
<img
src=
"{settings.gravatarUrl ? settings.gravatarUrl : '/user'}.png"
class=
" max-w-[28px] object-cover rounded-full"
alt=
"User profile"
/>
{:else}
<img
src=
"{message.role == 'user'
? settings.gravatarUrl
? settings.gravatarUrl
: '/user'
: '/favicon'}.png"
src=
{$user.profile_image_url}
class=
" max-w-[28px] object-cover rounded-full"
alt=
"User profile"
/>
{/if}
{:else}
<img
src=
"/favicon.png"
class=
" max-w-[28px] object-cover rounded-full"
alt=
"Ollama profile"
/>
{/if}
</div>
<div
class=
"w-full"
>
...
...
src/routes/+layout.svelte
View file @
8547b780
...
...
@@ -11,7 +11,7 @@
let loaded = false;
onMount(async () => {
const
web
Backend
Status
= await fetch(`${WEBUI_API_BASE_URL}/`, {
const
res
Backend = await fetch(`${WEBUI_API_BASE_URL}/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
...
...
@@ -26,11 +26,11 @@
return null;
});
console.log(
web
Backend
Status
);
await config.set(
web
Backend
Status
);
console.log(
res
Backend);
await config.set(
res
Backend);
if (
webBackendStatus
) {
if (
webBackendStatus
.auth) {
if (
$config
) {
if (
$config
.auth) {
if (localStorage.token) {
const res = await fetch(`${WEBUI_API_BASE_URL}/auths`, {
method: 'GET',
...
...
@@ -49,9 +49,14 @@
return null;
});
if (res) {
await user.set(res);
} else {
goto('/auth');
localStorage.removeItem('token');
await goto('/auth');
}
} else {
await goto('/auth');
}
}
}
...
...
src/routes/auth/+page.svelte
View file @
8547b780
...
...
@@ -2,8 +2,10 @@
import { goto } from '$app/navigation';
import { WEBUI_API_BASE_URL } from '$lib/constants';
import { config, user } from '$lib/stores';
import { onMount } from 'svelte';
import toast from 'svelte-french-toast';
let loaded = false;
let mode = 'signin';
let name = '';
...
...
@@ -33,7 +35,7 @@
if (res) {
console.log(res);
toast.success(`You're now logged in. Redirecting you to the main page.
"
`);
toast.success(`You're now logged in. Redirecting you to the main page.`);
localStorage.token = res.token;
await user.set(res);
goto('/');
...
...
@@ -71,12 +73,15 @@
}
};
onMount(async () => {
if ($config === null || !$config.auth || ($config.auth && $user !== undefined)) {
goto('/');
await
goto('/');
}
loaded = true;
});
</script>
{#if $config && $config.auth}
{#if
loaded &&
$config && $config.auth}
<div class="fixed m-10 z-50">
<div class="flex space-x-2">
<div class=" self-center">
...
...
@@ -1065,6 +1070,146 @@
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment