From d86f1ba09970048a7423a1bd8925dce9ecd34960 Mon Sep 17 00:00:00 2001 From: jpmvaz Date: Sun, 13 Sep 2026 19:59:54 +0100 Subject: [PATCH] v_1.5 --- .dockerignore | 19 + .env.example | 79 ++ .gitignore | 11 + Dockerfile | 45 ++ OLD_VERSIONS/v1.0/alerthub.zip | Bin 0 -> 48307 bytes OLD_VERSIONS/v1.2/alerthub.zip | Bin 0 -> 55955 bytes OLD_VERSIONS/v1.3/alerthub.zip | Bin 0 -> 56865 bytes OLD_VERSIONS/v1.4/alerthub.zip | Bin 0 -> 57926 bytes OLD_VERSIONS/v1.5/alerthub.zip | Bin 0 -> 58308 bytes README.md | 144 ++++ app/__init__.py | 278 +++++++ app/auth_utils.py | 37 + app/config.py | 81 ++ app/forms.py | 139 ++++ app/mailer.py | 161 ++++ app/models.py | 192 +++++ app/routes_auth.py | 308 ++++++++ app/routes_backoffice.py | 426 +++++++++++ app/routes_main.py | 137 ++++ app/scheduler.py | 79 ++ app/static/css/app.css | 869 ++++++++++++++++++++++ app/static/favicon.svg | 1 + app/static/js/app.js | 73 ++ app/templates/account.html | 81 ++ app/templates/alert_form.html | 48 ++ app/templates/alerts_list.html | 64 ++ app/templates/backoffice/audit_log.html | 51 ++ app/templates/backoffice/dashboard.html | 77 ++ app/templates/backoffice/mail_config.html | 90 +++ app/templates/backoffice/mail_log.html | 73 ++ app/templates/backoffice/settings.html | 45 ++ app/templates/backoffice/user_form.html | 41 + app/templates/backoffice/users_list.html | 65 ++ app/templates/base.html | 136 ++++ app/templates/error.html | 12 + app/templates/index.html | 60 ++ app/templates/login.html | 23 + app/templates/mfa.html | 19 + app/templates/mfa_setup.html | 34 + app/templates/setup.html | 35 + docker-compose.yml | 34 + entrypoint.sh | 26 + requirements.txt | 14 + wsgi.py | 13 + wsgi_backoffice.py | 9 + wsgi_frontoffice.py | 9 + 46 files changed, 4138 insertions(+) create mode 100644 .dockerignore create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 OLD_VERSIONS/v1.0/alerthub.zip create mode 100644 OLD_VERSIONS/v1.2/alerthub.zip create mode 100644 OLD_VERSIONS/v1.3/alerthub.zip create mode 100644 OLD_VERSIONS/v1.4/alerthub.zip create mode 100644 OLD_VERSIONS/v1.5/alerthub.zip create mode 100644 README.md create mode 100644 app/__init__.py create mode 100644 app/auth_utils.py create mode 100644 app/config.py create mode 100644 app/forms.py create mode 100644 app/mailer.py create mode 100644 app/models.py create mode 100644 app/routes_auth.py create mode 100644 app/routes_backoffice.py create mode 100644 app/routes_main.py create mode 100644 app/scheduler.py create mode 100644 app/static/css/app.css create mode 100644 app/static/favicon.svg create mode 100644 app/static/js/app.js create mode 100644 app/templates/account.html create mode 100644 app/templates/alert_form.html create mode 100644 app/templates/alerts_list.html create mode 100644 app/templates/backoffice/audit_log.html create mode 100644 app/templates/backoffice/dashboard.html create mode 100644 app/templates/backoffice/mail_config.html create mode 100644 app/templates/backoffice/mail_log.html create mode 100644 app/templates/backoffice/settings.html create mode 100644 app/templates/backoffice/user_form.html create mode 100644 app/templates/backoffice/users_list.html create mode 100644 app/templates/base.html create mode 100644 app/templates/error.html create mode 100644 app/templates/index.html create mode 100644 app/templates/login.html create mode 100644 app/templates/mfa.html create mode 100644 app/templates/mfa_setup.html create mode 100644 app/templates/setup.html create mode 100644 docker-compose.yml create mode 100644 entrypoint.sh create mode 100644 requirements.txt create mode 100644 wsgi.py create mode 100644 wsgi_backoffice.py create mode 100644 wsgi_frontoffice.py diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..378ea1e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,19 @@ +.git +.gitignore +.env +*.md +__pycache__/ +*.pyc +*.pyo +*.pyd +.venv/ +venv/ +.env.local +data/ +*.db +*.sqlite* +.DS_Store +.idea/ +.vscode/ +node_modules/ +tests/ diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..699da0c --- /dev/null +++ b/.env.example @@ -0,0 +1,79 @@ +# ============================================================================= +# AlertHub Configuration +# All configuration is done here. NEVER commit your real .env to source control. +# ============================================================================= + +# ---- Flask Core ---- +# Generate a strong key: python -c "import secrets; print(secrets.token_hex(32))" +SECRET_KEY=change-me-to-a-long-random-string-please-generate-with-secrets-module +FLASK_ENV=production + +# ---- Ports ---- +# AlertHub now runs as a SINGLE service on one port. The admin backoffice is +# served on the same port under the /backoffice path (admins only). +FRONTOFFICE_PORT=8080 + +# BACKOFFICE_PORT is no longer used (the backoffice shares the main port under +# /backoffice). It is kept here only so old .env files remain valid; you can +# safely delete it. +# BACKOFFICE_PORT=9001 + +# ---- Gunicorn (optional) ---- +# Keep GUNICORN_WORKERS at 1 so the reminder scheduler runs exactly once. +# Increase GUNICORN_THREADS for more concurrency instead. +GUNICORN_WORKERS=1 +GUNICORN_THREADS=4 + +# ---- Database ---- +DATABASE_PATH=/data/alerthub.db + +# ---- Initial Administrator ---- +# There is NO seeded admin any more. On first launch, open the app and you'll be +# taken to a one-time "Create administrator" screen to set the username & +# password yourself. (The variables below are legacy/no-ops, kept for reference.) +# INITIAL_ADMIN_USERNAME=admin +# INITIAL_ADMIN_EMAIL=admin@example.com +# INITIAL_ADMIN_PASSWORD= + +# ---- Session / Security ---- +# Set SESSION_COOKIE_SECURE=true if you serve via HTTPS (recommended in production) +SESSION_COOKIE_SECURE=false +SESSION_LIFETIME_HOURS=8 +PASSWORD_MIN_LENGTH=10 + +# ---- MFA ---- +# MFA is optional and self-service: any user can enable it from Account → MFA. +MFA_ISSUER_NAME=AlertHub + +# ---- Scheduler ---- +# How often (in minutes) the scheduler checks alerts and dispatches reminder emails +SCHEDULER_INTERVAL_MINUTES=60 + +# ---- Default Reminder Lead Times (in days) ---- +# Comma-separated list. Configurable per-alert later, but these are defaults. +DEFAULT_REMINDER_DAYS=30,14,7,1 + +# ---- SMTP / Outgoing mail (Purelymail defaults) ---- +# Note: these values seed the DB on first run only. After that, edit them in +# the backoffice at /mail-config. +# Purelymail SMTP: smtp.purelymail.com : 465 (SSL/TLS) +# If your client only supports STARTTLS, use port 587 with SMTP_ENCRYPTION=starttls +SMTP_HOST=smtp.purelymail.com +SMTP_PORT=465 +SMTP_USERNAME=you@yourdomain.com +SMTP_PASSWORD= +SMTP_ENCRYPTION=ssl +# Allowed: none | starttls | ssl +SMTP_FROM_ADDRESS=you@yourdomain.com +SMTP_FROM_NAME=AlertHub Notifications + +# ---- IMAP / Sent-folder copy (Purelymail defaults) ---- +# When a mail is sent from the platform, a copy is appended to the mailbox's +# Sent folder over IMAP so it shows up in your normal mail client. +# Purelymail IMAP: imap.purelymail.com : 993 (SSL/TLS) +# Leave IMAP_USERNAME / IMAP_PASSWORD blank to reuse the SMTP credentials. +IMAP_HOST=imap.purelymail.com +IMAP_PORT=993 +IMAP_USERNAME= +IMAP_PASSWORD= +IMAP_SENT_FOLDER=Sent diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9bc2f00 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.env +*.pyc +__pycache__/ +.venv/ +venv/ +data/ +*.db +*.sqlite* +.DS_Store +.idea/ +.vscode/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..dedbe96 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,45 @@ +# syntax=docker/dockerfile:1.7 +# ---- Build / runtime image ------------------------------------------------- +# python:3.13-slim-trixie is the current stable Debian-based slim variant. +FROM python:3.13-slim-trixie + +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + PIP_NO_CACHE_DIR=1 \ + PIP_DISABLE_PIP_VERSION_CHECK=1 + +# Install only what we strictly need (curl for healthchecks). +# Use --no-install-recommends, then immediately purge apt lists to keep +# the image small. +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl \ + && rm -rf /var/lib/apt/lists/* + +# Create an unprivileged user +RUN groupadd --system --gid 10001 alerthub \ + && useradd --system --uid 10001 --gid alerthub --home-dir /app --shell /usr/sbin/nologin alerthub + +WORKDIR /app + +# Install Python dependencies first (better layer cache) +COPY --chown=alerthub:alerthub requirements.txt ./ +RUN pip install --upgrade pip \ + && pip install --no-cache-dir -r requirements.txt + +# Copy application sources +COPY --chown=alerthub:alerthub app/ ./app/ +COPY --chown=alerthub:alerthub wsgi.py wsgi_frontoffice.py wsgi_backoffice.py entrypoint.sh ./ + +RUN chmod +x entrypoint.sh \ + && mkdir -p /data \ + && chown -R alerthub:alerthub /app /data + +USER alerthub + +EXPOSE 8080 + +# Start the unified app (frontoffice + /backoffice) on a single port. +ENTRYPOINT ["./entrypoint.sh"] + +HEALTHCHECK --interval=30s --timeout=4s --start-period=10s --retries=3 \ + CMD curl -fsS http://127.0.0.1:${FRONTOFFICE_PORT:-8080}/login >/dev/null || exit 1 diff --git a/OLD_VERSIONS/v1.0/alerthub.zip b/OLD_VERSIONS/v1.0/alerthub.zip new file mode 100644 index 0000000000000000000000000000000000000000..a8503c03ee89e31fcc32b08f8615a99f228c15b8 GIT binary patch literal 48307 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 literal 0 HcmV?d00001 diff --git a/OLD_VERSIONS/v1.3/alerthub.zip b/OLD_VERSIONS/v1.3/alerthub.zip new file mode 100644 index 0000000000000000000000000000000000000000..2fef4575f8e704b6a2414b114f9926abf00d8016 GIT binary patch literal 56865 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

