Use password hashes for authentication

This commit is contained in:
2026-07-07 16:16:50 +02:00
parent 8f64760add
commit dcca3fa042
5 changed files with 127 additions and 13 deletions
+20 -2
View File
@@ -14,7 +14,7 @@ Backend:
- `app.py` creates the Flask app.
- HTTP Basic Auth is implemented with `Flask-HTTPAuth`.
- Users are currently hardcoded in `BETA_USERS`.
- Users are currently configured in `BETA_USERS` with Werkzeug password hashes.
- `/` renders the active calculator template.
- `/static/<path:filename>` is intended to serve static files behind Basic Auth.
- `/api/health` returns health/version information.
@@ -50,6 +50,24 @@ If `build_info.json` is missing, malformed, or does not contain a usable value,
The data is loaded centrally in `app.py` and made available to every template as `build_info`.
## Authentication
RollCalc uses HTTP Basic Auth. User entries in `BETA_USERS` have this shape:
```python
"username": {
"password_hash": "..."
}
```
Password verification uses `werkzeug.security.check_password_hash`. New hashes or config snippets can be generated with:
```bash
python scripts/manage_users.py username
```
Passwords and hashes must not be logged.
## Roll Geometry
Calculations assume an ideal cylindrical winding.
@@ -210,7 +228,7 @@ Login/access logging remains part of RollCalc, but log viewing and article-data
## Known Technical Risks
- Hardcoded plaintext credentials in `app.py`.
- Password hashes are currently configured in `app.py`; this should eventually move to a protected external configuration or secrets mechanism.
- Basic Auth only; no sessions or role framework beyond the user dictionary.
- `access_log.json` is rewritten on every logged request and is not concurrency-safe.
- No log rotation or retention policy is implemented.
+19 -4
View File
@@ -42,7 +42,7 @@ If port `5000` is already occupied, start through Flask's CLI without changing f
flask --app app run --host 127.0.0.1 --port 5001
```
The app uses HTTP Basic Auth. Current credentials are defined in `BETA_USERS` in `app.py`.
The app uses HTTP Basic Auth. Current users are defined in `BETA_USERS` in `app.py` with Werkzeug password hashes.
## Project Layout
@@ -90,7 +90,22 @@ Implemented routes:
| `/api/health` | `GET` | Basic Auth | Returns app health and version. |
| `/api/user` | `GET` | Basic Auth | Returns current authenticated user info. |
Authentication is implemented with `Flask-HTTPAuth`. The current code checks `BETA_USERS` and performs direct plaintext string comparison.
Authentication is implemented with `Flask-HTTPAuth`. The current code checks `BETA_USERS` with `werkzeug.security.check_password_hash`; plaintext passwords are not stored in the application.
Generate a password hash or user entry with:
```bash
python scripts/manage_users.py username
python scripts/manage_users.py username --json
```
For non-interactive local maintenance only:
```bash
python scripts/manage_users.py username --password 'new-password'
```
Do not commit real passwords or print them in logs.
Access logging is handled by `log_access()`, which reads `access_log.json`, appends a record, and writes the whole file back.
@@ -287,11 +302,11 @@ Utility script that updates relative frontend fetch/register paths to Flask-styl
- There are duplicated or legacy-looking files with similar names. Before editing JavaScript under `static/`, confirm it is actually referenced by the active template.
- The documentation under `docs/` contains deployment and feature notes, but some filenames and route assumptions may not match the current app exactly.
- The Flask dev server is used for local development only. Production should use a WSGI server.
- Use `scripts/manage_users.py` to create password hashes when adding or rotating Basic Auth users.
## Known Risks and Maintenance Items
- Credentials are hardcoded in `app.py` and should be moved to environment variables or a secrets manager.
- Passwords are stored in plaintext and compared directly.
- Password hashes are currently configured in `app.py`; user configuration should eventually move to environment variables, a protected config file, or a secrets manager.
- `access_log.json` is not safe for concurrent writes.
- `access_log.json` grows without rotation or retention limits.
- The custom `/static/<path:filename>` route is intended to protect static files, but Flask also creates a default static route unless disabled. Verify effective route behavior before relying on static-file protection.
+7
View File
@@ -628,5 +628,12 @@
"endpoint": "/",
"method": "GET",
"status": 200
},
{
"timestamp": "2026-07-07T16:13:42.026433",
"username": "mtazl",
"endpoint": "/",
"method": "GET",
"status": 200
}
]
+25 -7
View File
@@ -5,6 +5,7 @@ Flask app with HTTP Basic Authentication
from flask import Flask, render_template, request, send_file, send_from_directory, jsonify
from flask_httpauth import HTTPBasicAuth
from werkzeug.security import check_password_hash
from functools import wraps
import os
from datetime import datetime
@@ -19,12 +20,24 @@ auth = HTTPBasicAuth()
# Benutzer für Beta-Phase (in Produktion aus env-Variablen laden!)
BETA_USERS = {
"beta": "rollcalc_beta_2026", # ⚠️ ÄNDERN!
"naue": "naue_access_2026", # ⚠️ ÄNDERN!
"cniehues": "beta_test_2026", # ⚠️ ÄNDERN!
"lvollmert": "Geheim!", # ⚠️ ÄNDERN!
"mtazl": "rollcalc", # ⚠️ ÄNDERN!
"controlling": "beta2026" # ⚠️ ÄNDERN!
"beta": {
"password_hash": "pbkdf2:sha256:600000$HNtF3VdZKdmtg8Vw$8f976a99a457c5e924916dc6f735dd631042c8c126af8d4bda9abcd7532d904f"
},
"naue": {
"password_hash": "pbkdf2:sha256:600000$RVPiW2mYXJJIp3d7$0f838b8619d386e2da57e60ae8ccb944bb0f9f63d81ce3d174ec3034b0abcd19"
},
"cniehues": {
"password_hash": "pbkdf2:sha256:600000$iNmhakf34xk26xrz$ae78846461370c52b7f56fad5ac0ae8e33860d6f00075ac78e3a5b8c31d51a3d"
},
"lvollmert": {
"password_hash": "pbkdf2:sha256:600000$8FGop5ymmHpuIqfK$8ba223e20c514cf6bc1297435a2e7e2be81668951297f0e01ad6a931718ad7de"
},
"mtazl": {
"password_hash": "pbkdf2:sha256:600000$H19skoalhWxlLnY5$d30bfeae38470b19900648fd110978b3677607c3a161d663a7e5d5c2167a3710"
},
"controlling": {
"password_hash": "pbkdf2:sha256:600000$ksj86lrKz6nnSpXz$5161e0b5c76bd72d6b2cd04866ef49e69f463f474d7e47075171894bf3366f29"
}
}
# Logging für Auditing
@@ -74,7 +87,12 @@ def inject_build_info():
@auth.verify_password
def verify_password(username, password):
"""Verify HTTP Basic Auth credentials"""
if username in BETA_USERS and BETA_USERS[username] == password:
user_config = BETA_USERS.get(username)
if not user_config:
return None
password_hash = user_config.get("password_hash")
if password_hash and check_password_hash(password_hash, password):
return username
return None
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""
Create password hashes or user config snippets for RollCalc Basic Auth users.
"""
import argparse
import getpass
import json
from werkzeug.security import generate_password_hash
def build_parser():
parser = argparse.ArgumentParser(
description="Generate RollCalc password hashes or user config entries."
)
parser.add_argument(
"username",
nargs="?",
help="Optional username for a generated user config snippet.",
)
parser.add_argument(
"--password",
help="Password to hash. Omit to enter it securely via prompt.",
)
parser.add_argument(
"--json",
action="store_true",
help="Output a JSON object for the user entry.",
)
return parser
def main():
args = build_parser().parse_args()
password = args.password
if password is None:
password = getpass.getpass("Password: ")
password_hash = generate_password_hash(password)
if args.username:
entry = {args.username: {"password_hash": password_hash}}
if args.json:
print(json.dumps(entry, indent=2))
else:
print(f'"{args.username}": {{')
print(f' "password_hash": "{password_hash}"')
print("}")
else:
print(password_hash)
if __name__ == "__main__":
main()