Add authentication improvements and search functionality
- Implement anonymous access control with ALLOW_ANONYMOUS_ACCESS env var - Add complete password reset workflow with token-based validation - Add username recovery functionality for better UX - Implement full-text search API with relevance scoring and highlighting - Add Docker compatibility improvements with permission handling and fallback storage - Add quick stats API for real-time dashboard updates - Improve security with proper token expiration and input validation - Add search result pagination and navigation - Enhance error handling and logging throughout the application 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
179
templates/reset_password.html
Normal file
179
templates/reset_password.html
Normal file
@@ -0,0 +1,179 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Reset Password - BalanceBoard{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="auth-container">
|
||||
<div class="auth-card">
|
||||
<div class="auth-logo">
|
||||
<img src="{{ url_for('serve_logo') }}" alt="BalanceBoard Logo">
|
||||
<h1><span class="balance">balance</span>Board</h1>
|
||||
<p style="color: var(--text-secondary); margin-top: 8px;">Create your new password</p>
|
||||
</div>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<div class="flash-messages">
|
||||
{% for category, message in messages %}
|
||||
<div class="flash-message {{ category }}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<form method="POST" class="auth-form" id="resetPasswordForm">
|
||||
<div class="form-group">
|
||||
<label for="password">New Password</label>
|
||||
<input type="password" id="password" name="password" required
|
||||
minlength="8"
|
||||
placeholder="Enter new password"
|
||||
oninput="checkPasswordStrength()">
|
||||
<small class="form-help">
|
||||
Password must be at least 8 characters long.
|
||||
</small>
|
||||
<div id="passwordStrength" class="password-strength" style="display: none;">
|
||||
<div class="strength-bar"></div>
|
||||
<small class="strength-text"></small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="confirm_password">Confirm New Password</label>
|
||||
<input type="password" id="confirm_password" name="confirm_password" required
|
||||
placeholder="Confirm your new password"
|
||||
oninput="checkPasswordMatch()">
|
||||
<small class="form-help" id="passwordMatch" style="display: none;"></small>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary btn-block" id="resetBtn">
|
||||
Reset Password
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="auth-footer">
|
||||
<p><a href="{{ url_for('login') }}">Back to login</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function checkPasswordStrength() {
|
||||
const password = document.getElementById('password').value;
|
||||
const strengthDiv = document.getElementById('passwordStrength');
|
||||
const strengthBar = strengthDiv.querySelector('.strength-bar');
|
||||
const strengthText = strengthDiv.querySelector('.strength-text');
|
||||
|
||||
if (password.length === 0) {
|
||||
strengthDiv.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
strengthDiv.style.display = 'block';
|
||||
|
||||
let strength = 0;
|
||||
if (password.length >= 8) strength++;
|
||||
if (password.length >= 12) strength++;
|
||||
if (/[a-z]/.test(password) && /[A-Z]/.test(password)) strength++;
|
||||
if (/[0-9]/.test(password)) strength++;
|
||||
if (/[^A-Za-z0-9]/.test(password)) strength++;
|
||||
|
||||
const strengthLevels = ['Very Weak', 'Weak', 'Fair', 'Good', 'Strong'];
|
||||
const strengthColors = ['#ff4444', '#ff8844', '#ffaa44', '#44ff88', '#44aa44'];
|
||||
|
||||
strengthBar.style.width = `${(strength + 1) * 20}%`;
|
||||
strengthBar.style.backgroundColor = strengthColors[strength];
|
||||
strengthText.textContent = strengthLevels[strength];
|
||||
strengthText.style.color = strengthColors[strength];
|
||||
}
|
||||
|
||||
function checkPasswordMatch() {
|
||||
const password = document.getElementById('password').value;
|
||||
const confirm = document.getElementById('confirm_password').value;
|
||||
const matchDiv = document.getElementById('passwordMatch');
|
||||
|
||||
if (confirm.length === 0) {
|
||||
matchDiv.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
matchDiv.style.display = 'block';
|
||||
|
||||
if (password === confirm) {
|
||||
matchDiv.textContent = '✓ Passwords match';
|
||||
matchDiv.style.color = '#44aa44';
|
||||
document.getElementById('resetBtn').disabled = false;
|
||||
} else {
|
||||
matchDiv.textContent = '✗ Passwords do not match';
|
||||
matchDiv.style.color = '#ff4444';
|
||||
document.getElementById('resetBtn').disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('resetPasswordForm').addEventListener('submit', function(e) {
|
||||
const password = document.getElementById('password').value;
|
||||
const confirm = document.getElementById('confirm_password').value;
|
||||
|
||||
if (password !== confirm) {
|
||||
e.preventDefault();
|
||||
document.getElementById('passwordMatch').click();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.form-help {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.password-strength {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.strength-bar {
|
||||
height: 4px;
|
||||
background: #e0e0e0;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.strength-text {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 12px 24px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--primary-color, #4db6ac);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
background: var(--primary-dark, #26a69a);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn-primary:disabled {
|
||||
background: #cccccc;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.btn-block {
|
||||
width: 100%;
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user