Authentication with passwords#

from functools import wraps
from passlib.context import CryptContext
from fasthtml.common import *
from libs.utils import isa_dev_computer, send_email, feedback_to_user

<<register>>
<<admin_required>>

Login form#

The actual form element is extracted into a MyForm() function. Its not really needed this time, since we don't use it a second time!

def MyForm(btn_text, target):
    return Form(
        Input(id="email", type="email", placeholder="Email", required=True),
        Input(id="password", type="password", placeholder="Password", required=True),
        Button(btn_text, type="submit"),
        Span(id="error", style="color:red"),
        hx_post=target,
        hx_target="#error",
    )

"""
@rt('/register')
def get():
    return authpass.register_get()
"""
def register_get():
    return Container(
        Article(
            H1("Register"),
            MyForm("Register", "/registercheck"),
            Hr(),
            P("Already have an account? ", A("Login", href="/login")),
            cls="mw-480 mx-auto"
        )
    )

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def get_password_hash(password):
    return pwd_context.hash(password)

"""
@rt('/registercheck')
def post(email:str, password:str):
    return authpass.register_post(email, password)
"""
def register_post(email, password, users):
    User = users.dataclass()
    try:
        users[email]
        return "User already exists"
    except NotFoundError:
        new_user = User(email=email, password=get_password_hash(password))
        users.insert(new_user)
        return HttpHeader('HX-Redirect', '/login')

"""
@rt('/login')
def get():
    return authpass.login_get()
"""
def login_get():
    return Container(
        Article(
            H1("Login"),
            MyForm("Login", target="/logincheck"),
            Hr(),
            # P("Want to create an Account? ", A("Register", href="/register")),
            cls="mw-480 mx-auto"
        )
    )

def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)

"""
@rt('/logincheck')
def post():
    return authpass.logincheck()
"""
def logincheck(session, email, password, users):
    try:
        user = users[email]
    except NotFoundError:
        return "Email or password are incorrect"
    if not verify_password(password, user.password):
        return "Email or password are incorrect"
    session['auth'] = user.email
    session['role'] = user.role_name
    return HttpHeader('HX-Redirect', '/dashboard')
def admin_required(handler):
    @wraps(handler)
    def wrapper(session, *args, **kwargs):
        role = session['role']
        if not role or not role == "admin":
            # Redirect to unauthorized page if not admin
            return RedirectResponse('/no_access_right')
        # Proceed if user is admin
        return handler(session, *args, **kwargs)
    return wrapper