Skip to content

feat(perf+security): per-request backend cache, DB indexes, CSRF, pro… - #27

Merged
antosubash merged 2 commits into
mainfrom
feat/perf-improvements
Apr 16, 2026
Merged

antosubash merged 2 commits into
mainfrom
feat/perf-improvements

Conversation

@antosubash

Copy link
Copy Markdown
Owner

…d-secret checks

Perf — per-request backend waste

  • AuthMiddleware caches resolved UserContext in the signed session cookie; subsequent requests skip the SELECT User + selectinload(roles) round-trip. Trade-off: role / disable changes apply on the affected user's next login.
  • InertiaLayoutDataMiddleware omits i18n.messages on Inertia XHR visits unless the session-tracked last locale differs; full page loads still ship the dict. Frontend i18n.ts guards updateI18n on messages presence.
  • MenuRegistry memoizes the sorted items list; invalidated on add/add_many.
  • UsersModule.on_startup warms a RoleSummary cache on app.state; admin views read from the cache (with async lazy fallback) instead of re-querying users_role per render.
  • I18nRegistry.messages() returns a MappingProxyType view — no per-call dict copy; Translator._lookup uses it directly.

Perf — DB schema

  • New migration b7e1af4c9d02 adds:
    • ix_users_user_email_lower — functional index on lower(email) so fastapi- users get_by_email stops seq-scanning on Postgres
    • ix_users_access_token_user_id, ix_users_user_role_role_id — FK indexes (Postgres does not auto-index FKs)
    • ix_products_product_is_deleted — supports soft-delete filtering
    • drops ix_products_product_is_active — low-cardinality boolean costs writes without helping the planner
  • All create/drop wrapped in autocommit_block with postgresql_concurrently and if_not_exists / if_exists for re-runnable zero-downtime deploys.
  • ProductService.get_all now filters is_deleted=False — fixes a correctness bug where soft-deleted + still-active rows leaked into public listings.
  • DB engine gets pool_size / max_overflow / pool_pre_ping / pool_recycle knobs driven by new SM_DB_POOL_* settings (Postgres only; SQLite ignores).

Security — CSRF + hardening

  • New CSRFMiddleware validates an X-CSRF-Token header against a session- scoped token for every unsafe (POST/PUT/PATCH/DELETE) request. Token is minted once per session and exposed via Inertia shared props; frontend packages/ui/src/lib/csrf.ts stores it and fetchWithCsrf echoes it back.
  • SecurityHeadersMiddleware gains configurable CSP and HSTS (pass None to suppress in dev / non-TLS loopbacks).
  • Settings rejects the placeholder SM_SECRET_KEY in production environments (new framework/core/simple_module_core/environments.py defines the non-prod allowlist).
  • Dev-only seeded credentials on the login page are now gated on both is_development AND the bootstrap env vars being set.

Tooling

  • rollup-plugin-visualizer (dev dep) gated behind ANALYZE=1 npm run build; writes host/static/dist/stats.html.
  • pytest addopts gets --durations=20 so the slowest tests surface on every run without a flag.

Tests / support

  • framework/testing/simple_module_testing/session_cookie.py — shared helper for forging signed session cookies; used by integration + users tests.
  • New framework/hosting/tests/test_csrf_middleware.py and test_settings_secrets.py cover the security additions.
  • Existing suite (558 Python + 8 JS tests) updated where needed and all passing. ty + ruff + biome + tsc clean.

…d-secret checks

## Perf — per-request backend waste
- AuthMiddleware caches resolved UserContext in the signed session cookie;
  subsequent requests skip the SELECT User + selectinload(roles) round-trip.
  Trade-off: role / disable changes apply on the affected user's next login.
- InertiaLayoutDataMiddleware omits i18n.messages on Inertia XHR visits unless
  the session-tracked last locale differs; full page loads still ship the
  dict. Frontend i18n.ts guards updateI18n on messages presence.
- MenuRegistry memoizes the sorted items list; invalidated on add/add_many.
- UsersModule.on_startup warms a RoleSummary cache on app.state; admin views
  read from the cache (with async lazy fallback) instead of re-querying
  users_role per render.
- I18nRegistry.messages() returns a MappingProxyType view — no per-call dict
  copy; Translator._lookup uses it directly.

## Perf — DB schema
- New migration b7e1af4c9d02 adds:
  * ix_users_user_email_lower — functional index on lower(email) so fastapi-
    users get_by_email stops seq-scanning on Postgres
  * ix_users_access_token_user_id, ix_users_user_role_role_id — FK indexes
    (Postgres does not auto-index FKs)
  * ix_products_product_is_deleted — supports soft-delete filtering
  * drops ix_products_product_is_active — low-cardinality boolean costs
    writes without helping the planner
- All create/drop wrapped in autocommit_block with postgresql_concurrently
  and if_not_exists / if_exists for re-runnable zero-downtime deploys.
- ProductService.get_all now filters is_deleted=False — fixes a correctness
  bug where soft-deleted + still-active rows leaked into public listings.
- DB engine gets pool_size / max_overflow / pool_pre_ping / pool_recycle
  knobs driven by new SM_DB_POOL_* settings (Postgres only; SQLite ignores).

## Security — CSRF + hardening
- New CSRFMiddleware validates an X-CSRF-Token header against a session-
  scoped token for every unsafe (POST/PUT/PATCH/DELETE) request. Token is
  minted once per session and exposed via Inertia shared props; frontend
  packages/ui/src/lib/csrf.ts stores it and fetchWithCsrf echoes it back.
- SecurityHeadersMiddleware gains configurable CSP and HSTS (pass None to
  suppress in dev / non-TLS loopbacks).
- Settings rejects the placeholder SM_SECRET_KEY in production environments
  (new framework/core/simple_module_core/environments.py defines the
  non-prod allowlist).
- Dev-only seeded credentials on the login page are now gated on both
  is_development AND the bootstrap env vars being set.

## Tooling
- rollup-plugin-visualizer (dev dep) gated behind ANALYZE=1 npm run build;
  writes host/static/dist/stats.html.
- pytest addopts gets --durations=20 so the slowest tests surface on every
  run without a flag.

## Tests / support
- framework/testing/simple_module_testing/session_cookie.py — shared helper
  for forging signed session cookies; used by integration + users tests.
- New framework/hosting/tests/test_csrf_middleware.py and
  test_settings_secrets.py cover the security additions.
- Existing suite (558 Python + 8 JS tests) updated where needed and all
  passing. ty + ruff + biome + tsc clean.
- framework/hosting/simple_module_hosting/_observability.py — extracted
  CorrelationIdMiddleware + RequestLoggingMiddleware. middleware.py keeps
  security / tenant / Inertia-layout concerns; re-exports the observability
  middlewares for back-compat.
- modules/users/users/endpoints/api_admin.py — split the 6 admin REST
  endpoints into their own router with the RequiresPermission("users.manage")
  dep applied once at the APIRouter level instead of per-route. api.py
  mounts it via include_router.

No behavior change. ci-check-file-size now passes.
@antosubash
antosubash merged commit ff53539 into main Apr 16, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant