# UGE / L2 / Intro to relational databases / Python project prototype # Author: Pacien TRAN-GIRARD # Licence: EUPL-1.2 from fastapi import APIRouter, Depends, Request, Form, status from passlib.context import CryptContext import re from embrace.exceptions import IntegrityError from psycopg2.errors import UniqueViolation from app_sessions import UserSession from app_database import db_transaction # Password hashing context. # Handles proper salting and migration automatically. password_ctx = CryptContext(schemes=['bcrypt'], deprecated='auto') username_pattern = re.compile(r'^[a-zA-Z0-9-_]{4,16}$') router = APIRouter() @router.get('/') def homepage( session: UserSession=Depends(UserSession), ): if session.is_logged_in(): return 'Welcome!' return 'Homepage here.' @router.post('/account/register') def account_register( session: UserSession=Depends(UserSession), username: str=Form(...), password: str=Form(...), ): try: if username_pattern.match(username) is None: return 'error: Invalid username format.' if not 4 <= len(password) <= 32: return 'error: Invalid password length.' hash = password_ctx.hash(password) with db_transaction() as tx: user = tx.create_account(username=username, password_hash=hash) session.login(user.id) return 'Account succesfully created. Welcome!' except IntegrityError as exception: if isinstance(exception.__cause__, UniqueViolation): return 'error: This username is already taken.' else: raise exception @router.post('/account/login') def session_login( session: UserSession=Depends(UserSession), username: str=Form(...), password: str=Form(...), ): with db_transaction() as tx: user = tx.fetch_account_username(username=username) if user is not None and password_ctx.verify(password, user.password_hash): session.login(user.id) return 'Welcome back!' else: return 'error: Invalid credentials.' @router.post('/account/logout') def session_logout( session: UserSession=Depends(UserSession), ): if session.is_logged_in(): session.logout() return 'You have been successfully logged out.' return 'Nothing to do'