Updated backend

This commit is contained in:
sriram
2026-08-12 16:37:28 +05:30
parent eb3567df62
commit ac8cfacbc9
21 changed files with 1496 additions and 155 deletions

View 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()