Sistema de Validación de Datos
De Wikiprompt, la enciclopedia libre de prompts
Sistema de Validación de Datos Un prompt de sistema integral para un experto en validación de datos, que cubre validación multicapa, seguridad, saneamiento y manejo de errores con salidas basadas en tareas.
Contenido del PromptGuardar
🌐
# Data Validator - Implementation Plan
## Context
- **Tech Stack**: Node.js 20+, TypeScript 5.x, Express 4.x, PostgreSQL 15, Zod 3.x, DOMPurify 3.x
- **Framework Versions**: Express 4.18, Zod 3.22, pg 8.11
- **Data Entry Points**:
- REST API endpoints (`/api/v1/*`)
- HTML forms (login, registration, contact)
- File uploads (avatar images, document attachments)
- Webhook endpoints (payment callbacks, third-party integrations)
- **Security Requirements**: OWASP Top 10 compliance, GDPR data protection, PCI-DSS for payment data
## Validation Plan
### Layer 1: Client-Side Validation
- [ ] **VAL-PLAN-1.1 [Client-Side Form Validation]**:
- **Layer**: Client-side (browser)
- **Entry Points**: All HTML forms (login, registration, contact, profile edit)
- **Rules**: Required fields, email format, password strength (min 8 chars, 1 uppercase, 1 number), phone format, date ranges
- **Libraries**: HTML5 validation attributes, Zod for shared schemas, DOMPurify for input sanitization
- [ ] **VAL-PLAN-1.2 [Client-Side API Request Validation]**:
- **Layer**: Client-side (browser)
- **Entry Points**: All fetch/axios calls to REST API
- **Rules**: Request payload structure matches API schemas, prevent sending malformed data
- **Libraries**: Zod schemas shared with server, TypeScript type checking
### Layer 2: Server-Side Validation
- [ ] **VAL-PLAN-2.1 [Express Middleware Validation]**:
- **Layer**: Server-side (application layer)
- **Entry Points**: All `/api/v1/*` endpoints
- **Rules**: Request body, query params, URL params validated against Zod schemas
- **Libraries**: Zod 3.x, express-validator middleware, custom validation middleware
- [ ] **VAL-PLAN-2.2 [File Upload Validation]**:
- **Layer**: Server-side (application layer)
- **Entry Points**: `/api/v1/upload/avatar`, `/api/v1/upload/document`
- **Rules**: File type (magic bytes), size limits (avatar: 2MB, documents: 10MB), content safety scan
- **Libraries**: multer, file-type, virus scanning via ClamAV
- [ ] **VAL-PLAN-2.3 [Webhook Payload Validation]**:
- **Layer**: Server-side (application layer)
- **Entry Points**: `/api/v1/webhooks/payment`, `/api/v1/webhooks/crm`
- **Rules**: Signature verification, payload schema validation, replay attack prevention
- **Libraries**: Zod, crypto for HMAC verification
### Layer 3: Database-Level Validation
- [ ] **VAL-PLAN-3.1 [PostgreSQL Constraints]**:
- **Layer**: Database-level
- **Entry Points**: All database write operations
- **Rules**: NOT NULL, UNIQUE, CHECK constraints, foreign key integrity, data type enforcement
- **Libraries**: PostgreSQL native constraints, migration scripts
## Validation Items
### User Input Fields
- [ ] **VAL-ITEM-1.1 [Email Address]**:
- **Type**: String, valid email format (RFC 5322), max length 254 chars
- **Sanitization**: Trim whitespace, lowercase domain part, escape HTML entities
- **Security**: Reject known disposable email domains, prevent header injection (no CR/LF)
- **Error Message**: "Please enter a valid email address (e.g., name@example.com)"
- [ ] **VAL-ITEM-1.2 [Password]**:
- **Type**: String, 8-128 chars, must contain uppercase, lowercase, number
- **Sanitization**: No sanitization (passwords are hashed, never stored in plaintext)
- **Security**: Check against breached password list, enforce complexity rules
- **Error Message**: "Password must be 8-128 characters with at least one uppercase letter, one lowercase letter, and one number"
- [ ] **VAL-ITEM-1.3 [Username]**:
- **Type**: String, 3-30 chars, alphanumeric + underscore only
- **Sanitization**: Trim whitespace, strip HTML tags, normalize Unicode to NFC
- **Security**: Blocklist for reserved words (admin, root, system), prevent homoglyph attacks
- **Error Message**: "Username must be 3-30 characters using letters, numbers, and underscores only"
- [ ] **VAL-ITEM-1.4 [Phone Number]**:
- **Type**: String, E.164 format, max 15 digits
- **Sanitization**: Strip spaces, dashes, parentheses; keep only + and digits
- **Security**: Validate against known country codes, prevent SMS bombing via rate limiting
- **Error Message**: "Please enter a valid phone number with country code (e.g., +1234567890)"
- [ ] **VAL-ITEM-1.5 [Date of Birth]**:
- **Type**: Date, ISO 8601 format, must be in the past, age 18+ for accounts
- **Sanitization**: Normalize to UTC, reject invalid calendar dates (Feb 30)
- **Security**: Prevent future dates, enforce minimum age for compliance
- **Error Message**: "Date of birth must be a valid past date and you must be at least 18 years old"
### API Request Fields
- [ ] **VAL-ITEM-2.1 [User ID]**:
- **Type**: UUID v4 format, required
- **Sanitization**: Validate UUID structure, no transformation
- **Security**: Prevent IDOR by checking authorization, not just format
- **Error Message**: "Invalid user identifier format"
- [ ] **VAL-ITEM-2.2 [Pagination Parameters]**:
- **Type**: Page (integer, min 1, max 10000), Limit (integer, min 1, max 100)
- **Sanitization**: Parse to integer, reject decimals and negative values
- **Security**: Prevent resource exhaustion via excessive limit values
- **Error Message**: "Page must be a positive integer. Limit must be between 1 and 100."
- [ ] **VAL-ITEM-2.3 [Search Query]**:
- **Type**: String, max 200 chars, UTF-8 encoded
- **Sanitization**: Strip HTML tags, escape special regex characters, trim whitespace
- **Security**: Prevent regex DoS (ReDoS), block SQL wildcard abuse
- **Error Message**: "Search query must be 200 characters or less"
- [ ] **VAL-ITEM-2.4 [Sort Field]**:
- **Type**: Enum allowlist: ['created_at', 'updated_at', 'name', 'email', 'status']
- **Sanitization**: Validate against allowlist, reject unknown values
- **Security**: Prevent SQL injection via sort field (no direct interpolation)
- **Error Message**: "Invalid sort field. Allowed values: created_at, updated_at, name, email, status"
### File Upload Fields
- [ ] **VAL-ITEM-3.1 [Avatar Image]**:
- **Type**: JPEG/PNG/WebP, max 2MB, dimensions 100x100 to 1000x1000
- **Sanitization**: Verify magic bytes, re-encode image to strip metadata, generate thumbnail
- **Security**: Prevent SVG upload (XSS vector), scan for embedded malware, block polyglot files
- **Error Message**: "Avatar must be a JPEG, PNG, or WebP image between 100x100 and 1000x1000 pixels, max 2MB"
- [ ] **VAL-ITEM-3.2 [Document Attachment]**:
- **Type**: PDF/DOCX/XLSX, max 10MB, valid file signature
- **Sanitization**: Verify magic bytes, strip macros from Office files, check PDF for JavaScript
- **Security**: Prevent zip bombs, scan for malware, block executable content
- **Error Message**: "Document must be a PDF, DOCX, or XLSX file, max 10MB"
### Webhook Payload Fields
- [ ] **VAL-ITEM-4.1 [Payment Webhook Signature]**:
- **Type**: String, HMAC-SHA256 signature, required
- **Sanitization**: Constant-time comparison, no transformation
- **Security**: Verify signature before processing, prevent replay with timestamp check
- **Error Message**: "Invalid webhook signature" (logged, not returned to caller)
- [ ] **VAL-ITEM-4.2 [Payment Amount]**:
- **Type**: Decimal, min 0.01, max 999999.99, exactly 2 decimal places
- **Sanitization**: Parse as decimal, reject scientific notation
- **Security**: Prevent integer overflow, check against order total
- **Error Message**: "Invalid payment amount" (logged, not returned to caller)
## Proposed Code Changes
### File: `src/middleware/validation.ts` (New)
```typescript
import { Request, Response, NextFunction } from 'express';
import { ZodSchema, ZodError } from 'zod';
export const validate = (schema: ZodSchema, source: 'body' | 'query' | 'params' = 'body') => {
return (req: Request, res: Response, next: NextFunction) => {
try {
const data = source === 'body' ? req.body : source === 'query' ? req.query : req.params;
const validated = schema.parse(data);
if (source === 'body') req.body = validated;
if (source === 'query') req.query = validated;
if (source === 'params') req.params = validated;
next();
} catch (error) {
if (error instanceof ZodError) {
const details = error.errors.map(e => ({
field: e.path.join('.'),
message: e.message,
}));
res.status(400).json({
error: 'VALIDATION_ERROR',
message: 'Request validation failed',
details,
});
} else {
next(error);
}
}
};
};
```
### File: `src/schemas/user.schema.ts` (New)
```typescript
import { z } from 'zod';
export const emailSchema = z
.string()
.trim()
.toLowerCase()
.email('Please enter a valid email address (e.g., name@example.com)')
.max(254, 'Email must be 254 characters or less')
.refine((email) => !email.includes('\n') && !email.includes('\r'), {
message: 'Invalid email format',
});
export const passwordSchema = z
.string()
.min(8, 'Password must be at least 8 characters')
.max(128, 'Password must be 128 characters or less')
.regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
.regex(/[a-z]/, 'Password must contain at least one lowercase letter')
.regex(/[0-9]/, 'Password must contain at least one number');
export const usernameSchema = z
.string()
.trim()
.min(3, 'Username must be at least 3 characters')
.max(30, 'Username must be 30 characters or less')
.regex(/^[a-zA-Z0-9_]+$/, 'Username must use letters, numbers, and underscores only')
.refine((username) => !['admin', 'root', 'system'].includes(username.toLowerCase()), {
message: 'This username is reserved',
});
export const phoneSchema = z
.string()
.trim()
.regex(/^\+[1-9]\d{1,14}$/, 'Please enter a valid phone number with country code (e.g., +1234567890)');
export const dateOfBirthSchema = z
.string()
.regex(/^\d{4}-\d{2}-\d{2}$/, 'Date of birth must be in YYYY-MM-DD format')
.refine((date) => {
const d = new Date(date);
return !isNaN(d.getTime()) && d < new Date();
}, 'Date of birth must be a valid past date')
.refine((date) => {
const d = new Date(date);
const eighteenYearsAgo = new Date();
eighteenYearsAgo.setFullYear(eighteenYearsAgo.getFullYear() - 18);
return d <= eighteenYearsAgo;
}, 'You must be at least 18 years old');
```
### File: `src/schemas/api.schema.ts` (New)
```typescript
import { z } from 'zod';
export const userIdSchema = z.string().uuid('Invalid user identifier format');
export const paginationSchema = z.object({
page: z.coerce.number().int().min(1).max(10000).default(1),
limit: z.coerce.number().int().min(1).max(100).default(20),
});
export const searchQuerySchema = z
.string()
.trim()
.max(200, 'Search query must be 200 characters or less')
.transform((query) => query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
export const sortFieldSchema = z.enum(['created_at', 'updated_at', 'name', 'email', 'status'], {
errorMap: () => ({ message: 'Invalid sort field. Allowed values: created_at, updated_at, name, email, status' }),
});
```
### File: `src/middleware/fileUpload.ts` (New)
```typescript
import multer from 'multer';
import { fileTypeFromBuffer } from 'file-type';
import { Request, Response, NextFunction } from 'express';
const upload = multer({
storage: multer.memoryStorage(),
limits: {
fileSize: 10 * 1024 * 1024, // 10MB
},
});
export const validateFileUpload = (allowedTypes: string[], maxSize: number) => {
return async (req: Request, res: Response, next: NextFunction) => {
if (!req.file) {
return res.status(400).json({ error: 'VALIDATION_ERROR', message: 'No file uploaded' });
}
if (req.file.size > maxSize) {
return res.status(400).json({ error: 'VALIDATION_ERROR', message: `File must be ${maxSize / 1024 / 1024}MB or less` });
}
try {
const detectedType = await fileTypeFromBuffer(req.file.buffer);
if (!detectedType || !allowedTypes.includes(detectedType.mime)) {
return res.status(400).json({ error: 'VALIDATION_ERROR', message: 'Invalid file type' });
}
next();
} catch (error) {
next(error);
}
};
};
```
### File: `src/middleware/webhookAuth.ts` (New)
```typescript
import crypto from 'crypto';
import { Request, Response, NextFunction } from 'express';
export const verifyWebhookSignature = (secret: string) => {
return (req: Request, res: Response, next: NextFunction) => {
const signature = req.headers['x-webhook-signature'] as string;
const timestamp = req.headers['x-webhook-timestamp'] as string;
if (!signature || !timestamp) {
return res.status(401).json({ error: 'UNAUTHORIZED', message: 'Missing webhook signature' });
}
// Replay protection: reject if timestamp is older than 5 minutes
const now = Date.now();
const webhookTime = parseInt(timestamp, 10);
if (isNaN(webhookTime) || now - webhookTime > 5 * 60 * 1000) {
return res.status(401).json({ error: 'UNAUTHORIZED', message: 'Webhook signature expired' });
}
const payload = `${timestamp}.${JSON.stringify(req.body)}`;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
const isValid = crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
if (!isValid) {
return res.status(401).json({ error: 'UNAUTHORIZED', message: 'Invalid webhook signature' });
}
next();
};
};
```
### File: `src/db/migrations/001_add_constraints.sql` (New)
```sql
-- Add CHECK constraints for data integrity
ALTER TABLE users
ADD CONSTRAINT chk_users_email_length CHECK (char_length(email) <= 254),
ADD CONSTRAINT chk_users_username_format CHECK (username ~ '^[a-zA-Z0-9_]{3,30}$'),
ADD CONSTRAINT chk_users_phone_format CHECK (phone ~ '^\+[1-9]\d{1,14}$');
ALTER TABLE payments
ADD CONSTRAINT chk_payments_amount CHECK (amount >= 0.01 AND amount <= 999999.99),
ADD CONSTRAINT chk_payments_currency CHECK (currency IN ('USD', 'EUR', 'GBP'));
-- Add indexes for validation-heavy queries
CREATE INDEX idx_users_email_lower ON users (LOWER(email));
CREATE INDEX idx_users_username_lower ON users (LOWER(username));
```
## Commands
### Local Development
```bash
# Install dependencies
npm install
# Run database migrations
npm run migrate:up
# Run validation tests
npm run test:validation
# Run security tests
npm run test:security
# Start development server
npm run dev
```
### CI Pipeline
```bash
# In CI environment
npm ci
npm run lint
npm run typecheck
npm run test:unit
npm run test:integration
npm run test:security
npm run build
```
## Quality Assurance Checklist
- [ ] **VAL-QA-1**: Validation rules cover all data entry points (forms, API, file uploads, webhooks)
- [ ] **VAL-QA-2**: Server-side validation cannot be bypassed regardless of client behavior
- [ ] **VAL-QA-3**: SQL injection prevented via parameterized queries (pg library parameterization)
- [ ] **VAL-QA-4**: XSS prevented via DOMPurify sanitization and context-aware output encoding
- [ ] **VAL-QA-5**: Command injection prevented by validating all shell arguments against allowlists
- [ ] **VAL-QA-6**: Error responses are helpful to users and do not expose stack traces or system internals
- [ ] **VAL-QA-7**: Validation tests cover valid inputs, invalid inputs, edge cases, and OWASP attack payloads
- [ ] **VAL-QA-8**: Performance impact measured - validation middleware adds < 5ms average latency
- [ ] **VAL-QA-9**: Validation failures logged with correlation IDs for security monitoring
- [ ] **VAL-QA-10**: File uploads validated for type (magic bytes), size limits, and content safety
- [ ] **VAL-QA-11**: Business rules validated semantically (age 18+, payment amount matches order)
- [ ] **VAL-QA-12**: Rate limiting implemented on validation-heavy endpoints (auth, search, upload)
Iniciá sesión para ver el prompt completo
Continuar con:
Al iniciar sesión, aceptás nuestros Términos de uso y Política de privacidad
Uso
Este prompt está diseñado para usarse con coding. Copiá el contenido de arriba y pegalo en tu herramienta de IA preferida.
Para mejores resultados, personalizá los marcadores (indicados con corchetes o mayúsculas) con tus requisitos específicos.
Discusión
0 comentarios