Provisioning: add new members to Open WebUI docs-RAG access group at signup
This commit is contained in:
1 parent
2a5277007e
commit
e18cd4b703
1 file changed
+76
+76
@@ -72,6 +72,15 @@ PORTAL_SECRET = os.environ.get("PORTAL_SECRET", "")
|
|||||||
# email itself is tamper-proof.
|
# email itself is tamper-proof.
|
||||||
OWUI_JWT_SECRET = os.environ.get("OWUI_JWT_SECRET", "")
|
OWUI_JWT_SECRET = os.environ.get("OWUI_JWT_SECRET", "")
|
||||||
|
|
||||||
|
# Open WebUI admin API key — used at provisioning time to add the new member to
|
||||||
|
# Open WebUI's `members` group, which gates read access to the shared
|
||||||
|
# "Inference Cooperative Docs" RAG collection. Without this, a new member's
|
||||||
|
# first chat session has no docs grounding (the nightly refresh would only fix
|
||||||
|
# it hours later). Machine-to-machine only; never logged.
|
||||||
|
OWUI_API_KEY = os.environ.get("OWUI_API_KEY", "")
|
||||||
|
OWUI_BASE = os.environ.get("OWUI_BASE", "http://chat:8080") # internal Cloudron network
|
||||||
|
OWUI_MEMBERS_GROUP_ID = os.environ.get("OWUI_MEMBERS_GROUP_ID", "eb348e1a-059a-4a8f-8920-0f73942f6272")
|
||||||
|
|
||||||
# Shared secret that the member dashboard uses to authenticate broker requests.
|
# Shared secret that the member dashboard uses to authenticate broker requests.
|
||||||
# The dashboard is Cloudron-SSO-gated and passes the authenticated member's
|
# The dashboard is Cloudron-SSO-gated and passes the authenticated member's
|
||||||
# email; the portal trusts that email only because it carries this secret. Kept
|
# email; the portal trusts that email only because it carries this secret. Kept
|
||||||
@@ -1209,6 +1218,64 @@ async def cloudron_set_active(user_id: str, active: bool) -> None:
|
|||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
|
|
||||||
|
|
||||||
|
async def owui_add_to_members_group(cloudron_user_id: str) -> bool:
|
||||||
|
"""Add a (new) member to Open WebUI's `members` group by their Cloudron uid.
|
||||||
|
|
||||||
|
Open WebUI authenticates via Cloudron SSO and stores users keyed by the SSO
|
||||||
|
`sub` — which is the Cloudron user id (uid-…). The shared docs-RAG knowledge
|
||||||
|
collection is granted read to the `members` group, so group membership is
|
||||||
|
what makes the docs visible. Called at provisioning time so a new member's
|
||||||
|
FIRST chat session already has the docs (the nightly refresh sync is too
|
||||||
|
slow for that).
|
||||||
|
|
||||||
|
Best-effort: any failure logs a warning and returns False — provisioning
|
||||||
|
must not fail over this; the nightly refresh self-heals within a day.
|
||||||
|
"""
|
||||||
|
if not OWUI_API_KEY:
|
||||||
|
logger.warning("OWUI_API_KEY not set; skipping Open WebUI group add for %s", cloudron_user_id)
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient(timeout=15.0) as client:
|
||||||
|
# find the OWUI user (SSO `sub` = Cloudron uid); if they've never
|
||||||
|
# logged in, the user row may not exist yet — the nightly sync
|
||||||
|
# covers it, and group-add by uid also works after first login.
|
||||||
|
r = await client.get(
|
||||||
|
f"{OWUI_BASE}/api/v1/users/",
|
||||||
|
headers={"Authorization": f"Bearer {OWUI_API_KEY}"},
|
||||||
|
params={"query": cloudron_user_id},
|
||||||
|
)
|
||||||
|
if r.status_code != 200:
|
||||||
|
logger.warning("OWUI user lookup failed: %s %s", r.status_code, r.text[:150])
|
||||||
|
return False
|
||||||
|
payload = r.json() or {}
|
||||||
|
users = payload.get("users", []) if isinstance(payload, dict) else (payload or [])
|
||||||
|
owui_uid = None
|
||||||
|
for u in users:
|
||||||
|
if u.get("id") == cloudron_user_id:
|
||||||
|
owui_uid = u["id"]
|
||||||
|
break
|
||||||
|
if not owui_uid:
|
||||||
|
# user hasn't logged in yet — they'll be added to the group by
|
||||||
|
# the nightly sync after first login. Not an error.
|
||||||
|
logger.info("OWUI: no user row yet for %s (not logged in); nightly sync will add", cloudron_user_id)
|
||||||
|
return False
|
||||||
|
|
||||||
|
r = await client.post(
|
||||||
|
f"{OWUI_BASE}/api/v1/groups/id/{OWUI_MEMBERS_GROUP_ID}/users/add",
|
||||||
|
headers={"Authorization": f"Bearer {OWUI_API_KEY}"},
|
||||||
|
json={"user_ids": [owui_uid]},
|
||||||
|
)
|
||||||
|
if r.status_code == 200:
|
||||||
|
logger.info("OWUI: added %s to members group", cloudron_user_id)
|
||||||
|
return True
|
||||||
|
# 400 "already in group" is fine
|
||||||
|
logger.warning("OWUI group add for %s: %s %s", cloudron_user_id, r.status_code, r.text[:150])
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning("OWUI group add failed for %s: %s", cloudron_user_id, e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
async def cloudron_update_profile(user_id: str, email: str, display_name: str, fallback_email: str = "") -> None:
|
async def cloudron_update_profile(user_id: str, email: str, display_name: str, fallback_email: str = "") -> None:
|
||||||
"""Update a user's profile (display name, email) after creation.
|
"""Update a user's profile (display name, email) after creation.
|
||||||
|
|
||||||
@@ -1279,6 +1346,15 @@ async def provision_member(email: str, name: str, slug: str = "") -> str:
|
|||||||
balance = get_member_balance(email)
|
balance = get_member_balance(email)
|
||||||
team_id = await litellm_get_or_create_team(email, balance)
|
team_id = await litellm_get_or_create_team(email, balance)
|
||||||
|
|
||||||
|
# Pre-provision the member into Open WebUI's docs-RAG access group so their
|
||||||
|
# FIRST chat session has the co-op docs. Best-effort (most new members
|
||||||
|
# haven't logged in yet, so the OWUI user row doesn't exist — the nightly
|
||||||
|
# refresh catches them after first login).
|
||||||
|
try:
|
||||||
|
await owui_add_to_members_group(user_id)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning("OWUI members-group add skipped for %s: %s", email, e)
|
||||||
|
|
||||||
# Reuse an existing key if we already have one (the sk- value is stored in
|
# Reuse an existing key if we already have one (the sk- value is stored in
|
||||||
# our DB at creation time and is the authoritative token for this member's
|
# our DB at creation time and is the authoritative token for this member's
|
||||||
# chat key). Only mint a fresh key when there is none.
|
# chat key). Only mint a fresh key when there is none.
|
||||||
|
|||||||
Reference in new issue
Block a user