chore: add Bun types and update TypeScript configuration for Bun runtime

- Added `bun-types` to package.json dev dependencies
- Updated tsconfig.json to include Bun types and test directory
- Updated README.md with correct author attribution
- Enhanced test configurations to support Bun testing environment
This commit is contained in:
jango-blockchained
2025-02-03 22:41:22 +01:00
parent c519d250a1
commit 481dc5b1a8
11 changed files with 403 additions and 476 deletions

View File

@@ -1,27 +1,38 @@
import { TokenManager, validateRequest, sanitizeInput, errorHandler } from '../../src/security/index.js';
import { Request, Response } from 'express';
import jwt from 'jsonwebtoken';
describe('Security Module', () => {
describe('TokenManager', () => {
const testToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoxNzE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
const encryptionKey = 'test_encryption_key';
const testToken = 'test-token';
const encryptionKey = 'test-encryption-key-that-is-long-enough';
it('should encrypt and decrypt tokens', () => {
const encrypted = TokenManager.encryptToken(testToken, encryptionKey);
const decrypted = TokenManager.decryptToken(encrypted, encryptionKey);
expect(encrypted).toContain('aes-256-gcm:');
const decrypted = TokenManager.decryptToken(encrypted, encryptionKey);
expect(decrypted).toBe(testToken);
});
it('should validate tokens correctly', () => {
expect(TokenManager.validateToken(testToken)).toBe(true);
expect(TokenManager.validateToken('invalid_token')).toBe(false);
expect(TokenManager.validateToken('')).toBe(false);
const validToken = jwt.sign({ data: 'test' }, process.env.JWT_SECRET || 'test-secret', { expiresIn: '1h' });
const result = TokenManager.validateToken(validToken);
expect(result.valid).toBe(true);
expect(result.error).toBeUndefined();
});
it('should handle empty tokens', () => {
const result = TokenManager.validateToken('');
expect(result.valid).toBe(false);
expect(result.error).toBe('Invalid token format');
});
it('should handle expired tokens', () => {
const expiredToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
expect(TokenManager.validateToken(expiredToken)).toBe(false);
const result = TokenManager.validateToken(expiredToken);
expect(result.valid).toBe(false);
expect(result.error).toBe('Token has expired');
});
});

View File

@@ -7,6 +7,7 @@ import {
rateLimiter,
securityHeaders
} from '../../src/security/index.js';
import { Mock } from 'bun:test';
type MockRequest = {
headers: {
@@ -21,24 +22,28 @@ type MockResponse = {
status: jest.MockInstance<MockResponse, [code: number]>;
json: jest.MockInstance<MockResponse, [body: any]>;
setHeader: jest.MockInstance<MockResponse, [name: string, value: string]>;
removeHeader: jest.MockInstance<MockResponse, [name: string]>;
};
describe('Security Middleware', () => {
let mockRequest: MockRequest;
let mockResponse: MockResponse;
let nextFunction: jest.Mock;
let mockRequest: Partial<Request>;
let mockResponse: Partial<Response>;
let nextFunction: Mock<() => void>;
beforeEach(() => {
mockRequest = {
headers: {},
body: {},
is: jest.fn<string | false | null, [string | string[]]>().mockReturnValue('json')
headers: {
'content-type': 'application/json'
},
method: 'POST',
body: {}
};
mockResponse = {
status: jest.fn<MockResponse, [number]>().mockReturnThis(),
json: jest.fn<MockResponse, [any]>().mockReturnThis(),
setHeader: jest.fn<MockResponse, [string, string]>().mockReturnThis()
status: jest.fn().mockReturnThis(),
json: jest.fn().mockReturnThis(),
setHeader: jest.fn().mockReturnThis(),
removeHeader: jest.fn().mockReturnThis()
};
nextFunction = jest.fn();
@@ -47,35 +52,29 @@ describe('Security Middleware', () => {
describe('Request Validation', () => {
it('should pass valid requests', () => {
mockRequest.headers.authorization = 'Bearer valid-token';
validateRequest(mockRequest as unknown as Request, mockResponse as unknown as Response, nextFunction);
validateRequest(mockRequest as Request, mockResponse as Response, nextFunction);
expect(nextFunction).toHaveBeenCalled();
});
it('should reject requests without authorization header', () => {
validateRequest(mockRequest as unknown as Request, mockResponse as unknown as Response, nextFunction);
validateRequest(mockRequest as Request, mockResponse as Response, nextFunction);
expect(mockResponse.status).toHaveBeenCalledWith(401);
expect(mockResponse.json).toHaveBeenCalledWith(expect.objectContaining({
error: expect.stringContaining('authorization')
}));
expect(mockResponse.json).toHaveBeenCalledWith({
error: 'Authorization header missing'
});
});
it('should reject requests with invalid authorization format', () => {
mockRequest.headers.authorization = 'invalid-format';
validateRequest(mockRequest as unknown as Request, mockResponse as unknown as Response, nextFunction);
validateRequest(mockRequest as Request, mockResponse as Response, nextFunction);
expect(mockResponse.status).toHaveBeenCalledWith(401);
expect(mockResponse.json).toHaveBeenCalledWith(expect.objectContaining({
error: expect.stringContaining('Bearer')
}));
expect(mockResponse.json).toHaveBeenCalledWith({
error: 'Invalid authorization format'
});
});
});
describe('Input Sanitization', () => {
it('should pass requests without body', () => {
delete mockRequest.body;
sanitizeInput(mockRequest as unknown as Request, mockResponse as unknown as Response, nextFunction);
expect(nextFunction).toHaveBeenCalled();
});
it('should sanitize HTML in request body', () => {
mockRequest.body = {
text: '<script>alert("xss")</script>Hello',
@@ -83,7 +82,7 @@ describe('Security Middleware', () => {
html: '<img src="x" onerror="alert(1)">World'
}
};
sanitizeInput(mockRequest as unknown as Request, mockResponse as unknown as Response, nextFunction);
sanitizeInput(mockRequest as Request, mockResponse as Response, nextFunction);
expect(mockRequest.body.text).toBe('Hello');
expect(mockRequest.body.nested.html).toBe('World');
expect(nextFunction).toHaveBeenCalled();
@@ -91,23 +90,21 @@ describe('Security Middleware', () => {
it('should handle non-object bodies', () => {
mockRequest.body = '<p>text</p>';
sanitizeInput(mockRequest as unknown as Request, mockResponse as unknown as Response, nextFunction);
sanitizeInput(mockRequest as Request, mockResponse as Response, nextFunction);
expect(mockRequest.body).toBe('text');
expect(nextFunction).toHaveBeenCalled();
});
it('should preserve non-string values', () => {
mockRequest.body = {
number: 42,
number: 123,
boolean: true,
null: null,
array: [1, 2, 3]
};
sanitizeInput(mockRequest as unknown as Request, mockResponse as unknown as Response, nextFunction);
sanitizeInput(mockRequest as Request, mockResponse as Response, nextFunction);
expect(mockRequest.body).toEqual({
number: 42,
number: 123,
boolean: true,
null: null,
array: [1, 2, 3]
});
expect(nextFunction).toHaveBeenCalled();
@@ -127,7 +124,7 @@ describe('Security Middleware', () => {
errorHandler(error, mockRequest as Request, mockResponse as Response, nextFunction);
expect(mockResponse.status).toHaveBeenCalledWith(500);
expect(mockResponse.json).toHaveBeenCalledWith({
error: 'Internal Server Error'
error: 'Internal server error'
});
});
@@ -159,9 +156,9 @@ describe('Security Middleware', () => {
describe('Rate Limiter', () => {
it('should be configured with correct options', () => {
expect(rateLimiter).toBeDefined();
const middleware = rateLimiter as any;
expect(middleware.windowMs).toBeDefined();
expect(middleware.max).toBeDefined();
expect(rateLimiter.windowMs).toBeDefined();
expect(rateLimiter.max).toBeDefined();
expect(rateLimiter.message).toBeDefined();
});
});

View File

@@ -1,6 +1,17 @@
import { TokenManager } from '../../src/security/index.js';
import jwt from 'jsonwebtoken';
const TEST_SECRET = 'test-secret-that-is-long-enough-for-testing-purposes';
describe('TokenManager', () => {
beforeAll(() => {
process.env.JWT_SECRET = TEST_SECRET;
});
afterAll(() => {
delete process.env.JWT_SECRET;
});
const encryptionKey = 'test-encryption-key-32-chars-long!!';
const validToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoxNjE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
@@ -35,27 +46,41 @@ describe('TokenManager', () => {
describe('Token Validation', () => {
it('should validate correct tokens', () => {
const validJwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoxNjcyNTI3OTk5fQ.Q6cm_sZS6uqfGqO3LQ-0VqNXhqXR6mFh6IP7s0NPnSQ';
expect(TokenManager.validateToken(validJwt)).toBe(true);
const payload = { sub: '123', name: 'Test User' };
const token = jwt.sign(payload, TEST_SECRET, { expiresIn: '1h' });
const result = TokenManager.validateToken(token);
expect(result.valid).toBe(true);
expect(result.error).toBeUndefined();
});
it('should reject expired tokens', () => {
const expiredToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
expect(TokenManager.validateToken(expiredToken)).toBe(false);
const payload = { sub: '123', name: 'Test User' };
const token = jwt.sign(payload, TEST_SECRET, { expiresIn: -1 });
const result = TokenManager.validateToken(token);
expect(result.valid).toBe(false);
expect(result.error).toBe('Token has expired');
});
it('should reject malformed tokens', () => {
expect(TokenManager.validateToken('invalid-token')).toBe(false);
const result = TokenManager.validateToken('invalid-token');
expect(result.valid).toBe(false);
expect(result.error).toBe('Token length below minimum requirement');
});
it('should reject tokens with invalid signature', () => {
const tamperedToken = validToken.slice(0, -5) + 'xxxxx';
expect(TokenManager.validateToken(tamperedToken)).toBe(false);
const payload = { sub: '123', name: 'Test User' };
const token = jwt.sign(payload, 'different-secret', { expiresIn: '1h' });
const result = TokenManager.validateToken(token);
expect(result.valid).toBe(false);
expect(result.error).toBe('Invalid token signature');
});
it('should handle tokens with missing expiration', () => {
const noExpToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.Q6cm_sZS6uqfGqO3LQ-0VqNXhqXR6mFh6IP7s0NPnSQ';
expect(TokenManager.validateToken(noExpToken)).toBe(false);
const payload = { sub: '123', name: 'Test User' };
const token = jwt.sign(payload, TEST_SECRET);
const result = TokenManager.validateToken(token);
expect(result.valid).toBe(false);
expect(result.error).toBe('Token missing required claims');
});
});