enforce password change if password is changeme
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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')
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
50
templates/auth/change_password.html
Normal file
50
templates/auth/change_password.html
Normal file
@@ -0,0 +1,50 @@
|
||||
{% extends "common/base.html" %}
|
||||
|
||||
{% block title %}Change Password{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="max-w-md mx-auto">
|
||||
<div class="bg-white rounded-lg shadow p-6">
|
||||
<h2 class="text-2xl font-bold mb-6 text-center" style="color: var(--primary-color);">Change Password</h2>
|
||||
<form method="POST" class="space-y-4">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
|
||||
<div>
|
||||
<label for="current_password" class="block text-sm font-medium text-gray-700 mb-1">
|
||||
<i class="fas fa-key me-2" style="color: var(--primary-color);"></i>Current Password
|
||||
</label>
|
||||
<input type="password" name="current_password" id="current_password" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2"
|
||||
style="--tw-ring-color: var(--primary-color);">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="new_password" class="block text-sm font-medium text-gray-700 mb-1">
|
||||
<i class="fas fa-lock me-2" style="color: var(--primary-color);"></i>New Password
|
||||
</label>
|
||||
<input type="password" name="new_password" id="new_password" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2"
|
||||
style="--tw-ring-color: var(--primary-color);">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="confirm_password" class="block text-sm font-medium text-gray-700 mb-1">
|
||||
<i class="fas fa-check-circle me-2" style="color: var(--primary-color);"></i>Confirm New Password
|
||||
</label>
|
||||
<input type="password" name="confirm_password" id="confirm_password" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2"
|
||||
style="--tw-ring-color: var(--primary-color);">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="w-full text-white px-6 py-2 rounded-lg transition duration-200"
|
||||
style="background-color: var(--primary-color); border: 1px solid var(--primary-color);"
|
||||
onmouseover="this.style.backgroundColor='var(--primary-light)'"
|
||||
onmouseout="this.style.backgroundColor='var(--primary-color)'">
|
||||
<i class="fas fa-save me-2"></i>Change Password
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -6,7 +6,7 @@
|
||||
<div class="d-flex justify-content-between align-items-center mb-6">
|
||||
<div>
|
||||
<h3 class="mb-0">
|
||||
<i class="fas fa-user me-2" style="color:#16767b;"></i>
|
||||
<i class="fas fa-user me-2" style="color: var(--primary-color);"></i>
|
||||
{% if title %}
|
||||
{{ title }}
|
||||
{% else %}
|
||||
@@ -71,36 +71,6 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if current_user.is_admin %}
|
||||
<div class="relative">
|
||||
{{ form.new_password.label(class="block text-sm font-medium text-gray-700 mb-1") }}
|
||||
{{ form.new_password(class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 pr-10", autocomplete="new-password", id="new_password") }}
|
||||
<button type="button" tabindex="-1" class="absolute right-2 top-9 text-gray-500" style="background: none; border: none;" onmousedown="showPwd('new_password')" onmouseup="hidePwd('new_password')" onmouseleave="hidePwd('new_password')">
|
||||
<i class="fas fa-eye"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="relative">
|
||||
{{ form.confirm_password.label(class="block text-sm font-medium text-gray-700 mb-1") }}
|
||||
{{ form.confirm_password(class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 pr-10", autocomplete="new-password", id="confirm_password") }}
|
||||
<button type="button" tabindex="-1" class="absolute right-2 top-9 text-gray-500" style="background: none; border: none;" onmousedown="showPwd('confirm_password')" onmouseup="hidePwd('confirm_password')" onmouseleave="hidePwd('confirm_password')">
|
||||
<i class="fas fa-eye"></i>
|
||||
</button>
|
||||
{% if form.confirm_password.errors %}
|
||||
{% for error in form.confirm_password.errors %}
|
||||
<p class="mt-1 text-sm text-red-600">{{ error }}</p>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<script>
|
||||
function showPwd(id) {
|
||||
document.getElementById(id).type = 'text';
|
||||
}
|
||||
function hidePwd(id) {
|
||||
document.getElementById(id).type = 'password';
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
{{ form.phone.label(class="block text-sm font-medium text-gray-700 mb-1") }}
|
||||
@@ -177,7 +147,10 @@
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
{{ form.submit(class="text-white px-6 py-2 rounded-lg transition duration-200", style="background-color: #16767b; border: 1px solid #16767b;", onmouseover="this.style.backgroundColor='#1a8a90'", onmouseout="this.style.backgroundColor='#16767b'") }}
|
||||
{{ form.submit(class="text-white px-6 py-2 rounded-lg transition duration-200",
|
||||
style="background-color: var(--primary-color); border: 1px solid var(--primary-color);",
|
||||
onmouseover="this.style.backgroundColor='var(--primary-light)'",
|
||||
onmouseout="this.style.backgroundColor='var(--primary-color)'") }}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user