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:
@@ -8,12 +8,12 @@
|
||||
<p class="welcome-subtitle">{{ 'registration_confirmed'|t }}</p>
|
||||
|
||||
{% for event in events %}
|
||||
<section class="event-section">
|
||||
<section class="event-section card">
|
||||
<h2>{{ event.name }}</h2>
|
||||
<div class="event-details">
|
||||
<p><strong>{{ 'date'|t }}:</strong> {{ event.start_time|localized_date if event.start_time else 'TBD' }}</p>
|
||||
<p><strong>{{ 'location'|t }}:</strong> {{ event.location }}</p>
|
||||
<p><strong>{{ 'status'|t }}:</strong> <span class="status-registered">{{ 'registered'|t }}</span></p>
|
||||
<p><strong>{{ 'status'|t }}:</strong> <span class="status-registered"><i data-lucide="check-circle"></i> {{ 'registered'|t }}</span></p>
|
||||
</div>
|
||||
|
||||
<h3>{{ 'breakout_sessions'|t }}</h3>
|
||||
@@ -23,179 +23,36 @@
|
||||
<div class="session-item {% if session.my_rsvp_status == 'registered' %}registered{% endif %}">
|
||||
<div class="session-info">
|
||||
<strong>{{ session.name }}</strong>
|
||||
<span class="session-time">{{ session.start_time|localized_date('%H:%M') if session.start_time else '' }} - {{ session.end_time|localized_date('%H:%M') if session.end_time else '' }}</span>
|
||||
<span class="session-location">{{ session.location }}</span>
|
||||
<span class="session-time"><i data-lucide="clock"></i> {{ session.start_time|localized_date('%H:%M') if session.start_time else '' }} - {{ session.end_time|localized_date('%H:%M') if session.end_time else '' }}</span>
|
||||
<span class="session-location"><i data-lucide="map-pin"></i> {{ session.location }}</span>
|
||||
{% if session.description %}
|
||||
<span class="session-description">{{ session.description }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="session-status">
|
||||
{% if session.my_rsvp_status == 'registered' %}
|
||||
<span class="badge badge-success">{{ 'registered'|t }}</span>
|
||||
<span class="badge badge-success"><i data-lucide="check"></i> {{ 'registered'|t }}</span>
|
||||
{% else %}
|
||||
<span class="badge badge-secondary">{{ 'not_registered'|t }}</span>
|
||||
<span class="badge badge-pending">{{ 'not_registered'|t }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="no-sessions">{{ 'no_breakout_sessions'|t }}</p>
|
||||
<p class="no-sessions"><i data-lucide="calendar"></i> {{ 'no_breakout_sessions'|t }}</p>
|
||||
{% endif %}
|
||||
</section>
|
||||
{% endfor %}
|
||||
|
||||
<div class="actions">
|
||||
<a href="{{ url_for('attendee_dashboard') }}" class="btn btn-primary">{{ 'go_to_dashboard'|t }}</a>
|
||||
<a href="{{ url_for('attendee_dashboard') }}" class="btn btn-primary"><i data-lucide="layout-dashboard"></i> {{ 'go_to_dashboard'|t }}</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_styles %}
|
||||
<style>
|
||||
.personal-page {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 50px 40px;
|
||||
}
|
||||
|
||||
.personal-page h1 {
|
||||
color: #2c3e50;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.welcome-subtitle {
|
||||
color: #666;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.event-section {
|
||||
background: #fff;
|
||||
padding: 25px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.event-section h2 {
|
||||
color: #2c3e50;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #3498db;
|
||||
}
|
||||
|
||||
.event-details {
|
||||
background: #f8f9fa;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.event-details p {
|
||||
margin: 8px 0;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.status-registered {
|
||||
color: #27ae60;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.event-section h3 {
|
||||
color: #2c3e50;
|
||||
margin: 20px 0 15px 0;
|
||||
}
|
||||
|
||||
.sessions-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.session-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #95a5a6;
|
||||
}
|
||||
|
||||
.session-item.registered {
|
||||
border-left-color: #27ae60;
|
||||
background: #f0f9f4;
|
||||
}
|
||||
|
||||
.session-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.session-info strong {
|
||||
display: block;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.session-info span {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.session-info .session-time {
|
||||
color: #3498db;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.session-info .session-location {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.session-info .session-description {
|
||||
color: #666;
|
||||
margin-top: 5px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.session-status {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.badge-success {
|
||||
background: #27ae60;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.badge-secondary {
|
||||
background: #95a5a6;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.no-sessions {
|
||||
text-align: center;
|
||||
color: #888;
|
||||
padding: 20px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.actions .btn {
|
||||
display: inline-block;
|
||||
padding: 12px 30px;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
lucide.createIcons();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user