mirror of
https://github.com/open-webui/open-webui
synced 2025-05-17 12:03:41 +00:00
Merge pull request #6743 from morgan55555/dev
feat: LDAP User management
This commit is contained in:
commit
2777620527
@ -50,6 +50,18 @@ from open_webui.config import (
|
|||||||
WEBHOOK_URL,
|
WEBHOOK_URL,
|
||||||
WEBUI_AUTH,
|
WEBUI_AUTH,
|
||||||
WEBUI_BANNERS,
|
WEBUI_BANNERS,
|
||||||
|
ENABLE_LDAP,
|
||||||
|
LDAP_SERVER_LABEL,
|
||||||
|
LDAP_SERVER_HOST,
|
||||||
|
LDAP_SERVER_PORT,
|
||||||
|
LDAP_ATTRIBUTE_FOR_USERNAME,
|
||||||
|
LDAP_SEARCH_FILTERS,
|
||||||
|
LDAP_SEARCH_BASE,
|
||||||
|
LDAP_APP_DN,
|
||||||
|
LDAP_APP_PASSWORD,
|
||||||
|
LDAP_USE_TLS,
|
||||||
|
LDAP_CA_CERT_FILE,
|
||||||
|
LDAP_CIPHERS,
|
||||||
AppConfig,
|
AppConfig,
|
||||||
)
|
)
|
||||||
from open_webui.env import (
|
from open_webui.env import (
|
||||||
@ -111,6 +123,19 @@ app.state.config.OAUTH_ROLES_CLAIM = OAUTH_ROLES_CLAIM
|
|||||||
app.state.config.OAUTH_ALLOWED_ROLES = OAUTH_ALLOWED_ROLES
|
app.state.config.OAUTH_ALLOWED_ROLES = OAUTH_ALLOWED_ROLES
|
||||||
app.state.config.OAUTH_ADMIN_ROLES = OAUTH_ADMIN_ROLES
|
app.state.config.OAUTH_ADMIN_ROLES = OAUTH_ADMIN_ROLES
|
||||||
|
|
||||||
|
app.state.config.ENABLE_LDAP = ENABLE_LDAP
|
||||||
|
app.state.config.LDAP_SERVER_LABEL = LDAP_SERVER_LABEL
|
||||||
|
app.state.config.LDAP_SERVER_HOST = LDAP_SERVER_HOST
|
||||||
|
app.state.config.LDAP_SERVER_PORT = LDAP_SERVER_PORT
|
||||||
|
app.state.config.LDAP_ATTRIBUTE_FOR_USERNAME = LDAP_ATTRIBUTE_FOR_USERNAME
|
||||||
|
app.state.config.LDAP_APP_DN = LDAP_APP_DN
|
||||||
|
app.state.config.LDAP_APP_PASSWORD = LDAP_APP_PASSWORD
|
||||||
|
app.state.config.LDAP_SEARCH_BASE = LDAP_SEARCH_BASE
|
||||||
|
app.state.config.LDAP_SEARCH_FILTERS = LDAP_SEARCH_FILTERS
|
||||||
|
app.state.config.LDAP_USE_TLS = LDAP_USE_TLS
|
||||||
|
app.state.config.LDAP_CA_CERT_FILE = LDAP_CA_CERT_FILE
|
||||||
|
app.state.config.LDAP_CIPHERS = LDAP_CIPHERS
|
||||||
|
|
||||||
app.state.MODELS = {}
|
app.state.MODELS = {}
|
||||||
app.state.TOOLS = {}
|
app.state.TOOLS = {}
|
||||||
app.state.FUNCTIONS = {}
|
app.state.FUNCTIONS = {}
|
||||||
|
@ -64,6 +64,11 @@ class SigninForm(BaseModel):
|
|||||||
password: str
|
password: str
|
||||||
|
|
||||||
|
|
||||||
|
class LdapForm(BaseModel):
|
||||||
|
user: str
|
||||||
|
password: str
|
||||||
|
|
||||||
|
|
||||||
class ProfileImageUrlForm(BaseModel):
|
class ProfileImageUrlForm(BaseModel):
|
||||||
profile_image_url: str
|
profile_image_url: str
|
||||||
|
|
||||||
|
@ -2,12 +2,14 @@ import re
|
|||||||
import uuid
|
import uuid
|
||||||
import time
|
import time
|
||||||
import datetime
|
import datetime
|
||||||
|
import logging
|
||||||
|
|
||||||
from open_webui.apps.webui.models.auths import (
|
from open_webui.apps.webui.models.auths import (
|
||||||
AddUserForm,
|
AddUserForm,
|
||||||
ApiKey,
|
ApiKey,
|
||||||
Auths,
|
Auths,
|
||||||
Token,
|
Token,
|
||||||
|
LdapForm,
|
||||||
SigninForm,
|
SigninForm,
|
||||||
SigninResponse,
|
SigninResponse,
|
||||||
SignupForm,
|
SignupForm,
|
||||||
@ -23,6 +25,7 @@ from open_webui.env import (
|
|||||||
WEBUI_AUTH_TRUSTED_NAME_HEADER,
|
WEBUI_AUTH_TRUSTED_NAME_HEADER,
|
||||||
WEBUI_SESSION_COOKIE_SAME_SITE,
|
WEBUI_SESSION_COOKIE_SAME_SITE,
|
||||||
WEBUI_SESSION_COOKIE_SECURE,
|
WEBUI_SESSION_COOKIE_SECURE,
|
||||||
|
SRC_LOG_LEVELS,
|
||||||
)
|
)
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||||
from fastapi.responses import Response
|
from fastapi.responses import Response
|
||||||
@ -37,10 +40,16 @@ from open_webui.utils.utils import (
|
|||||||
get_password_hash,
|
get_password_hash,
|
||||||
)
|
)
|
||||||
from open_webui.utils.webhook import post_webhook
|
from open_webui.utils.webhook import post_webhook
|
||||||
from typing import Optional
|
from typing import Optional, List
|
||||||
|
|
||||||
|
from ldap3 import Server, Connection, ALL, Tls
|
||||||
|
from ssl import CERT_REQUIRED, PROTOCOL_TLS
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
log.setLevel(SRC_LOG_LEVELS["MAIN"])
|
||||||
|
|
||||||
############################
|
############################
|
||||||
# GetSessionUser
|
# GetSessionUser
|
||||||
############################
|
############################
|
||||||
@ -137,6 +146,110 @@ async def update_password(
|
|||||||
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
|
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
|
||||||
|
|
||||||
|
|
||||||
|
############################
|
||||||
|
# LDAP Authentication
|
||||||
|
############################
|
||||||
|
@router.post("/ldap", response_model=SigninResponse)
|
||||||
|
async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
|
||||||
|
ENABLE_LDAP = request.app.state.config.ENABLE_LDAP
|
||||||
|
LDAP_SERVER_LABEL = request.app.state.config.LDAP_SERVER_LABEL
|
||||||
|
LDAP_SERVER_HOST = request.app.state.config.LDAP_SERVER_HOST
|
||||||
|
LDAP_SERVER_PORT = request.app.state.config.LDAP_SERVER_PORT
|
||||||
|
LDAP_ATTRIBUTE_FOR_USERNAME = request.app.state.config.LDAP_ATTRIBUTE_FOR_USERNAME
|
||||||
|
LDAP_SEARCH_BASE = request.app.state.config.LDAP_SEARCH_BASE
|
||||||
|
LDAP_SEARCH_FILTERS = request.app.state.config.LDAP_SEARCH_FILTERS
|
||||||
|
LDAP_APP_DN = request.app.state.config.LDAP_APP_DN
|
||||||
|
LDAP_APP_PASSWORD = request.app.state.config.LDAP_APP_PASSWORD
|
||||||
|
LDAP_USE_TLS = request.app.state.config.LDAP_USE_TLS
|
||||||
|
LDAP_CA_CERT_FILE = request.app.state.config.LDAP_CA_CERT_FILE
|
||||||
|
LDAP_CIPHERS = request.app.state.config.LDAP_CIPHERS if request.app.state.config.LDAP_CIPHERS else 'ALL'
|
||||||
|
|
||||||
|
if not ENABLE_LDAP:
|
||||||
|
raise HTTPException(400, detail="LDAP authentication is not enabled")
|
||||||
|
|
||||||
|
try:
|
||||||
|
tls = Tls(validate=CERT_REQUIRED, version=PROTOCOL_TLS, ca_certs_file=LDAP_CA_CERT_FILE, ciphers=LDAP_CIPHERS)
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"An error occurred on TLS: {str(e)}")
|
||||||
|
raise HTTPException(400, detail=str(e))
|
||||||
|
|
||||||
|
try:
|
||||||
|
server = Server(host=LDAP_SERVER_HOST, port=LDAP_SERVER_PORT, get_info=ALL, use_ssl=LDAP_USE_TLS, tls=tls)
|
||||||
|
connection_app = Connection(server, LDAP_APP_DN, LDAP_APP_PASSWORD, auto_bind='NONE', authentication='SIMPLE')
|
||||||
|
if not connection_app.bind():
|
||||||
|
raise HTTPException(400, detail="Application account bind failed")
|
||||||
|
|
||||||
|
search_success = connection_app.search(
|
||||||
|
search_base=LDAP_SEARCH_BASE,
|
||||||
|
search_filter=f'(&({LDAP_ATTRIBUTE_FOR_USERNAME}={form_data.user.lower()}){LDAP_SEARCH_FILTERS})',
|
||||||
|
attributes=[f'{LDAP_ATTRIBUTE_FOR_USERNAME}', 'mail', 'cn']
|
||||||
|
)
|
||||||
|
|
||||||
|
if not search_success:
|
||||||
|
raise HTTPException(400, detail="User not found in the LDAP server")
|
||||||
|
|
||||||
|
entry = connection_app.entries[0]
|
||||||
|
username = str(entry[f'{LDAP_ATTRIBUTE_FOR_USERNAME}']).lower()
|
||||||
|
mail = str(entry['mail'])
|
||||||
|
cn = str(entry['cn'])
|
||||||
|
user_dn = entry.entry_dn
|
||||||
|
|
||||||
|
if username == form_data.user.lower():
|
||||||
|
connection_user = Connection(server, user_dn, form_data.password, auto_bind='NONE', authentication='SIMPLE')
|
||||||
|
if not connection_user.bind():
|
||||||
|
raise HTTPException(400, f"Authentication failed for {form_data.user}")
|
||||||
|
|
||||||
|
user = Users.get_user_by_email(mail)
|
||||||
|
if not user:
|
||||||
|
|
||||||
|
try:
|
||||||
|
hashed = get_password_hash(form_data.password)
|
||||||
|
user = Auths.insert_new_auth(
|
||||||
|
mail,
|
||||||
|
hashed,
|
||||||
|
cn
|
||||||
|
)
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(500, detail=ERROR_MESSAGES.CREATE_USER_ERROR)
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as err:
|
||||||
|
raise HTTPException(500, detail=ERROR_MESSAGES.DEFAULT(err))
|
||||||
|
|
||||||
|
user = Auths.authenticate_user(mail, password=str(form_data.password))
|
||||||
|
|
||||||
|
if user:
|
||||||
|
token = create_token(
|
||||||
|
data={"id": user.id},
|
||||||
|
expires_delta=parse_duration(request.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
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"token": token,
|
||||||
|
"token_type": "Bearer",
|
||||||
|
"id": user.id,
|
||||||
|
"email": user.email,
|
||||||
|
"name": user.name,
|
||||||
|
"role": user.role,
|
||||||
|
"profile_image_url": user.profile_image_url,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
|
||||||
|
else:
|
||||||
|
raise HTTPException(400, f"User {form_data.user} does not match the record. Search result: {str(entry[f'{LDAP_ATTRIBUTE_FOR_USERNAME}'])}")
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(400, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
############################
|
############################
|
||||||
# SignIn
|
# SignIn
|
||||||
############################
|
############################
|
||||||
@ -465,6 +578,89 @@ async def update_admin_config(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class LdapServerConfig(BaseModel):
|
||||||
|
label: str
|
||||||
|
host: str
|
||||||
|
port: Optional[int] = None
|
||||||
|
attribute_for_username: str = 'uid'
|
||||||
|
app_dn: str
|
||||||
|
app_dn_password: str
|
||||||
|
search_base: str
|
||||||
|
search_filters: str = ''
|
||||||
|
use_tls: bool = True
|
||||||
|
certificate_path: Optional[str] = None
|
||||||
|
ciphers: Optional[str] = 'ALL'
|
||||||
|
|
||||||
|
@router.get("/admin/config/ldap/server", response_model=LdapServerConfig)
|
||||||
|
async def get_ldap_server(
|
||||||
|
request: Request, user=Depends(get_admin_user)
|
||||||
|
):
|
||||||
|
return {
|
||||||
|
"label": request.app.state.config.LDAP_SERVER_LABEL,
|
||||||
|
"host": request.app.state.config.LDAP_SERVER_HOST,
|
||||||
|
"port": request.app.state.config.LDAP_SERVER_PORT,
|
||||||
|
"attribute_for_username": request.app.state.config.LDAP_ATTRIBUTE_FOR_USERNAME,
|
||||||
|
"app_dn": request.app.state.config.LDAP_APP_DN,
|
||||||
|
"app_dn_password": request.app.state.config.LDAP_APP_PASSWORD,
|
||||||
|
"search_base": request.app.state.config.LDAP_SEARCH_BASE,
|
||||||
|
"search_filters": request.app.state.config.LDAP_SEARCH_FILTERS,
|
||||||
|
"use_tls": request.app.state.config.LDAP_USE_TLS,
|
||||||
|
"certificate_path": request.app.state.config.LDAP_CA_CERT_FILE,
|
||||||
|
"ciphers": request.app.state.config.LDAP_CIPHERS
|
||||||
|
}
|
||||||
|
|
||||||
|
@router.post("/admin/config/ldap/server")
|
||||||
|
async def update_ldap_server(
|
||||||
|
request: Request, form_data: LdapServerConfig, user=Depends(get_admin_user)
|
||||||
|
):
|
||||||
|
required_fields = ['label', 'host', 'attribute_for_username', 'app_dn', 'app_dn_password', 'search_base']
|
||||||
|
for key in required_fields:
|
||||||
|
value = getattr(form_data, key)
|
||||||
|
if not value:
|
||||||
|
raise HTTPException(400, detail=f"Required field {key} is empty")
|
||||||
|
|
||||||
|
if form_data.use_tls and not form_data.certificate_path:
|
||||||
|
raise HTTPException(400, detail="TLS is enabled but certificate file path is missing")
|
||||||
|
|
||||||
|
request.app.state.config.LDAP_SERVER_LABEL = form_data.label
|
||||||
|
request.app.state.config.LDAP_SERVER_HOST = form_data.host
|
||||||
|
request.app.state.config.LDAP_SERVER_PORT = form_data.port
|
||||||
|
request.app.state.config.LDAP_ATTRIBUTE_FOR_USERNAME = form_data.attribute_for_username
|
||||||
|
request.app.state.config.LDAP_APP_DN = form_data.app_dn
|
||||||
|
request.app.state.config.LDAP_APP_PASSWORD = form_data.app_dn_password
|
||||||
|
request.app.state.config.LDAP_SEARCH_BASE = form_data.search_base
|
||||||
|
request.app.state.config.LDAP_SEARCH_FILTERS = form_data.search_filters
|
||||||
|
request.app.state.config.LDAP_USE_TLS = form_data.use_tls
|
||||||
|
request.app.state.config.LDAP_CA_CERT_FILE = form_data.certificate_path
|
||||||
|
request.app.state.config.LDAP_CIPHERS = form_data.ciphers
|
||||||
|
|
||||||
|
return {
|
||||||
|
"label": request.app.state.config.LDAP_SERVER_LABEL,
|
||||||
|
"host": request.app.state.config.LDAP_SERVER_HOST,
|
||||||
|
"port": request.app.state.config.LDAP_SERVER_PORT,
|
||||||
|
"attribute_for_username": request.app.state.config.LDAP_ATTRIBUTE_FOR_USERNAME,
|
||||||
|
"app_dn": request.app.state.config.LDAP_APP_DN,
|
||||||
|
"app_dn_password": request.app.state.config.LDAP_APP_PASSWORD,
|
||||||
|
"search_base": request.app.state.config.LDAP_SEARCH_BASE,
|
||||||
|
"search_filters": request.app.state.config.LDAP_SEARCH_FILTERS,
|
||||||
|
"use_tls": request.app.state.config.LDAP_USE_TLS,
|
||||||
|
"certificate_path": request.app.state.config.LDAP_CA_CERT_FILE,
|
||||||
|
"ciphers": request.app.state.config.LDAP_CIPHERS
|
||||||
|
}
|
||||||
|
|
||||||
|
@router.get("/admin/config/ldap")
|
||||||
|
async def get_ldap_config(request: Request, user=Depends(get_admin_user)):
|
||||||
|
return {"ENABLE_LDAP": request.app.state.config.ENABLE_LDAP}
|
||||||
|
|
||||||
|
class LdapConfigForm(BaseModel):
|
||||||
|
enable_ldap: Optional[bool] = None
|
||||||
|
|
||||||
|
@router.post("/admin/config/ldap")
|
||||||
|
async def update_ldap_config(request: Request, form_data: LdapConfigForm, user=Depends(get_admin_user)):
|
||||||
|
request.app.state.config.ENABLE_LDAP = form_data.enable_ldap
|
||||||
|
return {"ENABLE_LDAP": request.app.state.config.ENABLE_LDAP}
|
||||||
|
|
||||||
|
|
||||||
############################
|
############################
|
||||||
# API Key
|
# API Key
|
||||||
############################
|
############################
|
||||||
|
@ -1578,3 +1578,80 @@ AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT = PersistentConfig(
|
|||||||
"AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT", "audio-24khz-160kbitrate-mono-mp3"
|
"AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT", "audio-24khz-160kbitrate-mono-mp3"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
####################################
|
||||||
|
# LDAP
|
||||||
|
####################################
|
||||||
|
|
||||||
|
ENABLE_LDAP = PersistentConfig(
|
||||||
|
"ENABLE_LDAP",
|
||||||
|
"ldap.enable",
|
||||||
|
os.environ.get("ENABLE_LDAP", "True").lower() == "true",
|
||||||
|
)
|
||||||
|
|
||||||
|
LDAP_SERVER_LABEL = PersistentConfig(
|
||||||
|
"LDAP_SERVER_LABEL",
|
||||||
|
"ldap.server.label",
|
||||||
|
os.environ.get("LDAP_SERVER_LABEL", "LDAP Server"),
|
||||||
|
)
|
||||||
|
|
||||||
|
LDAP_SERVER_HOST = PersistentConfig(
|
||||||
|
"LDAP_SERVER_HOST",
|
||||||
|
"ldap.server.host",
|
||||||
|
os.environ.get("LDAP_SERVER_HOST", "localhost")
|
||||||
|
)
|
||||||
|
|
||||||
|
LDAP_SERVER_PORT = PersistentConfig(
|
||||||
|
"LDAP_SERVER_PORT",
|
||||||
|
"ldap.server.port",
|
||||||
|
int(os.environ.get("LDAP_SERVER_PORT", "389"))
|
||||||
|
)
|
||||||
|
|
||||||
|
LDAP_ATTRIBUTE_FOR_USERNAME = PersistentConfig(
|
||||||
|
"LDAP_ATTRIBUTE_FOR_USERNAME",
|
||||||
|
"ldap.server.attribute_for_username",
|
||||||
|
os.environ.get("LDAP_ATTRIBUTE_FOR_USERNAME", "uid")
|
||||||
|
)
|
||||||
|
|
||||||
|
LDAP_APP_DN = PersistentConfig(
|
||||||
|
"LDAP_APP_DN",
|
||||||
|
"ldap.server.app_dn",
|
||||||
|
os.environ.get("LDAP_APP_DN", "")
|
||||||
|
)
|
||||||
|
|
||||||
|
LDAP_APP_PASSWORD = PersistentConfig(
|
||||||
|
"LDAP_APP_PASSWORD",
|
||||||
|
"ldap.server.app_password",
|
||||||
|
os.environ.get("LDAP_APP_PASSWORD", "")
|
||||||
|
)
|
||||||
|
|
||||||
|
LDAP_SEARCH_BASE = PersistentConfig(
|
||||||
|
"LDAP_SEARCH_BASE",
|
||||||
|
"ldap.server.users_dn",
|
||||||
|
os.environ.get("LDAP_SEARCH_BASE", "")
|
||||||
|
)
|
||||||
|
|
||||||
|
LDAP_SEARCH_FILTERS = PersistentConfig(
|
||||||
|
"LDAP_SEARCH_FILTER",
|
||||||
|
"ldap.server.search_filter",
|
||||||
|
os.environ.get("LDAP_SEARCH_FILTER", "")
|
||||||
|
)
|
||||||
|
|
||||||
|
LDAP_USE_TLS = PersistentConfig(
|
||||||
|
"LDAP_USE_TLS",
|
||||||
|
"ldap.server.use_tls",
|
||||||
|
os.environ.get("LDAP_USE_TLS", "True").lower() == "true"
|
||||||
|
)
|
||||||
|
|
||||||
|
LDAP_CA_CERT_FILE = PersistentConfig(
|
||||||
|
"LDAP_CA_CERT_FILE",
|
||||||
|
"ldap.server.ca_cert_file",
|
||||||
|
os.environ.get("LDAP_CA_CERT_FILE", "")
|
||||||
|
)
|
||||||
|
|
||||||
|
LDAP_CIPHERS = PersistentConfig(
|
||||||
|
"LDAP_CIPHERS",
|
||||||
|
"ldap.server.ciphers",
|
||||||
|
os.environ.get("LDAP_CIPHERS", "ALL")
|
||||||
|
)
|
||||||
|
@ -2237,6 +2237,7 @@ async def get_app_config(request: Request):
|
|||||||
"auth_trusted_header": bool(webui_app.state.AUTH_TRUSTED_EMAIL_HEADER),
|
"auth_trusted_header": bool(webui_app.state.AUTH_TRUSTED_EMAIL_HEADER),
|
||||||
"enable_signup": webui_app.state.config.ENABLE_SIGNUP,
|
"enable_signup": webui_app.state.config.ENABLE_SIGNUP,
|
||||||
"enable_login_form": webui_app.state.config.ENABLE_LOGIN_FORM,
|
"enable_login_form": webui_app.state.config.ENABLE_LOGIN_FORM,
|
||||||
|
"enable_ldap_form": webui_app.state.config.ENABLE_LDAP,
|
||||||
**(
|
**(
|
||||||
{
|
{
|
||||||
"enable_web_search": retrieval_app.state.config.ENABLE_RAG_WEB_SEARCH,
|
"enable_web_search": retrieval_app.state.config.ENABLE_RAG_WEB_SEARCH,
|
||||||
|
@ -93,3 +93,6 @@ pytest~=8.3.2
|
|||||||
pytest-docker~=3.1.1
|
pytest-docker~=3.1.1
|
||||||
|
|
||||||
googleapis-common-protos==1.63.2
|
googleapis-common-protos==1.63.2
|
||||||
|
|
||||||
|
## LDAP
|
||||||
|
ldap3==2.9.1
|
||||||
|
@ -110,6 +110,150 @@ export const getSessionUser = async (token: string) => {
|
|||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ldapUserSignIn = async (user: string, password: string) => {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
const res = await fetch(`${WEBUI_API_BASE_URL}/auths/ldap`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
body: JSON.stringify({
|
||||||
|
user: user,
|
||||||
|
password: password
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.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 getLdapConfig = async (token: string = '') => {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
const res = await fetch(`${WEBUI_API_BASE_URL}/auths/admin/config/ldap`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...(token && { 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 updateLdapConfig = async (token: string = '', enable_ldap: boolean) => {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
const res = await fetch(`${WEBUI_API_BASE_URL}/auths/admin/config/ldap`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...(token && { authorization: `Bearer ${token}` })
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
enable_ldap: enable_ldap
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.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 getLdapServer = async (token: string = '') => {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
const res = await fetch(`${WEBUI_API_BASE_URL}/auths/admin/config/ldap/server`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...(token && { 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 updateLdapServer = async (token: string = '', body: object) => {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
const res = await fetch(`${WEBUI_API_BASE_URL}/auths/admin/config/ldap/server`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...(token && { authorization: `Bearer ${token}` })
|
||||||
|
},
|
||||||
|
body: JSON.stringify(body)
|
||||||
|
})
|
||||||
|
.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 userSignIn = async (email: string, password: string) => {
|
export const userSignIn = async (email: string, password: string) => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
|
@ -20,6 +20,12 @@
|
|||||||
updateOpenAIKeys,
|
updateOpenAIKeys,
|
||||||
updateOpenAIUrls
|
updateOpenAIUrls
|
||||||
} from '$lib/apis/openai';
|
} from '$lib/apis/openai';
|
||||||
|
import {
|
||||||
|
getLdapConfig,
|
||||||
|
updateLdapConfig,
|
||||||
|
getLdapServer,
|
||||||
|
updateLdapServer,
|
||||||
|
} from '$lib/apis/auths';
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
import Switch from '$lib/components/common/Switch.svelte';
|
import Switch from '$lib/components/common/Switch.svelte';
|
||||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||||
@ -45,6 +51,23 @@
|
|||||||
let ENABLE_OPENAI_API = null;
|
let ENABLE_OPENAI_API = null;
|
||||||
let ENABLE_OLLAMA_API = null;
|
let ENABLE_OLLAMA_API = null;
|
||||||
|
|
||||||
|
|
||||||
|
// LDAP
|
||||||
|
let ENABLE_LDAP = false;
|
||||||
|
let LDAP_SERVER = {
|
||||||
|
label: '',
|
||||||
|
host: '',
|
||||||
|
port: '',
|
||||||
|
attribute_for_username: 'uid',
|
||||||
|
app_dn: '',
|
||||||
|
app_dn_password: '',
|
||||||
|
search_base: '',
|
||||||
|
search_filters: '',
|
||||||
|
use_tls: false,
|
||||||
|
certificate_path: '',
|
||||||
|
ciphers: ''
|
||||||
|
};
|
||||||
|
|
||||||
const verifyOpenAIHandler = async (idx) => {
|
const verifyOpenAIHandler = async (idx) => {
|
||||||
OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS.map((url) => url.replace(/\/$/, ''));
|
OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS.map((url) => url.replace(/\/$/, ''));
|
||||||
|
|
||||||
@ -136,6 +159,17 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateLdapServerHandler = async () => {
|
||||||
|
if (!ENABLE_LDAP) return;
|
||||||
|
const res = await updateLdapServer(localStorage.token, LDAP_SERVER).catch((error) => {
|
||||||
|
toast.error(error);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
if (res) {
|
||||||
|
toast.success($i18n.t('LDAP server updated'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if ($user.role === 'admin') {
|
if ($user.role === 'admin') {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
@ -147,14 +181,19 @@
|
|||||||
})(),
|
})(),
|
||||||
(async () => {
|
(async () => {
|
||||||
OPENAI_API_KEYS = await getOpenAIKeys(localStorage.token);
|
OPENAI_API_KEYS = await getOpenAIKeys(localStorage.token);
|
||||||
|
})(),
|
||||||
|
(async () => {
|
||||||
|
LDAP_SERVER = await getLdapServer(localStorage.token);
|
||||||
})()
|
})()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const ollamaConfig = await getOllamaConfig(localStorage.token);
|
const ollamaConfig = await getOllamaConfig(localStorage.token);
|
||||||
const openaiConfig = await getOpenAIConfig(localStorage.token);
|
const openaiConfig = await getOpenAIConfig(localStorage.token);
|
||||||
|
const ldapConfig = await getLdapConfig(localStorage.token);
|
||||||
|
|
||||||
ENABLE_OPENAI_API = openaiConfig.ENABLE_OPENAI_API;
|
ENABLE_OPENAI_API = openaiConfig.ENABLE_OPENAI_API;
|
||||||
ENABLE_OLLAMA_API = ollamaConfig.ENABLE_OLLAMA_API;
|
ENABLE_OLLAMA_API = ollamaConfig.ENABLE_OLLAMA_API;
|
||||||
|
ENABLE_LDAP = ldapConfig.ENABLE_LDAP;
|
||||||
|
|
||||||
if (ENABLE_OPENAI_API) {
|
if (ENABLE_OPENAI_API) {
|
||||||
OPENAI_API_BASE_URLS.forEach(async (url, idx) => {
|
OPENAI_API_BASE_URLS.forEach(async (url, idx) => {
|
||||||
@ -173,12 +212,13 @@
|
|||||||
on:submit|preventDefault={() => {
|
on:submit|preventDefault={() => {
|
||||||
updateOpenAIHandler();
|
updateOpenAIHandler();
|
||||||
updateOllamaUrlsHandler();
|
updateOllamaUrlsHandler();
|
||||||
|
updateLdapServerHandler();
|
||||||
|
|
||||||
dispatch('save');
|
dispatch('save');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="space-y-3 overflow-y-scroll scrollbar-hidden h-full">
|
<div class="space-y-3 overflow-y-scroll scrollbar-hidden h-full">
|
||||||
{#if ENABLE_OPENAI_API !== null && ENABLE_OLLAMA_API !== null}
|
{#if ENABLE_OPENAI_API !== null && ENABLE_OLLAMA_API !== null && ENABLE_LDAP !== null}
|
||||||
<div class=" space-y-3">
|
<div class=" space-y-3">
|
||||||
<div class="mt-2 space-y-2 pr-1.5">
|
<div class="mt-2 space-y-2 pr-1.5">
|
||||||
<div class="flex justify-between items-center text-sm">
|
<div class="flex justify-between items-center text-sm">
|
||||||
@ -428,6 +468,188 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<hr class=" dark:border-gray-850" />
|
||||||
|
|
||||||
|
<div class=" space-y-3">
|
||||||
|
<div class="mt-2 space-y-2 pr-1.5">
|
||||||
|
<div class="flex justify-between items-center text-sm">
|
||||||
|
<div class=" font-medium">{$i18n.t('LDAP')}</div>
|
||||||
|
|
||||||
|
<div class="mt-1">
|
||||||
|
<Switch
|
||||||
|
bind:state={ENABLE_LDAP}
|
||||||
|
on:change={async () => {
|
||||||
|
updateLdapConfig(localStorage.token, ENABLE_LDAP);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if ENABLE_LDAP}
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<div class="flex w-full gap-2">
|
||||||
|
<div class="w-full">
|
||||||
|
<div class=" self-center text-xs font-medium min-w-fit mb-1">
|
||||||
|
{$i18n.t('Label')}
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||||
|
required
|
||||||
|
placeholder={$i18n.t('Enter server label')}
|
||||||
|
bind:value={LDAP_SERVER.label}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="w-full"></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex w-full gap-2">
|
||||||
|
<div class="w-full">
|
||||||
|
<div class=" self-center text-xs font-medium min-w-fit mb-1">
|
||||||
|
{$i18n.t('Host')}
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||||
|
required
|
||||||
|
placeholder={$i18n.t('Enter server host')}
|
||||||
|
bind:value={LDAP_SERVER.host}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="w-full">
|
||||||
|
<div class=" self-center text-xs font-medium min-w-fit mb-1">
|
||||||
|
{$i18n.t('Port')}
|
||||||
|
</div>
|
||||||
|
<Tooltip placement="top-start" content={$i18n.t("Default to 389 or 636 if TLS is enabled")} className="w-full">
|
||||||
|
<input
|
||||||
|
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||||
|
type="number"
|
||||||
|
placeholder={$i18n.t('Enter server port')}
|
||||||
|
bind:value={LDAP_SERVER.port}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex w-full gap-2">
|
||||||
|
<div class="w-full">
|
||||||
|
<div class=" self-center text-xs font-medium min-w-fit mb-1">
|
||||||
|
{$i18n.t('Application DN')}
|
||||||
|
</div>
|
||||||
|
<Tooltip content={$i18n.t('The Application Account DN you bind with for search')} placement="top-start">
|
||||||
|
<input
|
||||||
|
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||||
|
required
|
||||||
|
placeholder={$i18n.t('Enter Application DN')}
|
||||||
|
bind:value={LDAP_SERVER.app_dn}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="w-full">
|
||||||
|
<div class=" self-center text-xs font-medium min-w-fit mb-1">
|
||||||
|
{$i18n.t('Application DN Password')}
|
||||||
|
</div>
|
||||||
|
<SensitiveInput
|
||||||
|
placeholder={$i18n.t('Enter Application DN Password')}
|
||||||
|
bind:value={LDAP_SERVER.app_dn_password}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex w-full gap-2">
|
||||||
|
<div class="w-full">
|
||||||
|
<div class=" self-center text-xs font-medium min-w-fit mb-1">
|
||||||
|
{$i18n.t('Attribute for Username')}
|
||||||
|
</div>
|
||||||
|
<Tooltip content={$i18n.t('The LDAP attribute that maps to the username that users use to sign in.')} placement="top-start">
|
||||||
|
<input
|
||||||
|
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||||
|
required
|
||||||
|
placeholder={$i18n.t('Example: sAMAccountName or uid or userPrincipalName')}
|
||||||
|
bind:value={LDAP_SERVER.attribute_for_username}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex w-full gap-2">
|
||||||
|
<div class="w-full">
|
||||||
|
<div class=" self-center text-xs font-medium min-w-fit mb-1">
|
||||||
|
{$i18n.t('Search Base')}
|
||||||
|
</div>
|
||||||
|
<Tooltip content={$i18n.t('The base to search for users')} placement="top-start">
|
||||||
|
<input
|
||||||
|
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||||
|
required
|
||||||
|
placeholder={$i18n.t('Example: ou=users,dc=foo,dc=example')}
|
||||||
|
bind:value={LDAP_SERVER.search_base}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex w-full gap-2">
|
||||||
|
<div class="w-full">
|
||||||
|
<div class=" self-center text-xs font-medium min-w-fit mb-1">
|
||||||
|
{$i18n.t('Search Filters')}
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||||
|
placeholder={$i18n.t('Example: (&(objectClass=inetOrgPerson)(uid=%s))')}
|
||||||
|
bind:value={LDAP_SERVER.search_filters}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
|
||||||
|
<a
|
||||||
|
class=" text-gray-300 font-medium underline"
|
||||||
|
href="https://ldap.com/ldap-filters/"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{$i18n.t('Click here for filter guides.')}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="flex justify-between items-center text-sm">
|
||||||
|
<div class=" font-medium">{$i18n.t('TLS')}</div>
|
||||||
|
|
||||||
|
<div class="mt-1">
|
||||||
|
<Switch
|
||||||
|
bind:state={LDAP_SERVER.use_tls}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{#if LDAP_SERVER.use_tls}
|
||||||
|
<div class="flex w-full gap-2">
|
||||||
|
<div class="w-full">
|
||||||
|
<div class=" self-center text-xs font-medium min-w-fit mb-1 mt-1">
|
||||||
|
{$i18n.t('Certificate Path')}
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||||
|
required
|
||||||
|
placeholder={$i18n.t('Enter certificate path')}
|
||||||
|
bind:value={LDAP_SERVER.certificate_path}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex w-full gap-2">
|
||||||
|
<div class="w-full">
|
||||||
|
<div class=" self-center text-xs font-medium min-w-fit mb-1">
|
||||||
|
{$i18n.t('Ciphers')}
|
||||||
|
</div>
|
||||||
|
<Tooltip content={$i18n.t('Default to ALL')} placement="top-start">
|
||||||
|
<input
|
||||||
|
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||||
|
placeholder={$i18n.t('Example: ALL')}
|
||||||
|
bind:value={LDAP_SERVER.ciphers}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="w-full"></div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex h-full justify-center">
|
<div class="flex h-full justify-center">
|
||||||
<div class="my-auto">
|
<div class="my-auto">
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { getSessionUser, userSignIn, userSignUp } from '$lib/apis/auths';
|
import { ldapUserSignIn, getSessionUser, userSignIn, userSignUp } from '$lib/apis/auths';
|
||||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||||
import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
|
import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
|
||||||
import { WEBUI_NAME, config, user, socket } from '$lib/stores';
|
import { WEBUI_NAME, config, user, socket } from '$lib/stores';
|
||||||
@ -15,12 +15,22 @@
|
|||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
let loaded = false;
|
let loaded = false;
|
||||||
let mode = 'signin';
|
let mode = (
|
||||||
|
!$config?.features.enable_login_form &&
|
||||||
|
Object.keys($config?.oauth?.providers ?? {}).length == 0 &&
|
||||||
|
$config?.features.enable_ldap_form
|
||||||
|
) ? 'ldap' : 'signin';
|
||||||
|
|
||||||
let name = '';
|
let name = '';
|
||||||
let email = '';
|
let email = '';
|
||||||
let password = '';
|
let password = '';
|
||||||
|
|
||||||
|
let ldapUsername = '';
|
||||||
|
let ldapPassword = '';
|
||||||
|
|
||||||
|
$: showSwitchButtonForSignInForm = ($config?.features.enable_ldap_form && mode !== 'ldap') || ($config?.features.enable_login_form && mode === 'ldap');
|
||||||
|
$: showOtherSignInMethods = Object.keys($config?.oauth?.providers ?? {}).length > 0 || showSwitchButtonForSignInForm;
|
||||||
|
|
||||||
const setSessionUser = async (sessionUser) => {
|
const setSessionUser = async (sessionUser) => {
|
||||||
if (sessionUser) {
|
if (sessionUser) {
|
||||||
console.log(sessionUser);
|
console.log(sessionUser);
|
||||||
@ -36,6 +46,14 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ldapSignInHandler = async () => {
|
||||||
|
const sessionUser = await ldapUserSignIn(ldapUsername, ldapPassword).catch((error) => {
|
||||||
|
toast.error(error);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
await setSessionUser(sessionUser);
|
||||||
|
};
|
||||||
|
|
||||||
const signInHandler = async () => {
|
const signInHandler = async () => {
|
||||||
const sessionUser = await userSignIn(email, password).catch((error) => {
|
const sessionUser = await userSignIn(email, password).catch((error) => {
|
||||||
toast.error(error);
|
toast.error(error);
|
||||||
@ -57,7 +75,10 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const submitHandler = async () => {
|
const submitHandler = async () => {
|
||||||
if (mode === 'signin') {
|
if (mode === 'ldap'){
|
||||||
|
await ldapSignInHandler();
|
||||||
|
}
|
||||||
|
else if (mode === 'signin') {
|
||||||
await signInHandler();
|
await signInHandler();
|
||||||
} else {
|
} else {
|
||||||
await signUpHandler();
|
await signUpHandler();
|
||||||
@ -114,7 +135,11 @@
|
|||||||
bind:show={onboarding}
|
bind:show={onboarding}
|
||||||
getStartedHandler={() => {
|
getStartedHandler={() => {
|
||||||
onboarding = false;
|
onboarding = false;
|
||||||
mode = 'signup';
|
mode = (
|
||||||
|
!$config?.features.enable_login_form &&
|
||||||
|
Object.keys($config?.oauth?.providers ?? {}).length == 0 &&
|
||||||
|
$config?.features.enable_ldap_form
|
||||||
|
) ? 'ldap' : 'signup';;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -167,12 +192,14 @@
|
|||||||
{$i18n.t(`Sign in to {{WEBUI_NAME}}`, { WEBUI_NAME: $WEBUI_NAME })}
|
{$i18n.t(`Sign in to {{WEBUI_NAME}}`, { WEBUI_NAME: $WEBUI_NAME })}
|
||||||
{:else if $config?.onboarding ?? false}
|
{:else if $config?.onboarding ?? false}
|
||||||
{$i18n.t(`Get started with {{WEBUI_NAME}}`, { WEBUI_NAME: $WEBUI_NAME })}
|
{$i18n.t(`Get started with {{WEBUI_NAME}}`, { WEBUI_NAME: $WEBUI_NAME })}
|
||||||
|
{:else if mode === 'ldap'}
|
||||||
|
{$i18n.t(`Sign in to {{WEBUI_NAME}} with LDAP`, { WEBUI_NAME: $WEBUI_NAME })}
|
||||||
{:else}
|
{:else}
|
||||||
{$i18n.t(`Sign up to {{WEBUI_NAME}}`, { WEBUI_NAME: $WEBUI_NAME })}
|
{$i18n.t(`Sign up to {{WEBUI_NAME}}`, { WEBUI_NAME: $WEBUI_NAME })}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if mode === 'signup' && ($config?.onboarding ?? false)}
|
{#if (mode === 'signup' || mode === 'ldap') && ($config?.onboarding ?? false)}
|
||||||
<div class=" mt-1 text-xs font-medium text-gray-500">
|
<div class=" mt-1 text-xs font-medium text-gray-500">
|
||||||
ⓘ {$WEBUI_NAME}
|
ⓘ {$WEBUI_NAME}
|
||||||
{$i18n.t(
|
{$i18n.t(
|
||||||
@ -182,7 +209,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if $config?.features.enable_login_form}
|
{#if $config?.features.enable_login_form || $config?.features.enable_ldap_form }
|
||||||
<div class="flex flex-col mt-4">
|
<div class="flex flex-col mt-4">
|
||||||
{#if mode === 'signup'}
|
{#if mode === 'signup'}
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
@ -198,17 +225,31 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="mb-2">
|
{#if mode === 'ldap'}
|
||||||
<div class=" text-sm font-medium text-left mb-1">{$i18n.t('Email')}</div>
|
<div class="mb-2">
|
||||||
<input
|
<div class=" text-sm font-medium text-left mb-1">{$i18n.t('Username')}</div>
|
||||||
bind:value={email}
|
<input
|
||||||
type="email"
|
bind:value={ldapUsername}
|
||||||
class="my-0.5 w-full text-sm outline-none bg-transparent"
|
type="text"
|
||||||
autocomplete="email"
|
class="my-0.5 w-full text-sm outline-none bg-transparent"
|
||||||
placeholder={$i18n.t('Enter Your Email')}
|
autocomplete="username"
|
||||||
required
|
placeholder={$i18n.t('Enter Your Username')}
|
||||||
/>
|
required
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="mb-2">
|
||||||
|
<div class=" text-sm font-medium text-left mb-1">{$i18n.t('Email')}</div>
|
||||||
|
<input
|
||||||
|
bind:value={email}
|
||||||
|
type="email"
|
||||||
|
class="my-0.5 w-full text-sm outline-none bg-transparent"
|
||||||
|
autocomplete="email"
|
||||||
|
placeholder={$i18n.t('Enter Your Email')}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class=" text-sm font-medium text-left mb-1">{$i18n.t('Password')}</div>
|
<div class=" text-sm font-medium text-left mb-1">{$i18n.t('Password')}</div>
|
||||||
@ -225,7 +266,18 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $config?.features.enable_login_form}
|
{#if $config?.features.enable_ldap_form && mode === 'ldap'}
|
||||||
|
<div class="mt-5">
|
||||||
|
<button
|
||||||
|
class="bg-gray-700/5 hover:bg-gray-700/10 dark:bg-gray-100/5 dark:hover:bg-gray-100/10 dark:text-gray-300 dark:hover:text-white transition w-full rounded-full font-medium text-sm py-2.5"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
{($config?.onboarding ?? false)
|
||||||
|
? $i18n.t('Authenticate as Admin')
|
||||||
|
: $i18n.t('Authenticate')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{:else if $config?.features.enable_login_form && mode !== 'ldap'}
|
||||||
<div class="mt-5">
|
<div class="mt-5">
|
||||||
<button
|
<button
|
||||||
class="bg-gray-700/5 hover:bg-gray-700/10 dark:bg-gray-100/5 dark:hover:bg-gray-100/10 dark:text-gray-300 dark:hover:text-white transition w-full rounded-full font-medium text-sm py-2.5"
|
class="bg-gray-700/5 hover:bg-gray-700/10 dark:bg-gray-100/5 dark:hover:bg-gray-100/10 dark:text-gray-300 dark:hover:text-white transition w-full rounded-full font-medium text-sm py-2.5"
|
||||||
@ -263,7 +315,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{#if Object.keys($config?.oauth?.providers ?? {}).length > 0}
|
{#if showOtherSignInMethods}
|
||||||
<div class="inline-flex items-center justify-center w-full">
|
<div class="inline-flex items-center justify-center w-full">
|
||||||
<hr class="w-32 h-px my-4 border-0 dark:bg-gray-100/10 bg-gray-700/10" />
|
<hr class="w-32 h-px my-4 border-0 dark:bg-gray-100/10 bg-gray-700/10" />
|
||||||
{#if $config?.features.enable_login_form}
|
{#if $config?.features.enable_login_form}
|
||||||
@ -355,6 +407,51 @@
|
|||||||
>
|
>
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if showSwitchButtonForSignInForm}
|
||||||
|
<button
|
||||||
|
class="flex items-center px-6 border-2 dark:border-gray-800 duration-300 dark:bg-gray-900 hover:bg-gray-100 dark:hover:bg-gray-800 w-full rounded-2xl dark:text-white text-sm py-3 transition"
|
||||||
|
on:click={() => {
|
||||||
|
if (mode === 'ldap')
|
||||||
|
mode = ($config?.onboarding ?? false) ? 'signup' : 'signin';
|
||||||
|
else mode = 'ldap';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{#if mode === 'ldap'}
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
class="size-6 mr-3"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M4 7.00005L10.2 11.65C11.2667 12.45 12.7333 12.45 13.8 11.65L20 7"
|
||||||
|
stroke-width="2"
|
||||||
|
/>
|
||||||
|
<rect x="3" y="5" width="18" height="14" rx="2" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
{:else}
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
class="size-6 mr-3"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M15.75 5.25a3 3 0 0 1 3 3m3 0a6 6 0 0 1-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1 1 21.75 8.25Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
|
<span>{mode === 'ldap' ? $i18n.t('Continue with Email') : $i18n.t('Continue with LDAP')}</span>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user