Initial commit: Interactives site
Astro-based site with 40+ interactive math games, puzzles, and explorations. Includes admin dashboard, todo system, blog, theme/subcategory navigation, and embed pages for iframe embedding. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
c5926b4213
265 changed files with 51899 additions and 0 deletions
76
src/utils/security.ts
Normal file
76
src/utils/security.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/**
|
||||
* Security utilities for input validation and sanitization
|
||||
*/
|
||||
|
||||
// URL validation for social sharing
|
||||
export const isValidUrl = (url: string): boolean => {
|
||||
try {
|
||||
const urlObj = new URL(url);
|
||||
// Only allow http and https protocols
|
||||
return ['http:', 'https:'].includes(urlObj.protocol);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Sanitize text input by removing potentially harmful characters
|
||||
export const sanitizeText = (text: string): string => {
|
||||
return text
|
||||
.replace(/[<>]/g, '') // Remove angle brackets to prevent XSS
|
||||
.replace(/javascript:/gi, '') // Remove javascript: protocol
|
||||
.replace(/data:/gi, '') // Remove data: protocol
|
||||
.trim();
|
||||
};
|
||||
|
||||
// Validate and sanitize URL for sharing
|
||||
export const sanitizeUrl = (url: string): string => {
|
||||
if (!isValidUrl(url)) {
|
||||
return window.location.href; // Fallback to current page
|
||||
}
|
||||
|
||||
try {
|
||||
const urlObj = new URL(url);
|
||||
// Keep the full URL including pathname, search, and hash for proper routing
|
||||
const cleanUrl = `${urlObj.protocol}//${urlObj.host}${urlObj.pathname}${urlObj.search}${urlObj.hash}`;
|
||||
return cleanUrl;
|
||||
} catch {
|
||||
return window.location.href;
|
||||
}
|
||||
};
|
||||
|
||||
// Validate social share data
|
||||
export const validateShareData = (title: string, description: string, url?: string) => {
|
||||
return {
|
||||
title: sanitizeText(title).slice(0, 100), // Limit title length
|
||||
description: sanitizeText(description).slice(0, 200), // Limit description length
|
||||
url: url ? sanitizeUrl(url) : window.location.href
|
||||
};
|
||||
};
|
||||
|
||||
// Rate limiting for actions (simple client-side implementation)
|
||||
class RateLimiter {
|
||||
private actions: Map<string, number[]> = new Map();
|
||||
|
||||
isAllowed(action: string, maxAttempts: number = 5, windowMs: number = 60000): boolean {
|
||||
const now = Date.now();
|
||||
const windowStart = now - windowMs;
|
||||
|
||||
if (!this.actions.has(action)) {
|
||||
this.actions.set(action, []);
|
||||
}
|
||||
|
||||
const attempts = this.actions.get(action)!;
|
||||
// Remove old attempts outside the window
|
||||
const recentAttempts = attempts.filter(time => time > windowStart);
|
||||
|
||||
if (recentAttempts.length >= maxAttempts) {
|
||||
return false;
|
||||
}
|
||||
|
||||
recentAttempts.push(now);
|
||||
this.actions.set(action, recentAttempts);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export const rateLimiter = new RateLimiter();
|
||||
Loading…
Add table
Add a link
Reference in a new issue