UNPKG

@ordojs/security

Version:

Security package for OrdoJS with XSS, CSRF, and injection protection

313 lines (254 loc) 10.1 kB
/** * Input Validator Tests * Comprehensive tests for input validation and sanitization */ import { beforeEach, describe, expect, it } from 'vitest'; import { InputValidator } from './input-validator'; import { ValidationSchema } from './types'; describe('InputValidator', () => { let validator: InputValidator; beforeEach(() => { validator = new InputValidator(); }); describe('Basic Validation Rules', () => { it('should validate required fields', () => { const schema: ValidationSchema = { name: [validator.getRule('required')!] }; const validData = { name: 'John Doe' }; const invalidData = { name: '' }; const validResult = validator.validate(validData, schema); const invalidResult = validator.validate(invalidData, schema); expect(validResult.isValid).toBe(true); expect(invalidResult.isValid).toBe(false); expect(invalidResult.errors[0].rule).toBe('required'); }); it('should validate email addresses', () => { const schema: ValidationSchema = { email: [validator.getRule('email')!] }; const validData = { email: 'test@example.com' }; const invalidData = { email: 'invalid-email' }; const validResult = validator.validate(validData, schema); const invalidResult = validator.validate(invalidData, schema); expect(validResult.isValid).toBe(true); expect(invalidResult.isValid).toBe(false); expect(invalidResult.errors[0].rule).toBe('email'); }); it('should validate URLs', () => { const schema: ValidationSchema = { website: [validator.getRule('url')!] }; const validData = { website: 'https://example.com' }; const invalidData = { website: 'not-a-url' }; const validResult = validator.validate(validData, schema); const invalidResult = validator.validate(invalidData, schema); expect(validResult.isValid).toBe(true); expect(invalidResult.isValid).toBe(false); }); it('should validate numeric values', () => { const schema: ValidationSchema = { age: [validator.getRule('numeric')!] }; const validData = { age: '25' }; const invalidData = { age: 'not-a-number' }; const validResult = validator.validate(validData, schema); const invalidResult = validator.validate(invalidData, schema); expect(validResult.isValid).toBe(true); expect(validResult.sanitizedData.age).toBe(25); expect(invalidResult.isValid).toBe(false); }); }); describe('SQL Injection Prevention', () => { it('should detect SQL injection attempts', () => { const maliciousInputs = [ "'; DROP TABLE users; --", "1' OR '1'='1", "UNION SELECT * FROM passwords", "'; EXEC xp_cmdshell('dir'); --" ]; for (const input of maliciousInputs) { const result = validator.validateSqlInput(input); expect(result.isValid).toBe(false); expect(result.threats.length).toBeGreaterThan(0); } }); it('should allow safe SQL inputs', () => { const safeInputs = [ 'John Doe', 'user@example.com', '12345', 'A normal string with spaces' ]; for (const input of safeInputs) { const result = validator.validateSqlInput(input); expect(result.isValid).toBe(true); expect(result.threats.length).toBe(0); } }); it('should sanitize SQL injection attempts', () => { const input = "'; DROP TABLE users; --"; const result = validator.validateSqlInput(input); expect(typeof result.sanitized).toBe('string'); expect(result.sanitized).not.toContain('DROP TABLE'); expect(result.sanitized).not.toContain('--'); }); }); describe('Path Traversal Prevention', () => { it('should detect path traversal attempts', () => { const maliciousPaths = [ '../../../etc/passwd', '..\\..\\windows\\system32', '/etc/shadow', 'C:\\Windows\\System32\\config\\SAM' ]; for (const path of maliciousPaths) { const result = validator.validatePath(path); expect(result.isValid).toBe(false); expect(result.errors.length).toBeGreaterThan(0); } }); it('should allow safe paths', () => { const safePaths = [ 'documents/file.txt', 'images/photo.jpg', 'data/export.csv' ]; for (const path of safePaths) { const result = validator.validatePath(path); expect(result.isValid).toBe(true); expect(result.errors.length).toBe(0); } }); it('should sanitize dangerous paths', () => { const dangerousPath = '../../../etc/passwd'; const result = validator.validatePath(dangerousPath); // The path should be invalid, so sanitizedPath might be undefined if (result.sanitizedPath) { expect(result.sanitizedPath).not.toContain('..'); } else { expect(result.isValid).toBe(false); } }); }); describe('XSS Prevention', () => { it('should detect XSS attempts', () => { const xssRule = validator.getRule('noXss'); expect(xssRule).toBeDefined(); // Ensure the rule exists const schema: ValidationSchema = { content: [xssRule!] }; const xssInputs = [ '<script>alert("xss")</script>', '<img src="x" onerror="alert(1)">', 'javascript:alert("xss")', '<iframe src="javascript:alert(1)"></iframe>' ]; for (const input of xssInputs) { const result = validator.validate({ content: input }, schema); expect(result.isValid).toBe(false); } }); it('should sanitize XSS attempts', () => { const xssInput = '<script>alert("xss")</script>'; const sanitized = validator.sanitize(xssInput, { escapeHtml: true, stripHtml: false }); expect(sanitized).not.toContain('<script>'); expect(sanitized).toContain('&lt;script&gt;'); }); }); describe('Custom Rules', () => { it('should allow creating custom validation rules', () => { const customRule = validator.createRule( 'strongPassword', (value: string) => { if (!value) return false; return value.length >= 8 && /[A-Z]/.test(value) && /[a-z]/.test(value) && /[0-9]/.test(value); }, 'Password must be at least 8 characters with uppercase, lowercase, and numbers' ); const schema: ValidationSchema = { password: [customRule] }; const weakPassword = { password: 'weak' }; const strongPassword = { password: 'StrongPass123' }; const weakResult = validator.validate(weakPassword, schema); const strongResult = validator.validate(strongPassword, schema); expect(weakResult.isValid).toBe(false); expect(strongResult.isValid).toBe(true); }); }); describe('Sanitization', () => { it('should trim whitespace', () => { const input = ' hello world '; const sanitized = validator.sanitize(input, { trimWhitespace: true }); expect(sanitized).toBe('hello world'); }); it('should enforce max length', () => { const input = 'this is a very long string that should be truncated'; const sanitized = validator.sanitize(input, { maxLength: 10 }); expect(sanitized.length).toBe(10); }); it('should strip HTML tags', () => { const input = '<p>Hello <strong>world</strong></p>'; const sanitized = validator.sanitize(input, { stripHtml: true }); expect(sanitized).toBe('Hello world'); }); it('should escape HTML characters', () => { const input = '<script>alert("test")</script>'; const sanitized = validator.sanitize(input, { escapeHtml: true, stripHtml: false }); expect(sanitized).toContain('&lt;script&gt;'); }); }); describe('Complex Validation Scenarios', () => { it('should handle multiple validation rules', () => { const schema: ValidationSchema = { email: [ validator.getRule('required')!, validator.getRule('email')!, validator.getRule('noXss')! ] }; const validData = { email: 'test@example.com' }; const invalidData = { email: '<script>alert("xss")</script>' }; const validResult = validator.validate(validData, schema); const invalidResult = validator.validate(invalidData, schema); expect(validResult.isValid).toBe(true); expect(invalidResult.isValid).toBe(false); expect(invalidResult.errors.length).toBeGreaterThan(0); }); it('should validate complex nested data', () => { const maliciousQuery = "'; DROP TABLE users; --"; const sqlResult = validator.validateSqlInput(maliciousQuery); expect(sqlResult.isValid).toBe(false); // Should detect SQL injection const safeQuery = "user profile information"; const safeResult = validator.validateSqlInput(safeQuery); expect(safeResult.isValid).toBe(true); // Should allow safe content }); }); describe('Configuration Options', () => { it('should respect disabled security features', () => { const permissiveValidator = new InputValidator({ enableSqlInjectionPrevention: false, enableXssProtection: false, enablePathTraversalPrevention: false }); const maliciousInput = "'; DROP TABLE users; --"; const result = permissiveValidator.validateSqlInput(maliciousInput); expect(result.isValid).toBe(true); // Should pass when SQL injection prevention is disabled }); it('should use custom sanitization defaults', () => { const customValidator = new InputValidator({ sanitizationDefaults: { maxLength: 5, trimWhitespace: true } }); const longInput = ' this is a very long string '; const sanitized = customValidator.sanitize(longInput); expect(sanitized.length).toBeLessThanOrEqual(5); }); }); });