Jarian Cottingham e2cbfa15b2 fix: security hardening - auth, CSRF, path traversal, XSS, secrets, headers
- #3: Path traversal fix in /archive and /archive-file routes via resolve() check
- #4: SSRF mitigation - env-based SERVER_URL, no hardcoded internal IPs
- #5: Stored XSS fix - remove |safe filter from article.html template
- #6: Missing import os in scheduler.py (crash on import)
- #7: Flask auth (password via NEWSARCHIVER_PASSWORD) + CSRF tokens
- #8: Same as #5 (template XSS via |safe)
- #9: Motley Fool API key removed - use env var interpolation
- #10: Hardcoded paths in setup_cron.sh, stop_services.sh - use BASH_SOURCE
- #11: Hardcoded user paths in singlefile_archive.py - use Path.home()
- #16: HTTP RSS feeds updated to HTTPS (Barchart, Guardian, BBC, MarketWatch)
- #24: SSRF - replace hardcoded 192.168.8.150:5000 with NEWSARCHIVER_SERVER_URL
- #25: Command execution details sanitized in error messages
- #26: Security headers (X-Content-Type-Options, X-Frame-Options, X-XSS-Protection, Referrer-Policy, CSP)
- #27: Auth guard on all routes except RSS/Atom feeds
- archive_engine.py: Add missing import os
2026-08-21 15:45:00 +00:00

96 lines
2.5 KiB
HTML

{% extends "base.html" %}
{% block content %}
<div class="login-container">
<div class="login-box">
<h2>NewsArchiver Login</h2>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<form method="POST" action="{{ url_for('login') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
{% if request.args.get('next') %}
<input type="hidden" name="next" value="{{ request.args.get('next') }}">
{% endif %}
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" required autofocus>
</div>
<button type="submit" class="btn btn-primary">Login</button>
</form>
</div>
</div>
<style>
.login-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 60vh;
}
.login-box {
background: var(--card-bg, #fff);
border: 1px solid var(--border-color, #ddd);
border-radius: 8px;
padding: 2rem;
width: 100%;
max-width: 400px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.login-box h2 {
margin-top: 0;
margin-bottom: 1.5rem;
text-align: center;
}
.form-group {
margin-bottom: 1rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: bold;
}
.form-group input {
width: 100%;
padding: 0.5rem;
border: 1px solid var(--border-color, #ccc);
border-radius: 4px;
box-sizing: border-box;
}
.btn {
width: 100%;
padding: 0.75rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
}
.btn-primary {
background: var(--accent-color, #0066cc);
color: white;
}
.btn-primary:hover {
opacity: 0.9;
}
.alert {
padding: 0.75rem;
border-radius: 4px;
margin-bottom: 1rem;
}
.alert-error {
background: #fee;
color: #c00;
border: 1px solid #fcc;
}
.alert-info {
background: #eef;
color: #00c;
border: 1px solid #ccf;
}
</style>
{% endblock %}