Avoids shell-quoting hell. Used as:
docker compose ... exec -T api \
python -m app.cli.reset_password admin@example.com NEW_PASSWORD
docker compose ... exec -T api \
python -m app.cli.reset_password --list
Lists existing users on lookup miss so it's obvious which email to
target. Refuses passwords shorter than 4 chars to catch typos.
78 lines
2.3 KiB
Python
78 lines
2.3 KiB
Python
"""Reset a user's password from the command line.
|
|
|
|
Usage (from the repo root, on the server, after a git pull):
|
|
|
|
sudo docker compose -p hp-studios-ai-content-agent \\
|
|
-f docker-compose.yml -f docker-compose.prod.yml \\
|
|
exec -T api python -m app.cli.reset_password admin@example.com NEW_PASSWORD
|
|
|
|
To list users without resetting anything:
|
|
|
|
sudo docker compose -p hp-studios-ai-content-agent \\
|
|
-f docker-compose.yml -f docker-compose.prod.yml \\
|
|
exec -T api python -m app.cli.reset_password --list
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
|
|
from app.core.security import hash_password
|
|
from app.db.models import User
|
|
from app.db.session import SessionLocal
|
|
|
|
|
|
def list_users() -> int:
|
|
db = SessionLocal()
|
|
try:
|
|
rows = db.query(User).order_by(User.created_at).all()
|
|
if not rows:
|
|
print("No users in the database.")
|
|
return 0
|
|
print(f"{'EMAIL':40s} {'ROLE':6s} {'NAME'}")
|
|
print("-" * 80)
|
|
for u in rows:
|
|
print(f"{u.email:40s} {u.role:6s} {u.name}")
|
|
return 0
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
def reset(email: str, new_password: str) -> int:
|
|
db = SessionLocal()
|
|
try:
|
|
u = db.query(User).filter(User.email == email).first()
|
|
if not u:
|
|
print(f"No user found with email: {email}")
|
|
print()
|
|
print("Existing users:")
|
|
for row in db.query(User).all():
|
|
print(f" {row.email:40s} {row.role:6s} {row.name}")
|
|
return 1
|
|
u.password_hash = hash_password(new_password)
|
|
db.commit()
|
|
print(f"OK — password reset for {u.email} (role={u.role})")
|
|
return 0
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
def main() -> int:
|
|
args = sys.argv[1:]
|
|
if not args or args[0] in ("-h", "--help"):
|
|
print(__doc__)
|
|
return 0
|
|
if args[0] in ("--list", "-l", "list"):
|
|
return list_users()
|
|
if len(args) != 2:
|
|
print("Usage: python -m app.cli.reset_password EMAIL NEW_PASSWORD")
|
|
print(" python -m app.cli.reset_password --list")
|
|
return 2
|
|
email, new_password = args[0], args[1]
|
|
if len(new_password) < 4:
|
|
print("Refusing to set a password shorter than 4 characters.")
|
|
return 2
|
|
return reset(email, new_password)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|