def _set_session_cookie(response: Response, token: str) -> None: response.set_cookie( settings.SESSION_COOKIE_NAME, token, httponly=True, samesite="lax", secure=(settings.ENVIRONMENT == "production"), max_age=settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60, path="/", ) response.set_cookie( settings.CSRF_COOKIE_NAME, secrets.token_urlsafe(24), httponly=False, samesite="lax", secure=(settings.ENVIRONMENT == "production"), max_age=settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60, path="/", ) @router.post("/register", status_code=201) async def register(payload: RegisterIn, request: Request, response: Response, db: AsyncSession = Depends(get_db)): existing = (await db.execute(select(User).where(User.email == payload.email.lower()))).scalar_one_or_none() if existing: raise HTTPException(409, "Email already registered") user = User(email=payload.email.lower(), password_hash=hash_password(payload.password), full_name=payload.full_name, is_admin=True) db.add(user) await db.commit() token = create_access_token(user.id) _set_session_cookie(response, token) await log_audit(db, user.id, "user.register", "user", user.id, {"email": user.email}, request.client.host) return {"id": user.id, "email": user.email, "token": token} @router.post("/login") async def login(payload: LoginIn, request: Request, response: Response, db: AsyncSession = Depends(get_db)): user = (await db.execute(select(User).where(User.email == payload.email.lower()))).scalar_one_or_none() if not user or not verify_password(payload.password, user.password_hash): raise HTTPException(401, "Invalid email or password") if not user.is_active: raise HTTPException(403, "Account disabled") user.last_login_at = datetime.now(timezone.utc) await db.commit() token = create_access_token(user.id) _set_session_cookie(response, token) return {"id": user.id, "email": user.email, "token": token} @router.post("/logout") async def logout(response: Response): response.delete_cookie(settings.SESSION_COOKIE_NAME) response.delete_cookie(settings.CSRF_COOKIE_NAME) return {"ok": True}