This document outlines the different permission levels and capabilities for users, moderators, and administrators in the Divemap application.
The application has three main permission levels:
- Regular User - Basic authenticated user with limited privileges
- Moderator - Elevated privileges for content moderation and management
- Admin - Full system access and administrative capabilities
| Feature/Action | Regular User | Moderator | Admin |
|---|---|---|---|
| Authentication | |||
| Register account | ✅ | ✅ | ✅ |
| Login/Logout | ✅ | ✅ | ✅ |
| Google OAuth login | ✅ | ✅ | ✅ |
| Change own password | ✅ | ✅ | ✅ |
| View own profile | ✅ | ✅ | ✅ |
| Update own profile | ✅ | ✅ | ✅ |
| View public user profiles | ✅ | ✅ | ✅ |
| User Management | |||
| View all users | ❌ | ✅ | ✅ |
| Create new users | ❌ | ❌ | ✅ |
| Update any user | ❌ | ❌ | ✅ |
| Delete users | ❌ | ❌ | ✅ |
| Enable/disable users | ❌ | ❌ | ✅ |
| Dive Sites | |||
| View dive sites | ✅ | ✅ | ✅ |
| View dive site details | ✅ | ✅ | ✅ |
| Create dive sites | ✅ | ✅ | ✅ |
| Edit own dive sites | ✅ | ✅ | ✅ |
| Edit any dive site | ❌ | ✅ | ✅ |
| Delete own dive sites | ✅ | ✅ | ✅ |
| Delete any dive site | ❌ | ✅ | ✅ |
| Add media to dive sites | ✅ | ✅ | ✅ |
| Delete media from dive sites | ✅ | ✅ | ✅ |
| Rate dive sites | ✅ | ✅ | ✅ |
| Comment on dive sites | ✅ | ✅ | ✅ |
| Manage dive site aliases | ❌ | ✅ | ✅ |
| Diving Centers | |||
| View diving centers | ✅ | ✅ | ✅ |
| View diving center details | ✅ | ✅ | ✅ |
| Create diving centers | ✅ | ✅ | ✅ |
| Edit own diving centers | ✅ | ✅ | ✅ |
| Edit any diving center | ❌ | ✅ | ✅ |
| Delete own diving centers | ✅ | ✅ | ✅ |
| Delete any diving center | ❌ | ✅ | ✅ |
| Rate diving centers | ✅ | ✅ | ✅ |
| Comment on diving centers | ✅ | ✅ | ✅ |
| Manage gear rental costs | ✅ | ✅ | ✅ |
| Manage organization affiliations | ✅ | ✅ | ✅ |
| Claim diving center ownership | ✅ | ✅ | ✅ |
| Approve ownership requests | ❌ | ✅ | ✅ |
| Dives | |||
| View public dives | ✅ | ✅ | ✅ |
| View private dives | Own only | Own only | All |
| Create dives | ✅ | ✅ | ✅ |
| Edit own dives | ✅ | ✅ | ✅ |
| Edit any dive | ❌ | ❌ | ✅ |
| Delete own dives | ✅ | ✅ | ✅ |
| Delete any dive | ❌ | ❌ | ✅ |
| Add media to dives | ✅ | ✅ | ✅ |
| Delete media from dives | ✅ | ✅ | ✅ |
| Manage dive tags | ✅ | ✅ | ✅ |
| Tags | |||
| View all tags | ✅ | ✅ | ✅ |
| Create new tags | ❌ | ✅ | ✅ |
| Update tags | ❌ | ✅ | ✅ |
| Delete tags | ❌ | ✅ | ✅ |
| Assign tags to dive sites | ❌ | ✅ | ✅ |
| Remove tags from dive sites | ❌ | ✅ | ✅ |
| Diving Organizations | |||
| View diving organizations | ✅ | ✅ | ✅ |
| Create diving organizations | ❌ | ✅ | ✅ |
| Update diving organizations | ❌ | ✅ | ✅ |
| Delete diving organizations | ❌ | ✅ | ✅ |
| User Certifications | |||
| View own certifications | ✅ | ✅ | ✅ |
| View other users' certifications | ❌ | ❌ | ✅ |
| Add own certifications | ✅ | ✅ | ✅ |
| Update own certifications | ✅ | ✅ | ✅ |
| Delete own certifications | ✅ | ✅ | ✅ |
| Newsletters | |||
| View newsletters | ❌ | ✅ | ✅ |
| Upload newsletters | ❌ | ✅ | ✅ |
| Update newsletters | ❌ | ✅ | ✅ |
| Delete newsletters | ❌ | ✅ | ✅ |
| View parsed dive trips | ❌ | ✅ | ✅ |
| Manage dive trips | ❌ | ✅ | ✅ |
| System Administration | |||
| Access admin panel | ❌ | ❌ | ✅ |
| View system statistics | ❌ | ❌ | ✅ |
| Manage rate limiting | ❌ | ❌ | ✅ |
| Bypass rate limiting | ❌ | ❌ | ✅ |
| Content Moderation | |||
| Moderate comments | ❌ | ✅ | ✅ |
| Moderate ratings | ❌ | ✅ | ✅ |
| Moderate user-generated content | ❌ | ✅ | ✅ |
| Data Management | |||
| Export data | ❌ | ❌ | ✅ |
| Import data | ❌ | ❌ | ✅ |
| Backup/restore data | ❌ | ❌ | ✅ |
- Admins have all permissions that moderators and regular users have
- Moderators have all permissions that regular users have, plus content moderation capabilities
- Regular users have basic authenticated user permissions
Permissions are enforced at the API level using dependency injection:
# Admin-only endpoints
@router.get("/admin/users")
async def list_all_users(
current_user: User = Depends(get_current_admin_user)
):
# Only admins can access this endpoint
pass
# Admin or moderator endpoints
@router.post("/tags")
async def create_tag(
current_user: User = Depends(is_admin_or_moderator)
):
# Both admins and moderators can access this endpoint
pass
# Regular authenticated user endpoints
@router.post("/dives")
async def create_dive(
current_user: User = Depends(get_current_active_user)
):
# Any authenticated user can access this endpoint
passPermissions are enforced at the component level:
// Admin-only routes
<Route
path="/admin"
element={
<ProtectedRoute requireAdmin={true}>
<Admin />
</ProtectedRoute>
}
/>
// Admin/moderator or owner routes
<Route
path="/dive-sites/:id/edit"
element={
<ProtectedEditRoute>
<EditDiveSite />
</ProtectedEditRoute>
}
/>get_current_user()- Basic authentication checkget_current_active_user()- Ensures user account is enabledget_current_admin_user()- Ensures user is adminget_current_moderator_user()- Ensures user is admin or moderatoris_admin_or_moderator()- Alternative check for admin/moderator permissions
class User(Base):
is_admin = Column(Boolean, default=False)
is_moderator = Column(Boolean, default=False)
enabled = Column(Boolean, default=True)- Backend Validation: All permissions are validated on the backend, never trust frontend-only checks
- Rate Limiting: Admins can bypass rate limiting for administrative tasks
- Ownership Checks: Users can only modify content they own, unless they have elevated permissions
- Account Status: Disabled accounts cannot access any protected endpoints
- Token Validation: All API requests require valid JWT tokens
- Always use the appropriate permission check function for each endpoint
- Test permission boundaries thoroughly
- Log administrative actions for audit purposes
- Implement proper error messages for permission denials
- Use the principle of least privilege - only grant necessary permissions
from app.auth import (
get_current_user,
get_current_active_user,
get_current_admin_user,
get_current_moderator_user,
is_admin_or_moderator
)1. Admin-Only Endpoints
@router.get("/admin/users")
async def list_all_users(
current_user: User = Depends(get_current_admin_user),
db: Session = Depends(get_db)
):
"""List all users (admin only)"""
# Only admins can access this endpoint
pass2. Admin/Moderator Endpoints
@router.post("/newsletters/upload")
async def upload_newsletter(
file: UploadFile = File(...),
current_user: User = Depends(is_admin_or_moderator),
db: Session = Depends(get_db)
):
"""Upload newsletter (admin/moderator only)"""
# Both admins and moderators can access this endpoint
pass3. Regular Authenticated User Endpoints
@router.post("/dives")
async def create_dive(
dive: DiveCreate,
current_user: User = Depends(get_current_active_user),
db: Session = Depends(get_db)
):
"""Create a dive (authenticated users only)"""
# Any authenticated and enabled user can access this endpoint
pass4. Optional Authentication Endpoints
@router.get("/dive-sites")
async def get_dive_sites(
current_user: Optional[User] = Depends(get_current_user_optional),
db: Session = Depends(get_db)
):
"""Get dive sites (public, but user context available if authenticated)"""
# Public endpoint, but user context available if authenticated
passNever use manual permission checks:
# ❌ WRONG - Manual permission checking
@router.get("/admin/users")
async def list_all_users(
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
if not current_user.is_admin:
raise HTTPException(status_code=403, detail="Admin required")
# ... rest of functionNever use inconsistent dependencies:
# ❌ WRONG - Inconsistent permission level
@router.get("/admin/users")
async def list_all_users(
current_user: User = Depends(is_admin_or_moderator), # Should be get_current_admin_user
db: Session = Depends(get_db)
):
# ... function bodyUse proper dependency functions:
# ✅ CORRECT - Proper admin dependency
@router.get("/admin/users")
async def list_all_users(
current_user: User = Depends(get_current_admin_user),
db: Session = Depends(get_db)
):
# ... function bodyUse appropriate permission levels:
# ✅ CORRECT - Admin/moderator for content management
@router.post("/tags")
async def create_tag(
tag: TagCreate,
current_user: User = Depends(is_admin_or_moderator),
db: Session = Depends(get_db)
):
# ... function body// Admin-only routes
<Route
path="/admin"
element={
<ProtectedRoute requireAdmin={true}>
<Admin />
</ProtectedRoute>
}
/>
// Admin/moderator routes
<Route
path="/admin/newsletters"
element={
<ProtectedRoute requireAdmin={true} allowModerator={true}>
<AdminNewsletters />
</ProtectedRoute>
}
/>
// Owner or admin routes
<Route
path="/dive-sites/:id/edit"
element={
<ProtectedEditRoute>
<EditDiveSite />
</ProtectedEditRoute>
}
/>// Check permissions in components
const { user } = useAuth();
if (!user?.is_admin) {
return <div>Access denied. Admin privileges required.</div>;
}
// For admin/moderator functionality
if (!user?.is_admin && !user?.is_moderator) {
return <div>Access denied. Admin or moderator privileges required.</div>;
}| Function | Purpose | Use Case |
|---|---|---|
get_current_user() |
Basic authentication | Public endpoints with user context |
get_current_active_user() |
Authenticated + enabled | Regular user functionality |
get_current_admin_user() |
Admin only | Admin-only endpoints |
get_current_moderator_user() |
Admin or moderator | Content moderation |
is_admin_or_moderator() |
Admin or moderator | Alternative to get_current_moderator_user |
get_current_user_optional() |
Optional authentication | Public endpoints with optional user context |
Standard HTTP Status Codes:
401 Unauthorized- No valid authentication token403 Forbidden- Valid token but insufficient permissions404 Not Found- Resource not found (don't reveal existence)
Example Error Responses:
# Automatic error responses from dependency functions
# get_current_admin_user() returns 403 if user is not admin
# get_current_active_user() returns 401 if user is disabledTest Cases to Include:
def test_admin_endpoint_requires_admin(self, client):
"""Test that admin endpoints require admin privileges"""
def test_moderator_endpoint_allows_moderator(self, client, moderator_headers):
"""Test that moderator endpoints allow moderators"""
def test_regular_user_cannot_access_admin_endpoint(self, client, user_headers):
"""Test that regular users cannot access admin endpoints"""
def test_disabled_user_cannot_access_protected_endpoint(self, client, disabled_user_headers):
"""Test that disabled users cannot access protected endpoints"""When reviewing permission-related code, ensure:
- Uses appropriate dependency function (not manual checks)
- Permission level matches endpoint purpose
- No hardcoded permission checks in function body
- Proper error handling for permission denials
- Frontend routes properly protected
- Tests cover permission boundaries
- Documentation reflects permission requirements
- No security bypasses or workarounds
The following permission changes were implemented to provide moderators with enhanced content management capabilities:
- Moderators can now approve ownership requests - Previously admin-only
- Moderators can assign diving center owners - Previously admin-only
- Moderators can create diving organizations - Previously admin-only
- Moderators can update diving organizations - Previously admin-only
- Moderators can delete diving organizations - Previously admin-only
- Moderators can upload newsletters - Previously admin-only
- Moderators can update newsletters - Previously admin-only
- Moderators can delete newsletters - Previously admin-only
- Moderators can manage dive trips - Previously admin-only
- Moderators can view all users - Previously admin-only