Updated backend
This commit is contained in:
103
scripts/make_auth_secrets.py
Normal file
103
scripts/make_auth_secrets.py
Normal file
@@ -0,0 +1,103 @@
|
||||
"""
|
||||
Generate the authentication secrets that backend/.env needs.
|
||||
|
||||
python scripts/make_auth_secrets.py # random passwords
|
||||
python scripts/make_auth_secrets.py --admin-password 'my pass' --user-password 'other'
|
||||
|
||||
Prints .env lines ready to paste. Passwords are shown once, on stdout only -
|
||||
they are not written anywhere, because only their PBKDF2 digest is stored. If
|
||||
you lose one, rerun this and replace the hash.
|
||||
|
||||
Imports nothing from `app` on purpose: settings.py refuses to load without the
|
||||
very values this script exists to produce, so importing it would deadlock the
|
||||
one workflow that fixes that.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import hashlib
|
||||
import secrets
|
||||
import string
|
||||
|
||||
_PBKDF2_ITERATIONS = 600_000
|
||||
|
||||
# Ambiguous glyphs removed - these get retyped off a screen or read aloud.
|
||||
_ALPHABET = "".join(
|
||||
c for c in string.ascii_letters + string.digits if c not in "0O1lI"
|
||||
)
|
||||
|
||||
|
||||
def hash_password(password: str, *, iterations: int = _PBKDF2_ITERATIONS) -> str:
|
||||
"""Must stay byte-compatible with app/infrastructure/security.hash_password."""
|
||||
salt = secrets.token_bytes(16)
|
||||
digest = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, iterations)
|
||||
return "$".join(
|
||||
(
|
||||
"pbkdf2_sha256",
|
||||
str(iterations),
|
||||
base64.b64encode(salt).decode("ascii"),
|
||||
base64.b64encode(digest).decode("ascii"),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def generate_password(length: int = 20) -> str:
|
||||
return "".join(secrets.choice(_ALPHABET) for _ in range(length))
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--admin-password", help="Use this instead of a generated one")
|
||||
parser.add_argument("--user-password", help="Use this instead of a generated one")
|
||||
parser.add_argument(
|
||||
"--api-key",
|
||||
metavar="NAME:ROLE",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Also mint an API_KEYS entry, e.g. --api-key partner-x:user (repeatable)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
admin_password = args.admin_password or generate_password()
|
||||
user_password = args.user_password or generate_password()
|
||||
|
||||
print("# --- Paste into backend/.env -------------------------------------")
|
||||
print(f"AUTH_ENABLED=true")
|
||||
print(f"AUTH_SECRET_KEY={secrets.token_urlsafe(48)}")
|
||||
print(f"AUTH_ADMIN_USERNAME=admin")
|
||||
print(f"AUTH_ADMIN_PASSWORD_HASH={hash_password(admin_password)}")
|
||||
print(f"AUTH_USER_USERNAME=user")
|
||||
print(f"AUTH_USER_PASSWORD_HASH={hash_password(user_password)}")
|
||||
|
||||
if args.api_key:
|
||||
entries = []
|
||||
secrets_shown = []
|
||||
for spec in args.api_key:
|
||||
try:
|
||||
name, role = spec.split(":", 1)
|
||||
except ValueError:
|
||||
parser.error(f"--api-key expects NAME:ROLE, got {spec!r}")
|
||||
if role not in {"admin", "user"}:
|
||||
parser.error(f"--api-key role must be 'admin' or 'user', got {role!r}")
|
||||
key = secrets.token_urlsafe(32)
|
||||
entries.append(f"{name}:{role}:{key}")
|
||||
secrets_shown.append((name, key))
|
||||
print(f"API_KEYS={','.join(entries)}")
|
||||
|
||||
print()
|
||||
print("# --- Sign-in passwords. Shown once; store them in a password manager.")
|
||||
print(f"# admin : {admin_password}")
|
||||
print(f"# user : {user_password}")
|
||||
if args.api_key:
|
||||
print("#")
|
||||
print("# --- API keys. Consumers send: X-API-Key: <key>")
|
||||
for name, key in secrets_shown:
|
||||
print(f"# {name} : {key}")
|
||||
print()
|
||||
print("# The passwords above are NOT stored - only the hashes are. Rerun this")
|
||||
print("# script to replace one you have lost.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user