enforce password change if password is changeme

This commit is contained in:
2025-05-27 15:13:36 +02:00
parent 071b8ca2aa
commit 149487195b
12 changed files with 132 additions and 42 deletions

View File

@@ -1,6 +1,16 @@
from flask import render_template, request, flash, redirect, url_for
from flask_login import login_user, logout_user, login_required, current_user
from models import db, User
from functools import wraps
def require_password_change(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if current_user.is_authenticated and current_user.check_password('changeme'):
flash('Please change your password before continuing.', 'warning')
return redirect(url_for('auth.change_password'))
return f(*args, **kwargs)
return decorated_function
def init_routes(auth_bp):
@auth_bp.route('/login', methods=['GET', 'POST'])
@@ -18,8 +28,14 @@ def init_routes(auth_bp):
if not user or not user.check_password(password):
flash('Please check your login details and try again.', 'danger')
return redirect(url_for('auth.login'))
login_user(user, remember=remember)
# Check if user is using default password
if password == 'changeme':
flash('Please change your password before continuing.', 'warning')
return redirect(url_for('auth.change_password'))
next_page = request.args.get('next')
if next_page:
return redirect(next_page)
@@ -62,4 +78,27 @@ def init_routes(auth_bp):
@login_required
def logout():
logout_user()
return redirect(url_for('auth.login'))
return redirect(url_for('auth.login'))
@auth_bp.route('/change-password', methods=['GET', 'POST'])
@login_required
def change_password():
if request.method == 'POST':
current_password = request.form.get('current_password')
new_password = request.form.get('new_password')
confirm_password = request.form.get('confirm_password')
if not current_user.check_password(current_password):
flash('Current password is incorrect.', 'danger')
return redirect(url_for('auth.change_password'))
if new_password != confirm_password:
flash('New passwords do not match.', 'danger')
return redirect(url_for('auth.change_password'))
current_user.set_password(new_password)
db.session.commit()
flash('Password changed successfully!', 'success')
return redirect(url_for('main.dashboard'))
return render_template('auth/change_password.html')

View File

@@ -4,6 +4,7 @@ from models import db, User
from forms import UserForm
from flask import abort
from sqlalchemy import or_
from routes.auth import require_password_change
import json
import os
from werkzeug.utils import secure_filename
@@ -23,6 +24,7 @@ def admin_required():
@contacts_bp.route('/')
@login_required
@require_password_change
def contacts_list():
result = admin_required()
if result: return result
@@ -76,6 +78,7 @@ def contacts_list():
@contacts_bp.route('/new', methods=['GET', 'POST'])
@login_required
@require_password_change
def new_contact():
result = admin_required()
if result: return result
@@ -114,19 +117,16 @@ def new_contact():
is_admin=form.is_admin.data,
profile_picture=profile_picture
)
if form.new_password.data:
user.set_password(form.new_password.data)
else:
flash('Password is required when creating a new user.', 'error')
return render_template('contacts/form.html', form=form, title='New User', total_admins=total_admins)
user.set_password('changeme') # Set default password
db.session.add(user)
db.session.commit()
flash('User created successfully!', 'success')
flash('User created successfully! They will need to change their password on first login.', 'success')
return redirect(url_for('contacts.contacts_list'))
return render_template('contacts/form.html', form=form, title='New User', total_admins=total_admins)
@contacts_bp.route('/profile/edit', methods=['GET', 'POST'])
@login_required
@require_password_change
def edit_profile():
form = UserForm()
total_admins = User.query.filter_by(is_admin=True).count()
@@ -168,6 +168,7 @@ def edit_profile():
@contacts_bp.route('/<int:id>/edit', methods=['GET', 'POST'])
@login_required
@require_password_change
def edit_contact(id):
result = admin_required()
if result: return result
@@ -237,6 +238,7 @@ def edit_contact(id):
@contacts_bp.route('/<int:id>/delete', methods=['POST'])
@login_required
@require_password_change
def delete_contact(id):
result = admin_required()
if result: return result
@@ -251,6 +253,7 @@ def delete_contact(id):
@contacts_bp.route('/<int:id>/toggle-active', methods=['POST'])
@login_required
@require_password_change
def toggle_active(id):
result = admin_required()
if result: return result

View File

