pranit144's picture
Upload 23 files
f88a7bc verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Add Password - Secure Password Manager</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
:root {
--primary-color: #2563eb;
--primary-hover: #1d4ed8;
--success-color: #10b981;
--warning-color: #f59e0b;
--danger-color: #ef4444;
--text-primary: #1f2937;
--text-secondary: #4b5563;
--bg-card: #ffffff;
--border-color: #e5e7eb;
--transition: all 0.3s ease;
}
body {
font-family: 'Inter', sans-serif;
line-height: 1.6;
color: var(--text-primary);
background: #f3f4f6;
margin: 0;
padding: 0;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
header {
background: var(--bg-card);
border-radius: 1rem;
padding: 1.5rem;
margin-bottom: 2rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.header-content {
width: 100%;
}
.header-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.header-controls {
display: flex;
align-items: center;
gap: 1rem;
}
h1 {
font-size: 2rem;
font-weight: 700;
color: var(--text-primary);
margin: 0;
}
nav ul {
display: flex;
gap: 1rem;
padding: 0;
margin: 1rem 0 0;
list-style: none;
}
nav a {
color: var(--text-secondary);
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
transition: var(--transition);
}
nav a:hover, nav a.active {
color: var(--primary-color);
background: #f0f9ff;
}
.card {
background: var(--bg-card);
border-radius: 1rem;
padding: 2rem;
margin-bottom: 2rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
transition: var(--transition);
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
}
.form-group {
margin-bottom: 1.5rem;
}
label {
display: block;
color: var(--text-primary);
font-weight: 500;
margin-bottom: 0.5rem;
}
input[type="text"],
input[type="password"] {
width: 100%;
padding: 0.75rem 1rem;
border: 2px solid var(--border-color);
border-radius: 0.5rem;
font-size: 1rem;
transition: var(--transition);
}
input[type="text"]:focus,
input[type="password"]:focus {
border-color: var(--primary-color);
outline: none;
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
}
.password-input-group {
position: relative;
display: flex;
align-items: center;
}
.toggle-button {
position: absolute;
right: 7.5rem;
padding: 0.5rem;
background: none;
border: none;
color: var(--text-secondary);
cursor: pointer;
font-size: 0.875rem;
}
.generate-button {
position: absolute;
right: 0.5rem;
padding: 0.5rem 1rem;
background: none;
border: 1px solid var(--border-color);
border-radius: 0.5rem;
color: var(--text-secondary);
cursor: pointer;
font-size: 0.875rem;
transition: var(--transition);
}
.generate-button:hover {
border-color: var(--primary-color);
color: var(--primary-color);
}
.password-strength-area {
margin-top: 1rem;
padding: 1rem;
background: #f8fafc;
border-radius: 0.5rem;
}
.strength-meter-container {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 1rem;
}
.strength-label {
color: var(--text-secondary);
font-size: 0.875rem;
min-width: 4rem;
}
.strength-bar {
flex-grow: 1;
height: 0.5rem;
background: #e5e7eb;
border-radius: 1rem;
overflow: hidden;
}
.strength-indicator {
height: 100%;
width: 0;
transition: width 0.3s ease, background-color 0.3s ease;
}
.strength-indicator.very-weak { background: var(--danger-color); }
.strength-indicator.weak { background: #f97316; }
.strength-indicator.medium { background: var(--warning-color); }
.strength-indicator.strong { background: #84cc16; }
.strength-indicator.very-strong { background: var(--success-color); }
.generator-options {
margin-top: 1.5rem;
padding: 1.5rem;
background: #f8fafc;
border-radius: 0.5rem;
border: 1px solid var(--border-color);
}
.generator-options h4 {
margin: 0 0 1rem;
color: var(--text-primary);
}
.length-control {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 1rem;
}
.length-control input[type="range"] {
flex-grow: 1;
}
.length-display {
min-width: 2.5rem;
text-align: center;
font-variant-numeric: tabular-nums;
}
.char-options {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
.option-group {
display: flex;
align-items: center;
gap: 0.5rem;
}
.option-group input[type="checkbox"] {
width: 1rem;
height: 1rem;
border-radius: 0.25rem;
border: 2px solid var(--border-color);
cursor: pointer;
}
.option-group label {
margin: 0;
cursor: pointer;
font-size: 0.875rem;
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.75rem 1.5rem;
background: var(--primary-color);
color: white;
border: none;
border-radius: 0.5rem;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: var(--transition);
}
.btn:hover {
background: var(--primary-hover);
transform: translateY(-1px);
}
.message {
margin-top: 1rem;
padding: 1rem;
border-radius: 0.5rem;
font-size: 0.875rem;
}
.message.success {
background: #dcfce7;
color: #166534;
}
.message.error {
background: #fee2e2;
color: #991b1b;
}
.breach-status-area {
margin-top: 1rem;
padding: 0.75rem;
border-radius: 0.5rem;
background: #f8fafc;
display: flex;
align-items: center;
gap: 0.5rem;
}
.breach-label {
color: var(--text-secondary);
font-size: 0.875rem;
}
.breach-indicator {
padding: 0.25rem 0.75rem;
border-radius: 1rem;
font-size: 0.875rem;
font-weight: 500;
}
.breach-indicator.safe {
background: #dcfce7;
color: #166534;
}
.breach-indicator.pwned {
background: #fee2e2;
color: #991b1b;
}
.breach-indicator.checking {
background: #dbeafe;
color: #1e40af;
}
#theme-toggle {
padding: 0.5rem;
background: none;
border: 1px solid var(--border-color);
border-radius: 0.5rem;
cursor: pointer;
transition: var(--transition);
}
#theme-toggle:hover {
border-color: var(--primary-color);
}
.logout-link {
color: var(--text-secondary);
text-decoration: none;
font-size: 0.875rem;
transition: var(--transition);
display: flex;
align-items: center;
gap: 0.5rem;
}
.logout-link:hover {
color: var(--primary-color);
}
footer {
text-align: center;
padding: 2rem;
color: var(--text-secondary);
font-size: 0.875rem;
}
@media (max-width: 768px) {
.container {
padding: 1rem;
}
.card {
padding: 1.5rem;
}
.header-top {
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
nav ul {
flex-direction: column;
}
nav a {
display: block;
}
.char-options {
grid-template-columns: 1fr;
}
.password-input-group {
flex-direction: column;
align-items: stretch;
}
.toggle-button,
.generate-button {
position: static;
margin-top: 0.5rem;
}
}
@media (prefers-color-scheme: dark) {
:root {
--bg-card: #1f2937;
--text-primary: #f3f4f6;
--text-secondary: #9ca3af;
--border-color: #374151;
}
body {
background: #111827;
}
input[type="text"],
input[type="password"] {
background: #374151;
color: #f3f4f6;
}
.password-strength-area,
.generator-options,
.breach-status-area {
background: #374151;
}
.card {
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.2);
}
.message.success {
background: #064e3b;
color: #6ee7b7;
}
.message.error {
background: #7f1d1d;
color: #fecaca;
}
.breach-indicator.safe {
background: #064e3b;
color: #6ee7b7;
}
.breach-indicator.pwned {
background: #7f1d1d;
color: #fecaca;
}
.breach-indicator.checking {
background: #1e3a8a;
color: #93c5fd;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<div class="header-content">
<div class="header-top">
<h1>Add New Password</h1>
<div class="header-controls">
<button id="theme-toggle" title="Toggle light/dark theme">
<span class="icon-sun">☀️</span>
<span class="icon-moon" style="display:none;">🌙</span>
</button>
{% if current_user.is_authenticated %}
<a href="{{ url_for('logout') }}" class="logout-link">
<span class="user-email">{{ current_user.email }}</span>
<span class="logout-text">Logout</span>
</a>
{% endif %}
</div>
</div>
<nav>
<ul>
<li><a href="{{ url_for('add_password_page') }}" class="active">Add Password</a></li>
<li><a href="{{ url_for('storage') }}">View Passwords</a></li>
<li><a href="{{ url_for('analyse') }}">Analyse Passwords</a></li>
</ul>
</nav>
</div>
</header>
<main>
<section class="card">
<h2>Add New Credential</h2>
<form id="password-form">
<div class="form-group">
<label for="service">Service/Website</label>
<input type="text" id="service" name="service" required placeholder="Enter service or website name">
</div>
<div class="form-group">
<label for="username">Username/Email</label>
<input type="text" id="username" name="username" required placeholder="Enter username or email">
</div>
<div class="form-group">
<label for="password">Password</label>
<div class="password-input-group">
<input type="password" id="password" name="password" required autocomplete="new-password" placeholder="Enter password">
<button type="button" id="toggle-password" class="toggle-button" title="Show/hide password">Show</button>
<button type="button" id="generate-password-btn" class="generate-button">Generate</button>
</div>
</div>
<div id="password-strength-area" class="password-strength-area" style="display: none;">
<div class="strength-meter-container">
<span class="strength-label">Strength:</span>
<div class="strength-bar">
<div id="strength-indicator" class="strength-indicator"></div>
</div>
</div>
<div id="password-strength-feedback"></div>
<div id="password-breach-status" class="breach-status-area" style="display: none;">
<span class="breach-label">Breach Check:</span>
<span id="breach-status-indicator" class="breach-indicator checking">Checking...</span>
</div>
</div>
<div id="generator-options" class="generator-options" style="display: none;">
<h4>Password Generator Options</h4>
<div class="length-control">
<label for="gen-length">Length:</label>
<input type="range" id="gen-length" name="gen-length" min="8" max="64" value="16">
<span class="length-display" id="gen-length-value">16</span>
</div>
<div class="char-options">
<div class="option-group">
<input type="checkbox" id="gen-lowercase" name="gen-lowercase" checked>
<label for="gen-lowercase">Lowercase (a-z)</label>
</div>
<div class="option-group">
<input type="checkbox" id="gen-uppercase" name="gen-uppercase" checked>
<label for="gen-uppercase">Uppercase (A-Z)</label>
</div>
<div class="option-group">
<input type="checkbox" id="gen-digits" name="gen-digits" checked>
<label for="gen-digits">Digits (0-9)</label>
</div>
<div class="option-group">
<input type="checkbox" id="gen-symbols" name="gen-symbols" checked>
<label for="gen-symbols">Symbols (!@#...)</label>
</div>
</div>
</div>
<div class="form-group">
<button type="submit" class="btn">Encrypt & Save</button>
</div>
</form>
<div id="message" class="message"></div>
</section>
</main>
<footer>
<p>Secure Password Manager - End-to-End Encrypted Password Management</p>
</footer>
</div>
<script src="{{ url_for('static', filename='js/zxcvbn.js') }}"></script>
<script src="{{ url_for('static', filename='js/crypto-helpers.js') }}"></script>
<script>
// --- E2EE Helper Functions (Encryption/Decryption - Keep for saving) ---
function base64UrlDecode(b64url){let b64=b64url.replace(/-/g,'+').replace(/_/g,'/');while(b64.length%4){b64+='=';}return base64ToArrayBuffer(b64);}
// Ensure base64ToArrayBuffer and arrayBufferToBase64 are loaded from crypto-helpers.js
// async function getEncryptionKeyRawBytes(){ ... } // Keep this function if needed elsewhere, but primarily use sessionStorage key
async function getEncryptionKeyRawBytesFromSession(){ const k=sessionStorage.getItem('encryptionKey'); if(!k){console.error("Key missing.");alert("Key missing. Login.");window.location.href="{{ url_for('login') }}"; return null;} try{ const rawKey = base64UrlDecode(k); console.log("Key from session (B64URL Decoded):", arrayBufferToBase64(rawKey)); return rawKey; } catch(e){console.error("Key decode fail:",e);alert("Invalid key. Login.");window.location.href="{{ url_for('login') }}";return null;}}
// Ensure encryptData is loaded from crypto-helpers.js
// --- Theme Toggler ---
const themeToggleBtn = document.getElementById('theme-toggle');
const sunIcon = themeToggleBtn?.querySelector('.icon-sun');
const moonIcon = themeToggleBtn?.querySelector('.icon-moon');
const currentTheme = localStorage.getItem('theme') || 'light'; // Default to light
function applyTheme(theme) {
document.body.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
if (sunIcon && moonIcon) {
if (theme === 'dark') {
sunIcon.style.display = 'none';
moonIcon.style.display = 'inline';
} else {
sunIcon.style.display = 'inline';
moonIcon.style.display = 'none';
}
}
}
applyTheme(currentTheme); // Apply initial theme on load
if (themeToggleBtn) {
themeToggleBtn.addEventListener('click', () => {
const newTheme = document.body.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
applyTheme(newTheme);
});
}
// --- Strength & Breach Meter Helpers ---
function getStrengthClassFromScore(score) {
const classes = ['very-weak', 'weak', 'medium', 'strong', 'very-strong'];
return classes[score] || 'very-weak';
}
function formatBackendFeedback(feedbackArray) {
let html = '<ul>';
if (!feedbackArray || feedbackArray.length === 0) {
html += '<li class="suggestion">Analysis complete.</li>';
} else {
feedbackArray.forEach(fb => {
let itemClass = 'suggestion'; // Default
if (fb.toLowerCase().includes('warning:') || fb.toLowerCase().includes('issue:')) {
itemClass = 'warning';
}
html += `<li class="${itemClass}">${escapeHtml(fb)}</li>`;
});
}
html += '</ul>';
return html;
}
// Ensure escapeHtml is loaded from crypto-helpers.js
// --- DOMContentLoaded Event Listener ---
document.addEventListener('DOMContentLoaded', function() {
// Ensure key is in sessionStorage from Flask session or redirect
const flaskProvidedKey = "{{ session.get('encryption_key', 'null') }}";
if (flaskProvidedKey && flaskProvidedKey !== 'null' && !sessionStorage.getItem('encryptionKey')) {
sessionStorage.setItem('encryptionKey', flaskProvidedKey);
console.log("Encryption key loaded into sessionStorage from Flask.");
} else if (!sessionStorage.getItem('encryptionKey')) {
console.warn("Encryption key missing from Flask session and sessionStorage. Redirecting to login.");
window.location.href = "{{ url_for('login') }}";
return; // Stop script execution if no key
}
// --- Get DOM Elements ---
const form = document.getElementById('password-form');
const messageEl = document.getElementById('message');
const togglePasswordBtn = document.getElementById('toggle-password');
const passwordInput = document.getElementById('password');
const submitButton = form.querySelector('button[type="submit"]');
const generatePasswordBtn = document.getElementById('generate-password-btn');
const genLengthSlider = document.getElementById('gen-length');
const genLengthValueSpan = document.getElementById('gen-length-value');
const genLowercaseCheckbox = document.getElementById('gen-lowercase');
const genUppercaseCheckbox = document.getElementById('gen-uppercase');
const genDigitsCheckbox = document.getElementById('gen-digits');
const genSymbolsCheckbox = document.getElementById('gen-symbols');
// Strength & Breach Display Elements
const strengthArea = document.getElementById('password-strength-area');
const strengthIndicator = document.getElementById('strength-indicator');
const strengthTextLabel = document.getElementById('strength-text-label');
const strengthFeedbackDiv = document.getElementById('password-strength-feedback');
const breachStatusArea = document.getElementById('password-breach-status');
const breachStatusIndicator = document.getElementById('breach-status-indicator');
// --- Debounce Function ---
let debounceTimerStrength;
function debounce(func, delay) {
return function(...args) {
clearTimeout(debounceTimerStrength);
debounceTimerStrength = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// --- Function to Update UI for Both Strength and Breach ---
async function updatePasswordAnalysisUI(password) {
if (!password) {
// Hide strength meter and breach status if password is empty
strengthArea.style.display = 'none';
strengthIndicator.className = 'strength-indicator';
strengthTextLabel.textContent = 'Strength:';
strengthFeedbackDiv.innerHTML = '';
strengthArea.className = 'password-strength-area';
breachStatusArea.style.display = 'none'; // Hide breach area
breachStatusIndicator.textContent = 'Checking...';
breachStatusIndicator.className = 'breach-indicator'; // Reset breach style
return;
}
// --- Show elements and indicate loading ---
strengthArea.style.display = 'block'; // Show combined area
breachStatusArea.style.display = 'flex'; // Show breach area (use flex for alignment)
strengthTextLabel.textContent = 'Strength: Checking...';
strengthIndicator.className = 'strength-indicator'; // Reset bar
strengthFeedbackDiv.innerHTML = '<ul><li>Checking...</li></ul>';
strengthArea.className = 'password-strength-area'; // Reset background
breachStatusIndicator.textContent = 'Checking...';
breachStatusIndicator.className = 'breach-indicator loading'; // Style for loading
// --- Perform Checks Concurrently ---
const strengthPromise = fetch("{{ url_for('strength_check_api') }}", {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password: password })
});
// Use the FREE HIBP check directly from crypto-helpers.js
const breachPromise = checkHIBPPassword(password); // Assumes checkHIBPPassword is loaded
// --- Process Strength Results ---
try {
const response = await strengthPromise;
if (!response.ok) {
const errorData = await response.json().catch(() => ({ error: 'Unknown server error' }));
throw new Error(errorData.error || `Server error: ${response.status}`);
}
const result = await response.json();
const score = result.score; // Score 0-4
const strengthClass = getStrengthClassFromScore(score);
const assessment = result.assessment || "Unknown";
strengthIndicator.className = `strength-indicator ${strengthClass}`;
strengthTextLabel.textContent = `Strength: ${assessment}`;
strengthFeedbackDiv.innerHTML = formatBackendFeedback(result.feedback);
strengthArea.className = `password-strength-area strength-${score}`;
} catch (error) {
console.error('Strength Check Error:', error);
strengthTextLabel.textContent = 'Strength: Error';
strengthFeedbackDiv.innerHTML = `<ul><li class="warning">Error checking strength: ${escapeHtml(error.message)}</li></ul>`;
strengthArea.className = 'password-strength-area strength-0'; // Show error style
}
// --- Process Breach Results ---
try {
const breachResult = await breachPromise;
if (breachResult.error) {
breachStatusIndicator.textContent = `Error: ${escapeHtml(breachResult.error)}`;
breachStatusIndicator.className = 'breach-indicator error';
} else if (breachResult.isPwned) {
breachStatusIndicator.textContent = `Compromised! Found in ${breachResult.count} breach${breachResult.count > 1 ? 'es' : ''}.`;
breachStatusIndicator.className = 'breach-indicator pwned';
} else {
breachStatusIndicator.textContent = 'Not found in known breaches.';
breachStatusIndicator.className = 'breach-indicator safe';
}
} catch (error) { // Should not happen if checkHIBPPassword handles errors, but for safety
console.error("Error processing breach result:", error);
breachStatusIndicator.textContent = 'Error checking breach status.';
breachStatusIndicator.className = 'breach-indicator error';
}
}
// --- Password Input Listener ---
passwordInput.addEventListener('input', debounce(function() {
if (typeof checkHIBPPassword === 'function' && typeof zxcvbn === 'function') { // Check helpers are loaded
updatePasswordAnalysisUI(this.value);
} else {
console.error("Required analysis functions (checkHIBPPassword or zxcvbn) not found. Ensure scripts are loaded correctly.");
// Basic fallback or visual error indication could go here
strengthTextLabel.textContent = 'Strength: Error';
strengthArea.style.display = 'block';
strengthFeedbackDiv.innerHTML = `<ul><li class="warning">Error: Analysis script missing.</li></ul>`;
breachStatusIndicator.textContent = 'Error: Checker missing.';
breachStatusIndicator.className = 'breach-indicator error';
breachStatusArea.style.display = 'flex';
}
}, 600)); // Debounce API calls by 600ms
// --- Generator Logic ---
genLengthSlider.addEventListener('input', function() { genLengthValueSpan.textContent = this.value; });
togglePasswordBtn.addEventListener('click', function() { const t=passwordInput.type==='password'?'text':'password'; passwordInput.type=t; this.textContent=t==='password'?'Show':'Hide'; });
generatePasswordBtn.addEventListener('click', async function() {
messageEl.textContent = ''; messageEl.className = 'message';
generatePasswordBtn.disabled = true; generatePasswordBtn.textContent = '...';
const options = {
length: parseInt(genLengthSlider.value, 10),
use_lowercase: genLowercaseCheckbox.checked,
use_uppercase: genUppercaseCheckbox.checked,
use_digits: genDigitsCheckbox.checked,
use_symbols: genSymbolsCheckbox.checked
};
try {
// Fetch generated password from backend
const response = await fetch("{{ url_for('generate_password_api') }}", {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(options)
});
const result = await response.json();
if (response.ok && result.password) {
passwordInput.value = result.password;
passwordInput.type = 'text'; // Show generated password briefly
togglePasswordBtn.textContent = 'Hide';
// *** Trigger the analysis immediately after generation ***
if (typeof checkHIBPPassword === 'function' && typeof zxcvbn === 'function') {
updatePasswordAnalysisUI(result.password); // Call analysis function
} else {
console.error("Analysis functions not available after generation.");
}
// Hide after a delay
setTimeout(() => {
if (passwordInput.type === 'text') {
passwordInput.type = 'password';
togglePasswordBtn.textContent = 'Show';
}
}, 2500); // Show for 2.5 seconds
} else {
throw new Error(result.error || 'Failed to generate password.');
}
} catch (error) {
messageEl.textContent = `Generation Error: ${escapeHtml(error.message)}`;
messageEl.className = 'message error';
console.error('Generate Password Error:', error);
} finally {
generatePasswordBtn.disabled = false;
generatePasswordBtn.textContent = 'Generate';
}
});
// --- Form Submission Logic ---
form.addEventListener('submit', async function(e) {
e.preventDefault();
messageEl.textContent = ''; messageEl.className = 'message';
submitButton.disabled = true; submitButton.textContent = 'Saving...';
const service = document.getElementById('service').value.trim();
const username = document.getElementById('username').value.trim();
const password = document.getElementById('password').value; // Get password from input
if (!service || !username || !password) {
messageEl.textContent = 'Service, Username, and Password are required.';
messageEl.className = 'message error';
submitButton.disabled = false;
submitButton.textContent = 'Encrypt & Save';
return;
}
try {
// Get the raw key bytes from session storage
const keyBuffer = await getEncryptionKeyRawBytesFromSession();
if (!keyBuffer) {
// Error handled within getEncryptionKeyRawBytesFromSession (alert/redirect)
submitButton.disabled = false;
submitButton.textContent = 'Encrypt & Save';
return;
}
// Encrypt the data
const dataToEncrypt = { service: service, username: username, password: password };
const encryptedB64Data = await encryptData(keyBuffer, dataToEncrypt); // Assumes encryptData is loaded
// Send to backend
const response = await fetch("{{ url_for('add_credential') }}", {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
encrypted_data: encryptedB64Data,
service_hint: service // Use service name as hint
})
});
const result = await response.json();
if (response.ok && result.success) {
messageEl.textContent = 'Credential encrypted and saved successfully!';
messageEl.className = 'message success';
form.reset(); // Clear the form
// Reset generator options to default
genLengthSlider.value = 16;
genLengthValueSpan.textContent = '16';
genLowercaseCheckbox.checked = true;
genUppercaseCheckbox.checked = true;
genDigitsCheckbox.checked = true;
genSymbolsCheckbox.checked = true;
passwordInput.type = 'password';
togglePasswordBtn.textContent = 'Show';
// Reset strength & breach display on successful save
strengthArea.style.display = 'none'; // Hide the whole area
breachStatusArea.style.display = 'none';
strengthIndicator.className = 'strength-indicator';
strengthTextLabel.textContent = 'Strength:';
strengthFeedbackDiv.innerHTML = '';
strengthArea.className = 'password-strength-area';
breachStatusIndicator.textContent = 'Checking...';
breachStatusIndicator.className = 'breach-indicator';
} else {
messageEl.textContent = `Error: ${escapeHtml(result.message || 'Failed to save credential.')}`;
messageEl.className = 'message error';
}
} catch (error) {
messageEl.textContent = `An error occurred: ${escapeHtml(error.message)}`;
messageEl.className = 'message error';
console.error('Save Credential Error:', error);
// Log specific crypto errors if they occur
if (error.message.includes("encrypt") || error.message.includes("Base64")) {
console.error("Potential encryption or encoding error during save.");
}
} finally {
submitButton.disabled = false;
submitButton.textContent = 'Encrypt & Save';
}
}); // End form submit listener
}); // End DOMContentLoaded
</script>
</body>
</html>