diff --git a/README.md b/README.md index e94dcd7..35ccead 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ python scripts/generate_api_key_hash.py 'YOUR_PLAIN_KEY' - `GET /me` (Bearer token required) - `GET /me/permissions/snapshot` (Bearer token required) - `POST /internal/users/upsert-by-sub` -- `GET /internal/permissions/{authentik_sub}/snapshot` +- `GET /internal/permissions/{user_sub}/snapshot` - `POST /internal/authentik/users/ensure` - `POST /admin/permissions/grant` - `POST /admin/permissions/revoke` @@ -58,7 +58,7 @@ python scripts/generate_api_key_hash.py 'YOUR_PLAIN_KEY' - `GET|POST /admin/sites` - `GET /admin/members` - `GET|POST /admin/permission-groups` -- `POST|DELETE /admin/permission-groups/{group_key}/members/{authentik_sub}` +- `POST|DELETE /admin/permission-groups/{group_key}/members/{user_sub}` - `POST /admin/permission-groups/{group_key}/permissions/grant|revoke` - `GET /internal/systems` - `GET /internal/modules` diff --git a/app/api/admin.py b/app/api/admin.py index c01fc64..7a393b3 100644 --- a/app/api/admin.py +++ b/app/api/admin.py @@ -71,7 +71,7 @@ def grant_permission( perms_repo = PermissionsRepository(db) user = users_repo.upsert_by_sub( - authentik_sub=payload.authentik_sub, + user_sub=payload.user_sub, username=None, email=payload.email, display_name=payload.display_name, @@ -99,7 +99,7 @@ def revoke_permission( users_repo = UsersRepository(db) perms_repo = PermissionsRepository(db) - user = users_repo.get_by_sub(payload.authentik_sub) + user = users_repo.get_by_sub(payload.user_sub) if user is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="user_not_found") diff --git a/app/api/admin_catalog.py b/app/api/admin_catalog.py index 13c5472..15cecc6 100644 --- a/app/api/admin_catalog.py +++ b/app/api/admin_catalog.py @@ -135,8 +135,8 @@ def _generate_api_key() -> str: def _sync_member_to_authentik( *, - authentik_sub: str | None, - authentik_user_id: int | None, + user_sub: str | None, + idp_user_id: int | None, username: str | None, email: str | None, display_name: str | None, @@ -147,17 +147,17 @@ def _sync_member_to_authentik( settings = get_settings() service = AuthentikAdminService(settings=settings) result = service.ensure_user( - sub=authentik_sub, + sub=user_sub, email=email, username=username, display_name=display_name, is_active=is_active, - authentik_user_id=authentik_user_id, + idp_user_id=idp_user_id, ) return { - "authentik_user_id": result.user_id, + "idp_user_id": result.user_id, "sync_action": result.action, - "authentik_sub": result.authentik_sub or "", + "user_sub": result.user_sub or "", } @@ -316,7 +316,7 @@ def list_system_members( return { "items": [ MemberRelationItem( - authentik_sub=m.authentik_sub, + user_sub=m.user_sub, email=m.email, display_name=m.display_name, is_active=m.is_active, @@ -359,7 +359,7 @@ def list_module_members( return { "items": [ MemberRelationItem( - authentik_sub=m.authentik_sub, + user_sub=m.user_sub, email=m.email, display_name=m.display_name, is_active=m.is_active, @@ -567,7 +567,7 @@ def list_members( "items": [ MemberItem( id=i.id, - authentik_sub=i.authentik_sub, + user_sub=i.user_sub, username=i.username, email=i.email, display_name=i.display_name, @@ -587,37 +587,37 @@ def upsert_member( db: Session = Depends(get_db), ) -> MemberItem: users_repo = UsersRepository(db) - resolved_sub = payload.authentik_sub + resolved_sub = payload.user_sub resolved_username = payload.username - authentik_user_id = None + idp_user_id = None if payload.sync_to_authentik: - seed_sub = payload.authentik_sub or payload.username + seed_sub = payload.user_sub or payload.username if not seed_sub: - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="authentik_sub_or_username_required") + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="user_sub_or_username_required") sync = _sync_member_to_authentik( - authentik_sub=seed_sub, - authentik_user_id=authentik_user_id, + user_sub=seed_sub, + idp_user_id=idp_user_id, username=payload.username, 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"]) + idp_user_id = int(sync["idp_user_id"]) + if sync.get("user_sub"): + resolved_sub = str(sync["user_sub"]) if not resolved_sub: - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="authentik_sub_required") + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="user_sub_required") row = users_repo.upsert_by_sub( - authentik_sub=resolved_sub, + user_sub=resolved_sub, username=resolved_username, email=payload.email, display_name=payload.display_name, is_active=payload.is_active, - authentik_user_id=authentik_user_id, + idp_user_id=idp_user_id, ) return MemberItem( id=row.id, - authentik_sub=row.authentik_sub, + user_sub=row.user_sub, username=row.username, email=row.email, display_name=row.display_name, @@ -625,14 +625,14 @@ def upsert_member( ) -@router.patch("/members/{authentik_sub}", response_model=MemberItem) +@router.patch("/members/{user_sub}", response_model=MemberItem) def update_member( - authentik_sub: str, + user_sub: str, payload: MemberUpdateRequest, db: Session = Depends(get_db), ) -> MemberItem: users_repo = UsersRepository(db) - row = users_repo.get_by_sub(authentik_sub) + row = users_repo.get_by_sub(user_sub) if not row: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="user_not_found") @@ -641,29 +641,29 @@ def update_member( next_display_name = payload.display_name if payload.display_name is not None else row.display_name next_is_active = payload.is_active if payload.is_active is not None else row.is_active - authentik_user_id = row.authentik_user_id + idp_user_id = row.idp_user_id if payload.sync_to_authentik: sync = _sync_member_to_authentik( - authentik_sub=row.authentik_sub, - authentik_user_id=row.authentik_user_id, + user_sub=row.user_sub, + idp_user_id=row.idp_user_id, username=next_username, email=next_email, display_name=next_display_name, is_active=next_is_active, ) - authentik_user_id = int(sync["authentik_user_id"]) + idp_user_id = int(sync["idp_user_id"]) row = users_repo.upsert_by_sub( - authentik_sub=row.authentik_sub, + user_sub=row.user_sub, username=next_username, email=next_email, display_name=next_display_name, is_active=next_is_active, - authentik_user_id=authentik_user_id, + idp_user_id=idp_user_id, ) return MemberItem( id=row.id, - authentik_sub=row.authentik_sub, + user_sub=row.user_sub, username=row.username, email=row.email, display_name=row.display_name, @@ -671,78 +671,78 @@ def update_member( ) -@router.delete("/members/{authentik_sub}") +@router.delete("/members/{user_sub}") def delete_member( - authentik_sub: str, + user_sub: str, db: Session = Depends(get_db), ) -> dict[str, int | str]: users_repo = UsersRepository(db) - row = users_repo.get_by_sub(authentik_sub) + row = users_repo.get_by_sub(user_sub) if not row: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="user_not_found") settings = get_settings() service = AuthentikAdminService(settings=settings) service.delete_user( - authentik_user_id=row.authentik_user_id, + idp_user_id=row.idp_user_id, email=row.email, username=row.username, ) - db.execute(delete(PermissionGroupMember).where(PermissionGroupMember.authentik_sub == authentik_sub)) + db.execute(delete(PermissionGroupMember).where(PermissionGroupMember.user_sub == user_sub)) db.delete(row) db.commit() return {"deleted": 1, "result": "deleted"} -@router.post("/members/{authentik_sub}/password/reset", response_model=MemberPasswordResetResponse) +@router.post("/members/{user_sub}/password/reset", response_model=MemberPasswordResetResponse) def reset_member_password( - authentik_sub: str, + user_sub: str, db: Session = Depends(get_db), ) -> MemberPasswordResetResponse: users_repo = UsersRepository(db) - user = users_repo.get_by_sub(authentik_sub) + user = users_repo.get_by_sub(user_sub) if not user: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="user_not_found") settings = get_settings() service = AuthentikAdminService(settings=settings) result = service.reset_password( - authentik_user_id=user.authentik_user_id, + idp_user_id=user.idp_user_id, email=user.email, username=user.username, ) user = users_repo.upsert_by_sub( - authentik_sub=user.authentik_sub, + user_sub=user.user_sub, username=user.username, email=user.email, display_name=user.display_name, is_active=user.is_active, - authentik_user_id=result.user_id, + idp_user_id=result.user_id, ) - return MemberPasswordResetResponse(authentik_sub=user.authentik_sub, temporary_password=result.temporary_password) + return MemberPasswordResetResponse(user_sub=user.user_sub, temporary_password=result.temporary_password) -@router.get("/members/{authentik_sub}/permission-groups", response_model=MemberPermissionGroupsResponse) +@router.get("/members/{user_sub}/permission-groups", response_model=MemberPermissionGroupsResponse) def get_member_permission_groups( - authentik_sub: str, + user_sub: str, db: Session = Depends(get_db), ) -> MemberPermissionGroupsResponse: users_repo = UsersRepository(db) groups_repo = PermissionGroupsRepository(db) - user = users_repo.get_by_sub(authentik_sub) + user = users_repo.get_by_sub(user_sub) if not user: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="user_not_found") - group_keys = groups_repo.list_group_keys_by_member_sub(authentik_sub) - return MemberPermissionGroupsResponse(authentik_sub=authentik_sub, group_keys=group_keys) + group_keys = groups_repo.list_group_keys_by_member_sub(user_sub) + return MemberPermissionGroupsResponse(user_sub=user_sub, group_keys=group_keys) -@router.put("/members/{authentik_sub}/permission-groups", response_model=MemberPermissionGroupsResponse) +@router.put("/members/{user_sub}/permission-groups", response_model=MemberPermissionGroupsResponse) def set_member_permission_groups( - authentik_sub: str, + user_sub: str, payload: MemberPermissionGroupsUpdateRequest, db: Session = Depends(get_db), ) -> MemberPermissionGroupsResponse: users_repo = UsersRepository(db) groups_repo = PermissionGroupsRepository(db) - user = users_repo.get_by_sub(authentik_sub) + user = users_repo.get_by_sub(user_sub) if not user: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="user_not_found") @@ -753,8 +753,8 @@ def set_member_permission_groups( if missing: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"group_not_found:{','.join(missing)}") - groups_repo.replace_member_groups(authentik_sub, [g.id for g in groups]) - return MemberPermissionGroupsResponse(authentik_sub=authentik_sub, group_keys=unique_group_keys) + groups_repo.replace_member_groups(user_sub, [g.id for g in groups]) + return MemberPermissionGroupsResponse(user_sub=user_sub, group_keys=unique_group_keys) @router.get("/api-clients") @@ -1023,31 +1023,31 @@ def delete_permission_group( return {"deleted": 1, "result": "deleted"} -@router.post("/permission-groups/{group_key}/members/{authentik_sub}") +@router.post("/permission-groups/{group_key}/members/{user_sub}") def add_group_member( group_key: str, - authentik_sub: str, + user_sub: str, db: Session = Depends(get_db), ) -> dict[str, str]: groups_repo = PermissionGroupsRepository(db) group = groups_repo.get_by_key(group_key) if not group: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="group_not_found") - row = groups_repo.add_member_if_not_exists(group.id, authentik_sub) + row = groups_repo.add_member_if_not_exists(group.id, user_sub) return {"membership_id": row.id, "result": "added"} -@router.delete("/permission-groups/{group_key}/members/{authentik_sub}") +@router.delete("/permission-groups/{group_key}/members/{user_sub}") def remove_group_member( group_key: str, - authentik_sub: str, + user_sub: str, db: Session = Depends(get_db), ) -> dict[str, int | str]: groups_repo = PermissionGroupsRepository(db) group = groups_repo.get_by_key(group_key) if not group: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="group_not_found") - deleted = groups_repo.remove_member(group.id, authentik_sub) + deleted = groups_repo.remove_member(group.id, user_sub) return {"deleted": deleted, "result": "removed"} diff --git a/app/api/internal.py b/app/api/internal.py index f580d55..e0578b0 100644 --- a/app/api/internal.py +++ b/app/api/internal.py @@ -23,7 +23,7 @@ def upsert_user_by_sub( ) -> InternalUpsertUserBySubResponse: repo = UsersRepository(db) user = repo.upsert_by_sub( - authentik_sub=payload.sub, + user_sub=payload.user_sub, username=payload.username, email=payload.email, display_name=payload.display_name, @@ -31,8 +31,8 @@ def upsert_user_by_sub( ) return { "id": user.id, - "sub": user.authentik_sub, - "authentik_user_id": user.authentik_user_id, + "user_sub": user.user_sub, + "idp_user_id": user.idp_user_id, "username": user.username, "email": user.email, "display_name": user.display_name, @@ -40,20 +40,20 @@ def upsert_user_by_sub( } -@router.get("/permissions/{authentik_sub}/snapshot", response_model=PermissionSnapshotResponse) +@router.get("/permissions/{user_sub}/snapshot", response_model=PermissionSnapshotResponse) def get_permission_snapshot( - authentik_sub: str, + user_sub: str, db: Session = Depends(get_db), ) -> PermissionSnapshotResponse: users_repo = UsersRepository(db) perms_repo = PermissionsRepository(db) - user = users_repo.get_by_sub(authentik_sub) + user = users_repo.get_by_sub(user_sub) if user is None: - return PermissionSnapshotResponse(authentik_sub=authentik_sub, permissions=[]) + return PermissionSnapshotResponse(user_sub=user_sub, permissions=[]) - permissions = perms_repo.list_by_user(user.id, user.authentik_sub) - return PermissionService.build_snapshot(authentik_sub=authentik_sub, permissions=permissions) + permissions = perms_repo.list_by_user(user.id, user.user_sub) + return PermissionService.build_snapshot(user_sub=user_sub, permissions=permissions) @router.post("/authentik/users/ensure", response_model=AuthentikEnsureUserResponse) @@ -64,7 +64,7 @@ def ensure_authentik_user( settings = get_settings() authentik_service = AuthentikAdminService(settings=settings) sync_result = authentik_service.ensure_user( - sub=payload.sub, + sub=payload.user_sub, email=payload.email, username=payload.username, display_name=payload.display_name, @@ -72,17 +72,17 @@ def ensure_authentik_user( ) users_repo = UsersRepository(db) - resolved_sub = payload.sub or "" - if sync_result.authentik_sub: - resolved_sub = sync_result.authentik_sub + resolved_sub = payload.user_sub or "" + if sync_result.user_sub: + resolved_sub = sync_result.user_sub if not resolved_sub: raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail="authentik_missing_sub") users_repo.upsert_by_sub( - authentik_sub=resolved_sub, + user_sub=resolved_sub, username=payload.username, email=payload.email, display_name=payload.display_name, is_active=payload.is_active, - authentik_user_id=sync_result.user_id, + idp_user_id=sync_result.user_id, ) - return AuthentikEnsureUserResponse(authentik_user_id=sync_result.user_id, action=sync_result.action) + return AuthentikEnsureUserResponse(idp_user_id=sync_result.user_id, action=sync_result.action) diff --git a/app/api/internal_catalog.py b/app/api/internal_catalog.py index 5d43536..ad048ff 100644 --- a/app/api/internal_catalog.py +++ b/app/api/internal_catalog.py @@ -100,7 +100,7 @@ def internal_list_members( "items": [ { "id": i.id, - "authentik_sub": i.authentik_sub, + "user_sub": i.user_sub, "username": i.username, "email": i.email, "display_name": i.display_name, diff --git a/app/api/me.py b/app/api/me.py index 1ec711d..e9e1c2b 100644 --- a/app/api/me.py +++ b/app/api/me.py @@ -21,13 +21,13 @@ def get_me( try: users_repo = UsersRepository(db) user = users_repo.upsert_by_sub( - authentik_sub=principal.sub, + user_sub=principal.sub, username=principal.preferred_username, email=principal.email, display_name=principal.name or principal.preferred_username, is_active=True, ) - return MeSummaryResponse(sub=user.authentik_sub, email=user.email, display_name=user.display_name) + return MeSummaryResponse(sub=user.user_sub, email=user.email, display_name=user.display_name) except SQLAlchemyError: # DB schema compatibility fallback for local bring-up. return MeSummaryResponse( @@ -47,13 +47,13 @@ def get_my_permission_snapshot( perms_repo = PermissionsRepository(db) user = users_repo.upsert_by_sub( - authentik_sub=principal.sub, + user_sub=principal.sub, username=principal.preferred_username, email=principal.email, display_name=principal.name or principal.preferred_username, is_active=True, ) - permissions = perms_repo.list_by_user(user.id, user.authentik_sub) - return PermissionService.build_snapshot(authentik_sub=principal.sub, permissions=permissions) + permissions = perms_repo.list_by_user(user.id, user.user_sub) + return PermissionService.build_snapshot(user_sub=principal.sub, permissions=permissions) except SQLAlchemyError: - return PermissionSnapshotResponse(authentik_sub=principal.sub, permissions=[]) + return PermissionSnapshotResponse(user_sub=principal.sub, permissions=[]) diff --git a/app/models/permission_group_member.py b/app/models/permission_group_member.py index 1f58494..9f67480 100644 --- a/app/models/permission_group_member.py +++ b/app/models/permission_group_member.py @@ -10,11 +10,11 @@ from app.db.base import Base class PermissionGroupMember(Base): __tablename__ = "permission_group_members" - __table_args__ = (UniqueConstraint("group_id", "authentik_sub", name="uq_permission_group_members_group_sub"),) + __table_args__ = (UniqueConstraint("group_id", "user_sub", name="uq_permission_group_members_group_sub"),) id: Mapped[str] = mapped_column(UUID(as_uuid=False), primary_key=True, default=lambda: str(uuid4())) group_id: Mapped[str] = mapped_column( UUID(as_uuid=False), ForeignKey("permission_groups.id", ondelete="CASCADE"), nullable=False ) - authentik_sub: Mapped[str] = mapped_column(String(255), nullable=False) + user_sub: Mapped[str] = mapped_column(String(255), nullable=False) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False) diff --git a/app/models/user.py b/app/models/user.py index 410cead..82eb77f 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -12,8 +12,8 @@ class User(Base): __tablename__ = "users" id: Mapped[str] = mapped_column(UUID(as_uuid=False), primary_key=True, default=lambda: str(uuid4())) - authentik_sub: Mapped[str] = mapped_column(String(255), unique=True, nullable=False, index=True) - authentik_user_id: Mapped[int | None] = mapped_column(Integer) + user_sub: Mapped[str] = mapped_column(String(255), unique=True, nullable=False, index=True) + idp_user_id: Mapped[int | None] = mapped_column(Integer) username: Mapped[str | None] = mapped_column(String(255), unique=True) email: Mapped[str | None] = mapped_column(String(320)) display_name: Mapped[str | None] = mapped_column(String(255)) diff --git a/app/repositories/permission_groups_repo.py b/app/repositories/permission_groups_repo.py index 099349c..d8d4164 100644 --- a/app/repositories/permission_groups_repo.py +++ b/app/repositories/permission_groups_repo.py @@ -46,43 +46,43 @@ class PermissionGroupsRepository: self.db.refresh(item) return item - def add_member_if_not_exists(self, group_id: str, authentik_sub: str) -> PermissionGroupMember: + def add_member_if_not_exists(self, group_id: str, user_sub: str) -> PermissionGroupMember: existing = self.db.scalar( select(PermissionGroupMember).where( - PermissionGroupMember.group_id == group_id, PermissionGroupMember.authentik_sub == authentik_sub + PermissionGroupMember.group_id == group_id, PermissionGroupMember.user_sub == user_sub ) ) if existing: return existing - row = PermissionGroupMember(group_id=group_id, authentik_sub=authentik_sub) + row = PermissionGroupMember(group_id=group_id, user_sub=user_sub) self.db.add(row) self.db.commit() self.db.refresh(row) return row - def remove_member(self, group_id: str, authentik_sub: str) -> int: + def remove_member(self, group_id: str, user_sub: str) -> int: result = self.db.execute( delete(PermissionGroupMember).where( - PermissionGroupMember.group_id == group_id, PermissionGroupMember.authentik_sub == authentik_sub + PermissionGroupMember.group_id == group_id, PermissionGroupMember.user_sub == user_sub ) ) self.db.commit() return int(result.rowcount or 0) - def list_group_keys_by_member_sub(self, authentik_sub: str) -> list[str]: + def list_group_keys_by_member_sub(self, user_sub: str) -> list[str]: stmt = ( select(PermissionGroup.group_key) .select_from(PermissionGroupMember) .join(PermissionGroup, PermissionGroup.id == PermissionGroupMember.group_id) - .where(PermissionGroupMember.authentik_sub == authentik_sub) + .where(PermissionGroupMember.user_sub == user_sub) .order_by(PermissionGroup.group_key.asc()) ) return [row[0] for row in self.db.execute(stmt).all()] - def replace_member_groups(self, authentik_sub: str, group_ids: list[str]) -> None: - self.db.execute(delete(PermissionGroupMember).where(PermissionGroupMember.authentik_sub == authentik_sub)) + def replace_member_groups(self, user_sub: str, group_ids: list[str]) -> None: + self.db.execute(delete(PermissionGroupMember).where(PermissionGroupMember.user_sub == user_sub)) for group_id in group_ids: - self.db.add(PermissionGroupMember(group_id=group_id, authentik_sub=authentik_sub)) + self.db.add(PermissionGroupMember(group_id=group_id, user_sub=user_sub)) self.db.commit() def grant_group_permission( @@ -155,7 +155,7 @@ class PermissionGroupsRepository: self.db.execute(delete(PermissionGroupMember).where(PermissionGroupMember.group_id == group_id)) for sub in normalized_member_subs: - self.db.add(PermissionGroupMember(group_id=group_id, authentik_sub=sub)) + self.db.add(PermissionGroupMember(group_id=group_id, user_sub=sub)) for site_key in normalized_sites: for action in normalized_actions: @@ -199,9 +199,9 @@ class PermissionGroupsRepository: def list_group_member_subs(self, group_id: str) -> list[str]: stmt = ( - select(PermissionGroupMember.authentik_sub) + select(PermissionGroupMember.user_sub) .where(PermissionGroupMember.group_id == group_id) - .order_by(PermissionGroupMember.authentik_sub.asc()) + .order_by(PermissionGroupMember.user_sub.asc()) ) return [row[0] for row in self.db.execute(stmt).all()] @@ -218,10 +218,10 @@ class PermissionGroupsRepository: def list_system_members(self, system_key: str) -> list[User]: stmt = ( select(User) - .join(PermissionGroupMember, PermissionGroupMember.authentik_sub == User.authentik_sub) + .join(PermissionGroupMember, PermissionGroupMember.user_sub == User.user_sub) .join(PermissionGroupPermission, PermissionGroupPermission.group_id == PermissionGroupMember.group_id) .where(PermissionGroupPermission.system == system_key) - .order_by(User.email.asc(), User.authentik_sub.asc()) + .order_by(User.email.asc(), User.user_sub.asc()) .distinct() ) return list(self.db.scalars(stmt).all()) @@ -239,10 +239,10 @@ class PermissionGroupsRepository: def list_module_members(self, system_key: str, module_name: str) -> list[User]: stmt = ( select(User) - .join(PermissionGroupMember, PermissionGroupMember.authentik_sub == User.authentik_sub) + .join(PermissionGroupMember, PermissionGroupMember.user_sub == User.user_sub) .join(PermissionGroupPermission, PermissionGroupPermission.group_id == PermissionGroupMember.group_id) .where(PermissionGroupPermission.system == system_key, PermissionGroupPermission.module == module_name) - .order_by(User.email.asc(), User.authentik_sub.asc()) + .order_by(User.email.asc(), User.user_sub.asc()) .distinct() ) return list(self.db.scalars(stmt).all()) diff --git a/app/repositories/permissions_repo.py b/app/repositories/permissions_repo.py index 6bcc693..78b1941 100644 --- a/app/repositories/permissions_repo.py +++ b/app/repositories/permissions_repo.py @@ -14,7 +14,7 @@ class PermissionsRepository: def __init__(self, db: Session) -> None: self.db = db - def list_by_user(self, user_id: str, authentik_sub: str) -> list[tuple[str, str, str | None, str, str]]: + def list_by_user(self, user_id: str, user_sub: str) -> list[tuple[str, str, str | None, str, str]]: direct_stmt = ( select( literal("direct"), @@ -44,7 +44,7 @@ class PermissionsRepository: ) .select_from(PermissionGroupPermission) .join(PermissionGroupMember, PermissionGroupMember.group_id == PermissionGroupPermission.group_id) - .where(PermissionGroupMember.authentik_sub == authentik_sub) + .where(PermissionGroupMember.user_sub == user_sub) .where(PermissionGroupPermission.action.in_(["view", "edit"])) .where(PermissionGroupPermission.scope_type == "site") ) @@ -138,7 +138,7 @@ class PermissionsRepository: stmt = ( select( UserScopePermission.id, - User.authentik_sub, + User.user_sub, User.email, User.display_name, UserScopePermission.scope_type, @@ -175,7 +175,7 @@ class PermissionsRepository: if keyword: pattern = f"%{keyword}%" cond = or_( - User.authentik_sub.ilike(pattern), + User.user_sub.ilike(pattern), User.email.ilike(pattern), User.display_name.ilike(pattern), Module.module_key.ilike(pattern), @@ -193,7 +193,7 @@ class PermissionsRepository: for row in rows: ( permission_id, - authentik_sub, + user_sub, email, display_name, row_scope_type, @@ -211,7 +211,7 @@ class PermissionsRepository: items.append( { "permission_id": permission_id, - "authentik_sub": authentik_sub, + "user_sub": user_sub, "email": email, "display_name": display_name, "scope_type": row_scope_type, diff --git a/app/repositories/users_repo.py b/app/repositories/users_repo.py index 1517084..71615ac 100644 --- a/app/repositories/users_repo.py +++ b/app/repositories/users_repo.py @@ -8,8 +8,8 @@ class UsersRepository: def __init__(self, db: Session) -> None: self.db = db - def get_by_sub(self, authentik_sub: str) -> User | None: - stmt = select(User).where(User.authentik_sub == authentik_sub) + def get_by_sub(self, user_sub: str) -> User | None: + stmt = select(User).where(User.user_sub == user_sub) return self.db.scalar(stmt) def get_by_id(self, user_id: str) -> User | None: @@ -29,7 +29,7 @@ class UsersRepository: if keyword: pattern = f"%{keyword}%" cond = or_( - User.authentik_sub.ilike(pattern), + User.user_sub.ilike(pattern), User.username.ilike(pattern), User.email.ilike(pattern), User.display_name.ilike(pattern), @@ -48,18 +48,18 @@ class UsersRepository: def upsert_by_sub( self, - authentik_sub: str, + user_sub: str, username: str | None, email: str | None, display_name: str | None, is_active: bool, - authentik_user_id: int | None = None, + idp_user_id: int | None = None, ) -> User: - user = self.get_by_sub(authentik_sub) + user = self.get_by_sub(user_sub) if user is None: user = User( - authentik_sub=authentik_sub, - authentik_user_id=authentik_user_id, + user_sub=user_sub, + idp_user_id=idp_user_id, username=username, email=email, display_name=display_name, @@ -67,8 +67,8 @@ class UsersRepository: ) self.db.add(user) else: - if authentik_user_id is not None: - user.authentik_user_id = authentik_user_id + if idp_user_id is not None: + user.idp_user_id = idp_user_id user.username = username user.email = email user.display_name = display_name diff --git a/app/schemas/authentik_admin.py b/app/schemas/authentik_admin.py index 6d06c0b..44a2222 100644 --- a/app/schemas/authentik_admin.py +++ b/app/schemas/authentik_admin.py @@ -1,8 +1,8 @@ -from pydantic import BaseModel +from pydantic import AliasChoices, BaseModel, Field class AuthentikEnsureUserRequest(BaseModel): - sub: str | None = None + user_sub: str | None = Field(default=None, validation_alias=AliasChoices("user_sub", "sub")) username: str | None = None email: str display_name: str | None = None @@ -10,5 +10,5 @@ class AuthentikEnsureUserRequest(BaseModel): class AuthentikEnsureUserResponse(BaseModel): - authentik_user_id: int + idp_user_id: int action: str diff --git a/app/schemas/catalog.py b/app/schemas/catalog.py index 70b2000..228a6ef 100644 --- a/app/schemas/catalog.py +++ b/app/schemas/catalog.py @@ -77,7 +77,7 @@ class SiteItem(BaseModel): class MemberItem(BaseModel): id: str - authentik_sub: str + user_sub: str username: str | None = None email: str | None = None display_name: str | None = None @@ -85,7 +85,7 @@ class MemberItem(BaseModel): class MemberUpsertRequest(BaseModel): - authentik_sub: str | None = None + user_sub: str | None = None username: str | None = None email: str | None = None display_name: str | None = None @@ -102,7 +102,7 @@ class MemberUpdateRequest(BaseModel): class MemberPasswordResetResponse(BaseModel): - authentik_sub: str + user_sub: str temporary_password: str @@ -144,7 +144,7 @@ class PermissionGroupPermissionItem(BaseModel): class MemberPermissionGroupsResponse(BaseModel): - authentik_sub: str + user_sub: str group_keys: list[str] @@ -172,7 +172,7 @@ class GroupRelationItem(BaseModel): class MemberRelationItem(BaseModel): - authentik_sub: str + user_sub: str email: str | None = None display_name: str | None = None is_active: bool diff --git a/app/schemas/internal.py b/app/schemas/internal.py index 81bfb1b..3ec2d2f 100644 --- a/app/schemas/internal.py +++ b/app/schemas/internal.py @@ -61,7 +61,7 @@ class InternalSiteListResponse(BaseModel): class InternalMemberItem(BaseModel): id: str - authentik_sub: str + user_sub: str username: str | None = None email: str | None = None display_name: str | None = None @@ -77,8 +77,8 @@ class InternalMemberListResponse(BaseModel): class InternalUpsertUserBySubResponse(BaseModel): id: str - sub: str - authentik_user_id: int | None = None + user_sub: str + idp_user_id: int | None = None username: str | None = None email: str | None = None display_name: str | None = None diff --git a/app/schemas/permissions.py b/app/schemas/permissions.py index 2b83224..92b0808 100644 --- a/app/schemas/permissions.py +++ b/app/schemas/permissions.py @@ -8,7 +8,7 @@ ScopeType = Literal["site"] class PermissionGrantRequest(BaseModel): - authentik_sub: str + user_sub: str email: str | None = None display_name: str | None = None scope_type: ScopeType @@ -19,7 +19,7 @@ class PermissionGrantRequest(BaseModel): class PermissionRevokeRequest(BaseModel): - authentik_sub: str + user_sub: str scope_type: ScopeType scope_id: str system: str @@ -36,13 +36,13 @@ class PermissionItem(BaseModel): class PermissionSnapshotResponse(BaseModel): - authentik_sub: str + user_sub: str permissions: list[PermissionItem] class DirectPermissionRow(BaseModel): permission_id: str - authentik_sub: str + user_sub: str email: str | None = None display_name: str | None = None scope_type: ScopeType diff --git a/app/schemas/users.py b/app/schemas/users.py index 9bfd151..bf9cd6d 100644 --- a/app/schemas/users.py +++ b/app/schemas/users.py @@ -1,8 +1,8 @@ -from pydantic import BaseModel +from pydantic import AliasChoices, BaseModel, Field class UserUpsertBySubRequest(BaseModel): - sub: str + user_sub: str = Field(validation_alias=AliasChoices("user_sub", "sub")) username: str | None = None email: str | None = None display_name: str | None = None diff --git a/app/services/authentik_admin_service.py b/app/services/authentik_admin_service.py index 87b9c99..a21e61c 100644 --- a/app/services/authentik_admin_service.py +++ b/app/services/authentik_admin_service.py @@ -14,7 +14,7 @@ from app.core.config import Settings class AuthentikSyncResult: user_id: int action: str - authentik_sub: str | None = None + user_sub: str | None = None @dataclass @@ -108,7 +108,7 @@ class AuthentikAdminService: username: str | None, display_name: str | None, is_active: bool = True, - authentik_user_id: int | None = None, + idp_user_id: int | None = None, ) -> AuthentikSyncResult: resolved_username = username or self._safe_username(sub=sub, email=email) payload = { @@ -120,8 +120,8 @@ class AuthentikAdminService: with self._client() as client: existing = None - if authentik_user_id is not None: - existing = self._lookup_user_by_id(client, authentik_user_id) + if idp_user_id is not None: + existing = self._lookup_user_by_id(client, idp_user_id) if existing is None: existing = self._lookup_user_by_email_or_username(client, email=email, username=resolved_username) @@ -130,7 +130,7 @@ 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", authentik_sub=existing.get("uid")) + return AuthentikSyncResult(user_id=user_pk, action="updated", user_sub=existing.get("uid")) create_resp = client.post("/api/v3/core/users/", json=payload) if create_resp.status_code >= 400: @@ -139,20 +139,20 @@ class AuthentikAdminService: return AuthentikSyncResult( user_id=int(created["pk"]), action="created", - authentik_sub=created.get("uid"), + user_sub=created.get("uid"), ) def reset_password( self, *, - authentik_user_id: int | None, + idp_user_id: int | None, email: str | None, username: str | None, ) -> AuthentikPasswordResetResult: with self._client() as client: existing = None - if authentik_user_id is not None: - existing = self._lookup_user_by_id(client, authentik_user_id) + if idp_user_id is not None: + existing = self._lookup_user_by_id(client, idp_user_id) if existing is None: existing = self._lookup_user_by_email_or_username(client, email=email, username=username) if not existing or existing.get("pk") is None: @@ -169,14 +169,14 @@ class AuthentikAdminService: def delete_user( self, *, - authentik_user_id: int | None, + idp_user_id: int | None, email: str | None, username: str | None, ) -> AuthentikDeleteResult: with self._client() as client: existing = None - if authentik_user_id is not None: - existing = self._lookup_user_by_id(client, authentik_user_id) + if idp_user_id is not None: + existing = self._lookup_user_by_id(client, idp_user_id) if existing is None: existing = self._lookup_user_by_email_or_username(client, email=email, username=username) if not existing or existing.get("pk") is None: diff --git a/app/services/permission_service.py b/app/services/permission_service.py index 4cdceb4..f261626 100644 --- a/app/services/permission_service.py +++ b/app/services/permission_service.py @@ -3,9 +3,9 @@ from app.schemas.permissions import PermissionItem, PermissionSnapshotResponse class PermissionService: @staticmethod - def build_snapshot(authentik_sub: str, permissions: list[tuple[str, str, str | None, str, str]]) -> PermissionSnapshotResponse: + def build_snapshot(user_sub: str, permissions: list[tuple[str, str, str | None, str, str]]) -> PermissionSnapshotResponse: return PermissionSnapshotResponse( - authentik_sub=authentik_sub, + user_sub=user_sub, permissions=[ PermissionItem(scope_type=s_type, scope_id=s_id, system=system, module=module, action=action) for s_type, s_id, system, module, action in permissions diff --git a/scripts/init_schema.sql b/scripts/init_schema.sql index 07e5258..6f401e9 100644 --- a/scripts/init_schema.sql +++ b/scripts/init_schema.sql @@ -19,8 +19,8 @@ DROP TABLE IF EXISTS permissions CASCADE; CREATE TABLE users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - authentik_sub TEXT NOT NULL UNIQUE, - authentik_user_id INTEGER, + user_sub TEXT NOT NULL UNIQUE, + idp_user_id INTEGER, username TEXT UNIQUE, email TEXT UNIQUE, display_name TEXT, @@ -105,9 +105,9 @@ CREATE TABLE permission_groups ( CREATE TABLE permission_group_members ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), group_id UUID NOT NULL REFERENCES permission_groups(id) ON DELETE CASCADE, - authentik_sub TEXT NOT NULL, + user_sub TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - CONSTRAINT uq_permission_group_members_group_sub UNIQUE (group_id, authentik_sub) + CONSTRAINT uq_permission_group_members_group_sub UNIQUE (group_id, user_sub) ); CREATE TABLE permission_group_permissions ( @@ -144,7 +144,7 @@ INSERT INTO systems (system_key, name, status) VALUES ('member', 'Member Center', 'active') ON CONFLICT (system_key) DO NOTHING; -CREATE INDEX idx_users_authentik_sub ON users(authentik_sub); +CREATE INDEX idx_users_user_sub ON users(user_sub); CREATE INDEX idx_users_username ON users(username); CREATE INDEX idx_sites_company_id ON sites(company_id); CREATE INDEX idx_usp_user_id ON user_scope_permissions(user_id); @@ -153,7 +153,7 @@ CREATE INDEX idx_usp_site_id ON user_scope_permissions(site_id); CREATE UNIQUE INDEX uq_usp_site ON user_scope_permissions(user_id, module_id, action, scope_type, site_id); CREATE INDEX idx_pgm_group_id ON permission_group_members(group_id); -CREATE INDEX idx_pgm_authentik_sub ON permission_group_members(authentik_sub); +CREATE INDEX idx_pgm_user_sub ON permission_group_members(user_sub); CREATE INDEX idx_pgp_group_id ON permission_group_permissions(group_id); CREATE INDEX idx_pgp_scope_site ON permission_group_permissions(scope_id); CREATE INDEX idx_api_clients_status ON api_clients(status); diff --git a/scripts/migrate_add_authentik_user_id.sql b/scripts/migrate_add_authentik_user_id.sql index 3a742aa..c638e20 100644 --- a/scripts/migrate_add_authentik_user_id.sql +++ b/scripts/migrate_add_authentik_user_id.sql @@ -1,2 +1,2 @@ ALTER TABLE users - ADD COLUMN IF NOT EXISTS authentik_user_id INTEGER; + ADD COLUMN IF NOT EXISTS idp_user_id INTEGER; diff --git a/scripts/migrate_align_company_site_member_system.sql b/scripts/migrate_align_company_site_member_system.sql index 4df9cef..7789e86 100644 --- a/scripts/migrate_align_company_site_member_system.sql +++ b/scripts/migrate_align_company_site_member_system.sql @@ -46,9 +46,9 @@ CREATE TABLE IF NOT EXISTS permission_groups ( CREATE TABLE IF NOT EXISTS permission_group_members ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), group_id UUID NOT NULL REFERENCES permission_groups(id) ON DELETE CASCADE, - authentik_sub TEXT NOT NULL, + user_sub TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - CONSTRAINT uq_permission_group_members_group_sub UNIQUE (group_id, authentik_sub) + CONSTRAINT uq_permission_group_members_group_sub UNIQUE (group_id, user_sub) ); CREATE TABLE IF NOT EXISTS permission_group_permissions ( @@ -64,7 +64,7 @@ CREATE TABLE IF NOT EXISTS permission_group_permissions ( CREATE INDEX IF NOT EXISTS idx_systems_system_key ON systems(system_key); CREATE INDEX IF NOT EXISTS idx_pgm_group_id ON permission_group_members(group_id); -CREATE INDEX IF NOT EXISTS idx_pgm_authentik_sub ON permission_group_members(authentik_sub); +CREATE INDEX IF NOT EXISTS idx_pgm_user_sub ON permission_group_members(user_sub); CREATE INDEX IF NOT EXISTS idx_pgp_group_id ON permission_group_permissions(group_id); CREATE UNIQUE INDEX IF NOT EXISTS uq_pgp_group_rule diff --git a/scripts/migrate_rename_identity_columns.sql b/scripts/migrate_rename_identity_columns.sql new file mode 100644 index 0000000..7a78ed5 --- /dev/null +++ b/scripts/migrate_rename_identity_columns.sql @@ -0,0 +1,43 @@ +BEGIN; + +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'users' AND column_name = 'authentik_sub' + ) AND NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'users' AND column_name = 'user_sub' + ) THEN + ALTER TABLE users RENAME COLUMN authentik_sub TO user_sub; + END IF; + + IF EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'users' AND column_name = 'authentik_user_id' + ) AND NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'users' AND column_name = 'idp_user_id' + ) THEN + ALTER TABLE users RENAME COLUMN authentik_user_id TO idp_user_id; + END IF; + + IF EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'permission_group_members' AND column_name = 'authentik_sub' + ) AND NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'permission_group_members' AND column_name = 'user_sub' + ) THEN + ALTER TABLE permission_group_members RENAME COLUMN authentik_sub TO user_sub; + END IF; +END +$$; + +ALTER INDEX IF EXISTS idx_users_authentik_sub RENAME TO idx_users_user_sub; +ALTER INDEX IF EXISTS idx_pgm_authentik_sub RENAME TO idx_pgm_user_sub; + +CREATE INDEX IF NOT EXISTS idx_users_user_sub ON users(user_sub); +CREATE INDEX IF NOT EXISTS idx_pgm_user_sub ON permission_group_members(user_sub); + +COMMIT;