@@ -3,6 +3,7 @@ from flask_login import login_required, current_user
from flask_socketio import emit, join_room, leave_room
from models import db, Conversation, User, Message, MessageAttachment
from forms import ConversationForm
from routes.auth import require_password_change
import os
from werkzeug.utils import secure_filename
from datetime import datetime
@@ -44,6 +45,7 @@ def get_file_extension(filename):
@conversations_bp.route('/')
@login_required
@require_password_change
def conversations():
search = request.args.get('search', '').strip()
if current_user.is_admin:
@@ -57,6 +59,7 @@ def conversations():
@conversations_bp.route('/create', methods=['GET', 'POST'])
@login_required
@require_password_change
def create_conversation():
if not current_user.is_admin:
flash('Only administrators can create conversations.', 'error')
@@ -89,6 +92,7 @@ def create_conversation():
@conversations_bp.route('/<int:conversation_id>')
@login_required
@require_password_change
def conversation(conversation_id):
conversation = Conversation.query.get_or_404(conversation_id)
# Check if user is a member
@@ -109,6 +113,7 @@ def conversation(conversation_id):
@conversations_bp.route('/<int:conversation_id>/members')
@login_required
@require_password_change
def conversation_members(conversation_id):
conversation = Conversation.query.get_or_404(conversation_id)
if not current_user.is_admin and current_user not in conversation.members:
@@ -126,6 +131,7 @@ def conversation_members(conversation_id):
@conversations_bp.route('/<int:conversation_id>/members/add', methods=['POST'])
@login_required
@require_password_change
def add_member(conversation_id):
conversation = Conversation.query.get_or_404(conversation_id)
if not current_user.is_admin:
@@ -149,6 +155,7 @@ def add_member(conversation_id):
@conversations_bp.route('/<int:conversation_id>/members/<int:user_id>/remove', methods=['POST'])
@login_required
@require_password_change
def remove_member(conversation_id, user_id):
conversation = Conversation.query.get_or_404(conversation_id)
if not current_user.is_admin:
@@ -170,6 +177,7 @@ def remove_member(conversation_id, user_id):
@conversations_bp.route('/<int:conversation_id>/edit', methods=['GET', 'POST'])
@login_required
@require_password_change
def edit_conversation(conversation_id):
if not current_user.is_admin:
flash('Only administrators can edit conversations.', 'error')
@@ -213,6 +221,7 @@ def edit_conversation(conversation_id):
@conversations_bp.route('/<int:conversation_id>/delete', methods=['POST'])
@login_required
@require_password_change
def delete_conversation(conversation_id):
if not current_user.is_admin:
flash('Only administrators can delete conversations.', 'error')
@@ -251,6 +260,7 @@ def on_leave(data):
@conversations_bp.route('/<int:conversation_id>/send_message', methods=['POST'])
@login_required
@require_password_change
def send_message(conversation_id):
conversation = Conversation.query.get_or_404(conversation_id)
@@ -331,6 +341,7 @@ def send_message(conversation_id):
@conversations_bp.route('/messages/<int:message_id>/attachment/<int:attachment_index>')
@login_required
@require_password_change
def download_attachment(message_id, attachment_index):
message = Message.query.get_or_404(message_id)
conversation = message.conversation
@@ -353,6 +364,7 @@ def download_attachment(message_id, attachment_index):
@conversations_bp.route('/<int:conversation_id>/messages')
@login_required
@require_password_change
def get_messages(conversation_id):
conversation = Conversation.query.get_or_404(conversation_id)

View File

@@ -1,6 +1,7 @@
from flask import render_template, Blueprint, redirect, url_for, request, flash, Response
from flask_login import current_user, login_required
from models import User, db, Room, RoomFile, RoomMemberPermission, SiteSettings
from routes.auth import require_password_change
import os
from werkzeug.utils import secure_filename
from sqlalchemy import func, case, literal_column, text
@@ -26,13 +27,14 @@ def init_routes(main_bp):
return dict(site_settings=site_settings)
@main_bp.route('/')
@login_required
@require_password_change
def home():
if not current_user.is_authenticated:
return redirect(url_for('auth.login'))
return redirect(url_for('main.dashboard'))
@main_bp.route('/dashboard')
@login_required
@require_password_change
def dashboard():
logger.info("Loading dashboard...")
# Get 3 most recent users
@@ -265,6 +267,7 @@ def init_routes(main_bp):
@main_bp.route('/profile', methods=['GET', 'POST'])
@login_required
@require_password_change
def profile():
if request.method == 'POST':
# Handle profile picture removal
@@ -321,16 +324,19 @@ def init_routes(main_bp):
@main_bp.route('/starred')
@login_required
@require_password_change
def starred():
return render_template('starred/starred.html')
@main_bp.route('/conversations')
@login_required
@require_password_change
def conversations():
return redirect(url_for('conversations.conversations'))
@main_bp.route('/trash')
@login_required
@require_password_change
def trash():
return render_template('trash/trash.html')

View File

@@ -3,11 +3,13 @@ from flask_login import login_required, current_user
from models import db, Room, User, RoomMemberPermission, RoomFile
from forms import RoomForm
from routes.room_files import user_has_permission
from routes.auth import require_password_change
rooms_bp = Blueprint('rooms', __name__, url_prefix='/rooms')
@rooms_bp.route('/')
@login_required
@require_password_change
def rooms():
search = request.args.get('search', '').strip()
if current_user.is_admin:
@@ -27,6 +29,7 @@ def rooms():
@rooms_bp.route('/create', methods=['GET', 'POST'])
@login_required
@require_password_change
def create_room():
form = RoomForm()
if form.validate_on_submit():
@@ -56,6 +59,7 @@ def create_room():
@rooms_bp.route('/<int:room_id>')
@login_required
@require_password_change
def room(room_id):
room = Room.query.get_or_404(room_id)
# Admins always have access
@@ -74,6 +78,7 @@ def room(room_id):
@rooms_bp.route('/<int:room_id>/members')
@login_required
@require_password_change
def room_members(room_id):
room = Room.query.get_or_404(room_id)
# Admins always have access
@@ -91,6 +96,7 @@ def room_members(room_id):
@rooms_bp.route('/<int:room_id>/members/add', methods=['POST'])
@login_required
@require_password_change
def add_member(room_id):
room = Room.query.get_or_404(room_id)
# Membership check using RoomMemberPermission
@@ -121,6 +127,7 @@ def add_member(room_id):
@rooms_bp.route('/<int:room_id>/members/<int:user_id>/remove', methods=['POST'])
@login_required
@require_password_change
def remove_member(room_id, user_id):
room = Room.query.get_or_404(room_id)
# Membership check using RoomMemberPermission