refactor(auth): unify authorization patterns with MANAGE_OWN_PROFILE permission
Issue #2: The profile route used a custom role-based check instead of the permission-based pattern used everywhere else. Changes: - Add MANAGE_OWN_PROFILE permission to backend Permission enum - Add permission to ROLE_REGULAR role definition - Update profile routes to use require_permission(MANAGE_OWN_PROFILE) - Remove custom require_regular_user dependency - Update frontend Permission constant and profile page - Update invites page to use permission instead of role check - Update profile tests with proper permission mocking This ensures consistent authorization patterns across all routes.
This commit is contained in:
parent
81cd34b0e7
commit
21698203fe
7 changed files with 40 additions and 23 deletions
|
|
@ -40,6 +40,9 @@ class Permission(str, PyEnum):
|
|||
# Audit permissions
|
||||
VIEW_AUDIT = "view_audit"
|
||||
|
||||
# Profile permissions
|
||||
MANAGE_OWN_PROFILE = "manage_own_profile"
|
||||
|
||||
# Invite permissions
|
||||
MANAGE_INVITES = "manage_invites"
|
||||
VIEW_OWN_INVITES = "view_own_invites"
|
||||
|
|
@ -93,6 +96,7 @@ ROLE_DEFINITIONS: dict[str, RoleConfig] = {
|
|||
Permission.VIEW_COUNTER,
|
||||
Permission.INCREMENT_COUNTER,
|
||||
Permission.USE_SUM,
|
||||
Permission.MANAGE_OWN_PROFILE,
|
||||
Permission.VIEW_OWN_INVITES,
|
||||
Permission.BOOK_APPOINTMENT,
|
||||
Permission.VIEW_OWN_APPOINTMENTS,
|
||||
|
|
|
|||
|
|
@ -1,30 +1,18 @@
|
|||
"""Profile routes for user contact details."""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from auth import get_current_user
|
||||
from auth import require_permission
|
||||
from database import get_db
|
||||
from models import ROLE_REGULAR, User
|
||||
from models import Permission, User
|
||||
from schemas import ProfileResponse, ProfileUpdate
|
||||
from validation import validate_profile_fields
|
||||
|
||||
router = APIRouter(prefix="/api/profile", tags=["profile"])
|
||||
|
||||
|
||||
async def require_regular_user(
|
||||
current_user: User = Depends(get_current_user),
|
||||
) -> User:
|
||||
"""Dependency that requires the user to have the 'regular' role."""
|
||||
if ROLE_REGULAR not in current_user.role_names:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Profile access is only available to regular users",
|
||||
)
|
||||
return current_user
|
||||
|
||||
|
||||
async def get_godfather_email(db: AsyncSession, godfather_id: int | None) -> str | None:
|
||||
"""Get the email of a godfather user by ID."""
|
||||
if not godfather_id:
|
||||
|
|
@ -35,7 +23,7 @@ async def get_godfather_email(db: AsyncSession, godfather_id: int | None) -> str
|
|||
|
||||
@router.get("", response_model=ProfileResponse)
|
||||
async def get_profile(
|
||||
current_user: User = Depends(require_regular_user),
|
||||
current_user: User = Depends(require_permission(Permission.MANAGE_OWN_PROFILE)),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
) -> ProfileResponse:
|
||||
"""Get the current user's profile (contact details and godfather)."""
|
||||
|
|
@ -54,7 +42,7 @@ async def get_profile(
|
|||
async def update_profile(
|
||||
data: ProfileUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_regular_user),
|
||||
current_user: User = Depends(require_permission(Permission.MANAGE_OWN_PROFILE)),
|
||||
) -> ProfileResponse:
|
||||
"""Update the current user's profile (contact details)."""
|
||||
# Validate all fields
|
||||
|
|
|
|||
|
|
@ -166,12 +166,12 @@ class TestGetProfileEndpoint:
|
|||
assert data["nostr_npub"] is None
|
||||
|
||||
async def test_admin_user_cannot_access_profile(self, client_factory, admin_user):
|
||||
"""Admin user gets 403 when trying to access profile."""
|
||||
"""Admin user gets 403 when trying to access profile (lacks MANAGE_OWN_PROFILE)."""
|
||||
async with client_factory.create(cookies=admin_user["cookies"]) as client:
|
||||
response = await client.get("/api/profile")
|
||||
|
||||
assert response.status_code == 403
|
||||
assert "regular users" in response.json()["detail"].lower()
|
||||
assert "manage_own_profile" in response.json()["detail"].lower()
|
||||
|
||||
async def test_unauthenticated_user_gets_401(self, client_factory):
|
||||
"""Unauthenticated user gets 401."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue