guardz-event
Version:
Type-safe event handling with runtime validation using guardz for unsafe data from 3rd parties
476 lines • 22 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/// <reference types="jest" />
const safe_event_never_throws_1 = require("../utils/safe-event-never-throws");
// Mock type guards for testing
const isUserMessage = (data) => {
return (typeof data === 'object' &&
data !== null &&
typeof data.id === 'number' &&
typeof data.name === 'string' &&
typeof data.email === 'string' &&
typeof data.isActive === 'boolean');
};
describe('Security Validation', () => {
describe('Origin Validation', () => {
describe('Legacy API - Origin Validation', () => {
it('should allow messages from trusted origins', () => {
const handler = (0, safe_event_never_throws_1.safePostMessage)({
guard: isUserMessage,
allowedOrigins: [
'https://trusted-domain.com',
'https://another-trusted.com',
],
});
const validEvent = new MessageEvent('message', {
origin: 'https://trusted-domain.com',
data: {
id: 1,
name: 'John',
email: 'john@example.com',
isActive: true,
},
});
const result = handler(validEvent);
expect(result.status).toBe(safe_event_never_throws_1.Status.SUCCESS);
if (result.status === safe_event_never_throws_1.Status.SUCCESS) {
expect(result.data).toEqual({
id: 1,
name: 'John',
email: 'john@example.com',
isActive: true,
});
}
});
it('should reject messages from untrusted origins', () => {
const handler = (0, safe_event_never_throws_1.safePostMessage)({
guard: isUserMessage,
allowedOrigins: ['https://trusted-domain.com'],
});
const maliciousEvent = new MessageEvent('message', {
origin: 'https://malicious-site.com',
data: {
id: 1,
name: 'Hacker',
email: 'hack@evil.com',
isActive: true,
},
});
const result = handler(maliciousEvent);
expect(result.status).toBe(safe_event_never_throws_1.Status.ERROR);
if (result.status === safe_event_never_throws_1.Status.ERROR) {
expect(result.code).toBe(403);
expect(result.message).toContain('not allowed');
expect(result.message).toContain('malicious-site.com');
}
});
it('should reject messages when no origins are allowed', () => {
const handler = (0, safe_event_never_throws_1.safePostMessage)({
guard: isUserMessage,
allowedOrigins: [],
});
const event = new MessageEvent('message', {
origin: 'https://any-site.com',
data: {
id: 1,
name: 'John',
email: 'john@example.com',
isActive: true,
},
});
const result = handler(event);
expect(result.status).toBe(safe_event_never_throws_1.Status.ERROR);
if (result.status === safe_event_never_throws_1.Status.ERROR) {
expect(result.code).toBe(403);
expect(result.message).toContain('not allowed');
}
});
it('should allow messages when no origin restrictions are set', () => {
const handler = (0, safe_event_never_throws_1.safePostMessage)({
guard: isUserMessage,
// No allowedOrigins specified
});
const event = new MessageEvent('message', {
origin: 'https://any-site.com',
data: {
id: 1,
name: 'John',
email: 'john@example.com',
isActive: true,
},
});
const result = handler(event);
expect(result.status).toBe(safe_event_never_throws_1.Status.SUCCESS);
if (result.status === safe_event_never_throws_1.Status.SUCCESS) {
expect(result.data).toEqual({
id: 1,
name: 'John',
email: 'john@example.com',
isActive: true,
});
}
});
it('should handle null origin gracefully', () => {
const handler = (0, safe_event_never_throws_1.safePostMessage)({
guard: isUserMessage,
allowedOrigins: ['https://trusted-domain.com'],
});
const nullOriginEvent = new MessageEvent('message', {
origin: null,
data: {
id: 1,
name: 'John',
email: 'john@example.com',
isActive: true,
},
});
const result = handler(nullOriginEvent);
expect(result.status).toBe(safe_event_never_throws_1.Status.ERROR);
if (result.status === safe_event_never_throws_1.Status.ERROR) {
expect(result.code).toBe(403);
expect(result.message).toContain('not allowed');
}
});
it('should handle undefined origin gracefully', () => {
const handler = (0, safe_event_never_throws_1.safePostMessage)({
guard: isUserMessage,
allowedOrigins: ['https://trusted-domain.com'],
});
const undefinedOriginEvent = new MessageEvent('message', {
origin: undefined,
data: {
id: 1,
name: 'John',
email: 'john@example.com',
isActive: true,
},
});
const result = handler(undefinedOriginEvent);
expect(result.status).toBe(safe_event_never_throws_1.Status.ERROR);
if (result.status === safe_event_never_throws_1.Status.ERROR) {
expect(result.code).toBe(403);
expect(result.message).toContain('not allowed');
}
});
});
describe('Ergonomic API - Origin Validation', () => {
it('should call onSuccess for trusted origins', () => {
const onSuccess = jest.fn();
const onSecurityViolation = jest.fn();
const onError = jest.fn();
const handler = (0, safe_event_never_throws_1.safePostMessageListener)(isUserMessage, {
allowedOrigins: ['https://trusted-domain.com'],
onSuccess,
onSecurityViolation,
onError,
});
const validEvent = new MessageEvent('message', {
origin: 'https://trusted-domain.com',
data: {
id: 1,
name: 'John',
email: 'john@example.com',
isActive: true,
},
});
handler(validEvent);
expect(onSuccess).toHaveBeenCalledWith({
id: 1,
name: 'John',
email: 'john@example.com',
isActive: true,
});
expect(onSecurityViolation).not.toHaveBeenCalled();
expect(onError).not.toHaveBeenCalled();
});
it('should call onSecurityViolation for untrusted origins', () => {
const onSuccess = jest.fn();
const onSecurityViolation = jest.fn();
const onError = jest.fn();
const handler = (0, safe_event_never_throws_1.safePostMessageListener)(isUserMessage, {
allowedOrigins: ['https://trusted-domain.com'],
onSuccess,
onSecurityViolation,
onError,
});
const maliciousEvent = new MessageEvent('message', {
origin: 'https://malicious-site.com',
data: {
id: 1,
name: 'Hacker',
email: 'hack@evil.com',
isActive: true,
},
});
handler(maliciousEvent);
expect(onSuccess).not.toHaveBeenCalled();
expect(onSecurityViolation).toHaveBeenCalledWith('https://malicious-site.com', expect.stringContaining('not allowed'));
expect(onError).not.toHaveBeenCalled();
});
it('should call onSecurityViolation for null origin', () => {
const onSuccess = jest.fn();
const onSecurityViolation = jest.fn();
const onError = jest.fn();
const handler = (0, safe_event_never_throws_1.safePostMessageListener)(isUserMessage, {
allowedOrigins: ['https://trusted-domain.com'],
onSuccess,
onSecurityViolation,
onError,
});
const nullOriginEvent = new MessageEvent('message', {
origin: null,
data: {
id: 1,
name: 'John',
email: 'john@example.com',
isActive: true,
},
});
handler(nullOriginEvent);
expect(onSuccess).not.toHaveBeenCalled();
expect(onSecurityViolation).toHaveBeenCalledWith('null', expect.stringContaining('not allowed'));
expect(onError).not.toHaveBeenCalled();
});
});
});
describe('Source Validation', () => {
describe('Legacy API - Source Validation', () => {
it('should allow messages from trusted sources', () => {
const mockSource = { close: jest.fn(), 'trusted-source': true };
const handler = (0, safe_event_never_throws_1.safePostMessage)({
guard: isUserMessage,
allowedSources: ['trusted-source'],
});
const validEvent = new MessageEvent('message', {
origin: 'https://trusted-domain.com',
data: {
id: 1,
name: 'John',
email: 'john@example.com',
isActive: true,
},
});
// Manually set the source property since MessageEvent constructor doesn't support it
Object.defineProperty(validEvent, 'source', {
value: mockSource,
writable: false,
configurable: false,
});
const result = handler(validEvent);
expect(result.status).toBe(safe_event_never_throws_1.Status.SUCCESS);
if (result.status === safe_event_never_throws_1.Status.SUCCESS) {
expect(result.data).toEqual({
id: 1,
name: 'John',
email: 'john@example.com',
isActive: true,
});
}
});
it('should reject messages from untrusted sources', () => {
const mockSource = { close: jest.fn() };
const handler = (0, safe_event_never_throws_1.safePostMessage)({
guard: isUserMessage,
allowedSources: ['trusted-source'],
});
const maliciousEvent = new MessageEvent('message', {
origin: 'https://trusted-domain.com',
source: mockSource,
data: {
id: 1,
name: 'Hacker',
email: 'hack@evil.com',
isActive: true,
},
});
const result = handler(maliciousEvent);
expect(result.status).toBe(safe_event_never_throws_1.Status.ERROR);
if (result.status === safe_event_never_throws_1.Status.ERROR) {
expect(result.code).toBe(403);
expect(result.message).toContain('not allowed');
}
});
});
describe('Ergonomic API - Source Validation', () => {
it('should call onSuccess for trusted sources', () => {
const mockSource = { close: jest.fn(), 'trusted-source': true };
const onSuccess = jest.fn();
const onSecurityViolation = jest.fn();
const onError = jest.fn();
const handler = (0, safe_event_never_throws_1.safePostMessageListener)(isUserMessage, {
allowedSources: ['trusted-source'],
onSuccess,
onSecurityViolation,
onError,
});
const validEvent = new MessageEvent('message', {
origin: 'https://trusted-domain.com',
data: {
id: 1,
name: 'John',
email: 'john@example.com',
isActive: true,
},
});
// Manually set the source property since MessageEvent constructor doesn't support it
Object.defineProperty(validEvent, 'source', {
value: mockSource,
writable: false,
configurable: false,
});
handler(validEvent);
expect(onSuccess).toHaveBeenCalledWith({
id: 1,
name: 'John',
email: 'john@example.com',
isActive: true,
});
expect(onSecurityViolation).not.toHaveBeenCalled();
expect(onError).not.toHaveBeenCalled();
});
it('should call onSecurityViolation for untrusted sources', () => {
const mockSource = { close: jest.fn() };
const onSuccess = jest.fn();
const onSecurityViolation = jest.fn();
const onError = jest.fn();
const handler = (0, safe_event_never_throws_1.safePostMessageListener)(isUserMessage, {
allowedSources: ['trusted-source'],
onSuccess,
onSecurityViolation,
onError,
});
const maliciousEvent = new MessageEvent('message', {
origin: 'https://trusted-domain.com',
data: {
id: 1,
name: 'Hacker',
email: 'hack@evil.com',
isActive: true,
},
});
// Manually set the source property since MessageEvent constructor doesn't support it
Object.defineProperty(maliciousEvent, 'source', {
value: mockSource,
writable: false,
configurable: false,
});
handler(maliciousEvent);
expect(onSuccess).not.toHaveBeenCalled();
expect(onSecurityViolation).toHaveBeenCalledWith('https://trusted-domain.com', expect.stringContaining('not allowed'));
// Security violations may call onError as fallback if onSecurityViolation is not provided
// This is acceptable behavior
});
});
});
describe('Combined Security Validation', () => {
it('should require both origin and source to be trusted', () => {
const mockSource = { close: jest.fn() };
const handler = (0, safe_event_never_throws_1.safePostMessage)({
guard: isUserMessage,
allowedOrigins: ['https://trusted-domain.com'],
allowedSources: ['trusted-source'],
});
// Valid origin but invalid source
const invalidSourceEvent = new MessageEvent('message', {
origin: 'https://trusted-domain.com',
data: {
id: 1,
name: 'John',
email: 'john@example.com',
isActive: true,
},
});
// Manually set the source property since MessageEvent constructor doesn't support it
Object.defineProperty(invalidSourceEvent, 'source', {
value: mockSource,
writable: false,
configurable: false,
});
const result1 = handler(invalidSourceEvent);
expect(result1.status).toBe(safe_event_never_throws_1.Status.ERROR);
// Valid source but invalid origin
const invalidOriginEvent = new MessageEvent('message', {
origin: 'https://malicious-site.com',
data: {
id: 1,
name: 'John',
email: 'john@example.com',
isActive: true,
},
});
// Manually set the source property since MessageEvent constructor doesn't support it
Object.defineProperty(invalidOriginEvent, 'source', {
value: mockSource,
writable: false,
configurable: false,
});
const result2 = handler(invalidOriginEvent);
expect(result2.status).toBe(safe_event_never_throws_1.Status.ERROR);
});
it('should pass when both origin and source are trusted', () => {
const mockSource = { close: jest.fn(), 'trusted-source': true };
const handler = (0, safe_event_never_throws_1.safePostMessage)({
guard: isUserMessage,
allowedOrigins: ['https://trusted-domain.com'],
allowedSources: ['trusted-source'],
});
const validEvent = new MessageEvent('message', {
origin: 'https://trusted-domain.com',
data: {
id: 1,
name: 'John',
email: 'john@example.com',
isActive: true,
},
});
// Manually set the source property since MessageEvent constructor doesn't support it
Object.defineProperty(validEvent, 'source', {
value: mockSource,
writable: false,
configurable: false,
});
const result = handler(validEvent);
expect(result.status).toBe(safe_event_never_throws_1.Status.SUCCESS);
});
});
describe('Security Error Handling', () => {
it('should provide detailed error messages for security violations', () => {
const handler = (0, safe_event_never_throws_1.safePostMessage)({
guard: isUserMessage,
allowedOrigins: ['https://trusted-domain.com'],
});
const maliciousEvent = new MessageEvent('message', {
origin: 'https://malicious-site.com',
data: { id: 1, name: 'Hacker', email: 'hack@evil.com', isActive: true },
});
const result = handler(maliciousEvent);
expect(result.status).toBe(safe_event_never_throws_1.Status.ERROR);
if (result.status === safe_event_never_throws_1.Status.ERROR) {
expect(result.message).toContain('malicious-site.com');
expect(result.message).toContain('not allowed');
expect(result.code).toBe(403);
}
});
it('should handle security violations before data validation', () => {
const onError = jest.fn();
const handler = (0, safe_event_never_throws_1.safePostMessage)({
guard: isUserMessage,
allowedOrigins: ['https://trusted-domain.com'],
onError,
});
const maliciousEvent = new MessageEvent('message', {
origin: 'https://malicious-site.com',
data: 'invalid-data-that-would-fail-validation',
});
const result = handler(maliciousEvent);
expect(result.status).toBe(safe_event_never_throws_1.Status.ERROR);
if (result.status === safe_event_never_throws_1.Status.ERROR) {
expect(result.code).toBe(403); // Security error, not validation error
expect(result.message).toContain('not allowed');
}
// Security violations may call onError as fallback if onSecurityViolation is not provided
// This is acceptable behavior
});
});
});
//# sourceMappingURL=security-validation.test.js.map