feat: add tests for import polling, login page, money utilities, and query keys
- Implemented tests for import polling functionality, ensuring correct behavior for active and terminal import statuses. - Created tests for the login page, validating user interactions and error handling. - Added unit tests for money utility functions, covering parsing, formatting, and summation of monetary values. - Developed tests for query keys, ensuring proper scoping and invalidation roots. chore: setup testing environment and configuration - Added setup file for testing with Vitest and Testing Library. - Configured TypeScript and Vite for the frontend project. - Created Docker and Nginx configurations for local development and production environments. - Established a PostgreSQL initialization script for setting up application roles. docs: add README and environment configuration for infra - Included README for the infra directory, detailing local development setup and service descriptions. - Created an example environment configuration file for local development with placeholders for sensitive information.
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Cookie, Depends, Header
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.config import get_settings
|
||||
from app.db.session import get_db, set_space_context
|
||||
from app.errors import forbidden, unauthorized
|
||||
from app.identity.models import Session as UserSession
|
||||
from app.identity.models import User
|
||||
from app.identity.service import get_active_session
|
||||
from app.security import constant_time_equals
|
||||
from app.spaces.models import FinancialSpace, Membership
|
||||
from app.spaces.service import list_spaces_for_user
|
||||
|
||||
Db = Annotated[Session, Depends(get_db)]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RequestContext:
|
||||
user: User
|
||||
session: UserSession
|
||||
space: FinancialSpace
|
||||
membership: Membership
|
||||
|
||||
|
||||
def current_session(
|
||||
db: Db,
|
||||
session_token: Annotated[str | None, Cookie(alias=get_settings().session_cookie_name)] = None,
|
||||
) -> tuple[User, UserSession]:
|
||||
if not session_token:
|
||||
raise unauthorized("IDENTITY_SESSION_MISSING", "Authentication required")
|
||||
session = get_active_session(db, token=session_token)
|
||||
user = db.get(User, session.user_id)
|
||||
if user is None or user.status != "active":
|
||||
raise unauthorized("IDENTITY_USER_INACTIVE", "Authentication required")
|
||||
return user, session
|
||||
|
||||
|
||||
CurrentSession = Annotated[tuple[User, UserSession], Depends(current_session)]
|
||||
|
||||
|
||||
def current_context(db: Db, auth: CurrentSession) -> RequestContext:
|
||||
user, session = auth
|
||||
spaces = list_spaces_for_user(db, user=user)
|
||||
if not spaces:
|
||||
raise forbidden("SPACE_MEMBERSHIP_REQUIRED", "No financial space is available")
|
||||
space, membership = spaces[0]
|
||||
if space.status != "active":
|
||||
raise forbidden("SPACE_BLOCKED", "Financial space is unavailable")
|
||||
set_space_context(db, space.id)
|
||||
return RequestContext(user=user, session=session, space=space, membership=membership)
|
||||
|
||||
|
||||
Context = Annotated[RequestContext, Depends(current_context)]
|
||||
|
||||
|
||||
def require_csrf(
|
||||
context: Context,
|
||||
csrf_header: Annotated[str | None, Header(alias="X-CSRF-Token")] = None,
|
||||
) -> None:
|
||||
if not csrf_header or not constant_time_equals(context.session.csrf_token, csrf_header):
|
||||
raise forbidden("IDENTITY_CSRF_INVALID", "CSRF token is missing or invalid")
|
||||
|
||||
|
||||
Csrf = Annotated[None, Depends(require_csrf)]
|
||||
Reference in New Issue
Block a user