14 KiB
Admin Feature Documentation
Complete reference for the admin role management, authorization, and cross-tenant oversight capabilities in MotoVaultPro.
Overview
The admin feature provides role-based access control for system administrators to manage:
- Admin user accounts (create, revoke, reinstate)
- Vehicle catalog data (makes, models, years, trims, engines)
- Gas stations and user favorites
- Complete audit trail of all admin actions
Architecture
Backend Feature Capsule
Location: backend/src/features/admin/
Structure:
admin/
├── api/
│ ├── admin.controller.ts - HTTP handlers for admin management
│ ├── admin.routes.ts - Route registration
│ ├── admin.validation.ts - Input validation schemas
│ ├── catalog.controller.ts - Vehicle catalog handlers
│ └── stations.controller.ts - Station oversight handlers
├── domain/
│ ├── admin.types.ts - TypeScript type definitions
│ ├── admin.service.ts - Admin user management logic
│ ├── vehicle-catalog.service.ts - Catalog CRUD logic
│ └── station-oversight.service.ts - Station management logic
├── data/
│ └── admin.repository.ts - Database access layer
├── migrations/
│ ├── 001_create_admin_users.sql - Admin tables and seed
│ └── 002_create_platform_change_log.sql - Catalog audit log
└── tests/
├── unit/ - Service and guard tests
├── integration/ - Full API endpoint tests
└── fixtures/ - Test data
Core Plugins
- auth.plugin.ts: Enhanced with
request.userContextcontaininguserId,email,isAdmin,adminRecord - admin-guard.plugin.ts:
fastify.requireAdminpreHandler that checksadmin_userstable and enforces 403 on non-admins
Frontend Feature
Location: frontend/src/features/admin/
Structure:
admin/
├── types/admin.types.ts - TypeScript types (mirroring backend)
├── api/admin.api.ts - API client functions
├── hooks/
│ ├── useAdminAccess.ts - Verify admin status
│ ├── useAdmins.ts - Admin user management
│ ├── useCatalog.ts - Vehicle catalog
│ └── useStationOverview.ts - Station management
├── pages/
│ ├── AdminUsersPage.tsx - Desktop user management
│ ├── AdminCatalogPage.tsx - Desktop catalog management
│ └── AdminStationsPage.tsx - Desktop station management
├── mobile/
│ ├── AdminUsersMobileScreen.tsx - Mobile user management
│ ├── AdminCatalogMobileScreen.tsx - Mobile catalog management
│ └── AdminStationsMobileScreen.tsx - Mobile station management
└── __tests__/ - Component and hook tests
Database Schema
admin_users table
CREATE TABLE admin_users (
auth0_sub VARCHAR(255) PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
role VARCHAR(50) NOT NULL DEFAULT 'admin',
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
created_by VARCHAR(255) NOT NULL,
revoked_at TIMESTAMP WITH TIME ZONE,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
Indexes:
auth0_sub(PRIMARY KEY) - OAuth ID from Auth0email- For admin lookups by emailcreated_at- For audit trailsrevoked_at- For active admin queries
admin_audit_logs table
CREATE TABLE admin_audit_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
actor_admin_id VARCHAR(255) NOT NULL,
target_admin_id VARCHAR(255),
action VARCHAR(100) NOT NULL,
resource_type VARCHAR(100),
resource_id VARCHAR(255),
context JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
Actions logged:
- CREATE - New admin or resource created
- UPDATE - Resource updated
- DELETE - Resource deleted
- REVOKE - Admin access revoked
- REINSTATE - Admin access restored
- VIEW - Data accessed (for sensitive operations)
platform_change_log table
CREATE TABLE platform_change_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
change_type VARCHAR(50) NOT NULL,
resource_type VARCHAR(100) NOT NULL,
resource_id VARCHAR(255),
old_value JSONB,
new_value JSONB,
changed_by VARCHAR(255) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
Resource types:
- makes, models, years, trims, engines
- stations
- users
API Reference
Phase 2: Admin Management
List all admins
GET /api/admin/admins
Authorization: Bearer <JWT>
Guard: fastify.requireAdmin
Response (200):
{
"total": 2,
"admins": [
{
"auth0Sub": "auth0|admin1",
"email": "admin@motovaultpro.com",
"role": "admin",
"createdAt": "2024-01-01T00:00:00Z",
"createdBy": "system",
"revokedAt": null,
"updatedAt": "2024-01-01T00:00:00Z"
}
]
}
Create admin
POST /api/admin/admins
Authorization: Bearer <JWT>
Guard: fastify.requireAdmin
Content-Type: application/json
Request:
{
"email": "newadmin@example.com",
"role": "admin"
}
Response (201):
{
"auth0Sub": "auth0|newadmin",
"email": "newadmin@example.com",
"role": "admin",
"createdAt": "2024-01-15T10:30:00Z",
"createdBy": "auth0|existing",
"revokedAt": null,
"updatedAt": "2024-01-15T10:30:00Z"
}
Audit log entry:
{
"actor_admin_id": "auth0|existing",
"target_admin_id": "auth0|newadmin",
"action": "CREATE",
"resource_type": "admin_user",
"resource_id": "newadmin@example.com",
"context": { "email": "newadmin@example.com", "role": "admin" }
}
Revoke admin
PATCH /api/admin/admins/:auth0Sub/revoke
Authorization: Bearer <JWT>
Guard: fastify.requireAdmin
Response (200):
{
"auth0Sub": "auth0|toadmin",
"email": "admin@motovaultpro.com",
"role": "admin",
"createdAt": "2024-01-01T00:00:00Z",
"createdBy": "system",
"revokedAt": "2024-01-15T10:35:00Z",
"updatedAt": "2024-01-15T10:35:00Z"
}
Errors:
- 400 Bad Request - Last active admin (cannot revoke)
- 403 Forbidden - Not an admin
- 404 Not Found - Admin not found
Reinstate admin
PATCH /api/admin/admins/:auth0Sub/reinstate
Authorization: Bearer <JWT>
Guard: fastify.requireAdmin
Response (200):
{
"auth0Sub": "auth0|toadmin",
"email": "admin@motovaultpro.com",
"role": "admin",
"createdAt": "2024-01-01T00:00:00Z",
"createdBy": "system",
"revokedAt": null,
"updatedAt": "2024-01-15T10:40:00Z"
}
Audit logs
GET /api/admin/audit-logs?limit=100&offset=0
Authorization: Bearer <JWT>
Guard: fastify.requireAdmin
Response (200):
{
"total": 150,
"logs": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"actor_admin_id": "auth0|admin1",
"target_admin_id": "auth0|admin2",
"action": "CREATE",
"resource_type": "admin_user",
"resource_id": "admin2@motovaultpro.com",
"context": { "email": "admin2@motovaultpro.com", "role": "admin" },
"created_at": "2024-01-15T10:30:00Z"
}
]
}
Phase 3: Catalog CRUD
All catalog endpoints follow RESTful patterns:
GET /api/admin/catalog/{resource} - List all
GET /api/admin/catalog/{parent}/{parentId}/{resource} - List by parent
POST /api/admin/catalog/{resource} - Create
PUT /api/admin/catalog/{resource}/:id - Update
DELETE /api/admin/catalog/{resource}/:id - Delete
Resources: makes, models, years, trims, engines
Example: Get all makes
GET /api/admin/catalog/makes
Guard: fastify.requireAdmin
Response (200):
{
"total": 42,
"makes": [
{ "id": "1", "name": "Toyota", "createdAt": "...", "updatedAt": "..." },
{ "id": "2", "name": "Honda", "createdAt": "...", "updatedAt": "..." }
]
}
Cache invalidation: All mutations invalidate platform:* Redis keys
Audit trail: All mutations recorded in platform_change_log with old and new values
Phase 4: Station Oversight
List all stations
GET /api/admin/stations?limit=100&offset=0&search=query
Guard: fastify.requireAdmin
Response (200):
{
"total": 1250,
"stations": [
{
"id": "station-1",
"placeId": "ChIJxxx",
"name": "Shell Station Downtown",
"address": "123 Main St",
"latitude": 40.7128,
"longitude": -74.0060,
"createdAt": "...",
"deletedAt": null
}
]
}
Create station
POST /api/admin/stations
Guard: fastify.requireAdmin
Content-Type: application/json
Request:
{
"placeId": "ChIJxxx",
"name": "New Station",
"address": "456 Oak Ave",
"latitude": 40.7580,
"longitude": -73.9855
}
Response (201): Station object with all fields
Cache invalidation:
- mvp:stations:* - All station caches
- mvp:stations:search:* - Search result caches
Delete station (soft or hard)
DELETE /api/admin/stations/:stationId?force=false
Guard: fastify.requireAdmin
Query parameters:
- force=false (default) - Soft delete (set deleted_at)
- force=true - Hard delete (permanent removal)
Response (204 No Content)
User station management
GET /api/admin/users/:userId/stations
Guard: fastify.requireAdmin
Response (200):
{
"userId": "auth0|user123",
"stations": [...]
}
Authorization Rules
Admin Guard
The fastify.requireAdmin preHandler enforces:
- JWT validation - User must be authenticated
- Admin check - User must exist in
admin_userstable - Active status - User's
revoked_atmust be NULL - Error response - Returns 403 Forbidden with message "Admin access required"
Last Admin Protection
The system maintains at least one active admin:
- Cannot revoke the last active admin (returns 400 Bad Request)
- Prevents system lockout
- Enforced in
AdminService.revokeAdmin()
Audit Trail
All admin actions logged:
- Actor admin ID (who performed action)
- Target admin ID (who was affected, if applicable)
- Action type (CREATE, UPDATE, DELETE, REVOKE, REINSTATE)
- Resource type and ID
- Context (relevant data, like old/new values)
- Timestamp
Security Considerations
Input Validation
All inputs validated using Zod schemas:
- Email format and uniqueness
- Role enum validation
- Required field presence
- Type checking
Parameterized Queries
All database operations use parameterized queries:
// Good - Parameterized
const result = await pool.query(
'SELECT * FROM admin_users WHERE email = $1',
[email]
);
// Bad - SQL concatenation (never done)
const result = await pool.query(
`SELECT * FROM admin_users WHERE email = '${email}'`
);
Transaction Support
Catalog mutations wrapped in transactions:
BEGIN;
-- INSERT/UPDATE/DELETE operations
COMMIT; -- or ROLLBACK on error
Cache Invalidation
Prevents stale data:
- All catalog mutations invalidate
platform:*keys - All station mutations invalidate
mvp:stations:*keys - User station mutations invalidate
mvp:stations:saved:{userId}
Deployment
Prerequisites
- Database migrations - Run all migrations before deploying
- Initial admin - First admin seeded automatically in migration
- Auth0 configuration - Admin user must exist in Auth0
Deployment Steps
# 1. Build containers
make rebuild
# 2. Run migrations (automatically on startup)
docker compose exec mvp-backend npm run migrate
# 3. Verify admin user created
docker compose exec mvp-backend npm run verify-admin
# 4. Check backend health
curl https://motovaultpro.com/api/health
# 5. Verify frontend build
curl https://motovaultpro.com
Rollback
If issues occur:
# Revoke problematic admin
docker compose exec mvp-backend npm run admin:revoke admin@motovaultpro.com
# Reinstate previous admin
docker compose exec mvp-backend npm run admin:reinstate <auth0_sub>
# Downgrade admin feature (keep data)
docker compose down
git checkout previous-version
make rebuild
make start
Testing
Backend Unit Tests
Location: backend/src/features/admin/tests/unit/
npm test -- features/admin/tests/unit
Tests:
- Admin guard authorization logic
- Admin service business rules
- Repository error handling
- Last admin protection
Backend Integration Tests
Location: backend/src/features/admin/tests/integration/
npm test -- features/admin/tests/integration
Tests:
- Full API endpoints
- Database persistence
- Audit logging
- Admin guard in request context
- CRUD operations
- Cache invalidation
- Permission enforcement
Frontend Tests
Location: frontend/src/features/admin/__tests__/
docker compose exec mvp-frontend npm test
Tests:
- useAdminAccess hook (loading, admin, non-admin, error states)
- Admin page rendering
- Admin route guards
- Navigation
E2E Testing
-
Desktop workflow
- Navigate to
/garage/settings - Verify "Admin Console" card visible (if admin)
- Click "User Management"
- Verify admin list loads
- Try to create new admin (if permitted)
- Navigate to
-
Mobile workflow
- Open app on mobile viewport (375px)
- Navigate to settings
- Verify admin section visible (if admin)
- Tap "Users" button
- Verify admin list loads
Monitoring & Troubleshooting
Common Issues
Issue: "Admin access required" (403 Forbidden)
- Verify user in
admin_userstable - Check
revoked_atis NULL - Verify JWT token valid
- Check Auth0 configuration
Issue: Stale catalog data
- Verify Redis is running
- Check cache invalidation logs
- Manually flush:
redis-cli DEL 'mvp:platform:*'
Issue: Audit log not recording
- Check
admin_audit_logstable exists - Verify migrations ran
- Check database connection
Logs
View admin-related logs:
# Backend logs
make logs | grep -i admin
# Check specific action
docker compose exec mvp-backend psql -U postgres -d motovaultpro \
-c "SELECT * FROM admin_audit_logs WHERE action = 'CREATE' ORDER BY created_at DESC LIMIT 10;"
# Check revoked admins
docker compose exec mvp-backend psql -U postgres -d motovaultpro \
-c "SELECT email, revoked_at FROM admin_users WHERE revoked_at IS NOT NULL;"
Next Steps
Planned Enhancements
- Role-based permissions - Extend from binary admin to granular roles (admin, catalog_editor, station_manager)
- 2FA for admins - Enhanced security with two-factor authentication
- Admin impersonation - Test user issues as admin without changing password
- Bulk operations - Import/export catalog data
- Advanced analytics - Admin activity dashboards
References
- Backend feature:
backend/src/features/admin/README.md - Frontend feature:
frontend/src/features/admin/(see individual files) - Architecture:
docs/PLATFORM-SERVICES.md - Testing:
docs/TESTING.md