provinans / scripts /seed_users.py
reversely's picture
Upload folder using huggingface_hub
c808721 verified
Raw
History Blame Contribute Delete
3.41 kB
"""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. The password comes from ENDOPATH_SEED_PASSWORD or an interactive prompt,
# never a flag, so it does not land in shell history or the process listing:
ENDOPATH_SEED_PASSWORD=... PYTHONPATH=src python scripts/seed_users.py \
--username apath --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 getpass
import os
from endopath import auth, storage
# The single-account path reads the password from here (or an interactive prompt), never a --password
# flag, so a real credential never enters shell history or the process listing.
SEED_PASSWORD_ENV = "ENDOPATH_SEED_PASSWORD"
def main() -> None:
parser = argparse.ArgumentParser(description="Provision reviewer accounts (#128).")
parser.add_argument("--username")
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:
password = os.environ.get(SEED_PASSWORD_ENV) or getpass.getpass(
f"password for {args.username!r}: "
)
seeds = [
auth.SeedUser(
username=args.username,
password=password,
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 (with {SEED_PASSWORD_ENV} or a prompt), "
f"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()