7d24e7f527
Auth: - Fix local login (was redirecting to portal; now authenticates AdminUser directly) - Portal SSO still takes priority in production via nginx headers Role system (admin | editor | readonly): - New app/utils/decorators.py with editor_required and admin_required decorators - All write routes protected with editor_required (create/edit/delete/import/mask) - Settings user management protected with admin_required - Sidebar hides write-only links for readonly users - Dashboard quick actions and list page buttons hidden for readonly Settings page: - Role colour badges (admin=red, editor=blue, readonly=grey) - Inline role changer per user (dropdown auto-submit) - Reset password modal per user - Delete user button with confirmation - Add user form includes role selector with legend Portal user sync: - New /internal/sync-user endpoint receives user pre-creation from portal - INTERNAL_SYNC_SECRET added to config - portal/config.py: added internal_url for itassets app so _sync_user_to_app works
107 lines
3.8 KiB
Python
107 lines
3.8 KiB
Python
from flask import Blueprint, render_template, redirect, url_for, flash, request, current_app
|
|
from flask_login import login_required, current_user
|
|
from app.extensions import db
|
|
from app.models.admin_user import AdminUser
|
|
from app.utils.decorators import admin_required
|
|
|
|
bp = Blueprint('settings', __name__, url_prefix='/settings')
|
|
|
|
|
|
@bp.route('/')
|
|
@login_required
|
|
def index():
|
|
admins = AdminUser.query.order_by(AdminUser.username).all()
|
|
return render_template('settings/index.html', admins=admins, config=current_app.config)
|
|
|
|
|
|
@bp.route('/admin/new', methods=['POST'])
|
|
@login_required
|
|
@admin_required
|
|
def create_admin():
|
|
username = request.form.get('username', '').strip()
|
|
email = request.form.get('email', '').strip()
|
|
full_name = request.form.get('full_name', '').strip()
|
|
password = request.form.get('password', '')
|
|
role = request.form.get('role', 'readonly')
|
|
|
|
if role not in ('admin', 'editor', 'readonly'):
|
|
role = 'readonly'
|
|
|
|
if not username or not email or not password:
|
|
flash('Username, email and password are required.', 'danger')
|
|
return redirect(url_for('settings.index'))
|
|
|
|
if AdminUser.query.filter_by(username=username).first():
|
|
flash(f'Username "{username}" is already taken.', 'danger')
|
|
return redirect(url_for('settings.index'))
|
|
|
|
admin = AdminUser(username=username, email=email, full_name=full_name, role=role)
|
|
admin.set_password(password)
|
|
db.session.add(admin)
|
|
db.session.commit()
|
|
flash(f'User "{username}" created with role "{role}".', 'success')
|
|
return redirect(url_for('settings.index'))
|
|
|
|
|
|
@bp.route('/admin/<int:admin_id>/toggle', methods=['POST'])
|
|
@login_required
|
|
@admin_required
|
|
def toggle_admin(admin_id):
|
|
admin = AdminUser.query.get_or_404(admin_id)
|
|
if admin.id == current_user.id:
|
|
flash('You cannot deactivate your own account.', 'danger')
|
|
return redirect(url_for('settings.index'))
|
|
admin.is_active = not admin.is_active
|
|
db.session.commit()
|
|
status = 'activated' if admin.is_active else 'deactivated'
|
|
flash(f'User "{admin.username}" {status}.', 'success')
|
|
return redirect(url_for('settings.index'))
|
|
|
|
|
|
@bp.route('/admin/<int:admin_id>/change-role', methods=['POST'])
|
|
@login_required
|
|
@admin_required
|
|
def change_role(admin_id):
|
|
admin = AdminUser.query.get_or_404(admin_id)
|
|
if admin.id == current_user.id:
|
|
flash('You cannot change your own role.', 'danger')
|
|
return redirect(url_for('settings.index'))
|
|
role = request.form.get('role', 'readonly')
|
|
if role not in ('admin', 'editor', 'readonly'):
|
|
flash('Invalid role.', 'danger')
|
|
return redirect(url_for('settings.index'))
|
|
admin.role = role
|
|
db.session.commit()
|
|
flash(f'Role for "{admin.username}" updated to "{role}".', 'success')
|
|
return redirect(url_for('settings.index'))
|
|
|
|
|
|
@bp.route('/admin/<int:admin_id>/reset-password', methods=['POST'])
|
|
@login_required
|
|
@admin_required
|
|
def reset_password(admin_id):
|
|
admin = AdminUser.query.get_or_404(admin_id)
|
|
new_password = request.form.get('new_password', '')
|
|
if len(new_password) < 8:
|
|
flash('Password must be at least 8 characters.', 'danger')
|
|
return redirect(url_for('settings.index'))
|
|
admin.set_password(new_password)
|
|
db.session.commit()
|
|
flash(f'Password for "{admin.username}" has been reset.', 'success')
|
|
return redirect(url_for('settings.index'))
|
|
|
|
|
|
@bp.route('/admin/<int:admin_id>/delete', methods=['POST'])
|
|
@login_required
|
|
@admin_required
|
|
def delete_admin(admin_id):
|
|
admin = AdminUser.query.get_or_404(admin_id)
|
|
if admin.id == current_user.id:
|
|
flash('You cannot delete your own account.', 'danger')
|
|
return redirect(url_for('settings.index'))
|
|
username = admin.username
|
|
db.session.delete(admin)
|
|
db.session.commit()
|
|
flash(f'User "{username}" deleted.', 'success')
|
|
return redirect(url_for('settings.index'))
|