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