Files
smom-dbis-138/frontend-dapp/src/utils/security.ts
T

139 lines
3.4 KiB
TypeScript

/**
* Security utility functions for input validation and security checks
* Adapted for wagmi/viem
*/
import { isAddress, getAddress } from 'viem';
import type { PublicClient } from 'viem';
import { ERROR_MESSAGES, VALIDATION, SECURITY } from './constants';
/**
* Validates Ethereum address with checksum verification
*/
export function validateAddress(address: string): {
valid: boolean;
error?: string;
checksummed?: string;
} {
if (!address || typeof address !== 'string') {
return { valid: false, error: ERROR_MESSAGES.INVALID_ADDRESS };
}
if (address.length > VALIDATION.ADDRESS_MAX_LENGTH) {
return { valid: false, error: 'Address exceeds maximum length' };
}
if (!isAddress(address)) {
return { valid: false, error: 'Invalid Ethereum address format' };
}
try {
const checksummed = getAddress(address);
return { valid: true, checksummed };
} catch (error: unknown) {
return {
valid: false,
error: error instanceof Error ? error.message : 'Address validation failed',
};
}
}
/**
* Checks if address is a contract (has code)
*/
export async function isContractAddress(
address: string,
publicClient: Pick<PublicClient, 'getBytecode'> // viem PublicClient
): Promise<boolean> {
try {
const code = await publicClient.getBytecode({ address: address as `0x${string}` });
return code !== undefined && code !== '0x' && code !== '0x0';
} catch {
return false;
}
}
/**
* Validates transaction data field
*/
export function validateTransactionData(data: string): {
valid: boolean;
error?: string;
} {
if (!data) {
return { valid: true }; // Empty data is valid
}
if (typeof data !== 'string') {
return { valid: false, error: 'Data must be a string' };
}
if (!data.startsWith('0x')) {
return { valid: false, error: 'Data must start with 0x' };
}
if (data.length > SECURITY.MAX_TRANSACTION_DATA_LENGTH * 2 + 2) {
return {
valid: false,
error: `Data exceeds maximum length (${SECURITY.MAX_TRANSACTION_DATA_LENGTH} bytes)`,
};
}
if (!/^0x[0-9a-fA-F]*$/.test(data)) {
return { valid: false, error: 'Data contains invalid hex characters' };
}
return { valid: true };
}
/**
* Rate limiter for admin functions
*/
export class RateLimiter {
private requests: Map<string, number[]> = new Map();
checkLimit(identifier: string, maxRequests: number = SECURITY.DEFAULT_RATE_LIMIT_REQUESTS): boolean {
const now = Date.now();
const windowStart = now - SECURITY.DEFAULT_RATE_LIMIT_WINDOW_MS;
if (!this.requests.has(identifier)) {
this.requests.set(identifier, []);
}
const requests = this.requests.get(identifier)!;
const recentRequests = requests.filter((time) => time > windowStart);
if (recentRequests.length >= maxRequests) {
return false;
}
recentRequests.push(now);
this.requests.set(identifier, recentRequests);
return true;
}
clear(identifier?: string): void {
if (identifier) {
this.requests.delete(identifier);
} else {
this.requests.clear();
}
}
}
/**
* Generate secure ID
*/
export function generateSecureId(): string {
return `tx_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
}
/**
* Validate network ID
*/
export function validateNetworkId(networkId: number): boolean {
// Add supported networks as needed
const supportedNetworks = [1, 5, 137, 42161, 10, 8453, 100, 56, 250, 43114, 138];
return supportedNetworks.includes(networkId);
}