575 lines
22 KiB
HTML
575 lines
22 KiB
HTML
{{template "base.html" .}}
|
|
|
|
{{define "title"}}Password Generator - HeaderAnalyzer{{end}}
|
|
|
|
{{define "content"}}
|
|
<div class="password-generator">
|
|
<h1>🔐 Password Generator</h1>
|
|
|
|
<!-- Hidden CSRF token for API calls -->
|
|
<input type="hidden" id="csrfToken" value="{{.CSRFToken}}">
|
|
|
|
<div class="tab-buttons">
|
|
<button class="tab-btn" id="randomTab">Random Password</button>
|
|
<button class="tab-btn active" id="passphraseTab">Passphrase</button>
|
|
</div>
|
|
|
|
<div class="password-output">
|
|
<div class="password-text" id="passwordDisplay">Click "Generate Password" to create a secure password</div>
|
|
<button class="copy-btn" id="copyBtn" onclick="copyPassword()" style="display: none;">Copy to Clipboard</button>
|
|
</div>
|
|
|
|
<button class="generate-btn" onclick="generatePassword()">🎲 Generate Password</button>
|
|
|
|
<div class="controls">
|
|
<div class="control-group">
|
|
<h3>🔧 Basic Settings</h3>
|
|
|
|
<div class="form-row">
|
|
<label for="length">Password Length:</label>
|
|
<input type="number" id="length" min="4" max="128" value="{{.Config.Length}}" onchange="updateURL()">
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<label for="includeUpper">Include Uppercase (A-Z):</label>
|
|
<input type="checkbox" id="includeUpper" {{if .Config.IncludeUpper}}checked{{end}} onchange="updateURL()">
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<label for="includeLower">Include Lowercase (a-z):</label>
|
|
<input type="checkbox" id="includeLower" {{if .Config.IncludeLower}}checked{{end}} onchange="updateURL()">
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<label for="numberCount">Number of Digits:</label>
|
|
<input type="number" id="numberCount" min="0" max="20" value="{{.Config.NumberCount}}" onchange="updateURL()">
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<label for="specialChars">Special Characters:</label>
|
|
<input type="text" id="specialChars" value="{{.Config.SpecialChars}}" onchange="updateURL()">
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<label for="noConsecutive">No consecutive identical characters:</label>
|
|
<input type="checkbox" id="noConsecutive" {{if .Config.NoConsecutive}}checked{{end}} onchange="updateURL()">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="control-group">
|
|
<h3>🎯 Advanced Settings</h3>
|
|
|
|
<div class="passphrase-controls {{if eq .Config.Type "passphrase"}}active{{end}}" id="passphraseControls">
|
|
<div class="form-row">
|
|
<label for="wordCount">Number of Words:</label>
|
|
<input type="number" id="wordCount" min="2" max="10" value="{{.Config.WordCount}}" onchange="updateURL()">
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<label for="passphraseUseNumbers">Include Numbers:</label>
|
|
<input type="checkbox" id="passphraseUseNumbers" {{if .Config.UseNumbers}}checked{{end}} onchange="updateURL()">
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<label for="passphraseUseSpecial">Include Special Characters:</label>
|
|
<input type="checkbox" id="passphraseUseSpecial" {{if .Config.UseSpecial}}checked{{end}} onchange="updateURL()">
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<label for="numberPosition">Number Position:</label>
|
|
<select id="numberPosition" onchange="updateURL()">
|
|
<option value="end" {{if eq .Config.NumberPosition "end"}}selected{{end}}>At End</option>
|
|
<option value="start" {{if eq .Config.NumberPosition "start"}}selected{{end}}>At Start</option>
|
|
<option value="each" {{if eq .Config.NumberPosition "each"}}selected{{end}}>After Each Word</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<label>Strength Indicator:</label>
|
|
<div id="strengthIndicator" style="color: #999;">Generate a password to see strength</div>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<label>Word List Status:</label>
|
|
<div id="wordListStatus" style="color: #999; font-size: 12px;">Loading...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Settings Management -->
|
|
<div class="control-group" style="margin-top: 20px;">
|
|
<h3>💾 Settings & History</h3>
|
|
<div class="form-row">
|
|
<button onclick="saveSettings()" style="background: #007acc; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; margin-right: 10px;">Save Current Settings</button>
|
|
<button onclick="loadSettingsManual()" style="background: #6c757d; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; margin-right: 10px;">Load Saved Settings</button>
|
|
<button onclick="clearSettings()" style="background: #dc3545; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer;">Clear Saved Settings</button>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<label for="savePasswords">Save Generated Passwords (in web browser cookies only):</label>
|
|
<input type="checkbox" id="savePasswords" {{if .Config.SavePasswords}}checked{{end}} onchange="togglePasswordSaving(); updateURL()">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Password History -->
|
|
<div class="control-group" style="margin-top: 20px;">
|
|
<h3>📚 Password History</h3>
|
|
<div id="passwordHistory" style="max-height: 200px; overflow-y: auto; background: #1a1a1a; border: 1px solid #333; border-radius: 4px; padding: 10px;">
|
|
<p style="color: #999; font-style: italic;">No passwords generated yet</p>
|
|
</div>
|
|
<div class="form-row" style="margin-top: 10px;">
|
|
<button onclick="clearHistory()" style="background: #dc3545; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer;">Clear History</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
|
|
{{define "scripts"}}
|
|
<script>
|
|
let currentMode = 'passphrase'; // Default to passphrase
|
|
|
|
// Initialize the interface based on saved settings or URL parameters
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Load settings first (from URL parameters or cookies)
|
|
loadSettings();
|
|
|
|
// Update URL to reflect current state
|
|
updateURL();
|
|
|
|
// Load password history
|
|
loadPasswordHistory();
|
|
|
|
// Load word list info
|
|
loadWordListInfo();
|
|
|
|
// Auto-generate if URL has parameters (excluding default)
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
if (urlParams.toString()) {
|
|
generatePassword();
|
|
}
|
|
|
|
// Add event listeners to automatically save settings on change
|
|
document.querySelectorAll('input, select').forEach(element => {
|
|
element.addEventListener('change', function() {
|
|
updateURL();
|
|
saveSettings();
|
|
});
|
|
});
|
|
});
|
|
|
|
// Tab switching
|
|
function switchTab(mode) {
|
|
currentMode = mode;
|
|
|
|
document.getElementById('randomTab').classList.toggle('active', mode === 'random');
|
|
document.getElementById('passphraseTab').classList.toggle('active', mode === 'passphrase');
|
|
document.getElementById('passphraseControls').classList.toggle('active', mode === 'passphrase');
|
|
|
|
updateURL();
|
|
saveSettings();
|
|
}
|
|
|
|
document.getElementById('randomTab').addEventListener('click', () => switchTab('random'));
|
|
document.getElementById('passphraseTab').addEventListener('click', () => switchTab('passphrase'));
|
|
|
|
// URL parameter management
|
|
function updateURL() {
|
|
const config = getCurrentConfig();
|
|
const params = new URLSearchParams();
|
|
|
|
// Define default values
|
|
const defaults = {
|
|
type: "passphrase",
|
|
length: 12,
|
|
includeUpper: true,
|
|
includeLower: true,
|
|
numberCount: 1,
|
|
specialChars: "!@#$%^&*-_=+",
|
|
noConsecutive: false,
|
|
wordCount: 3,
|
|
useNumbers: true,
|
|
useSpecial: false,
|
|
numberPosition: "end",
|
|
savePasswords: false
|
|
};
|
|
|
|
// Only add parameters that differ from defaults
|
|
Object.keys(config).forEach(key => {
|
|
if (config[key] !== defaults[key]) {
|
|
params.set(key, config[key]);
|
|
}
|
|
});
|
|
|
|
// Update the URL without causing a page reload
|
|
const queryString = params.toString();
|
|
const newURL = queryString ? window.location.pathname + '?' + queryString : window.location.pathname;
|
|
window.history.replaceState({}, '', newURL);
|
|
}
|
|
|
|
function getCurrentConfig() {
|
|
return {
|
|
type: currentMode,
|
|
length: parseInt(document.getElementById('length').value),
|
|
includeUpper: document.getElementById('includeUpper').checked,
|
|
includeLower: document.getElementById('includeLower').checked,
|
|
numberCount: parseInt(document.getElementById('numberCount').value),
|
|
specialChars: document.getElementById('specialChars').value,
|
|
noConsecutive: document.getElementById('noConsecutive').checked,
|
|
wordCount: parseInt(document.getElementById('wordCount').value),
|
|
useNumbers: document.getElementById('passphraseUseNumbers').checked,
|
|
useSpecial: document.getElementById('passphraseUseSpecial').checked,
|
|
numberPosition: document.getElementById('numberPosition').value,
|
|
savePasswords: document.getElementById('savePasswords').checked
|
|
};
|
|
}
|
|
|
|
// Cookie management
|
|
function saveSettings() {
|
|
const config = getCurrentConfig();
|
|
config.mode = currentMode;
|
|
const settings = JSON.stringify(config);
|
|
|
|
// Set cookie to expire in 1 year
|
|
const expiryDate = new Date();
|
|
expiryDate.setFullYear(expiryDate.getFullYear() + 1);
|
|
|
|
document.cookie = `passwordGenSettings=${encodeURIComponent(settings)}; expires=${expiryDate.toUTCString()}; path=/`;
|
|
|
|
showNotification('Settings saved! They will be remembered when you visit this page again.', 'success');
|
|
}
|
|
|
|
function loadSettings() {
|
|
// First try URL parameters
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
if (urlParams.toString()) {
|
|
const config = {};
|
|
for (const [key, value] of urlParams) {
|
|
if (key === 'type') config[key] = value;
|
|
else if (key === 'length' || key === 'numberCount' || key === 'wordCount') config[key] = parseInt(value);
|
|
else if (key === 'includeUpper' || key === 'includeLower' || key === 'noConsecutive' ||
|
|
key === 'useNumbers' || key === 'useSpecial' || key === 'savePasswords') config[key] = value === 'true';
|
|
else config[key] = value;
|
|
}
|
|
applyConfig(config);
|
|
return;
|
|
}
|
|
|
|
// Then try cookies
|
|
const settings = getCookie('passwordGenSettings');
|
|
if (settings) {
|
|
try {
|
|
const config = JSON.parse(decodeURIComponent(settings));
|
|
applyConfig(config);
|
|
} catch (e) {
|
|
console.error('Failed to parse saved settings:', e);
|
|
}
|
|
}
|
|
}
|
|
|
|
function loadSettingsManual() {
|
|
const settings = getCookie('passwordGenSettings');
|
|
if (settings) {
|
|
try {
|
|
const config = JSON.parse(decodeURIComponent(settings));
|
|
applyConfig(config);
|
|
updateURL();
|
|
showNotification('Settings loaded successfully!', 'success');
|
|
} catch (e) {
|
|
showNotification('Error loading settings: ' + e.message, 'error');
|
|
}
|
|
} else {
|
|
showNotification('No saved settings found.', 'warning');
|
|
}
|
|
}
|
|
|
|
function clearSettings() {
|
|
document.cookie = 'passwordGenSettings=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
|
|
showNotification('Saved settings cleared.', 'info');
|
|
}
|
|
|
|
function applyConfig(config) {
|
|
// Apply the configuration to form controls
|
|
currentMode = config.type || config.mode || 'passphrase';
|
|
|
|
document.getElementById('length').value = config.length || 12;
|
|
document.getElementById('includeUpper').checked = config.includeUpper !== false;
|
|
document.getElementById('includeLower').checked = config.includeLower !== false;
|
|
document.getElementById('numberCount').value = config.numberCount || 1;
|
|
document.getElementById('specialChars').value = config.specialChars || "!@#$%^&*-_=+";
|
|
document.getElementById('noConsecutive').checked = config.noConsecutive || false;
|
|
document.getElementById('wordCount').value = config.wordCount || 3;
|
|
document.getElementById('passphraseUseNumbers').checked = config.useNumbers !== false;
|
|
document.getElementById('passphraseUseSpecial').checked = config.useSpecial || false;
|
|
document.getElementById('numberPosition').value = config.numberPosition || "end";
|
|
document.getElementById('savePasswords').checked = config.savePasswords || false;
|
|
|
|
// Update tab state
|
|
if (currentMode === 'passphrase') {
|
|
document.getElementById('passphraseTab').classList.add('active');
|
|
document.getElementById('randomTab').classList.remove('active');
|
|
document.getElementById('passphraseControls').classList.add('active');
|
|
} else {
|
|
document.getElementById('randomTab').classList.add('active');
|
|
document.getElementById('passphraseTab').classList.remove('active');
|
|
document.getElementById('passphraseControls').classList.remove('active');
|
|
}
|
|
}
|
|
|
|
// Password history management
|
|
function addToHistory(password) {
|
|
// Check if password saving is enabled
|
|
const savePasswords = document.getElementById('savePasswords').checked;
|
|
if (!savePasswords) {
|
|
return; // Don't save if checkbox is unchecked
|
|
}
|
|
|
|
let history = getPasswordHistory();
|
|
const timestamp = new Date().toLocaleString();
|
|
const entry = { password, timestamp, type: currentMode };
|
|
|
|
// Add to beginning of array
|
|
history.unshift(entry);
|
|
|
|
// Keep only last 20 passwords
|
|
if (history.length > 20) {
|
|
history = history.slice(0, 20);
|
|
}
|
|
|
|
// Save to cookie
|
|
const historyData = JSON.stringify(history);
|
|
const expiryDate = new Date();
|
|
expiryDate.setMonth(expiryDate.getMonth() + 3); // 3 months
|
|
|
|
document.cookie = `passwordHistory=${encodeURIComponent(historyData)}; expires=${expiryDate.toUTCString()}; path=/`;
|
|
|
|
// Update display
|
|
displayPasswordHistory(history);
|
|
}
|
|
|
|
function getPasswordHistory() {
|
|
const historyData = getCookie('passwordHistory');
|
|
if (historyData) {
|
|
try {
|
|
return JSON.parse(decodeURIComponent(historyData));
|
|
} catch (e) {
|
|
return [];
|
|
}
|
|
}
|
|
return [];
|
|
}
|
|
|
|
function loadPasswordHistory() {
|
|
const savePasswords = document.getElementById('savePasswords').checked;
|
|
|
|
if (savePasswords) {
|
|
const history = getPasswordHistory();
|
|
displayPasswordHistory(history);
|
|
} else {
|
|
const historyDiv = document.getElementById('passwordHistory');
|
|
historyDiv.innerHTML = '<p style="color: #999; font-style: italic;">Password saving is disabled</p>';
|
|
}
|
|
}
|
|
|
|
function togglePasswordSaving() {
|
|
const savePasswords = document.getElementById('savePasswords').checked;
|
|
const historyDiv = document.getElementById('passwordHistory');
|
|
|
|
if (savePasswords) {
|
|
// Re-display existing history
|
|
const history = getPasswordHistory();
|
|
displayPasswordHistory(history);
|
|
showNotification('Password saving enabled', 'success');
|
|
} else {
|
|
// Clear stored passwords and hide history display
|
|
document.cookie = 'passwordHistory=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
|
|
historyDiv.innerHTML = '<p style="color: #999; font-style: italic;">Password saving is disabled</p>';
|
|
showNotification('Password saving disabled - history cleared', 'info');
|
|
}
|
|
|
|
// Auto-save the setting change
|
|
saveSettings();
|
|
}
|
|
|
|
function displayPasswordHistory(history) {
|
|
const historyDiv = document.getElementById('passwordHistory');
|
|
|
|
if (history.length === 0) {
|
|
historyDiv.innerHTML = '<p style="color: #999; font-style: italic;">No passwords generated yet</p>';
|
|
return;
|
|
}
|
|
|
|
let html = '';
|
|
history.forEach((entry, index) => {
|
|
const shortPassword = entry.password.length > 30 ? entry.password.substring(0, 30) + '...' : entry.password;
|
|
html += `
|
|
<div style="margin-bottom: 10px; padding: 8px; background: #2a2a2a; border-radius: 4px; display: flex; align-items: center; justify-content: space-between;">
|
|
<div style="flex: 1;">
|
|
<div style="font-family: monospace; color: #00ff88; margin-bottom: 2px;">${shortPassword}</div>
|
|
<div style="font-size: 12px; color: #999;">${entry.type} • ${entry.timestamp}</div>
|
|
</div>
|
|
<button onclick="copyHistoryPassword('${entry.password.replace(/'/g, "\\'")}', ${index})"
|
|
style="background: #007acc; color: white; border: none; padding: 4px 8px; border-radius: 3px; cursor: pointer; margin-left: 10px;">
|
|
Copy
|
|
</button>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
historyDiv.innerHTML = html;
|
|
}
|
|
|
|
function copyHistoryPassword(password, index) {
|
|
navigator.clipboard.writeText(password).then(function() {
|
|
// Temporarily change button text
|
|
const buttons = document.querySelectorAll('#passwordHistory button');
|
|
if (buttons[index]) {
|
|
const originalText = buttons[index].textContent;
|
|
buttons[index].textContent = 'Copied!';
|
|
buttons[index].style.background = '#00aa44';
|
|
setTimeout(function() {
|
|
buttons[index].textContent = originalText;
|
|
buttons[index].style.background = '#007acc';
|
|
}, 1500);
|
|
}
|
|
});
|
|
}
|
|
|
|
function clearHistory() {
|
|
if (confirm('Are you sure you want to clear all password history?')) {
|
|
document.cookie = 'passwordHistory=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
|
|
loadPasswordHistory();
|
|
alert('Password history cleared.');
|
|
}
|
|
}
|
|
|
|
// Utility function to get cookie value
|
|
function getCookie(name) {
|
|
const value = `; ${document.cookie}`;
|
|
const parts = value.split(`; ${name}=`);
|
|
if (parts.length === 2) return parts.pop().split(';').shift();
|
|
return null;
|
|
}
|
|
|
|
// Load word list info
|
|
function loadWordListInfo() {
|
|
fetch('/api/password/info')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
document.getElementById('wordListStatus').innerHTML =
|
|
`${data.wordCount} words loaded<br>Source: ${data.source}<br>Updated: ${data.lastUpdate}`;
|
|
})
|
|
.catch(error => {
|
|
document.getElementById('wordListStatus').textContent = 'Error loading word list info';
|
|
});
|
|
}
|
|
|
|
function generatePassword() {
|
|
const config = getCurrentConfig();
|
|
|
|
fetch('/api/password', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(config)
|
|
})
|
|
.then(response => response.text())
|
|
.then(password => {
|
|
document.getElementById('passwordDisplay').textContent = password;
|
|
document.getElementById('copyBtn').style.display = 'inline-block';
|
|
|
|
// Calculate and display strength
|
|
const strength = calculatePasswordStrength(password);
|
|
document.getElementById('strengthIndicator').innerHTML = strength;
|
|
|
|
// Add to history
|
|
addToHistory(password);
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
document.getElementById('passwordDisplay').textContent = 'Error generating password: ' + error.message;
|
|
});
|
|
}
|
|
|
|
function copyPassword() {
|
|
const password = document.getElementById('passwordDisplay').textContent;
|
|
if (password && password !== 'Click "Generate Password" to create a secure password') {
|
|
navigator.clipboard.writeText(password).then(function() {
|
|
const btn = document.getElementById('copyBtn');
|
|
btn.textContent = 'Copied!';
|
|
btn.classList.add('copied');
|
|
setTimeout(function() {
|
|
btn.textContent = 'Copy to Clipboard';
|
|
btn.classList.remove('copied');
|
|
}, 2000);
|
|
}, function(err) {
|
|
console.error('Could not copy text: ', err);
|
|
});
|
|
}
|
|
}
|
|
|
|
function calculatePasswordStrength(password) {
|
|
let score = 0;
|
|
let feedback = [];
|
|
|
|
// Length scoring
|
|
if (password.length >= 12) score += 25;
|
|
else if (password.length >= 8) score += 15;
|
|
else if (password.length >= 6) score += 10;
|
|
else feedback.push("Too short (< 6 chars)");
|
|
|
|
// Character variety
|
|
if (/[a-z]/.test(password)) score += 15;
|
|
if (/[A-Z]/.test(password)) score += 15;
|
|
if (/[0-9]/.test(password)) score += 15;
|
|
if (/[^A-Za-z0-9]/.test(password)) score += 15;
|
|
|
|
// Complexity patterns
|
|
if (password.length > 8 && /(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[^A-Za-z0-9])/.test(password)) score += 15;
|
|
|
|
let strength = "Very Weak";
|
|
let color = "#ff4444";
|
|
|
|
if (score >= 80) { strength = "Very Strong"; color = "#00ff88"; }
|
|
else if (score >= 60) { strength = "Strong"; color = "#88ff00"; }
|
|
else if (score >= 40) { strength = "Moderate"; color = "#ffaa00"; }
|
|
else if (score >= 20) { strength = "Weak"; color = "#ff8800"; }
|
|
|
|
return `<span style="color: ${color}; font-weight: bold;">${strength}</span> (${score}/100)`;
|
|
}
|
|
|
|
// Notification system
|
|
function showNotification(message, type = 'info') {
|
|
// Remove any existing notifications
|
|
const existingNotifications = document.querySelectorAll('.notification');
|
|
existingNotifications.forEach(notification => notification.remove());
|
|
|
|
// Create new notification
|
|
const notification = document.createElement('div');
|
|
notification.className = `notification ${type}`;
|
|
notification.textContent = message;
|
|
|
|
// Add to page
|
|
document.body.appendChild(notification);
|
|
|
|
// Show with animation
|
|
setTimeout(() => {
|
|
notification.classList.add('show');
|
|
}, 10);
|
|
|
|
// Auto-remove after 5 seconds
|
|
setTimeout(() => {
|
|
notification.classList.remove('show');
|
|
setTimeout(() => {
|
|
if (notification.parentNode) {
|
|
notification.remove();
|
|
}
|
|
}, 300);
|
|
}, 5000);
|
|
}
|
|
</script>
|
|
{{end}}
|