601 lines
14 KiB
Markdown
601 lines
14 KiB
Markdown
# 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.userContext` containing `userId`, `email`, `isAdmin`, `adminRecord`
|
|
- **admin-guard.plugin.ts**: `fastify.requireAdmin` preHandler that checks `admin_users` table 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
|
|
|
|
```sql
|
|
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 Auth0
|
|
- `email` - For admin lookups by email
|
|
- `created_at` - For audit trails
|
|
- `revoked_at` - For active admin queries
|
|
|
|
### admin_audit_logs table
|
|
|
|
```sql
|
|
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
|
|
|
|
```sql
|
|
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:
|
|
|
|
1. **JWT validation** - User must be authenticated
|
|
2. **Admin check** - User must exist in `admin_users` table
|
|
3. **Active status** - User's `revoked_at` must be NULL
|
|
4. **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:
|
|
```typescript
|
|
// 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:
|
|
```sql
|
|
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
|
|
|
|
1. **Database migrations** - Run all migrations before deploying
|
|
2. **Initial admin** - First admin seeded automatically in migration
|
|
3. **Auth0 configuration** - Admin user must exist in Auth0
|
|
|
|
### Deployment Steps
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```bash
|
|
# 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/`
|
|
|
|
```bash
|
|
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/`
|
|
|
|
```bash
|
|
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__/`
|
|
|
|
```bash
|
|
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
|
|
|
|
1. **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)
|
|
|
|
2. **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_users` table
|
|
- Check `revoked_at` is 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_logs` table exists
|
|
- Verify migrations ran
|
|
- Check database connection
|
|
|
|
### Logs
|
|
|
|
View admin-related logs:
|
|
|
|
```bash
|
|
# 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
|
|
|
|
1. **Role-based permissions** - Extend from binary admin to granular roles (admin, catalog_editor, station_manager)
|
|
2. **2FA for admins** - Enhanced security with two-factor authentication
|
|
3. **Admin impersonation** - Test user issues as admin without changing password
|
|
4. **Bulk operations** - Import/export catalog data
|
|
5. **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`
|