Skip to content

Latest commit

 

History

History
474 lines (346 loc) · 16.5 KB

File metadata and controls

474 lines (346 loc) · 16.5 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Key Commands

IMPORTANT: This project requires Node.js >=22.0.0. Always use Node 22 for all commands:

# Use Node 22 (required for this project)
unset npm_config_prefix && source ~/.nvm/nvm.sh && nvm use 22

Setup

# Install dependencies
pnpm install

# Setup HTTPS with Caddy
sudo caddy trust         # Install Caddy's internal CA root to macOS trust store
sudo caddy run --config ./caddyfile  # Run Caddy in another terminal window
# OR use the pnpm script:
pnpm caddy

Development

Note: All development commands require Node 22. Run unset npm_config_prefix && source ~/.nvm/nvm.sh && nvm use 22 first.

# Run all services in development mode with hot reload
# Note: This command includes NODE_EXTRA_CA_CERTS for Caddy's internal CA
pnpm dev

# Run individual services
pnpm dev:auth  # Authorization Server
pnpm dev:api   # Resource Server (API)
pnpm dev:app   # Client App (Relying Party)

# Build all applications
pnpm build

# Lint code
pnpm lint
pnpm lint:fix  # Auto-fix linting issues

# Production commands (after build)
pnpm --filter @apps/auth start  # Start Auth Server in production
pnpm --filter @apps/api start   # Start API in production  
pnpm --filter @apps/app start   # Start APP in production

Caddy Configuration

The current caddyfile configuration routes traffic as follows:

  • id.localtest.melocalhost:3001 (OP)
  • app.localtest.melocalhost:3004 (Client App)
  • api.localtest.melocalhost:3003 (Resource Server)

If you prefer not to use sudo, you can modify the caddyfile to use high ports:

:8443 {
  tls internal
  reverse_proxy localhost:3001
}

