- #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
96 lines
2.5 KiB
HTML
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 %}
|