mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
- Remove nested git repository from bugulma/frontend/.git - Add all frontend files to main repository tracking - Convert from separate frontend/backend repos to unified monorepo - Preserve all frontend code and development history as tracked files - Eliminate nested repository complexity for simpler development workflow This creates a proper monorepo structure with frontend and backend coexisting in the same repository for easier development and deployment.
253 lines
6.9 KiB
TypeScript
253 lines
6.9 KiB
TypeScript
/**
|
|
* Security utilities for the Bugulma City Resource Graph frontend
|
|
* Provides additional security measures beyond basic API client security
|
|
*/
|
|
|
|
/**
|
|
* Secure random string generation for nonces, tokens, etc.
|
|
*/
|
|
export function generateSecureRandom(length = 32): string {
|
|
const array = new Uint8Array(length);
|
|
crypto.getRandomValues(array);
|
|
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
|
|
}
|
|
|
|
/**
|
|
* Content Security Policy violation handler
|
|
*/
|
|
export function setupCSPViolationReporting(): void {
|
|
if (typeof document !== 'undefined') {
|
|
document.addEventListener('securitypolicyviolation', (event) => {
|
|
console.warn('CSP Violation:', {
|
|
violatedDirective: event.violatedDirective,
|
|
blockedURI: event.blockedURI,
|
|
sourceFile: event.sourceFile,
|
|
lineNumber: event.lineNumber,
|
|
columnNumber: event.columnNumber,
|
|
});
|
|
|
|
// In production, send to monitoring service
|
|
if (import.meta.env.PROD) {
|
|
// TODO: Send to monitoring service like Sentry, LogRocket, etc.
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Secure localStorage wrapper with encryption (basic implementation)
|
|
* Note: This is still vulnerable to XSS, but provides additional protection
|
|
*/
|
|
export class SecureStorage {
|
|
private static readonly PREFIX = 'bugulma_secure_';
|
|
|
|
static setItem(key: string, value: string): void {
|
|
try {
|
|
const prefixedKey = this.PREFIX + key;
|
|
// Basic obfuscation (not true encryption - for demo purposes)
|
|
const obfuscated = btoa(encodeURIComponent(value));
|
|
localStorage.setItem(prefixedKey, obfuscated);
|
|
} catch (error) {
|
|
console.warn('SecureStorage setItem failed:', error);
|
|
}
|
|
}
|
|
|
|
static getItem(key: string): string | null {
|
|
try {
|
|
const prefixedKey = this.PREFIX + key;
|
|
const obfuscated = localStorage.getItem(prefixedKey);
|
|
if (obfuscated) {
|
|
return decodeURIComponent(atob(obfuscated));
|
|
}
|
|
return null;
|
|
} catch (error) {
|
|
console.warn('SecureStorage getItem failed:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
static removeItem(key: string): void {
|
|
try {
|
|
const prefixedKey = this.PREFIX + key;
|
|
localStorage.removeItem(prefixedKey);
|
|
} catch (error) {
|
|
console.warn('SecureStorage removeItem failed:', error);
|
|
}
|
|
}
|
|
|
|
static clear(): void {
|
|
try {
|
|
const keys = Object.keys(localStorage);
|
|
keys.forEach(key => {
|
|
if (key.startsWith(this.PREFIX)) {
|
|
localStorage.removeItem(key);
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.warn('SecureStorage clear failed:', error);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Input sanitization for different contexts
|
|
*/
|
|
export class InputSanitizer {
|
|
/**
|
|
* Sanitize HTML content (removes dangerous tags)
|
|
*/
|
|
static sanitizeHtml(input: string): string {
|
|
// Create a temporary DOM element to leverage browser's HTML parsing
|
|
const temp = document.createElement('div');
|
|
temp.textContent = input;
|
|
return temp.innerHTML;
|
|
}
|
|
|
|
/**
|
|
* Sanitize filename (remove dangerous characters)
|
|
*/
|
|
static sanitizeFilename(filename: string): string {
|
|
return filename.replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
}
|
|
|
|
/**
|
|
* Sanitize URL (ensure it's safe)
|
|
*/
|
|
static sanitizeUrl(url: string): string {
|
|
try {
|
|
const parsed = new URL(url);
|
|
// Only allow http/https protocols
|
|
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
|
return '';
|
|
}
|
|
return parsed.toString();
|
|
} catch {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sanitize SQL-like inputs (remove dangerous characters)
|
|
*/
|
|
static sanitizeSqlInput(input: string): string {
|
|
// Remove or escape common SQL injection characters
|
|
return input.replace(/['";\\]/g, '');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Security monitoring and logging
|
|
*/
|
|
export class SecurityMonitor {
|
|
private static violations: SecurityViolation[] = [];
|
|
|
|
static logViolation(type: string, details: Record<string, any>): void {
|
|
const violation: SecurityViolation = {
|
|
type,
|
|
details,
|
|
timestamp: new Date().toISOString(),
|
|
userAgent: navigator.userAgent,
|
|
url: window.location.href,
|
|
};
|
|
|
|
this.violations.push(violation);
|
|
console.warn('Security violation logged:', violation);
|
|
|
|
// Keep only last 100 violations to prevent memory issues
|
|
if (this.violations.length > 100) {
|
|
this.violations = this.violations.slice(-100);
|
|
}
|
|
|
|
// In production, send to monitoring service
|
|
if (import.meta.env.PROD) {
|
|
this.reportToMonitoring(violation);
|
|
}
|
|
}
|
|
|
|
static getViolations(): SecurityViolation[] {
|
|
return [...this.violations];
|
|
}
|
|
|
|
private static reportToMonitoring(violation: SecurityViolation): void {
|
|
// TODO: Integrate with monitoring service
|
|
// Example: Sentry, LogRocket, DataDog, etc.
|
|
try {
|
|
// Placeholder for monitoring integration
|
|
console.log('Reporting violation to monitoring service:', violation);
|
|
} catch (error) {
|
|
console.error('Failed to report violation:', error);
|
|
}
|
|
}
|
|
}
|
|
|
|
interface SecurityViolation {
|
|
type: string;
|
|
details: Record<string, any>;
|
|
timestamp: string;
|
|
userAgent: string;
|
|
url: string;
|
|
}
|
|
|
|
/**
|
|
* Initialize security measures on app startup
|
|
*/
|
|
export function initializeSecurity(): void {
|
|
// Setup CSP violation reporting
|
|
setupCSPViolationReporting();
|
|
|
|
// Log security initialization
|
|
console.log('🔒 Security measures initialized');
|
|
|
|
// Additional security checks
|
|
if (typeof window !== 'undefined') {
|
|
// Check for insecure protocols in production
|
|
if (import.meta.env.PROD && window.location.protocol === 'http:') {
|
|
console.warn('🚨 SECURITY WARNING: Application is running over HTTP in production!');
|
|
}
|
|
|
|
// Check for missing security headers (basic check)
|
|
const cspMeta = document.querySelector('meta[http-equiv="Content-Security-Policy"]');
|
|
if (!cspMeta) {
|
|
console.warn('🚨 SECURITY WARNING: Content Security Policy not found!');
|
|
} else {
|
|
// Log CSP configuration for debugging
|
|
console.log('🔒 CSP configured:', cspMeta.getAttribute('content'));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate file upload security
|
|
*/
|
|
export function validateFileUpload(file: File): { isValid: boolean; error?: string } {
|
|
// Check file size (10MB limit)
|
|
const maxSize = 10 * 1024 * 1024; // 10MB
|
|
if (file.size > maxSize) {
|
|
return { isValid: false, error: 'File size exceeds 10MB limit' };
|
|
}
|
|
|
|
// Check file type
|
|
const allowedTypes = [
|
|
'image/jpeg',
|
|
'image/png',
|
|
'image/gif',
|
|
'image/webp',
|
|
'application/pdf',
|
|
'text/plain',
|
|
'application/msword',
|
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
];
|
|
|
|
if (!allowedTypes.includes(file.type)) {
|
|
return { isValid: false, error: 'File type not allowed' };
|
|
}
|
|
|
|
// Check filename
|
|
const sanitizedName = InputSanitizer.sanitizeFilename(file.name);
|
|
if (sanitizedName !== file.name) {
|
|
return { isValid: false, error: 'Filename contains invalid characters' };
|
|
}
|
|
|
|
return { isValid: true };
|
|
}
|