mirror of
https://github.com/open-webui/open-webui.git
synced 2025-12-12 04:15:25 +00:00
refac/enh: display oauth error as toast
This commit is contained in:
parent
9368d0ac75
commit
3d6d050ad8
2 changed files with 196 additions and 165 deletions
|
|
@ -401,185 +401,207 @@ class OAuthManager:
|
||||||
async def handle_callback(self, request, provider, response):
|
async def handle_callback(self, request, provider, response):
|
||||||
if provider not in OAUTH_PROVIDERS:
|
if provider not in OAUTH_PROVIDERS:
|
||||||
raise HTTPException(404)
|
raise HTTPException(404)
|
||||||
client = self.get_client(provider)
|
|
||||||
|
error_message = None
|
||||||
try:
|
try:
|
||||||
token = await client.authorize_access_token(request)
|
client = self.get_client(provider)
|
||||||
except Exception as e:
|
try:
|
||||||
log.warning(f"OAuth callback error: {e}")
|
token = await client.authorize_access_token(request)
|
||||||
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
|
except Exception as e:
|
||||||
user_data: UserInfo = token.get("userinfo")
|
log.warning(f"OAuth callback error: {e}")
|
||||||
if (
|
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
|
||||||
(not user_data)
|
user_data: UserInfo = token.get("userinfo")
|
||||||
or (auth_manager_config.OAUTH_EMAIL_CLAIM not in user_data)
|
if (
|
||||||
or (auth_manager_config.OAUTH_USERNAME_CLAIM not in user_data)
|
(not user_data)
|
||||||
):
|
or (auth_manager_config.OAUTH_EMAIL_CLAIM not in user_data)
|
||||||
user_data: UserInfo = await client.userinfo(token=token)
|
or (auth_manager_config.OAUTH_USERNAME_CLAIM not in user_data)
|
||||||
if not user_data:
|
):
|
||||||
log.warning(f"OAuth callback failed, user data is missing: {token}")
|
user_data: UserInfo = await client.userinfo(token=token)
|
||||||
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
|
if not user_data:
|
||||||
|
log.warning(f"OAuth callback failed, user data is missing: {token}")
|
||||||
|
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
|
||||||
|
|
||||||
if auth_manager_config.OAUTH_SUB_CLAIM:
|
if auth_manager_config.OAUTH_SUB_CLAIM:
|
||||||
sub = user_data.get(auth_manager_config.OAUTH_SUB_CLAIM)
|
sub = user_data.get(auth_manager_config.OAUTH_SUB_CLAIM)
|
||||||
else:
|
else:
|
||||||
# Fallback to the default sub claim if not configured
|
# Fallback to the default sub claim if not configured
|
||||||
sub = user_data.get(OAUTH_PROVIDERS[provider].get("sub_claim", "sub"))
|
sub = user_data.get(OAUTH_PROVIDERS[provider].get("sub_claim", "sub"))
|
||||||
|
|
||||||
if not sub:
|
if not sub:
|
||||||
log.warning(f"OAuth callback failed, sub is missing: {user_data}")
|
log.warning(f"OAuth callback failed, sub is missing: {user_data}")
|
||||||
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
|
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
|
||||||
|
|
||||||
provider_sub = f"{provider}@{sub}"
|
provider_sub = f"{provider}@{sub}"
|
||||||
|
|
||||||
email_claim = auth_manager_config.OAUTH_EMAIL_CLAIM
|
email_claim = auth_manager_config.OAUTH_EMAIL_CLAIM
|
||||||
email = user_data.get(email_claim, "")
|
email = user_data.get(email_claim, "")
|
||||||
# We currently mandate that email addresses are provided
|
# We currently mandate that email addresses are provided
|
||||||
if not email:
|
if not email:
|
||||||
# If the provider is GitHub,and public email is not provided, we can use the access token to fetch the user's email
|
# If the provider is GitHub,and public email is not provided, we can use the access token to fetch the user's email
|
||||||
if provider == "github":
|
if provider == "github":
|
||||||
try:
|
try:
|
||||||
access_token = token.get("access_token")
|
access_token = token.get("access_token")
|
||||||
headers = {"Authorization": f"Bearer {access_token}"}
|
headers = {"Authorization": f"Bearer {access_token}"}
|
||||||
async with aiohttp.ClientSession(trust_env=True) as session:
|
async with aiohttp.ClientSession(trust_env=True) as session:
|
||||||
async with session.get(
|
async with session.get(
|
||||||
"https://api.github.com/user/emails",
|
"https://api.github.com/user/emails",
|
||||||
headers=headers,
|
headers=headers,
|
||||||
ssl=AIOHTTP_CLIENT_SESSION_SSL,
|
ssl=AIOHTTP_CLIENT_SESSION_SSL,
|
||||||
) as resp:
|
) as resp:
|
||||||
if resp.ok:
|
if resp.ok:
|
||||||
emails = await resp.json()
|
emails = await resp.json()
|
||||||
# use the primary email as the user's email
|
# use the primary email as the user's email
|
||||||
primary_email = next(
|
primary_email = next(
|
||||||
(e["email"] for e in emails if e.get("primary")),
|
(
|
||||||
None,
|
e["email"]
|
||||||
)
|
for e in emails
|
||||||
if primary_email:
|
if e.get("primary")
|
||||||
email = primary_email
|
),
|
||||||
else:
|
None,
|
||||||
log.warning(
|
|
||||||
"No primary email found in GitHub response"
|
|
||||||
)
|
)
|
||||||
|
if primary_email:
|
||||||
|
email = primary_email
|
||||||
|
else:
|
||||||
|
log.warning(
|
||||||
|
"No primary email found in GitHub response"
|
||||||
|
)
|
||||||
|
raise HTTPException(
|
||||||
|
400, detail=ERROR_MESSAGES.INVALID_CRED
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
log.warning("Failed to fetch GitHub email")
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
400, detail=ERROR_MESSAGES.INVALID_CRED
|
400, detail=ERROR_MESSAGES.INVALID_CRED
|
||||||
)
|
)
|
||||||
else:
|
except Exception as e:
|
||||||
log.warning("Failed to fetch GitHub email")
|
log.warning(f"Error fetching GitHub email: {e}")
|
||||||
raise HTTPException(
|
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
|
||||||
400, detail=ERROR_MESSAGES.INVALID_CRED
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
log.warning(f"Error fetching GitHub email: {e}")
|
|
||||||
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
|
|
||||||
else:
|
|
||||||
log.warning(f"OAuth callback failed, email is missing: {user_data}")
|
|
||||||
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
|
|
||||||
email = email.lower()
|
|
||||||
if (
|
|
||||||
"*" not in auth_manager_config.OAUTH_ALLOWED_DOMAINS
|
|
||||||
and email.split("@")[-1] not in auth_manager_config.OAUTH_ALLOWED_DOMAINS
|
|
||||||
):
|
|
||||||
log.warning(
|
|
||||||
f"OAuth callback failed, e-mail domain is not in the list of allowed domains: {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 auth_manager_config.OAUTH_MERGE_ACCOUNTS_BY_EMAIL:
|
|
||||||
# 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 user:
|
|
||||||
determined_role = self.get_user_role(user, user_data)
|
|
||||||
if user.role != determined_role:
|
|
||||||
Users.update_user_role_by_id(user.id, determined_role)
|
|
||||||
|
|
||||||
# Update profile picture if enabled and different from current
|
|
||||||
if auth_manager_config.OAUTH_UPDATE_PICTURE_ON_LOGIN:
|
|
||||||
picture_claim = auth_manager_config.OAUTH_PICTURE_CLAIM
|
|
||||||
if picture_claim:
|
|
||||||
new_picture_url = user_data.get(
|
|
||||||
picture_claim, OAUTH_PROVIDERS[provider].get("picture_url", "")
|
|
||||||
)
|
|
||||||
processed_picture_url = await self._process_picture_url(
|
|
||||||
new_picture_url, token.get("access_token")
|
|
||||||
)
|
|
||||||
if processed_picture_url != user.profile_image_url:
|
|
||||||
Users.update_user_profile_image_url_by_id(
|
|
||||||
user.id, processed_picture_url
|
|
||||||
)
|
|
||||||
log.debug(f"Updated profile picture for user {user.email}")
|
|
||||||
|
|
||||||
if not user:
|
|
||||||
# If the user does not exist, check if signups are enabled
|
|
||||||
if auth_manager_config.ENABLE_OAUTH_SIGNUP:
|
|
||||||
# Check if an existing user with the same email already exists
|
|
||||||
existing_user = Users.get_user_by_email(email)
|
|
||||||
if existing_user:
|
|
||||||
raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN)
|
|
||||||
|
|
||||||
picture_claim = auth_manager_config.OAUTH_PICTURE_CLAIM
|
|
||||||
if picture_claim:
|
|
||||||
picture_url = user_data.get(
|
|
||||||
picture_claim, OAUTH_PROVIDERS[provider].get("picture_url", "")
|
|
||||||
)
|
|
||||||
picture_url = await self._process_picture_url(
|
|
||||||
picture_url, token.get("access_token")
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
picture_url = "/user.png"
|
log.warning(f"OAuth callback failed, email is missing: {user_data}")
|
||||||
|
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
|
||||||
username_claim = auth_manager_config.OAUTH_USERNAME_CLAIM
|
email = email.lower()
|
||||||
|
if (
|
||||||
name = user_data.get(username_claim)
|
"*" not in auth_manager_config.OAUTH_ALLOWED_DOMAINS
|
||||||
if not name:
|
and email.split("@")[-1]
|
||||||
log.warning("Username claim is missing, using email as name")
|
not in auth_manager_config.OAUTH_ALLOWED_DOMAINS
|
||||||
name = email
|
):
|
||||||
|
log.warning(
|
||||||
role = self.get_user_role(None, user_data)
|
f"OAuth callback failed, e-mail domain is not in the list of allowed domains: {user_data}"
|
||||||
|
|
||||||
user = Auths.insert_new_auth(
|
|
||||||
email=email,
|
|
||||||
password=get_password_hash(
|
|
||||||
str(uuid.uuid4())
|
|
||||||
), # Random password, not used
|
|
||||||
name=name,
|
|
||||||
profile_image_url=picture_url,
|
|
||||||
role=role,
|
|
||||||
oauth_sub=provider_sub,
|
|
||||||
)
|
)
|
||||||
|
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
|
||||||
|
|
||||||
if auth_manager_config.WEBHOOK_URL:
|
# Check if the user exists
|
||||||
await post_webhook(
|
user = Users.get_user_by_oauth_sub(provider_sub)
|
||||||
WEBUI_NAME,
|
|
||||||
auth_manager_config.WEBHOOK_URL,
|
if not user:
|
||||||
WEBHOOK_MESSAGES.USER_SIGNUP(user.name),
|
# If the user does not exist, check if merging is enabled
|
||||||
{
|
if auth_manager_config.OAUTH_MERGE_ACCOUNTS_BY_EMAIL:
|
||||||
"action": "signup",
|
# Check if the user exists by email
|
||||||
"message": WEBHOOK_MESSAGES.USER_SIGNUP(user.name),
|
user = Users.get_user_by_email(email)
|
||||||
"user": user.model_dump_json(exclude_none=True),
|
if user:
|
||||||
},
|
# Update the user with the new oauth sub
|
||||||
|
Users.update_user_oauth_sub_by_id(user.id, provider_sub)
|
||||||
|
|
||||||
|
if user:
|
||||||
|
determined_role = self.get_user_role(user, user_data)
|
||||||
|
if user.role != determined_role:
|
||||||
|
Users.update_user_role_by_id(user.id, determined_role)
|
||||||
|
|
||||||
|
# Update profile picture if enabled and different from current
|
||||||
|
if auth_manager_config.OAUTH_UPDATE_PICTURE_ON_LOGIN:
|
||||||
|
picture_claim = auth_manager_config.OAUTH_PICTURE_CLAIM
|
||||||
|
if picture_claim:
|
||||||
|
new_picture_url = user_data.get(
|
||||||
|
picture_claim,
|
||||||
|
OAUTH_PROVIDERS[provider].get("picture_url", ""),
|
||||||
|
)
|
||||||
|
processed_picture_url = await self._process_picture_url(
|
||||||
|
new_picture_url, token.get("access_token")
|
||||||
|
)
|
||||||
|
if processed_picture_url != user.profile_image_url:
|
||||||
|
Users.update_user_profile_image_url_by_id(
|
||||||
|
user.id, processed_picture_url
|
||||||
|
)
|
||||||
|
log.debug(f"Updated profile picture for user {user.email}")
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
# If the user does not exist, check if signups are enabled
|
||||||
|
if auth_manager_config.ENABLE_OAUTH_SIGNUP:
|
||||||
|
# Check if an existing user with the same email already exists
|
||||||
|
existing_user = Users.get_user_by_email(email)
|
||||||
|
if existing_user:
|
||||||
|
raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN)
|
||||||
|
|
||||||
|
picture_claim = auth_manager_config.OAUTH_PICTURE_CLAIM
|
||||||
|
if picture_claim:
|
||||||
|
picture_url = user_data.get(
|
||||||
|
picture_claim,
|
||||||
|
OAUTH_PROVIDERS[provider].get("picture_url", ""),
|
||||||
|
)
|
||||||
|
picture_url = await self._process_picture_url(
|
||||||
|
picture_url, token.get("access_token")
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
picture_url = "/user.png"
|
||||||
|
|
||||||
|
username_claim = auth_manager_config.OAUTH_USERNAME_CLAIM
|
||||||
|
|
||||||
|
name = user_data.get(username_claim)
|
||||||
|
if not name:
|
||||||
|
log.warning("Username claim is missing, using email as name")
|
||||||
|
name = email
|
||||||
|
|
||||||
|
role = self.get_user_role(None, user_data)
|
||||||
|
|
||||||
|
user = Auths.insert_new_auth(
|
||||||
|
email=email,
|
||||||
|
password=get_password_hash(
|
||||||
|
str(uuid.uuid4())
|
||||||
|
), # Random password, not used
|
||||||
|
name=name,
|
||||||
|
profile_image_url=picture_url,
|
||||||
|
role=role,
|
||||||
|
oauth_sub=provider_sub,
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
raise HTTPException(
|
if auth_manager_config.WEBHOOK_URL:
|
||||||
status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED
|
await post_webhook(
|
||||||
|
WEBUI_NAME,
|
||||||
|
auth_manager_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(auth_manager_config.JWT_EXPIRES_IN),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
auth_manager_config.ENABLE_OAUTH_GROUP_MANAGEMENT
|
||||||
|
and user.role != "admin"
|
||||||
|
):
|
||||||
|
self.update_user_groups(
|
||||||
|
user=user,
|
||||||
|
user_data=user_data,
|
||||||
|
default_permissions=request.app.state.config.USER_PERMISSIONS,
|
||||||
)
|
)
|
||||||
|
|
||||||
jwt_token = create_token(
|
except Exception as e:
|
||||||
data={"id": user.id},
|
log.error(f"Error during OAuth process: {e}")
|
||||||
expires_delta=parse_duration(auth_manager_config.JWT_EXPIRES_IN),
|
error_message = (
|
||||||
)
|
e.detail
|
||||||
|
if isinstance(e, HTTPException) and e.detail
|
||||||
if auth_manager_config.ENABLE_OAUTH_GROUP_MANAGEMENT and user.role != "admin":
|
else ERROR_MESSAGES.DEFAULT("Error during OAuth process")
|
||||||
self.update_user_groups(
|
|
||||||
user=user,
|
|
||||||
user_data=user_data,
|
|
||||||
default_permissions=request.app.state.config.USER_PERMISSIONS,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
redirect_base_url = str(request.app.state.config.WEBUI_URL or request.base_url)
|
redirect_base_url = str(request.app.state.config.WEBUI_URL or request.base_url)
|
||||||
|
|
@ -587,6 +609,10 @@ class OAuthManager:
|
||||||
redirect_base_url = redirect_base_url[:-1]
|
redirect_base_url = redirect_base_url[:-1]
|
||||||
redirect_url = f"{redirect_base_url}/auth"
|
redirect_url = f"{redirect_base_url}/auth"
|
||||||
|
|
||||||
|
if error_message:
|
||||||
|
redirect_url = f"{redirect_url}?error={error_message}"
|
||||||
|
return RedirectResponse(url=redirect_url, headers=response.headers)
|
||||||
|
|
||||||
response = RedirectResponse(url=redirect_url, headers=response.headers)
|
response = RedirectResponse(url=redirect_url, headers=response.headers)
|
||||||
|
|
||||||
# Set the cookie token
|
# Set the cookie token
|
||||||
|
|
|
||||||
|
|
@ -162,8 +162,13 @@
|
||||||
localStorage.setItem('redirectPath', redirectPath);
|
localStorage.setItem('redirectPath', redirectPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await oauthCallbackHandler();
|
|
||||||
|
|
||||||
|
const error = $page.url.searchParams.get('error');
|
||||||
|
if (error) {
|
||||||
|
toast.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
await oauthCallbackHandler();
|
||||||
form = $page.url.searchParams.get('form');
|
form = $page.url.searchParams.get('form');
|
||||||
|
|
||||||
loaded = true;
|
loaded = true;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue