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
d17dc592
Unverified
Commit
d17dc592
authored
Jun 24, 2024
by
Timothy Jaeryang Baek
Committed by
GitHub
Jun 24, 2024
Browse files
Merge pull request #2574 from cheahjs/feat/oauth
feat: experimental SSO support for Google, Microsoft, and OIDC
parents
09082a07
79f8620b
Changes
52
Show whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
431 additions
and
10 deletions
+431
-10
backend/apps/webui/internal/migrations/017_add_user_oauth_sub.py
.../apps/webui/internal/migrations/017_add_user_oauth_sub.py
+49
-0
backend/apps/webui/main.py
backend/apps/webui/main.py
+2
-0
backend/apps/webui/models/auths.py
backend/apps/webui/models/auths.py
+4
-1
backend/apps/webui/models/users.py
backend/apps/webui/models/users.py
+25
-0
backend/apps/webui/routers/auths.py
backend/apps/webui/routers/auths.py
+0
-1
backend/config.py
backend/config.py
+139
-0
backend/main.py
backend/main.py
+174
-2
backend/requirements.txt
backend/requirements.txt
+1
-0
pyproject.toml
pyproject.toml
+1
-0
requirements-dev.lock
requirements-dev.lock
+9
-3
requirements.lock
requirements.lock
+9
-3
src/lib/i18n/locales/ar-BH/translation.json
src/lib/i18n/locales/ar-BH/translation.json
+2
-0
src/lib/i18n/locales/bg-BG/translation.json
src/lib/i18n/locales/bg-BG/translation.json
+2
-0
src/lib/i18n/locales/bn-BD/translation.json
src/lib/i18n/locales/bn-BD/translation.json
+2
-0
src/lib/i18n/locales/ca-ES/translation.json
src/lib/i18n/locales/ca-ES/translation.json
+2
-0
src/lib/i18n/locales/ceb-PH/translation.json
src/lib/i18n/locales/ceb-PH/translation.json
+2
-0
src/lib/i18n/locales/de-DE/translation.json
src/lib/i18n/locales/de-DE/translation.json
+2
-0
src/lib/i18n/locales/dg-DG/translation.json
src/lib/i18n/locales/dg-DG/translation.json
+2
-0
src/lib/i18n/locales/en-GB/translation.json
src/lib/i18n/locales/en-GB/translation.json
+2
-0
src/lib/i18n/locales/en-US/translation.json
src/lib/i18n/locales/en-US/translation.json
+2
-0
No files found.
backend/apps/webui/internal/migrations/017_add_user_oauth_sub.py
0 → 100644
View file @
d17dc592
"""Peewee migrations -- 017_add_user_oauth_sub.py.
Some examples (model - class or model name)::
> Model = migrator.orm['table_name'] # Return model in current state by name
> Model = migrator.ModelClass # Return model in current state by name
> migrator.sql(sql) # Run custom SQL
> migrator.run(func, *args, **kwargs) # Run python function with the given args
> migrator.create_model(Model) # Create a model (could be used as decorator)
> migrator.remove_model(model, cascade=True) # Remove a model
> migrator.add_fields(model, **fields) # Add fields to a model
> migrator.change_fields(model, **fields) # Change fields
> migrator.remove_fields(model, *field_names, cascade=True)
> migrator.rename_field(model, old_field_name, new_field_name)
> migrator.rename_table(model, new_table_name)
> migrator.add_index(model, *col_names, unique=False)
> migrator.add_not_null(model, *field_names)
> migrator.add_default(model, field_name, default)
> migrator.add_constraint(model, name, sql)
> migrator.drop_index(model, *col_names)
> migrator.drop_not_null(model, *field_names)
> migrator.drop_constraints(model, *constraints)
"""
from
contextlib
import
suppress
import
peewee
as
pw
from
peewee_migrate
import
Migrator
with
suppress
(
ImportError
):
import
playhouse.postgres_ext
as
pw_pext
def
migrate
(
migrator
:
Migrator
,
database
:
pw
.
Database
,
*
,
fake
=
False
):
"""Write your migrations here."""
migrator
.
add_fields
(
"user"
,
oauth_sub
=
pw
.
TextField
(
null
=
True
,
unique
=
True
),
)
def
rollback
(
migrator
:
Migrator
,
database
:
pw
.
Database
,
*
,
fake
=
False
):
"""Write your rollback migrations here."""
migrator
.
remove_fields
(
"user"
,
"oauth_sub"
)
backend/apps/webui/main.py
View file @
d17dc592
...
...
@@ -2,6 +2,8 @@ from fastapi import FastAPI, Depends
from
fastapi.routing
import
APIRoute
from
fastapi.responses
import
StreamingResponse
from
fastapi.middleware.cors
import
CORSMiddleware
from
starlette.middleware.sessions
import
SessionMiddleware
from
apps.webui.routers
import
(
auths
,
users
,
...
...
backend/apps/webui/models/auths.py
View file @
d17dc592
...
...
@@ -105,6 +105,7 @@ class AuthsTable:
name
:
str
,
profile_image_url
:
str
=
"/user.png"
,
role
:
str
=
"pending"
,
oauth_sub
:
Optional
[
str
]
=
None
,
)
->
Optional
[
UserModel
]:
log
.
info
(
"insert_new_auth"
)
...
...
@@ -115,7 +116,9 @@ class AuthsTable:
)
result
=
Auth
.
create
(
**
auth
.
model_dump
())
user
=
Users
.
insert_new_user
(
id
,
name
,
email
,
profile_image_url
,
role
)
user
=
Users
.
insert_new_user
(
id
,
name
,
email
,
profile_image_url
,
role
,
oauth_sub
)
if
result
and
user
:
return
user
...
...
backend/apps/webui/models/users.py
View file @
d17dc592
...
...
@@ -28,6 +28,8 @@ class User(Model):
settings
=
JSONField
(
null
=
True
)
info
=
JSONField
(
null
=
True
)
oauth_sub
=
TextField
(
null
=
True
,
unique
=
True
)
class
Meta
:
database
=
DB
...
...
@@ -53,6 +55,8 @@ class UserModel(BaseModel):
settings
:
Optional
[
UserSettings
]
=
None
info
:
Optional
[
dict
]
=
None
oauth_sub
:
Optional
[
str
]
=
None
####################
# Forms
...
...
@@ -83,6 +87,7 @@ class UsersTable:
email
:
str
,
profile_image_url
:
str
=
"/user.png"
,
role
:
str
=
"pending"
,
oauth_sub
:
Optional
[
str
]
=
None
,
)
->
Optional
[
UserModel
]:
user
=
UserModel
(
**
{
...
...
@@ -94,6 +99,7 @@ class UsersTable:
"last_active_at"
:
int
(
time
.
time
()),
"created_at"
:
int
(
time
.
time
()),
"updated_at"
:
int
(
time
.
time
()),
"oauth_sub"
:
oauth_sub
,
}
)
result
=
User
.
create
(
**
user
.
model_dump
())
...
...
@@ -123,6 +129,13 @@ class UsersTable:
except
:
return
None
def
get_user_by_oauth_sub
(
self
,
sub
:
str
)
->
Optional
[
UserModel
]:
try
:
user
=
User
.
get
(
User
.
oauth_sub
==
sub
)
return
UserModel
(
**
model_to_dict
(
user
))
except
:
return
None
def
get_users
(
self
,
skip
:
int
=
0
,
limit
:
int
=
50
)
->
List
[
UserModel
]:
return
[
UserModel
(
**
model_to_dict
(
user
))
...
...
@@ -174,6 +187,18 @@ class UsersTable:
except
:
return
None
def
update_user_oauth_sub_by_id
(
self
,
id
:
str
,
oauth_sub
:
str
)
->
Optional
[
UserModel
]:
try
:
query
=
User
.
update
(
oauth_sub
=
oauth_sub
).
where
(
User
.
id
==
id
)
query
.
execute
()
user
=
User
.
get
(
User
.
id
==
id
)
return
UserModel
(
**
model_to_dict
(
user
))
except
:
return
None
def
update_user_by_id
(
self
,
id
:
str
,
updated
:
dict
)
->
Optional
[
UserModel
]:
try
:
query
=
User
.
update
(
**
updated
).
where
(
User
.
id
==
id
)
...
...
backend/apps/webui/routers/auths.py
View file @
d17dc592
...
...
@@ -10,7 +10,6 @@ import re
import
uuid
import
csv
from
apps.webui.models.auths
import
(
SigninForm
,
SignupForm
,
...
...
backend/config.py
View file @
d17dc592
...
...
@@ -305,6 +305,135 @@ JWT_EXPIRES_IN = PersistentConfig(
"JWT_EXPIRES_IN"
,
"auth.jwt_expiry"
,
os
.
environ
.
get
(
"JWT_EXPIRES_IN"
,
"-1"
)
)
####################################
# OAuth config
####################################
ENABLE_OAUTH_SIGNUP
=
PersistentConfig
(
"ENABLE_OAUTH_SIGNUP"
,
"oauth.enable_signup"
,
os
.
environ
.
get
(
"ENABLE_OAUTH_SIGNUP"
,
"False"
).
lower
()
==
"true"
,
)
OAUTH_MERGE_ACCOUNTS_BY_EMAIL
=
PersistentConfig
(
"OAUTH_MERGE_ACCOUNTS_BY_EMAIL"
,
"oauth.merge_accounts_by_email"
,
os
.
environ
.
get
(
"OAUTH_MERGE_ACCOUNTS_BY_EMAIL"
,
"False"
).
lower
()
==
"true"
,
)
OAUTH_PROVIDERS
=
{}
GOOGLE_CLIENT_ID
=
PersistentConfig
(
"GOOGLE_CLIENT_ID"
,
"oauth.google.client_id"
,
os
.
environ
.
get
(
"GOOGLE_CLIENT_ID"
,
""
),
)
GOOGLE_CLIENT_SECRET
=
PersistentConfig
(
"GOOGLE_CLIENT_SECRET"
,
"oauth.google.client_secret"
,
os
.
environ
.
get
(
"GOOGLE_CLIENT_SECRET"
,
""
),
)
GOOGLE_OAUTH_SCOPE
=
PersistentConfig
(
"GOOGLE_OAUTH_SCOPE"
,
"oauth.google.scope"
,
os
.
environ
.
get
(
"GOOGLE_OAUTH_SCOPE"
,
"openid email profile"
),
)
MICROSOFT_CLIENT_ID
=
PersistentConfig
(
"MICROSOFT_CLIENT_ID"
,
"oauth.microsoft.client_id"
,
os
.
environ
.
get
(
"MICROSOFT_CLIENT_ID"
,
""
),
)
MICROSOFT_CLIENT_SECRET
=
PersistentConfig
(
"MICROSOFT_CLIENT_SECRET"
,
"oauth.microsoft.client_secret"
,
os
.
environ
.
get
(
"MICROSOFT_CLIENT_SECRET"
,
""
),
)
MICROSOFT_CLIENT_TENANT_ID
=
PersistentConfig
(
"MICROSOFT_CLIENT_TENANT_ID"
,
"oauth.microsoft.tenant_id"
,
os
.
environ
.
get
(
"MICROSOFT_CLIENT_TENANT_ID"
,
""
),
)
MICROSOFT_OAUTH_SCOPE
=
PersistentConfig
(
"MICROSOFT_OAUTH_SCOPE"
,
"oauth.microsoft.scope"
,
os
.
environ
.
get
(
"MICROSOFT_OAUTH_SCOPE"
,
"openid email profile"
),
)
OAUTH_CLIENT_ID
=
PersistentConfig
(
"OAUTH_CLIENT_ID"
,
"oauth.oidc.client_id"
,
os
.
environ
.
get
(
"OAUTH_CLIENT_ID"
,
""
),
)
OAUTH_CLIENT_SECRET
=
PersistentConfig
(
"OAUTH_CLIENT_SECRET"
,
"oauth.oidc.client_secret"
,
os
.
environ
.
get
(
"OAUTH_CLIENT_SECRET"
,
""
),
)
OPENID_PROVIDER_URL
=
PersistentConfig
(
"OPENID_PROVIDER_URL"
,
"oauth.oidc.provider_url"
,
os
.
environ
.
get
(
"OPENID_PROVIDER_URL"
,
""
),
)
OAUTH_SCOPES
=
PersistentConfig
(
"OAUTH_SCOPES"
,
"oauth.oidc.scopes"
,
os
.
environ
.
get
(
"OAUTH_SCOPES"
,
"openid email profile"
),
)
OAUTH_PROVIDER_NAME
=
PersistentConfig
(
"OAUTH_PROVIDER_NAME"
,
"oauth.oidc.provider_name"
,
os
.
environ
.
get
(
"OAUTH_PROVIDER_NAME"
,
"SSO"
),
)
def
load_oauth_providers
():
OAUTH_PROVIDERS
.
clear
()
if
GOOGLE_CLIENT_ID
.
value
and
GOOGLE_CLIENT_SECRET
.
value
:
OAUTH_PROVIDERS
[
"google"
]
=
{
"client_id"
:
GOOGLE_CLIENT_ID
.
value
,
"client_secret"
:
GOOGLE_CLIENT_SECRET
.
value
,
"server_metadata_url"
:
"https://accounts.google.com/.well-known/openid-configuration"
,
"scope"
:
GOOGLE_OAUTH_SCOPE
.
value
,
}
if
(
MICROSOFT_CLIENT_ID
.
value
and
MICROSOFT_CLIENT_SECRET
.
value
and
MICROSOFT_CLIENT_TENANT_ID
.
value
):
OAUTH_PROVIDERS
[
"microsoft"
]
=
{
"client_id"
:
MICROSOFT_CLIENT_ID
.
value
,
"client_secret"
:
MICROSOFT_CLIENT_SECRET
.
value
,
"server_metadata_url"
:
f
"https://login.microsoftonline.com/
{
MICROSOFT_CLIENT_TENANT_ID
.
value
}
/v2.0/.well-known/openid-configuration"
,
"scope"
:
MICROSOFT_OAUTH_SCOPE
.
value
,
}
if
(
OAUTH_CLIENT_ID
.
value
and
OAUTH_CLIENT_SECRET
.
value
and
OPENID_PROVIDER_URL
.
value
):
OAUTH_PROVIDERS
[
"oidc"
]
=
{
"client_id"
:
OAUTH_CLIENT_ID
.
value
,
"client_secret"
:
OAUTH_CLIENT_SECRET
.
value
,
"server_metadata_url"
:
OPENID_PROVIDER_URL
.
value
,
"scope"
:
OAUTH_SCOPES
.
value
,
"name"
:
OAUTH_PROVIDER_NAME
.
value
,
}
load_oauth_providers
()
####################################
# Static DIR
####################################
...
...
@@ -733,6 +862,16 @@ WEBUI_SECRET_KEY = os.environ.get(
),
# DEPRECATED: remove at next major version
)
WEBUI_SESSION_COOKIE_SAME_SITE
=
os
.
environ
.
get
(
"WEBUI_SESSION_COOKIE_SAME_SITE"
,
os
.
environ
.
get
(
"WEBUI_SESSION_COOKIE_SAME_SITE"
,
"lax"
),
)
WEBUI_SESSION_COOKIE_SECURE
=
os
.
environ
.
get
(
"WEBUI_SESSION_COOKIE_SECURE"
,
os
.
environ
.
get
(
"WEBUI_SESSION_COOKIE_SECURE"
,
"false"
).
lower
()
==
"true"
,
)
if
WEBUI_AUTH
and
WEBUI_SECRET_KEY
==
""
:
raise
ValueError
(
ERROR_MESSAGES
.
ENV_VAR_NOT_FOUND
)
...
...
backend/main.py
View file @
d17dc592
import
base64
import
uuid
from
contextlib
import
asynccontextmanager
from
authlib.integrations.starlette_client
import
OAuth
from
authlib.oidc.core
import
UserInfo
from
bs4
import
BeautifulSoup
import
json
import
markdown
...
...
@@ -24,7 +29,8 @@ from fastapi.middleware.wsgi import WSGIMiddleware
from
fastapi.middleware.cors
import
CORSMiddleware
from
starlette.exceptions
import
HTTPException
as
StarletteHTTPException
from
starlette.middleware.base
import
BaseHTTPMiddleware
from
starlette.responses
import
StreamingResponse
,
Response
from
starlette.middleware.sessions
import
SessionMiddleware
from
starlette.responses
import
StreamingResponse
,
Response
,
RedirectResponse
from
apps.socket.main
import
app
as
socket_app
...
...
@@ -53,9 +59,11 @@ from apps.webui.main import (
from
pydantic
import
BaseModel
from
typing
import
List
,
Optional
,
Iterator
,
Generator
,
Union
from
apps.webui.models.auths
import
Auths
from
apps.webui.models.models
import
Models
,
ModelModel
from
apps.webui.models.tools
import
Tools
from
apps.webui.models.functions
import
Functions
from
apps.webui.models.users
import
Users
from
apps.webui.utils
import
load_toolkit_module_by_id
,
load_function_module_by_id
...
...
@@ -64,6 +72,8 @@ from utils.utils import (
get_verified_user
,
get_current_user
,
get_http_authorization_cred
,
get_password_hash
,
create_token
,
)
from
utils.task
import
(
title_generation_template
,
...
...
@@ -74,6 +84,7 @@ from utils.misc import (
get_last_user_message
,
add_or_update_system_message
,
stream_message_template
,
parse_duration
,
)
from
apps.rag.utils
import
get_rag_context
,
rag_template
...
...
@@ -106,9 +117,16 @@ from config import (
SEARCH_QUERY_PROMPT_LENGTH_THRESHOLD
,
TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE
,
SAFE_MODE
,
OAUTH_PROVIDERS
,
ENABLE_OAUTH_SIGNUP
,
OAUTH_MERGE_ACCOUNTS_BY_EMAIL
,
WEBUI_SECRET_KEY
,
WEBUI_SESSION_COOKIE_SAME_SITE
,
WEBUI_SESSION_COOKIE_SECURE
,
AppConfig
,
)
from
constants
import
ERROR_MESSAGES
from
constants
import
ERROR_MESSAGES
,
WEBHOOK_MESSAGES
from
utils.webhook
import
post_webhook
if
SAFE_MODE
:
print
(
"SAFE MODE ENABLED"
)
...
...
@@ -1725,6 +1743,12 @@ async def get_app_config():
"engine"
:
audio_app
.
state
.
config
.
STT_ENGINE
,
},
},
"oauth"
:
{
"providers"
:
{
name
:
config
.
get
(
"name"
,
name
)
for
name
,
config
in
OAUTH_PROVIDERS
.
items
()
}
},
}
...
...
@@ -1806,6 +1830,154 @@ async def get_app_latest_release_version():
)
############################
# OAuth Login & Callback
############################
oauth
=
OAuth
()
for
provider_name
,
provider_config
in
OAUTH_PROVIDERS
.
items
():
oauth
.
register
(
name
=
provider_name
,
client_id
=
provider_config
[
"client_id"
],
client_secret
=
provider_config
[
"client_secret"
],
server_metadata_url
=
provider_config
[
"server_metadata_url"
],
client_kwargs
=
{
"scope"
:
provider_config
[
"scope"
],
},
)
# SessionMiddleware is used by authlib for oauth
if
len
(
OAUTH_PROVIDERS
)
>
0
:
app
.
add_middleware
(
SessionMiddleware
,
secret_key
=
WEBUI_SECRET_KEY
,
session_cookie
=
"oui-session"
,
same_site
=
WEBUI_SESSION_COOKIE_SAME_SITE
,
https_only
=
WEBUI_SESSION_COOKIE_SECURE
,
)
@
app
.
get
(
"/oauth/{provider}/login"
)
async
def
oauth_login
(
provider
:
str
,
request
:
Request
):
if
provider
not
in
OAUTH_PROVIDERS
:
raise
HTTPException
(
404
)
redirect_uri
=
request
.
url_for
(
"oauth_callback"
,
provider
=
provider
)
return
await
oauth
.
create_client
(
provider
).
authorize_redirect
(
request
,
redirect_uri
)
# OAuth login logic is as follows:
# 1. Attempt to find a user with matching subject ID, tied to the provider
# 2. If OAUTH_MERGE_ACCOUNTS_BY_EMAIL is true, find a user with the email address provided via OAuth
# - This is considered insecure in general, as OAuth providers do not always verify email addresses
# 3. If there is no user, and ENABLE_OAUTH_SIGNUP is true, create a user
# - Email addresses are considered unique, so we fail registration if the email address is alreayd taken
@
app
.
get
(
"/oauth/{provider}/callback"
)
async
def
oauth_callback
(
provider
:
str
,
request
:
Request
,
response
:
Response
):
if
provider
not
in
OAUTH_PROVIDERS
:
raise
HTTPException
(
404
)
client
=
oauth
.
create_client
(
provider
)
try
:
token
=
await
client
.
authorize_access_token
(
request
)
except
Exception
as
e
:
log
.
warning
(
f
"OAuth callback error:
{
e
}
"
)
raise
HTTPException
(
400
,
detail
=
ERROR_MESSAGES
.
INVALID_CRED
)
user_data
:
UserInfo
=
token
[
"userinfo"
]
sub
=
user_data
.
get
(
"sub"
)
if
not
sub
:
log
.
warning
(
f
"OAuth callback failed, sub is missing:
{
user_data
}
"
)
raise
HTTPException
(
400
,
detail
=
ERROR_MESSAGES
.
INVALID_CRED
)
provider_sub
=
f
"
{
provider
}
@
{
sub
}
"
email
=
user_data
.
get
(
"email"
,
""
).
lower
()
# We currently mandate that email addresses are provided
if
not
email
:
log
.
warning
(
f
"OAuth callback failed, email is missing:
{
user_data
}
"
)
raise
HTTPException
(
400
,
detail
=
ERROR_MESSAGES
.
INVALID_CRED
)
# Check if the user exists
user
=
Users
.
get_user_by_oauth_sub
(
provider_sub
)
if
not
user
:
# If the user does not exist, check if merging is enabled
if
OAUTH_MERGE_ACCOUNTS_BY_EMAIL
.
value
:
# Check if the user exists by email
user
=
Users
.
get_user_by_email
(
email
)
if
user
:
# Update the user with the new oauth sub
Users
.
update_user_oauth_sub_by_id
(
user
.
id
,
provider_sub
)
if
not
user
:
# If the user does not exist, check if signups are enabled
if
ENABLE_OAUTH_SIGNUP
.
value
:
# Check if an existing user with the same email already exists
existing_user
=
Users
.
get_user_by_email
(
user_data
.
get
(
"email"
,
""
).
lower
())
if
existing_user
:
raise
HTTPException
(
400
,
detail
=
ERROR_MESSAGES
.
EMAIL_TAKEN
)
picture_url
=
user_data
.
get
(
"picture"
,
""
)
if
picture_url
:
# Download the profile image into a base64 string
try
:
async
with
aiohttp
.
ClientSession
()
as
session
:
async
with
session
.
get
(
picture_url
)
as
resp
:
picture
=
await
resp
.
read
()
base64_encoded_picture
=
base64
.
b64encode
(
picture
).
decode
(
"utf-8"
)
guessed_mime_type
=
mimetypes
.
guess_type
(
picture_url
)[
0
]
if
guessed_mime_type
is
None
:
# assume JPG, browsers are tolerant enough of image formats
guessed_mime_type
=
"image/jpeg"
picture_url
=
f
"data:
{
guessed_mime_type
}
;base64,
{
base64_encoded_picture
}
"
except
Exception
as
e
:
log
.
error
(
f
"Error downloading profile image '
{
picture_url
}
':
{
e
}
"
)
picture_url
=
""
if
not
picture_url
:
picture_url
=
"/user.png"
user
=
Auths
.
insert_new_auth
(
email
=
email
,
password
=
get_password_hash
(
str
(
uuid
.
uuid4
())
),
# Random password, not used
name
=
user_data
.
get
(
"name"
,
"User"
),
profile_image_url
=
picture_url
,
role
=
webui_app
.
state
.
config
.
DEFAULT_USER_ROLE
,
oauth_sub
=
provider_sub
,
)
if
webui_app
.
state
.
config
.
WEBHOOK_URL
:
post_webhook
(
webui_app
.
state
.
config
.
WEBHOOK_URL
,
WEBHOOK_MESSAGES
.
USER_SIGNUP
(
user
.
name
),
{
"action"
:
"signup"
,
"message"
:
WEBHOOK_MESSAGES
.
USER_SIGNUP
(
user
.
name
),
"user"
:
user
.
model_dump_json
(
exclude_none
=
True
),
},
)
else
:
raise
HTTPException
(
status
.
HTTP_403_FORBIDDEN
,
detail
=
ERROR_MESSAGES
.
ACCESS_PROHIBITED
)
jwt_token
=
create_token
(
data
=
{
"id"
:
user
.
id
},
expires_delta
=
parse_duration
(
webui_app
.
state
.
config
.
JWT_EXPIRES_IN
),
)
# Set the cookie token
response
.
set_cookie
(
key
=
"token"
,
value
=
token
,
httponly
=
True
,
# Ensures the cookie is not accessible via JavaScript
)
# Redirect back to the frontend with the JWT token
redirect_url
=
f
"
{
request
.
base_url
}
auth#token=
{
jwt_token
}
"
return
RedirectResponse
(
url
=
redirect_url
)
@
app
.
get
(
"/manifest.json"
)
async
def
get_manifest_json
():
return
{
...
...
backend/requirements.txt
View file @
d17dc592
...
...
@@ -58,6 +58,7 @@ rank-bm25==0.2.2
faster-whisper==1.0.2
PyJWT[crypto]==2.8.0
authlib==1.3.0
black==24.4.2
langfuse==2.33.0
...
...
pyproject.toml
View file @
d17dc592
...
...
@@ -59,6 +59,7 @@ dependencies = [
"faster-whisper==1.0.2"
,
"PyJWT[crypto]==2.8.0"
,
"authlib==1.3.0"
,
"black==24.4.2"
,
"langfuse==2.33.0"
,
...
...
requirements-dev.lock
View file @
d17dc592
...
...
@@ -31,6 +31,8 @@ asgiref==3.8.1
# via opentelemetry-instrumentation-asgi
attrs==23.2.0
# via aiohttp
authlib==1.3.0
# via open-webui
av==11.0.0
# via faster-whisper
backoff==2.2.1
...
...
@@ -93,6 +95,7 @@ coloredlogs==15.0.1
compressed-rtf==1.0.6
# via extract-msg
cryptography==42.0.7
# via authlib
# via msoffcrypto-tool
# via pyjwt
ctranslate2==4.2.1
...
...
@@ -395,6 +398,7 @@ pandas==2.2.2
# via open-webui
passlib==1.7.4
# via open-webui
# via passlib
pathspec==0.12.1
# via black
pcodedmp==1.2.6
...
...
@@ -453,6 +457,7 @@ pygments==2.18.0
# via rich
pyjwt==2.8.0
# via open-webui
# via pyjwt
pymysql==1.1.0
# via open-webui
pypandoc==1.13
...
...
@@ -554,9 +559,6 @@ scipy==1.13.0
# via sentence-transformers
sentence-transformers==2.7.0
# via open-webui
setuptools==69.5.1
# via ctranslate2
# via opentelemetry-instrumentation
shapely==2.0.4
# via rapidocr-onnxruntime
shellingham==1.5.4
...
...
@@ -651,6 +653,7 @@ uvicorn==0.22.0
# via chromadb
# via fastapi
# via open-webui
# via uvicorn
uvloop==0.19.0
# via uvicorn
validators==0.28.1
...
...
@@ -678,3 +681,6 @@ youtube-transcript-api==0.6.2
# via open-webui
zipp==3.18.1
# via importlib-metadata
setuptools==69.5.1
# via ctranslate2
# via opentelemetry-instrumentation
requirements.lock
View file @
d17dc592
...
...
@@ -31,6 +31,8 @@ asgiref==3.8.1
# via opentelemetry-instrumentation-asgi
attrs==23.2.0
# via aiohttp
authlib==1.3.0
# via open-webui
av==11.0.0
# via faster-whisper
backoff==2.2.1
...
...
@@ -93,6 +95,7 @@ coloredlogs==15.0.1
compressed-rtf==1.0.6
# via extract-msg
cryptography==42.0.7
# via authlib
# via msoffcrypto-tool
# via pyjwt
ctranslate2==4.2.1
...
...
@@ -395,6 +398,7 @@ pandas==2.2.2
# via open-webui
passlib==1.7.4
# via open-webui
# via passlib
pathspec==0.12.1
# via black
pcodedmp==1.2.6
...
...
@@ -453,6 +457,7 @@ pygments==2.18.0
# via rich
pyjwt==2.8.0
# via open-webui
# via pyjwt
pymysql==1.1.0
# via open-webui
pypandoc==1.13
...
...
@@ -554,9 +559,6 @@ scipy==1.13.0
# via sentence-transformers
sentence-transformers==2.7.0
# via open-webui
setuptools==69.5.1
# via ctranslate2
# via opentelemetry-instrumentation
shapely==2.0.4
# via rapidocr-onnxruntime
shellingham==1.5.4
...
...
@@ -651,6 +653,7 @@ uvicorn==0.22.0
# via chromadb
# via fastapi
# via open-webui
# via uvicorn
uvloop==0.19.0
# via uvicorn
validators==0.28.1
...
...
@@ -678,3 +681,6 @@ youtube-transcript-api==0.6.2
# via open-webui
zipp==3.18.1
# via importlib-metadata
setuptools==69.5.1
# via ctranslate2
# via opentelemetry-instrumentation
src/lib/i18n/locales/ar-BH/translation.json
View file @
d17dc592
...
...
@@ -126,6 +126,7 @@
"Content"
:
"الاتصال"
,
"Context Length"
:
"طول السياق"
,
"Continue Response"
:
"متابعة الرد"
,
"Continue with {{provider}}"
:
""
,
"Copied shared chat URL to clipboard!"
:
"تم نسخ عنوان URL للدردشة المشتركة إلى الحافظة"
,
"Copy"
:
"نسخ"
,
"Copy last code block"
:
"انسخ كتلة التعليمات البرمجية الأخيرة"
,
...
...
@@ -378,6 +379,7 @@
"Notifications"
:
"إشعارات"
,
"November"
:
"نوفمبر"
,
"num_thread (Ollama)"
:
"num_thread (أولاما)"
,
"OAuth ID"
:
""
,
"October"
:
"اكتوبر"
,
"Off"
:
"أغلاق"
,
"Okay, Let's Go!"
:
"حسنا دعنا نذهب!"
,
...
...
src/lib/i18n/locales/bg-BG/translation.json
View file @
d17dc592
...
...
@@ -126,6 +126,7 @@
"Content"
:
"Съдържание"
,
"Context Length"
:
"Дължина на Контекста"
,
"Continue Response"
:
"Продължи отговора"
,
"Continue with {{provider}}"
:
""
,
"Copied shared chat URL to clipboard!"
:
"Копирана е връзката за чат!"
,
"Copy"
:
"Копирай"
,
"Copy last code block"
:
"Копиране на последен код блок"
,
...
...
@@ -378,6 +379,7 @@
"Notifications"
:
"Десктоп Известия"
,
"November"
:
"Ноември"
,
"num_thread (Ollama)"
:
"num_thread (Ollama)"
,
"OAuth ID"
:
""
,
"October"
:
"Октомври"
,
"Off"
:
"Изкл."
,
"Okay, Let's Go!"
:
"ОК, Нека започваме!"
,
...
...
src/lib/i18n/locales/bn-BD/translation.json
View file @
d17dc592
...
...
@@ -126,6 +126,7 @@
"Content"
:
"বিষয়বস্তু"
,
"Context Length"
:
"কনটেক্সটের দৈর্ঘ্য"
,
"Continue Response"
:
"যাচাই করুন"
,
"Continue with {{provider}}"
:
""
,
"Copied shared chat URL to clipboard!"
:
"শেয়ারকৃত কথা-ব্যবহারের URL ক্লিপবোর্ডে কপি করা হয়েছে!"
,
"Copy"
:
"অনুলিপি"
,
"Copy last code block"
:
"সর্বশেষ কোড ব্লক কপি করুন"
,
...
...
@@ -378,6 +379,7 @@
"Notifications"
:
"নোটিফিকেশনসমূহ"
,
"November"
:
"নভেম্বর"
,
"num_thread (Ollama)"
:
"num_thread (ওলামা)"
,
"OAuth ID"
:
""
,
"October"
:
"অক্টোবর"
,
"Off"
:
"বন্ধ"
,
"Okay, Let's Go!"
:
"ঠিক আছে, চলুন যাই!"
,
...
...
src/lib/i18n/locales/ca-ES/translation.json
View file @
d17dc592
...
...
@@ -126,6 +126,7 @@
"Content"
:
"Contingut"
,
"Context Length"
:
"Mida del context"
,
"Continue Response"
:
"Continuar la resposta"
,
"Continue with {{provider}}"
:
""
,
"Copied shared chat URL to clipboard!"
:
"S'ha copiat l'URL compartida al porta-retalls!"
,
"Copy"
:
"Copiar"
,
"Copy last code block"
:
"Copiar l'últim bloc de codi"
,
...
...
@@ -378,6 +379,7 @@
"Notifications"
:
"Notificacions"
,
"November"
:
"Novembre"
,
"num_thread (Ollama)"
:
"num_thread (Ollama)"
,
"OAuth ID"
:
""
,
"October"
:
"Octubre"
,
"Off"
:
"Desactivat"
,
"Okay, Let's Go!"
:
"D'acord, som-hi!"
,
...
...
src/lib/i18n/locales/ceb-PH/translation.json
View file @
d17dc592
...
...
@@ -126,6 +126,7 @@
"Content"
:
"Kontento"
,
"Context Length"
:
"Ang gitas-on sa konteksto"
,
"Continue Response"
:
""
,
"Continue with {{provider}}"
:
""
,
"Copied shared chat URL to clipboard!"
:
""
,
"Copy"
:
""
,
"Copy last code block"
:
"Kopyaha ang katapusang bloke sa code"
,
...
...
@@ -378,6 +379,7 @@
"Notifications"
:
"Mga pahibalo sa desktop"
,
"November"
:
""
,
"num_thread (Ollama)"
:
""
,
"OAuth ID"
:
""
,
"October"
:
""
,
"Off"
:
"Napuo"
,
"Okay, Let's Go!"
:
"Okay, lakaw na!"
,
...
...
src/lib/i18n/locales/de-DE/translation.json
View file @
d17dc592
...
...
@@ -126,6 +126,7 @@
"Content"
:
"Info"
,
"Context Length"
:
"Context Length"
,
"Continue Response"
:
"Antwort fortsetzen"
,
"Continue with {{provider}}"
:
""
,
"Copied shared chat URL to clipboard!"
:
"Geteilte Chat-URL in die Zwischenablage kopiert!"
,
"Copy"
:
"Kopieren"
,
"Copy last code block"
:
"Letzten Codeblock kopieren"
,
...
...
@@ -378,6 +379,7 @@
"Notifications"
:
"Desktop-Benachrichtigungen"
,
"November"
:
"November"
,
"num_thread (Ollama)"
:
"num_thread (Ollama)"
,
"OAuth ID"
:
""
,
"October"
:
"Oktober"
,
"Off"
:
"Aus"
,
"Okay, Let's Go!"
:
"Okay, los geht's!"
,
...
...
src/lib/i18n/locales/dg-DG/translation.json
View file @
d17dc592
...
...
@@ -126,6 +126,7 @@
"Content"
:
"Content"
,
"Context Length"
:
"Context Length"
,
"Continue Response"
:
""
,
"Continue with {{provider}}"
:
""
,
"Copied shared chat URL to clipboard!"
:
""
,
"Copy"
:
""
,
"Copy last code block"
:
"Copy last code block"
,
...
...
@@ -378,6 +379,7 @@
"Notifications"
:
"Notifications"
,
"November"
:
""
,
"num_thread (Ollama)"
:
""
,
"OAuth ID"
:
""
,
"October"
:
""
,
"Off"
:
"Off"
,
"Okay, Let's Go!"
:
"Okay, Let's Go!"
,
...
...
src/lib/i18n/locales/en-GB/translation.json
View file @
d17dc592
...
...
@@ -126,6 +126,7 @@
"Content"
:
""
,
"Context Length"
:
""
,
"Continue Response"
:
""
,
"Continue with {{provider}}"
:
""
,
"Copied shared chat URL to clipboard!"
:
""
,
"Copy"
:
""
,
"Copy last code block"
:
""
,
...
...
@@ -378,6 +379,7 @@
"Notifications"
:
""
,
"November"
:
""
,
"num_thread (Ollama)"
:
""
,
"OAuth ID"
:
""
,
"October"
:
""
,
"Off"
:
""
,
"Okay, Let's Go!"
:
""
,
...
...
src/lib/i18n/locales/en-US/translation.json
View file @
d17dc592
...
...
@@ -126,6 +126,7 @@
"Content"
:
""
,
"Context Length"
:
""
,
"Continue Response"
:
""
,
"Continue with {{provider}}"
:
""
,
"Copied shared chat URL to clipboard!"
:
""
,
"Copy"
:
""
,
"Copy last code block"
:
""
,
...
...
@@ -378,6 +379,7 @@
"Notifications"
:
""
,
"November"
:
""
,
"num_thread (Ollama)"
:
""
,
"OAuth ID"
:
""
,
"October"
:
""
,
"Off"
:
""
,
"Okay, Let's Go!"
:
""
,
...
...
Prev
1
2
3
Next
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