"""Provision reviewer accounts into the store (#128). The write endpoints authenticate a per-user login, so a deployment needs accounts before anyone can confirm a value. Two ways to supply them, both idempotent (a re-run rotates a password or role in place): # From the ENDOPATH_SEED_USERS secret (the same JSON the Space seeds from at startup): ENDOPATH_SEED_USERS='[{"username":"apath","password":"...","role":"pathologist","holds_licence":true, "display_name":"A. Pathologist"}]' \ PYTHONPATH=src python scripts/seed_users.py # Or one account from flags: PYTHONPATH=src python scripts/seed_users.py \ --username apath --password ... --role pathologist --licence --display-name "A. Pathologist" Writes to SQLite by default and to Postgres when DATABASE_URL is set (the deployed store), the same resolution storage.connect uses. The plaintext password is hashed once and never stored. """ from __future__ import annotations import argparse import os from endopath import auth, storage def main() -> None: parser = argparse.ArgumentParser(description="Provision reviewer accounts (#128).") parser.add_argument("--username") parser.add_argument("--password") parser.add_argument("--role", default="pathologist") parser.add_argument( "--licence", action="store_true", help="the account holds a pathology licence (may confirm licence-required fields)", ) parser.add_argument("--display-name", default="") args = parser.parse_args() if args.username: seeds = [ auth.SeedUser( username=args.username, password=args.password or "", role=args.role, holds_licence=args.licence, display_name=args.display_name, ) ] else: seeds = auth.parse_seed_users(os.environ.get(auth.SEED_USERS_ENV, "")) if not seeds: parser.error( f"no accounts to seed: pass --username/--password, or set {auth.SEED_USERS_ENV} to a JSON list" ) conn = storage.connect() try: for seed in seeds: if not seed.password: parser.error(f"account {seed.username!r} has no password") storage.upsert_user( conn, username=seed.username, password_hash=auth.hash_password(seed.password), role=seed.role, holds_licence=seed.holds_licence, display_name=seed.display_name, ) print( f"provisioned {seed.username!r} " f"(role={seed.role}, licence={'yes' if seed.holds_licence else 'no'})" ) print(f"{storage.count_users(conn)} account(s) total") finally: conn.close() if __name__ == "__main__": main()