7a4453108a
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
138 lines
4.9 KiB
Python
138 lines
4.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Operator-account admin CLI — the bootstrap and the break-glass. Run inside the
|
|
terra-view container against the live DB. Temp/raw passwords are printed ONCE; only
|
|
hashes persist.
|
|
|
|
# first superadmin (before any UI is reachable) — prompts for a password, or --generate
|
|
python3 backend/operator_admin.py create-superadmin --email you@x.com --name "Brian"
|
|
|
|
# a parent's account — generates a temp password, must-change on first login
|
|
python3 backend/operator_admin.py create-user --email dad@x.com --name "Dad" --role admin
|
|
|
|
python3 backend/operator_admin.py reset-password --email dad@x.com
|
|
python3 backend/operator_admin.py list
|
|
python3 backend/operator_admin.py disable --email dad@x.com
|
|
python3 backend/operator_admin.py enable --email dad@x.com
|
|
"""
|
|
import os
|
|
import sys
|
|
import getpass
|
|
import argparse
|
|
from datetime import datetime
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
from backend.database import SessionLocal
|
|
from backend.models import OperatorUser
|
|
from backend.operator_auth import (
|
|
create_operator, reset_operator_password, set_operator_active, _norm_email,
|
|
)
|
|
|
|
|
|
def _get(db, email):
|
|
u = db.query(OperatorUser).filter_by(email=_norm_email(email)).first()
|
|
if not u:
|
|
sys.exit(f"No operator with email '{email}'.")
|
|
return u
|
|
|
|
|
|
def cmd_create_superadmin(email, name, password=None, generate=False):
|
|
db = SessionLocal()
|
|
try:
|
|
if password is None and not generate:
|
|
password = getpass.getpass("Password for new superadmin: ")
|
|
if not password or len(password) < 8:
|
|
sys.exit("Password must be at least 8 characters.")
|
|
user, raw = create_operator(db, email, name, "superadmin",
|
|
password=None if generate else password)
|
|
if generate:
|
|
print(f"✓ Superadmin {user.email} created. Temp password (shown once): {raw}")
|
|
else:
|
|
print(f"✓ Superadmin {user.email} created.")
|
|
except ValueError as e:
|
|
sys.exit(str(e))
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
def cmd_create_user(email, name, role="admin"):
|
|
db = SessionLocal()
|
|
try:
|
|
user, raw = create_operator(db, email, name, role)
|
|
print(f"✓ {role} {user.email} created. Temp password (shown once): {raw}")
|
|
print(" They'll be required to change it on first login.")
|
|
except ValueError as e:
|
|
sys.exit(str(e))
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
def cmd_reset_password(email):
|
|
db = SessionLocal()
|
|
try:
|
|
user = _get(db, email)
|
|
raw = reset_operator_password(db, user)
|
|
print(f"✓ Reset {user.email}. Temp password (shown once): {raw}")
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
def cmd_set_active(email, active):
|
|
db = SessionLocal()
|
|
try:
|
|
user = _get(db, email)
|
|
set_operator_active(db, user, active)
|
|
print(f"✓ {user.email} {'enabled' if active else 'disabled'}.")
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
def cmd_list():
|
|
db = SessionLocal()
|
|
try:
|
|
users = db.query(OperatorUser).order_by(OperatorUser.display_name).all()
|
|
if not users:
|
|
print("No operators yet. Run create-superadmin first.")
|
|
return
|
|
for u in users:
|
|
locked = " [LOCKED]" if (u.locked_until and u.locked_until > datetime.utcnow()) else ""
|
|
state = "active" if u.active else "DISABLED"
|
|
last = u.last_login_at.strftime("%Y-%m-%d %H:%M") if u.last_login_at else "never"
|
|
print(f" {u.display_name:<12} {u.email:<28} {u.role:<11} {state}{locked} last: {last}")
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description="Operator-account admin")
|
|
sub = ap.add_subparsers(dest="cmd", required=True)
|
|
|
|
p = sub.add_parser("create-superadmin")
|
|
p.add_argument("--email", required=True); p.add_argument("--name", required=True)
|
|
p.add_argument("--generate", action="store_true", help="generate a temp password instead of prompting")
|
|
p.set_defaults(fn=lambda a: cmd_create_superadmin(a.email, a.name, generate=a.generate))
|
|
|
|
p = sub.add_parser("create-user")
|
|
p.add_argument("--email", required=True); p.add_argument("--name", required=True)
|
|
p.add_argument("--role", default="admin", choices=["admin", "superadmin"])
|
|
p.set_defaults(fn=lambda a: cmd_create_user(a.email, a.name, a.role))
|
|
|
|
p = sub.add_parser("reset-password")
|
|
p.add_argument("--email", required=True)
|
|
p.set_defaults(fn=lambda a: cmd_reset_password(a.email))
|
|
|
|
p = sub.add_parser("disable"); p.add_argument("--email", required=True)
|
|
p.set_defaults(fn=lambda a: cmd_set_active(a.email, False))
|
|
|
|
p = sub.add_parser("enable"); p.add_argument("--email", required=True)
|
|
p.set_defaults(fn=lambda a: cmd_set_active(a.email, True))
|
|
|
|
p = sub.add_parser("list"); p.set_defaults(fn=lambda a: cmd_list())
|
|
|
|
args = ap.parse_args()
|
|
args.fn(args)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|