Files
finance/backend/app/dependencies.py
T
k1nq d582f15681 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.
2026-09-14 09:26:33 +05:00

68 lines
2.3 KiB
Python

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)]