password reset
This commit is contained in:
159
routes/auth.py
159
routes/auth.py
@@ -1,10 +1,12 @@
|
||||
from flask import render_template, request, flash, redirect, url_for, Blueprint, jsonify
|
||||
from flask_login import login_user, logout_user, login_required, current_user
|
||||
from models import db, User, Notif, PasswordSetupToken
|
||||
from models import db, User, Notif, PasswordSetupToken, PasswordResetToken
|
||||
from functools import wraps
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from utils import log_event, create_notification, get_unread_count
|
||||
from utils.notification import generate_mail_from_notification
|
||||
import string
|
||||
import secrets
|
||||
|
||||
auth_bp = Blueprint('auth', __name__)
|
||||
|
||||
@@ -306,4 +308,155 @@ def init_routes(auth_bp):
|
||||
flash('Password set up successfully! Welcome to DocuPulse.', 'success')
|
||||
return redirect(url_for('main.dashboard'))
|
||||
|
||||
return render_template('auth/setup_password.html')
|
||||
return render_template('auth/setup_password.html')
|
||||
|
||||
@auth_bp.route('/forgot-password', methods=['GET', 'POST'])
|
||||
def forgot_password():
|
||||
if current_user.is_authenticated:
|
||||
return redirect(url_for('main.dashboard'))
|
||||
|
||||
if request.method == 'POST':
|
||||
email = request.form.get('email')
|
||||
|
||||
if not email:
|
||||
flash('Please enter your email address.', 'error')
|
||||
return render_template('auth/forgot_password.html')
|
||||
|
||||
# Check if user exists
|
||||
user = User.query.filter_by(email=email).first()
|
||||
|
||||
if user:
|
||||
# Generate a secure token
|
||||
token = secrets.token_urlsafe(32)
|
||||
|
||||
# Create password reset token
|
||||
reset_token = PasswordResetToken(
|
||||
user_id=user.id,
|
||||
token=token,
|
||||
expires_at=datetime.utcnow() + timedelta(hours=1), # 1 hour expiration
|
||||
ip_address=request.remote_addr
|
||||
)
|
||||
db.session.add(reset_token)
|
||||
|
||||
# Create notification for password reset
|
||||
notif = create_notification(
|
||||
notif_type='password_reset',
|
||||
user_id=user.id,
|
||||
details={
|
||||
'message': 'You requested a password reset. Click the link below to reset your password.',
|
||||
'reset_link': url_for('auth.reset_password', token=token, _external=True),
|
||||
'expiry_time': (datetime.utcnow() + timedelta(hours=1)).strftime('%Y-%m-%d %H:%M:%S UTC'),
|
||||
'ip_address': request.remote_addr,
|
||||
'timestamp': datetime.utcnow().isoformat()
|
||||
},
|
||||
generate_mail=False # Don't auto-generate email, we'll do it manually
|
||||
)
|
||||
|
||||
# Generate and send email manually
|
||||
if notif:
|
||||
generate_mail_from_notification(notif)
|
||||
|
||||
# Log the password reset request
|
||||
log_event(
|
||||
event_type='user_update',
|
||||
details={
|
||||
'user_id': user.id,
|
||||
'user_name': f"{user.username} {user.last_name}",
|
||||
'email': user.email,
|
||||
'update_type': 'password_reset_request',
|
||||
'ip_address': request.remote_addr,
|
||||
'success': True
|
||||
}
|
||||
)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
# Always show success message to prevent email enumeration
|
||||
flash('If an account with that email exists, a password reset link has been sent to your email address.', 'success')
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
return render_template('auth/forgot_password.html')
|
||||
|
||||
@auth_bp.route('/reset-password/<token>', methods=['GET', 'POST'])
|
||||
def reset_password(token):
|
||||
if current_user.is_authenticated:
|
||||
return redirect(url_for('main.dashboard'))
|
||||
|
||||
# Find the token
|
||||
reset_token = PasswordResetToken.query.filter_by(token=token).first()
|
||||
|
||||
if not reset_token or not reset_token.is_valid():
|
||||
flash('Invalid or expired password reset link. Please request a new password reset.', 'error')
|
||||
return redirect(url_for('auth.forgot_password'))
|
||||
|
||||
if request.method == 'POST':
|
||||
password = request.form.get('password')
|
||||
confirm_password = request.form.get('confirm_password')
|
||||
|
||||
if not password or not confirm_password:
|
||||
flash('Please fill in all fields.', 'error')
|
||||
return render_template('auth/reset_password.html', token=token)
|
||||
|
||||
if password != confirm_password:
|
||||
flash('Passwords do not match.', 'error')
|
||||
return render_template('auth/reset_password.html', token=token)
|
||||
|
||||
# Password requirements
|
||||
if len(password) < 8:
|
||||
flash('Password must be at least 8 characters long.', 'error')
|
||||
return render_template('auth/reset_password.html', token=token)
|
||||
|
||||
if not any(c.isupper() for c in password):
|
||||
flash('Password must contain at least one uppercase letter.', 'error')
|
||||
return render_template('auth/reset_password.html', token=token)
|
||||
|
||||
if not any(c.islower() for c in password):
|
||||
flash('Password must contain at least one lowercase letter.', 'error')
|
||||
return render_template('auth/reset_password.html', token=token)
|
||||
|
||||
if not any(c.isdigit() for c in password):
|
||||
flash('Password must contain at least one number.', 'error')
|
||||
return render_template('auth/reset_password.html', token=token)
|
||||
|
||||
if not any(c in string.punctuation for c in password):
|
||||
flash('Password must contain at least one special character.', 'error')
|
||||
return render_template('auth/reset_password.html', token=token)
|
||||
|
||||
# Update user's password
|
||||
user = reset_token.user
|
||||
user.set_password(password)
|
||||
|
||||
# Mark token as used
|
||||
reset_token.used = True
|
||||
|
||||
# Create password change notification
|
||||
create_notification(
|
||||
notif_type='password_changed',
|
||||
user_id=user.id,
|
||||
details={
|
||||
'message': 'Your password has been reset successfully.',
|
||||
'timestamp': datetime.utcnow().isoformat()
|
||||
}
|
||||
)
|
||||
|
||||
# Log password reset event
|
||||
log_event(
|
||||
event_type='user_update',
|
||||
details={
|
||||
'user_id': user.id,
|
||||
'user_name': f"{user.username} {user.last_name}",
|
||||
'email': user.email,
|
||||
'update_type': 'password_reset',
|
||||
'ip_address': request.remote_addr,
|
||||
'success': True
|
||||
}
|
||||
)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
# Log the user in and redirect to dashboard
|
||||
login_user(user)
|
||||
flash('Password reset successfully! Welcome back to DocuPulse.', 'success')
|
||||
return redirect(url_for('main.dashboard'))
|
||||
|
||||
return render_template('auth/reset_password.html', token=token)
|
||||
Reference in New Issue
Block a user