Spencer Twaddle 389446ee21 Phase 1: Wire up OpenTelemetry in Budget.Api
Adds OTel packages (Extensions.Hosting, Instrumentation.AspNetCore/Http/EFCore,
Exporter.OTLP) and configures logging + tracing in Program.cs. Endpoint is
intentionally left unset in code — read from OTEL_EXPORTER_OTLP_ENDPOINT at runtime.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 17:29:01 -05:00
2026-05-03 07:20:19 -05:00

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

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):

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

.\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:

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:

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:

    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:

    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):

    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:

    {
      "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.

S
Description
No description provided
Readme 240 KiB
Languages
TypeScript 46.8%
C# 43.9%
CSS 7.8%
Dockerfile 0.5%
JavaScript 0.4%
Other 0.6%