Add README and fix Dockerfile VITE build args

README covers all env vars, docker-compose/env examples, and full
auth server setup (scope, client registration, user roles).

Dockerfile now accepts VITE_AUTH_* build args with production defaults
so the values are baked into the client bundle correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Spencer Twaddle
2026-04-25 08:51:55 -05:00
parent 9941ecc1a9
commit a8cf6957b5
2 changed files with 222 additions and 0 deletions
+9
View File
@@ -4,6 +4,15 @@ WORKDIR /app/client
COPY src/Budget.Client/package*.json ./
RUN npm ci
COPY src/Budget.Client/ ./
ARG VITE_AUTH_AUTHORITY=https://auth.stwaddle.com/
ARG VITE_AUTH_CLIENT_ID=budget-client
ARG VITE_AUTH_REDIRECT_URI=https://budget.stwaddle.com/callback
ENV VITE_AUTH_AUTHORITY=$VITE_AUTH_AUTHORITY
ENV VITE_AUTH_CLIENT_ID=$VITE_AUTH_CLIENT_ID
ENV VITE_AUTH_REDIRECT_URI=$VITE_AUTH_REDIRECT_URI
RUN npm run build
# Stage 2: Build and publish ASP.NET app
+213
View File
@@ -0,0 +1,213 @@
# 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.