From cc9ad1631127277e8af66a3f7a5566f6b0b983cf Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 30 Mar 2026 03:33:50 +0800 Subject: [PATCH] feat(flow): auto-resolve authentik sub and improve admin dropdown UX --- app/api/admin_catalog.py | 18 +++++++++++++++--- app/schemas/catalog.py | 2 +- app/services/authentik_admin_service.py | 9 +++++++-- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/app/api/admin_catalog.py b/app/api/admin_catalog.py index 19fb4eb..3ee0d84 100644 --- a/app/api/admin_catalog.py +++ b/app/api/admin_catalog.py @@ -83,7 +83,11 @@ def _sync_member_to_authentik( display_name=display_name, is_active=is_active, ) - return {"authentik_user_id": result.user_id, "sync_action": result.action} + return { + "authentik_user_id": result.user_id, + "sync_action": result.action, + "authentik_sub": result.authentik_sub or "", + } @router.get("/systems") @@ -332,17 +336,25 @@ def upsert_member( db: Session = Depends(get_db), ) -> MemberItem: users_repo = UsersRepository(db) + resolved_sub = payload.authentik_sub authentik_user_id = None if payload.sync_to_authentik: + seed_sub = payload.authentik_sub or (payload.email or "") + if not seed_sub: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="authentik_sub_or_email_required") sync = _sync_member_to_authentik( - authentik_sub=payload.authentik_sub, + authentik_sub=seed_sub, email=payload.email, display_name=payload.display_name, is_active=payload.is_active, ) authentik_user_id = int(sync["authentik_user_id"]) + if sync.get("authentik_sub"): + resolved_sub = str(sync["authentik_sub"]) + if not resolved_sub: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="authentik_sub_required") row = users_repo.upsert_by_sub( - authentik_sub=payload.authentik_sub, + authentik_sub=resolved_sub, email=payload.email, display_name=payload.display_name, is_active=payload.is_active, diff --git a/app/schemas/catalog.py b/app/schemas/catalog.py index 637f8e8..1242447 100644 --- a/app/schemas/catalog.py +++ b/app/schemas/catalog.py @@ -87,7 +87,7 @@ class MemberItem(BaseModel): class MemberUpsertRequest(BaseModel): - authentik_sub: str + authentik_sub: str | None = None email: str | None = None display_name: str | None = None is_active: bool = True diff --git a/app/services/authentik_admin_service.py b/app/services/authentik_admin_service.py index 793255e..d1dcb9b 100644 --- a/app/services/authentik_admin_service.py +++ b/app/services/authentik_admin_service.py @@ -12,6 +12,7 @@ from app.core.config import Settings class AuthentikSyncResult: user_id: int action: str + authentik_sub: str | None = None class AuthentikAdminService: @@ -66,10 +67,14 @@ class AuthentikAdminService: patch_resp = client.patch(f"/api/v3/core/users/{user_pk}/", json=payload) if patch_resp.status_code >= 400: raise HTTPException(status_code=502, detail="authentik_update_failed") - return AuthentikSyncResult(user_id=user_pk, action="updated") + return AuthentikSyncResult(user_id=user_pk, action="updated", authentik_sub=existing.get("uid")) create_resp = client.post("/api/v3/core/users/", json=payload) if create_resp.status_code >= 400: raise HTTPException(status_code=502, detail="authentik_create_failed") created = create_resp.json() - return AuthentikSyncResult(user_id=int(created["pk"]), action="created") + return AuthentikSyncResult( + user_id=int(created["pk"]), + action="created", + authentik_sub=created.get("uid"), + )