Architecture¶
Django apps¶
Comaney is split into three Django apps and a project package.
comaney/ (project package)¶
Settings, root URL configuration, and middleware. Also contains a dynamic markdown renderer that serves optional public pages (imprint, privacy policy, etc.) from environment-variable-supplied markdown content.
feusers/¶
Everything related to users and authentication. This app defines the FeUser model, which is Comaney's custom user type. It does not use django.contrib.auth.User.
Responsibilities: registration, login, logout, email confirmation, password reset, multi-method two-factor auth (TOTP and FIDO2/WebAuthn security keys), API key management, account settings, and account deletion.
Session authentication works by storing feuser_id in the Django session after a successful login. The @feuser_required decorator (in budget/decorators.py) checks this on each request and sets request.feuser.
budget/¶
The core budgeting functionality. Contains all models (Expense, ScheduledExpense, Category, Tag, DashboardCard), all budgeting views, and the business logic for:
- Expense CRUD and bulk actions
- Scheduled expense template management and cron-driven generation
- Category and tag management
- The modular dashboard (YAML card parsing and data computation)
- AI express creation
- Email notifications
- End-of-month allowance handling logic
- The query language parser (translates the search bar mini-language into Django Q objects)
api/¶
A thin REST API layer over the budget models. Authenticated via Bearer token rather than session cookies. Contains serializers for converting model instances to JSON, and an @_require_auth decorator that validates the token and injects the FeUser into the view.
No business logic lives here that isn't already in the budget layer.
URL routing¶
/ → feusers app (login, register, public pages)
/budget/ → budget app (expenses, dashboard, scheduled, categories)
/api/v1/ → api app (Bearer token REST API)
/admin/ → Django admin
/docs/ → Docs site served from docs/build/site/
Middleware¶
Requests pass through the middleware stack in this order:
- SystemMisconfiguredMiddleware: injects a warning banner if the app is misconfigured (e.g., SMTP not set up).
- SecurityMiddleware: Django's standard security headers.
- WhiteNoiseMiddleware: serves compressed static files directly from Gunicorn.
- SessionMiddleware: cookie-based session support.
- LastSeenMiddleware: updates
feuser.last_seenat most once every 5 minutes for authenticated requests. - Standard Django middleware (CSRF, messages, X-Frame-Options, etc.).
Financial periods¶
All budget data is scoped to a financial period. The period is determined by the user's month_start_day and month_start_prev settings. budget/date_utils.py provides financial_month_range() and financial_year_range() to compute the date boundaries for any given month or year.
The dashboard, expense list, and API all accept ?year= and ?month= parameters and use these helpers to scope queries.
Dashboard card system¶
Cards are stored as DashboardCard model instances containing only a raw YAML string and a creation timestamp. All layout and configuration information lives inside the YAML.
When the dashboard loads, the frontend fetches all cards via the session-authenticated card API. For each card, the server parses the YAML, applies the card's query filter against the current period, and computes either a scalar value (cell cards) or grouped chart data (bar/pie charts). The result is returned as JSON.
Static files¶
SCSS and JS source files in build/ are compiled to static/dist/ by the build script. WhiteNoise serves them with a content-hash in the URL (via CompressedManifestStaticFilesStorage), enabling aggressive browser caching.
Authentication flows¶
Session auth (web UI): On login, the server looks up the FeUser by email, verifies the submitted password with check_password(), and only then stores the user's database PK in the session as feuser_id. If the account has any second factor (FeUser.has_2fa_enabled), a twofa_pending_id is stored instead and the user is redirected to twofa_verify; the full session (feuser_id) is only established after a factor is validated.
Second-factor architecture (feusers/second_factor_registry.py, feusers/second_factor_service.py): TOTP and FIDO2/WebAuthn are both specializations of an abstract SecondFactorAuth model (feusers/models/second_factor.py); concrete subclasses are TOTPFactor and WebAuthnFactor. Each method registers itself once via register_factor_type(FactorType(...)), which records its model, display name, setup URL, and login-challenge template. feusers/views/twofa.py (login verification, method switching, removal, primary selection, recovery-code regeneration) and feusers/views/account.py (the profile list) drive entirely off this registry via get_all_factors()/method_key_of() and never branch on a method name directly, so adding a further method needs only a new model, a register_factor_type() call, a setup view, and one entry in twofa.py's _LOGIN_VERIFIERS dispatch table.
Exactly one factor (of any type) is is_primary=True per user at a time, enforced by second_factor_service.set_primary(). A single global FeUser.twofa_recovery_hash (not per-method) protects the whole account: it is generated once on a user's first-ever factor, and consuming it via second_factor_service.consume_recovery_code() deletes every factor of every method, since a user who needed the recovery code has just proven they can reach none of them.
Bearer token auth (REST API): The Authorization: Bearer <key> header is read on every API request. The key is looked up against FeUser.api_key in the database.
Registration protection: The registration form uses a client-side proof-of-work challenge to deter bot signups. The browser must compute a hash nonce before the form can be submitted.