from fastapi import (APIRouter, Request, status, Depends, Form ) from fastapi.responses import HTMLResponse, RedirectResponse from fastapi.templating import Jinja2Templates from ..db.db_connect import get_db, verify_password from sqlalchemy.orm import Session from ..models.users import User, UserRole import os, hashlib, binascii router = APIRouter() templates = Jinja2Templates(directory="app/frontend/templates") #router to the login page @router.get("/login", response_class=HTMLResponse) def login_page(request: Request): return templates.TemplateResponse( "validation/login.html", {"request": request, "active": "login"} ) # route for submitting login form @router.post("/login", response_class=HTMLResponse) def handle_login( request: Request, email: str = Form(...), password: str = Form(...), db: Session = Depends(get_db), ): # query user based on email input user = db.query(User).filter(User.email == email).first() # if the user doesn't exist or the password isn't verified or the account is not active return error message if not user or not verify_password(password, user.hashed_password) or not user.is_active: return templates.TemplateResponse( "validation/login.html", {"request": request, "active": "login", "error": "Invalid email or password."}, status_code=status.HTTP_400_BAD_REQUEST, ) # assigning the get attribute function for the specific role attribute to a variable role_obj = getattr(user, "role", None) # assigning the rol_obj function to query the value of the attribute role_value = getattr(role_obj, "value", None) if role_obj is not None else None if role_value is None and role_obj is not None: role_value = str(role_obj) # start user session request.session["user"] = { "id": user.id, "email": user.email, "name": (user.full_name or user.email), "role": role_value or "end_user", "first_name": user.first_name } role_lower = (role_value or "end_user").lower() #redirect to dashboard return RedirectResponse(url=f"/dashboard/{role_lower}", status_code=status.HTTP_303_SEE_OTHER) #logic for ending user session on logout @router.get("/logout") def logout(request: Request): request.session.clear() # redirect to homepage return RedirectResponse(url="/") # password encryption function applied to password registration # plain variable refers to a plain unencrypted password def _hash_password(plain: str, iterations: int = 260000) -> str: # generate 16 bytes of random data salt = os.urandom(16) # derive a cryptographic key by applying HMAC-SHA256 hashing dk = hashlib.pbkdf2_hmac("sha256", plain.encode(), salt, iterations) # format the encryption result return f"pbkdf2_sha256${iterations}${binascii.hexlify(salt).decode()}${binascii.hexlify(dk).decode()}" # return registration html @router.get("/register", response_class=HTMLResponse) def register_page(request: Request): return templates.TemplateResponse( "validation/register.html", {"request": request, "active": "register"} ) # registration logic @router.post("/register", response_class=HTMLResponse) def handle_register( request: Request, first_name: str = Form(...), surname: str = Form(...), email: str = Form(...), password: str = Form(...), confirm_password: str = Form(...), db: Session = Depends(get_db), ): # format input data, if field is empty assign empty string first_name = (first_name or "").strip() surname = (surname or "").strip() email = (email or "").strip().lower() password = password or "" confirm_password = confirm_password or "" # if full name or email is misisng return bad request error if not first_name or not email: return templates.TemplateResponse( "validation/register.html", {"request": request, "active": "register", "error": "First name and email are required."}, status_code=status.HTTP_400_BAD_REQUEST, ) # password and confirmation password validation, return error if they dont match or are less than 8 characters if password != confirm_password or len(password) < 8: return templates.TemplateResponse( "validation/register.html", {"request": request, "active": "register", "error": "Passwords must match and be at least 8 characters."}, status_code=status.HTTP_400_BAD_REQUEST, ) # query user table by email existing = db.query(User).filter(User.email == email).first() # if the email exists reject registration if existing: return templates.TemplateResponse( "validation/register.html", {"request": request, "active": "register", "error": "An account with this email already exists."}, status_code=status.HTTP_400_BAD_REQUEST, ) # apply user model to inputted data, default role to end user, hash password user = User( email=email, first_name=first_name, surname=surname or None, hashed_password=_hash_password(password), role=UserRole.END_USER, is_active=True, ) # add user to database db.add(user) db.commit() db.refresh(user) # store data into the user session request.session["user"] = { "id": user.id, "email": user.email, "name": (user.full_name or user.email), "role": "end_user", } # redirect to end user dashboard return RedirectResponse(url="/dashboard/end_user", status_code=status.HTTP_303_SEE_OTHER)