From 3a200be924739deacaa7ff8da5672ef4e36dfcaf Mon Sep 17 00:00:00 2001 From: NguyenND Date: Wed, 4 Mar 2026 11:42:03 -0500 Subject: [PATCH] Mar 04 2026: added enable/disable user functions --- app/models/user.py | 20 +++++++++++++------ app/routes/auth.py | 28 ++++++++++++++++++++++++++ app/templates/auth/user_form.html | 33 +++++++++++++++++++++++++++++++ app/templates/auth/users.html | 26 ++++++++++++++++++++---- 4 files changed, 97 insertions(+), 10 deletions(-) diff --git a/app/models/user.py b/app/models/user.py index c39aa6d..37856d4 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -10,16 +10,24 @@ def load_user(user_id): class User(UserMixin, db.Model): __tablename__ = 'users' - id = db.Column(db.Integer, primary_key=True) - username = db.Column(db.String(100), unique=True, nullable=False, index=True) - email = db.Column(db.String(255), unique=True, nullable=False, index=True) + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(100), unique=True, nullable=False, index=True) + email = db.Column(db.String(255), unique=True, nullable=False, index=True) password_hash = db.Column(db.String(255), nullable=False) - role = db.Column(db.Enum('admin', 'supervisor', 'inspector'), nullable=False) - created_at = db.Column(db.DateTime, default=now_eastern) + role = db.Column(db.Enum('admin', 'supervisor', 'inspector'), nullable=False) + created_at = db.Column(db.DateTime, default=now_eastern) + active = db.Column(db.Boolean, default=True, nullable=False) # Relationships inspections = db.relationship('Inspection', backref='inspector', lazy='dynamic') + # ── Flask-Login integration ──────────────────────────────────────────── + # Override UserMixin.is_active so that disabled accounts are rejected + # automatically by login_required and login_user() without any extra code. + @property + def is_active(self): + return self.active + def set_password(self, password): self.password_hash = generate_password_hash(password) @@ -27,4 +35,4 @@ class User(UserMixin, db.Model): return check_password_hash(self.password_hash, password) def __repr__(self): - return f'' + return f'' \ No newline at end of file diff --git a/app/routes/auth.py b/app/routes/auth.py index 8542e22..24ee932 100644 --- a/app/routes/auth.py +++ b/app/routes/auth.py @@ -39,6 +39,9 @@ def login(): user = User.query.filter_by(username=form.username.data).first() if user and user.check_password(form.password.data): + if not user.active: + flash('Your account has been disabled. Please contact an administrator.', 'danger') + return render_template('auth/login.html', form=form) login_user(user, remember=form.remember_me.data) # Use validated next URL — never redirect blindly to request.args['next'] next_page = _safe_next(request.args.get('next')) @@ -203,3 +206,28 @@ def delete_user(user_id): log_action(ACTION_DELETE, 'User', user_id, username) flash(f'User {username} deleted successfully.', 'success') return redirect(url_for('auth.list_users')) + +@bp.route('/users//toggle-active', methods=['POST']) +@login_required +@admin_required +def toggle_active(user_id): + user = User.query.get_or_404(user_id) + + if user.id == current_user.id: + flash('You cannot disable your own account.', 'danger') + return redirect(url_for('auth.list_users')) + + user.active = not user.active + db.session.commit() + + action_label = 'enabled' if user.active else 'disabled' + logger.info( + 'AUTH | user_%s | admin_id=%s admin=%s target_user=%s', + action_label, current_user.id, current_user.username, user.username, + ) + log_action( + ACTION_UPDATE, 'User', user.id, user.username, + f'account {action_label} by {current_user.username}', + ) + flash(f'User {user.username} has been {action_label}.', 'success') + return redirect(request.referrer or url_for('auth.list_users')) \ No newline at end of file diff --git a/app/templates/auth/user_form.html b/app/templates/auth/user_form.html index ae342f3..7f42cae 100644 --- a/app/templates/auth/user_form.html +++ b/app/templates/auth/user_form.html @@ -10,6 +10,12 @@

{{ title }}

+ {% if user and not user.active %} +
+ + This account is currently disabled. The user cannot log in. +
+ {% endif %}
{{ form.hidden_tag() }} @@ -62,6 +68,33 @@ {{ form.role(class="form-select") }}
+ {% if user and user.id != current_user.id %} +
+ +
+ + {{ 'Active' if user.active else 'Disabled' }} + + + + + +
+
+ {% if user.active %} + Disabling this account will immediately prevent the user from logging in. + {% else %} + Enabling this account will restore the user's ability to log in. + {% endif %} +
+
+ {% endif %} +
+
-