Security Fixes
This commit is contained in:
@@ -25,7 +25,8 @@
|
|||||||
"Bash(git add:*)",
|
"Bash(git add:*)",
|
||||||
"Bash(git commit:*)",
|
"Bash(git commit:*)",
|
||||||
"Bash(git tag:*)",
|
"Bash(git tag:*)",
|
||||||
"Bash(docker build:*)"
|
"Bash(docker build:*)",
|
||||||
|
"Bash(docker run:*)"
|
||||||
],
|
],
|
||||||
"deny": []
|
"deny": []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,12 @@
|
|||||||
Vehicle management platform using Modified Feature Capsules. Each feature in backend/src/features/[name]/ is 100% self-contained with API, domain, data, migrations, external integrations, tests, and docs. Single directory load gives complete context. No shared business logic, only pure utilities in shared-minimal/.
|
Vehicle management platform using Modified Feature Capsules. Each feature in backend/src/features/[name]/ is 100% self-contained with API, domain, data, migrations, external integrations, tests, and docs. Single directory load gives complete context. No shared business logic, only pure utilities in shared-minimal/.
|
||||||
|
|
||||||
## Architecture Philosophy
|
## Architecture Philosophy
|
||||||
Each feature is a complete, self-contained capsule. Load ONE directory for 100% context. Evaluate every feature if it should be in it's own Docker container. This is a microservices based archiecture where production will be run on k8s.
|
- Each feature is a complete, self-contained capsule.
|
||||||
|
- Load ONE directory for 100% context.
|
||||||
|
- Evaluate every feature if it should be in it's own Docker container.
|
||||||
|
- This is a microservices based archiecture where production will be run on k8s.
|
||||||
|
- This is a production only application architecture.
|
||||||
|
- Always assume this application is going into production when you are done. All security and linting should pass.
|
||||||
|
|
||||||
## Navigation & Quick Tasks
|
## Navigation & Quick Tasks
|
||||||
|
|
||||||
@@ -36,25 +41,26 @@ cd backend/src/features/[feature-name]/
|
|||||||
# Creates complete capsule structure with all subdirectories
|
# Creates complete capsule structure with all subdirectories
|
||||||
```
|
```
|
||||||
|
|
||||||
### Running Feature Migrations
|
### Running Migrations
|
||||||
```bash
|
```bash
|
||||||
# Single feature
|
# All features (in dependency order)
|
||||||
npm run migrate:feature [feature-name]
|
|
||||||
|
|
||||||
# All features (respects dependencies)
|
|
||||||
npm run migrate:all
|
npm run migrate:all
|
||||||
|
|
||||||
|
# From project root using Docker
|
||||||
|
make migrate
|
||||||
```
|
```
|
||||||
|
Note: Single-feature migration is not implemented yet. Run the full migration set.
|
||||||
|
|
||||||
### Testing Strategy
|
### Testing Strategy
|
||||||
```bash
|
```bash
|
||||||
# Test single feature (complete isolation)
|
# Run all tests (from project root)
|
||||||
|
make test
|
||||||
|
|
||||||
|
# In backend container shell
|
||||||
|
make shell-backend
|
||||||
|
npm test # all tests
|
||||||
npm test -- features/[feature-name]
|
npm test -- features/[feature-name]
|
||||||
|
|
||||||
# Test feature integration
|
|
||||||
npm test -- features/[feature-name]/tests/integration
|
npm test -- features/[feature-name]/tests/integration
|
||||||
|
|
||||||
# Test everything
|
|
||||||
npm test
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Docker Development Workflow
|
### Docker Development Workflow
|
||||||
@@ -128,10 +134,14 @@ features/[name]/
|
|||||||
|
|
||||||
## Development Environment
|
## Development Environment
|
||||||
All development happens in Docker containers:
|
All development happens in Docker containers:
|
||||||
- **Development**: Dockerfile.dev with npm install during container build
|
- **Development**: `make dev` builds and runs the stack
|
||||||
- **Testing**: make test runs tests in container
|
- **Testing**: `make test` runs backend tests in the container
|
||||||
- **Rebuilding**: make rebuild for code changes
|
- **Rebuilding**: `make rebuild` for code/dependency changes
|
||||||
- **Package changes**: Container rebuild required
|
- **Package changes**: Rebuild backend/frontend containers as needed
|
||||||
|
|
||||||
|
## Authentication (Current State)
|
||||||
|
- Backend uses a Fastify auth plugin that injects a mock user in development/test.
|
||||||
|
- JWT validation via Auth0 is planned; production configuration will enforce it.
|
||||||
|
|
||||||
## External Services
|
## External Services
|
||||||
- **PostgreSQL**: Primary database (port 5432)
|
- **PostgreSQL**: Primary database (port 5432)
|
||||||
@@ -152,4 +162,4 @@ Features must be migrated in dependency order:
|
|||||||
1. **vehicles** (base feature)
|
1. **vehicles** (base feature)
|
||||||
2. **fuel-logs** (depends on vehicles)
|
2. **fuel-logs** (depends on vehicles)
|
||||||
3. **maintenance** (depends on vehicles)
|
3. **maintenance** (depends on vehicles)
|
||||||
4. **stations** (independent)
|
4. **stations** (independent)
|
||||||
|
|||||||
15
Makefile
15
Makefile
@@ -1,10 +1,10 @@
|
|||||||
.PHONY: help setup dev stop clean test logs shell-backend shell-frontend migrate rebuild
|
.PHONY: help setup start dev stop clean test logs shell-backend shell-frontend migrate rebuild
|
||||||
|
|
||||||
help:
|
help:
|
||||||
@echo "MotoVaultPro - Fully Containerized Modified Feature Capsule Architecture"
|
@echo "MotoVaultPro - Production-Ready Modified Feature Capsule Architecture"
|
||||||
@echo "Commands:"
|
@echo "Commands:"
|
||||||
@echo " make setup - Initial project setup"
|
@echo " make setup - Initial project setup"
|
||||||
@echo " make dev - Start all services in development mode"
|
@echo " make start - Start all services"
|
||||||
@echo " make rebuild - Rebuild and restart containers (for code changes)"
|
@echo " make rebuild - Rebuild and restart containers (for code changes)"
|
||||||
@echo " make stop - Stop all services"
|
@echo " make stop - Stop all services"
|
||||||
@echo " make clean - Clean all data and volumes"
|
@echo " make clean - Clean all data and volumes"
|
||||||
@@ -27,14 +27,17 @@ setup:
|
|||||||
@echo "Backend: http://localhost:3001"
|
@echo "Backend: http://localhost:3001"
|
||||||
@echo "MinIO Console: http://localhost:9001"
|
@echo "MinIO Console: http://localhost:9001"
|
||||||
|
|
||||||
dev:
|
start:
|
||||||
@echo "Starting development environment..."
|
@echo "Starting application services..."
|
||||||
@docker compose up -d --build
|
@docker compose up -d --build
|
||||||
@echo "✅ Development environment running!"
|
@echo "✅ Application running!"
|
||||||
@echo "Frontend: http://localhost:3000"
|
@echo "Frontend: http://localhost:3000"
|
||||||
@echo "Backend: http://localhost:3001/health"
|
@echo "Backend: http://localhost:3001/health"
|
||||||
@echo "View logs with: make logs"
|
@echo "View logs with: make logs"
|
||||||
|
|
||||||
|
# Alias for backward compatibility
|
||||||
|
dev: start
|
||||||
|
|
||||||
stop:
|
stop:
|
||||||
@docker compose down
|
@docker compose down
|
||||||
|
|
||||||
|
|||||||
37
README.md
37
README.md
@@ -32,11 +32,17 @@ Load `docs/database-schema.md` for complete schema overview
|
|||||||
Load `docs/testing.md` for Docker-based testing workflow
|
Load `docs/testing.md` for Docker-based testing workflow
|
||||||
Only use docker containers for testing. Never install local tools if they do not exist already.
|
Only use docker containers for testing. Never install local tools if they do not exist already.
|
||||||
|
|
||||||
### 4. Development Environment (1 command)
|
### 4. Development Environment (Docker-first)
|
||||||
```bash
|
```bash
|
||||||
make dev # Starts complete Docker environment
|
# One-time setup (copies .env and builds containers)
|
||||||
|
make setup
|
||||||
|
|
||||||
|
# Start/rebuild the full environment
|
||||||
|
make dev
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note: The frontend runs behind nginx with HTTPS in dev. You must provide local certificates in `./certs` (see SSL section below) or the frontend container will fail to start.
|
||||||
|
|
||||||
### 5. Key Principles
|
### 5. Key Principles
|
||||||
- **Docker-First**: All development in containers, no local installs
|
- **Docker-First**: All development in containers, no local installs
|
||||||
- **Feature Independence**: Each feature is completely isolated
|
- **Feature Independence**: Each feature is completely isolated
|
||||||
@@ -45,12 +51,16 @@ make dev # Starts complete Docker environment
|
|||||||
|
|
||||||
### 6. Common Tasks
|
### 6. Common Tasks
|
||||||
```bash
|
```bash
|
||||||
# Test specific feature
|
# Run all migrations (inside containers)
|
||||||
npm test -- features/vehicles
|
|
||||||
|
|
||||||
# Run migrations
|
|
||||||
make migrate
|
make migrate
|
||||||
|
|
||||||
|
# Run all backend tests (inside containers)
|
||||||
|
make test
|
||||||
|
|
||||||
|
# Run tests for a specific feature (from backend container shell)
|
||||||
|
make shell-backend
|
||||||
|
npm test -- features/vehicles
|
||||||
|
|
||||||
# View logs
|
# View logs
|
||||||
make logs
|
make logs
|
||||||
|
|
||||||
@@ -60,10 +70,21 @@ make shell-backend
|
|||||||
|
|
||||||
### 7. Feature Status
|
### 7. Feature Status
|
||||||
- **vehicles**: Complete (primary entity, VIN decoding)
|
- **vehicles**: Complete (primary entity, VIN decoding)
|
||||||
- **fuel-logs**: Implemented (depends on vehicles)
|
- **fuel-logs**: Implemented (depends on vehicles); tests pending
|
||||||
- **maintenance**: Scaffolded (depends on vehicles)
|
- **maintenance**: Scaffolded (depends on vehicles)
|
||||||
- **stations**: Partial (Google Maps integration)
|
- **stations**: Partial (Google Maps integration)
|
||||||
|
|
||||||
|
## SSL for Frontend (Local Dev)
|
||||||
|
- Place `motovaultpro.com.crt` and `motovaultpro.com.key` in `./certs`.
|
||||||
|
- To generate self-signed dev certs:
|
||||||
|
```bash
|
||||||
|
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
|
||||||
|
-keyout certs/motovaultpro.com.key \
|
||||||
|
-out certs/motovaultpro.com.crt \
|
||||||
|
-subj "/CN=localhost"
|
||||||
|
```
|
||||||
|
- Access frontend at `https://localhost:3443` (HTTP on `:3000` redirects to HTTPS).
|
||||||
|
|
||||||
## Architecture Summary
|
## Architecture Summary
|
||||||
Vehicle management platform using Modified Feature Capsule design where each feature is self-contained with API, domain logic, database layer, migrations, external integrations, tests, and documentation in a single directory. Built for AI maintainability with Docker-first development.
|
Vehicle management platform using Modified Feature Capsule design where each feature is self-contained with API, domain logic, database layer, migrations, external integrations, tests, and documentation in a single directory. Built for AI maintainability with Docker-first development.
|
||||||
|
|
||||||
@@ -72,4 +93,4 @@ Vehicle management platform using Modified Feature Capsule design where each fea
|
|||||||
- **Features**: backend/src/features/[name]/README.md
|
- **Features**: backend/src/features/[name]/README.md
|
||||||
- **Database**: docs/database-schema.md
|
- **Database**: docs/database-schema.md
|
||||||
- **Testing**: docs/testing.md
|
- **Testing**: docs/testing.md
|
||||||
- **Security**: docs/security.md
|
- **Security**: docs/security.md
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ RUN npm install && npm cache clean --force
|
|||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Build the application
|
# Build the application
|
||||||
RUN npm run build:docker
|
RUN npm run build
|
||||||
|
|
||||||
# Stage 2: Production runtime
|
# Stage 2: Production runtime
|
||||||
FROM node:20-alpine AS production
|
FROM node:20-alpine AS production
|
||||||
@@ -30,8 +30,9 @@ RUN apk add --no-cache dumb-init
|
|||||||
# Set working directory
|
# Set working directory
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Copy package files
|
# Copy package files and any lock file generated in builder stage
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
|
COPY --from=builder /app/package-lock.json ./
|
||||||
|
|
||||||
# Install only production dependencies
|
# Install only production dependencies
|
||||||
RUN npm ci --omit=dev && npm cache clean --force
|
RUN npm ci --omit=dev && npm cache clean --force
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ make test
|
|||||||
- `database.ts` - PostgreSQL connection pool
|
- `database.ts` - PostgreSQL connection pool
|
||||||
- `redis.ts` - Redis client and cache service
|
- `redis.ts` - Redis client and cache service
|
||||||
|
|
||||||
### Security (`src/core/security/`)
|
### Security (Fastify Plugin)
|
||||||
- `auth.middleware.ts` - JWT authentication via Auth0
|
- `src/core/plugins/auth.plugin.ts` - Auth plugin (mock user in dev; plan for Auth0 JWT)
|
||||||
|
|
||||||
### Logging (`src/core/logging/`)
|
### Logging (`src/core/logging/`)
|
||||||
- `logger.ts` - Structured logging with Winston
|
- `logger.ts` - Structured logging with Winston
|
||||||
@@ -89,7 +89,7 @@ Run tests:
|
|||||||
npm test
|
npm test
|
||||||
|
|
||||||
# Specific feature
|
# Specific feature
|
||||||
npm run test:feature -- --feature=vehicles
|
npm test -- features/vehicles
|
||||||
|
|
||||||
# Watch mode
|
# Watch mode
|
||||||
npm run test:watch
|
npm run test:watch
|
||||||
@@ -100,5 +100,5 @@ npm run test:watch
|
|||||||
See `.env.example` for required variables. Key variables:
|
See `.env.example` for required variables. Key variables:
|
||||||
- Database connection (DB_*)
|
- Database connection (DB_*)
|
||||||
- Redis connection (REDIS_*)
|
- Redis connection (REDIS_*)
|
||||||
- Auth0 configuration (AUTH0_*)
|
- Auth0 configuration (AUTH0_*) — backend currently uses mock auth; JWT enforcement planned
|
||||||
- External API keys
|
- External API keys
|
||||||
|
|||||||
8723
backend/package-lock.json
generated
8723
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -5,8 +5,7 @@
|
|||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "nodemon --watch src --exec ts-node src/index.ts",
|
"dev": "nodemon --watch src --exec ts-node src/index.ts",
|
||||||
"build": "tsc",
|
"build": "tsc --project tsconfig.build.json",
|
||||||
"build:docker": "tsc --project tsconfig.build.json",
|
|
||||||
"start": "node dist/index.js",
|
"start": "node dist/index.js",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
@@ -39,7 +38,8 @@
|
|||||||
"@fastify/type-provider-typebox": "^4.0.0",
|
"@fastify/type-provider-typebox": "^4.0.0",
|
||||||
"@sinclair/typebox": "^0.31.28",
|
"@sinclair/typebox": "^0.31.28",
|
||||||
"fastify-plugin": "^4.5.1",
|
"fastify-plugin": "^4.5.1",
|
||||||
"@fastify/autoload": "^5.8.0"
|
"@fastify/autoload": "^5.8.0",
|
||||||
|
"get-jwks": "^9.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.10.0",
|
"@types/node": "^20.10.0",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import * as dotenv from 'dotenv';
|
|||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
const envSchema = z.object({
|
const envSchema = z.object({
|
||||||
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
|
NODE_ENV: z.string().default('development'),
|
||||||
PORT: z.string().transform(Number).default('3001'),
|
PORT: z.string().transform(Number).default('3001'),
|
||||||
|
|
||||||
// Database
|
// Database
|
||||||
@@ -22,11 +22,11 @@ const envSchema = z.object({
|
|||||||
REDIS_HOST: z.string().default('localhost'),
|
REDIS_HOST: z.string().default('localhost'),
|
||||||
REDIS_PORT: z.string().transform(Number).default('6379'),
|
REDIS_PORT: z.string().transform(Number).default('6379'),
|
||||||
|
|
||||||
// Auth0
|
// Auth0 - Required for JWT validation
|
||||||
AUTH0_DOMAIN: z.string().default('localhost'),
|
AUTH0_DOMAIN: z.string().min(1, 'AUTH0_DOMAIN is required for JWT authentication'),
|
||||||
AUTH0_CLIENT_ID: z.string().default('development'),
|
AUTH0_CLIENT_ID: z.string().min(1, 'AUTH0_CLIENT_ID is required'),
|
||||||
AUTH0_CLIENT_SECRET: z.string().default('development'),
|
AUTH0_CLIENT_SECRET: z.string().min(1, 'AUTH0_CLIENT_SECRET is required'),
|
||||||
AUTH0_AUDIENCE: z.string().default('https://api.motovaultpro.com'),
|
AUTH0_AUDIENCE: z.string().min(1, 'AUTH0_AUDIENCE is required for JWT validation'),
|
||||||
|
|
||||||
// External APIs
|
// External APIs
|
||||||
GOOGLE_MAPS_API_KEY: z.string().default('development'),
|
GOOGLE_MAPS_API_KEY: z.string().default('development'),
|
||||||
@@ -45,7 +45,4 @@ export type Environment = z.infer<typeof envSchema>;
|
|||||||
// Validate and export - now with defaults for build-time compilation
|
// Validate and export - now with defaults for build-time compilation
|
||||||
export const env = envSchema.parse(process.env);
|
export const env = envSchema.parse(process.env);
|
||||||
|
|
||||||
// Convenience exports
|
// Environment configuration validated and exported
|
||||||
export const isDevelopment = env.NODE_ENV === 'development';
|
|
||||||
export const isProduction = env.NODE_ENV === 'production';
|
|
||||||
export const isTest = env.NODE_ENV === 'test';
|
|
||||||
@@ -3,10 +3,9 @@
|
|||||||
* @ai-context All features use this for consistent logging
|
* @ai-context All features use this for consistent logging
|
||||||
*/
|
*/
|
||||||
import * as winston from 'winston';
|
import * as winston from 'winston';
|
||||||
import { env, isDevelopment } from '../config/environment';
|
|
||||||
|
|
||||||
export const logger = winston.createLogger({
|
export const logger = winston.createLogger({
|
||||||
level: isDevelopment ? 'debug' : 'info',
|
level: 'info',
|
||||||
format: winston.format.combine(
|
format: winston.format.combine(
|
||||||
winston.format.timestamp(),
|
winston.format.timestamp(),
|
||||||
winston.format.errors({ stack: true }),
|
winston.format.errors({ stack: true }),
|
||||||
@@ -14,16 +13,10 @@ export const logger = winston.createLogger({
|
|||||||
),
|
),
|
||||||
defaultMeta: {
|
defaultMeta: {
|
||||||
service: 'motovaultpro-backend',
|
service: 'motovaultpro-backend',
|
||||||
environment: env.NODE_ENV,
|
|
||||||
},
|
},
|
||||||
transports: [
|
transports: [
|
||||||
new winston.transports.Console({
|
new winston.transports.Console({
|
||||||
format: isDevelopment
|
format: winston.format.json(),
|
||||||
? winston.format.combine(
|
|
||||||
winston.format.colorize(),
|
|
||||||
winston.format.simple()
|
|
||||||
)
|
|
||||||
: winston.format.json(),
|
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* @ai-summary Fastify JWT authentication plugin using Auth0
|
* @ai-summary Fastify JWT authentication plugin using Auth0
|
||||||
* @ai-context Validates JWT tokens in production, mocks in development
|
* @ai-context Validates JWT tokens against Auth0 JWKS endpoint
|
||||||
*/
|
*/
|
||||||
import { FastifyPluginAsync, FastifyRequest, FastifyReply } from 'fastify';
|
import { FastifyPluginAsync, FastifyRequest, FastifyReply } from 'fastify';
|
||||||
import fp from 'fastify-plugin';
|
import fp from 'fastify-plugin';
|
||||||
|
import buildGetJwks from 'get-jwks';
|
||||||
import { env } from '../config/environment';
|
import { env } from '../config/environment';
|
||||||
import { logger } from '../logging/logger';
|
import { logger } from '../logging/logger';
|
||||||
|
|
||||||
@@ -11,20 +12,67 @@ declare module 'fastify' {
|
|||||||
interface FastifyInstance {
|
interface FastifyInstance {
|
||||||
authenticate: (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
authenticate: (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
interface FastifyRequest {
|
||||||
|
jwtVerify(): Promise<void>;
|
||||||
|
user?: any;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const authPlugin: FastifyPluginAsync = async (fastify) => {
|
const authPlugin: FastifyPluginAsync = async (fastify) => {
|
||||||
// For now, use mock authentication in all environments
|
// Initialize JWKS client for Auth0 public key retrieval
|
||||||
// The frontend Auth0 flow should work independently
|
const getJwks = buildGetJwks({
|
||||||
// TODO: Implement proper JWKS validation when needed for API security
|
ttl: 60 * 60 * 1000, // 1 hour cache
|
||||||
|
});
|
||||||
fastify.decorate('authenticate', async (request: FastifyRequest, _reply: FastifyReply) => {
|
|
||||||
(request as any).user = { sub: 'dev-user-123' };
|
// Register @fastify/jwt with Auth0 JWKS validation
|
||||||
|
await fastify.register(require('@fastify/jwt'), {
|
||||||
if (env.NODE_ENV === 'development') {
|
decode: { complete: true },
|
||||||
logger.debug('Using mock user for development', { userId: 'dev-user-123' });
|
secret: async (_request: FastifyRequest, token: any) => {
|
||||||
} else {
|
try {
|
||||||
logger.info('Using mock authentication - Auth0 handled by frontend', { userId: 'dev-user-123' });
|
const { header: { kid, alg }, payload: { iss } } = token;
|
||||||
|
|
||||||
|
// Validate issuer matches Auth0 domain
|
||||||
|
const expectedIssuer = `https://${env.AUTH0_DOMAIN}/`;
|
||||||
|
if (iss !== expectedIssuer) {
|
||||||
|
throw new Error(`Invalid issuer: ${iss}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get public key from Auth0 JWKS endpoint
|
||||||
|
return getJwks.getPublicKey({ kid, domain: env.AUTH0_DOMAIN, alg });
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('JWKS key retrieval failed', {
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
domain: env.AUTH0_DOMAIN
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
verify: {
|
||||||
|
allowedIss: `https://${env.AUTH0_DOMAIN}/`,
|
||||||
|
allowedAud: env.AUTH0_AUDIENCE,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Decorate with authenticate function that validates JWT
|
||||||
|
fastify.decorate('authenticate', async function(request: FastifyRequest, reply: FastifyReply) {
|
||||||
|
try {
|
||||||
|
await request.jwtVerify();
|
||||||
|
|
||||||
|
logger.info('JWT authentication successful', {
|
||||||
|
userId: request.user?.sub?.substring(0, 8) + '...',
|
||||||
|
audience: env.AUTH0_AUDIENCE
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn('JWT authentication failed', {
|
||||||
|
path: request.url,
|
||||||
|
method: request.method,
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
});
|
||||||
|
|
||||||
|
reply.code(401).send({
|
||||||
|
error: 'Unauthorized',
|
||||||
|
message: 'Invalid or missing JWT token'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ const errorPlugin: FastifyPluginAsync = async (fastify) => {
|
|||||||
|
|
||||||
reply.status(500).send({
|
reply.status(500).send({
|
||||||
error: 'Internal server error',
|
error: 'Internal server error',
|
||||||
message: process.env.NODE_ENV === 'development' ? error.message : undefined,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,13 +10,17 @@ import { cacheService } from '../../../../core/config/redis';
|
|||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
|
||||||
// Mock auth middleware to bypass JWT validation in tests
|
// Mock auth plugin to bypass JWT validation in tests
|
||||||
jest.mock('../../../../core/security/auth.middleware', () => ({
|
jest.mock('../../../../core/plugins/auth.plugin', () => {
|
||||||
authMiddleware: (req: any, _res: any, next: any) => {
|
const fastifyPlugin = require('fastify-plugin');
|
||||||
req.user = { sub: 'test-user-123' };
|
return {
|
||||||
next();
|
default: fastifyPlugin(async function(fastify) {
|
||||||
}
|
fastify.decorate('authenticate', async function(request, _reply) {
|
||||||
}));
|
request.user = { sub: 'test-user-123' };
|
||||||
|
});
|
||||||
|
}, { name: 'auth-plugin' })
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
// Mock external VIN decoder
|
// Mock external VIN decoder
|
||||||
jest.mock('../../external/vpic/vpic.client', () => ({
|
jest.mock('../../external/vpic/vpic.client', () => ({
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ services:
|
|||||||
- node:20-alpine
|
- node:20-alpine
|
||||||
container_name: mvp-backend
|
container_name: mvp-backend
|
||||||
environment:
|
environment:
|
||||||
NODE_ENV: development
|
|
||||||
PORT: 3001
|
PORT: 3001
|
||||||
DB_HOST: postgres
|
DB_HOST: postgres
|
||||||
DB_PORT: 5432
|
DB_PORT: 5432
|
||||||
@@ -104,7 +103,6 @@ services:
|
|||||||
VITE_API_BASE_URL: ${VITE_API_BASE_URL:-http://backend:3001/api}
|
VITE_API_BASE_URL: ${VITE_API_BASE_URL:-http://backend:3001/api}
|
||||||
container_name: mvp-frontend
|
container_name: mvp-frontend
|
||||||
environment:
|
environment:
|
||||||
NODE_ENV: development
|
|
||||||
VITE_API_BASE_URL: http://backend:3001/api
|
VITE_API_BASE_URL: http://backend:3001/api
|
||||||
VITE_AUTH0_DOMAIN: ${VITE_AUTH0_DOMAIN:-motovaultpro.us.auth0.com}
|
VITE_AUTH0_DOMAIN: ${VITE_AUTH0_DOMAIN:-motovaultpro.us.auth0.com}
|
||||||
VITE_AUTH0_CLIENT_ID: ${VITE_AUTH0_CLIENT_ID:-yspR8zdnSxmV8wFIghHynQ08iXAPoQJ3}
|
VITE_AUTH0_CLIENT_ID: ${VITE_AUTH0_CLIENT_ID:-yspR8zdnSxmV8wFIghHynQ08iXAPoQJ3}
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ Complete database schema for MotoVaultPro Modified Feature Capsule architecture.
|
|||||||
|
|
||||||
### Migration Tracking
|
### Migration Tracking
|
||||||
- **Table**: `_migrations`
|
- **Table**: `_migrations`
|
||||||
- **Purpose**: Tracks executed migrations per feature
|
- **Purpose**: Created by `backend/src/_system/migrations/run-all.ts` (not yet used for skipping executed files)
|
||||||
- **Location**: Created by `backend/src/_system/migrations/run-all.ts`
|
- **Note**: Some SQL files use `IF NOT EXISTS`. Re-running all migrations may fail on indexes without `IF NOT EXISTS`.
|
||||||
|
|
||||||
## Core Tables
|
## Core Tables
|
||||||
|
|
||||||
@@ -183,17 +183,7 @@ npm run migrate:all
|
|||||||
# Via Docker
|
# Via Docker
|
||||||
make migrate
|
make migrate
|
||||||
```
|
```
|
||||||
|
Single-feature migration is not implemented yet.
|
||||||
### Run Single Feature
|
|
||||||
```bash
|
|
||||||
# In container
|
|
||||||
npm run migrate:feature vehicles
|
|
||||||
|
|
||||||
# Individual features
|
|
||||||
npm run migrate:feature fuel-logs
|
|
||||||
npm run migrate:feature maintenance
|
|
||||||
npm run migrate:feature stations
|
|
||||||
```
|
|
||||||
|
|
||||||
### Migration Files
|
### Migration Files
|
||||||
- **Location**: `backend/src/features/[feature]/migrations/`
|
- **Location**: `backend/src/features/[feature]/migrations/`
|
||||||
@@ -226,4 +216,4 @@ npm run migrate:feature stations
|
|||||||
- Regular pg_dump backups
|
- Regular pg_dump backups
|
||||||
- Point-in-time recovery
|
- Point-in-time recovery
|
||||||
- Read replicas for analytics
|
- Read replicas for analytics
|
||||||
- Connection pooling (PgBouncer)
|
- Connection pooling (PgBouncer)
|
||||||
|
|||||||
@@ -2,7 +2,11 @@
|
|||||||
|
|
||||||
## Authentication & Authorization
|
## Authentication & Authorization
|
||||||
|
|
||||||
### Protected Endpoints
|
### Current State (MVP / Dev)
|
||||||
|
- Backend uses a Fastify authentication plugin that injects a mock user for development/test.
|
||||||
|
- JWT validation via Auth0 is not yet enabled on the backend; the frontend Auth0 flow works independently.
|
||||||
|
|
||||||
|
### Intended Production Behavior
|
||||||
All vehicle CRUD operations require JWT authentication via Auth0:
|
All vehicle CRUD operations require JWT authentication via Auth0:
|
||||||
- `POST /api/vehicles` - Create vehicle
|
- `POST /api/vehicles` - Create vehicle
|
||||||
- `GET /api/vehicles` - Get user vehicles
|
- `GET /api/vehicles` - Get user vehicles
|
||||||
@@ -37,7 +41,7 @@ GET /api/vehicles/dropdown/trims
|
|||||||
4. **Information Disclosure**: Exposes system capabilities to unauthenticated users
|
4. **Information Disclosure**: Exposes system capabilities to unauthenticated users
|
||||||
|
|
||||||
**Recommended Mitigations for Production:**
|
**Recommended Mitigations for Production:**
|
||||||
1. **Rate Limiting**: Implement express-rate-limit (e.g., 100 requests/hour per IP)
|
1. **Rate Limiting**: Implement request rate limiting (e.g., 100 requests/hour per IP)
|
||||||
2. **Input Validation**: Sanitize make parameter in controller
|
2. **Input Validation**: Sanitize make parameter in controller
|
||||||
3. **CORS Restrictions**: Limit to application domain
|
3. **CORS Restrictions**: Limit to application domain
|
||||||
4. **Monitoring**: Add abuse detection logging
|
4. **Monitoring**: Add abuse detection logging
|
||||||
@@ -74,4 +78,4 @@ GET /api/vehicles/dropdown/trims
|
|||||||
- Separate authenticated/unauthenticated HTTP clients
|
- Separate authenticated/unauthenticated HTTP clients
|
||||||
- Request/response interceptors for error handling
|
- Request/response interceptors for error handling
|
||||||
- Timeout configurations to prevent hanging requests
|
- Timeout configurations to prevent hanging requests
|
||||||
- Auth token handling via Auth0 wrapper
|
- Auth token handling via Auth0 wrapper
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ backend/src/features/[name]/tests/
|
|||||||
make test
|
make test
|
||||||
```
|
```
|
||||||
|
|
||||||
This executes: `docker-compose exec backend npm test`
|
This executes: `docker compose exec backend npm test`
|
||||||
|
|
||||||
### Feature-Specific Testing
|
### Feature-Specific Testing
|
||||||
```bash
|
```bash
|
||||||
@@ -45,9 +45,9 @@ npm test -- features/vehicles --coverage
|
|||||||
|
|
||||||
### Test Environment Setup
|
### Test Environment Setup
|
||||||
1. **Container-Based**: All tests run inside Docker containers
|
1. **Container-Based**: All tests run inside Docker containers
|
||||||
2. **Test Database**: Isolated test database per feature
|
2. **Database**: Uses the development database in the stack (`motovaultpro`)
|
||||||
3. **Mock External APIs**: No real API calls during testing
|
3. **Mock External APIs**: No real API calls during testing (where implemented)
|
||||||
4. **Cleanup**: Automatic test data cleanup after each test
|
4. **Cleanup**: Prefer transactions/cleanup per test; see feature tests for patterns
|
||||||
|
|
||||||
## Test Types
|
## Test Types
|
||||||
|
|
||||||
@@ -149,10 +149,14 @@ make clean && make dev
|
|||||||
**Coverage**: Exclude node_modules, include src only
|
**Coverage**: Exclude node_modules, include src only
|
||||||
|
|
||||||
### Database Testing
|
### Database Testing
|
||||||
- **Test DB**: Same as development (motovaultpro)
|
- **DB**: Same as development (`motovaultpro`) within Docker
|
||||||
- **Transactions**: Each test runs in transaction, rolled back after
|
- **Transactions**: Recommended pattern is one transaction per test
|
||||||
- **Isolation**: Tests cannot interfere with each other
|
- **Isolation**: Keep tests independent; avoid shared state
|
||||||
- **Seeding**: Minimal seed data, test-specific fixtures
|
- **Seeding**: Use feature-level fixtures when needed
|
||||||
|
|
||||||
|
### Coverage and Availability
|
||||||
|
- Full test suite exists for `vehicles`.
|
||||||
|
- Other features (e.g., `fuel-logs`, `stations`, `maintenance`) have placeholders and are being built out.
|
||||||
|
|
||||||
### Mock Strategy
|
### Mock Strategy
|
||||||
- **External APIs**: Completely mocked (vPIC, Google Maps)
|
- **External APIs**: Completely mocked (vPIC, Google Maps)
|
||||||
@@ -214,7 +218,7 @@ make rebuild
|
|||||||
#### Database Connection Issues
|
#### Database Connection Issues
|
||||||
```bash
|
```bash
|
||||||
# Check postgres container
|
# Check postgres container
|
||||||
docker-compose logs postgres
|
docker compose logs postgres
|
||||||
|
|
||||||
# Reset database
|
# Reset database
|
||||||
make clean && make dev
|
make clean && make dev
|
||||||
@@ -288,4 +292,4 @@ describe('Error Handling', () => {
|
|||||||
- Mock API failures to test error handling
|
- Mock API failures to test error handling
|
||||||
- Test timeout scenarios
|
- Test timeout scenarios
|
||||||
- Test network connectivity issues
|
- Test network connectivity issues
|
||||||
- Verify graceful degradation paths
|
- Verify graceful degradation paths
|
||||||
|
|||||||
@@ -4,8 +4,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc --project tsconfig.build.json && vite build",
|
||||||
"build:docker": "tsc --project tsconfig.build.json && vite build",
|
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"test:ui": "vitest --ui",
|
"test:ui": "vitest --ui",
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ cat > "$BACKEND_DIR/README.md" << EOF
|
|||||||
# Run feature tests
|
# Run feature tests
|
||||||
npm test -- features/$FEATURE_NAME
|
npm test -- features/$FEATURE_NAME
|
||||||
|
|
||||||
# Run feature migrations
|
# Run migrations (all features)
|
||||||
npm run migrate:feature $FEATURE_NAME
|
npm run migrate:all
|
||||||
\`\`\`
|
\`\`\`
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
@@ -92,4 +92,4 @@ echo "1. Implement business logic in domain/${FEATURE_CAMEL}.service.ts"
|
|||||||
echo "2. Add database columns to migrations/"
|
echo "2. Add database columns to migrations/"
|
||||||
echo "3. Implement API validation"
|
echo "3. Implement API validation"
|
||||||
echo "4. Add tests"
|
echo "4. Add tests"
|
||||||
echo "5. Register routes in backend/src/app.ts"
|
echo "5. Register routes in backend/src/app.ts"
|
||||||
|
|||||||
Reference in New Issue
Block a user