Then update the .env file to reflect the new URL pattern (e.g., https://localhost:8443).

Architecture Overview

This is an OpenID Connect (OIDC) implementation using Express for all services in a pnpm monorepo. The system consists of three main services:

1. Authorization Server (Auth - OpenID Provider)

  • Located in apps/auth
  • Built with oidc-provider embedded in Express
  • Handles authentication, authorization, and token issuance
  • Uses EJS templates for interaction views (login/consent)
  • Includes Tailwind CSS for styling
  • Currently uses in-memory storage (Postgres adapter planned)
  • Default test user: user@example.test / passw0rd!
  • Runs on port 3001

Key components:

  • OIDC configuration (clients, claims, scopes)
  • Basic interaction handlers (login and consent flows)
  • In-memory user store (temporary)
  • Tailwind CSS build process

Key features:

  • Multiple client support: Configure multiple OIDC clients via .env.clients.json or OIDC_CLIENTS environment variable
  • Refresh token support: Issues refresh tokens when offline_access scope is requested and client supports refresh_token grant type
  • Configurable TTLs: Session (1 day), Grant (1 year), AccessToken (1 hour), IdToken (1 hour), RefreshToken (14 days)
  • Dynamic scope display: Consent screen shows requested scopes with human-readable descriptions
  • Forced interactions: Authorization flow uses prompt: "login consent" to always show login and consent screens
  • RP-initiated logout: Supports logout with automatic form submission
  • Token signing (JWKS):
    • Development: Uses ephemeral keys (auto-generated on startup with kid="keystore-CHANGE-ME")
    • Production: Configure persistent JWKS via JWKS environment variable to prevent token invalidation on restarts
    • Generate keys with: node scripts/secrets.js jwks

2. Resource Server (API)

  • Located in apps/api
  • Protects resources with JWT validation using jose
  • Validates tokens against the Auth server's JWKS endpoint
  • Enforces scope-based authorization (e.g., accounts:read)
  • Includes customer and account data repositories
  • Uses Pino for structured logging
  • Runs on port 3003
  • Implements FDX with Plaid's Core Exchange API specification (v6.3.1)

Key components:

  • JWT verification middleware
  • Protected resources requiring specific scopes
  • Customer and account data models
  • Repository pattern for data access
  • Request validation utilities
  • Public health endpoint

Available endpoints (FDX compliant):

  • /api/fdx/v6/customers/current - Get current customer information
  • /api/fdx/v6/accounts - List customer accounts
  • /api/fdx/v6/accounts/{accountId} - Get account details
  • /api/fdx/v6/accounts/{accountId}/contact - Get account contact information
  • /api/fdx/v6/accounts/{accountId}/statements - List account statements
  • /api/fdx/v6/accounts/{accountId}/statements/{statementId} - Download statement PDF
  • /api/fdx/v6/accounts/{accountId}/transactions - Get account transactions
  • /api/fdx/v6/accounts/{accountId}/payment-networks - Get payment network information
  • /api/fdx/v6/accounts/{accountId}/asset-transfer-networks - Get asset transfer network information

3. Client Application (APP)

  • Located in apps/app
  • Acts as a Relying Party using openid-client
  • Implements Authorization Code flow with PKCE
  • Stores tokens in HTTP-only cookies (access token, refresh token, ID token)
  • Makes authenticated calls to the API
  • Uses EJS templates for views
  • Includes Tailwind CSS for styling
  • Runs on port 3004

Key components:

  • OIDC client setup and discovery
  • Login/callback handling with PKCE
  • Token storage in secure cookies
  • API calls with access token
  • EJS view templates
  • Tailwind CSS build process

Features:

  • API Explorer: Interactive UI for testing all FDX Core Exchange endpoints with parameter inputs
  • Token Inspector: View decoded ID token claims and user information at /token
  • Token debug endpoint: View raw and decoded tokens at /debug/tokens
  • Offline access: Requests offline_access scope to receive refresh tokens
  • Comprehensive scopes: Requests openid, email, profile, offline_access, and accounts:read scopes

Infrastructure

  • All services communicate via HTTPS using Caddy's internal CA (local) or Heroku's SSL (production)
  • Local development: Caddy handles routing via *.localtest.me subdomains
    • Auth: https://id.localtest.me (port 3001)
    • API: https://api.localtest.me (port 3003)
    • APP: https://app.localtest.me (port 3004)
  • Production (Heroku): Deployed as Docker containers to Heroku apps with custom domains
    • Auth: https://auth.plaidypus.dev
    • API: https://api.plaidypus.dev
    • App: https://app.plaidypus.dev
    • See docs/heroku-setup.md for full setup instructions
  • Environment variables in .env control service configuration
  • TypeScript with ESM modules across all apps
  • Shared TypeScript configuration via tsconfig.base.json
  • ESLint configuration with TypeScript and Stylistic plugins
  • Pino structured logging throughout

Configuration

Client Configuration

The authorization server supports multiple client configurations:

  1. Single client (default): Set CLIENT_ID, CLIENT_SECRET, and REDIRECT_URI in .env
  2. Multiple clients: Create .env.clients.json file in the project root (see .env.clients.example.json)
  3. Environment variable: Set OIDC_CLIENTS as a JSON string

Each client configuration must include:

  • client_id: Unique client identifier
  • client_secret: Client secret for authentication
  • redirect_uris: Array of allowed redirect URIs
  • post_logout_redirect_uris: Array of allowed logout redirect URIs
  • grant_types: Array including authorization_code and optionally refresh_token
  • response_types: Array with code
  • token_endpoint_auth_method: Usually client_secret_basic

Scopes

Supported scopes:

  • openid - Basic identity (required)
  • profile - Profile information (name)
  • email - Email address
  • offline_access - Offline access (enables refresh tokens)
  • accounts:read - Read access to account data

Token TTLs

Default token lifetimes (configured in apps/auth/src/index.ts):

  • Session: 1 day (86400 seconds)
  • Grant: 1 year (31536000 seconds)
  • Access Token: 1 hour (3600 seconds)
  • ID Token: 1 hour (3600 seconds)
  • Refresh Token: 14 days (1209600 seconds)

Token Signing Keys (JWKS)

The authorization server uses JWKS (JSON Web Key Set) to sign JWT tokens:

Development (default):

  • No JWKS environment variable needed
  • oidc-provider auto-generates ephemeral keys on startup
  • Keys have kid="keystore-CHANGE-ME" in JWT headers
  • Keys regenerate on each restart (invalidates all tokens)
  • Perfectly acceptable for local development

Production (required):

  • Set JWKS environment variable with persistent signing keys
  • Prevents token invalidation on server restarts
  • Enables proper key rotation strategy
  • Generate with: node scripts/secrets.js jwks
  • Store securely (AWS Secrets Manager, HashiCorp Vault, etc.)
  • Contains private key material - never commit to version control

Why persistent keys matter:

  • Tokens survive service restarts and deployments
  • Multiple server instances can share the same keys
  • Proper cryptographic key rotation
  • Unique key IDs for debugging (e.g., key-abc123def456)

Configuration location: apps/auth/src/index.ts (lines 110-129) loads JWKS from environment and logs warnings if not set

Sensitive Data Handling

This project implements security best practices for handling sensitive configuration and credentials.

Environment Variables

All sensitive configuration is managed through environment variables:

Variable Purpose Security Level
CLIENT_SECRET OAuth client authentication High - Never commit
COOKIE_SECRET Session cookie signing High - Never commit
JWKS Token signing keys (contains private key) Critical - Never commit
OIDC_CLIENTS Multiple client configurations High - Never commit
POST_LOGOUT_REDIRECT_URI Post-logout redirect URL Low - Configurable per environment

Template Configuration Files

The project provides .env.example templates at multiple levels:

  • Root level: .env.example - Complete configuration template
  • Auth server: apps/auth/.env.example - Authorization server configuration
  • API server: apps/api/.env.example - Resource server configuration
  • Client app: apps/app/.env.example - Relying party configuration
  • Multiple clients: apps/auth/.env.clients.example.json - Multi-client template

To set up a new environment:

# Copy the root template
cp .env.example .env

# Or copy individual app templates
cp apps/auth/.env.example apps/auth/.env
cp apps/api/.env.example apps/api/.env
cp apps/app/.env.example apps/app/.env

Generating Production Secrets

Use the built-in secrets CLI to generate cryptographically secure values:

# Generate all secrets at once
node scripts/secrets.js all

# Or generate individual components
node scripts/secrets.js client   # OAuth client credentials
node scripts/secrets.js secrets  # Application secrets (COOKIE_SECRET)
node scripts/secrets.js jwks     # Token signing keys

Gitignore Protection

The .gitignore is configured to prevent accidental commits of sensitive files:

  • .env, .env.local, .env.prod, .env.production, .env.staging
  • .env.clients.json (actual client configurations)
  • *.pem, *.key, *.p12, *.pfx (certificate/key files)
  • secrets.json, credentials.json, *-secrets.json
  • service-account*.json (cloud provider credentials)

Security Checklist

Before deploying to production:

  1. Generate new secrets: Run node scripts/secrets.js all and use the output
  2. Never use dev values: Replace all *-CHANGE-FOR-PRODUCTION placeholders
  3. Use a secret manager: Store secrets in AWS Secrets Manager, HashiCorp Vault, or similar
  4. Rotate regularly: Establish a secret rotation schedule
  5. Audit access: Limit who can access production secrets
  6. Monitor for leaks: Use tools like git-secrets or truffleHog to scan for exposed credentials

Demo Credentials

The following credentials are intentionally hardcoded for development/demo purposes only:

  • Test user: user@example.test / passw0rd!
  • Blocked user: blocked@example.test / passw0rd!

These are stored in-memory in apps/auth/src/index.ts and should be replaced with a proper user database and password hashing (bcrypt/argon2) for production use.

CI/CD and Build Processes

This project includes automated CI/CD pipelines and containerization support for secure, repeatable deployments.

GitHub Actions Workflows

Workflow Trigger Purpose
ci.yml PRs, push to main Lint, build, security audit
security.yml Weekly, dependency changes CodeQL analysis
deploy-heroku.yml Push to main, manual Build and deploy all services to Heroku
deploy-*.yml Push to paths Deploy individual services to VM

CI Workflow (ci.yml)

Runs on every pull request and push to main:

# Jobs run in parallel:
- Lint        # ESLint validation
- Build       # TypeScript compilation + CSS build
- Security    # npm audit for vulnerabilities

Security Workflow (security.yml)

Comprehensive security scanning:

  • Dependency Audit: Weekly npm audit for known vulnerabilities
  • CodeQL Analysis: Static analysis for security issues

Dependabot

Automated dependency updates via .github/dependabot.yml:

  • npm packages: Weekly updates, grouped by type (production vs dev)
  • GitHub Actions: Weekly updates for workflow actions

Docker Support

Each service has a production-ready Dockerfile with multi-stage builds:

# Build individual images
docker build -f apps/auth/Dockerfile -t core-exchange-auth .
docker build -f apps/api/Dockerfile -t core-exchange-api .
docker build -f apps/app/Dockerfile -t core-exchange-app .

# Or use docker-compose
docker compose up --build

Dockerfile features:

  • Multi-stage builds for minimal image size
  • Non-root user for security
  • Health checks for container orchestration
  • Production-only dependencies
  • Heroku PORT compatibility (maps dynamic PORT to service-specific port vars)

Docker Compose

Two compose configurations are provided:

File Purpose
docker-compose.yml Local development with Docker
docker-compose.prod.example.yml Production template (copy and customize)
# Development
docker compose up --build

# Production (after customizing)
cp docker-compose.prod.example.yml docker-compose.prod.yml
# Edit docker-compose.prod.yml with production values
docker compose -f docker-compose.prod.yml up -d

Build Commands

# Install dependencies (frozen lockfile for reproducibility)
pnpm install --frozen-lockfile

# Build all services
pnpm build

# Build individual services
pnpm --filter @apps/shared build
pnpm --filter @apps/auth build
pnpm --filter @apps/api build
pnpm --filter @apps/app build

# Run linting
pnpm lint
pnpm lint:fix

Deployment Security Checklist

Before deploying to production:

  1. Use frozen lockfile: Always pnpm install --frozen-lockfile
  2. Pin Node.js version: Use exact version (22.x) in CI and Dockerfiles
  3. Run security audit: pnpm audit --audit-level=high
  4. Build verification: Ensure pnpm build succeeds before deployment
  5. Use secrets management: Never commit secrets; use environment variables or secret managers
  6. Enable branch protection: Require PR reviews and passing CI checks

Branch Protection (Recommended)

Configure these settings on your main branch:

  • Require pull request reviews before merging
  • Require status checks to pass (CI workflow)
  • Require branches to be up to date before merging
  • Do not allow bypassing the above settings

Testing the Flow

  1. Visit https://id.localtest.me/.well-known/openid-configuration to verify the Auth server is running
  2. Go to https://app.localtest.me and click "Login"
  3. Use demo credentials: user@example.test / passw0rd!
  4. Approve consent (you'll see all requested scopes: openid, email, profile, offline_access, accounts:read)
  5. After redirect, explore the features:
    • API Explorer (/api-explorer): Test all FDX endpoints interactively
    • Token Inspector (/token): View your ID token claims and user information
    • Token Debug (/debug/tokens): Inspect raw and decoded access/ID/refresh tokens

Next Steps

The repository has identified the following future improvements:

  • Implement a Postgres adapter for oidc-provider for persistent storage
  • Replace the in-memory user store with a DB table + proper password hashing
  • Add E2E tests (Playwright/Cypress) to verify the authentication flow