diff --git a/enferno/commands.py b/enferno/commands.py index 5674640ba..f5f8896a5 100644 --- a/enferno/commands.py +++ b/enferno/commands.py @@ -22,10 +22,7 @@ from enferno.utils.db_alignment_helpers import DBAlignmentChecker from enferno.utils.logging_utils import get_logger from sqlalchemy import text -from enferno.admin.models import Bulletin -from enferno.admin.models.DynamicField import DynamicField from enferno.admin.models.DynamicFormHistory import DynamicFormHistory -from enferno.utils.date_helper import DateHelper from enferno.utils.form_history_utils import record_form_history from enferno.utils.validation_utils import validate_password_policy @@ -255,7 +252,7 @@ def reset(username: str, password: str) -> None: except ValueError as e: click.echo(str(e)) return - user.password = hash_password(password) + user.set_password(password) user.save() click.echo("User password has been reset successfully.") logger.info("User password has been reset successfully.") @@ -303,7 +300,7 @@ def generate_password(length: int = 16) -> str: results.append((user.username, user.email, new_password)) if not dry_run: - user.password = hash_password(new_password) + user.set_password(new_password) user.set_security_reset_key() user.save() @@ -528,7 +525,6 @@ def fail(msg): fail("Redis not reachable") try: - from celery import current_app as celery_app from enferno.tasks import celery inspector = celery.control.inspect(timeout=2) @@ -771,7 +767,7 @@ def status() -> None: total_extracted = sum(s["count"] for s in status_map.values()) pending = total_media - total_extracted - click.echo(f"\nOCR Status Summary") + click.echo("\nOCR Status Summary") click.echo(f"{'─' * 40}") click.echo(f"Total media: {total_media:,}") click.echo(f"Pending (no OCR): {pending:,}") diff --git a/enferno/user/models.py b/enferno/user/models.py index 326fa0fac..fb9c75856 100644 --- a/enferno/user/models.py +++ b/enferno/user/models.py @@ -234,6 +234,17 @@ def unset_security_reset_key(self) -> None: key = f"{SECURITY_KEY_NAMESPACE}:{self.id}" rds.delete(key) + def set_password(self, password: str) -> None: + """Hash and set the user password, clearing any active force-reset flag. + + Centralizing this on the model keeps the force-reset Redis flag in sync + with the stored hash, regardless of whether the password is written via + a CLI command or the admin UI. The web /change flow continues to clear + the flag via the `password_changed` signal. + """ + self.password = hash_password(password) + self.unset_security_reset_key() + def roles_in(self, roles: list) -> bool: chk = [self.has_role(r) for r in roles] return any(chk) @@ -354,7 +365,7 @@ def from_json(self, item: dict) -> "User": # check password is not empty password = item.get("password") if password: - self.password = hash_password(password) + self.set_password(password) self.name = item.get("name")