feat: document user management system and RBAC implementation
- Update copilot-instructions.md with user model, API routes, and frontend patterns - Update README.md with RBAC details, user management API, and security sections - Add user management technical documentation to TECH-CHANGELOG.md - Bump version to 2025.1.0-alpha.13 with user management changelog entries
This commit is contained in:
@@ -10,8 +10,10 @@ from flask import Blueprint, request, jsonify, session
|
||||
import os
|
||||
from server.database import Session
|
||||
from models.models import User, UserRole
|
||||
from server.permissions import require_auth
|
||||
import bcrypt
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
|
||||
sys.path.append('/workspace')
|
||||
|
||||
@@ -66,8 +68,17 @@ def login():
|
||||
|
||||
# Verify password
|
||||
if not bcrypt.checkpw(password.encode('utf-8'), user.password_hash.encode('utf-8')):
|
||||
# Track failed login attempt
|
||||
user.last_failed_login_at = datetime.now(timezone.utc)
|
||||
user.failed_login_attempts = (user.failed_login_attempts or 0) + 1
|
||||
db_session.commit()
|
||||
return jsonify({"error": "Invalid credentials"}), 401
|
||||
|
||||
# Successful login: update last_login_at and reset failed attempts
|
||||
user.last_login_at = datetime.now(timezone.utc)
|
||||
user.failed_login_attempts = 0
|
||||
db_session.commit()
|
||||
|
||||
# Create session
|
||||
session['user_id'] = user.id
|
||||
session['username'] = user.username
|
||||
@@ -173,6 +184,57 @@ def check_auth():
|
||||
return jsonify({"authenticated": False}), 200
|
||||
|
||||
|
||||
@auth_bp.route("/change-password", methods=["PUT"])
|
||||
@require_auth
|
||||
def change_password():
|
||||
"""
|
||||
Allow the authenticated user to change their own password.
|
||||
|
||||
Request body:
|
||||
{
|
||||
"current_password": "string",
|
||||
"new_password": "string"
|
||||
}
|
||||
|
||||
Returns:
|
||||
200: {"message": "Password changed successfully"}
|
||||
400: {"error": "Validation error"}
|
||||
401: {"error": "Invalid current password"}
|
||||
404: {"error": "User not found"}
|
||||
"""
|
||||
data = request.get_json() or {}
|
||||
current_password = data.get("current_password", "")
|
||||
new_password = data.get("new_password", "")
|
||||
|
||||
if not current_password or not new_password:
|
||||
return jsonify({"error": "Current password and new password are required"}), 400
|
||||
|
||||
if len(new_password) < 6:
|
||||
return jsonify({"error": "New password must be at least 6 characters"}), 400
|
||||
|
||||
user_id = session.get('user_id')
|
||||
db_session = Session()
|
||||
try:
|
||||
user = db_session.query(User).filter_by(id=user_id).first()
|
||||
if not user:
|
||||
session.clear()
|
||||
return jsonify({"error": "User not found"}), 404
|
||||
|
||||
# Verify current password
|
||||
if not bcrypt.checkpw(current_password.encode('utf-8'), user.password_hash.encode('utf-8')):
|
||||
return jsonify({"error": "Current password is incorrect"}), 401
|
||||
|
||||
# Update password hash and timestamp
|
||||
new_hash = bcrypt.hashpw(new_password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
|
||||
user.password_hash = new_hash
|
||||
user.last_password_change_at = datetime.now(timezone.utc)
|
||||
db_session.commit()
|
||||
|
||||
return jsonify({"message": "Password changed successfully"}), 200
|
||||
finally:
|
||||
db_session.close()
|
||||
|
||||
|
||||
@auth_bp.route("/dev-login-superadmin", methods=["POST"])
|
||||
def dev_login_superadmin():
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user