# Budget A multi-user budget web app built around the 50/30/20 framework. Users manage Income and Outgo entries, view a Summary breakdown by type (Need / Want / Save), and can share budgets with other users in edit or view-only mode. **Stack:** ASP.NET Core 10 + Vite/React (TypeScript) — served as a single Docker container. PostgreSQL via EF Core. OIDC authentication against `auth.stwaddle.com`. --- ## Environment Variables ### Backend (ASP.NET Core) | Variable | Required | Description | |----------|----------|-------------| | `ConnectionStrings__DefaultConnection` | Yes | Full Npgsql connection string | | `AUTH__AUTHORITY` | Yes | OIDC authority base URL (trailing slash required) | | `AUTH__AUDIENCE` | Yes | Expected `aud` claim in access tokens — must match the scope's Resource value in the auth server | | `ASPNETCORE_ENVIRONMENT` | No | `Production` or `Development` (default: `Production` in Docker) | If `ConnectionStrings__DefaultConnection` is absent, the app falls back to assembling a connection string from individual variables: | Variable | Default | Description | |----------|---------|-------------| | `POSTGRES_HOST` | `localhost` | Database host | | `POSTGRES_PORT` | `5432` | Database port | | `POSTGRES_DB` | `budget` | Database name | | `POSTGRES_USER` | `budget` | Database user | | `POSTGRES_PASSWORD` | `changeme` | Database password | ### Frontend (Vite — baked in at build time) These are compiled into the static assets during the Docker build and cannot be changed at runtime. | Variable | Description | |----------|-------------| | `VITE_AUTH_AUTHORITY` | OIDC authority base URL — must match `AUTH__AUTHORITY` | | `VITE_AUTH_CLIENT_ID` | OIDC client ID registered in the auth server | | `VITE_AUTH_REDIRECT_URI` | Full URL of the `/callback` route (e.g. `https://budget.stwaddle.com/callback`) | To change frontend auth config after the image is built, rebuild the image with updated build args. See the Dockerfile. --- ## Production Deployment The service is defined in `docker/docker-compose.yaml` and managed alongside the rest of the stwaddle.com infrastructure. ### docker-compose service ```yaml budget: image: docker.stwaddle.com/budget:latest environment: - VIRTUAL_HOST=budget.stwaddle.com - LETSENCRYPT_HOST=budget.stwaddle.com - VIRTUAL_PORT=8080 - ASPNETCORE_ENVIRONMENT=Production - ConnectionStrings__DefaultConnection=Host=apps-db;Port=5432;Database=budget;Username=postgres;Password=${APPS_DB_PASSWORD} - AUTH__AUTHORITY=https://auth.stwaddle.com/ - AUTH__AUDIENCE=budget-api depends_on: - apps-db - auth networks: - web - apps-internal restart: unless-stopped ``` ### .env entries Add to the `.env` file on the server (alongside the existing entries): ```env BUDGET_VIRTUAL_HOST=budget.stwaddle.com BUDGET_LETSENCRYPT_HOST=budget.stwaddle.com ``` `APPS_DB_PASSWORD` is already set in `.env` for the recipes app and is shared. The budget app gets its own `budget` database within the same Postgres instance — EF Core's `MigrateAsync()` creates it automatically on first startup. ### Build and push ```powershell .\build-budget-image.ps1 ``` This builds the multi-stage Docker image (Node → .NET SDK → ASP.NET runtime) and pushes it to `docker.stwaddle.com/budget:latest`. The `VITE_*` build args default to the production values in the Dockerfile. To override: ```powershell docker build ` --build-arg VITE_AUTH_AUTHORITY=https://auth.stwaddle.com/ ` --build-arg VITE_AUTH_CLIENT_ID=budget-client ` --build-arg VITE_AUTH_REDIRECT_URI=https://budget.stwaddle.com/callback ` -t docker.stwaddle.com/budget:latest . docker push docker.stwaddle.com/budget:latest ``` ### DNS Add an A record for `budget.stwaddle.com` pointing to the Linode server IP before deploying. The acme-companion will issue the Let's Encrypt certificate automatically. If DNS hasn't propagated when the container first starts: ```bash docker compose restart acme-companion ``` --- ## Auth Server Setup The budget app requires two registrations in the auth server admin panel at `https://auth.stwaddle.com/Admin/` (requires an account with the Admin role). ### 1. Create the API Scope > **Admin → Scopes → Create** | Field | Value | |-------|-------| | Name | `budget_api` | | Display Name | `Budget API` | | Resources | `budget-api` | The **Resources** field is what gets written into the `aud` claim of access tokens. It must exactly match `AUTH__AUDIENCE=budget-api` set on the API container. ### 2. Register the Client Application > **Admin → Sites → Create** | Field | Value | |-------|-------| | Client ID | `budget-client` | | Display Name | `Budget` | | Redirect URIs | `https://budget.stwaddle.com/callback` | | Post-Logout Redirect URIs | `https://budget.stwaddle.com` | | API Scopes | ✅ `budget_api` | The client is always created as **Public** (no secret) with **Authorization Code + PKCE + Refresh Token** — this is enforced by the auth server for all SPA clients. For local development, also add the dev URIs (you can edit the client after creation): - Redirect URI: `http://localhost:5173/callback` - Post-Logout URI: `http://localhost:5173` ### 3. Grant Users Access Users must be explicitly granted a role on the client before they can log in. > **Admin → Sites → [Budget] → Roles → Grant** Assign any user who needs access at least the `user` role. Without a role entry the auth server returns `access_denied` at the authorize endpoint. ### Token details | Property | Value | |----------|-------| | Flow | Authorization Code + PKCE | | Access token lifetime | 15 minutes | | Refresh token lifetime | 14 days | | Claims in access token | `sub`, `email`, `name`, `role`, `aud` (`budget-api`) | The frontend uses `oidc-client-ts` with automatic silent renewal, so users stay logged in across the 15-minute access token window without re-authenticating. --- ## Local Development ### Prerequisites - .NET 10 SDK - Node 22 - PostgreSQL (or Docker) ### Setup 1. Copy `.env.example` to `.env` and fill in values for your local Postgres instance. 2. Start the API: ```bash cd src/Budget.Api dotnet run ``` The API listens on `http://localhost:5000` by default. EF migrations run automatically on startup. 3. Start the frontend dev server: ```bash cd src/Budget.Client npm install npm run dev ``` Vite proxies `/api` requests to `http://localhost:5000`. The app is available at `http://localhost:5173`. 4. Set the following in `src/Budget.Client/.env.local` (Vite picks this up automatically, it is gitignored): ```env VITE_AUTH_AUTHORITY=https://auth.stwaddle.com/ VITE_AUTH_CLIENT_ID=budget-client VITE_AUTH_REDIRECT_URI=http://localhost:5173/callback ``` 5. Set the following in `src/Budget.Api/appsettings.Development.json`: ```json { "AUTH__AUTHORITY": "https://auth.stwaddle.com/", "AUTH__AUDIENCE": "budget-api" } ``` The auth server's dev redirect URI (`http://localhost:5173/callback`) must be registered on the client as described in the Auth Server Setup section above.