Security hardening: env-based config, CSRF protection, secure code generation, plus presenter management and email communications system
- Move all credentials from hardcoded to .env with python-dotenv (add .env.example template and .gitignore) - Add CSRF token generation and validation on all state-changing requests - Replace random.choices with secrets.choice for all secure code generation - Add session cookie security headers (HttpOnly, Secure, SameSite) - Add presenter management: assign/remove presenters to breakout sessions, presenter QR scanning - Add email communications system: templates per event, custom email sending, sent email tracking - Add staff/organizer profile pages with staff code display - New DB tables: event_email_templates, sent_emails, user_communications - New DB columns: staff.reminder_count/reminder_dates, breakout_session_organizers.presenter_code - Expand English translation strings across all new features Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+12
-111
@@ -7,7 +7,7 @@
|
||||
<div class="scan-header">
|
||||
<h1>{{ 'scan_attendee'|t }}</h1>
|
||||
<p>{{ 'scan_qr_description'|t }}</p>
|
||||
<a href="{{ url_for('attendee_dashboard') }}" class="btn btn-outline">{{ 'back_to_dashboard'|t }}</a>
|
||||
<a href="{{ url_for('attendee_dashboard') }}" class="btn btn-outline"><i data-lucide="arrow-left"></i> {{ 'back_to_dashboard'|t }}</a>
|
||||
</div>
|
||||
|
||||
<div class="scanner-container">
|
||||
@@ -16,7 +16,7 @@
|
||||
<div class="result-icon" id="result-icon"></div>
|
||||
<h2 id="result-title"></h2>
|
||||
<p id="result-message"></p>
|
||||
<button id="scan-again" class="btn btn-primary">{{ 'scan_again'|t }}</button>
|
||||
<button id="scan-again" class="btn btn-primary"><i data-lucide="scan-line"></i> {{ 'scan_again'|t }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -26,109 +26,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.scan-page {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.scan-header {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.scan-header h1 {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.scan-header p {
|
||||
color: #666;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.scanner-container {
|
||||
background: #000;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
min-height: 300px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.qr-reader {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.qr-reader video {
|
||||
width: 100% !important;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.scan-result {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
background: #fff;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.scan-result.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.result-icon {
|
||||
font-size: 64px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.result-icon.success::before {
|
||||
content: '✓';
|
||||
color: #22c55e;
|
||||
}
|
||||
|
||||
.result-icon.pending::before {
|
||||
content: '⏳';
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
.result-icon.error::before {
|
||||
content: '✗';
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.result-icon.info::before {
|
||||
content: 'ℹ';
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
#result-title {
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
#result-message {
|
||||
color: #666;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.scan-info {
|
||||
background: #f8fafc;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.scan-info p {
|
||||
margin: 10px 0;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.scan-info strong {
|
||||
color: #1e293b;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="https://unpkg.com/html5-qrcode@2.3.8/html5-qrcode.min.js"></script>
|
||||
<script>
|
||||
const myId = {{ session.user_id }};
|
||||
@@ -151,25 +48,29 @@ async function sendConnectionRequest(scannedId) {
|
||||
const message = document.getElementById('result-message');
|
||||
|
||||
icon.className = 'result-icon';
|
||||
icon.innerHTML = '<i data-lucide="clock"></i>';
|
||||
|
||||
if (data.success) {
|
||||
document.getElementById('qr-reader').style.display = 'none';
|
||||
resultDiv.classList.remove('hidden');
|
||||
icon.classList.add('pending');
|
||||
icon.innerHTML = '<i data-lucide="clock"></i>';
|
||||
title.textContent = '{{ "request_sent"|t }}';
|
||||
message.textContent = '{{ "request_sent_message"|t }}';
|
||||
} else {
|
||||
document.getElementById('qr-reader').style.display = 'none';
|
||||
resultDiv.classList.remove('hidden');
|
||||
icon.classList.add('error');
|
||||
icon.innerHTML = '<i data-lucide="x-circle"></i>';
|
||||
title.textContent = '{{ "error"|t }}';
|
||||
message.textContent = data.error || '{{ "failed_send_request"|t }}';
|
||||
}
|
||||
|
||||
lucide.createIcons();
|
||||
scanning = false;
|
||||
} catch (error) {
|
||||
console.error('Connection error:', error);
|
||||
alert('Error sending connection request');
|
||||
showToast('Error sending connection request', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,16 +85,16 @@ function onScanSuccess(decodedText) {
|
||||
|
||||
if (eventId === myEventId) {
|
||||
if (attendeeId === myId) {
|
||||
alert('{{ "cannot_scan_own_qr"|t }}');
|
||||
showToast('{{ "cannot_scan_own_qr"|t }}', 'warning');
|
||||
return;
|
||||
}
|
||||
sendConnectionRequest(attendeeId);
|
||||
} else {
|
||||
alert('{{ "qr_different_event"|t }}');
|
||||
showToast('{{ "qr_different_event"|t }}', 'error');
|
||||
}
|
||||
} else {
|
||||
console.log('Unknown QR format:', decodedText);
|
||||
alert('{{ "unrecognized_qr"|t }}');
|
||||
// QR format not recognized
|
||||
showToast('{{ "unrecognized_qr"|t }}', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +113,7 @@ function startScanner() {
|
||||
}
|
||||
).catch(err => {
|
||||
console.error('Camera error:', err);
|
||||
alert('{{ "camera_permission_error"|t }}');
|
||||
showToast('{{ "camera_permission_error"|t }}', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user