refactor(identity): rename authentik_sub to user_sub and authentik_user_id to idp_user_id
This commit is contained in:
@@ -48,7 +48,7 @@ python scripts/generate_api_key_hash.py 'YOUR_PLAIN_KEY'
|
|||||||
- `GET /me` (Bearer token required)
|
- `GET /me` (Bearer token required)
|
||||||
- `GET /me/permissions/snapshot` (Bearer token required)
|
- `GET /me/permissions/snapshot` (Bearer token required)
|
||||||
- `POST /internal/users/upsert-by-sub`
|
- `POST /internal/users/upsert-by-sub`
|
||||||
- `GET /internal/permissions/{authentik_sub}/snapshot`
|
- `GET /internal/permissions/{user_sub}/snapshot`
|
||||||
- `POST /internal/authentik/users/ensure`
|
- `POST /internal/authentik/users/ensure`
|
||||||
- `POST /admin/permissions/grant`
|
- `POST /admin/permissions/grant`
|
||||||
- `POST /admin/permissions/revoke`
|
- `POST /admin/permissions/revoke`
|
||||||
@@ -58,7 +58,7 @@ python scripts/generate_api_key_hash.py 'YOUR_PLAIN_KEY'
|
|||||||
- `GET|POST /admin/sites`
|
- `GET|POST /admin/sites`
|
||||||
- `GET /admin/members`
|
- `GET /admin/members`
|
||||||
- `GET|POST /admin/permission-groups`
|
- `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`
|
- `POST /admin/permission-groups/{group_key}/permissions/grant|revoke`
|
||||||
- `GET /internal/systems`
|
- `GET /internal/systems`
|
||||||
- `GET /internal/modules`
|
- `GET /internal/modules`
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ def grant_permission(
|
|||||||
perms_repo = PermissionsRepository(db)
|
perms_repo = PermissionsRepository(db)
|
||||||
|
|
||||||
user = users_repo.upsert_by_sub(
|
user = users_repo.upsert_by_sub(
|
||||||
authentik_sub=payload.authentik_sub,
|
user_sub=payload.user_sub,
|
||||||
username=None,
|
username=None,
|
||||||
email=payload.email,
|
email=payload.email,
|
||||||
display_name=payload.display_name,
|
display_name=payload.display_name,
|
||||||
@@ -99,7 +99,7 @@ def revoke_permission(
|
|||||||
users_repo = UsersRepository(db)
|
users_repo = UsersRepository(db)
|
||||||
perms_repo = PermissionsRepository(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:
|
if user is None:
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="user_not_found")
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="user_not_found")
|
||||||
|
|
||||||
|
|||||||
@@ -135,8 +135,8 @@ def _generate_api_key() -> str:
|
|||||||
|
|
||||||
def _sync_member_to_authentik(
|
def _sync_member_to_authentik(
|
||||||
*,
|
*,
|
||||||
authentik_sub: str | None,
|
user_sub: str | None,
|
||||||
authentik_user_id: int | None,
|
idp_user_id: int | None,
|
||||||
username: str | None,
|
username: str | None,
|
||||||
email: str | None,
|
email: str | None,
|
||||||
display_name: str | None,
|
display_name: str | None,
|
||||||
@@ -147,17 +147,17 @@ def _sync_member_to_authentik(
|
|||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
service = AuthentikAdminService(settings=settings)
|
service = AuthentikAdminService(settings=settings)
|
||||||
result = service.ensure_user(
|
result = service.ensure_user(
|
||||||
sub=authentik_sub,
|
sub=user_sub,
|
||||||
email=email,
|
email=email,
|
||||||
username=username,
|
username=username,
|
||||||
display_name=display_name,
|
display_name=display_name,
|
||||||
is_active=is_active,
|
is_active=is_active,
|
||||||
authentik_user_id=authentik_user_id,
|
idp_user_id=idp_user_id,
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
"authentik_user_id": result.user_id,
|
"idp_user_id": result.user_id,
|
||||||
"sync_action": result.action,
|
"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 {
|
return {
|
||||||
"items": [
|
"items": [
|
||||||
MemberRelationItem(
|
MemberRelationItem(
|
||||||
authentik_sub=m.authentik_sub,
|
user_sub=m.user_sub,
|
||||||
email=m.email,
|
email=m.email,
|
||||||
display_name=m.display_name,
|
display_name=m.display_name,
|
||||||
is_active=m.is_active,
|
is_active=m.is_active,
|
||||||
@@ -359,7 +359,7 @@ def list_module_members(
|
|||||||
return {
|
return {
|
||||||
"items": [
|
"items": [
|
||||||
MemberRelationItem(
|
MemberRelationItem(
|
||||||
authentik_sub=m.authentik_sub,
|
user_sub=m.user_sub,
|
||||||
email=m.email,
|
email=m.email,
|
||||||
display_name=m.display_name,
|
display_name=m.display_name,
|
||||||
is_active=m.is_active,
|
is_active=m.is_active,
|
||||||
@@ -567,7 +567,7 @@ def list_members(
|
|||||||
"items": [
|
"items": [
|
||||||
MemberItem(
|
MemberItem(
|
||||||
id=i.id,
|
id=i.id,
|
||||||
authentik_sub=i.authentik_sub,
|
user_sub=i.user_sub,
|
||||||
username=i.username,
|
username=i.username,
|
||||||
email=i.email,
|
email=i.email,
|
||||||
display_name=i.display_name,
|
display_name=i.display_name,
|
||||||
@@ -587,37 +587,37 @@ def upsert_member(
|
|||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
) -> MemberItem:
|
) -> MemberItem:
|
||||||
users_repo = UsersRepository(db)
|
users_repo = UsersRepository(db)
|
||||||
resolved_sub = payload.authentik_sub
|
resolved_sub = payload.user_sub
|
||||||
resolved_username = payload.username
|
resolved_username = payload.username
|
||||||
authentik_user_id = None
|
idp_user_id = None
|
||||||
if payload.sync_to_authentik:
|
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:
|
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(
|
sync = _sync_member_to_authentik(
|
||||||
authentik_sub=seed_sub,
|
user_sub=seed_sub,
|
||||||
authentik_user_id=authentik_user_id,
|
idp_user_id=idp_user_id,
|
||||||
username=payload.username,
|
username=payload.username,
|
||||||
email=payload.email,
|
email=payload.email,
|
||||||
display_name=payload.display_name,
|
display_name=payload.display_name,
|
||||||
is_active=payload.is_active,
|
is_active=payload.is_active,
|
||||||
)
|
)
|
||||||
authentik_user_id = int(sync["authentik_user_id"])
|
idp_user_id = int(sync["idp_user_id"])
|
||||||
if sync.get("authentik_sub"):
|
if sync.get("user_sub"):
|
||||||
resolved_sub = str(sync["authentik_sub"])
|
resolved_sub = str(sync["user_sub"])
|
||||||
if not resolved_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(
|
row = users_repo.upsert_by_sub(
|
||||||
authentik_sub=resolved_sub,
|
user_sub=resolved_sub,
|
||||||
username=resolved_username,
|
username=resolved_username,
|
||||||
email=payload.email,
|
email=payload.email,
|
||||||
display_name=payload.display_name,
|
display_name=payload.display_name,
|
||||||
is_active=payload.is_active,
|
is_active=payload.is_active,
|
||||||
authentik_user_id=authentik_user_id,
|
idp_user_id=idp_user_id,
|
||||||
)
|
)
|
||||||
return MemberItem(
|
return MemberItem(
|
||||||
id=row.id,
|
id=row.id,
|
||||||
authentik_sub=row.authentik_sub,
|
user_sub=row.user_sub,
|
||||||
username=row.username,
|
username=row.username,
|
||||||
email=row.email,
|
email=row.email,
|
||||||
display_name=row.display_name,
|
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(
|
def update_member(
|
||||||
authentik_sub: str,
|
user_sub: str,
|
||||||
payload: MemberUpdateRequest,
|
payload: MemberUpdateRequest,
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
) -> MemberItem:
|
) -> MemberItem:
|
||||||
users_repo = UsersRepository(db)
|
users_repo = UsersRepository(db)
|
||||||
row = users_repo.get_by_sub(authentik_sub)
|
row = users_repo.get_by_sub(user_sub)
|
||||||
if not row:
|
if not row:
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="user_not_found")
|
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_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
|
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:
|
if payload.sync_to_authentik:
|
||||||
sync = _sync_member_to_authentik(
|
sync = _sync_member_to_authentik(
|
||||||
authentik_sub=row.authentik_sub,
|
user_sub=row.user_sub,
|
||||||
authentik_user_id=row.authentik_user_id,
|
idp_user_id=row.idp_user_id,
|
||||||
username=next_username,
|
username=next_username,
|
||||||
email=next_email,
|
email=next_email,
|
||||||
display_name=next_display_name,
|
display_name=next_display_name,
|
||||||
is_active=next_is_active,
|
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(
|
row = users_repo.upsert_by_sub(
|
||||||
authentik_sub=row.authentik_sub,
|
user_sub=row.user_sub,
|
||||||
username=next_username,
|
username=next_username,
|
||||||
email=next_email,
|
email=next_email,
|
||||||
display_name=next_display_name,
|
display_name=next_display_name,
|
||||||
is_active=next_is_active,
|
is_active=next_is_active,
|
||||||
authentik_user_id=authentik_user_id,
|
idp_user_id=idp_user_id,
|
||||||
)
|
)
|
||||||
return MemberItem(
|
return MemberItem(
|
||||||
id=row.id,
|
id=row.id,
|
||||||
authentik_sub=row.authentik_sub,
|
user_sub=row.user_sub,
|
||||||
username=row.username,
|
username=row.username,
|
||||||
email=row.email,
|
email=row.email,
|
||||||
display_name=row.display_name,
|
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(
|
def delete_member(
|
||||||
authentik_sub: str,
|
user_sub: str,
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
) -> dict[str, int | str]:
|
) -> dict[str, int | str]:
|
||||||
users_repo = UsersRepository(db)
|
users_repo = UsersRepository(db)
|
||||||
row = users_repo.get_by_sub(authentik_sub)
|
row = users_repo.get_by_sub(user_sub)
|
||||||
if not row:
|
if not row:
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="user_not_found")
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="user_not_found")
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
service = AuthentikAdminService(settings=settings)
|
service = AuthentikAdminService(settings=settings)
|
||||||
service.delete_user(
|
service.delete_user(
|
||||||
authentik_user_id=row.authentik_user_id,
|
idp_user_id=row.idp_user_id,
|
||||||
email=row.email,
|
email=row.email,
|
||||||
username=row.username,
|
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.delete(row)
|
||||||
db.commit()
|
db.commit()
|
||||||
return {"deleted": 1, "result": "deleted"}
|
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(
|
def reset_member_password(
|
||||||
authentik_sub: str,
|
user_sub: str,
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
) -> MemberPasswordResetResponse:
|
) -> MemberPasswordResetResponse:
|
||||||
users_repo = UsersRepository(db)
|
users_repo = UsersRepository(db)
|
||||||
user = users_repo.get_by_sub(authentik_sub)
|
user = users_repo.get_by_sub(user_sub)
|
||||||
if not user:
|
if not user:
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="user_not_found")
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="user_not_found")
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
service = AuthentikAdminService(settings=settings)
|
service = AuthentikAdminService(settings=settings)
|
||||||
result = service.reset_password(
|
result = service.reset_password(
|
||||||
authentik_user_id=user.authentik_user_id,
|
idp_user_id=user.idp_user_id,
|
||||||
email=user.email,
|
email=user.email,
|
||||||
username=user.username,
|
username=user.username,
|
||||||
)
|
)
|
||||||
user = users_repo.upsert_by_sub(
|
user = users_repo.upsert_by_sub(
|
||||||
authentik_sub=user.authentik_sub,
|
user_sub=user.user_sub,
|
||||||
username=user.username,
|
username=user.username,
|
||||||
email=user.email,
|
email=user.email,
|
||||||
display_name=user.display_name,
|
display_name=user.display_name,
|
||||||
is_active=user.is_active,
|
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(
|
def get_member_permission_groups(
|
||||||
authentik_sub: str,
|
user_sub: str,
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
) -> MemberPermissionGroupsResponse:
|
) -> MemberPermissionGroupsResponse:
|
||||||
users_repo = UsersRepository(db)
|
users_repo = UsersRepository(db)
|
||||||
groups_repo = PermissionGroupsRepository(db)
|
groups_repo = PermissionGroupsRepository(db)
|
||||||
user = users_repo.get_by_sub(authentik_sub)
|
user = users_repo.get_by_sub(user_sub)
|
||||||
if not user:
|
if not user:
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="user_not_found")
|
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)
|
group_keys = groups_repo.list_group_keys_by_member_sub(user_sub)
|
||||||
return MemberPermissionGroupsResponse(authentik_sub=authentik_sub, group_keys=group_keys)
|
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(
|
def set_member_permission_groups(
|
||||||
authentik_sub: str,
|
user_sub: str,
|
||||||
payload: MemberPermissionGroupsUpdateRequest,
|
payload: MemberPermissionGroupsUpdateRequest,
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
) -> MemberPermissionGroupsResponse:
|
) -> MemberPermissionGroupsResponse:
|
||||||
users_repo = UsersRepository(db)
|
users_repo = UsersRepository(db)
|
||||||
groups_repo = PermissionGroupsRepository(db)
|
groups_repo = PermissionGroupsRepository(db)
|
||||||
user = users_repo.get_by_sub(authentik_sub)
|
user = users_repo.get_by_sub(user_sub)
|
||||||
if not user:
|
if not user:
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="user_not_found")
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="user_not_found")
|
||||||
|
|
||||||
@@ -753,8 +753,8 @@ def set_member_permission_groups(
|
|||||||
if missing:
|
if missing:
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"group_not_found:{','.join(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])
|
groups_repo.replace_member_groups(user_sub, [g.id for g in groups])
|
||||||
return MemberPermissionGroupsResponse(authentik_sub=authentik_sub, group_keys=unique_group_keys)
|
return MemberPermissionGroupsResponse(user_sub=user_sub, group_keys=unique_group_keys)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api-clients")
|
@router.get("/api-clients")
|
||||||
@@ -1023,31 +1023,31 @@ def delete_permission_group(
|
|||||||
return {"deleted": 1, "result": "deleted"}
|
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(
|
def add_group_member(
|
||||||
group_key: str,
|
group_key: str,
|
||||||
authentik_sub: str,
|
user_sub: str,
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
) -> dict[str, str]:
|
) -> dict[str, str]:
|
||||||
groups_repo = PermissionGroupsRepository(db)
|
groups_repo = PermissionGroupsRepository(db)
|
||||||
group = groups_repo.get_by_key(group_key)
|
group = groups_repo.get_by_key(group_key)
|
||||||
if not group:
|
if not group:
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="group_not_found")
|
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"}
|
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(
|
def remove_group_member(
|
||||||
group_key: str,
|
group_key: str,
|
||||||
authentik_sub: str,
|
user_sub: str,
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
) -> dict[str, int | str]:
|
) -> dict[str, int | str]:
|
||||||
groups_repo = PermissionGroupsRepository(db)
|
groups_repo = PermissionGroupsRepository(db)
|
||||||
group = groups_repo.get_by_key(group_key)
|
group = groups_repo.get_by_key(group_key)
|
||||||
if not group:
|
if not group:
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="group_not_found")
|
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"}
|
return {"deleted": deleted, "result": "removed"}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ def upsert_user_by_sub(
|
|||||||
) -> InternalUpsertUserBySubResponse:
|
) -> InternalUpsertUserBySubResponse:
|
||||||
repo = UsersRepository(db)
|
repo = UsersRepository(db)
|
||||||
user = repo.upsert_by_sub(
|
user = repo.upsert_by_sub(
|
||||||
authentik_sub=payload.sub,
|
user_sub=payload.user_sub,
|
||||||
username=payload.username,
|
username=payload.username,
|
||||||
email=payload.email,
|
email=payload.email,
|
||||||
display_name=payload.display_name,
|
display_name=payload.display_name,
|
||||||
@@ -31,8 +31,8 @@ def upsert_user_by_sub(
|
|||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
"id": user.id,
|
"id": user.id,
|
||||||
"sub": user.authentik_sub,
|
"user_sub": user.user_sub,
|
||||||
"authentik_user_id": user.authentik_user_id,
|
"idp_user_id": user.idp_user_id,
|
||||||
"username": user.username,
|
"username": user.username,
|
||||||
"email": user.email,
|
"email": user.email,
|
||||||
"display_name": user.display_name,
|
"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(
|
def get_permission_snapshot(
|
||||||
authentik_sub: str,
|
user_sub: str,
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
) -> PermissionSnapshotResponse:
|
) -> PermissionSnapshotResponse:
|
||||||
users_repo = UsersRepository(db)
|
users_repo = UsersRepository(db)
|
||||||
perms_repo = PermissionsRepository(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:
|
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)
|
permissions = perms_repo.list_by_user(user.id, user.user_sub)
|
||||||
return PermissionService.build_snapshot(authentik_sub=authentik_sub, permissions=permissions)
|
return PermissionService.build_snapshot(user_sub=user_sub, permissions=permissions)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/authentik/users/ensure", response_model=AuthentikEnsureUserResponse)
|
@router.post("/authentik/users/ensure", response_model=AuthentikEnsureUserResponse)
|
||||||
@@ -64,7 +64,7 @@ def ensure_authentik_user(
|
|||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
authentik_service = AuthentikAdminService(settings=settings)
|
authentik_service = AuthentikAdminService(settings=settings)
|
||||||
sync_result = authentik_service.ensure_user(
|
sync_result = authentik_service.ensure_user(
|
||||||
sub=payload.sub,
|
sub=payload.user_sub,
|
||||||
email=payload.email,
|
email=payload.email,
|
||||||
username=payload.username,
|
username=payload.username,
|
||||||
display_name=payload.display_name,
|
display_name=payload.display_name,
|
||||||
@@ -72,17 +72,17 @@ def ensure_authentik_user(
|
|||||||
)
|
)
|
||||||
|
|
||||||
users_repo = UsersRepository(db)
|
users_repo = UsersRepository(db)
|
||||||
resolved_sub = payload.sub or ""
|
resolved_sub = payload.user_sub or ""
|
||||||
if sync_result.authentik_sub:
|
if sync_result.user_sub:
|
||||||
resolved_sub = sync_result.authentik_sub
|
resolved_sub = sync_result.user_sub
|
||||||
if not resolved_sub:
|
if not resolved_sub:
|
||||||
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail="authentik_missing_sub")
|
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail="authentik_missing_sub")
|
||||||
users_repo.upsert_by_sub(
|
users_repo.upsert_by_sub(
|
||||||
authentik_sub=resolved_sub,
|
user_sub=resolved_sub,
|
||||||
username=payload.username,
|
username=payload.username,
|
||||||
email=payload.email,
|
email=payload.email,
|
||||||
display_name=payload.display_name,
|
display_name=payload.display_name,
|
||||||
is_active=payload.is_active,
|
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)
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ def internal_list_members(
|
|||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"id": i.id,
|
"id": i.id,
|
||||||
"authentik_sub": i.authentik_sub,
|
"user_sub": i.user_sub,
|
||||||
"username": i.username,
|
"username": i.username,
|
||||||
"email": i.email,
|
"email": i.email,
|
||||||
"display_name": i.display_name,
|
"display_name": i.display_name,
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ def get_me(
|
|||||||
try:
|
try:
|
||||||
users_repo = UsersRepository(db)
|
users_repo = UsersRepository(db)
|
||||||
user = users_repo.upsert_by_sub(
|
user = users_repo.upsert_by_sub(
|
||||||
authentik_sub=principal.sub,
|
user_sub=principal.sub,
|
||||||
username=principal.preferred_username,
|
username=principal.preferred_username,
|
||||||
email=principal.email,
|
email=principal.email,
|
||||||
display_name=principal.name or principal.preferred_username,
|
display_name=principal.name or principal.preferred_username,
|
||||||
is_active=True,
|
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:
|
except SQLAlchemyError:
|
||||||
# DB schema compatibility fallback for local bring-up.
|
# DB schema compatibility fallback for local bring-up.
|
||||||
return MeSummaryResponse(
|
return MeSummaryResponse(
|
||||||
@@ -47,13 +47,13 @@ def get_my_permission_snapshot(
|
|||||||
perms_repo = PermissionsRepository(db)
|
perms_repo = PermissionsRepository(db)
|
||||||
|
|
||||||
user = users_repo.upsert_by_sub(
|
user = users_repo.upsert_by_sub(
|
||||||
authentik_sub=principal.sub,
|
user_sub=principal.sub,
|
||||||
username=principal.preferred_username,
|
username=principal.preferred_username,
|
||||||
email=principal.email,
|
email=principal.email,
|
||||||
display_name=principal.name or principal.preferred_username,
|
display_name=principal.name or principal.preferred_username,
|
||||||
is_active=True,
|
is_active=True,
|
||||||
)
|
)
|
||||||
permissions = perms_repo.list_by_user(user.id, user.authentik_sub)
|
permissions = perms_repo.list_by_user(user.id, user.user_sub)
|
||||||
return PermissionService.build_snapshot(authentik_sub=principal.sub, permissions=permissions)
|
return PermissionService.build_snapshot(user_sub=principal.sub, permissions=permissions)
|
||||||
except SQLAlchemyError:
|
except SQLAlchemyError:
|
||||||
return PermissionSnapshotResponse(authentik_sub=principal.sub, permissions=[])
|
return PermissionSnapshotResponse(user_sub=principal.sub, permissions=[])
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ from app.db.base import Base
|
|||||||
|
|
||||||
class PermissionGroupMember(Base):
|
class PermissionGroupMember(Base):
|
||||||
__tablename__ = "permission_group_members"
|
__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()))
|
id: Mapped[str] = mapped_column(UUID(as_uuid=False), primary_key=True, default=lambda: str(uuid4()))
|
||||||
group_id: Mapped[str] = mapped_column(
|
group_id: Mapped[str] = mapped_column(
|
||||||
UUID(as_uuid=False), ForeignKey("permission_groups.id", ondelete="CASCADE"), nullable=False
|
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)
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ class User(Base):
|
|||||||
__tablename__ = "users"
|
__tablename__ = "users"
|
||||||
|
|
||||||
id: Mapped[str] = mapped_column(UUID(as_uuid=False), primary_key=True, default=lambda: str(uuid4()))
|
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)
|
user_sub: Mapped[str] = mapped_column(String(255), unique=True, nullable=False, index=True)
|
||||||
authentik_user_id: Mapped[int | None] = mapped_column(Integer)
|
idp_user_id: Mapped[int | None] = mapped_column(Integer)
|
||||||
username: Mapped[str | None] = mapped_column(String(255), unique=True)
|
username: Mapped[str | None] = mapped_column(String(255), unique=True)
|
||||||
email: Mapped[str | None] = mapped_column(String(320))
|
email: Mapped[str | None] = mapped_column(String(320))
|
||||||
display_name: Mapped[str | None] = mapped_column(String(255))
|
display_name: Mapped[str | None] = mapped_column(String(255))
|
||||||
|
|||||||
@@ -46,43 +46,43 @@ class PermissionGroupsRepository:
|
|||||||
self.db.refresh(item)
|
self.db.refresh(item)
|
||||||
return 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(
|
existing = self.db.scalar(
|
||||||
select(PermissionGroupMember).where(
|
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:
|
if existing:
|
||||||
return 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.add(row)
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
self.db.refresh(row)
|
self.db.refresh(row)
|
||||||
return 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(
|
result = self.db.execute(
|
||||||
delete(PermissionGroupMember).where(
|
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()
|
self.db.commit()
|
||||||
return int(result.rowcount or 0)
|
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 = (
|
stmt = (
|
||||||
select(PermissionGroup.group_key)
|
select(PermissionGroup.group_key)
|
||||||
.select_from(PermissionGroupMember)
|
.select_from(PermissionGroupMember)
|
||||||
.join(PermissionGroup, PermissionGroup.id == PermissionGroupMember.group_id)
|
.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())
|
.order_by(PermissionGroup.group_key.asc())
|
||||||
)
|
)
|
||||||
return [row[0] for row in self.db.execute(stmt).all()]
|
return [row[0] for row in self.db.execute(stmt).all()]
|
||||||
|
|
||||||
def replace_member_groups(self, authentik_sub: str, group_ids: list[str]) -> None:
|
def replace_member_groups(self, user_sub: str, group_ids: list[str]) -> None:
|
||||||
self.db.execute(delete(PermissionGroupMember).where(PermissionGroupMember.authentik_sub == authentik_sub))
|
self.db.execute(delete(PermissionGroupMember).where(PermissionGroupMember.user_sub == user_sub))
|
||||||
for group_id in group_ids:
|
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()
|
self.db.commit()
|
||||||
|
|
||||||
def grant_group_permission(
|
def grant_group_permission(
|
||||||
@@ -155,7 +155,7 @@ class PermissionGroupsRepository:
|
|||||||
self.db.execute(delete(PermissionGroupMember).where(PermissionGroupMember.group_id == group_id))
|
self.db.execute(delete(PermissionGroupMember).where(PermissionGroupMember.group_id == group_id))
|
||||||
|
|
||||||
for sub in normalized_member_subs:
|
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 site_key in normalized_sites:
|
||||||
for action in normalized_actions:
|
for action in normalized_actions:
|
||||||
@@ -199,9 +199,9 @@ class PermissionGroupsRepository:
|
|||||||
|
|
||||||
def list_group_member_subs(self, group_id: str) -> list[str]:
|
def list_group_member_subs(self, group_id: str) -> list[str]:
|
||||||
stmt = (
|
stmt = (
|
||||||
select(PermissionGroupMember.authentik_sub)
|
select(PermissionGroupMember.user_sub)
|
||||||
.where(PermissionGroupMember.group_id == group_id)
|
.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()]
|
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]:
|
def list_system_members(self, system_key: str) -> list[User]:
|
||||||
stmt = (
|
stmt = (
|
||||||
select(User)
|
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)
|
.join(PermissionGroupPermission, PermissionGroupPermission.group_id == PermissionGroupMember.group_id)
|
||||||
.where(PermissionGroupPermission.system == system_key)
|
.where(PermissionGroupPermission.system == system_key)
|
||||||
.order_by(User.email.asc(), User.authentik_sub.asc())
|
.order_by(User.email.asc(), User.user_sub.asc())
|
||||||
.distinct()
|
.distinct()
|
||||||
)
|
)
|
||||||
return list(self.db.scalars(stmt).all())
|
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]:
|
def list_module_members(self, system_key: str, module_name: str) -> list[User]:
|
||||||
stmt = (
|
stmt = (
|
||||||
select(User)
|
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)
|
.join(PermissionGroupPermission, PermissionGroupPermission.group_id == PermissionGroupMember.group_id)
|
||||||
.where(PermissionGroupPermission.system == system_key, PermissionGroupPermission.module == module_name)
|
.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()
|
.distinct()
|
||||||
)
|
)
|
||||||
return list(self.db.scalars(stmt).all())
|
return list(self.db.scalars(stmt).all())
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class PermissionsRepository:
|
|||||||
def __init__(self, db: Session) -> None:
|
def __init__(self, db: Session) -> None:
|
||||||
self.db = db
|
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 = (
|
direct_stmt = (
|
||||||
select(
|
select(
|
||||||
literal("direct"),
|
literal("direct"),
|
||||||
@@ -44,7 +44,7 @@ class PermissionsRepository:
|
|||||||
)
|
)
|
||||||
.select_from(PermissionGroupPermission)
|
.select_from(PermissionGroupPermission)
|
||||||
.join(PermissionGroupMember, PermissionGroupMember.group_id == PermissionGroupPermission.group_id)
|
.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.action.in_(["view", "edit"]))
|
||||||
.where(PermissionGroupPermission.scope_type == "site")
|
.where(PermissionGroupPermission.scope_type == "site")
|
||||||
)
|
)
|
||||||
@@ -138,7 +138,7 @@ class PermissionsRepository:
|
|||||||
stmt = (
|
stmt = (
|
||||||
select(
|
select(
|
||||||
UserScopePermission.id,
|
UserScopePermission.id,
|
||||||
User.authentik_sub,
|
User.user_sub,
|
||||||
User.email,
|
User.email,
|
||||||
User.display_name,
|
User.display_name,
|
||||||
UserScopePermission.scope_type,
|
UserScopePermission.scope_type,
|
||||||
@@ -175,7 +175,7 @@ class PermissionsRepository:
|
|||||||
if keyword:
|
if keyword:
|
||||||
pattern = f"%{keyword}%"
|
pattern = f"%{keyword}%"
|
||||||
cond = or_(
|
cond = or_(
|
||||||
User.authentik_sub.ilike(pattern),
|
User.user_sub.ilike(pattern),
|
||||||
User.email.ilike(pattern),
|
User.email.ilike(pattern),
|
||||||
User.display_name.ilike(pattern),
|
User.display_name.ilike(pattern),
|
||||||
Module.module_key.ilike(pattern),
|
Module.module_key.ilike(pattern),
|
||||||
@@ -193,7 +193,7 @@ class PermissionsRepository:
|
|||||||
for row in rows:
|
for row in rows:
|
||||||
(
|
(
|
||||||
permission_id,
|
permission_id,
|
||||||
authentik_sub,
|
user_sub,
|
||||||
email,
|
email,
|
||||||
display_name,
|
display_name,
|
||||||
row_scope_type,
|
row_scope_type,
|
||||||
@@ -211,7 +211,7 @@ class PermissionsRepository:
|
|||||||
items.append(
|
items.append(
|
||||||
{
|
{
|
||||||
"permission_id": permission_id,
|
"permission_id": permission_id,
|
||||||
"authentik_sub": authentik_sub,
|
"user_sub": user_sub,
|
||||||
"email": email,
|
"email": email,
|
||||||
"display_name": display_name,
|
"display_name": display_name,
|
||||||
"scope_type": row_scope_type,
|
"scope_type": row_scope_type,
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ class UsersRepository:
|
|||||||
def __init__(self, db: Session) -> None:
|
def __init__(self, db: Session) -> None:
|
||||||
self.db = db
|
self.db = db
|
||||||
|
|
||||||
def get_by_sub(self, authentik_sub: str) -> User | None:
|
def get_by_sub(self, user_sub: str) -> User | None:
|
||||||
stmt = select(User).where(User.authentik_sub == authentik_sub)
|
stmt = select(User).where(User.user_sub == user_sub)
|
||||||
return self.db.scalar(stmt)
|
return self.db.scalar(stmt)
|
||||||
|
|
||||||
def get_by_id(self, user_id: str) -> User | None:
|
def get_by_id(self, user_id: str) -> User | None:
|
||||||
@@ -29,7 +29,7 @@ class UsersRepository:
|
|||||||
if keyword:
|
if keyword:
|
||||||
pattern = f"%{keyword}%"
|
pattern = f"%{keyword}%"
|
||||||
cond = or_(
|
cond = or_(
|
||||||
User.authentik_sub.ilike(pattern),
|
User.user_sub.ilike(pattern),
|
||||||
User.username.ilike(pattern),
|
User.username.ilike(pattern),
|
||||||
User.email.ilike(pattern),
|
User.email.ilike(pattern),
|
||||||
User.display_name.ilike(pattern),
|
User.display_name.ilike(pattern),
|
||||||
@@ -48,18 +48,18 @@ class UsersRepository:
|
|||||||
|
|
||||||
def upsert_by_sub(
|
def upsert_by_sub(
|
||||||
self,
|
self,
|
||||||
authentik_sub: str,
|
user_sub: str,
|
||||||
username: str | None,
|
username: str | None,
|
||||||
email: str | None,
|
email: str | None,
|
||||||
display_name: str | None,
|
display_name: str | None,
|
||||||
is_active: bool,
|
is_active: bool,
|
||||||
authentik_user_id: int | None = None,
|
idp_user_id: int | None = None,
|
||||||
) -> User:
|
) -> User:
|
||||||
user = self.get_by_sub(authentik_sub)
|
user = self.get_by_sub(user_sub)
|
||||||
if user is None:
|
if user is None:
|
||||||
user = User(
|
user = User(
|
||||||
authentik_sub=authentik_sub,
|
user_sub=user_sub,
|
||||||
authentik_user_id=authentik_user_id,
|
idp_user_id=idp_user_id,
|
||||||
username=username,
|
username=username,
|
||||||
email=email,
|
email=email,
|
||||||
display_name=display_name,
|
display_name=display_name,
|
||||||
@@ -67,8 +67,8 @@ class UsersRepository:
|
|||||||
)
|
)
|
||||||
self.db.add(user)
|
self.db.add(user)
|
||||||
else:
|
else:
|
||||||
if authentik_user_id is not None:
|
if idp_user_id is not None:
|
||||||
user.authentik_user_id = authentik_user_id
|
user.idp_user_id = idp_user_id
|
||||||
user.username = username
|
user.username = username
|
||||||
user.email = email
|
user.email = email
|
||||||
user.display_name = display_name
|
user.display_name = display_name
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
from pydantic import BaseModel
|
from pydantic import AliasChoices, BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
class AuthentikEnsureUserRequest(BaseModel):
|
class AuthentikEnsureUserRequest(BaseModel):
|
||||||
sub: str | None = None
|
user_sub: str | None = Field(default=None, validation_alias=AliasChoices("user_sub", "sub"))
|
||||||
username: str | None = None
|
username: str | None = None
|
||||||
email: str
|
email: str
|
||||||
display_name: str | None = None
|
display_name: str | None = None
|
||||||
@@ -10,5 +10,5 @@ class AuthentikEnsureUserRequest(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class AuthentikEnsureUserResponse(BaseModel):
|
class AuthentikEnsureUserResponse(BaseModel):
|
||||||
authentik_user_id: int
|
idp_user_id: int
|
||||||
action: str
|
action: str
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ class SiteItem(BaseModel):
|
|||||||
|
|
||||||
class MemberItem(BaseModel):
|
class MemberItem(BaseModel):
|
||||||
id: str
|
id: str
|
||||||
authentik_sub: str
|
user_sub: str
|
||||||
username: str | None = None
|
username: str | None = None
|
||||||
email: str | None = None
|
email: str | None = None
|
||||||
display_name: str | None = None
|
display_name: str | None = None
|
||||||
@@ -85,7 +85,7 @@ class MemberItem(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class MemberUpsertRequest(BaseModel):
|
class MemberUpsertRequest(BaseModel):
|
||||||
authentik_sub: str | None = None
|
user_sub: str | None = None
|
||||||
username: str | None = None
|
username: str | None = None
|
||||||
email: str | None = None
|
email: str | None = None
|
||||||
display_name: str | None = None
|
display_name: str | None = None
|
||||||
@@ -102,7 +102,7 @@ class MemberUpdateRequest(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class MemberPasswordResetResponse(BaseModel):
|
class MemberPasswordResetResponse(BaseModel):
|
||||||
authentik_sub: str
|
user_sub: str
|
||||||
temporary_password: str
|
temporary_password: str
|
||||||
|
|
||||||
|
|
||||||
@@ -144,7 +144,7 @@ class PermissionGroupPermissionItem(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class MemberPermissionGroupsResponse(BaseModel):
|
class MemberPermissionGroupsResponse(BaseModel):
|
||||||
authentik_sub: str
|
user_sub: str
|
||||||
group_keys: list[str]
|
group_keys: list[str]
|
||||||
|
|
||||||
|
|
||||||
@@ -172,7 +172,7 @@ class GroupRelationItem(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class MemberRelationItem(BaseModel):
|
class MemberRelationItem(BaseModel):
|
||||||
authentik_sub: str
|
user_sub: str
|
||||||
email: str | None = None
|
email: str | None = None
|
||||||
display_name: str | None = None
|
display_name: str | None = None
|
||||||
is_active: bool
|
is_active: bool
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class InternalSiteListResponse(BaseModel):
|
|||||||
|
|
||||||
class InternalMemberItem(BaseModel):
|
class InternalMemberItem(BaseModel):
|
||||||
id: str
|
id: str
|
||||||
authentik_sub: str
|
user_sub: str
|
||||||
username: str | None = None
|
username: str | None = None
|
||||||
email: str | None = None
|
email: str | None = None
|
||||||
display_name: str | None = None
|
display_name: str | None = None
|
||||||
@@ -77,8 +77,8 @@ class InternalMemberListResponse(BaseModel):
|
|||||||
|
|
||||||
class InternalUpsertUserBySubResponse(BaseModel):
|
class InternalUpsertUserBySubResponse(BaseModel):
|
||||||
id: str
|
id: str
|
||||||
sub: str
|
user_sub: str
|
||||||
authentik_user_id: int | None = None
|
idp_user_id: int | None = None
|
||||||
username: str | None = None
|
username: str | None = None
|
||||||
email: str | None = None
|
email: str | None = None
|
||||||
display_name: str | None = None
|
display_name: str | None = None
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ ScopeType = Literal["site"]
|
|||||||
|
|
||||||
|
|
||||||
class PermissionGrantRequest(BaseModel):
|
class PermissionGrantRequest(BaseModel):
|
||||||
authentik_sub: str
|
user_sub: str
|
||||||
email: str | None = None
|
email: str | None = None
|
||||||
display_name: str | None = None
|
display_name: str | None = None
|
||||||
scope_type: ScopeType
|
scope_type: ScopeType
|
||||||
@@ -19,7 +19,7 @@ class PermissionGrantRequest(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class PermissionRevokeRequest(BaseModel):
|
class PermissionRevokeRequest(BaseModel):
|
||||||
authentik_sub: str
|
user_sub: str
|
||||||
scope_type: ScopeType
|
scope_type: ScopeType
|
||||||
scope_id: str
|
scope_id: str
|
||||||
system: str
|
system: str
|
||||||
@@ -36,13 +36,13 @@ class PermissionItem(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class PermissionSnapshotResponse(BaseModel):
|
class PermissionSnapshotResponse(BaseModel):
|
||||||
authentik_sub: str
|
user_sub: str
|
||||||
permissions: list[PermissionItem]
|
permissions: list[PermissionItem]
|
||||||
|
|
||||||
|
|
||||||
class DirectPermissionRow(BaseModel):
|
class DirectPermissionRow(BaseModel):
|
||||||
permission_id: str
|
permission_id: str
|
||||||
authentik_sub: str
|
user_sub: str
|
||||||
email: str | None = None
|
email: str | None = None
|
||||||
display_name: str | None = None
|
display_name: str | None = None
|
||||||
scope_type: ScopeType
|
scope_type: ScopeType
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
from pydantic import BaseModel
|
from pydantic import AliasChoices, BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
class UserUpsertBySubRequest(BaseModel):
|
class UserUpsertBySubRequest(BaseModel):
|
||||||
sub: str
|
user_sub: str = Field(validation_alias=AliasChoices("user_sub", "sub"))
|
||||||
username: str | None = None
|
username: str | None = None
|
||||||
email: str | None = None
|
email: str | None = None
|
||||||
display_name: str | None = None
|
display_name: str | None = None
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from app.core.config import Settings
|
|||||||
class AuthentikSyncResult:
|
class AuthentikSyncResult:
|
||||||
user_id: int
|
user_id: int
|
||||||
action: str
|
action: str
|
||||||
authentik_sub: str | None = None
|
user_sub: str | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -108,7 +108,7 @@ class AuthentikAdminService:
|
|||||||
username: str | None,
|
username: str | None,
|
||||||
display_name: str | None,
|
display_name: str | None,
|
||||||
is_active: bool = True,
|
is_active: bool = True,
|
||||||
authentik_user_id: int | None = None,
|
idp_user_id: int | None = None,
|
||||||
) -> AuthentikSyncResult:
|
) -> AuthentikSyncResult:
|
||||||
resolved_username = username or self._safe_username(sub=sub, email=email)
|
resolved_username = username or self._safe_username(sub=sub, email=email)
|
||||||
payload = {
|
payload = {
|
||||||
@@ -120,8 +120,8 @@ class AuthentikAdminService:
|
|||||||
|
|
||||||
with self._client() as client:
|
with self._client() as client:
|
||||||
existing = None
|
existing = None
|
||||||
if authentik_user_id is not None:
|
if idp_user_id is not None:
|
||||||
existing = self._lookup_user_by_id(client, authentik_user_id)
|
existing = self._lookup_user_by_id(client, idp_user_id)
|
||||||
if existing is None:
|
if existing is None:
|
||||||
existing = self._lookup_user_by_email_or_username(client, email=email, username=resolved_username)
|
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)
|
patch_resp = client.patch(f"/api/v3/core/users/{user_pk}/", json=payload)
|
||||||
if patch_resp.status_code >= 400:
|
if patch_resp.status_code >= 400:
|
||||||
raise HTTPException(status_code=502, detail="authentik_update_failed")
|
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)
|
create_resp = client.post("/api/v3/core/users/", json=payload)
|
||||||
if create_resp.status_code >= 400:
|
if create_resp.status_code >= 400:
|
||||||
@@ -139,20 +139,20 @@ class AuthentikAdminService:
|
|||||||
return AuthentikSyncResult(
|
return AuthentikSyncResult(
|
||||||
user_id=int(created["pk"]),
|
user_id=int(created["pk"]),
|
||||||
action="created",
|
action="created",
|
||||||
authentik_sub=created.get("uid"),
|
user_sub=created.get("uid"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def reset_password(
|
def reset_password(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
authentik_user_id: int | None,
|
idp_user_id: int | None,
|
||||||
email: str | None,
|
email: str | None,
|
||||||
username: str | None,
|
username: str | None,
|
||||||
) -> AuthentikPasswordResetResult:
|
) -> AuthentikPasswordResetResult:
|
||||||
with self._client() as client:
|
with self._client() as client:
|
||||||
existing = None
|
existing = None
|
||||||
if authentik_user_id is not None:
|
if idp_user_id is not None:
|
||||||
existing = self._lookup_user_by_id(client, authentik_user_id)
|
existing = self._lookup_user_by_id(client, idp_user_id)
|
||||||
if existing is None:
|
if existing is None:
|
||||||
existing = self._lookup_user_by_email_or_username(client, email=email, username=username)
|
existing = self._lookup_user_by_email_or_username(client, email=email, username=username)
|
||||||
if not existing or existing.get("pk") is None:
|
if not existing or existing.get("pk") is None:
|
||||||
@@ -169,14 +169,14 @@ class AuthentikAdminService:
|
|||||||
def delete_user(
|
def delete_user(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
authentik_user_id: int | None,
|
idp_user_id: int | None,
|
||||||
email: str | None,
|
email: str | None,
|
||||||
username: str | None,
|
username: str | None,
|
||||||
) -> AuthentikDeleteResult:
|
) -> AuthentikDeleteResult:
|
||||||
with self._client() as client:
|
with self._client() as client:
|
||||||
existing = None
|
existing = None
|
||||||
if authentik_user_id is not None:
|
if idp_user_id is not None:
|
||||||
existing = self._lookup_user_by_id(client, authentik_user_id)
|
existing = self._lookup_user_by_id(client, idp_user_id)
|
||||||
if existing is None:
|
if existing is None:
|
||||||
existing = self._lookup_user_by_email_or_username(client, email=email, username=username)
|
existing = self._lookup_user_by_email_or_username(client, email=email, username=username)
|
||||||
if not existing or existing.get("pk") is None:
|
if not existing or existing.get("pk") is None:
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ from app.schemas.permissions import PermissionItem, PermissionSnapshotResponse
|
|||||||
|
|
||||||
class PermissionService:
|
class PermissionService:
|
||||||
@staticmethod
|
@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(
|
return PermissionSnapshotResponse(
|
||||||
authentik_sub=authentik_sub,
|
user_sub=user_sub,
|
||||||
permissions=[
|
permissions=[
|
||||||
PermissionItem(scope_type=s_type, scope_id=s_id, system=system, module=module, action=action)
|
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
|
for s_type, s_id, system, module, action in permissions
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ DROP TABLE IF EXISTS permissions CASCADE;
|
|||||||
|
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
authentik_sub TEXT NOT NULL UNIQUE,
|
user_sub TEXT NOT NULL UNIQUE,
|
||||||
authentik_user_id INTEGER,
|
idp_user_id INTEGER,
|
||||||
username TEXT UNIQUE,
|
username TEXT UNIQUE,
|
||||||
email TEXT UNIQUE,
|
email TEXT UNIQUE,
|
||||||
display_name TEXT,
|
display_name TEXT,
|
||||||
@@ -105,9 +105,9 @@ CREATE TABLE permission_groups (
|
|||||||
CREATE TABLE permission_group_members (
|
CREATE TABLE permission_group_members (
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
group_id UUID NOT NULL REFERENCES permission_groups(id) ON DELETE CASCADE,
|
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(),
|
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 (
|
CREATE TABLE permission_group_permissions (
|
||||||
@@ -144,7 +144,7 @@ INSERT INTO systems (system_key, name, status)
|
|||||||
VALUES ('member', 'Member Center', 'active')
|
VALUES ('member', 'Member Center', 'active')
|
||||||
ON CONFLICT (system_key) DO NOTHING;
|
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_users_username ON users(username);
|
||||||
CREATE INDEX idx_sites_company_id ON sites(company_id);
|
CREATE INDEX idx_sites_company_id ON sites(company_id);
|
||||||
CREATE INDEX idx_usp_user_id ON user_scope_permissions(user_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
|
CREATE UNIQUE INDEX uq_usp_site
|
||||||
ON user_scope_permissions(user_id, module_id, action, scope_type, site_id);
|
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_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_group_id ON permission_group_permissions(group_id);
|
||||||
CREATE INDEX idx_pgp_scope_site ON permission_group_permissions(scope_id);
|
CREATE INDEX idx_pgp_scope_site ON permission_group_permissions(scope_id);
|
||||||
CREATE INDEX idx_api_clients_status ON api_clients(status);
|
CREATE INDEX idx_api_clients_status ON api_clients(status);
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
ALTER TABLE users
|
ALTER TABLE users
|
||||||
ADD COLUMN IF NOT EXISTS authentik_user_id INTEGER;
|
ADD COLUMN IF NOT EXISTS idp_user_id INTEGER;
|
||||||
|
|||||||
@@ -46,9 +46,9 @@ CREATE TABLE IF NOT EXISTS permission_groups (
|
|||||||
CREATE TABLE IF NOT EXISTS permission_group_members (
|
CREATE TABLE IF NOT EXISTS permission_group_members (
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
group_id UUID NOT NULL REFERENCES permission_groups(id) ON DELETE CASCADE,
|
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(),
|
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 (
|
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_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_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 INDEX IF NOT EXISTS idx_pgp_group_id ON permission_group_permissions(group_id);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_pgp_group_rule
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_pgp_group_rule
|
||||||
|
|||||||
43
backend/scripts/migrate_rename_identity_columns.sql
Normal file
43
backend/scripts/migrate_rename_identity_columns.sql
Normal file
@@ -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;
|
||||||
@@ -18,8 +18,8 @@
|
|||||||
## 會員資料與 Authentik 對齊
|
## 會員資料與 Authentik 對齊
|
||||||
- `username`:登入帳號(可編輯,可同步)
|
- `username`:登入帳號(可編輯,可同步)
|
||||||
- `display_name`:顯示名稱(可編輯,可同步到 Authentik `name`)
|
- `display_name`:顯示名稱(可編輯,可同步到 Authentik `name`)
|
||||||
- `authentik_sub`:由 Authentik UID 回寫
|
- `user_sub`:由 Authentik UID 回寫
|
||||||
- `authentik_user_id`:保留 Authentik user id,供更新/密碼重設
|
- `idp_user_id`:保留 Authentik user id,供更新/密碼重設
|
||||||
|
|
||||||
## 密碼流程
|
## 密碼流程
|
||||||
- 目前:後台可觸發重設密碼(產生臨時密碼)
|
- 目前:後台可觸發重設密碼(產生臨時密碼)
|
||||||
|
|||||||
@@ -13,5 +13,5 @@
|
|||||||
- [x] 管理 API 完成 systems/modules/companies/sites/members/permission-groups CRUD
|
- [x] 管理 API 完成 systems/modules/companies/sites/members/permission-groups CRUD
|
||||||
- [x] 會員 upsert/update 可同步 Authentik
|
- [x] 會員 upsert/update 可同步 Authentik
|
||||||
- [x] 會員資料新增 `username` 欄位,與 `display_name` 分離
|
- [x] 會員資料新增 `username` 欄位,與 `display_name` 分離
|
||||||
- [x] 新增 `POST /admin/members/{authentik_sub}/password/reset`
|
- [x] 新增 `POST /admin/members/{user_sub}/password/reset`
|
||||||
- [x] DB 新增 `users.username`(含 migration 腳本)
|
- [x] DB 新增 `users.username`(含 migration 腳本)
|
||||||
|
|||||||
@@ -3,11 +3,12 @@
|
|||||||
## 真實來源
|
## 真實來源
|
||||||
- `backend/scripts/init_schema.sql`
|
- `backend/scripts/init_schema.sql`
|
||||||
- 線上增量:`backend/scripts/migrate_add_users_username.sql`
|
- 線上增量:`backend/scripts/migrate_add_users_username.sql`
|
||||||
|
- 欄位重命名增量:`backend/scripts/migrate_rename_identity_columns.sql`
|
||||||
|
|
||||||
## 主要表
|
## 主要表
|
||||||
- `users`
|
- `users`
|
||||||
- `authentik_sub` UNIQUE
|
- `user_sub` UNIQUE
|
||||||
- `authentik_user_id` INTEGER
|
- `idp_user_id` INTEGER
|
||||||
- `username` UNIQUE
|
- `username` UNIQUE
|
||||||
- `email` UNIQUE
|
- `email` UNIQUE
|
||||||
- `display_name`
|
- `display_name`
|
||||||
@@ -17,7 +18,7 @@
|
|||||||
- `systems`
|
- `systems`
|
||||||
- `modules`(`system_key -> systems.system_key`)
|
- `modules`(`system_key -> systems.system_key`)
|
||||||
- `permission_groups`
|
- `permission_groups`
|
||||||
- `permission_group_members`(group + authentik_sub)
|
- `permission_group_members`(group + user_sub)
|
||||||
- `permission_group_permissions`(group + site/system/module/action)
|
- `permission_group_permissions`(group + site/system/module/action)
|
||||||
- `user_scope_permissions`(相容保留)
|
- `user_scope_permissions`(相容保留)
|
||||||
- `api_clients`(保留給機器對機器用途)
|
- `api_clients`(保留給機器對機器用途)
|
||||||
@@ -27,7 +28,7 @@
|
|||||||
- `action in ('view','edit')`
|
- `action in ('view','edit')`
|
||||||
|
|
||||||
## 會員與 Authentik 對齊
|
## 會員與 Authentik 對齊
|
||||||
- `users.authentik_sub` 對應 Authentik `uid`
|
- `users.user_sub` 對應 Authentik `uid`
|
||||||
- `users.username` 對應 Authentik `username`
|
- `users.username` 對應 Authentik `username`
|
||||||
- `users.display_name` 對應 Authentik `name`
|
- `users.display_name` 對應 Authentik `name`
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
## 目前後端契約重點
|
## 目前後端契約重點
|
||||||
- 後台登入:只吃 Bearer + admin 群組檢查
|
- 後台登入:只吃 Bearer + admin 群組檢查
|
||||||
- 會員模型:`authentik_sub`, `username`, `email`, `display_name`, `is_active`
|
- 會員模型:`user_sub`, `username`, `email`, `display_name`, `is_active`
|
||||||
- 會員密碼:支援重設 API(回傳臨時密碼)
|
- 會員密碼:支援重設 API(回傳臨時密碼)
|
||||||
|
|
||||||
## 會員頁必做
|
## 會員頁必做
|
||||||
1. 新增會員表單欄位:`username`、`email`、`display_name`
|
1. 新增會員表單欄位:`username`、`email`、`display_name`
|
||||||
2. 編輯會員表單欄位:`username`、`email`、`display_name`、`is_active`
|
2. 編輯會員表單欄位:`username`、`email`、`display_name`、`is_active`
|
||||||
3. 表格欄位要顯示:`authentik_sub`、`username`、`email`、`display_name`
|
3. 表格欄位要顯示:`user_sub`、`username`、`email`、`display_name`
|
||||||
4. 操作欄新增「重設密碼」按鈕,串 `POST /admin/members/{authentik_sub}/password/reset`
|
4. 操作欄新增「重設密碼」按鈕,串 `POST /admin/members/{user_sub}/password/reset`
|
||||||
5. 重設成功後顯示臨時密碼,並提醒管理員安全轉交
|
5. 重設成功後顯示臨時密碼,並提醒管理員安全轉交
|
||||||
|
|
||||||
## 其他頁面
|
## 其他頁面
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ Response:
|
|||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"id": "uuid",
|
"id": "uuid",
|
||||||
"authentik_sub": "authentik-uid",
|
"user_sub": "authentik-uid",
|
||||||
"username": "chris",
|
"username": "chris",
|
||||||
"email": "chris@ose.tw",
|
"email": "chris@ose.tw",
|
||||||
"display_name": "Chris",
|
"display_name": "Chris",
|
||||||
@@ -128,7 +128,7 @@ Response:
|
|||||||
Request:
|
Request:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"sub": "authentik-uid",
|
"user_sub": "authentik-uid",
|
||||||
"username": "chris",
|
"username": "chris",
|
||||||
"email": "chris@ose.tw",
|
"email": "chris@ose.tw",
|
||||||
"display_name": "Chris",
|
"display_name": "Chris",
|
||||||
@@ -140,8 +140,8 @@ Response:
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"id": "uuid",
|
"id": "uuid",
|
||||||
"sub": "authentik-uid",
|
"user_sub": "authentik-uid",
|
||||||
"authentik_user_id": 123,
|
"idp_user_id": 123,
|
||||||
"username": "chris",
|
"username": "chris",
|
||||||
"email": "chris@ose.tw",
|
"email": "chris@ose.tw",
|
||||||
"display_name": "Chris",
|
"display_name": "Chris",
|
||||||
@@ -149,11 +149,11 @@ Response:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### GET `/internal/permissions/{authentik_sub}/snapshot`
|
### GET `/internal/permissions/{user_sub}/snapshot`
|
||||||
Response:
|
Response:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"authentik_sub": "authentik-uid",
|
"user_sub": "authentik-uid",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"scope_type": "site",
|
"scope_type": "site",
|
||||||
@@ -170,7 +170,7 @@ Response:
|
|||||||
Request:
|
Request:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"sub": "authentik-uid",
|
"user_sub": "authentik-uid",
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"username": "user1",
|
"username": "user1",
|
||||||
"display_name": "User One",
|
"display_name": "User One",
|
||||||
@@ -181,7 +181,7 @@ Request:
|
|||||||
Response:
|
Response:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"authentik_user_id": 123,
|
"idp_user_id": 123,
|
||||||
"action": "created"
|
"action": "created"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -25,6 +25,6 @@ npm run dev
|
|||||||
|
|
||||||
## 5) 會員流程驗收
|
## 5) 會員流程驗收
|
||||||
1. 新增會員(username/email/display_name,開啟 sync_to_authentik)
|
1. 新增會員(username/email/display_name,開啟 sync_to_authentik)
|
||||||
2. 確認列表可看到新會員與 `authentik_sub`
|
2. 確認列表可看到新會員與 `user_sub`
|
||||||
3. 點「重設密碼」,取得臨時密碼
|
3. 點「重設密碼」,取得臨時密碼
|
||||||
4. 到 Authentik 驗證該會員可用新密碼登入
|
4. 到 Authentik 驗證該會員可用新密碼登入
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import { adminHttp } from './http'
|
|||||||
|
|
||||||
export const getMembers = () => adminHttp.get('/admin/members')
|
export const getMembers = () => adminHttp.get('/admin/members')
|
||||||
export const upsertMember = (data) => adminHttp.post('/admin/members/upsert', data)
|
export const upsertMember = (data) => adminHttp.post('/admin/members/upsert', data)
|
||||||
export const updateMember = (authentikSub, data) => adminHttp.patch(`/admin/members/${authentikSub}`, data)
|
export const updateMember = (userSub, data) => adminHttp.patch(`/admin/members/${userSub}`, data)
|
||||||
export const deleteMember = (authentikSub) => adminHttp.delete(`/admin/members/${authentikSub}`)
|
export const deleteMember = (userSub) => adminHttp.delete(`/admin/members/${userSub}`)
|
||||||
export const resetMemberPassword = (authentikSub) => adminHttp.post(`/admin/members/${authentikSub}/password/reset`)
|
export const resetMemberPassword = (userSub) => adminHttp.post(`/admin/members/${userSub}/password/reset`)
|
||||||
export const getMemberPermissionGroups = (authentikSub) => adminHttp.get(`/admin/members/${authentikSub}/permission-groups`)
|
export const getMemberPermissionGroups = (userSub) => adminHttp.get(`/admin/members/${userSub}/permission-groups`)
|
||||||
export const setMemberPermissionGroups = (authentikSub, groupKeys) =>
|
export const setMemberPermissionGroups = (userSub, groupKeys) =>
|
||||||
adminHttp.put(`/admin/members/${authentikSub}/permission-groups`, { group_keys: groupKeys })
|
adminHttp.put(`/admin/members/${userSub}/permission-groups`, { group_keys: groupKeys })
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
<el-table v-else :data="members" stripe border class="w-full shadow-sm">
|
<el-table v-else :data="members" stripe border class="w-full shadow-sm">
|
||||||
<template #empty><el-empty description="目前無會員" /></template>
|
<template #empty><el-empty description="目前無會員" /></template>
|
||||||
<el-table-column prop="authentik_sub" label="Authentik Sub" min-width="260" />
|
<el-table-column prop="user_sub" label="User Sub" min-width="260" />
|
||||||
<el-table-column prop="username" label="Username" min-width="160" />
|
<el-table-column prop="username" label="Username" min-width="160" />
|
||||||
<el-table-column prop="email" label="Email" min-width="220" />
|
<el-table-column prop="email" label="Email" min-width="220" />
|
||||||
<el-table-column prop="display_name" label="顯示名稱" min-width="180" />
|
<el-table-column prop="display_name" label="顯示名稱" min-width="180" />
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
|
|
||||||
<el-dialog v-model="showEditDialog" title="編輯會員" @close="resetEditForm">
|
<el-dialog v-model="showEditDialog" title="編輯會員" @close="resetEditForm">
|
||||||
<el-form :model="editForm" label-width="120px">
|
<el-form :model="editForm" label-width="120px">
|
||||||
<el-form-item label="Authentik Sub"><el-input :model-value="editForm.authentik_sub" disabled /></el-form-item>
|
<el-form-item label="User Sub"><el-input :model-value="editForm.user_sub" disabled /></el-form-item>
|
||||||
<el-form-item label="Username"><el-input v-model="editForm.username" /></el-form-item>
|
<el-form-item label="Username"><el-input v-model="editForm.username" /></el-form-item>
|
||||||
<el-form-item label="Email"><el-input v-model="editForm.email" /></el-form-item>
|
<el-form-item label="Email"><el-input v-model="editForm.email" /></el-form-item>
|
||||||
<el-form-item label="顯示名稱"><el-input v-model="editForm.display_name" /></el-form-item>
|
<el-form-item label="顯示名稱"><el-input v-model="editForm.display_name" /></el-form-item>
|
||||||
@@ -110,7 +110,7 @@ const createRules = {
|
|||||||
const showEditDialog = ref(false)
|
const showEditDialog = ref(false)
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
const editForm = ref({
|
const editForm = ref({
|
||||||
authentik_sub: '',
|
user_sub: '',
|
||||||
username: '',
|
username: '',
|
||||||
email: '',
|
email: '',
|
||||||
display_name: '',
|
display_name: '',
|
||||||
@@ -147,7 +147,7 @@ function resetCreateForm() {
|
|||||||
|
|
||||||
async function openEdit(row) {
|
async function openEdit(row) {
|
||||||
editForm.value = {
|
editForm.value = {
|
||||||
authentik_sub: row.authentik_sub,
|
user_sub: row.user_sub,
|
||||||
username: row.username || '',
|
username: row.username || '',
|
||||||
email: row.email || '',
|
email: row.email || '',
|
||||||
display_name: row.display_name || '',
|
display_name: row.display_name || '',
|
||||||
@@ -156,7 +156,7 @@ async function openEdit(row) {
|
|||||||
sync_to_authentik: true
|
sync_to_authentik: true
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const res = await getMemberPermissionGroups(row.authentik_sub)
|
const res = await getMemberPermissionGroups(row.user_sub)
|
||||||
editForm.value.group_keys = res.data?.group_keys || []
|
editForm.value.group_keys = res.data?.group_keys || []
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ElMessage.warning('載入會員群組失敗,仍可先編輯基本資料')
|
ElMessage.warning('載入會員群組失敗,仍可先編輯基本資料')
|
||||||
@@ -166,7 +166,7 @@ async function openEdit(row) {
|
|||||||
|
|
||||||
function resetEditForm() {
|
function resetEditForm() {
|
||||||
editForm.value = {
|
editForm.value = {
|
||||||
authentik_sub: '',
|
user_sub: '',
|
||||||
username: '',
|
username: '',
|
||||||
email: '',
|
email: '',
|
||||||
display_name: '',
|
display_name: '',
|
||||||
@@ -182,7 +182,7 @@ async function handleCreate() {
|
|||||||
creating.value = true
|
creating.value = true
|
||||||
try {
|
try {
|
||||||
const created = await upsertMember({ ...createForm.value })
|
const created = await upsertMember({ ...createForm.value })
|
||||||
const createdSub = created.data?.authentik_sub
|
const createdSub = created.data?.user_sub
|
||||||
if (createdSub && createForm.value.group_keys.length > 0) {
|
if (createdSub && createForm.value.group_keys.length > 0) {
|
||||||
await setMemberPermissionGroups(createdSub, createForm.value.group_keys)
|
await setMemberPermissionGroups(createdSub, createForm.value.group_keys)
|
||||||
}
|
}
|
||||||
@@ -201,14 +201,14 @@ async function handleCreate() {
|
|||||||
async function handleEdit() {
|
async function handleEdit() {
|
||||||
saving.value = true
|
saving.value = true
|
||||||
try {
|
try {
|
||||||
await updateMember(editForm.value.authentik_sub, {
|
await updateMember(editForm.value.user_sub, {
|
||||||
username: editForm.value.username || null,
|
username: editForm.value.username || null,
|
||||||
email: editForm.value.email || null,
|
email: editForm.value.email || null,
|
||||||
display_name: editForm.value.display_name || null,
|
display_name: editForm.value.display_name || null,
|
||||||
is_active: editForm.value.is_active,
|
is_active: editForm.value.is_active,
|
||||||
sync_to_authentik: editForm.value.sync_to_authentik
|
sync_to_authentik: editForm.value.sync_to_authentik
|
||||||
})
|
})
|
||||||
await setMemberPermissionGroups(editForm.value.authentik_sub, editForm.value.group_keys || [])
|
await setMemberPermissionGroups(editForm.value.user_sub, editForm.value.group_keys || [])
|
||||||
ElMessage.success('更新會員成功')
|
ElMessage.success('更新會員成功')
|
||||||
showEditDialog.value = false
|
showEditDialog.value = false
|
||||||
await load()
|
await load()
|
||||||
@@ -222,7 +222,7 @@ async function handleEdit() {
|
|||||||
|
|
||||||
async function handleResetPassword(row) {
|
async function handleResetPassword(row) {
|
||||||
try {
|
try {
|
||||||
const res = await resetMemberPassword(row.authentik_sub)
|
const res = await resetMemberPassword(row.user_sub)
|
||||||
const pwd = res.data?.temporary_password || ''
|
const pwd = res.data?.temporary_password || ''
|
||||||
if (!pwd) {
|
if (!pwd) {
|
||||||
ElMessage.success('密碼已重設')
|
ElMessage.success('密碼已重設')
|
||||||
@@ -239,11 +239,11 @@ async function handleResetPassword(row) {
|
|||||||
async function handleDelete(row) {
|
async function handleDelete(row) {
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(
|
await ElMessageBox.confirm(
|
||||||
`確認刪除會員 ${row.display_name || row.email || row.username || row.authentik_sub}?`,
|
`確認刪除會員 ${row.display_name || row.email || row.username || row.user_sub}?`,
|
||||||
'刪除確認',
|
'刪除確認',
|
||||||
{ type: 'warning' }
|
{ type: 'warning' }
|
||||||
)
|
)
|
||||||
await deleteMember(row.authentik_sub)
|
await deleteMember(row.user_sub)
|
||||||
ElMessage.success('刪除成功')
|
ElMessage.success('刪除成功')
|
||||||
await load()
|
await load()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -85,7 +85,7 @@
|
|||||||
<el-tab-pane label="涉及會員" name="members">
|
<el-tab-pane label="涉及會員" name="members">
|
||||||
<el-table :data="relationMembers" border stripe v-loading="relationLoading">
|
<el-table :data="relationMembers" border stripe v-loading="relationLoading">
|
||||||
<template #empty><el-empty description="尚無關聯會員" /></template>
|
<template #empty><el-empty description="尚無關聯會員" /></template>
|
||||||
<el-table-column prop="authentik_sub" label="Authentik Sub" min-width="260" />
|
<el-table-column prop="user_sub" label="User Sub" min-width="260" />
|
||||||
<el-table-column prop="email" label="Email" min-width="220" />
|
<el-table-column prop="email" label="Email" min-width="220" />
|
||||||
<el-table-column prop="display_name" label="顯示名稱" min-width="160" />
|
<el-table-column prop="display_name" label="顯示名稱" min-width="160" />
|
||||||
<el-table-column label="啟用" width="80">
|
<el-table-column label="啟用" width="80">
|
||||||
|
|||||||
@@ -56,9 +56,9 @@
|
|||||||
<el-select v-model="bindingForm.member_subs" multiple filterable clearable style="width: 100%" placeholder="選擇會員">
|
<el-select v-model="bindingForm.member_subs" multiple filterable clearable style="width: 100%" placeholder="選擇會員">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="m in members"
|
v-for="m in members"
|
||||||
:key="m.authentik_sub"
|
:key="m.user_sub"
|
||||||
:label="`${m.display_name || m.email || '(no-name)'} (${m.authentik_sub})`"
|
:label="`${m.display_name || m.email || '(no-name)'} (${m.user_sub})`"
|
||||||
:value="m.authentik_sub"
|
:value="m.user_sub"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|||||||
@@ -77,7 +77,7 @@
|
|||||||
<el-tab-pane label="涉及會員" name="members">
|
<el-tab-pane label="涉及會員" name="members">
|
||||||
<el-table :data="relationMembers" border stripe v-loading="relationLoading">
|
<el-table :data="relationMembers" border stripe v-loading="relationLoading">
|
||||||
<template #empty><el-empty description="尚無關聯會員" /></template>
|
<template #empty><el-empty description="尚無關聯會員" /></template>
|
||||||
<el-table-column prop="authentik_sub" label="Authentik Sub" min-width="260" />
|
<el-table-column prop="user_sub" label="User Sub" min-width="260" />
|
||||||
<el-table-column prop="email" label="Email" min-width="220" />
|
<el-table-column prop="email" label="Email" min-width="220" />
|
||||||
<el-table-column prop="display_name" label="顯示名稱" min-width="160" />
|
<el-table-column prop="display_name" label="顯示名稱" min-width="160" />
|
||||||
<el-table-column label="啟用" width="80">
|
<el-table-column label="啟用" width="80">
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
class="max-w-xl mt-4"
|
class="max-w-xl mt-4"
|
||||||
@submit.prevent="handleGrant"
|
@submit.prevent="handleGrant"
|
||||||
>
|
>
|
||||||
<el-form-item label="Authentik Sub" prop="authentik_sub">
|
<el-form-item label="User Sub" prop="user_sub">
|
||||||
<el-select v-model="grantForm.authentik_sub" filterable allow-create default-first-option placeholder="選擇會員或輸入 sub" style="width: 100%">
|
<el-select v-model="grantForm.user_sub" filterable allow-create default-first-option placeholder="選擇會員或輸入 sub" style="width: 100%">
|
||||||
<el-option v-for="m in members" :key="m.authentik_sub" :label="`${m.display_name || m.email || '(no-name)'} (${m.authentik_sub})`" :value="m.authentik_sub" />
|
<el-option v-for="m in members" :key="m.user_sub" :label="`${m.display_name || m.email || '(no-name)'} (${m.user_sub})`" :value="m.user_sub" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="Email" prop="email">
|
<el-form-item label="Email" prop="email">
|
||||||
@@ -91,9 +91,9 @@
|
|||||||
class="max-w-xl mt-4"
|
class="max-w-xl mt-4"
|
||||||
@submit.prevent="handleRevoke"
|
@submit.prevent="handleRevoke"
|
||||||
>
|
>
|
||||||
<el-form-item label="Authentik Sub" prop="authentik_sub">
|
<el-form-item label="User Sub" prop="user_sub">
|
||||||
<el-select v-model="revokeForm.authentik_sub" filterable allow-create default-first-option placeholder="選擇會員或輸入 sub" style="width: 100%">
|
<el-select v-model="revokeForm.user_sub" filterable allow-create default-first-option placeholder="選擇會員或輸入 sub" style="width: 100%">
|
||||||
<el-option v-for="m in members" :key="m.authentik_sub" :label="`${m.display_name || m.email || '(no-name)'} (${m.authentik_sub})`" :value="m.authentik_sub" />
|
<el-option v-for="m in members" :key="m.user_sub" :label="`${m.display_name || m.email || '(no-name)'} (${m.user_sub})`" :value="m.user_sub" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="Scope 類型" prop="scope_type">
|
<el-form-item label="Scope 類型" prop="scope_type">
|
||||||
@@ -171,7 +171,7 @@
|
|||||||
<template #empty><el-empty description="目前沒有直接授權資料" /></template>
|
<template #empty><el-empty description="目前沒有直接授權資料" /></template>
|
||||||
<el-table-column prop="display_name" label="名稱" min-width="140" />
|
<el-table-column prop="display_name" label="名稱" min-width="140" />
|
||||||
<el-table-column prop="email" label="Email" min-width="200" />
|
<el-table-column prop="email" label="Email" min-width="200" />
|
||||||
<el-table-column prop="authentik_sub" label="Sub" min-width="200" />
|
<el-table-column prop="user_sub" label="Sub" min-width="200" />
|
||||||
<el-table-column prop="scope_type" label="Scope" width="90" />
|
<el-table-column prop="scope_type" label="Scope" width="90" />
|
||||||
<el-table-column prop="scope_id" label="Scope ID" min-width="120" />
|
<el-table-column prop="scope_id" label="Scope ID" min-width="120" />
|
||||||
<el-table-column prop="system" label="系統" width="100" />
|
<el-table-column prop="system" label="系統" width="100" />
|
||||||
@@ -220,7 +220,7 @@ const grantError = ref('')
|
|||||||
const grantSuccess = ref('')
|
const grantSuccess = ref('')
|
||||||
|
|
||||||
const grantForm = reactive({
|
const grantForm = reactive({
|
||||||
authentik_sub: '',
|
user_sub: '',
|
||||||
email: '',
|
email: '',
|
||||||
display_name: '',
|
display_name: '',
|
||||||
scope_type: '',
|
scope_type: '',
|
||||||
@@ -251,7 +251,7 @@ const grantScopeOptions = computed(() => {
|
|||||||
|
|
||||||
const required = { required: true, message: '必填', trigger: 'blur' }
|
const required = { required: true, message: '必填', trigger: 'blur' }
|
||||||
const grantRules = {
|
const grantRules = {
|
||||||
authentik_sub: [required],
|
user_sub: [required],
|
||||||
email: [required],
|
email: [required],
|
||||||
display_name: [required],
|
display_name: [required],
|
||||||
scope_type: [required],
|
scope_type: [required],
|
||||||
@@ -291,7 +291,7 @@ const revokeError = ref('')
|
|||||||
const revokeSuccess = ref('')
|
const revokeSuccess = ref('')
|
||||||
|
|
||||||
const revokeForm = reactive({
|
const revokeForm = reactive({
|
||||||
authentik_sub: '',
|
user_sub: '',
|
||||||
scope_type: '',
|
scope_type: '',
|
||||||
scope_id: '',
|
scope_id: '',
|
||||||
system: '',
|
system: '',
|
||||||
@@ -319,7 +319,7 @@ const revokeScopeOptions = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const revokeRules = {
|
const revokeRules = {
|
||||||
authentik_sub: [required],
|
user_sub: [required],
|
||||||
scope_type: [required],
|
scope_type: [required],
|
||||||
scope_id: [required],
|
scope_id: [required],
|
||||||
system: [required],
|
system: [required],
|
||||||
@@ -426,8 +426,8 @@ watch(() => grantForm.system, () => { grantForm.module = '' })
|
|||||||
watch(() => revokeForm.scope_type, () => { revokeForm.scope_id = '' })
|
watch(() => revokeForm.scope_type, () => { revokeForm.scope_id = '' })
|
||||||
watch(() => revokeForm.system, () => { revokeForm.module = '' })
|
watch(() => revokeForm.system, () => { revokeForm.module = '' })
|
||||||
|
|
||||||
watch(() => grantForm.authentik_sub, (sub) => {
|
watch(() => grantForm.user_sub, (sub) => {
|
||||||
const user = members.value.find(m => m.authentik_sub === sub)
|
const user = members.value.find(m => m.user_sub === sub)
|
||||||
if (!user) return
|
if (!user) return
|
||||||
grantForm.email = user.email || ''
|
grantForm.email = user.email || ''
|
||||||
grantForm.display_name = user.display_name || ''
|
grantForm.display_name = user.display_name || ''
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
<template v-if="snapshot">
|
<template v-if="snapshot">
|
||||||
<p class="text-sm text-gray-500 mb-3">
|
<p class="text-sm text-gray-500 mb-3">
|
||||||
Sub:<span class="font-mono">{{ snapshot.authentik_sub }}</span>
|
Sub:<span class="font-mono">{{ snapshot.user_sub }}</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<el-empty
|
<el-empty
|
||||||
|
|||||||
Reference in New Issue
Block a user