Conversation
Applies the project's existing Prettier defaults consistently across config files, source, tests, and workflow YAML; bumps app version to 3.1.0 ahead of the dependency/security/auth work landing on this branch.
Bumps react-router, vite, vitest, @vitejs/plugin-react, @vitest/coverage-v8, and postcss to the versions proposed by the open Dependabot PRs (#5, #9, #10, #11, #12), then runs npm audit fix to clear the remaining transitive vulnerabilities (ajv, brace-expansion, flatted, js-yaml, minimatch, ws, @babel/core) down to 0 findings. Also fixes two issues the Vite 8 / Vitest 4.1 upgrade exposed: - vitest.config.ts was missing the __APP_VERSION__ define that vite.config.ts already had, crashing every test that renders Sidebar - the Sidebar nav list and its test had drifted: Settings was never linked in the sidebar despite the /settings route existing, so the page was only reachable by typing the URL directly
actions/checkout v4->v7, actions/setup-python v5->v6, actions/setup-node v4->v6, docker/setup-buildx-action v3->v4, docker/login-action v3->v4, docker/metadata-action v5->v6, docker/build-push-action v6->v7
Adds a login gate so the dashboard and its API can no longer be used without authenticating: - One-time setup wizard creates the single admin account (username + bcrypt-hashed password); POST /auth/setup is rejected once a user already exists - Server-side sessions stored in a new `sessions` table (survive a container restart), identified by a random token whose SHA-256 hash is persisted — the raw token only ever lives in an httpOnly cookie - Login lockout: 5 failed attempts locks the account for 15 minutes - Every existing API router now requires a valid session (CurrentUserDep), and the /ws/events WebSocket checks the same cookie during the handshake and closes the connection if missing or invalid — auth is enforced at the API/WebSocket layer, not just in the frontend - SESSION_COOKIE_SECURE env var (default true) lets LAN/no-TLS deployments disable the cookie's Secure flag Also fixes two issues found while wiring this up: - get_db() rolled back the login-lockout counter every time a login attempt failed, because it treats any exception raised inside a request as a reason to discard all writes; authenticate() now commits the failed-attempt/lockout state before raising so it survives the expected 401 - greenlet was missing from dependencies — SQLAlchemy's async engine needs it, but its own install_requires markers don't cover macOS arm64, so importing AsyncEngine failed on Apple Silicon dev machines
Adds AuthProvider (mirrors the existing WebSocketProvider pattern) that wraps the app: while /auth/status and /auth/me resolve it shows a spinner, then renders either the Login page (setup wizard or login form, depending on whether an admin account exists yet) or the routed app. The provider sits above WebSocketProvider so the WebSocket only connects once a session exists. Also: - api.ts now sends credentials: "include" so the session cookie is attached to every request - Settings page gets an Account card with the logged-in username, change-password form, and logout button
The container HEALTHCHECK and both docker-compose healthchecks hit /api/v1/system/health, which is now behind login — every check failed with 401 and the container reported unhealthy forever. Added an nginx location for /health that proxies straight to the backend's unauthenticated /health endpoint (outside /api/, so it isn't gated), and pointed all three healthchecks at it. Verified the container reports "healthy" after this change.
backend/src/db/migrations/ was an orphaned leftover from before the project moved to backend/src/alembic/ (the one alembic.ini actually points at via script_location). Neither directory has an __init__.py, and nothing in backend/src, Docker, or CI imports "db.migrations" — confirmed while chasing a revision-mismatch bug earlier on this branch. Also drops the stale "db.migrations" entry from pyproject.toml's packages list.
backend/src/db/seeds/ contained only an empty __init__.py, never imported or referenced by any script, CLI command, or docs. Also removes NotificationService.get_logs_count(), which had zero callers outside its own docstring example.
None of useGames/useSystem (already removed in the previous commit), useEntry, useHistory, useGiveaway, useRefreshGiveawayGame, useEntryTrends, or useWebSocketAnyEvent were imported by any page or component - only re-exported through the hooks barrel and, for a few, their own test file. Also drops the types that existed solely to support them (SystemInfo, HealthCheck, TrendDataPoint, GameFilters) and the date-fns dependency, which nothing in src/ imports.
README: adds a "Login Protection" feature bullet and a "First run: account setup" section explaining the setup wizard and the SESSION_COOKIE_SECURE env var for non-TLS deployments. CHANGELOG: adds a Removed section for the v3.1.0 entry covering the dead-code cleanup (orphaned migrations directory, unused seeds package, unused frontend hooks/types, unused date-fns dependency).
Three low-risk trims, no architecture changes (kept nginx/supervisor and the python:3.13-slim base as-is): - Add a .dockerignore. There wasn't one at all, so COPY frontend/ ./ in the frontend-build stage would copy the host's frontend/node_modules straight into the image if it happened to exist locally (it does, from earlier work this session) - silently overwriting the npm-ci-installed one and bloating the build context on every build. Also excludes .venv, caches, docs, and other files the image never needs. - Drop curl from the final image (58.2MB apt layer, down from 69.8MB) and replace the curl-based HEALTHCHECK with a Python one-liner using the venv's Python that's already in the image. Updated both docker-compose files' healthcheck to match, since they override the image's built-in HEALTHCHECK. - Drop uvicorn's [standard] extra (72.6MB venv layer, down from 96MB) - it pulls in uvloop/httptools/watchfiles/pyyaml, none of which are used since the container runs uvicorn without --reload. Plain uvicorn is a reasonable trade for a low-traffic single-user tool. python-dotenv (also part of [standard]) stays available regardless, since pydantic-settings depends on it directly. Verified end-to-end: fresh setup wizard -> login -> dashboard through nginx, `docker compose ps` reports "healthy", full backend/frontend test suites still green.
Root cause: the backend has two different response shapes. Normal
responses are { success, data, meta }, but responses from the global
exception handlers in api/middleware.py (e.g. an expired SteamGifts
session, which is exactly what triggered this via Sync Wins) are
{ error: { message, code, details } } with no success/data keys at
all. Every hook does `throw new Error(response.error || fallback)`,
so whenever the second shape came back, response.error was an object
rather than a string, and `new Error(object)` stringifies to
"[object Object]" - shown as-is in the error toast.
Fixed at the single choke point (api.ts's request()) instead of
patching every hook individually: detect the exception-handler shape
and normalize it to { success: false, data: null, error: "<message>" }
before it reaches any hook. This fixes every action that can hit a
custom backend exception (session expired, rate limits, insufficient
points, scheduler errors, etc.), not just Sync Wins.
Reproduced against a running backend first (bogus PHPSESSID -> real
SG_004 "session expired" response) to confirm the exact payload shape
before writing the fix.
AppException.__init__() requires `code` with no default, but AccountService.get_account() and .set_default() constructed ResourceNotFoundError with only a message. The TypeError from that malformed constructor call happened before the intended exception object even existed, so it never reached resource_not_found_handler (which would have returned a clean 404) - it fell through to unhandled_exception_handler instead, producing a 500 and a traceback repeating in the logs on every request for a stale/removed account. Confirmed via grep this is the only missing-code call site in the codebase: steamgifts_client.py, auth_service.py, and dependencies.py all pass code correctly, and the SteamAPIError(...) calls in utils/steam_client.py use an unrelated local exception class with the same name, not core.exceptions.SteamAPIError. Adds ACCT_001 to ERROR_CODES and a regression test file for AccountService (which previously had none) that fails with the exact TypeError from the logs when the fix is reverted.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Full details: see CHANGELOG.md
Test plan
docker compose psreports "healthy"