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:
counterweight 2025-12-21 23:50:06 +01:00
parent 81cd34b0e7
commit 21698203fe
Signed by: counterweight
GPG key ID: 883EDBAA726BD96C
7 changed files with 40 additions and 23 deletions

View file

@ -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