Files
bookmarksite/tests/test_security_features.html
Rainer Koschnick 0abee5b794 Add comprehensive database setup and user management system
- Implement PostgreSQL database schema with users and bookmarks tables
- Add database connection pooling with retry logic and error handling
- Create migration system with automatic schema initialization
- Add database CLI tools for management (init, status, validate, etc.)
- Include comprehensive error handling and diagnostics
- Add development seed data and testing utilities
- Implement health monitoring and connection pool statistics
- Create detailed documentation and troubleshooting guide

Database features:
- Users table with authentication fields and email verification
- Bookmarks table with user association and metadata
- Proper indexes for performance optimization
- Automatic timestamp triggers
- Transaction support with rollback handling
- Connection pooling (20 max connections, 30s idle timeout)
- Graceful shutdown handling

CLI commands available:
- npm run db:init - Initialize database
- npm run db:status - Check database status
- npm run db:validate - Validate schema
- npm run db:test - Run database tests
- npm run db:diagnostics - Full diagnostics
2025-07-19 23:21:50 +02:00

392 lines
15 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Security Features Test</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.test-section {
margin: 20px 0;
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
}
.test-result {
margin: 10px 0;
padding: 10px;
border-radius: 3px;
}
.test-pass {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.test-fail {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
button {
margin: 5px;
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #0056b3;
}
</style>
</head>
<body>
<h1>Security Features Test</h1>
<div class="test-section">
<h2>Encryption Tests</h2>
<button onclick="testEncryption()">Test Encryption</button>
<div id="encryptionResults"></div>
</div>
<div class="test-section">
<h2>Privacy Mode Tests</h2>
<button onclick="testPrivacyMode()">Test Privacy Mode</button>
<div id="privacyResults"></div>
</div>
<div class="test-section">
<h2>Access Logging Tests</h2>
<button onclick="testAccessLogging()">Test Access Logging</button>
<div id="loggingResults"></div>
</div>
<div class="test-section">
<h2>Password Protection Tests</h2>
<button onclick="testPasswordProtection()">Test Password Protection</button>
<div id="passwordResults"></div>
</div>
<script>
// Mock BookmarkManager for testing
class MockBookmarkManager {
constructor() {
this.securitySettings = {
encryptionEnabled: false,
encryptionKey: null,
privacyMode: false,
accessLogging: true,
passwordProtection: false,
sessionTimeout: 30 * 60 * 1000,
maxLoginAttempts: 3,
lockoutDuration: 15 * 60 * 1000
};
this.accessLog = [];
this.encryptedCollections = new Set();
this.privateBookmarks = new Set();
this.securitySession = {
isAuthenticated: false,
lastActivity: Date.now(),
loginAttempts: 0,
lockedUntil: null
};
this.bookmarks = [
{ id: '1', title: 'Test Bookmark 1', url: 'https://example.com' },
{ id: '2', title: 'Test Bookmark 2', url: 'https://test.com' }
];
}
// Hash password for storage (simple implementation)
hashPassword(password) {
let hash = 0;
for (let i = 0; i < password.length; i++) {
const char = password.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return hash.toString();
}
// Simple encryption function
simpleEncrypt(text, key) {
let result = '';
for (let i = 0; i < text.length; i++) {
result += String.fromCharCode(text.charCodeAt(i) ^ key.toString().charCodeAt(i % key.toString().length));
}
return btoa(result);
}
// Simple decryption function
simpleDecrypt(encryptedText, key) {
try {
const decoded = atob(encryptedText);
let result = '';
for (let i = 0; i < decoded.length; i++) {
result += String.fromCharCode(decoded.charCodeAt(i) ^ key.toString().charCodeAt(i % key.toString().length));
}
return result;
} catch (error) {
return encryptedText;
}
}
// Encrypt bookmark data
encryptBookmark(bookmark) {
if (!this.securitySettings.encryptionEnabled || !this.securitySettings.encryptionKey) {
return bookmark;
}
try {
const key = this.securitySettings.encryptionKey;
const encryptedTitle = this.simpleEncrypt(bookmark.title, key);
const encryptedUrl = this.simpleEncrypt(bookmark.url, key);
return {
...bookmark,
title: encryptedTitle,
url: encryptedUrl,
encrypted: true
};
} catch (error) {
return bookmark;
}
}
// Decrypt bookmark data
decryptBookmark(bookmark) {
if (!bookmark.encrypted || !this.securitySettings.encryptionKey) {
return bookmark;
}
try {
const key = this.securitySettings.encryptionKey;
const decryptedTitle = this.simpleDecrypt(bookmark.title, key);
const decryptedUrl = this.simpleDecrypt(bookmark.url, key);
return {
...bookmark,
title: decryptedTitle,
url: decryptedUrl
};
} catch (error) {
return bookmark;
}
}
// Log access events
logAccess(action, details = {}) {
if (!this.securitySettings.accessLogging) return;
const logEntry = {
timestamp: Date.now(),
action: action,
details: details,
sessionId: 'test_session'
};
this.accessLog.push(logEntry);
}
// Toggle bookmark privacy
toggleBookmarkPrivacy(bookmarkId) {
if (this.privateBookmarks.has(bookmarkId)) {
this.privateBookmarks.delete(bookmarkId);
} else {
this.privateBookmarks.add(bookmarkId);
}
}
// Check if bookmark is private
isBookmarkPrivate(bookmarkId) {
return this.privateBookmarks.has(bookmarkId);
}
// Filter bookmarks for export
getExportableBookmarks(bookmarks) {
if (!this.securitySettings.privacyMode) {
return bookmarks;
}
return bookmarks.filter(bookmark => !this.isBookmarkPrivate(bookmark.id));
}
// Authenticate user
authenticateUser(password) {
const hashedPassword = this.hashPassword(password);
if (hashedPassword === this.securitySettings.encryptionKey) {
this.securitySession.isAuthenticated = true;
this.securitySession.loginAttempts = 0;
this.logAccess('successful_login');
return true;
} else {
this.securitySession.loginAttempts++;
this.logAccess('failed_login_attempt');
return false;
}
}
}
const mockManager = new MockBookmarkManager();
function displayResult(containerId, message, isPass) {
const container = document.getElementById(containerId);
const resultDiv = document.createElement('div');
resultDiv.className = `test-result ${isPass ? 'test-pass' : 'test-fail'}`;
resultDiv.textContent = message;
container.appendChild(resultDiv);
}
function testEncryption() {
const container = document.getElementById('encryptionResults');
container.innerHTML = '';
try {
// Enable encryption
mockManager.securitySettings.encryptionEnabled = true;
mockManager.securitySettings.encryptionKey = mockManager.hashPassword('testpassword123');
// Test encryption
const originalBookmark = { id: '1', title: 'Secret Bookmark', url: 'https://secret.com' };
const encryptedBookmark = mockManager.encryptBookmark(originalBookmark);
// Verify encryption worked
const titleEncrypted = encryptedBookmark.title !== originalBookmark.title;
const urlEncrypted = encryptedBookmark.url !== originalBookmark.url;
const hasEncryptedFlag = encryptedBookmark.encrypted === true;
displayResult('encryptionResults',
`Title encryption: ${titleEncrypted ? 'PASS' : 'FAIL'}`, titleEncrypted);
displayResult('encryptionResults',
`URL encryption: ${urlEncrypted ? 'PASS' : 'FAIL'}`, urlEncrypted);
displayResult('encryptionResults',
`Encrypted flag: ${hasEncryptedFlag ? 'PASS' : 'FAIL'}`, hasEncryptedFlag);
// Test decryption
const decryptedBookmark = mockManager.decryptBookmark(encryptedBookmark);
const titleDecrypted = decryptedBookmark.title === originalBookmark.title;
const urlDecrypted = decryptedBookmark.url === originalBookmark.url;
displayResult('encryptionResults',
`Title decryption: ${titleDecrypted ? 'PASS' : 'FAIL'}`, titleDecrypted);
displayResult('encryptionResults',
`URL decryption: ${urlDecrypted ? 'PASS' : 'FAIL'}`, urlDecrypted);
} catch (error) {
displayResult('encryptionResults', `Error: ${error.message}`, false);
}
}
function testPrivacyMode() {
const container = document.getElementById('privacyResults');
container.innerHTML = '';
try {
// Test privacy toggle
mockManager.toggleBookmarkPrivacy('1');
const isPrivate = mockManager.isBookmarkPrivate('1');
displayResult('privacyResults',
`Privacy toggle: ${isPrivate ? 'PASS' : 'FAIL'}`, isPrivate);
// Test privacy mode filtering
mockManager.securitySettings.privacyMode = true;
const exportableBookmarks = mockManager.getExportableBookmarks(mockManager.bookmarks);
const filteredCorrectly = exportableBookmarks.length === 1 &&
exportableBookmarks[0].id === '2';
displayResult('privacyResults',
`Privacy filtering: ${filteredCorrectly ? 'PASS' : 'FAIL'}`, filteredCorrectly);
// Test with privacy mode disabled
mockManager.securitySettings.privacyMode = false;
const allBookmarks = mockManager.getExportableBookmarks(mockManager.bookmarks);
const noFiltering = allBookmarks.length === 2;
displayResult('privacyResults',
`No filtering when disabled: ${noFiltering ? 'PASS' : 'FAIL'}`, noFiltering);
} catch (error) {
displayResult('privacyResults', `Error: ${error.message}`, false);
}
}
function testAccessLogging() {
const container = document.getElementById('loggingResults');
container.innerHTML = '';
try {
// Clear existing logs
mockManager.accessLog = [];
// Test logging enabled
mockManager.securitySettings.accessLogging = true;
mockManager.logAccess('test_action', { detail: 'test' });
const logCreated = mockManager.accessLog.length === 1;
displayResult('loggingResults',
`Log entry created: ${logCreated ? 'PASS' : 'FAIL'}`, logCreated);
const logHasCorrectAction = mockManager.accessLog[0].action === 'test_action';
displayResult('loggingResults',
`Log has correct action: ${logHasCorrectAction ? 'PASS' : 'FAIL'}`, logHasCorrectAction);
// Test logging disabled
mockManager.securitySettings.accessLogging = false;
mockManager.logAccess('should_not_log');
const noNewLog = mockManager.accessLog.length === 1;
displayResult('loggingResults',
`No logging when disabled: ${noNewLog ? 'PASS' : 'FAIL'}`, noNewLog);
} catch (error) {
displayResult('loggingResults', `Error: ${error.message}`, false);
}
}
function testPasswordProtection() {
const container = document.getElementById('passwordResults');
container.innerHTML = '';
try {
// Set up password protection
const testPassword = 'securepassword123';
mockManager.securitySettings.encryptionKey = mockManager.hashPassword(testPassword);
mockManager.securitySession.isAuthenticated = false;
// Test correct password
const correctAuth = mockManager.authenticateUser(testPassword);
displayResult('passwordResults',
`Correct password authentication: ${correctAuth ? 'PASS' : 'FAIL'}`, correctAuth);
const isAuthenticated = mockManager.securitySession.isAuthenticated;
displayResult('passwordResults',
`Session authenticated: ${isAuthenticated ? 'PASS' : 'FAIL'}`, isAuthenticated);
// Reset for wrong password test
mockManager.securitySession.isAuthenticated = false;
mockManager.securitySession.loginAttempts = 0;
// Test wrong password
const wrongAuth = mockManager.authenticateUser('wrongpassword');
displayResult('passwordResults',
`Wrong password rejected: ${!wrongAuth ? 'PASS' : 'FAIL'}`, !wrongAuth);
const attemptsIncremented = mockManager.securitySession.loginAttempts === 1;
displayResult('passwordResults',
`Login attempts incremented: ${attemptsIncremented ? 'PASS' : 'FAIL'}`, attemptsIncremented);
} catch (error) {
displayResult('passwordResults', `Error: ${error.message}`, false);
}
}
</script>
</body>
</html>