javascript
exercises
exercises.jsâĄjavascript
/**
* 21.1 Security Fundamentals - Exercises
*
* Practice implementing security features
*/
// ============================================
// EXERCISE 1: Build a Complete XSS Filter
// ============================================
/**
* Create an XSS filter that:
* - Escapes HTML entities
* - Removes dangerous tags
* - Sanitizes URLs
* - Handles nested attacks
* - Preserves safe formatting
*/
class XSSFilter {
// Your implementation here
}
/*
// SOLUTION:
class XSSFilter {
constructor(options = {}) {
this.allowedTags = new Set(options.allowedTags || [
'p', 'br', 'b', 'i', 'u', 'strong', 'em',
'ul', 'ol', 'li', 'a', 'span', 'div'
]);
this.allowedAttributes = new Map(Object.entries(options.allowedAttributes || {
'a': ['href', 'title'],
'span': ['class'],
'div': ['class'],
'*': ['class', 'id']
}));
this.allowedProtocols = new Set(options.allowedProtocols || [
'http:', 'https:', 'mailto:'
]);
}
// Main sanitize function
sanitize(html) {
if (typeof html !== 'string') return '';
// Decode entities first to catch encoded attacks
html = this.decodeEntities(html);
// Remove script tags and content
html = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
// Remove style tags and content
html = html.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, '');
// Remove HTML comments
html = html.replace(/<!--[\s\S]*?-->/g, '');
// Process remaining HTML
html = this.processHTML(html);
return html;
}
// Decode HTML entities
decodeEntities(str) {
// Decode common entities
const entities = {
'<': '<',
'>': '>',
'&': '&',
'"': '"',
''': "'",
''': "'",
'/': '/',
'<': '<',
'>': '>'
};
return str.replace(/&(?:#x?[0-9a-f]+|[a-z]+);/gi, entity => {
return entities[entity.toLowerCase()] || entity;
});
}
// Process HTML tags
processHTML(html) {
// Match HTML tags
const tagRegex = /<\/?([a-zA-Z][a-zA-Z0-9]*)\b([^>]*)>/g;
return html.replace(tagRegex, (match, tagName, attributes) => {
const tag = tagName.toLowerCase();
// Check if tag is allowed
if (!this.allowedTags.has(tag)) {
return ''; // Remove disallowed tags
}
// Check if closing tag
if (match.startsWith('</')) {
return `</${tag}>`;
}
// Process attributes
const safeAttrs = this.processAttributes(tag, attributes);
// Self-closing tags
if (['br', 'hr', 'img'].includes(tag)) {
return `<${tag}${safeAttrs} />`;
}
return `<${tag}${safeAttrs}>`;
});
}
// Process attributes
processAttributes(tag, attrString) {
if (!attrString || !attrString.trim()) return '';
const allowedForTag = this.allowedAttributes.get(tag) || [];
const allowedGlobal = this.allowedAttributes.get('*') || [];
const allowed = [...allowedForTag, ...allowedGlobal];
const safeAttrs = [];
// Match attributes
const attrRegex = /([a-zA-Z][a-zA-Z0-9-]*)\s*=\s*(?:"([^"]*)"|'([^']*)'|(\S+))/g;
let match;
while ((match = attrRegex.exec(attrString)) !== null) {
const attrName = match[1].toLowerCase();
const attrValue = match[2] || match[3] || match[4] || '';
// Skip event handlers
if (attrName.startsWith('on')) continue;
// Skip javascript: in any attribute
if (attrValue.toLowerCase().includes('javascript:')) continue;
// Skip data: in src attributes
if (['src', 'href'].includes(attrName) &&
attrValue.toLowerCase().startsWith('data:') &&
!attrValue.toLowerCase().startsWith('data:image/')) continue;
// Validate href/src URLs
if (['href', 'src'].includes(attrName)) {
if (!this.isValidUrl(attrValue)) continue;
}
// Check if attribute is allowed
if (allowed.includes(attrName)) {
safeAttrs.push(`${attrName}="${this.escapeAttribute(attrValue)}"`);
}
}
return safeAttrs.length ? ' ' + safeAttrs.join(' ') : '';
}
// Validate URL
isValidUrl(url) {
if (!url) return false;
// Check for dangerous protocols
const lower = url.toLowerCase().trim();
if (lower.startsWith('javascript:')) return false;
if (lower.startsWith('vbscript:')) return false;
if (lower.startsWith('data:') && !lower.startsWith('data:image/')) return false;
// Check protocol whitelist for absolute URLs
try {
const parsed = new URL(url, 'https://example.com');
if (url.includes('://') && !this.allowedProtocols.has(parsed.protocol)) {
return false;
}
} catch {
return false;
}
return true;
}
// Escape attribute value
escapeAttribute(value) {
return value
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/</g, '<')
.replace(/>/g, '>');
}
// Escape HTML text
escapeText(text) {
return text
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
}
// Test XSS Filter
function testXSSFilter() {
console.log('=== XSS Filter Tests ===\n');
const filter = new XSSFilter();
const testCases = [
{
name: 'Script tag',
input: '<script>alert("XSS")</script>',
expected: ''
},
{
name: 'Event handler',
input: '<img src="x" onerror="alert(1)">',
expected: ''
},
{
name: 'JavaScript URL',
input: '<a href="javascript:alert(1)">Click</a>',
expected: '<a>Click</a>'
},
{
name: 'Safe link',
input: '<a href="https://example.com">Safe</a>',
expected: '<a href="https://example.com">Safe</a>'
},
{
name: 'Encoded attack',
input: '<script>alert(1)</script>',
expected: ''
},
{
name: 'Nested attack',
input: '<scr<script>ipt>alert(1)</sc</script>ript>',
expected: ''
},
{
name: 'Safe formatting',
input: '<p><strong>Bold</strong> and <em>italic</em></p>',
expected: '<p><strong>Bold</strong> and <em>italic</em></p>'
}
];
for (const tc of testCases) {
const result = filter.sanitize(tc.input);
const passed = result === tc.expected;
console.log(`${passed ? 'â' : 'â'} ${tc.name}`);
if (!passed) {
console.log(` Input: ${tc.input}`);
console.log(` Expected: ${tc.expected}`);
console.log(` Got: ${result}`);
}
}
console.log('\n=== XSS Filter Tests Complete ===\n');
}
*/
// ============================================
// EXERCISE 2: Implement Rate Limiter
// ============================================
/**
* Create a rate limiter that:
* - Limits requests per time window
* - Supports different limits per endpoint
* - Uses sliding window algorithm
* - Returns appropriate headers
*/
class RateLimiter {
// Your implementation here
}
/*
// SOLUTION:
class RateLimiter {
constructor(options = {}) {
this.windowMs = options.windowMs || 60000; // 1 minute
this.maxRequests = options.maxRequests || 100;
this.store = new Map();
this.endpointLimits = new Map(Object.entries(options.endpointLimits || {}));
}
// Get limit for endpoint
getLimit(endpoint) {
return this.endpointLimits.get(endpoint) || this.maxRequests;
}
// Generate key
getKey(identifier, endpoint = '') {
return `${identifier}:${endpoint}`;
}
// Check and update rate limit
check(identifier, endpoint = '') {
const key = this.getKey(identifier, endpoint);
const now = Date.now();
const windowStart = now - this.windowMs;
const limit = this.getLimit(endpoint);
// Get or create record
let record = this.store.get(key);
if (!record) {
record = { timestamps: [] };
this.store.set(key, record);
}
// Remove expired timestamps (sliding window)
record.timestamps = record.timestamps.filter(ts => ts > windowStart);
// Check if limit exceeded
const remaining = limit - record.timestamps.length;
if (remaining <= 0) {
// Calculate retry after
const oldestInWindow = record.timestamps[0];
const retryAfter = Math.ceil((oldestInWindow + this.windowMs - now) / 1000);
return {
allowed: false,
remaining: 0,
limit,
retryAfter,
headers: {
'X-RateLimit-Limit': limit,
'X-RateLimit-Remaining': 0,
'X-RateLimit-Reset': Math.ceil((oldestInWindow + this.windowMs) / 1000),
'Retry-After': retryAfter
}
};
}
// Add timestamp
record.timestamps.push(now);
return {
allowed: true,
remaining: remaining - 1,
limit,
headers: {
'X-RateLimit-Limit': limit,
'X-RateLimit-Remaining': remaining - 1,
'X-RateLimit-Reset': Math.ceil((now + this.windowMs) / 1000)
}
};
}
// Reset for identifier
reset(identifier, endpoint = '') {
const key = this.getKey(identifier, endpoint);
this.store.delete(key);
}
// Middleware for Express
middleware(options = {}) {
const getIdentifier = options.getIdentifier || (req => req.ip);
const getEndpoint = options.getEndpoint || (req => req.path);
return (req, res, next) => {
const identifier = getIdentifier(req);
const endpoint = getEndpoint(req);
const result = this.check(identifier, endpoint);
// Set headers
for (const [header, value] of Object.entries(result.headers)) {
res.setHeader(header, value);
}
if (!result.allowed) {
return res.status(429).json({
error: 'Too Many Requests',
retryAfter: result.retryAfter
});
}
next();
};
}
// Cleanup old entries
cleanup() {
const cutoff = Date.now() - this.windowMs;
for (const [key, record] of this.store) {
record.timestamps = record.timestamps.filter(ts => ts > cutoff);
if (record.timestamps.length === 0) {
this.store.delete(key);
}
}
}
// Get stats
getStats() {
let totalRequests = 0;
let activeKeys = 0;
for (const record of this.store.values()) {
if (record.timestamps.length > 0) {
activeKeys++;
totalRequests += record.timestamps.length;
}
}
return {
activeClients: activeKeys,
totalRequests,
windowMs: this.windowMs,
defaultLimit: this.maxRequests
};
}
}
// Test Rate Limiter
function testRateLimiter() {
console.log('=== Rate Limiter Tests ===\n');
const limiter = new RateLimiter({
windowMs: 1000, // 1 second for testing
maxRequests: 5,
endpointLimits: {
'/login': 3
}
});
const userId = 'user-123';
// Test normal endpoint
console.log('Testing normal endpoint (limit: 5):');
for (let i = 0; i < 7; i++) {
const result = limiter.check(userId);
console.log(` Request ${i + 1}: ${result.allowed ? 'â Allowed' : 'â Blocked'}, remaining: ${result.remaining}`);
}
// Test limited endpoint
console.log('\nTesting /login endpoint (limit: 3):');
limiter.reset(userId, '/login');
for (let i = 0; i < 5; i++) {
const result = limiter.check(userId, '/login');
console.log(` Request ${i + 1}: ${result.allowed ? 'â Allowed' : 'â Blocked'}, remaining: ${result.remaining}`);
}
// Test sliding window (wait and retry)
console.log('\nTesting sliding window (waiting 500ms):');
setTimeout(() => {
const result = limiter.check(userId);
console.log(` After wait: ${result.allowed ? 'â Allowed' : 'â Blocked'}, remaining: ${result.remaining}`);
console.log('\nStats:', limiter.getStats());
console.log('\n=== Rate Limiter Tests Complete ===\n');
}, 500);
}
*/
// ============================================
// EXERCISE 3: Build Security Audit Logger
// ============================================
/**
* Create a security audit logger that:
* - Logs security events
* - Detects suspicious patterns
* - Masks sensitive data
* - Generates alerts
*/
class SecurityAuditLogger {
// Your implementation here
}
/*
// SOLUTION:
class SecurityAuditLogger {
constructor(options = {}) {
this.logs = [];
this.maxLogs = options.maxLogs || 10000;
this.alertHandlers = [];
this.suspiciousPatterns = options.suspiciousPatterns || [
{ name: 'rapid-requests', threshold: 50, window: 60000 },
{ name: 'failed-logins', threshold: 5, window: 300000 },
{ name: 'unusual-hours', hours: [0, 1, 2, 3, 4, 5] }
];
this.eventCounts = new Map();
}
// Log security event
log(event) {
const entry = {
id: this.generateId(),
timestamp: Date.now(),
isoTime: new Date().toISOString(),
type: event.type,
severity: event.severity || 'info',
userId: event.userId,
ip: event.ip,
userAgent: this.maskSensitive(event.userAgent),
action: event.action,
resource: event.resource,
result: event.result,
details: this.maskSensitive(event.details),
metadata: event.metadata
};
this.logs.push(entry);
// Trim old logs
if (this.logs.length > this.maxLogs) {
this.logs = this.logs.slice(-this.maxLogs);
}
// Check for suspicious patterns
this.checkPatterns(entry);
// Output to console
this.output(entry);
return entry;
}
generateId() {
return 'audit_' + Date.now().toString(36) + Math.random().toString(36).substr(2, 9);
}
// Mask sensitive data
maskSensitive(data) {
if (typeof data !== 'object' || data === null) {
return data;
}
const sensitiveFields = ['password', 'token', 'secret', 'key', 'authorization', 'cookie'];
const masked = Array.isArray(data) ? [] : {};
for (const [key, value] of Object.entries(data)) {
const lowerKey = key.toLowerCase();
if (sensitiveFields.some(f => lowerKey.includes(f))) {
masked[key] = '[REDACTED]';
} else if (typeof value === 'object' && value !== null) {
masked[key] = this.maskSensitive(value);
} else {
masked[key] = value;
}
}
return masked;
}
// Check for suspicious patterns
checkPatterns(entry) {
const key = `${entry.type}:${entry.userId || entry.ip}`;
// Track event counts
if (!this.eventCounts.has(key)) {
this.eventCounts.set(key, []);
}
const counts = this.eventCounts.get(key);
counts.push(entry.timestamp);
// Check patterns
for (const pattern of this.suspiciousPatterns) {
switch (pattern.name) {
case 'rapid-requests': {
const windowStart = Date.now() - pattern.window;
const recent = counts.filter(t => t > windowStart);
if (recent.length > pattern.threshold) {
this.triggerAlert({
pattern: pattern.name,
entry,
count: recent.length,
threshold: pattern.threshold
});
}
break;
}
case 'failed-logins': {
if (entry.type === 'login' && entry.result === 'failure') {
const windowStart = Date.now() - pattern.window;
const recent = this.logs.filter(l =>
l.type === 'login' &&
l.result === 'failure' &&
(l.userId === entry.userId || l.ip === entry.ip) &&
l.timestamp > windowStart
);
if (recent.length >= pattern.threshold) {
this.triggerAlert({
pattern: pattern.name,
entry,
count: recent.length,
threshold: pattern.threshold
});
}
}
break;
}
case 'unusual-hours': {
const hour = new Date().getHours();
if (pattern.hours.includes(hour)) {
this.triggerAlert({
pattern: pattern.name,
entry,
hour
});
}
break;
}
}
}
}
// Trigger alert
triggerAlert(alert) {
const alertEntry = {
id: this.generateId(),
timestamp: Date.now(),
isoTime: new Date().toISOString(),
...alert
};
// Call alert handlers
for (const handler of this.alertHandlers) {
try {
handler(alertEntry);
} catch (e) {
console.error('Alert handler error:', e);
}
}
}
// Register alert handler
onAlert(handler) {
this.alertHandlers.push(handler);
return () => {
const idx = this.alertHandlers.indexOf(handler);
if (idx !== -1) this.alertHandlers.splice(idx, 1);
};
}
// Output log entry
output(entry) {
const severitySymbols = {
debug: 'đ',
info: 'âšī¸',
warn: 'â ī¸',
error: 'â',
critical: 'đ¨'
};
const symbol = severitySymbols[entry.severity] || 'âšī¸';
console.log(`${symbol} [${entry.isoTime}] ${entry.type}: ${entry.action} - ${entry.result}`);
if (entry.details && Object.keys(entry.details).length > 0) {
console.log(` Details:`, JSON.stringify(entry.details));
}
}
// Query logs
query(filter = {}) {
let results = [...this.logs];
if (filter.type) {
results = results.filter(l => l.type === filter.type);
}
if (filter.userId) {
results = results.filter(l => l.userId === filter.userId);
}
if (filter.ip) {
results = results.filter(l => l.ip === filter.ip);
}
if (filter.severity) {
results = results.filter(l => l.severity === filter.severity);
}
if (filter.since) {
results = results.filter(l => l.timestamp >= filter.since);
}
if (filter.until) {
results = results.filter(l => l.timestamp <= filter.until);
}
return results;
}
// Generate report
generateReport(options = {}) {
const since = options.since || Date.now() - 86400000; // Last 24 hours
const logs = this.query({ since });
const byType = {};
const byResult = {};
const bySeverity = {};
const byUser = {};
const byIp = {};
for (const log of logs) {
byType[log.type] = (byType[log.type] || 0) + 1;
byResult[log.result] = (byResult[log.result] || 0) + 1;
bySeverity[log.severity] = (bySeverity[log.severity] || 0) + 1;
if (log.userId) {
byUser[log.userId] = (byUser[log.userId] || 0) + 1;
}
if (log.ip) {
byIp[log.ip] = (byIp[log.ip] || 0) + 1;
}
}
return {
period: {
start: new Date(since).toISOString(),
end: new Date().toISOString()
},
totalEvents: logs.length,
byType,
byResult,
bySeverity,
topUsers: Object.entries(byUser)
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.map(([user, count]) => ({ user, count })),
topIPs: Object.entries(byIp)
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.map(([ip, count]) => ({ ip, count }))
};
}
}
// Test Security Audit Logger
function testSecurityAuditLogger() {
console.log('=== Security Audit Logger Tests ===\n');
const logger = new SecurityAuditLogger({
suspiciousPatterns: [
{ name: 'failed-logins', threshold: 3, window: 60000 }
]
});
// Register alert handler
logger.onAlert(alert => {
console.log('đ¨ ALERT:', alert.pattern, alert);
});
// Log some events
logger.log({
type: 'login',
userId: 'user-123',
ip: '192.168.1.1',
action: 'authenticate',
result: 'success',
details: { password: 'secret123' }
});
// Simulate failed login attempts (should trigger alert)
for (let i = 0; i < 4; i++) {
logger.log({
type: 'login',
userId: 'hacker',
ip: '10.0.0.100',
action: 'authenticate',
result: 'failure',
severity: 'warn',
details: { reason: 'Invalid password' }
});
}
// API access
logger.log({
type: 'api',
userId: 'user-123',
ip: '192.168.1.1',
action: 'read',
resource: '/api/users',
result: 'success'
});
// Generate report
console.log('\nSecurity Report:');
console.log(JSON.stringify(logger.generateReport(), null, 2));
console.log('\n=== Security Audit Logger Tests Complete ===\n');
}
*/
// ============================================
// EXERCISE 4: Implement Secure Token Manager
// ============================================
/**
* Create a secure token manager that:
* - Generates cryptographically secure tokens
* - Manages token lifecycle
* - Supports token refresh
* - Implements token revocation
*/
class SecureTokenManager {
// Your implementation here
}
/*
// SOLUTION:
class SecureTokenManager {
constructor(options = {}) {
this.tokens = new Map();
this.revokedTokens = new Set();
this.accessTokenTTL = options.accessTokenTTL || 900000; // 15 minutes
this.refreshTokenTTL = options.refreshTokenTTL || 604800000; // 7 days
this.tokenLength = options.tokenLength || 32;
}
// Generate secure random token
generateToken(length = this.tokenLength) {
const array = new Uint8Array(length);
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
crypto.getRandomValues(array);
} else {
const nodeCrypto = require('crypto');
const buffer = nodeCrypto.randomBytes(length);
array.set(buffer);
}
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
}
// Generate token pair (access + refresh)
createTokenPair(userId, metadata = {}) {
const accessToken = this.generateToken();
const refreshToken = this.generateToken();
const now = Date.now();
const tokenData = {
userId,
accessToken,
refreshToken,
accessTokenExpiry: now + this.accessTokenTTL,
refreshTokenExpiry: now + this.refreshTokenTTL,
createdAt: now,
metadata
};
// Store tokens
this.tokens.set(accessToken, { ...tokenData, type: 'access' });
this.tokens.set(refreshToken, { ...tokenData, type: 'refresh' });
return {
accessToken,
refreshToken,
accessTokenExpiresIn: this.accessTokenTTL,
refreshTokenExpiresIn: this.refreshTokenTTL
};
}
// Validate access token
validateAccessToken(token) {
// Check if revoked
if (this.revokedTokens.has(token)) {
return { valid: false, reason: 'Token revoked' };
}
const data = this.tokens.get(token);
if (!data) {
return { valid: false, reason: 'Token not found' };
}
if (data.type !== 'access') {
return { valid: false, reason: 'Not an access token' };
}
if (Date.now() > data.accessTokenExpiry) {
return { valid: false, reason: 'Token expired' };
}
return {
valid: true,
userId: data.userId,
metadata: data.metadata,
expiresIn: data.accessTokenExpiry - Date.now()
};
}
// Refresh tokens
refreshTokens(refreshToken) {
// Check if revoked
if (this.revokedTokens.has(refreshToken)) {
return { success: false, reason: 'Refresh token revoked' };
}
const data = this.tokens.get(refreshToken);
if (!data) {
return { success: false, reason: 'Refresh token not found' };
}
if (data.type !== 'refresh') {
return { success: false, reason: 'Not a refresh token' };
}
if (Date.now() > data.refreshTokenExpiry) {
return { success: false, reason: 'Refresh token expired' };
}
// Revoke old tokens
this.revoke(data.accessToken);
this.revoke(refreshToken);
// Create new token pair
const newTokens = this.createTokenPair(data.userId, data.metadata);
return {
success: true,
...newTokens
};
}
// Revoke token
revoke(token) {
this.revokedTokens.add(token);
this.tokens.delete(token);
return true;
}
// Revoke all tokens for user
revokeAllForUser(userId) {
let count = 0;
for (const [token, data] of this.tokens) {
if (data.userId === userId) {
this.revoke(token);
count++;
}
}
return count;
}
// Get token info
getTokenInfo(token) {
const data = this.tokens.get(token);
if (!data) return null;
return {
userId: data.userId,
type: data.type,
createdAt: new Date(data.createdAt).toISOString(),
expiresAt: new Date(
data.type === 'access' ? data.accessTokenExpiry : data.refreshTokenExpiry
).toISOString(),
isExpired: Date.now() > (data.type === 'access' ? data.accessTokenExpiry : data.refreshTokenExpiry),
isRevoked: this.revokedTokens.has(token)
};
}
// Cleanup expired tokens
cleanup() {
const now = Date.now();
let cleaned = 0;
for (const [token, data] of this.tokens) {
const expiry = data.type === 'access' ? data.accessTokenExpiry : data.refreshTokenExpiry;
if (now > expiry) {
this.tokens.delete(token);
cleaned++;
}
}
// Clean old revoked tokens (keep for 24 hours)
// In production, use a more sophisticated cleanup
return cleaned;
}
// Get stats
getStats() {
let accessTokens = 0;
let refreshTokens = 0;
let expired = 0;
const now = Date.now();
for (const data of this.tokens.values()) {
if (data.type === 'access') accessTokens++;
else refreshTokens++;
const expiry = data.type === 'access' ? data.accessTokenExpiry : data.refreshTokenExpiry;
if (now > expiry) expired++;
}
return {
totalTokens: this.tokens.size,
accessTokens,
refreshTokens,
revokedTokens: this.revokedTokens.size,
expired
};
}
}
// Test Secure Token Manager
function testSecureTokenManager() {
console.log('=== Secure Token Manager Tests ===\n');
const manager = new SecureTokenManager({
accessTokenTTL: 5000, // 5 seconds for testing
refreshTokenTTL: 10000 // 10 seconds for testing
});
// Create token pair
const tokens = manager.createTokenPair('user-123', { role: 'admin' });
console.log('Token pair created:');
console.log(' Access token:', tokens.accessToken.substring(0, 20) + '...');
console.log(' Refresh token:', tokens.refreshToken.substring(0, 20) + '...');
// Validate access token
const validation = manager.validateAccessToken(tokens.accessToken);
console.log('\nAccess token validation:', validation);
// Get token info
console.log('\nToken info:', manager.getTokenInfo(tokens.accessToken));
// Refresh tokens
const refreshed = manager.refreshTokens(tokens.refreshToken);
console.log('\nRefreshed tokens:', refreshed.success);
// Old token should be invalid
const oldValidation = manager.validateAccessToken(tokens.accessToken);
console.log('Old token valid:', oldValidation.valid);
// New token should be valid
const newValidation = manager.validateAccessToken(refreshed.accessToken);
console.log('New token valid:', newValidation.valid);
// Stats
console.log('\nStats:', manager.getStats());
console.log('\n=== Secure Token Manager Tests Complete ===\n');
}
*/
// ============================================
// RUN EXERCISES
// ============================================
console.log('=== Security Fundamentals Exercises ===\n');
console.log('Implement the following exercises:');
console.log('1. XSSFilter - Complete XSS filtering and sanitization');
console.log('2. RateLimiter - Sliding window rate limiting');
console.log('3. SecurityAuditLogger - Security event logging and alerting');
console.log('4. SecureTokenManager - Token lifecycle management');
console.log('');
console.log('Uncomment solutions to verify your implementations.');
// Export
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
XSSFilter,
RateLimiter,
SecurityAuditLogger,
SecureTokenManager,
};
}