huge updates
This commit is contained in:
1 parent
b5a43ac0ad
commit
6cc74a355f
40 files changed
+6878
-2378
No files matched your search
+220
-209
@@ -1,12 +1,13 @@
|
||||
# Core Django imports
|
||||
from django.contrib.auth import authenticate, login
|
||||
from django.shortcuts import redirect, render
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.decorators.csrf import csrf_protect
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.hashers import make_password
|
||||
from django.contrib import messages
|
||||
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import validate_email
|
||||
import re
|
||||
import uuid
|
||||
|
||||
# Local app imports
|
||||
@@ -23,239 +24,249 @@ from app.utils import (
|
||||
verify_reset_token
|
||||
)
|
||||
|
||||
def validate_phone_number(phone):
|
||||
"""Validate phone number format"""
|
||||
if not phone or not isinstance(phone, str):
|
||||
raise ValidationError("Phone number must be a string")
|
||||
if not re.match(r'^\+?1?\d{9,10}$', phone):
|
||||
raise ValidationError("Invalid phone number format")
|
||||
return True
|
||||
|
||||
|
||||
@csrf_protect
|
||||
def reset_password_options(request):
|
||||
"""View for selecting between phone and email password reset"""
|
||||
# Get institute for logo
|
||||
institute = Institute.objects.first()
|
||||
return render(request, "login/reset_options.html", {"institute": institute})
|
||||
if not request.user.is_authenticated:
|
||||
institute = Institute.objects.first()
|
||||
return render(request, "login/reset_options.html", {"institute": institute})
|
||||
return redirect('dashboard')
|
||||
|
||||
@csrf_protect
|
||||
def password_reset_phone(request):
|
||||
"""View for initiating password reset with phone number"""
|
||||
# Get institute for logo
|
||||
institute = Institute.objects.first()
|
||||
|
||||
if request.method == "POST":
|
||||
phone = request.POST.get("phone")
|
||||
if not request.user.is_authenticated:
|
||||
institute = Institute.objects.first()
|
||||
|
||||
# Validate phone number
|
||||
if not phone or not phone.isdigit():
|
||||
return render(request, "login/password_reset_phone.html", {
|
||||
"error": "Please enter a valid phone number",
|
||||
"is_email": False,
|
||||
"institute": institute
|
||||
})
|
||||
if request.method == "POST":
|
||||
try:
|
||||
phone = request.POST.get("phone", "").strip()
|
||||
validate_phone_number(phone)
|
||||
|
||||
# Check if a user with this phone exists
|
||||
student = Student.objects.filter(phone=phone).first()
|
||||
staff = Staff.objects.filter(phone=phone).first()
|
||||
|
||||
if not student and not staff:
|
||||
return render(request, "login/password_reset_phone.html", {
|
||||
"error": "No account found with this phone number",
|
||||
"is_email": False,
|
||||
"institute": institute
|
||||
})
|
||||
|
||||
# Generate secret key and store it
|
||||
secret_key = generate_secret_key()
|
||||
store_secret_key(phone, secret_key)
|
||||
|
||||
# Generate and send OTP
|
||||
otp = generate_otp(secret_key)
|
||||
send_result = send_otp_sms(phone, otp)
|
||||
|
||||
if send_result:
|
||||
return render(request, "login/password_reset_otp.html", {
|
||||
"phone": phone,
|
||||
"is_email": False,
|
||||
"institute": institute
|
||||
})
|
||||
else:
|
||||
return render(request, "login/password_reset_phone.html", {
|
||||
"error": "Failed to send OTP. Please try again.",
|
||||
"is_email": False,
|
||||
"institute": institute
|
||||
})
|
||||
except ValidationError as e:
|
||||
return render(request, "login/password_reset_phone.html", {
|
||||
"error": str(e),
|
||||
"is_email": False,
|
||||
"institute": institute
|
||||
})
|
||||
|
||||
# Check if a user with this phone exists
|
||||
student = Student.objects.filter(phone=phone).first()
|
||||
staff = Staff.objects.filter(phone=phone).first()
|
||||
|
||||
if not student and not staff:
|
||||
return render(request, "login/password_reset_phone.html", {
|
||||
"error": "No account found with this phone number",
|
||||
"is_email": False,
|
||||
"institute": institute
|
||||
})
|
||||
|
||||
# Generate secret key and store it
|
||||
secret_key = generate_secret_key()
|
||||
store_secret_key(phone, secret_key)
|
||||
|
||||
# Generate and send OTP
|
||||
otp = generate_otp(secret_key)
|
||||
send_result = send_otp_sms(phone, otp)
|
||||
|
||||
if send_result:
|
||||
# Redirect to OTP verification page
|
||||
return render(request, "login/password_reset_otp.html", {
|
||||
"phone": phone,
|
||||
"is_email": False,
|
||||
"institute": institute
|
||||
})
|
||||
else:
|
||||
return render(request, "login/password_reset_phone.html", {
|
||||
"error": "Failed to send OTP. Please try again.",
|
||||
"is_email": False,
|
||||
"institute": institute
|
||||
})
|
||||
|
||||
return render(request, "login/password_reset_phone.html", {
|
||||
"is_email": False,
|
||||
"institute": institute
|
||||
})
|
||||
return render(request, "login/password_reset_phone.html", {
|
||||
"is_email": False,
|
||||
"institute": institute
|
||||
})
|
||||
return redirect('dashboard')
|
||||
|
||||
@csrf_protect
|
||||
def password_reset_phone_verify(request):
|
||||
"""View for verifying OTP for password reset"""
|
||||
# Get institute for logo
|
||||
institute = Institute.objects.first()
|
||||
|
||||
if request.method == "POST":
|
||||
phone = request.POST.get("phone")
|
||||
otp = request.POST.get("otp")
|
||||
if not request.user.is_authenticated:
|
||||
institute = Institute.objects.first()
|
||||
|
||||
if not phone or not otp:
|
||||
return render(request, "login/password_reset_otp.html", {
|
||||
"error": "Phone and OTP are required",
|
||||
"phone": phone,
|
||||
"is_email": False,
|
||||
"institute": institute
|
||||
})
|
||||
if request.method == "POST":
|
||||
try:
|
||||
phone = request.POST.get("phone", "").strip()
|
||||
otp = request.POST.get("otp", "").strip()
|
||||
|
||||
validate_phone_number(phone)
|
||||
if not otp or not otp.isdigit() or len(otp) != 6:
|
||||
raise ValidationError("Invalid OTP format")
|
||||
|
||||
# Verify OTP
|
||||
if verify_otp(phone, otp):
|
||||
token = str(uuid.uuid4())
|
||||
store_reset_token(token, phone)
|
||||
|
||||
return render(request, "login/password_reset_set.html", {
|
||||
"phone": phone,
|
||||
"token": token,
|
||||
"is_email": False,
|
||||
"institute": institute
|
||||
})
|
||||
else:
|
||||
return render(request, "login/password_reset_otp.html", {
|
||||
"error": "Invalid or expired OTP",
|
||||
"phone": phone,
|
||||
"is_email": False,
|
||||
"institute": institute
|
||||
})
|
||||
except ValidationError as e:
|
||||
return render(request, "login/password_reset_otp.html", {
|
||||
"error": str(e),
|
||||
"phone": phone if 'phone' in locals() else "",
|
||||
"is_email": False,
|
||||
"institute": institute
|
||||
})
|
||||
|
||||
# Verify OTP
|
||||
if verify_otp(phone, otp):
|
||||
# Generate a secure token for this reset
|
||||
token = str(uuid.uuid4())
|
||||
# Store token in database
|
||||
store_reset_token(token, phone)
|
||||
|
||||
# Redirect to set new password
|
||||
return render(request, "login/password_reset_set.html", {
|
||||
"phone": phone,
|
||||
"token": token,
|
||||
"is_email": False,
|
||||
"institute": institute
|
||||
})
|
||||
else:
|
||||
return render(request, "login/password_reset_otp.html", {
|
||||
"error": "Invalid or expired OTP",
|
||||
"phone": phone,
|
||||
"is_email": False,
|
||||
"institute": institute
|
||||
})
|
||||
|
||||
# If GET request, redirect to phone login
|
||||
return redirect("phone_reset_password")
|
||||
return redirect("phone_reset_password")
|
||||
return redirect('dashboard')
|
||||
|
||||
@csrf_protect
|
||||
def resend_phone_otp(request):
|
||||
"""View for resending OTP for password reset"""
|
||||
if request.method == "POST":
|
||||
phone = request.POST.get("phone")
|
||||
if not request.user.is_authenticated:
|
||||
if request.method == "POST":
|
||||
try:
|
||||
phone = request.POST.get("phone", "").strip()
|
||||
validate_phone_number(phone)
|
||||
|
||||
secret_key = generate_secret_key()
|
||||
store_secret_key(phone, secret_key)
|
||||
|
||||
otp = generate_otp(secret_key)
|
||||
send_result = send_otp_sms(phone, otp)
|
||||
|
||||
if send_result:
|
||||
return render(request, "login/password_reset_otp.html", {
|
||||
"phone": phone,
|
||||
"message": "OTP resent successfully",
|
||||
"is_email": False
|
||||
})
|
||||
else:
|
||||
return render(request, "login/password_reset_otp.html", {
|
||||
"phone": phone,
|
||||
"error": "Failed to resend OTP",
|
||||
"is_email": False
|
||||
})
|
||||
except ValidationError as e:
|
||||
return render(request, "login/password_reset_otp.html", {
|
||||
"phone": phone if 'phone' in locals() else "",
|
||||
"error": str(e),
|
||||
"is_email": False
|
||||
})
|
||||
|
||||
if not phone:
|
||||
return redirect("phone_reset_password")
|
||||
|
||||
# Generate new secret key and store it
|
||||
secret_key = generate_secret_key()
|
||||
store_secret_key(phone, secret_key)
|
||||
|
||||
# Generate and send new OTP
|
||||
otp = generate_otp(secret_key)
|
||||
send_result = send_otp_sms(phone, otp)
|
||||
|
||||
if send_result:
|
||||
return render(request, "login/password_reset_otp.html", {
|
||||
"phone": phone,
|
||||
"message": "OTP resent successfully",
|
||||
"is_email": False
|
||||
})
|
||||
else:
|
||||
return render(request, "login/password_reset_otp.html", {
|
||||
"phone": phone,
|
||||
"error": "Failed to resend OTP",
|
||||
"is_email": False
|
||||
})
|
||||
|
||||
return redirect("phone_reset_password")
|
||||
return redirect("phone_reset_password")
|
||||
return redirect('dashboard')
|
||||
|
||||
@csrf_protect
|
||||
def password_reset_email(request):
|
||||
"""View for initiating password reset with email"""
|
||||
# Get institute for logo
|
||||
institute = Institute.objects.first()
|
||||
|
||||
if request.method == "POST":
|
||||
email = request.POST.get("email")
|
||||
if not request.user.is_authenticated:
|
||||
institute = Institute.objects.first()
|
||||
|
||||
# Validate email
|
||||
if not email:
|
||||
return render(request, "login/email_reset.html", {
|
||||
"error": "Please enter a valid email address",
|
||||
"is_email": True,
|
||||
"institute": institute
|
||||
})
|
||||
if request.method == "POST":
|
||||
try:
|
||||
email = request.POST.get("email", "").strip()
|
||||
validate_email(email)
|
||||
|
||||
student = Student.objects.filter(email=email).first()
|
||||
staff = Staff.objects.filter(email=email).first()
|
||||
|
||||
if not student and not staff:
|
||||
return render(request, "login/email_reset.html", {
|
||||
"error": "No account found with this email address",
|
||||
"is_email": True,
|
||||
"institute": institute
|
||||
})
|
||||
|
||||
user = student or staff
|
||||
|
||||
if send_password_reset_email(user, request, email):
|
||||
return render(request, "login/password_reset_otp.html", {
|
||||
"email": email,
|
||||
"is_email": True,
|
||||
"institute": institute
|
||||
})
|
||||
else:
|
||||
return render(request, "login/email_reset.html", {
|
||||
"error": "Failed to send verification code. Please try again.",
|
||||
"is_email": True,
|
||||
"institute": institute
|
||||
})
|
||||
except ValidationError as e:
|
||||
return render(request, "login/email_reset.html", {
|
||||
"error": str(e),
|
||||
"is_email": True,
|
||||
"institute": institute
|
||||
})
|
||||
|
||||
# Check if a user with this email exists
|
||||
student = Student.objects.filter(email=email).first()
|
||||
staff = Staff.objects.filter(email=email).first()
|
||||
|
||||
if not student and not staff:
|
||||
return render(request, "login/email_reset.html", {
|
||||
"error": "No account found with this email address",
|
||||
"is_email": True,
|
||||
"institute": institute
|
||||
})
|
||||
|
||||
# Get the user
|
||||
user = student or staff
|
||||
|
||||
# Send password reset email
|
||||
if send_password_reset_email(user, request, email):
|
||||
# Redirect to verification page
|
||||
return render(request, "login/password_reset_otp.html", {
|
||||
"email": email,
|
||||
"is_email": True,
|
||||
"institute": institute
|
||||
})
|
||||
else:
|
||||
return render(request, "login/email_reset.html", {
|
||||
"error": "Failed to send verification code. Please try again.",
|
||||
"is_email": True,
|
||||
"institute": institute
|
||||
})
|
||||
|
||||
return render(request, "login/email_reset.html", {
|
||||
"is_email": True,
|
||||
"institute": institute
|
||||
})
|
||||
return render(request, "login/email_reset.html", {
|
||||
"is_email": True,
|
||||
"institute": institute
|
||||
})
|
||||
return redirect('dashboard')
|
||||
|
||||
@csrf_protect
|
||||
def password_reset_email_verify(request):
|
||||
"""View for verifying reset code for email"""
|
||||
# Get institute for logo
|
||||
institute = Institute.objects.first()
|
||||
|
||||
if request.method == "POST":
|
||||
email = request.POST.get("email")
|
||||
code = request.POST.get("otp")
|
||||
if not request.user.is_authenticated:
|
||||
institute = Institute.objects.first()
|
||||
|
||||
if not email or not code:
|
||||
return render(request, "login/password_reset_otp.html", {
|
||||
"error": "Email and verification code are required",
|
||||
"email": email,
|
||||
"is_email": True,
|
||||
"institute": institute
|
||||
})
|
||||
if request.method == "POST":
|
||||
try:
|
||||
email = request.POST.get("email", "").strip()
|
||||
code = request.POST.get("otp", "").strip()
|
||||
|
||||
validate_email(email)
|
||||
if not code or not code.isalnum():
|
||||
raise ValidationError("Invalid verification code format")
|
||||
|
||||
if verify_reset_code(email, code):
|
||||
token = str(uuid.uuid4())
|
||||
store_reset_token(token, email)
|
||||
|
||||
return render(request, "login/password_reset_set.html", {
|
||||
"email": email,
|
||||
"token": token,
|
||||
"is_email": True,
|
||||
"institute": institute
|
||||
})
|
||||
else:
|
||||
return render(request, "login/password_reset_otp.html", {
|
||||
"error": "Invalid or expired code",
|
||||
"email": email,
|
||||
"is_email": True,
|
||||
"message": "Please check your email for the verification code.",
|
||||
"institute": institute
|
||||
})
|
||||
except ValidationError as e:
|
||||
return render(request, "login/password_reset_otp.html", {
|
||||
"error": str(e),
|
||||
"email": email if 'email' in locals() else "",
|
||||
"is_email": True,
|
||||
"institute": institute
|
||||
})
|
||||
|
||||
# Verify code
|
||||
if verify_reset_code(email, code):
|
||||
# Generate a secure token for this reset
|
||||
token = str(uuid.uuid4())
|
||||
# Store token in database
|
||||
store_reset_token(token, email)
|
||||
|
||||
# Redirect to set new password
|
||||
return render(request, "login/password_reset_set.html", {
|
||||
"email": email,
|
||||
"token": token,
|
||||
"is_email": True,
|
||||
"institute": institute
|
||||
})
|
||||
else:
|
||||
return render(request, "login/password_reset_otp.html", {
|
||||
"error": "Invalid or expired code",
|
||||
"email": email,
|
||||
"is_email": True,
|
||||
"message": "Please check your email for the verification code.",
|
||||
"institute": institute
|
||||
})
|
||||
|
||||
# For GET request, show the verification form with email pre-filled
|
||||
email = request.GET.get("email")
|
||||
return render(request, "login/password_reset_otp.html", {
|
||||
"email": email,
|
||||
"is_email": True,
|
||||
"message": "Please check your email for the verification code.",
|
||||
"institute": institute
|
||||
})
|
||||
return redirect("email_reset_password")
|
||||
return redirect('dashboard')
|
||||
|
||||
def resend_email_code(request):
|
||||
"""View for resending verification code for password reset"""
|
||||
|
||||
Reference in new issue
Block a user