@ordojs/security
Version:
Security package for OrdoJS with XSS, CSRF, and injection protection
313 lines (254 loc) • 10.1 kB
text/typescript
/**
* 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('<script>');
});
});
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('<script>');
});
});
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);
});
});
});