diff --git a/README.md b/README.md index 60a09c8..b306f9b 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,15 @@ The initial administrator account is `admin` with password `admin`, as requested With the Docker service running, rotate the initial password locally. The command prompts for a new password without writing it to a file or command history. It also clears the old MFA binding so that the next administrator sign-in requires a new QR-based authenticator enrollment. ```powershell +# Windows PowerShell .\scripts\rotate-admin.ps1 ``` +```sh +# Linux, macOS, or any POSIX shell +sh ./scripts/rotate-admin.sh +``` + After it prints `ADMIN_PASSWORD_ROTATED_MFA_RESET`, sign in as `admin` with the new password, scan the fresh QR code with an authenticator application, and enter its six-digit code to complete enrollment. Store the new password in a password manager. ## Email and password recovery diff --git a/TODO.md b/TODO.md index 85ab7b6..363a879 100644 --- a/TODO.md +++ b/TODO.md @@ -5,7 +5,7 @@ - [x] QR-based MFA enrollment for iPhone and Android authenticators. - [x] Email addresses, member password changes, and expiring password-reset links. - [ ] Configure production SMTP credentials and verify outgoing email delivery. See the SMTP delivery steps in [README.md](README.md); complete after the test email is received. -- [ ] Run `./scripts/rotate-admin.ps1`, then sign in as `admin` and scan the new MFA QR code to complete authenticator enrollment. +- [ ] Run `./scripts/rotate-admin.ps1` on Windows or `sh ./scripts/rotate-admin.sh` on Linux/macOS, then sign in as `admin` and scan the new MFA QR code to complete authenticator enrollment. - [x] Add CSRF protection to all state-changing forms. - [ ] Add automated database backups and test restoration. - [ ] Configure TLS reverse proxy and production domain for `eternityproject.fi`. \ No newline at end of file diff --git a/scripts/rotate-admin.sh b/scripts/rotate-admin.sh new file mode 100644 index 0000000..c3961c1 --- /dev/null +++ b/scripts/rotate-admin.sh @@ -0,0 +1,44 @@ +#!/bin/sh +set -eu + +if ! command -v docker >/dev/null 2>&1; then + printf '%s\n' "Docker is required." >&2 + exit 1 +fi + +if ! docker compose ps --status running --services 2>/dev/null | grep -qx "eternityproject"; then + printf '%s\n' "The eternityproject Docker service must be running." >&2 + exit 1 +fi + +printf '%s' "Enter new admin password (10+ characters): " +stty -echo +IFS= read -r password +stty echo +printf '\n' +trap 'stty echo 2>/dev/null || true; unset password' EXIT HUP INT TERM + +if [ "${#password}" -lt 10 ]; then + printf '%s\n' "The new password must have at least 10 characters." >&2 + exit 1 +fi + +printf '%s\n' "$password" | docker compose exec -T eternityproject python -c ' +import sys +from werkzeug.security import generate_password_hash +from app import app, get_db + +password = sys.stdin.readline().rstrip("\r\n") +if len(password) < 10: + raise SystemExit("The new password must have at least 10 characters.") +with app.app_context(): + database = get_db() + result = database.execute( + "UPDATE users SET password_hash = ?, mfa_secret = NULL, mfa_enabled = 0 WHERE username = '\''admin'\'' AND role = '\''admin'\''", + (generate_password_hash(password),), + ) + database.commit() + if result.rowcount != 1: + raise SystemExit("The administrator account was not found.") +print("ADMIN_PASSWORD_ROTATED_MFA_RESET") +'