g7}>+tl!~X-`wYlO?7NQOp|IX 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 W?V1>I^FaT-8~j0olDCb}xBmeLQy;UCG<4D#^1F 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

amXd*-^R#Sze(hr`gLKLdr@;lkHl5BAZX~(B7 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 z

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�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 literal 0 HcmV?d00001 diff --git a/README.md b/README.md new file mode 100644 index 0000000..a779da7 --- /dev/null +++ b/README.md @@ -0,0 +1,144 @@ +# AlertHub + +**Version 1.5 — Martinhal IT (Joao Vaz)** + +A Dockerized alert/reminder web application with email notifications, MFA-capable authentication, and role-based access control. Designed for tracking expiration dates (licenses, renewals, certificates, contracts) with live countdown timers and configurable email reminders. + +## Features + +- **First-run admin setup** — On the very first launch (no accounts yet) you're taken to a "Create administrator" screen to set the admin username & password yourself. No seeded/default admin credentials. +- **Single-port architecture** — Everything runs on one port (`:8080`). The frontoffice (alerts + management) is at the root; the admin backoffice (users, mail, settings, audit log, mail log) is served on the **same port** under `/backoffice` and is restricted to admins. +- **Backoffice shortcut** — Admins get a **Backoffice** link in the top bar (and a **Frontoffice** link back) that switches between the two areas. Only visible to admins. +- **Authentication** — Password (bcrypt, 12 rounds) with optional TOTP MFA. **Any user** can self-enable MFA from *Account → MFA*; it is never mandatory. +- **User avatars** — Any user can upload a profile picture (PNG/JPG/GIF/WebP). Images are auto-squared and resized to 256×256 and stored in the database (survives upgrades). Shown in the top bar. +- **RBAC** — Two roles: `user` (view-only) and `admin` (full access) +- **Live countdown timers** — Per-alert ticking timers (days/hours/minutes/seconds) with state-based color coding (warning, critical, expired) +- **Email reminders** — Configurable SMTP + IMAP editable from the backoffice. Per-alert override of reminder lead times. +- **Mail log** — Every message the platform sends is recorded in the backoffice **Mail Log** (recipients, subject, delivery status, whether the Sent-folder copy was saved, and who triggered it). +- **Sent-folder copy (IMAP)** — After each successful send, a copy of the message is appended to the mailbox's **Sent** folder over IMAP, so it appears in your normal mail client. +- **Log export & email** — Both the Audit log and Mail log can be exported to CSV or emailed (as a CSV attachment) to any destination address. +- **Account security** — Lockout after 5 failed attempts (15-minute window), audit log of sensitive actions, security headers (CSP, X-Frame-Options, HSTS-ready) +- **IT-terminal aesthetic** — JetBrains Mono + IBM Plex Sans, signal-green accents, light + dark mode (auto-detect + manual toggle) +- **Consistent footer** — `© 2026 Martinhal IT - Joao Vaz - Version 1.5` on every page except the login screen. +- **Hardened container** — Non-root user, all capabilities dropped, `no-new-privileges`, healthcheck + +## Stack + +Python 3.13 · Flask 3.1 · SQLAlchemy 2 · Flask-Login · Flask-WTF · APScheduler · pyotp · bcrypt · gunicorn · SQLite + +## Quick start + +```bash +# 1. Get the code, then: +cp .env.example .env + +# 2. Edit .env — at minimum, change: +# - SECRET_KEY (use: python -c "import secrets; print(secrets.token_hex(32))") +# - SMTP_* / IMAP_* (your Purelymail credentials) + +# 3. Build and start: +docker compose up -d --build + +# 4. Open: +# App: http://localhost:8080 +# Backoffice: http://localhost:8080/backoffice (admins only) + +# 5. First visit → you'll be sent to the "Create administrator" screen. +# Set the admin username, email, and password there. +``` + +### First-run checklist + +1. Open the app — you'll land on **Create administrator**. Set the admin username, email and password. +2. Sign in with the credentials you just created. +3. (Optional) Go to **Account** → **Set up MFA** (scan the QR code with Google Authenticator, Authy, 1Password, etc.). Any user can do this. +4. (Optional) **Account** → **Profile avatar** → upload a picture. +5. Open the backoffice → **Mail** → confirm the Purelymail SMTP/IMAP settings, fill in the mailbox username/password, and send a test email. Check the **Mail Log** to confirm it sent and that a copy was saved to the mailbox's Sent folder. +6. Backoffice → **Settings** → set the default reminder lead times (e.g. `30,14,7,1` days before expiry). +7. Frontoffice → **Manage Alerts** → add your first alert. + +## Configuration + +All configuration lives in `.env`. The `docker-compose.yml` only references the file via `env_file` — no secrets are baked into the image or compose file. + +| Variable | Purpose | +|---|---| +| `SECRET_KEY` | Flask session signing key (required, generate a new one) | +| `FRONTOFFICE_PORT` | The single host port (default 8080). `BACKOFFICE_PORT` is no longer used. | +| `GUNICORN_WORKERS` / `GUNICORN_THREADS` | Web server sizing. Keep workers at 1 so the reminder scheduler runs once (default 1 worker / 4 threads). | +| `DATABASE_PATH` | SQLite path inside the container (default `/data/alerthub.db`) | +| `SESSION_LIFETIME_HOURS` | Login session length | +| `MAX_LOGIN_ATTEMPTS` / `LOCKOUT_MINUTES` | Brute-force protection | +| `SCHEDULER_INTERVAL_MINUTES` | How often the reminder job runs (default 60) | +| `SMTP_HOST` / `SMTP_PORT` / `SMTP_USERNAME` / `SMTP_PASSWORD` / `SMTP_ENCRYPTION` / `SMTP_FROM_ADDRESS` / `SMTP_FROM_NAME` | Initial outgoing-mail config (Purelymail defaults; seeded into DB on first run; admins edit live values via backoffice afterwards) | +| `IMAP_HOST` / `IMAP_PORT` / `IMAP_USERNAME` / `IMAP_PASSWORD` / `IMAP_SENT_FOLDER` | IMAP settings for saving a copy of sent mail to the Sent folder (Purelymail defaults). Leave user/pass blank to reuse the SMTP credentials. | +| `DEFAULT_REMINDER_DAYS` | Default reminder thresholds, comma-separated (e.g. `30,14,7,1`) | + +> **Admin account:** There is no seeded admin. The first launch presents a one-time **Create administrator** screen. The old `INITIAL_ADMIN_*` variables are no longer used. + +> **Note:** `SMTP_*` / `IMAP_*` values seed the database on first launch. After that, the backoffice **Mail** page is the source of truth — edits there persist independently of `.env`. + +### Purelymail mail settings + +Defaults ship pre-configured for Purelymail: + +- **Outgoing (SMTP):** `smtp.purelymail.com`, port **465**, SSL/TLS. (If your client only supports STARTTLS, use port **587** with `SMTP_ENCRYPTION=starttls`.) +- **Incoming (IMAP):** `imap.purelymail.com`, port **993**, SSL/TLS — used to append sent messages to the **Sent** folder. + +Just fill in your mailbox username and password (in `.env` or the backoffice **Mail** page). + +## Upgrading in place (no data loss) + +This build is designed to be **unzipped over the previous version**. On startup the app runs additive migrations: it creates any new tables (e.g. the mail log) and adds any new columns (avatars) to the existing SQLite database in the `/data` volume, without touching your existing users, alerts, settings, or audit history. + +```bash +# from the folder containing your existing docker-compose.yml / .env / app: +unzip -o alerthub.zip # overwrite code in place (keeps .env and the /data volume) +docker compose up -d --build # rebuild & restart; migrations run automatically +``` + +Your customised SMTP settings are preserved on upgrade; the new IMAP keys are seeded with the Purelymail defaults (adjust them in the backoffice **Mail** page). + +## Architecture + +``` +┌──────────── Docker container ────────────┐ +│ │ +│ entrypoint.sh │ +│ └── gunicorn wsgi:application :8080 │ +│ ├── frontoffice (/) │ +│ ├── backoffice (/backoffice) │ +│ └── scheduler (APScheduler) │ +│ │ +│ /data/alerthub.db (named volume) │ +└──────────────────────────────────────────┘ +``` + +Both processes share the same SQLite database (mounted as a Docker named volume). The reminder scheduler runs only inside the frontoffice process to avoid duplicate emails. + +## Security notes + +- Passwords stored as bcrypt hashes (cost 12). +- TOTP secrets stored encrypted at rest is NOT implemented — the secret is stored as-is. If your threat model requires it, mount the DB on an encrypted volume. +- CSP is strict (no inline scripts except the early theme bootstrap which is `nonce`-able if you tighten further). +- Container runs as uid `10001`, drops all Linux capabilities, and uses `no-new-privileges`. +- Brute-force lockout is per-username, not per-IP. + +## Operational tips + +- **Trigger a reminder check manually:** Backoffice → Settings → "Run check now". +- **Reset a user's MFA:** Backoffice → Users → row menu → Reset MFA. +- **Unlock a locked-out user:** Backoffice → Users → row menu → Unlock. +- **Inspect activity:** Backoffice → Audit log (paginated). +- **Reset reminder history for an alert** (e.g. after extending the date): Manage Alerts → row → Reset reminders. + +## Uninstall / data reset + +```bash +docker compose down # stop containers, keep data +docker compose down -v # also remove the alerthub_data volume (deletes DB) +``` + +## License + +Provided as-is for internal use. No warranty. diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..46a18fb --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,278 @@ +"""Application factory. + +Builds a single unified Flask app served on ONE port: + - frontoffice at the root: / (View) and /alerts (Add/Edit/Remove) + - backoffice under /backoffice: users, mail config, settings, audit log, + mail log — all restricted to admins. + +Everything shares the same database, models, and login system. The ``variant`` +argument is retained for backwards compatibility with existing WSGI entry +points but no longer changes what is registered — both areas are always +available on the same app. +""" +import os +import logging +from flask import Flask, redirect, url_for, render_template, request, session +from flask_login import LoginManager +from flask_wtf.csrf import CSRFProtect +from sqlalchemy import inspect, text +from sqlalchemy.exc import IntegrityError, OperationalError + +from .config import Config +from .models import db, User, AppSetting +from .scheduler import init_scheduler + +APP_VERSION = "1.5" + +login_manager = LoginManager() +csrf = CSRFProtect() + + +def _setup_logging(): + logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", + ) + + +def _run_migrations(app: Flask): + """Lightweight, additive schema migrations so an existing DB can be + upgraded in place (unzip-over-the-top) without any data loss. + + Only ADDs missing tables/columns — never drops or alters existing data. + Every step is idempotent and tolerant of a concurrent process having + already applied it (both gunicorn apps boot against the same file). + """ + with app.app_context(): + # 1. Create any brand-new tables (e.g. mail_log) and missing tables. + try: + db.create_all() + except OperationalError as e: + # Another booting process created a table at the same moment. + app.logger.info("create_all raced (ignored): %s", e) + db.session.rollback() + + # 2. Add any missing columns on the users table. + inspector = inspect(db.engine) + existing_cols = {c["name"] for c in inspector.get_columns("users")} + add_column = { + "avatar_data": "ALTER TABLE users ADD COLUMN avatar_data BLOB", + "avatar_mime": "ALTER TABLE users ADD COLUMN avatar_mime VARCHAR(64)", + } + for col, ddl in add_column.items(): + if col in existing_cols: + continue + try: + with db.engine.begin() as conn: + conn.execute(text(ddl)) + app.logger.info("Migration: added users.%s", col) + except OperationalError as e: + # "duplicate column name" — another process won the race. Fine. + app.logger.info("Add column %s raced (ignored): %s", col, e) + + +def _seed_settings(app: Flask): + """Seed default settings (mail config, IMAP, reminders) when rows are + missing. Never overwrites values an admin has already set — so upgrades + keep existing configuration. New keys (e.g. IMAP) are seeded on upgrade. + + Uses an INSERT that tolerates a concurrent process seeding the same key. + """ + with app.app_context(): + defaults = { + # ---- SMTP (outgoing) — Purelymail defaults ---- + "smtp_host": app.config.get("SMTP_HOST", "smtp.purelymail.com"), + "smtp_port": str(app.config.get("SMTP_PORT", 465)), + "smtp_username": app.config.get("SMTP_USERNAME", ""), + "smtp_password": app.config.get("SMTP_PASSWORD", ""), + "smtp_encryption": app.config.get("SMTP_ENCRYPTION", "ssl"), + "smtp_from_address": app.config.get("SMTP_FROM_ADDRESS", ""), + "smtp_from_name": app.config.get("SMTP_FROM_NAME", "AlertHub"), + # ---- IMAP (Sent-folder copy) — Purelymail defaults ---- + "imap_enabled": "true", + "imap_host": app.config.get("IMAP_HOST", "imap.purelymail.com"), + "imap_port": str(app.config.get("IMAP_PORT", 993)), + "imap_username": app.config.get("IMAP_USERNAME", ""), + "imap_password": app.config.get("IMAP_PASSWORD", ""), + "imap_sent_folder": app.config.get("IMAP_SENT_FOLDER", "Sent"), + # ---- Reminders ---- + "default_reminder_days": app.config.get("DEFAULT_REMINDER_DAYS", "30,14,7,1"), + } + for k, v in defaults.items(): + if AppSetting.get(k) is not None: + continue + try: + row = AppSetting(key=k, value=v) + db.session.add(row) + db.session.commit() + except IntegrityError: + # A concurrent boot inserted this key first — that's fine. + db.session.rollback() + + +def _initialise_database(app: Flask): + """Run migrations + seeding exactly once at a time across processes. + + The container starts several worker processes that all import the app and + hit the shared SQLite file simultaneously. A cross-process file lock + serialises initialisation so concurrent DDL/seeding can't collide. + """ + import fcntl # POSIX-only; the container is Linux. + + db_dir = os.path.dirname(app.config["DATABASE_PATH"]) or "." + os.makedirs(db_dir, exist_ok=True) + lock_path = os.path.join(db_dir, ".alerthub-init.lock") + + try: + lock_file = open(lock_path, "w") + except OSError: + # If we somehow can't open the lock file, fall back to unlocked init; + # the per-step tolerance above still guards against most races. + _run_migrations(app) + _seed_settings(app) + return + + with lock_file: + try: + fcntl.flock(lock_file, fcntl.LOCK_EX) + except OSError: + pass # advisory lock unavailable — proceed; steps are tolerant. + try: + _run_migrations(app) + _seed_settings(app) + finally: + try: + fcntl.flock(lock_file, fcntl.LOCK_UN) + except OSError: + pass + + +def create_app(variant: str = "frontoffice") -> Flask: + """Build the unified Flask app (frontoffice + /backoffice on one port). + + ``variant`` is accepted for backwards compatibility with the existing WSGI + entry points but no longer restricts which blueprints are registered. + """ + if variant not in ("frontoffice", "backoffice"): + variant = "frontoffice" + + _setup_logging() + + app = Flask( + __name__, + template_folder="templates", + static_folder="static", + ) + app.config.from_object(Config) + app.config["APP_VARIANT"] = variant + + # Make sure data dir exists + os.makedirs(os.path.dirname(app.config["DATABASE_PATH"]), exist_ok=True) + + db.init_app(app) + csrf.init_app(app) + login_manager.init_app(app) + login_manager.login_view = "auth.login" + login_manager.login_message = "Please sign in to continue." + login_manager.login_message_category = "info" + + @login_manager.user_loader + def load_user(user_id): + try: + return db.session.get(User, int(user_id)) + except (TypeError, ValueError): + return None + + # ---- Blueprints ---- + # Unified app: the frontoffice lives at the root and the backoffice is + # mounted under /backoffice, so both are served on the SAME port. Access to + # backoffice views is still restricted to admins by @admin_required. + from .routes_auth import auth_bp + from .routes_main import main_bp + from .routes_backoffice import backoffice_bp + + app.register_blueprint(auth_bp) + app.register_blueprint(main_bp) + app.register_blueprint(backoffice_bp, url_prefix="/backoffice") + + # ---- Error handlers ---- + @app.errorhandler(403) + def forbidden(_): + return render_template("error.html", code=403, message="You don't have permission to view this page."), 403 + + @app.errorhandler(404) + def not_found(_): + return render_template("error.html", code=404, message="Page not found."), 404 + + @app.errorhandler(500) + def server_error(_): + return render_template("error.html", code=500, message="Internal server error."), 500 + + # ---- Security headers ---- + @app.after_request + def add_security_headers(response): + response.headers.setdefault("X-Content-Type-Options", "nosniff") + response.headers.setdefault("X-Frame-Options", "DENY") + response.headers.setdefault("Referrer-Policy", "strict-origin-when-cross-origin") + response.headers.setdefault("Permissions-Policy", "geolocation=(), microphone=(), camera=()") + # Reasonable CSP for our self-hosted assets + the QR data URI + response.headers.setdefault( + "Content-Security-Policy", + "default-src 'self'; img-src 'self' data:; " + "style-src 'self' 'unsafe-inline'; script-src 'self'; " + "font-src 'self' data:; frame-ancestors 'none'; base-uri 'self'; form-action 'self'" + ) + return response + + # ---- Template context: brand + which area we're in ---- + @app.context_processor + def inject_globals(): + # The backoffice is now mounted under /backoffice on the same origin, + # so cross-links are simple same-site URLs and "is_backoffice" is + # determined by which blueprint served the current request. + is_bo = (request.blueprint == "backoffice") if request else False + try: + backoffice_url = url_for("backoffice.dashboard") + frontoffice_url = url_for("main.index") + except Exception: + backoffice_url = "/backoffice/" + frontoffice_url = "/" + return { + "app_variant": "backoffice" if is_bo else "frontoffice", + "brand_name": "AlertHub", + "is_backoffice": is_bo, + "app_version": APP_VERSION, + "footer_text": "© 2026 Martinhal IT - Joao Vaz - Version " + APP_VERSION, + "backoffice_url": backoffice_url, + "frontoffice_url": frontoffice_url, + } + + # ---- Initialise DB (additive migrations) + seed default settings. + # Serialised across the container's worker processes via a file lock. ---- + _initialise_database(app) + + # ---- First-run guard: if there are no users yet, force the operator + # through the "create administrator" setup screen before anything else. ---- + app._alerthub_has_users = False + + @app.before_request + def _require_initial_setup(): + if app._alerthub_has_users: + return None + # Allow static assets and the setup endpoint itself through. + if request.endpoint in ("static", "auth.setup"): + return None + try: + if User.query.count() > 0: + app._alerthub_has_users = True + return None + except Exception: + return None + return redirect(url_for("auth.setup")) + + # ---- Scheduler. init_scheduler() is idempotent (guarded by a module-level + # singleton), and the container runs a single Gunicorn worker, so exactly + # one reminder scheduler runs regardless of how many times this is called. ---- + init_scheduler(app) + + return app diff --git a/app/auth_utils.py b/app/auth_utils.py new file mode 100644 index 0000000..ecdf567 --- /dev/null +++ b/app/auth_utils.py @@ -0,0 +1,37 @@ +"""Auth helpers and decorators.""" +from functools import wraps +from flask import abort, redirect, url_for, session, flash, request +from flask_login import current_user + + +def admin_required(view): + @wraps(view) + def wrapped(*args, **kwargs): + if not current_user.is_authenticated: + return redirect(url_for("auth.login", next=request.full_path)) + if not current_user.is_admin: + abort(403) + if session.get("mfa_pending"): + return redirect(url_for("auth.mfa_challenge")) + return view(*args, **kwargs) + return wrapped + + +def login_required_full(view): + """Like flask_login.login_required, but also blocks users with pending MFA.""" + @wraps(view) + def wrapped(*args, **kwargs): + if not current_user.is_authenticated: + return redirect(url_for("auth.login", next=request.full_path)) + if session.get("mfa_pending"): + return redirect(url_for("auth.mfa_challenge")) + return view(*args, **kwargs) + return wrapped + + +def get_remote_ip() -> str: + """Best-effort IP detection for audit logs.""" + fwd = request.headers.get("X-Forwarded-For", "") + if fwd: + return fwd.split(",")[0].strip() + return request.remote_addr or "" diff --git a/app/config.py b/app/config.py new file mode 100644 index 0000000..ffa9a56 --- /dev/null +++ b/app/config.py @@ -0,0 +1,81 @@ +"""Configuration loaded from environment variables.""" +import os +from datetime import timedelta + + +def _bool(val: str, default: bool = False) -> bool: + if val is None: + return default + return val.strip().lower() in ("1", "true", "yes", "on") + + +class Config: + SECRET_KEY = os.environ.get("SECRET_KEY", "dev-only-replace-me") + FLASK_ENV = os.environ.get("FLASK_ENV", "production") + + DATABASE_PATH = os.environ.get("DATABASE_PATH", "/data/alerthub.db") + SQLALCHEMY_DATABASE_URI = f"sqlite:///{DATABASE_PATH}" + SQLALCHEMY_TRACK_MODIFICATIONS = False + SQLALCHEMY_ENGINE_OPTIONS = { + "pool_pre_ping": True, + # Wait (instead of erroring) when another process holds the SQLite + # write lock — both gunicorn apps share one database file. + "connect_args": {"timeout": 30}, + } + + # Bootstrap admin (only used on first DB init) + INITIAL_ADMIN_USERNAME = os.environ.get("INITIAL_ADMIN_USERNAME", "admin") + INITIAL_ADMIN_EMAIL = os.environ.get("INITIAL_ADMIN_EMAIL", "admin@example.com") + INITIAL_ADMIN_PASSWORD = os.environ.get("INITIAL_ADMIN_PASSWORD", "ChangeMeOnFirstLogin!") + + # Session security + SESSION_COOKIE_SECURE = _bool(os.environ.get("SESSION_COOKIE_SECURE"), False) + SESSION_COOKIE_HTTPONLY = True + SESSION_COOKIE_SAMESITE = "Lax" + PERMANENT_SESSION_LIFETIME = timedelta( + hours=int(os.environ.get("SESSION_LIFETIME_HOURS", "8")) + ) + + PASSWORD_MIN_LENGTH = int(os.environ.get("PASSWORD_MIN_LENGTH", "10")) + + # MFA + MFA_ISSUER_NAME = os.environ.get("MFA_ISSUER_NAME", "AlertHub") + + # Scheduler + SCHEDULER_INTERVAL_MINUTES = int(os.environ.get("SCHEDULER_INTERVAL_MINUTES", "60")) + + # Default reminder lead times in days + DEFAULT_REMINDER_DAYS = os.environ.get("DEFAULT_REMINDER_DAYS", "30,14,7,1") + + # SMTP defaults (seed only — admins edit live values via backoffice) + # Purelymail outgoing: smtp.purelymail.com : 465 (SSL/TLS) + SMTP_HOST = os.environ.get("SMTP_HOST", "smtp.purelymail.com") + SMTP_PORT = int(os.environ.get("SMTP_PORT", "465")) + SMTP_USERNAME = os.environ.get("SMTP_USERNAME", "") + SMTP_PASSWORD = os.environ.get("SMTP_PASSWORD", "") + SMTP_ENCRYPTION = os.environ.get("SMTP_ENCRYPTION", "ssl") + SMTP_FROM_ADDRESS = os.environ.get("SMTP_FROM_ADDRESS", "") + SMTP_FROM_NAME = os.environ.get("SMTP_FROM_NAME", "AlertHub") + + # IMAP defaults (seed only) — used to save a copy of sent mail to the + # mailbox's Sent folder. Purelymail incoming: imap.purelymail.com : 993 (SSL/TLS) + IMAP_HOST = os.environ.get("IMAP_HOST", "imap.purelymail.com") + IMAP_PORT = int(os.environ.get("IMAP_PORT", "993")) + IMAP_USERNAME = os.environ.get("IMAP_USERNAME", "") + IMAP_PASSWORD = os.environ.get("IMAP_PASSWORD", "") + IMAP_SENT_FOLDER = os.environ.get("IMAP_SENT_FOLDER", "Sent") + + # Ports (used by run scripts only) + FRONTOFFICE_PORT = int(os.environ.get("FRONTOFFICE_PORT", "8080")) + BACKOFFICE_PORT = int(os.environ.get("BACKOFFICE_PORT", "9001")) + + # Optional explicit public URLs for the cross-console links in the top bar. + # If empty, the app derives them from the request host + the ports above + # (e.g. http://:9001/). Set these when behind a reverse proxy + # or when the consoles are reached at custom hostnames/paths. + BACKOFFICE_PUBLIC_URL = os.environ.get("BACKOFFICE_PUBLIC_URL", "") + FRONTOFFICE_PUBLIC_URL = os.environ.get("FRONTOFFICE_PUBLIC_URL", "") + + # Resource limits + MAX_CONTENT_LENGTH = 2 * 1024 * 1024 # 2MB upload cap + WTF_CSRF_TIME_LIMIT = 3600 diff --git a/app/forms.py b/app/forms.py new file mode 100644 index 0000000..0947d0e --- /dev/null +++ b/app/forms.py @@ -0,0 +1,139 @@ +"""WTForms — all forms get CSRF protection automatically via Flask-WTF.""" +from flask_wtf import FlaskForm +from flask_wtf.file import FileField, FileAllowed, FileRequired +from wtforms import StringField, PasswordField, BooleanField, IntegerField, SelectField, DateField, TextAreaField, SubmitField +from wtforms.validators import DataRequired, Email, Length, EqualTo, Optional, NumberRange, Regexp + + +class LoginForm(FlaskForm): + username = StringField("Username", validators=[DataRequired(), Length(min=2, max=64)]) + password = PasswordField("Password", validators=[DataRequired(), Length(min=1, max=200)]) + submit = SubmitField("Sign in") + + +class SetupAdminForm(FlaskForm): + """First-run form: create the very first administrator account.""" + username = StringField("Admin username", validators=[ + DataRequired(), + Length(min=2, max=64), + Regexp(r"^[A-Za-z0-9_.\-]+$", message="Letters, digits, underscore, dot, dash only."), + ]) + email = StringField("Admin email", validators=[DataRequired(), Email(), Length(max=255)]) + password = PasswordField("Password", validators=[ + DataRequired(), + Length(min=10, max=200, message="Min 10 characters"), + ]) + confirm = PasswordField("Confirm password", validators=[ + DataRequired(), + EqualTo("password", message="Passwords must match."), + ]) + submit = SubmitField("Create administrator") + + +class AvatarForm(FlaskForm): + avatar = FileField("Avatar image", validators=[ + FileRequired(message="Choose an image file."), + FileAllowed(["png", "jpg", "jpeg", "gif", "webp"], "Images only (png, jpg, gif, webp)."), + ]) + submit = SubmitField("Upload avatar") + + +class MfaForm(FlaskForm): + code = StringField("Authentication code", validators=[ + DataRequired(), + Length(min=6, max=6, message="Must be 6 digits"), + Regexp(r"^\d{6}$", message="Digits only"), + ]) + submit = SubmitField("Verify") + + +class MfaSetupForm(FlaskForm): + code = StringField("Verification code", validators=[ + DataRequired(), + Length(min=6, max=6), + Regexp(r"^\d{6}$"), + ]) + submit = SubmitField("Enable MFA") + + +class ChangePasswordForm(FlaskForm): + current_password = PasswordField("Current password", validators=[DataRequired()]) + new_password = PasswordField("New password", validators=[ + DataRequired(), + Length(min=10, max=200, message="Min 10 characters"), + ]) + confirm = PasswordField("Confirm new password", validators=[ + DataRequired(), + EqualTo("new_password", message="Passwords must match."), + ]) + submit = SubmitField("Change password") + + +class AlertForm(FlaskForm): + title = StringField("Title", validators=[DataRequired(), Length(max=200)]) + category = StringField("Category", validators=[Optional(), Length(max=64)]) + description = TextAreaField("Description", validators=[Optional(), Length(max=5000)]) + expiration_date = DateField("Expiration date", validators=[DataRequired()], format="%Y-%m-%d") + reminder_days = StringField( + "Reminder days (comma-separated, days before expiration)", + validators=[DataRequired(), Regexp(r"^[\d,\s]+$", message="Numbers separated by commas only")], + default="30,14,7,1", + ) + is_active = BooleanField("Active", default=True) + submit = SubmitField("Save") + + +class UserForm(FlaskForm): + username = StringField("Username", validators=[ + DataRequired(), + Length(min=2, max=64), + Regexp(r"^[A-Za-z0-9_.\-]+$", message="Letters, digits, underscore, dot, dash only."), + ]) + email = StringField("Email", validators=[DataRequired(), Email(), Length(max=255)]) + role = SelectField("Role", choices=[("user", "User (view only)"), ("admin", "Administrator")]) + is_active = BooleanField("Active", default=True) + password = PasswordField("Password (leave blank to keep current)", validators=[Optional(), Length(min=10, max=200)]) + submit = SubmitField("Save") + + +class MailConfigForm(FlaskForm): + smtp_host = StringField("SMTP Host", validators=[DataRequired(), Length(max=255)]) + smtp_port = IntegerField("Port", validators=[DataRequired(), NumberRange(min=1, max=65535)]) + smtp_username = StringField("Username", validators=[Optional(), Length(max=255)]) + smtp_password = PasswordField("Password (leave blank to keep current)", validators=[Optional(), Length(max=255)]) + smtp_encryption = SelectField("Encryption", choices=[ + ("none", "None (plain — not recommended)"), + ("starttls", "STARTTLS (port 587)"), + ("ssl", "SSL/TLS (port 465)"), + ]) + smtp_from_address = StringField("From address", validators=[DataRequired(), Email(), Length(max=255)]) + smtp_from_name = StringField("From name", validators=[Optional(), Length(max=255)]) + + # ---- IMAP: used to save a copy of every sent message to the Sent folder ---- + imap_enabled = BooleanField("Save a copy of sent mail to the mailbox (IMAP)", default=True) + imap_host = StringField("IMAP Host", validators=[Optional(), Length(max=255)]) + imap_port = IntegerField("IMAP Port", validators=[Optional(), NumberRange(min=1, max=65535)]) + imap_username = StringField("IMAP Username (blank = use SMTP username)", validators=[Optional(), Length(max=255)]) + imap_password = PasswordField("IMAP Password (blank = keep current / use SMTP password)", validators=[Optional(), Length(max=255)]) + imap_sent_folder = StringField("Sent folder name", validators=[Optional(), Length(max=128)]) + + submit = SubmitField("Save mail configuration") + + +class TestMailForm(FlaskForm): + test_recipient = StringField("Send test email to", validators=[DataRequired(), Email()]) + submit = SubmitField("Send test email") + + +class LogEmailForm(FlaskForm): + """Email an exported log (CSV attachment) to a destination address.""" + log_recipient = StringField("Email log to", validators=[DataRequired(), Email()]) + submit = SubmitField("Email log") + + +class GlobalSettingsForm(FlaskForm): + default_reminder_days = StringField( + "Default reminder days (comma-separated)", + validators=[DataRequired(), Regexp(r"^[\d,\s]+$")], + ) + submit = SubmitField("Save settings") diff --git a/app/mailer.py b/app/mailer.py new file mode 100644 index 0000000..d1edfa5 --- /dev/null +++ b/app/mailer.py @@ -0,0 +1,161 @@ +"""SMTP mailer that pulls live config from the AppSetting store. + +Every send is recorded in the MailLog (visible in the backoffice "Mail Log"), +and — when IMAP is configured — a copy of the outgoing message is appended to +the external mailbox's Sent folder so it shows up in normal mail clients. +""" +import smtplib +import imaplib +import ssl +import time +import logging +from email.message import EmailMessage +from email.utils import formataddr, formatdate +from flask_login import current_user +from .models import AppSetting, MailLog + +logger = logging.getLogger(__name__) + + +def get_mail_config() -> dict: + """Read SMTP + IMAP settings from the database.""" + return { + # ---- SMTP (outgoing) ---- + "host": AppSetting.get("smtp_host", "") or "", + "port": int(AppSetting.get("smtp_port", "465") or "465"), + "username": AppSetting.get("smtp_username", "") or "", + "password": AppSetting.get("smtp_password", "") or "", + "encryption": (AppSetting.get("smtp_encryption", "ssl") or "ssl").lower(), + "from_address": AppSetting.get("smtp_from_address", "") or "", + "from_name": AppSetting.get("smtp_from_name", "AlertHub") or "AlertHub", + # ---- IMAP (for saving a copy in the Sent folder) ---- + "imap_enabled": (AppSetting.get("imap_enabled", "true") or "true").lower() in ("1", "true", "yes", "on"), + "imap_host": AppSetting.get("imap_host", "") or "", + "imap_port": int(AppSetting.get("imap_port", "993") or "993"), + "imap_username": AppSetting.get("imap_username", "") or "", + "imap_password": AppSetting.get("imap_password", "") or "", + "imap_sent_folder": AppSetting.get("imap_sent_folder", "Sent") or "Sent", + } + + +def _build_message(cfg, to_addresses, subject, body_text, body_html=None, attachments=None) -> EmailMessage: + msg = EmailMessage() + msg["Subject"] = subject + msg["From"] = formataddr((cfg["from_name"], cfg["from_address"])) + msg["To"] = ", ".join(to_addresses) + msg["Date"] = formatdate(localtime=True) + msg.set_content(body_text) + if body_html: + msg.add_alternative(body_html, subtype="html") + for att in (attachments or []): + # att = (filename, mime_maintype, mime_subtype, bytes) + fname, maintype, subtype, data = att + msg.add_attachment(data, maintype=maintype, subtype=subtype, filename=fname) + return msg + + +def _append_to_sent(cfg, msg) -> tuple[bool, str]: + """Append a copy of the message to the mailbox's Sent folder over IMAP.""" + if not cfg.get("imap_enabled"): + return False, "disabled" + imap_host = cfg["imap_host"] or "" + imap_user = cfg["imap_username"] or cfg["username"] + imap_pass = cfg["imap_password"] or cfg["password"] + if not imap_host or not imap_user or not imap_pass: + return False, "IMAP not fully configured" + folder = cfg.get("imap_sent_folder") or "Sent" + try: + context = ssl.create_default_context() + with imaplib.IMAP4_SSL(imap_host, cfg.get("imap_port", 993), ssl_context=context, timeout=20) as imap: + imap.login(imap_user, imap_pass) + raw = msg.as_bytes() + # \Seen so it doesn't show as unread in the Sent folder + typ, _ = imap.append(folder, "(\\Seen)", imaplib.Time2Internaldate(time.time()), raw) + if typ != "OK": + return False, f"IMAP append returned {typ}" + return True, "saved" + except (imaplib.IMAP4.error, ssl.SSLError, OSError) as e: + logger.exception("IMAP append to Sent failed") + return False, f"IMAP error: {e}" + + +def _smtp_send(cfg, msg) -> tuple[bool, str]: + encryption = cfg["encryption"] + try: + if encryption == "ssl": + context = ssl.create_default_context() + with smtplib.SMTP_SSL(cfg["host"], cfg["port"], context=context, timeout=20) as server: + if cfg["username"]: + server.login(cfg["username"], cfg["password"]) + server.send_message(msg) + else: + with smtplib.SMTP(cfg["host"], cfg["port"], timeout=20) as server: + server.ehlo() + if encryption == "starttls": + context = ssl.create_default_context() + server.starttls(context=context) + server.ehlo() + if cfg["username"]: + server.login(cfg["username"], cfg["password"]) + server.send_message(msg) + return True, "ok" + except smtplib.SMTPAuthenticationError as e: + logger.exception("SMTP auth failed") + err = e.smtp_error.decode("utf-8", errors="ignore") if isinstance(e.smtp_error, bytes) else e.smtp_error + return False, f"Authentication failed: {e.smtp_code} {err}" + except (smtplib.SMTPException, ssl.SSLError, OSError) as e: + logger.exception("SMTP error") + return False, f"SMTP error: {e}" + + +def send_mail(to_addresses, subject: str, body_text: str, body_html: str | None = None, + attachments=None, kind: str = "general", triggered_by: str | None = None) -> tuple[bool, str]: + """Send an email. Returns (success, message). Never raises. + + Side effects: + - Appends a copy to the external mailbox Sent folder (IMAP) on success. + - Records the send (and Sent-copy result) in the MailLog. + """ + cfg = get_mail_config() + + # Resolve who triggered this (username) for the log. + if triggered_by is None: + try: + triggered_by = current_user.username if current_user.is_authenticated else "system" + except Exception: + triggered_by = "system" + + if not cfg["host"] or not cfg["from_address"]: + info = "SMTP not configured (host or from-address missing)." + MailLog.record(kind, to_addresses, subject, "failed", info, "n/a", triggered_by) + return False, info + + if isinstance(to_addresses, str): + to_addresses = [to_addresses] + to_addresses = [a for a in to_addresses if a] + if not to_addresses: + info = "No recipients." + MailLog.record(kind, "", subject, "failed", info, "n/a", triggered_by) + return False, info + + msg = _build_message(cfg, to_addresses, subject, body_text, body_html, attachments) + + ok, info = _smtp_send(cfg, msg) + + sent_copy = "n/a" + if ok: + copied, copy_info = _append_to_sent(cfg, msg) + if copy_info == "disabled": + sent_copy = "disabled" + elif copied: + sent_copy = "saved" + else: + sent_copy = "failed" + info = f"{info}; Sent-copy: {copy_info}" + + MailLog.record(kind, to_addresses, subject, + "sent" if ok else "failed", info, sent_copy, triggered_by) + + if ok: + return True, f"Sent to {len(to_addresses)} recipient(s)." + return False, info diff --git a/app/models.py b/app/models.py new file mode 100644 index 0000000..b125171 --- /dev/null +++ b/app/models.py @@ -0,0 +1,192 @@ +"""SQLAlchemy models.""" +from datetime import datetime, timezone +from flask_sqlalchemy import SQLAlchemy +from flask_login import UserMixin +import bcrypt +import json + +db = SQLAlchemy() + + +def utcnow(): + return datetime.now(timezone.utc) + + +class User(UserMixin, db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(64), unique=True, nullable=False, index=True) + email = db.Column(db.String(255), unique=True, nullable=False) + password_hash = db.Column(db.String(255), nullable=False) + role = db.Column(db.String(16), nullable=False, default="user") # 'user' | 'admin' + is_active = db.Column(db.Boolean, default=True, nullable=False) + + mfa_secret = db.Column(db.String(64), nullable=True) + mfa_enabled = db.Column(db.Boolean, default=False, nullable=False) + + # Optional user avatar (stored as normalised PNG bytes in the DB so it + # survives an "unzip over the top" upgrade without any external files). + avatar_data = db.Column(db.LargeBinary, nullable=True) + avatar_mime = db.Column(db.String(64), nullable=True) + + created_at = db.Column(db.DateTime, default=utcnow, nullable=False) + last_login_at = db.Column(db.DateTime, nullable=True) + failed_login_count = db.Column(db.Integer, default=0, nullable=False) + locked_until = db.Column(db.DateTime, nullable=True) + + # ---- password helpers ---- + def set_password(self, password: str) -> None: + self.password_hash = bcrypt.hashpw( + password.encode("utf-8"), bcrypt.gensalt(rounds=12) + ).decode("utf-8") + + def check_password(self, password: str) -> bool: + try: + return bcrypt.checkpw( + password.encode("utf-8"), self.password_hash.encode("utf-8") + ) + except (ValueError, TypeError): + return False + + @property + def is_admin(self) -> bool: + return self.role == "admin" + + @property + def is_locked(self) -> bool: + return self.locked_until is not None and self.locked_until > utcnow() + + @property + def has_avatar(self) -> bool: + return self.avatar_data is not None and len(self.avatar_data) > 0 + + @property + def initials(self) -> str: + return (self.username or "?")[:2].upper() + + +class Alert(db.Model): + __tablename__ = "alerts" + + id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String(200), nullable=False) + description = db.Column(db.Text, nullable=True) + category = db.Column(db.String(64), nullable=True) # e.g. 'License', 'Renewal' + expiration_date = db.Column(db.Date, nullable=False) + # JSON-encoded list of integer days, e.g. "[30, 14, 7, 1]" + reminder_days_json = db.Column(db.String(255), nullable=False, default="[30,14,7,1]") + # JSON-encoded list of days for which the reminder has already been sent (avoid dupes) + reminders_sent_json = db.Column(db.String(255), nullable=False, default="[]") + is_active = db.Column(db.Boolean, default=True, nullable=False) + created_by = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True) + created_at = db.Column(db.DateTime, default=utcnow, nullable=False) + updated_at = db.Column(db.DateTime, default=utcnow, onupdate=utcnow, nullable=False) + + creator = db.relationship("User", foreign_keys=[created_by]) + + @property + def reminder_days(self) -> list: + try: + return sorted(set(int(x) for x in json.loads(self.reminder_days_json)), reverse=True) + except (ValueError, TypeError): + return [] + + @reminder_days.setter + def reminder_days(self, value) -> None: + self.reminder_days_json = json.dumps(sorted(set(int(x) for x in value), reverse=True)) + + @property + def reminders_sent(self) -> list: + try: + return list(json.loads(self.reminders_sent_json)) + except (ValueError, TypeError): + return [] + + @reminders_sent.setter + def reminders_sent(self, value) -> None: + self.reminders_sent_json = json.dumps(list(value)) + + +class AppSetting(db.Model): + """Generic key/value settings store (mail config + misc).""" + __tablename__ = "app_settings" + + key = db.Column(db.String(64), primary_key=True) + value = db.Column(db.Text, nullable=True) + updated_at = db.Column(db.DateTime, default=utcnow, onupdate=utcnow, nullable=False) + + @classmethod + def get(cls, key: str, default=None): + row = db.session.get(cls, key) + return row.value if row is not None else default + + @classmethod + def set(cls, key: str, value: str) -> None: + row = db.session.get(cls, key) + if row is None: + row = cls(key=key, value=value) + db.session.add(row) + else: + row.value = value + db.session.commit() + + @classmethod + def get_all(cls) -> dict: + return {row.key: row.value for row in cls.query.all()} + + +class AuditLog(db.Model): + __tablename__ = "audit_log" + + id = db.Column(db.Integer, primary_key=True) + timestamp = db.Column(db.DateTime, default=utcnow, nullable=False, index=True) + user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True) + username = db.Column(db.String(64), nullable=True) # denormalised for survival after delete + action = db.Column(db.String(64), nullable=False) + details = db.Column(db.Text, nullable=True) + ip_address = db.Column(db.String(45), nullable=True) + + @staticmethod + def record(action: str, user=None, details: str = "", ip: str = ""): + entry = AuditLog( + action=action, + user_id=getattr(user, "id", None), + username=getattr(user, "username", None), + details=details[:1000] if details else None, + ip_address=ip, + ) + db.session.add(entry) + db.session.commit() + + +class MailLog(db.Model): + """Record of every outbound email dispatched by the platform.""" + __tablename__ = "mail_log" + + id = db.Column(db.Integer, primary_key=True) + timestamp = db.Column(db.DateTime, default=utcnow, nullable=False, index=True) + kind = db.Column(db.String(32), nullable=False, default="general") # reminder | test | log-export | general + recipients = db.Column(db.Text, nullable=True) + subject = db.Column(db.String(500), nullable=True) + status = db.Column(db.String(16), nullable=False, default="unknown") # sent | failed + info = db.Column(db.Text, nullable=True) + sent_copy = db.Column(db.String(24), nullable=False, default="n/a") # saved | failed | disabled | n/a + triggered_by = db.Column(db.String(64), nullable=True) # username or "system" + + @staticmethod + def record(kind, recipients, subject, status, info="", sent_copy="n/a", triggered_by="system"): + if isinstance(recipients, (list, tuple)): + recipients = ", ".join(str(r) for r in recipients) + entry = MailLog( + kind=(kind or "general")[:32], + recipients=(recipients or "")[:2000], + subject=(subject or "")[:500], + status=(status or "unknown")[:16], + info=(info or "")[:2000], + sent_copy=(sent_copy or "n/a")[:24], + triggered_by=(triggered_by or "system")[:64], + ) + db.session.add(entry) + db.session.commit() + return entry diff --git a/app/routes_auth.py b/app/routes_auth.py new file mode 100644 index 0000000..5a094c0 --- /dev/null +++ b/app/routes_auth.py @@ -0,0 +1,308 @@ +"""Authentication routes: first-run setup, login, MFA, logout, password, avatar.""" +import io +import base64 +from datetime import timedelta +import pyotp +import qrcode +from PIL import Image, ImageOps +from flask import ( + Blueprint, render_template, redirect, url_for, flash, request, session, + current_app, abort, Response, +) +from flask_login import login_user, logout_user, login_required, current_user + +from .models import db, User, AuditLog, utcnow +from .forms import LoginForm, MfaForm, MfaSetupForm, ChangePasswordForm, SetupAdminForm, AvatarForm +from .auth_utils import get_remote_ip, login_required_full + +auth_bp = Blueprint("auth", __name__) + + +AVATAR_SIZE = 256 + + +def _normalise_avatar(file_storage) -> bytes: + """Open an uploaded image, square-crop + resize, return PNG bytes. + + Raises ValueError if the file is not a usable image. + """ + try: + img = Image.open(file_storage.stream) + img = ImageOps.exif_transpose(img) + img = img.convert("RGBA") if img.mode in ("RGBA", "LA", "P") else img.convert("RGB") + # Center-crop to a square, then resize. + img = ImageOps.fit(img, (AVATAR_SIZE, AVATAR_SIZE), method=Image.LANCZOS) + if img.mode == "RGBA": + background = Image.new("RGBA", img.size, (255, 255, 255, 0)) + img = Image.alpha_composite(background, img) + buf = io.BytesIO() + img.save(buf, format="PNG", optimize=True) + return buf.getvalue() + except Exception as exc: # noqa: BLE001 - surface a clean error to the user + raise ValueError(f"Could not process image: {exc}") + + +# --------------------------------------------------------------------------- # +# First-run setup — create the initial administrator +# --------------------------------------------------------------------------- # +@auth_bp.route("/setup", methods=["GET", "POST"]) +def setup(): + # Only available while there are zero users. Once an admin exists, hide it. + if User.query.count() > 0: + return redirect(url_for("auth.login")) + + form = SetupAdminForm() + if form.validate_on_submit(): + admin = User( + username=form.username.data.strip(), + email=form.email.data.strip().lower(), + role="admin", + is_active=True, + ) + admin.set_password(form.password.data) + db.session.add(admin) + db.session.commit() + current_app._alerthub_has_users = True + AuditLog.record("setup.admin_created", user=admin, details=f"username={admin.username}", ip=get_remote_ip()) + flash("Administrator account created. Please sign in.", "success") + return redirect(url_for("auth.login")) + + return render_template("setup.html", form=form) + + +# --------------------------------------------------------------------------- # +# Login +# --------------------------------------------------------------------------- # +@auth_bp.route("/login", methods=["GET", "POST"]) +def login(): + if current_user.is_authenticated and not session.get("mfa_pending"): + return redirect(url_for("main.index")) + + form = LoginForm() + if form.validate_on_submit(): + user = User.query.filter_by(username=form.username.data.strip()).first() + ip = get_remote_ip() + + if user is None or not user.check_password(form.password.data): + # Increment failed login if user exists + if user is not None: + user.failed_login_count = (user.failed_login_count or 0) + 1 + if user.failed_login_count >= 5: + user.locked_until = utcnow() + timedelta(minutes=15) + user.failed_login_count = 0 + AuditLog.record("login.lockout", user=user, details=f"5 failed attempts", ip=ip) + db.session.commit() + AuditLog.record("login.fail", details=f"username={form.username.data}", ip=ip) + flash("Invalid credentials.", "error") + return render_template("login.html", form=form), 401 + + if not user.is_active: + flash("Account is disabled.", "error") + AuditLog.record("login.disabled", user=user, ip=ip) + return render_template("login.html", form=form), 403 + + if user.is_locked: + flash("Account is temporarily locked due to failed login attempts. Try again later.", "error") + AuditLog.record("login.locked", user=user, ip=ip) + return render_template("login.html", form=form), 423 + + # Password OK — reset counters + user.failed_login_count = 0 + user.locked_until = None + + if user.mfa_enabled: + # Stage 1 success — but require MFA before granting full session + session["mfa_pending"] = True + session["pending_user_id"] = user.id + db.session.commit() + AuditLog.record("login.password_ok", user=user, ip=ip) + return redirect(url_for("auth.mfa_challenge")) + + # No MFA — full login + login_user(user, remember=False) + session.permanent = True + session.pop("mfa_pending", None) + session.pop("pending_user_id", None) + user.last_login_at = utcnow() + db.session.commit() + AuditLog.record("login.success", user=user, ip=ip) + + next_url = request.args.get("next") or url_for("main.index") + return redirect(next_url) + + return render_template("login.html", form=form) + + +# --------------------------------------------------------------------------- # +# MFA challenge (after password OK) +# --------------------------------------------------------------------------- # +@auth_bp.route("/mfa", methods=["GET", "POST"]) +def mfa_challenge(): + if not session.get("mfa_pending"): + return redirect(url_for("auth.login")) + + user_id = session.get("pending_user_id") + user = db.session.get(User, user_id) if user_id else None + if user is None or not user.mfa_enabled or not user.mfa_secret: + session.pop("mfa_pending", None) + session.pop("pending_user_id", None) + return redirect(url_for("auth.login")) + + form = MfaForm() + if form.validate_on_submit(): + totp = pyotp.TOTP(user.mfa_secret) + if totp.verify(form.code.data, valid_window=1): + login_user(user, remember=False) + session.permanent = True + session.pop("mfa_pending", None) + session.pop("pending_user_id", None) + user.last_login_at = utcnow() + db.session.commit() + AuditLog.record("login.mfa_ok", user=user, ip=get_remote_ip()) + return redirect(url_for("main.index")) + flash("Invalid authentication code.", "error") + AuditLog.record("login.mfa_fail", user=user, ip=get_remote_ip()) + + return render_template("mfa.html", form=form) + + +# --------------------------------------------------------------------------- # +# Logout +# --------------------------------------------------------------------------- # +@auth_bp.route("/logout", methods=["POST"]) +@login_required +def logout(): + AuditLog.record("logout", user=current_user, ip=get_remote_ip()) + logout_user() + session.clear() + flash("Signed out.", "success") + return redirect(url_for("auth.login")) + + +# --------------------------------------------------------------------------- # +# Account: change password / set up MFA +# --------------------------------------------------------------------------- # +@auth_bp.route("/account", methods=["GET"]) +@login_required_full +def account(): + return render_template( + "account.html", + password_form=ChangePasswordForm(), + mfa_setup_form=MfaSetupForm(), + avatar_form=AvatarForm(), + ) + + +# --------------------------------------------------------------------------- # +# Avatar: upload / remove / serve +# --------------------------------------------------------------------------- # +@auth_bp.route("/account/avatar", methods=["POST"]) +@login_required_full +def avatar_upload(): + form = AvatarForm() + if form.validate_on_submit(): + try: + png_bytes = _normalise_avatar(form.avatar.data) + except ValueError as e: + flash(str(e), "error") + return redirect(url_for("auth.account")) + current_user.avatar_data = png_bytes + current_user.avatar_mime = "image/png" + db.session.commit() + AuditLog.record("avatar.update", user=current_user, ip=get_remote_ip()) + flash("Avatar updated.", "success") + else: + for errs in form.errors.values(): + for e in errs: + flash(e, "error") + return redirect(url_for("auth.account")) + + +@auth_bp.route("/account/avatar/remove", methods=["POST"]) +@login_required_full +def avatar_remove(): + current_user.avatar_data = None + current_user.avatar_mime = None + db.session.commit() + AuditLog.record("avatar.remove", user=current_user, ip=get_remote_ip()) + flash("Avatar removed.", "info") + return redirect(url_for("auth.account")) + + +@auth_bp.route("/avatar/", methods=["GET"]) +@login_required_full +def avatar(user_id: int): + user = db.session.get(User, user_id) + if user is None or not user.has_avatar: + abort(404) + resp = Response(user.avatar_data, mimetype=user.avatar_mime or "image/png") + resp.headers["Cache-Control"] = "private, max-age=60" + return resp + + +@auth_bp.route("/account/password", methods=["POST"]) +@login_required_full +def change_password(): + form = ChangePasswordForm() + if form.validate_on_submit(): + if not current_user.check_password(form.current_password.data): + flash("Current password is incorrect.", "error") + else: + current_user.set_password(form.new_password.data) + db.session.commit() + AuditLog.record("password.change", user=current_user, ip=get_remote_ip()) + flash("Password updated.", "success") + return redirect(url_for("auth.account")) + return render_template( + "account.html", + password_form=form, + mfa_setup_form=MfaSetupForm(), + ) + + +@auth_bp.route("/account/mfa/setup", methods=["GET", "POST"]) +@login_required_full +def mfa_setup(): + """Generate a new TOTP secret + QR code; user confirms with a 6-digit code.""" + if current_user.mfa_enabled: + flash("MFA is already enabled.", "info") + return redirect(url_for("auth.account")) + + # Keep candidate secret in session until verified + if "mfa_setup_secret" not in session: + session["mfa_setup_secret"] = pyotp.random_base32() + + secret = session["mfa_setup_secret"] + issuer = current_app.config.get("MFA_ISSUER_NAME", "AlertHub") + uri = pyotp.TOTP(secret).provisioning_uri(name=current_user.email, issuer_name=issuer) + + # Generate QR code as base64 PNG + img = qrcode.make(uri) + buf = io.BytesIO() + img.save(buf, format="PNG") + qr_b64 = base64.b64encode(buf.getvalue()).decode("ascii") + + form = MfaSetupForm() + if form.validate_on_submit(): + if pyotp.TOTP(secret).verify(form.code.data, valid_window=1): + current_user.mfa_secret = secret + current_user.mfa_enabled = True + db.session.commit() + session.pop("mfa_setup_secret", None) + AuditLog.record("mfa.enabled", user=current_user, ip=get_remote_ip()) + flash("MFA enabled successfully. You will be prompted for a code on next sign-in.", "success") + return redirect(url_for("auth.account")) + flash("Verification code did not match. Try again.", "error") + + return render_template("mfa_setup.html", form=form, qr_b64=qr_b64, secret=secret) + + +@auth_bp.route("/account/mfa/disable", methods=["POST"]) +@login_required_full +def mfa_disable(): + current_user.mfa_enabled = False + current_user.mfa_secret = None + db.session.commit() + AuditLog.record("mfa.disabled", user=current_user, ip=get_remote_ip()) + flash("MFA disabled.", "info") + return redirect(url_for("auth.account")) diff --git a/app/routes_backoffice.py b/app/routes_backoffice.py new file mode 100644 index 0000000..baea9a8 --- /dev/null +++ b/app/routes_backoffice.py @@ -0,0 +1,426 @@ +"""Backoffice routes — admins only. +Sections: + 3. Manage Users & Permissions + - Mail / SMTP configuration + - Global settings (default reminder days, scheduler, run-now) + - Audit log viewer +""" +import csv +import io +from datetime import date, datetime +from flask import Blueprint, render_template, redirect, url_for, flash, request, abort, current_app, Response +from flask_login import current_user + +from .models import db, User, Alert, AppSetting, AuditLog, MailLog +from .forms import UserForm, MailConfigForm, TestMailForm, GlobalSettingsForm, LogEmailForm +from .auth_utils import admin_required, get_remote_ip +from .mailer import send_mail, get_mail_config +from .scheduler import trigger_now + +backoffice_bp = Blueprint("backoffice", __name__) + + +def _csv_response(filename: str, header: list, rows) -> Response: + buf = io.StringIO() + writer = csv.writer(buf) + writer.writerow(header) + for r in rows: + writer.writerow(r) + data = buf.getvalue() + return Response( + data, + mimetype="text/csv", + headers={"Content-Disposition": f'attachment; filename="{filename}"'}, + ) + + +def _audit_rows(): + for e in AuditLog.query.order_by(AuditLog.timestamp.desc()).all(): + yield [ + e.timestamp.strftime("%Y-%m-%d %H:%M:%S") if e.timestamp else "", + e.username or "", + e.action or "", + e.details or "", + e.ip_address or "", + ] + + +def _maillog_rows(): + for m in MailLog.query.order_by(MailLog.timestamp.desc()).all(): + yield [ + m.timestamp.strftime("%Y-%m-%d %H:%M:%S") if m.timestamp else "", + m.kind or "", + m.recipients or "", + m.subject or "", + m.status or "", + m.sent_copy or "", + m.triggered_by or "", + m.info or "", + ] + + +AUDIT_CSV_HEADER = ["timestamp_utc", "user", "action", "details", "ip"] +MAILLOG_CSV_HEADER = ["timestamp_utc", "kind", "recipients", "subject", "status", "sent_copy", "triggered_by", "info"] + + +def _build_csv_string(header, rows) -> str: + buf = io.StringIO() + writer = csv.writer(buf) + writer.writerow(header) + for r in rows: + writer.writerow(r) + return buf.getvalue() + + +# --------------------------------------------------------------------------- # +# Backoffice home — admin dashboard +# --------------------------------------------------------------------------- # +@backoffice_bp.route("/") +@admin_required +def dashboard(): + user_count = User.query.count() + admin_count = User.query.filter_by(role="admin").count() + active_alerts = Alert.query.filter_by(is_active=True).count() + today = date.today() + expiring_soon = Alert.query.filter( + Alert.is_active.is_(True), + Alert.expiration_date >= today, + ).order_by(Alert.expiration_date.asc()).limit(5).all() + recent_audit = AuditLog.query.order_by(AuditLog.timestamp.desc()).limit(10).all() + return render_template( + "backoffice/dashboard.html", + user_count=user_count, + admin_count=admin_count, + active_alerts=active_alerts, + expiring_soon=expiring_soon, + recent_audit=recent_audit, + today=today, + ) + + +# --------------------------------------------------------------------------- # +# User management +# --------------------------------------------------------------------------- # +@backoffice_bp.route("/users") +@admin_required +def users_list(): + users = User.query.order_by(User.username.asc()).all() + return render_template("backoffice/users_list.html", users=users) + + +@backoffice_bp.route("/users/new", methods=["GET", "POST"]) +@admin_required +def user_new(): + form = UserForm() + if form.validate_on_submit(): + if not form.password.data: + flash("Password is required for a new user.", "error") + elif User.query.filter((User.username == form.username.data) | (User.email == form.email.data)).first(): + flash("Username or email already exists.", "error") + elif len(form.password.data) < current_app.config["PASSWORD_MIN_LENGTH"]: + flash(f"Password must be at least {current_app.config['PASSWORD_MIN_LENGTH']} characters.", "error") + else: + user = User( + username=form.username.data.strip(), + email=form.email.data.strip().lower(), + role=form.role.data, + is_active=form.is_active.data, + ) + user.set_password(form.password.data) + db.session.add(user) + db.session.commit() + AuditLog.record("user.create", user=current_user, details=f"new={user.username} role={user.role}", ip=get_remote_ip()) + flash(f"User '{user.username}' created.", "success") + return redirect(url_for("backoffice.users_list")) + return render_template("backoffice/user_form.html", form=form, user=None) + + +@backoffice_bp.route("/users//edit", methods=["GET", "POST"]) +@admin_required +def user_edit(user_id: int): + user = db.session.get(User, user_id) + if user is None: + abort(404) + form = UserForm(obj=user) + if form.validate_on_submit(): + # Username / email uniqueness check (excluding self) + clash = User.query.filter( + User.id != user.id, + (User.username == form.username.data) | (User.email == form.email.data.lower()) + ).first() + if clash: + flash("Another user already has that username or email.", "error") + else: + # Prevent self-demotion of last admin / self-deactivation lock-out + if user.id == current_user.id: + if form.role.data != "admin": + flash("You cannot demote yourself.", "error") + return render_template("backoffice/user_form.html", form=form, user=user) + if not form.is_active.data: + flash("You cannot deactivate your own account.", "error") + return render_template("backoffice/user_form.html", form=form, user=user) + + # Prevent removing the last active admin + if user.role == "admin" and form.role.data != "admin": + other_admins = User.query.filter(User.id != user.id, User.role == "admin", User.is_active.is_(True)).count() + if other_admins == 0: + flash("Cannot demote the last administrator.", "error") + return render_template("backoffice/user_form.html", form=form, user=user) + + user.username = form.username.data.strip() + user.email = form.email.data.strip().lower() + user.role = form.role.data + user.is_active = form.is_active.data + if form.password.data: + if len(form.password.data) < current_app.config["PASSWORD_MIN_LENGTH"]: + flash(f"Password must be at least {current_app.config['PASSWORD_MIN_LENGTH']} characters.", "error") + return render_template("backoffice/user_form.html", form=form, user=user) + user.set_password(form.password.data) + db.session.commit() + AuditLog.record("user.update", user=current_user, details=f"id={user.id} username={user.username}", ip=get_remote_ip()) + flash("User updated.", "success") + return redirect(url_for("backoffice.users_list")) + return render_template("backoffice/user_form.html", form=form, user=user) + + +@backoffice_bp.route("/users//delete", methods=["POST"]) +@admin_required +def user_delete(user_id: int): + user = db.session.get(User, user_id) + if user is None: + abort(404) + if user.id == current_user.id: + flash("You cannot delete your own account.", "error") + return redirect(url_for("backoffice.users_list")) + if user.role == "admin": + other_admins = User.query.filter(User.id != user.id, User.role == "admin", User.is_active.is_(True)).count() + if other_admins == 0: + flash("Cannot delete the last administrator.", "error") + return redirect(url_for("backoffice.users_list")) + username = user.username + db.session.delete(user) + db.session.commit() + AuditLog.record("user.delete", user=current_user, details=f"deleted={username}", ip=get_remote_ip()) + flash(f"User '{username}' deleted.", "success") + return redirect(url_for("backoffice.users_list")) + + +@backoffice_bp.route("/users//reset-mfa", methods=["POST"]) +@admin_required +def user_reset_mfa(user_id: int): + user = db.session.get(User, user_id) + if user is None: + abort(404) + user.mfa_enabled = False + user.mfa_secret = None + db.session.commit() + AuditLog.record("user.mfa_reset", user=current_user, details=f"target={user.username}", ip=get_remote_ip()) + flash(f"MFA reset for '{user.username}'. They can re-enrol on next login.", "info") + return redirect(url_for("backoffice.users_list")) + + +@backoffice_bp.route("/users//unlock", methods=["POST"]) +@admin_required +def user_unlock(user_id: int): + user = db.session.get(User, user_id) + if user is None: + abort(404) + user.locked_until = None + user.failed_login_count = 0 + db.session.commit() + AuditLog.record("user.unlock", user=current_user, details=f"target={user.username}", ip=get_remote_ip()) + flash(f"Unlocked '{user.username}'.", "success") + return redirect(url_for("backoffice.users_list")) + + +# --------------------------------------------------------------------------- # +# Mail configuration +# --------------------------------------------------------------------------- # +@backoffice_bp.route("/mail-config", methods=["GET", "POST"]) +@admin_required +def mail_config(): + form = MailConfigForm() + test_form = TestMailForm() + + if request.method == "GET": + cfg = get_mail_config() + form.smtp_host.data = cfg["host"] + form.smtp_port.data = cfg["port"] + form.smtp_username.data = cfg["username"] + form.smtp_encryption.data = cfg["encryption"] + form.smtp_from_address.data = cfg["from_address"] + form.smtp_from_name.data = cfg["from_name"] + form.imap_enabled.data = cfg["imap_enabled"] + form.imap_host.data = cfg["imap_host"] + form.imap_port.data = cfg["imap_port"] + form.imap_username.data = cfg["imap_username"] + form.imap_sent_folder.data = cfg["imap_sent_folder"] + + if form.submit.data and form.validate_on_submit(): + AppSetting.set("smtp_host", form.smtp_host.data.strip()) + AppSetting.set("smtp_port", str(form.smtp_port.data)) + AppSetting.set("smtp_username", form.smtp_username.data.strip()) + if form.smtp_password.data: + AppSetting.set("smtp_password", form.smtp_password.data) + AppSetting.set("smtp_encryption", form.smtp_encryption.data) + AppSetting.set("smtp_from_address", form.smtp_from_address.data.strip().lower()) + AppSetting.set("smtp_from_name", (form.smtp_from_name.data or "").strip() or "AlertHub") + # ---- IMAP (Sent-folder copy) ---- + AppSetting.set("imap_enabled", "true" if form.imap_enabled.data else "false") + AppSetting.set("imap_host", (form.imap_host.data or "").strip()) + AppSetting.set("imap_port", str(form.imap_port.data or 993)) + AppSetting.set("imap_username", (form.imap_username.data or "").strip()) + if form.imap_password.data: + AppSetting.set("imap_password", form.imap_password.data) + AppSetting.set("imap_sent_folder", (form.imap_sent_folder.data or "").strip() or "Sent") + AuditLog.record("mailconfig.update", user=current_user, ip=get_remote_ip()) + flash("Mail configuration saved.", "success") + return redirect(url_for("backoffice.mail_config")) + + return render_template("backoffice/mail_config.html", form=form, test_form=test_form) + + +@backoffice_bp.route("/mail-config/test", methods=["POST"]) +@admin_required +def mail_test(): + test_form = TestMailForm() + if test_form.validate_on_submit(): + ok, info = send_mail( + test_form.test_recipient.data.strip(), + "[AlertHub] Test email", + "This is a test email from AlertHub. If you received this, SMTP is configured correctly.", + "

This is a test email from AlertHub. If you received this, SMTP is configured correctly.

", + kind="test", + triggered_by=current_user.username, + ) + AuditLog.record("mailconfig.test", user=current_user, details=f"to={test_form.test_recipient.data} ok={ok}", ip=get_remote_ip()) + flash(("Test email sent. " if ok else "Test failed: ") + info, "success" if ok else "error") + else: + flash("Provide a valid recipient email address.", "error") + return redirect(url_for("backoffice.mail_config")) + + +# --------------------------------------------------------------------------- # +# Global settings + scheduler trigger +# --------------------------------------------------------------------------- # +@backoffice_bp.route("/settings", methods=["GET", "POST"]) +@admin_required +def settings(): + form = GlobalSettingsForm() + if request.method == "GET": + form.default_reminder_days.data = AppSetting.get("default_reminder_days", "30,14,7,1") + if form.validate_on_submit(): + cleaned = ",".join( + str(x) for x in sorted({int(p.strip()) for p in form.default_reminder_days.data.split(",") if p.strip().isdigit()}, reverse=True) + ) + if not cleaned: + flash("Please enter at least one valid day value.", "error") + else: + AppSetting.set("default_reminder_days", cleaned) + AuditLog.record("settings.update", user=current_user, details=f"default_reminder_days={cleaned}", ip=get_remote_ip()) + flash("Settings saved.", "success") + return redirect(url_for("backoffice.settings")) + scheduler_interval = current_app.config.get("SCHEDULER_INTERVAL_MINUTES", 60) + return render_template("backoffice/settings.html", form=form, scheduler_interval=scheduler_interval) + + +@backoffice_bp.route("/settings/run-check", methods=["POST"]) +@admin_required +def run_check_now(): + """Manually run the reminder scan once.""" + try: + trigger_now(current_app._get_current_object()) + AuditLog.record("scheduler.manual_run", user=current_user, ip=get_remote_ip()) + flash("Reminder check executed.", "success") + except Exception as e: + current_app.logger.exception("Manual scheduler run failed") + flash(f"Reminder check failed: {e}", "error") + return redirect(url_for("backoffice.settings")) + + +# --------------------------------------------------------------------------- # +# Audit log viewer +# --------------------------------------------------------------------------- # +@backoffice_bp.route("/audit-log") +@admin_required +def audit_log(): + page = max(1, int(request.args.get("page", 1))) + per_page = 50 + pagination = AuditLog.query.order_by(AuditLog.timestamp.desc()).paginate(page=page, per_page=per_page, error_out=False) + return render_template("backoffice/audit_log.html", pagination=pagination, log_email_form=LogEmailForm()) + + +@backoffice_bp.route("/audit-log/export") +@admin_required +def audit_log_export(): + stamp = datetime.utcnow().strftime("%Y%m%d-%H%M%S") + AuditLog.record("auditlog.export", user=current_user, ip=get_remote_ip()) + return _csv_response(f"alerthub-audit-log-{stamp}.csv", AUDIT_CSV_HEADER, _audit_rows()) + + +@backoffice_bp.route("/audit-log/email", methods=["POST"]) +@admin_required +def audit_log_email(): + form = LogEmailForm() + if not form.validate_on_submit(): + flash("Provide a valid destination email address.", "error") + return redirect(url_for("backoffice.audit_log")) + recipient = form.log_recipient.data.strip() + stamp = datetime.utcnow().strftime("%Y%m%d-%H%M%S") + csv_str = _build_csv_string(AUDIT_CSV_HEADER, _audit_rows()) + ok, info = send_mail( + recipient, + f"[AlertHub] Audit log export ({stamp} UTC)", + "Attached is the AlertHub audit log export requested from the backoffice.", + "

Attached is the AlertHub audit log export requested from the backoffice.

", + attachments=[(f"alerthub-audit-log-{stamp}.csv", "text", "csv", csv_str.encode("utf-8"))], + kind="log-export", + triggered_by=current_user.username, + ) + AuditLog.record("auditlog.email", user=current_user, details=f"to={recipient} ok={ok}", ip=get_remote_ip()) + flash(("Audit log emailed. " if ok else "Failed to email log: ") + info, "success" if ok else "error") + return redirect(url_for("backoffice.audit_log")) + + +# --------------------------------------------------------------------------- # +# Mail log viewer (every message the platform sends) +# --------------------------------------------------------------------------- # +@backoffice_bp.route("/mail-log") +@admin_required +def mail_log(): + page = max(1, int(request.args.get("page", 1))) + per_page = 50 + pagination = MailLog.query.order_by(MailLog.timestamp.desc()).paginate(page=page, per_page=per_page, error_out=False) + return render_template("backoffice/mail_log.html", pagination=pagination, log_email_form=LogEmailForm()) + + +@backoffice_bp.route("/mail-log/export") +@admin_required +def mail_log_export(): + stamp = datetime.utcnow().strftime("%Y%m%d-%H%M%S") + AuditLog.record("maillog.export", user=current_user, ip=get_remote_ip()) + return _csv_response(f"alerthub-mail-log-{stamp}.csv", MAILLOG_CSV_HEADER, _maillog_rows()) + + +@backoffice_bp.route("/mail-log/email", methods=["POST"]) +@admin_required +def mail_log_email(): + form = LogEmailForm() + if not form.validate_on_submit(): + flash("Provide a valid destination email address.", "error") + return redirect(url_for("backoffice.mail_log")) + recipient = form.log_recipient.data.strip() + stamp = datetime.utcnow().strftime("%Y%m%d-%H%M%S") + csv_str = _build_csv_string(MAILLOG_CSV_HEADER, _maillog_rows()) + ok, info = send_mail( + recipient, + f"[AlertHub] Mail log export ({stamp} UTC)", + "Attached is the AlertHub mail log export requested from the backoffice.", + "

Attached is the AlertHub mail log export requested from the backoffice.

", + attachments=[(f"alerthub-mail-log-{stamp}.csv", "text", "csv", csv_str.encode("utf-8"))], + kind="log-export", + triggered_by=current_user.username, + ) + AuditLog.record("maillog.email", user=current_user, details=f"to={recipient} ok={ok}", ip=get_remote_ip()) + flash(("Mail log emailed. " if ok else "Failed to email log: ") + info, "success" if ok else "error") + return redirect(url_for("backoffice.mail_log")) diff --git a/app/routes_main.py b/app/routes_main.py new file mode 100644 index 0000000..4b5ebd1 --- /dev/null +++ b/app/routes_main.py @@ -0,0 +1,137 @@ +"""Frontoffice routes — visible to all authenticated users. + +Sections: + 1. View Data (everyone) + 2. Add/Remove/Edit (admins only) +""" +import json +from datetime import date +from flask import Blueprint, render_template, redirect, url_for, flash, request, abort +from flask_login import current_user + +from .models import db, Alert, AppSetting, AuditLog +from .forms import AlertForm +from .auth_utils import admin_required, login_required_full, get_remote_ip + +main_bp = Blueprint("main", __name__) + + +def _parse_days(text: str) -> list[int]: + return sorted({int(x.strip()) for x in text.split(",") if x.strip().isdigit() and int(x.strip()) >= 0}, reverse=True) + + +# --------------------------------------------------------------------------- # +# Section 1 — View Data +# --------------------------------------------------------------------------- # +@main_bp.route("/") +@login_required_full +def index(): + today = date.today() + alerts = Alert.query.filter_by(is_active=True).order_by(Alert.expiration_date.asc()).all() + payload = [] + for a in alerts: + days_left = (a.expiration_date - today).days + payload.append({ + "id": a.id, + "title": a.title, + "category": a.category, + "description": a.description, + "expiration_date": a.expiration_date.isoformat(), + "days_left": days_left, + "reminder_days": a.reminder_days, + }) + return render_template("index.html", alerts=payload, today_iso=today.isoformat()) + + +# --------------------------------------------------------------------------- # +# Section 2 — Add / Remove / Edit +# --------------------------------------------------------------------------- # +@main_bp.route("/alerts") +@login_required_full +def alerts_list(): + alerts = Alert.query.order_by(Alert.expiration_date.asc()).all() + return render_template("alerts_list.html", alerts=alerts) + + +@main_bp.route("/alerts/new", methods=["GET", "POST"]) +@admin_required +def alert_new(): + default_reminder_days = AppSetting.get("default_reminder_days", "30,14,7,1") + form = AlertForm(reminder_days=default_reminder_days) + if form.validate_on_submit(): + days = _parse_days(form.reminder_days.data) + if not days: + flash("Reminder days must contain at least one valid number.", "error") + else: + alert = Alert( + title=form.title.data.strip(), + category=(form.category.data or "").strip() or None, + description=(form.description.data or "").strip() or None, + expiration_date=form.expiration_date.data, + is_active=form.is_active.data, + created_by=current_user.id, + ) + alert.reminder_days = days + db.session.add(alert) + db.session.commit() + AuditLog.record("alert.create", user=current_user, details=f"id={alert.id} title={alert.title}", ip=get_remote_ip()) + flash("Alert created.", "success") + return redirect(url_for("main.alerts_list")) + return render_template("alert_form.html", form=form, alert=None) + + +@main_bp.route("/alerts//edit", methods=["GET", "POST"]) +@admin_required +def alert_edit(alert_id: int): + alert = db.session.get(Alert, alert_id) + if alert is None: + abort(404) + form = AlertForm(obj=alert) + if request.method == "GET": + form.reminder_days.data = ",".join(str(d) for d in alert.reminder_days) + if form.validate_on_submit(): + days = _parse_days(form.reminder_days.data) + if not days: + flash("Reminder days must contain at least one valid number.", "error") + else: + alert.title = form.title.data.strip() + alert.category = (form.category.data or "").strip() or None + alert.description = (form.description.data or "").strip() or None + alert.expiration_date = form.expiration_date.data + alert.is_active = form.is_active.data + # If reminder_days changed OR expiration_date changed, reset sent log + if days != alert.reminder_days: + alert.reminders_sent = [] + alert.reminder_days = days + db.session.commit() + AuditLog.record("alert.update", user=current_user, details=f"id={alert.id}", ip=get_remote_ip()) + flash("Alert updated.", "success") + return redirect(url_for("main.alerts_list")) + return render_template("alert_form.html", form=form, alert=alert) + + +@main_bp.route("/alerts//delete", methods=["POST"]) +@admin_required +def alert_delete(alert_id: int): + alert = db.session.get(Alert, alert_id) + if alert is None: + abort(404) + title = alert.title + db.session.delete(alert) + db.session.commit() + AuditLog.record("alert.delete", user=current_user, details=f"id={alert_id} title={title}", ip=get_remote_ip()) + flash(f"Alert '{title}' deleted.", "success") + return redirect(url_for("main.alerts_list")) + + +@main_bp.route("/alerts//reset-reminders", methods=["POST"]) +@admin_required +def alert_reset_reminders(alert_id: int): + alert = db.session.get(Alert, alert_id) + if alert is None: + abort(404) + alert.reminders_sent = [] + db.session.commit() + AuditLog.record("alert.reset_reminders", user=current_user, details=f"id={alert_id}", ip=get_remote_ip()) + flash("Reminder send-history cleared. Pending reminders will be re-sent on next scheduler run.", "info") + return redirect(url_for("main.alerts_list")) diff --git a/app/scheduler.py b/app/scheduler.py new file mode 100644 index 0000000..4668a6b --- /dev/null +++ b/app/scheduler.py @@ -0,0 +1,79 @@ +"""Background scheduler that scans alerts and dispatches reminder emails.""" +import logging +from datetime import date, timezone +from apscheduler.schedulers.background import BackgroundScheduler +from .models import Alert, User, db +from .mailer import send_mail + +logger = logging.getLogger(__name__) +_scheduler: BackgroundScheduler | None = None + + +def _check_and_send(app): + """Iterate active alerts; for each reminder threshold reached, email all users.""" + with app.app_context(): + today = date.today() + active_alerts = Alert.query.filter_by(is_active=True).all() + recipients = [u.email for u in User.query.filter_by(is_active=True).all() if u.email] + if not recipients: + logger.info("Scheduler tick: no recipients.") + return + + for alert in active_alerts: + days_left = (alert.expiration_date - today).days + already_sent = set(alert.reminders_sent) + newly_sent = [] + for threshold in alert.reminder_days: + if days_left <= threshold and threshold not in already_sent: + subject = f"[AlertHub] Reminder: {alert.title} expires in {days_left} day(s)" + body_text = ( + f"Alert: {alert.title}\n" + f"Category: {alert.category or '—'}\n" + f"Expiration date: {alert.expiration_date.isoformat()}\n" + f"Days remaining: {days_left}\n\n" + f"{alert.description or ''}\n" + ) + body_html = f""" +
+

⏰ AlertHub Reminder

+

{alert.title}

+

Category: {alert.category or '—'}
+ Expiration date: {alert.expiration_date.isoformat()}
+ Days remaining: {days_left}

+

{alert.description or ''}

+
+ """ + ok, info = send_mail(recipients, subject, body_text, body_html, + kind="reminder", triggered_by="system") + logger.info("Alert %s threshold %d: send=%s (%s)", alert.id, threshold, ok, info) + if ok: + newly_sent.append(threshold) + if newly_sent: + alert.reminders_sent = list(already_sent.union(newly_sent)) + db.session.commit() + + +def init_scheduler(app): + """Start the scheduler if not already running. Called once at app startup.""" + global _scheduler + if _scheduler is not None and _scheduler.running: + return _scheduler + interval = max(1, int(app.config.get("SCHEDULER_INTERVAL_MINUTES", 60))) + _scheduler = BackgroundScheduler(timezone="UTC") + _scheduler.add_job( + _check_and_send, + "interval", + minutes=interval, + args=[app], + id="alert_reminder_job", + replace_existing=True, + next_run_time=None, # don't run immediately on boot + ) + _scheduler.start() + logger.info("Scheduler started; reminder check every %d minute(s).", interval) + return _scheduler + + +def trigger_now(app): + """Manual trigger (for the 'Run check now' button).""" + _check_and_send(app) diff --git a/app/static/css/app.css b/app/static/css/app.css new file mode 100644 index 0000000..5b061a8 --- /dev/null +++ b/app/static/css/app.css @@ -0,0 +1,869 @@ +/* ====================================================================== + AlertHub — IT operations console aesthetic + Typography: JetBrains Mono (display/mono), Space Grotesk avoided in favour + of system stack with mono accents to keep the terminal vibe. + ====================================================================== */ + +@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=IBM+Plex+Sans:wght@400;500;600;700&display=swap'); + +:root { + --font-mono: 'JetBrains Mono', 'SF Mono', Menlo, Consolas, monospace; + --font-sans: 'IBM Plex Sans', -apple-system, system-ui, sans-serif; + + --radius-sm: 4px; + --radius-md: 8px; + --radius-lg: 14px; + + --tx-h: 1.15; + --tx-b: 1.55; +} + +/* ---------- Dark theme (default) ---------- */ +[data-theme="dark"] { + --bg-base: #0a0e14; + --bg-elev-1: #0f141b; + --bg-elev-2: #141a23; + --bg-elev-3: #1a212c; + --grid-line: rgba(0, 212, 170, 0.04); + --border: #1f2733; + --border-strong: #2a3442; + + --text-primary: #d8e2ec; + --text-secondary: #8b96a5; + --text-dim: #5a6371; + + --accent: #00d4aa; /* signal green */ + --accent-soft: rgba(0, 212, 170, 0.12); + --accent-hot: #00ffcc; + --magenta: #ff3e8b; /* alert magenta */ + --amber: #ffb454; + --danger: #ff5c5c; + --info: #4ad8ff; + + --shadow-1: 0 1px 0 rgba(255,255,255,0.03), 0 8px 24px rgba(0,0,0,0.5); + --glow-accent: 0 0 0 1px rgba(0,212,170,0.3), 0 0 20px rgba(0,212,170,0.15); +} + +/* ---------- Light theme ---------- */ +[data-theme="light"] { + --bg-base: #f4f6f9; + --bg-elev-1: #ffffff; + --bg-elev-2: #f9fbfd; + --bg-elev-3: #f0f3f7; + --grid-line: rgba(0, 130, 100, 0.05); + --border: #e2e8ef; + --border-strong: #c8d2dd; + + --text-primary: #0e1620; + --text-secondary: #4a5566; + --text-dim: #8693a3; + + --accent: #008f74; + --accent-soft: rgba(0, 143, 116, 0.08); + --accent-hot: #00b388; + --magenta: #d92671; + --amber: #d97a13; + --danger: #d93939; + --info: #1f9ec5; + + --shadow-1: 0 1px 2px rgba(15, 30, 45, 0.06), 0 8px 20px rgba(15, 30, 45, 0.05); + --glow-accent: 0 0 0 1px rgba(0, 143, 116, 0.25); +} + +* { box-sizing: border-box; } + +html, body { + margin: 0; + padding: 0; + background: var(--bg-base); + color: var(--text-primary); + font-family: var(--font-sans); + font-size: 15px; + line-height: var(--tx-b); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + min-height: 100vh; +} + +body { + position: relative; + overflow-x: hidden; +} + +/* Decorative grid background */ +.grid-bg { + position: fixed; + inset: 0; + pointer-events: none; + z-index: 0; + background-image: + linear-gradient(var(--grid-line) 1px, transparent 1px), + linear-gradient(90deg, var(--grid-line) 1px, transparent 1px); + background-size: 32px 32px; + mask-image: radial-gradient(ellipse at top, black 30%, transparent 75%); + -webkit-mask-image: radial-gradient(ellipse at top, black 30%, transparent 75%); +} + +a { color: var(--accent); text-decoration: none; } +a:hover { color: var(--accent-hot); text-decoration: underline; text-underline-offset: 3px; } + +h1, h2, h3, h4 { + font-family: var(--font-sans); + font-weight: 700; + letter-spacing: -0.01em; + line-height: var(--tx-h); + margin: 0 0 .6em; +} +h1 { font-size: 1.85rem; } +h2 { font-size: 1.35rem; } +h3 { font-size: 1.1rem; } + +code, .mono { font-family: var(--font-mono); font-size: .92em; } + +/* ---------- Top bar ---------- */ +.topbar { + position: sticky; + top: 0; + z-index: 50; + backdrop-filter: blur(10px) saturate(140%); + -webkit-backdrop-filter: blur(10px) saturate(140%); + background: color-mix(in srgb, var(--bg-elev-1) 82%, transparent); + border-bottom: 1px solid var(--border); +} +.topbar-inner { + max-width: 1320px; + margin: 0 auto; + padding: 12px 24px; + display: grid; + grid-template-columns: auto 1fr auto; + align-items: center; + gap: 24px; +} +.brand { + display: inline-flex; + align-items: center; + gap: 10px; + color: var(--text-primary); + font-family: var(--font-mono); + font-weight: 700; + letter-spacing: -0.01em; +} +.brand:hover { text-decoration: none; } +.brand-mark { + color: var(--accent); + font-size: 1.1em; + letter-spacing: -2px; + filter: drop-shadow(0 0 6px var(--accent)); +} +.brand-name { font-size: 1.05rem; } +.brand-tag { + font-family: var(--font-mono); + font-size: .65rem; + font-weight: 500; + letter-spacing: 0.15em; + padding: 2px 7px; + border-radius: 4px; + background: var(--accent-soft); + color: var(--accent); + margin-left: 6px; +} +body.is-backoffice .brand-tag { + background: color-mix(in srgb, var(--magenta) 14%, transparent); + color: var(--magenta); +} + +.topbar-nav { + display: flex; + gap: 4px; + justify-self: center; +} +.navlink { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 14px; + font-family: var(--font-mono); + font-size: .85rem; + font-weight: 500; + color: var(--text-secondary); + border-radius: var(--radius-sm); + transition: all .15s ease; +} +.navlink:hover { color: var(--text-primary); background: var(--bg-elev-2); text-decoration: none; } +.navlink.active { color: var(--accent); background: var(--accent-soft); } +.navlink.active .dot { background: var(--accent); box-shadow: 0 0 8px var(--accent); } +.dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--text-dim); +} +.dot-admin { background: var(--magenta); box-shadow: 0 0 8px var(--magenta); } +.navlink-cross { border: 1px solid var(--border); } +.navlink-cross:hover { border-color: var(--border-strong); color: var(--magenta); } +.navlink-cross.active { color: var(--magenta); background: color-mix(in srgb, var(--magenta) 12%, transparent); } +.navlink-cross.active .dot-admin { background: var(--magenta); } + +.topbar-right { display: inline-flex; gap: 10px; align-items: center; justify-self: end; } +.icon-btn { + background: transparent; + border: 1px solid var(--border); + color: var(--text-secondary); + width: 36px; height: 36px; + border-radius: var(--radius-sm); + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all .15s ease; +} +.icon-btn:hover { color: var(--text-primary); border-color: var(--border-strong); background: var(--bg-elev-2); } +.icon-sun { display: none; } +.icon-moon { display: block; } +[data-theme="light"] .icon-sun { display: block; } +[data-theme="light"] .icon-moon { display: none; } + +.user-chip { + display: inline-flex; + align-items: center; + gap: 10px; + padding: 4px 12px 4px 4px; + border: 1px solid var(--border); + border-radius: 999px; + color: var(--text-primary); + background: var(--bg-elev-2); +} +.user-chip:hover { border-color: var(--border-strong); text-decoration: none; } +.user-avatar { + width: 28px; height: 28px; + border-radius: 50%; + background: linear-gradient(135deg, var(--accent), var(--info)); + color: #061218; + font-family: var(--font-mono); + font-size: .72rem; + font-weight: 700; + display: inline-flex; + align-items: center; + justify-content: center; +} +.user-avatar-img { + object-fit: cover; + padding: 0; + border: 1px solid var(--border-strong); + background: var(--bg-elev-3); +} +.user-meta { display: flex; flex-direction: column; line-height: 1.1; } +.user-name { font-size: .85rem; } +.user-role { + font-family: var(--font-mono); + font-size: .6rem; + font-weight: 600; + letter-spacing: 0.1em; + text-transform: uppercase; +} +.user-role.role-admin { color: var(--magenta); } +.user-role.role-user { color: var(--text-dim); } + +.inline-form { display: inline; } + +/* ---------- Container ---------- */ +.container { + max-width: 1320px; + margin: 0 auto; + padding: 32px 24px 64px; + position: relative; + z-index: 1; +} + +/* ---------- Buttons ---------- */ +.btn { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 9px 16px; + border-radius: var(--radius-sm); + font-family: var(--font-mono); + font-size: .85rem; + font-weight: 500; + border: 1px solid transparent; + cursor: pointer; + transition: all .15s ease; + white-space: nowrap; + text-decoration: none; +} +.btn:hover { text-decoration: none; } +.btn-primary { + background: var(--accent); + color: #02181a; + border-color: var(--accent); +} +.btn-primary:hover { background: var(--accent-hot); box-shadow: var(--glow-accent); color: #02181a; } +.btn-ghost { + background: transparent; + color: var(--text-primary); + border-color: var(--border); +} +.btn-ghost:hover { border-color: var(--border-strong); background: var(--bg-elev-2); } +.btn-danger { + background: transparent; + color: var(--danger); + border-color: color-mix(in srgb, var(--danger) 35%, transparent); +} +.btn-danger:hover { background: color-mix(in srgb, var(--danger) 12%, transparent); border-color: var(--danger); } +.btn-sm { padding: 5px 10px; font-size: .78rem; } +.btn-block { display: flex; width: 100%; justify-content: center; } +.btn-row { display: flex; gap: 8px; flex-wrap: wrap; } + +/* ---------- Cards / Panels ---------- */ +.panel { + background: var(--bg-elev-1); + border: 1px solid var(--border); + border-radius: var(--radius-md); + box-shadow: var(--shadow-1); +} +.panel-header { + padding: 16px 20px; + border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; +} +.panel-header h2, .panel-header h3 { margin: 0; } +.panel-body { padding: 20px; } + +.section-head { + display: flex; + align-items: flex-end; + justify-content: space-between; + gap: 16px; + margin: 0 0 24px; + padding-bottom: 16px; + border-bottom: 1px dashed var(--border); +} +.section-head .lead { color: var(--text-secondary); margin: 6px 0 0; font-size: .95rem; } +.section-tag { + font-family: var(--font-mono); + font-size: .65rem; + letter-spacing: 0.15em; + color: var(--accent); + text-transform: uppercase; + margin-bottom: 6px; + display: block; +} + +/* ---------- Forms ---------- */ +.form { display: grid; gap: 16px; } +.form label { + display: block; + font-family: var(--font-mono); + font-size: .78rem; + font-weight: 500; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--text-secondary); + margin-bottom: 6px; +} +.form input[type=text], +.form input[type=email], +.form input[type=password], +.form input[type=number], +.form input[type=date], +.form select, +.form textarea { + width: 100%; + padding: 10px 12px; + background: var(--bg-elev-2); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + color: var(--text-primary); + font-family: var(--font-sans); + font-size: .95rem; + transition: all .15s ease; +} +.form textarea { font-family: var(--font-sans); resize: vertical; min-height: 90px; } +.form input:focus, .form select:focus, .form textarea:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-soft); + background: var(--bg-elev-1); +} +.form .check-row { + display: inline-flex; + align-items: center; + gap: 10px; +} +.form .check-row label { + margin: 0; + text-transform: none; + letter-spacing: 0; + font-family: var(--font-sans); + font-size: .95rem; + font-weight: 500; + color: var(--text-primary); +} +.help { color: var(--text-dim); font-size: .82rem; margin-top: 4px; } +.form-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; } +@media (max-width: 720px) { .form-grid-2 { grid-template-columns: 1fr; } } + +.error { color: var(--danger); font-size: .82rem; margin-top: 4px; } + +/* ---------- Flash messages ---------- */ +.flash-stack { display: grid; gap: 10px; margin-bottom: 24px; } +.flash { + padding: 12px 16px; + border-radius: var(--radius-sm); + border: 1px solid; + display: flex; + align-items: center; + gap: 12px; + background: var(--bg-elev-1); + font-size: .92rem; +} +.flash-icon { + width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; +} +.flash-success { border-color: color-mix(in srgb, var(--accent) 40%, transparent); } +.flash-success .flash-icon { background: var(--accent); box-shadow: 0 0 8px var(--accent); } +.flash-error { border-color: color-mix(in srgb, var(--danger) 40%, transparent); } +.flash-error .flash-icon { background: var(--danger); box-shadow: 0 0 8px var(--danger); } +.flash-info { border-color: color-mix(in srgb, var(--info) 40%, transparent); } +.flash-info .flash-icon { background: var(--info); } + +/* ---------- Auth pages ---------- */ +.auth-shell { + min-height: calc(100vh - 200px); + display: flex; + align-items: center; + justify-content: center; + padding: 40px 16px; +} +.auth-card { + width: 100%; + max-width: 420px; + background: var(--bg-elev-1); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + padding: 32px; + box-shadow: var(--shadow-1); + position: relative; +} +.auth-card::before { + content: ''; + position: absolute; + inset: -1px; + border-radius: inherit; + padding: 1px; + background: linear-gradient(135deg, var(--accent), transparent 40%, var(--magenta)); + -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); + -webkit-mask-composite: xor; + mask-composite: exclude; + opacity: .5; + pointer-events: none; +} +.auth-prompt { + font-family: var(--font-mono); + font-size: .8rem; + color: var(--accent); + margin-bottom: 6px; + letter-spacing: 0.06em; +} +.auth-prompt::before { content: '> '; opacity: .7; } +.auth-card h1 { margin-bottom: 6px; font-size: 1.5rem; } +.auth-sub { color: var(--text-secondary); font-size: .9rem; margin-bottom: 24px; } + +/* ---------- ALERT cards (the heart of the View page) ---------- */ +.alerts-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); + gap: 18px; +} +.alert-card { + background: var(--bg-elev-1); + border: 1px solid var(--border); + border-radius: var(--radius-md); + padding: 20px; + position: relative; + overflow: hidden; + transition: transform .2s ease, box-shadow .2s ease, border-color .2s ease; +} +.alert-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-1); border-color: var(--border-strong); } +.alert-card::before { + content: ''; + position: absolute; + top: 0; left: 0; right: 0; + height: 2px; + background: var(--accent); + opacity: .8; +} +.alert-card.is-warning::before { background: var(--amber); } +.alert-card.is-critical::before { background: var(--danger); } +.alert-card.is-expired::before { background: var(--text-dim); } + +.alert-head { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 12px; + margin-bottom: 12px; +} +.alert-title { margin: 0; font-size: 1.1rem; font-weight: 700; line-height: 1.3; } +.alert-cat { + display: inline-block; + font-family: var(--font-mono); + font-size: .65rem; + letter-spacing: 0.1em; + text-transform: uppercase; + padding: 3px 8px; + border-radius: 3px; + background: var(--bg-elev-3); + color: var(--text-secondary); +} +.alert-desc { + color: var(--text-secondary); + font-size: .9rem; + margin: 0 0 16px; + line-height: 1.5; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} +.alert-message { + font-family: var(--font-mono); + font-size: .85rem; + color: var(--text-secondary); + margin-bottom: 16px; + padding: 10px 12px; + background: var(--bg-elev-2); + border-left: 2px solid var(--accent); + border-radius: 0 var(--radius-sm) var(--radius-sm) 0; +} +.alert-card.is-warning .alert-message { border-left-color: var(--amber); } +.alert-card.is-critical .alert-message { border-left-color: var(--danger); } +.alert-card.is-expired .alert-message { border-left-color: var(--text-dim); } + +/* The countdown timer */ +.countdown { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 6px; +} +.countdown-segment { + background: var(--bg-elev-2); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + padding: 10px 4px 8px; + text-align: center; + position: relative; +} +.countdown-value { + font-family: var(--font-mono); + font-size: 1.5rem; + font-weight: 700; + color: var(--accent); + line-height: 1; + display: block; + letter-spacing: -0.02em; + font-variant-numeric: tabular-nums; +} +.alert-card.is-warning .countdown-value { color: var(--amber); } +.alert-card.is-critical .countdown-value { + color: var(--danger); + animation: pulse-num 1.4s ease-in-out infinite; +} +.alert-card.is-expired .countdown-value { color: var(--text-dim); } +.countdown-label { + font-family: var(--font-mono); + font-size: .55rem; + font-weight: 500; + letter-spacing: 0.18em; + text-transform: uppercase; + color: var(--text-dim); + display: block; + margin-top: 4px; +} +@keyframes pulse-num { + 0%, 100% { opacity: 1; } + 50% { opacity: .55; } +} + +.alert-meta { + margin-top: 14px; + display: flex; + justify-content: space-between; + align-items: center; + font-family: var(--font-mono); + font-size: .72rem; + color: var(--text-dim); + text-transform: uppercase; + letter-spacing: 0.08em; +} +.alert-status-dot { + display: inline-block; + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--accent); + margin-right: 6px; + box-shadow: 0 0 6px var(--accent); +} +.alert-card.is-warning .alert-status-dot { background: var(--amber); box-shadow: 0 0 6px var(--amber); } +.alert-card.is-critical .alert-status-dot { + background: var(--danger); + box-shadow: 0 0 8px var(--danger); + animation: blink 1s ease-in-out infinite; +} +.alert-card.is-expired .alert-status-dot { background: var(--text-dim); box-shadow: none; } +@keyframes blink { 50% { opacity: .25; } } + +/* Empty state */ +.empty { + text-align: center; + padding: 60px 20px; + color: var(--text-secondary); + border: 1px dashed var(--border); + border-radius: var(--radius-md); +} +.empty .empty-icon { font-size: 2rem; opacity: .5; margin-bottom: 12px; } + +/* ---------- Tables ---------- */ +.table { + width: 100%; + border-collapse: separate; + border-spacing: 0; + background: var(--bg-elev-1); + border: 1px solid var(--border); + border-radius: var(--radius-md); + overflow: hidden; +} +.table th, .table td { + padding: 12px 16px; + text-align: left; + border-bottom: 1px solid var(--border); + vertical-align: middle; +} +.table thead th { + background: var(--bg-elev-2); + font-family: var(--font-mono); + font-size: .72rem; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.1em; + color: var(--text-secondary); +} +.table tbody tr:hover { background: var(--bg-elev-2); } +.table tbody tr:last-child td { border-bottom: none; } +.table .actions { text-align: right; white-space: nowrap; } + +.role-badge { + display: inline-block; + font-family: var(--font-mono); + font-size: .68rem; + letter-spacing: 0.1em; + text-transform: uppercase; + padding: 3px 8px; + border-radius: 3px; +} +.role-badge.role-admin { background: color-mix(in srgb, var(--magenta) 14%, transparent); color: var(--magenta); } +.role-badge.role-user { background: var(--bg-elev-3); color: var(--text-secondary); } + +.status-badge { + display: inline-flex; + align-items: center; + gap: 6px; + font-family: var(--font-mono); + font-size: .7rem; + letter-spacing: 0.08em; + text-transform: uppercase; +} +.status-badge .led { width: 6px; height: 6px; border-radius: 50%; } +.status-active .led { background: var(--accent); box-shadow: 0 0 6px var(--accent); } +.status-inactive .led { background: var(--text-dim); } + +/* ---------- Stat cards ---------- */ +.stat-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 16px; + margin-bottom: 28px; +} +.stat { + background: var(--bg-elev-1); + border: 1px solid var(--border); + border-radius: var(--radius-md); + padding: 18px; + position: relative; +} +.stat-label { + font-family: var(--font-mono); + font-size: .68rem; + text-transform: uppercase; + letter-spacing: 0.12em; + color: var(--text-dim); +} +.stat-value { + font-family: var(--font-mono); + font-size: 2rem; + font-weight: 700; + color: var(--text-primary); + line-height: 1.1; + margin-top: 6px; +} +.stat-accent .stat-value { color: var(--accent); } +.stat-warn .stat-value { color: var(--amber); } + +/* ---------- QR / MFA pages ---------- */ +.mfa-qr { + display: block; + margin: 0 auto; + width: 200px; + height: 200px; + background: white; + padding: 12px; + border-radius: var(--radius-sm); + border: 1px solid var(--border); +} +.mfa-secret { + font-family: var(--font-mono); + font-size: .9rem; + background: var(--bg-elev-2); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + padding: 8px 12px; + user-select: all; + word-break: break-all; + text-align: center; +} + +/* ---------- Footer ---------- */ +.footer { + max-width: 1320px; + margin: 0 auto; + padding: 18px 24px 36px; + display: flex; + align-items: center; + gap: 10px; + font-family: var(--font-mono); + font-size: .75rem; + color: var(--text-dim); + border-top: 1px solid var(--border); +} +/* ---------- Avatar panel (account page) ---------- */ +.avatar-row { + display: flex; + gap: 24px; + align-items: flex-start; + flex-wrap: wrap; +} +.avatar-preview { + width: 96px; height: 96px; + border-radius: 50%; + overflow: hidden; + flex: 0 0 auto; + border: 1px solid var(--border-strong); + background: var(--bg-elev-3); + display: inline-flex; + align-items: center; + justify-content: center; +} +.avatar-preview img { width: 100%; height: 100%; object-fit: cover; display: block; } +.avatar-initials { + font-family: var(--font-mono); + font-size: 1.8rem; + font-weight: 700; + color: var(--accent); +} +.avatar-actions { flex: 1 1 260px; min-width: 260px; } + +/* ---------- Log export / email actions ---------- */ +.log-actions { + display: flex; + gap: 12px; + align-items: center; + flex-wrap: wrap; +} +.log-email-form { + display: inline-flex; + gap: 8px; + align-items: center; +} +.log-email-form input[type="text"], +.log-email-form input[type="email"] { margin: 0; min-width: 220px; } +@media (max-width: 720px) { + .log-actions { width: 100%; } + .log-email-form { width: 100%; } + .log-email-form input { flex: 1; min-width: 0; } +} + +.footer-sep { opacity: .5; } +.footer-copy { + margin-left: auto; + color: var(--text-secondary); + letter-spacing: .02em; +} +.footer .status-pill { margin-left: 18px; } +@media (max-width: 720px) { + .footer { flex-wrap: wrap; } + .footer-copy { margin-left: 0; flex-basis: 100%; order: 3; } + .footer .status-pill { margin-left: auto; } +} +.kbd { + background: var(--bg-elev-2); + border: 1px solid var(--border); + border-radius: 3px; + padding: 1px 6px; + font-size: .72rem; + color: var(--text-secondary); +} +.status-pill { + margin-left: auto; + display: inline-flex; + align-items: center; + gap: 6px; + text-transform: uppercase; +} +.status-pill .led { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--accent); + box-shadow: 0 0 6px var(--accent); + animation: led-pulse 2.5s ease-in-out infinite; +} +@keyframes led-pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: .4; } +} + +/* ---------- Pagination ---------- */ +.pagination { + display: flex; + justify-content: center; + gap: 6px; + margin-top: 20px; + flex-wrap: wrap; +} +.pagination a, .pagination span { + font-family: var(--font-mono); + font-size: .82rem; + padding: 6px 10px; + border: 1px solid var(--border); + border-radius: var(--radius-sm); + color: var(--text-secondary); +} +.pagination a:hover { color: var(--accent); border-color: var(--accent); text-decoration: none; } +.pagination .current { background: var(--accent-soft); color: var(--accent); border-color: var(--accent); } + +/* ---------- Misc ---------- */ +.row-spread { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; } +.row-gap { display: flex; gap: 12px; flex-wrap: wrap; } +.muted { color: var(--text-secondary); } +.dim { color: var(--text-dim); } +hr { border: 0; border-top: 1px dashed var(--border); margin: 24px 0; } + +@media (max-width: 800px) { + .topbar-inner { grid-template-columns: 1fr; gap: 12px; } + .topbar-nav { justify-self: flex-start; flex-wrap: wrap; } + .topbar-right { justify-self: flex-start; } +} diff --git a/app/static/favicon.svg b/app/static/favicon.svg new file mode 100644 index 0000000..6dd6096 --- /dev/null +++ b/app/static/favicon.svg @@ -0,0 +1 @@ + diff --git a/app/static/js/app.js b/app/static/js/app.js new file mode 100644 index 0000000..1d6a756 --- /dev/null +++ b/app/static/js/app.js @@ -0,0 +1,73 @@ +// ====== Theme toggle ======================================================= +(function () { + const btn = document.getElementById('theme-toggle'); + if (!btn) return; + btn.addEventListener('click', () => { + const root = document.documentElement; + const next = root.getAttribute('data-theme') === 'dark' ? 'light' : 'dark'; + root.setAttribute('data-theme', next); + try { localStorage.setItem('alerthub-theme', next); } catch (_) {} + }); +})(); + +// ====== Countdown timer ==================================================== +// Each .countdown element has data-expires="YYYY-MM-DD" and renders 4 cells +// for days / hours / minutes / seconds. Updates every second. +(function () { + const els = document.querySelectorAll('.countdown[data-expires]'); + if (!els.length) return; + + function pad(n) { return n < 10 ? '0' + n : '' + n; } + + function render() { + const now = new Date(); + els.forEach((el) => { + const target = new Date(el.dataset.expires + 'T23:59:59'); + let diff = (target.getTime() - now.getTime()) / 1000; + const card = el.closest('.alert-card'); + const negative = diff < 0; + diff = Math.abs(diff); + + const days = Math.floor(diff / 86400); + const hours = Math.floor((diff % 86400) / 3600); + const minutes = Math.floor((diff % 3600) / 60); + const seconds = Math.floor(diff % 60); + + const segs = el.querySelectorAll('.countdown-value'); + if (segs.length === 4) { + segs[0].textContent = days; + segs[1].textContent = pad(hours); + segs[2].textContent = pad(minutes); + segs[3].textContent = pad(seconds); + } + + if (card) { + card.classList.remove('is-warning', 'is-critical', 'is-expired'); + if (negative) { + card.classList.add('is-expired'); + } else if (days < 7) { + card.classList.add('is-critical'); + } else if (days < 30) { + card.classList.add('is-warning'); + } + } + + const dotLabel = el.parentElement && el.parentElement.querySelector('.alert-state-text'); + if (dotLabel) { + if (negative) dotLabel.textContent = 'EXPIRED'; + else if (days < 7) dotLabel.textContent = 'CRITICAL'; + else if (days < 30) dotLabel.textContent = 'WARNING'; + else dotLabel.textContent = 'NOMINAL'; + } + }); + } + render(); + setInterval(render, 1000); +})(); + +// ====== Confirm-on-click for destructive actions =========================== +document.querySelectorAll('form[data-confirm]').forEach((form) => { + form.addEventListener('submit', (e) => { + if (!window.confirm(form.dataset.confirm)) e.preventDefault(); + }); +}); diff --git a/app/templates/account.html b/app/templates/account.html new file mode 100644 index 0000000..faf373f --- /dev/null +++ b/app/templates/account.html @@ -0,0 +1,81 @@ +{% extends "base.html" %} +{% block title %}Account{% endblock %} +{% block content %} +
+
+ +

{{ current_user.username }}

+

{{ current_user.email }} · role {{ current_user.role }}

+
+
+ +
+

Profile avatar

+
+
+
+ {% if current_user.has_avatar %} + Current avatar + {% else %} + {{ current_user.initials }} + {% endif %} +
+
+
+ {{ avatar_form.csrf_token }} +
{{ avatar_form.avatar.label }}{{ avatar_form.avatar(accept="image/*") }}
+
PNG, JPG, GIF or WebP. Squared and resized to {{ 256 }}×{{ 256 }} automatically. Max 2 MB.
+
+ {{ avatar_form.submit(class_="btn btn-primary") }} + {% if current_user.has_avatar %} + + {% endif %} +
+
+ {% if current_user.has_avatar %} +
+ +
+ {% endif %} +
+
+
+
+ +
+
+

Change password

+
+
+ {{ password_form.csrf_token }} +
{{ password_form.current_password.label }}{{ password_form.current_password(autocomplete="current-password") }}
+
{{ password_form.new_password.label }}{{ password_form.new_password(autocomplete="new-password") }}
+
{{ password_form.confirm.label }}{{ password_form.confirm(autocomplete="new-password") }}
+ {{ password_form.submit(class_="btn btn-primary") }} +
+
+
+ +
+
+

Multi-factor authentication

+ + {{ 'enabled' if current_user.mfa_enabled else 'disabled' }} + +
+
+ {% if current_user.mfa_enabled %} +

MFA is currently active on your account. You'll be prompted for a 6-digit code on every sign-in.

+
+ + +
+ {% else %} +

MFA is currently not configured. Strongly recommended for admin accounts.

+ Set up MFA now + {% endif %} +
+
+
+ +{% endblock %} diff --git a/app/templates/alert_form.html b/app/templates/alert_form.html new file mode 100644 index 0000000..a2b4fb2 --- /dev/null +++ b/app/templates/alert_form.html @@ -0,0 +1,48 @@ +{% extends "base.html" %} +{% block title %}{{ 'Edit' if alert else 'New' }} alert{% endblock %} +{% block content %} +
+
+ +

{{ 'Edit alert' if alert else 'New alert' }}

+ {% if alert %}

Last updated {{ alert.updated_at.strftime('%Y-%m-%d %H:%M:%S') }} UTC.

{% endif %} +
+ ← Back +
+ +
+
+
+ {{ form.csrf_token }} +
+ {{ form.title.label }}{{ form.title(placeholder="e.g. Microsoft 365 Renewal") }} + {% for err in form.title.errors %}
{{ err }}
{% endfor %} +
+
+
+ {{ form.category.label }}{{ form.category(placeholder="License, SSL, Domain, Contract …") }} + {% for err in form.category.errors %}
{{ err }}
{% endfor %} +
+
+ {{ form.expiration_date.label }}{{ form.expiration_date() }} + {% for err in form.expiration_date.errors %}
{{ err }}
{% endfor %} +
+
+
+ {{ form.description.label }}{{ form.description(rows=4, placeholder="Optional detail shown on the alert card.") }} + {% for err in form.description.errors %}
{{ err }}
{% endfor %} +
+
+ {{ form.reminder_days.label }}{{ form.reminder_days() }} +
Comma-separated days BEFORE expiration when an email reminder fires. Example: 30,14,7,1
+ {% for err in form.reminder_days.errors %}
{{ err }}
{% endfor %} +
+
{{ form.is_active() }} {{ form.is_active.label }}
+
+ {{ form.submit(class_="btn btn-primary") }} + Cancel +
+
+
+
+{% endblock %} diff --git a/app/templates/alerts_list.html b/app/templates/alerts_list.html new file mode 100644 index 0000000..e0e2ed2 --- /dev/null +++ b/app/templates/alerts_list.html @@ -0,0 +1,64 @@ +{% extends "base.html" %} +{% block title %}Manage Alerts{% endblock %} +{% block content %} +
+
+ +

Manage alerts

+

Create, modify, or retire alerts. Only administrators can make changes.

+
+ {% if current_user.is_admin %} + + New alert + {% endif %} +
+ +{% if alerts %} + + + + + + + + + + + + + {% for a in alerts %} + + + + + + + + + {% endfor %} + +
TitleCategoryExpiresRemindersStatusActions
{{ a.title }}{% if a.description %}
{{ a.description[:80] }}{% if a.description|length > 80 %}…{% endif %}{% endif %}
{% if a.category %}{{ a.category }}{% else %}{% endif %}{{ a.expiration_date.isoformat() }}{{ a.reminder_days|join(', ') }}d + + {{ 'active' if a.is_active else 'inactive' }} + + + {% if current_user.is_admin %} + Edit +
+ + +
+
+ + +
+ {% else %} + view only + {% endif %} +
+{% else %} +
+

No alerts yet.

+ {% if current_user.is_admin %}Create one{% endif %} +
+{% endif %} +{% endblock %} diff --git a/app/templates/backoffice/audit_log.html b/app/templates/backoffice/audit_log.html new file mode 100644 index 0000000..c78f147 --- /dev/null +++ b/app/templates/backoffice/audit_log.html @@ -0,0 +1,51 @@ +{% extends "base.html" %} +{% block title %}Audit log{% endblock %} +{% block content %} +
+
+ +

Audit log

+

Sign-ins, configuration changes, and alert mutations. Most-recent first.

+
+
+ ⬇ Export CSV +
+ {{ log_email_form.csrf_token }} + {{ log_email_form.log_recipient(placeholder="destination@example.com") }} + {{ log_email_form.submit(class_="btn btn-primary") }} +
+
+
+ + + + + + + {% for e in pagination.items %} + + + + + + + + {% else %} + + {% endfor %} + +
Timestamp (UTC)UserActionDetailsIP
{{ e.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}{{ e.username or '—' }}{{ e.action }}{{ e.details or '' }}{{ e.ip_address or '' }}
No audit entries yet.
+ +{% if pagination.pages > 1 %} + +{% endif %} +{% endblock %} diff --git a/app/templates/backoffice/dashboard.html b/app/templates/backoffice/dashboard.html new file mode 100644 index 0000000..41b6226 --- /dev/null +++ b/app/templates/backoffice/dashboard.html @@ -0,0 +1,77 @@ +{% extends "base.html" %} +{% block title %}Dashboard{% endblock %} +{% block content %} +
+
+ +

Operations dashboard

+

System status as of {{ today.isoformat() }}.

+
+
+ +
+
+
Active alerts
+
{{ active_alerts }}
+
+
+
Users
+
{{ user_count }}
+
+
+
Administrators
+
{{ admin_count }}
+
+
+ +
+
+
+

Expiring soonest

+ settings → +
+
+ {% if expiring_soon %} + + + + + + {% for a in expiring_soon %} + {% set days = (a.expiration_date - today).days %} + + + + + + {% endfor %} + +
TitleExpiresDays
{{ a.title }}{% if a.category %} {{ a.category }}{% endif %}{{ a.expiration_date.isoformat() }}{{ days }}d
+ {% else %} +

No upcoming alerts.

+ {% endif %} +
+
+ +
+

Recent activity

+ full log → +
+
+ {% if recent_audit %} +
    + {% for entry in recent_audit %} +
  • + {{ entry.action }} {% if entry.username %}· {{ entry.username }}{% endif %}{% if entry.details %}
    {{ entry.details }}{% endif %}
    + {{ entry.timestamp.strftime('%m-%d %H:%M') }} +
  • + {% endfor %} +
+ {% else %} +

No activity yet.

+ {% endif %} +
+
+
+ +{% endblock %} diff --git a/app/templates/backoffice/mail_config.html b/app/templates/backoffice/mail_config.html new file mode 100644 index 0000000..14ef9c0 --- /dev/null +++ b/app/templates/backoffice/mail_config.html @@ -0,0 +1,90 @@ +{% extends "base.html" %} +{% block title %}Mail configuration{% endblock %} +{% block content %} +
+
+ +

SMTP configuration

+

External mail server used to dispatch alert reminders. Edits take effect immediately.

+
+
+ +
+
+

Server settings

+
+
+ {{ form.csrf_token }} +
+
+ {{ form.smtp_host.label }}{{ form.smtp_host(placeholder="smtp.example.com") }} + {% for err in form.smtp_host.errors %}
{{ err }}
{% endfor %} +
+
+ {{ form.smtp_port.label }}{{ form.smtp_port() }} + {% for err in form.smtp_port.errors %}
{{ err }}
{% endfor %} +
+
+ {{ form.smtp_encryption.label }}{{ form.smtp_encryption() }} +
587 → STARTTLS · 465 → SSL/TLS · 25 → none
+
+
+ {{ form.smtp_username.label }}{{ form.smtp_username(autocomplete="off") }} +
+
+ {{ form.smtp_password.label }}{{ form.smtp_password(autocomplete="new-password") }} +
Leave blank to keep the current password.
+
+
+ {{ form.smtp_from_address.label }}{{ form.smtp_from_address() }} +
+
+ {{ form.smtp_from_name.label }}{{ form.smtp_from_name() }} +
+
+ +
+

Sent-folder copy (IMAP)

+

When enabled, a copy of every message the platform sends is saved to the mailbox's Sent folder over IMAP, so it appears in your normal mail client.

+
{{ form.imap_enabled() }} {{ form.imap_enabled.label }}
+
+
+ {{ form.imap_host.label }}{{ form.imap_host(placeholder="imap.purelymail.com") }} + {% for err in form.imap_host.errors %}
{{ err }}
{% endfor %} +
+
+ {{ form.imap_port.label }}{{ form.imap_port(placeholder="993") }} + {% for err in form.imap_port.errors %}
{{ err }}
{% endfor %} +
+
+ {{ form.imap_sent_folder.label }}{{ form.imap_sent_folder(placeholder="Sent") }} +
Usually Sent.
+
+
+ {{ form.imap_username.label }}{{ form.imap_username(autocomplete="off") }} +
+
+ {{ form.imap_password.label }}{{ form.imap_password(autocomplete="new-password") }} +
Blank keeps current; if never set, the SMTP password is used.
+
+
+ + {{ form.submit(class_="btn btn-primary") }} +
+
+
+ +
+

Send test email

+
+

Confirms credentials, port, and encryption are correct.

+
+ {{ test_form.csrf_token }} +
{{ test_form.test_recipient.label }}{{ test_form.test_recipient(placeholder="you@example.com") }}
+ {{ test_form.submit(class_="btn btn-ghost btn-block") }} +
+
+
+
+ +{% endblock %} diff --git a/app/templates/backoffice/mail_log.html b/app/templates/backoffice/mail_log.html new file mode 100644 index 0000000..3370c4b --- /dev/null +++ b/app/templates/backoffice/mail_log.html @@ -0,0 +1,73 @@ +{% extends "base.html" %} +{% block title %}Mail log{% endblock %} +{% block content %} +
+
+ +

Mail log

+

Every message the platform has dispatched, whether a copy reached the Sent folder, and its delivery result. Most-recent first.

+
+
+ ⬇ Export CSV +
+ {{ log_email_form.csrf_token }} + {{ log_email_form.log_recipient(placeholder="destination@example.com") }} + {{ log_email_form.submit(class_="btn btn-primary") }} +
+
+
+ + + + + + + + + + {% for m in pagination.items %} + + + + + + + + + + + {% else %} + + {% endfor %} + +
Timestamp (UTC)KindRecipientsSubjectStatusSent copyByInfo
{{ m.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}{{ m.kind }}{{ m.recipients or '—' }}{{ m.subject or '—' }} + {% if m.status == 'sent' %} + sent + {% else %} + failed + {% endif %} + + {% if m.sent_copy == 'saved' %} + saved + {% elif m.sent_copy == 'failed' %} + failed + {% elif m.sent_copy == 'disabled' %} + disabled + {% else %} + + {% endif %} + {{ m.triggered_by or 'system' }}{{ m.info or '' }}
No mail has been sent yet.
+ +{% if pagination.pages > 1 %} + +{% endif %} +{% endblock %} diff --git a/app/templates/backoffice/settings.html b/app/templates/backoffice/settings.html new file mode 100644 index 0000000..f637740 --- /dev/null +++ b/app/templates/backoffice/settings.html @@ -0,0 +1,45 @@ +{% extends "base.html" %} +{% block title %}Settings{% endblock %} +{% block content %} +
+
+ +

Global settings

+

Default reminder cadence and scheduler controls.

+
+
+ +
+
+

Default reminder schedule

+
+
+ {{ form.csrf_token }} +
+ {{ form.default_reminder_days.label }}{{ form.default_reminder_days() }} +
When creating a new alert, this is the default. Existing alerts are unaffected.
+ {% for err in form.default_reminder_days.errors %}
{{ err }}
{% endfor %} +
+ {{ form.submit(class_="btn btn-primary") }} +
+
+
+ +
+

Scheduler

+
+

Reminder dispatch runs every {{ scheduler_interval }} minute(s).

+

Configured via the SCHEDULER_INTERVAL_MINUTES environment variable. Restart the container to change it.

+ +
+ +

You can trigger a check immediately to verify reminders dispatch correctly.

+
+ + +
+
+
+
+ +{% endblock %} diff --git a/app/templates/backoffice/user_form.html b/app/templates/backoffice/user_form.html new file mode 100644 index 0000000..ee90e1a --- /dev/null +++ b/app/templates/backoffice/user_form.html @@ -0,0 +1,41 @@ +{% extends "base.html" %} +{% block title %}{{ 'Edit' if user else 'New' }} user{% endblock %} +{% block content %} +
+
+ +

{{ 'Edit user' if user else 'New user' }}

+ {% if user %}

Created {{ user.created_at.strftime('%Y-%m-%d') }}

{% endif %} +
+ ← Back +
+ +
+
+
+ {{ form.csrf_token }} +
+
{{ form.username.label }}{{ form.username() }} + {% for err in form.username.errors %}
{{ err }}
{% endfor %} +
+
{{ form.email.label }}{{ form.email() }} + {% for err in form.email.errors %}
{{ err }}
{% endfor %} +
+
+
+
{{ form.role.label }}{{ form.role() }}
+
{{ form.is_active() }} {{ form.is_active.label }}
+
+
+ {{ form.password.label }}{{ form.password(autocomplete="new-password") }} +
{% if user %}Leave blank to keep the existing password.{% else %}Min {{ config.PASSWORD_MIN_LENGTH }} characters.{% endif %}
+ {% for err in form.password.errors %}
{{ err }}
{% endfor %} +
+
+ {{ form.submit(class_="btn btn-primary") }} + Cancel +
+
+
+
+{% endblock %} diff --git a/app/templates/backoffice/users_list.html b/app/templates/backoffice/users_list.html new file mode 100644 index 0000000..48a7e76 --- /dev/null +++ b/app/templates/backoffice/users_list.html @@ -0,0 +1,65 @@ +{% extends "base.html" %} +{% block title %}Users{% endblock %} +{% block content %} +
+
+ +

Users

+

Manage accounts, roles, MFA, and lockouts.

+
+ + New user +
+ + + + + + + + + {% for u in users %} + + + + + + + + + + {% endfor %} + +
UsernameEmailRoleMFAStatusLast loginActions
{{ u.username }}{% if u.id == current_user.id %} (you){% endif %}{{ u.email }}{{ u.role }} + + {{ 'on' if u.mfa_enabled else 'off' }} + + + {% if u.is_locked %} + locked + {% else %} + {{ 'active' if u.is_active else 'disabled' }} + {% endif %} + + {% if u.last_login_at %}{{ u.last_login_at.strftime('%Y-%m-%d %H:%M') }}{% else %}never{% endif %} + + Edit + {% if u.mfa_enabled %} +
+ + +
+ {% endif %} + {% if u.is_locked %} +
+ + +
+ {% endif %} + {% if u.id != current_user.id %} +
+ + +
+ {% endif %} +
+{% endblock %} diff --git a/app/templates/base.html b/app/templates/base.html new file mode 100644 index 0000000..3b74e3b --- /dev/null +++ b/app/templates/base.html @@ -0,0 +1,136 @@ + + + + + + + {% block title %}{{ brand_name }}{% endblock %} — {{ brand_name }}{% if is_backoffice %} · backoffice{% endif %} + + + + + + + + +
+
+ + ▮▮ + {{ brand_name }} + {{ 'BACKOFFICE' if is_backoffice else 'FRONTOFFICE' }} + + + {% if current_user.is_authenticated and not session.get('mfa_pending') %} + + + + {% else %} +
+ +
+ {% endif %} +
+
+ +
+ {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} +
+ {% for cat, msg in messages %} +
+ + {{ msg }} +
+ {% endfor %} +
+ {% endif %} + {% endwith %} + + {% block content %}{% endblock %} +
+ + {% if request.endpoint != 'auth.login' %} +
+ {{ brand_name }} + · + {{ 'admin console' if is_backoffice else 'alert console' }} + {{ footer_text }} + operational +
+ {% endif %} + + + {% block scripts %}{% endblock %} + + diff --git a/app/templates/error.html b/app/templates/error.html new file mode 100644 index 0000000..d964f8d --- /dev/null +++ b/app/templates/error.html @@ -0,0 +1,12 @@ +{% extends "base.html" %} +{% block title %}{{ code }} · Error{% endblock %} +{% block content %} +
+
+
error
+

{{ code }}

+

{{ message }}

+ ← Return home +
+
+{% endblock %} diff --git a/app/templates/index.html b/app/templates/index.html new file mode 100644 index 0000000..555e91f --- /dev/null +++ b/app/templates/index.html @@ -0,0 +1,60 @@ +{% extends "base.html" %} +{% block title %}View · Alerts{% endblock %} +{% block content %} +
+
+ +

Active alerts

+

Live countdown to expiration. Reminders are dispatched automatically by email per the configured schedule.

+
+
+ today: {{ today_iso }} + {% if current_user.is_admin %} + + New alert + {% endif %} +
+
+ +{% if alerts %} +
+ {% for a in alerts %} + {% set state = 'is-expired' if a.days_left < 0 else ('is-critical' if a.days_left < 7 else ('is-warning' if a.days_left < 30 else '')) %} + {% set state_text = 'EXPIRED' if a.days_left < 0 else ('CRITICAL' if a.days_left < 7 else ('WARNING' if a.days_left < 30 else 'NOMINAL')) %} +
+
+

{{ a.title }}

+ {% if a.category %}{{ a.category }}{% endif %} +
+ {% if a.description %}

{{ a.description }}

{% endif %} + +
+ {{ a.title }} · expires {{ a.expiration_date }} +
+ +
+
days
+
hours
+
min
+
sec
+
+ +
+ {{ state_text }} + reminders: {{ a.reminder_days|join(', ') }}d +
+
+ {% endfor %} +
+{% else %} +
+
▮▮
+

No active alerts.

+ {% if current_user.is_admin %} +

Add your first one — license renewals, certificates, contracts, …

+ Create the first alert + {% else %} +

Ask an administrator to create alerts for your team.

+ {% endif %} +
+{% endif %} +{% endblock %} diff --git a/app/templates/login.html b/app/templates/login.html new file mode 100644 index 0000000..776e0ee --- /dev/null +++ b/app/templates/login.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} +{% block title %}Sign in{% endblock %} +{% block content %} +
+
+
authenticate
+

Sign in to {{ brand_name }}

+

{% if is_backoffice %}Administrator console.{% else %}Alert operations console.{% endif %}

+
+ {{ form.csrf_token }} +
+ {{ form.username.label }} + {{ form.username(autocomplete="username", autofocus=True) }} +
+
+ {{ form.password.label }} + {{ form.password(autocomplete="current-password") }} +
+ {{ form.submit(class_="btn btn-primary btn-block") }} +
+
+
+{% endblock %} diff --git a/app/templates/mfa.html b/app/templates/mfa.html new file mode 100644 index 0000000..c2275e2 --- /dev/null +++ b/app/templates/mfa.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} +{% block title %}Verify identity{% endblock %} +{% block content %} +
+
+
verify
+

Two-factor authentication

+

Enter the 6-digit code from your authenticator app.

+
+ {{ form.csrf_token }} +
+ {{ form.code.label }} + {{ form.code(autocomplete="one-time-code", inputmode="numeric", pattern="[0-9]{6}", autofocus=True, maxlength=6) }} +
+ {{ form.submit(class_="btn btn-primary btn-block") }} +
+
+
+{% endblock %} diff --git a/app/templates/mfa_setup.html b/app/templates/mfa_setup.html new file mode 100644 index 0000000..7c07a59 --- /dev/null +++ b/app/templates/mfa_setup.html @@ -0,0 +1,34 @@ +{% extends "base.html" %} +{% block title %}Set up MFA{% endblock %} +{% block content %} +
+
+ +

Enable multi-factor authentication

+

Scan the QR code with your authenticator (Google Authenticator, Authy, 1Password, …) and enter the 6-digit code to confirm.

+
+
+ +
+
+ MFA QR code +

Manual key (if you can't scan):

+
{{ secret }}
+ +
+ +
+ {{ form.csrf_token }} +
+ {{ form.code.label }} + {{ form.code(autocomplete="one-time-code", inputmode="numeric", pattern="[0-9]{6}", maxlength=6) }} +
Enter the current 6-digit code shown in your authenticator.
+
+
+ {{ form.submit(class_="btn btn-primary") }} + Cancel +
+
+
+
+{% endblock %} diff --git a/app/templates/setup.html b/app/templates/setup.html new file mode 100644 index 0000000..dffc1c5 --- /dev/null +++ b/app/templates/setup.html @@ -0,0 +1,35 @@ +{% extends "base.html" %} +{% block title %}Initial setup{% endblock %} +{% block content %} +
+
+
first-run setup
+

Create administrator

+

No accounts exist yet. Create the first administrator to secure {{ brand_name }}.

+
+ {{ form.csrf_token }} +
+ {{ form.username.label }} + {{ form.username(autocomplete="username", autofocus=True) }} + {% for err in form.username.errors %}
{{ err }}
{% endfor %} +
+
+ {{ form.email.label }} + {{ form.email(autocomplete="email") }} + {% for err in form.email.errors %}
{{ err }}
{% endfor %} +
+
+ {{ form.password.label }} + {{ form.password(autocomplete="new-password") }} + {% for err in form.password.errors %}
{{ err }}
{% endfor %} +
+
+ {{ form.confirm.label }} + {{ form.confirm(autocomplete="new-password") }} + {% for err in form.confirm.errors %}
{{ err }}
{% endfor %} +
+ {{ form.submit(class_="btn btn-primary btn-block") }} +
+
+
+{% endblock %} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e7cbae8 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,34 @@ +# docker-compose.yml +# All runtime configuration is read from the .env file in the same folder. +# This file intentionally contains NO secrets and NO environment values. + +services: + alerthub: + build: + context: . + dockerfile: Dockerfile + image: alerthub:latest + container_name: alerthub + restart: unless-stopped + env_file: + - .env + ports: + - "${FRONTOFFICE_PORT}:${FRONTOFFICE_PORT}" + volumes: + - alerthub_data:/data + # Hardening + read_only: false # SQLite needs to write to /data; the rest of FS is owned by non-root user + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + healthcheck: + test: ["CMD", "curl", "-fsS", "http://127.0.0.1:${FRONTOFFICE_PORT}/login"] + interval: 30s + timeout: 5s + start_period: 15s + retries: 3 + +volumes: + alerthub_data: + name: alerthub_data diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..16727fe --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,26 @@ +#!/bin/sh +# Starts the unified AlertHub app (frontoffice + /backoffice) on a single port. +# POSIX /bin/sh (dash) compatible. Uses exec so Gunicorn becomes the container's +# main process and receives signals directly for clean, graceful shutdown. + +set -eu + +PORT="${APP_PORT:-${FRONTOFFICE_PORT:-8080}}" +# A single worker guarantees exactly one reminder scheduler. Concurrency is +# handled with threads, which is plenty for this internal tool. You can raise +# GUNICORN_THREADS if needed; leave GUNICORN_WORKERS at 1 to avoid running the +# scheduler more than once. +WORKERS="${GUNICORN_WORKERS:-1}" +THREADS="${GUNICORN_THREADS:-4}" + +echo ">> AlertHub starting (frontoffice + /backoffice on one port)" +echo " listening -> 0.0.0.0:${PORT}" + +exec gunicorn --bind "0.0.0.0:${PORT}" \ + --workers "${WORKERS}" \ + --threads "${THREADS}" \ + --timeout 60 \ + --graceful-timeout 30 \ + --access-logfile - \ + --error-logfile - \ + "wsgi:application" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7360340 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,14 @@ +Flask==3.1.3 +Flask-Login==0.6.3 +Flask-WTF==1.2.2 +WTForms==3.2.1 +SQLAlchemy==2.0.36 +Flask-SQLAlchemy==3.1.1 +bcrypt==4.2.1 +pyotp==2.9.0 +qrcode[pil]==8.0 +Pillow==11.1.0 +APScheduler==3.11.0 +python-dotenv==1.0.1 +gunicorn==23.0.0 +email-validator==2.2.0 diff --git a/wsgi.py b/wsgi.py new file mode 100644 index 0000000..ee21a41 --- /dev/null +++ b/wsgi.py @@ -0,0 +1,13 @@ +"""WSGI entry point for AlertHub. + +Serves the whole application — the frontoffice at the root and the backoffice +under /backoffice — on a single port. +""" +from app import create_app + +application = create_app() + +if __name__ == "__main__": + import os + port = int(os.environ.get("APP_PORT", os.environ.get("FRONTOFFICE_PORT", "8080"))) + application.run(host="0.0.0.0", port=port, debug=False) diff --git a/wsgi_backoffice.py b/wsgi_backoffice.py new file mode 100644 index 0000000..e31005a --- /dev/null +++ b/wsgi_backoffice.py @@ -0,0 +1,9 @@ +"""WSGI entry point for the backoffice (port 9001).""" +from app import create_app + +application = create_app("backoffice") + +if __name__ == "__main__": + import os + port = int(os.environ.get("BACKOFFICE_PORT", "9001")) + application.run(host="0.0.0.0", port=port, debug=False) diff --git a/wsgi_frontoffice.py b/wsgi_frontoffice.py new file mode 100644 index 0000000..cdc51cd --- /dev/null +++ b/wsgi_frontoffice.py @@ -0,0 +1,9 @@ +"""WSGI entry point for the frontoffice (port 8080).""" +from app import create_app + +application = create_app("frontoffice") + +if __name__ == "__main__": + import os + port = int(os.environ.get("FRONTOFFICE_PORT", "8080")) + application.run(host="0.0.0.0", port=port, debug=False)