From 7507b3279aa4724086f1771854a3c6ad6c4307d4 Mon Sep 17 00:00:00 2001 From: Jeff Dyke Date: Tue, 4 Nov 2025 22:10:08 -0500 Subject: [PATCH 1/3] remove cruft --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 72ed67e..2cda8d5 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ This service implements a **headless OAuth2 login/consent provider** that bridges Ory Hydra (OAuth2 server with DCR) and Google OAuth (identity provider without DCR support). +In words, I could not write myself: [Detailed breakdown of this OAuth2 flow](OAUTH2_ARCHITECTURE.md) ## Localhost vs Proxy @@ -15,8 +16,6 @@ Local development does not enable https - [Linting](./LINTING.md) - [Quality Baseline](./QUALITY_BASELINE.mc) - [Unit Tests](./README.test.md) -- [Authorization Flow](AUTH_FLOW.md) - - This ties nuances of the OAuth2 Authorization Flow into decisions made in the application, as a particular flow was required ### Running Locally From 9077c27b2600261b136b7c18e785cd8ef72b8017 Mon Sep 17 00:00:00 2001 From: Jeff Dyke Date: Tue, 4 Nov 2025 23:23:14 -0500 Subject: [PATCH 2/3] add local docker helper directory --- docker-compose.yml | 10 +- docker/postgres/README.md | 195 +++++++++++++++++++++++++ docker/postgres/init-session-table.sql | 28 ++++ 3 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 docker/postgres/README.md create mode 100644 docker/postgres/init-session-table.sql diff --git a/docker-compose.yml b/docker-compose.yml index c12d2e6..1e25da6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -47,12 +47,20 @@ services: - POSTGRES_PASSWORD=my-super-secret-password - POSTGRES_DB=hydra volumes: - - postgres-data:/var/lib/postgres/data + - postgres-data:/var/lib/postgresql/data + # Mount initialization script to create session table + - ./docker/postgres/init-session-table.sql:/docker-entrypoint-initdb.d/01-init-session-table.sql:ro networks: - hydra-net ports: - "5432:5432" restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pg_isready -U hydra -d hydra"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 10s redis: image: redis ports: diff --git a/docker/postgres/README.md b/docker/postgres/README.md new file mode 100644 index 0000000..32e9b72 --- /dev/null +++ b/docker/postgres/README.md @@ -0,0 +1,195 @@ +# PostgreSQL Session Storage Setup + +## Overview + +This directory contains initialization scripts for PostgreSQL that are automatically executed when the database container is first created. + +## Session Table + +The `init-session-table.sql` script creates the `session` table required by `connect-pg-simple` for storing Express session data used in OAuth2 flows. + +### Table Schema + +```sql +CREATE TABLE "session" ( + "sid" varchar NOT NULL, -- Session ID (primary key) + "sess" json NOT NULL, -- Session data (JSON) + "expire" timestamp(6) NOT NULL -- Expiration timestamp +) +``` + +### Idempotency + +The script uses `CREATE TABLE IF NOT EXISTS` and `CREATE INDEX IF NOT EXISTS` to ensure it can be run multiple times safely without errors. This is important because: + +- PostgreSQL's `docker-entrypoint-initdb.d` only runs scripts on **first container creation** +- The scripts are **not re-run** when the container restarts +- If you need to re-initialize, you must delete the volume: `docker volume rm hydra_postgres-data` + +## Usage + +### First Time Setup + +```bash +# Start all services (postgres will auto-initialize) +docker-compose up -d + +# Check logs to verify table creation +docker-compose logs postgres | grep "Session table" +``` + +You should see: +``` +postgres | Session table initialization complete +postgres | Table: session +postgres | Purpose: Express session storage for OAuth2 flows +postgres | Used by: connect-pg-simple middleware +``` + +### Verify Table Creation + +```bash +# Connect to PostgreSQL +docker-compose exec postgres psql -U hydra -d hydra + +# Check if table exists +\dt session + +# View table schema +\d session + +# Exit +\q +``` + +### Re-initialization (Clean Slate) + +If you need to completely reset the database: + +```bash +# Stop all services +docker-compose down + +# Remove the postgres volume +docker volume rm hydra_postgres-data + +# Start services (will re-run init scripts) +docker-compose up -d +``` + +## OAuth2 Flow Context + +The session table stores Express session data for OAuth2 authorization flows: + +- **PKCE State**: Temporary storage during authorization code flow +- **Login/Consent Challenges**: Session data from Hydra flows +- **User Session**: Authenticated user session across requests +- **CSRF Tokens**: Session-based CSRF protection + +Session data is stored in PostgreSQL instead of memory/Redis to ensure: +- **Persistence**: Sessions survive application restarts +- **Scalability**: Multiple application instances share session storage +- **Security**: Session data encrypted and isolated per user + +## Configuration + +Session storage is configured in [src/app-fp.ts](../../src/app-fp.ts): + +```typescript +import { PgStore } from './config.js' + +app.use(session({ + store: new PgStore({ + pool, + tableName: 'session', + createTableIfMissing: true, + }), + secret: process.env.SESSION_SECRET, + resave: false, + saveUninitialized: false, + proxy: true, +})) +``` + +## Database Connection + +Connection details are configured via environment variables: + +```bash +# Default values (local development) +POSTGRES_HOST=localhost +POSTGRES_PORT=5432 +POSTGRES_USER=hydra +POSTGRES_PASSWORD=my-super-secret-password +POSTGRES_DB=hydra + +# Connection string (DSN) +DSN=postgres://hydra:my-super-secret-password@localhost:5432/hydra?sslmode=disable +``` + +See [src/fp/config.ts](../../src/fp/config.ts) for full configuration details. + +## Troubleshooting + +### Table Not Created + +**Problem**: Application fails with "relation 'session' does not exist" + +**Solution**: +1. Check if volume was created before init script existed: + ```bash + docker volume ls | grep postgres + ``` +2. If volume exists, remove it and recreate: + ```bash + docker-compose down + docker volume rm hydra_postgres-data + docker-compose up -d + ``` + +### Permission Denied + +**Problem**: Application can't write to session table + +**Solution**: The init script grants permissions, but if you created the table manually: +```sql +GRANT SELECT, INSERT, UPDATE, DELETE ON "session" TO hydra; +``` + +### Connection Refused + +**Problem**: Application can't connect to PostgreSQL + +**Solution**: +1. Check if PostgreSQL is running: + ```bash + docker-compose ps postgres + ``` +2. Check healthcheck status: + ```bash + docker-compose ps postgres | grep healthy + ``` +3. Verify connection string in environment: + ```bash + docker-compose exec headless-ts env | grep DSN + ``` + +## Development vs Production + +### Development (Docker Compose) +- PostgreSQL runs in Docker container +- Data persists in named volume `postgres-data` +- Scripts in `docker-entrypoint-initdb.d/` auto-run on first start + +### Production +- Use managed PostgreSQL (RDS, Cloud SQL, etc.) +- Run init script manually or via migration tool +- Set appropriate `sslmode` (not `disable`) +- Use strong passwords (not defaults) + +## Related Documentation + +- [OAUTH2_ARCHITECTURE.md](../../OAUTH2_ARCHITECTURE.md) - OAuth2 flow architecture +- [src/fp/config.ts](../../src/fp/config.ts) - Database configuration +- [src/pool.ts](../../src/pool.ts) - PostgreSQL connection pool +- [connect-pg-simple](https://github.com/voxpelli/node-connect-pg-simple) - Session store documentation diff --git a/docker/postgres/init-session-table.sql b/docker/postgres/init-session-table.sql new file mode 100644 index 0000000..0bbe136 --- /dev/null +++ b/docker/postgres/init-session-table.sql @@ -0,0 +1,28 @@ +-- PostgreSQL initialization script for session storage +-- This script is idempotent and can be run multiple times safely +-- Used by connect-pg-simple for express-session storage + +-- Create session table if it doesn't exist +-- Schema matches connect-pg-simple v10.x requirements +CREATE TABLE IF NOT EXISTS "session" ( + "sid" varchar NOT NULL COLLATE "default", + "sess" json NOT NULL, + "expire" timestamp(6) NOT NULL, + CONSTRAINT "session_pkey" PRIMARY KEY ("sid") +) WITH (OIDS=FALSE); + +-- Create index on expire column for efficient cleanup +CREATE INDEX IF NOT EXISTS "IDX_session_expire" ON "session" ("expire"); + +-- Grant permissions to hydra user +-- This ensures the application can read/write session data +GRANT SELECT, INSERT, UPDATE, DELETE ON "session" TO hydra; + +-- Log successful initialization +DO $$ +BEGIN + RAISE NOTICE 'Session table initialization complete'; + RAISE NOTICE 'Table: session'; + RAISE NOTICE 'Purpose: Express session storage for OAuth2 flows'; + RAISE NOTICE 'Used by: connect-pg-simple middleware'; +END $$; From fcebf8513c9cfabcc81d4a72eaa109c4712ed064 Mon Sep 17 00:00:00 2001 From: Jeff Dyke Date: Tue, 4 Nov 2025 23:45:47 -0500 Subject: [PATCH 3/3] move image to Docker Hug --- docker-compose.yml | 4 +- docker/DOCKER_HUB_MIGRATION.md | 371 ++++++++++++++++++++++++++++++++ scripts/migrate-to-dockerhub.sh | 84 ++++++++ 3 files changed, 458 insertions(+), 1 deletion(-) create mode 100644 docker/DOCKER_HUB_MIGRATION.md create mode 100755 scripts/migrate-to-dockerhub.sh diff --git a/docker-compose.yml b/docker-compose.yml index 1e25da6..b38dd73 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -68,7 +68,9 @@ services: networks: - hydra-net headless-ts: - image: 668874212870.dkr.ecr.us-east-1.amazonaws.com/drone-hydra-headless-ts + # Migrated from AWS ECR to Docker Hub for easier access + # Previous: 668874212870.dkr.ecr.us-east-1.amazonaws.com/drone-hydra-headless-ts + image: jeffdyke/hydra-headless-ts:latest env_file: - /etc/hydra-headless-ts/.env.auth.hydra - /etc/hydra-headless-ts/.env.auth.google diff --git a/docker/DOCKER_HUB_MIGRATION.md b/docker/DOCKER_HUB_MIGRATION.md new file mode 100644 index 0000000..57022d5 --- /dev/null +++ b/docker/DOCKER_HUB_MIGRATION.md @@ -0,0 +1,371 @@ +# Docker Hub Migration Guide + +## Overview + +The `hydra-headless-ts` Docker image has been migrated from **AWS ECR** to **Docker Hub** for easier accessibility and distribution. + +## Migration Details + +### Previous Image Location (ECR) +``` +668874212870.dkr.ecr.us-east-1.amazonaws.com/drone-hydra-headless-ts:latest +``` + +**Limitations:** +- Requires AWS CLI authentication +- Requires ECR permissions +- Region-specific (us-east-1) +- Not publicly accessible + +### New Image Location (Docker Hub) +``` +jeffdyke/hydra-headless-ts:latest +``` + +**Benefits:** +- ✅ Publicly accessible (no authentication required for pulls) +- ✅ Platform-agnostic +- ✅ Faster pulls (Docker Hub CDN) +- ✅ Easier to share with team/community +- ✅ Version tracking via Docker Hub UI + +## Migration Process + +### One-Time Migration + +Use the provided migration script to copy the latest image from ECR to Docker Hub: + +```bash +# Run the migration script +./scripts/migrate-to-dockerhub.sh + +# Or specify a specific tag +./scripts/migrate-to-dockerhub.sh v1.0.0 +``` + +The script will: +1. ✅ Authenticate with AWS ECR +2. ✅ Pull the latest image from ECR +3. ✅ Tag the image for Docker Hub +4. ✅ Authenticate with Docker Hub +5. ✅ Push the image to Docker Hub +6. ✅ Verify the migration + +### Manual Migration (Alternative) + +If you prefer to migrate manually: + +```bash +# 1. Login to AWS ECR +aws ecr get-login-password --region us-east-1 | \ + docker login --username AWS --password-stdin \ + 668874212870.dkr.ecr.us-east-1.amazonaws.com + +# 2. Pull from ECR +docker pull 668874212870.dkr.ecr.us-east-1.amazonaws.com/drone-hydra-headless-ts:latest + +# 3. Tag for Docker Hub +docker tag \ + 668874212870.dkr.ecr.us-east-1.amazonaws.com/drone-hydra-headless-ts:latest \ + jeffdyke/hydra-headless-ts:latest + +# 4. Login to Docker Hub +docker login +# Username: jeffdyke +# Password: + +# 5. Push to Docker Hub +docker push jeffdyke/hydra-headless-ts:latest +``` + +## Using the Docker Hub Image + +### With docker-compose.yml + +The `docker-compose.yml` has been updated to use the Docker Hub image: + +```yaml +services: + headless-ts: + image: jeffdyke/hydra-headless-ts:latest + # ... rest of configuration +``` + +To use it: + +```bash +# Pull the latest image +docker-compose pull headless-ts + +# Start services +docker-compose up -d + +# Or rebuild and start +docker-compose up -d --build +``` + +### Standalone Docker Run + +```bash +# Pull the image +docker pull jeffdyke/hydra-headless-ts:latest + +# Run the container +docker run -d \ + --name hydra-headless-ts \ + -p 3000:3000 \ + -e NODE_ENV=production \ + -e BASE_URL=https://auth.example.com \ + -e HYDRA_PUBLIC_URL=https://auth.example.com \ + -e GOOGLE_CLIENT_ID=your-client-id \ + -e GOOGLE_CLIENT_SECRET=your-client-secret \ + jeffdyke/hydra-headless-ts:latest +``` + +## Building and Pushing New Versions + +### Build Locally + +```bash +# Build the image +docker build -f build/Dockerfile.headless-ts -t jeffdyke/hydra-headless-ts:latest . + +# Tag with version +docker tag jeffdyke/hydra-headless-ts:latest jeffdyke/hydra-headless-ts:v1.0.0 + +# Push to Docker Hub +docker push jeffdyke/hydra-headless-ts:latest +docker push jeffdyke/hydra-headless-ts:v1.0.0 +``` + +### CI/CD Pipeline (Recommended) + +Update your CI/CD pipeline to push directly to Docker Hub instead of ECR: + +```yaml +# Example GitHub Actions +- name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: build/Dockerfile.headless-ts + push: true + tags: | + jeffdyke/hydra-headless-ts:latest + jeffdyke/hydra-headless-ts:${{ github.sha }} +``` + +## Version Tagging Strategy + +### Recommended Tags + +1. **latest** - Always points to the most recent stable build + ``` + jeffdyke/hydra-headless-ts:latest + ``` + +2. **Semantic Version** - Specific release versions + ``` + jeffdyke/hydra-headless-ts:v1.0.0 + jeffdyke/hydra-headless-ts:v1.0.1 + ``` + +3. **Git SHA** - Specific commit references + ``` + jeffdyke/hydra-headless-ts:9077c27 + ``` + +4. **Environment-specific** - For different deployment stages + ``` + jeffdyke/hydra-headless-ts:staging + jeffdyke/hydra-headless-ts:production + ``` + +### Tagging Best Practices + +```bash +# Tag and push multiple versions +docker tag jeffdyke/hydra-headless-ts:latest jeffdyke/hydra-headless-ts:v1.0.0 +docker tag jeffdyke/hydra-headless-ts:latest jeffdyke/hydra-headless-ts:$(git rev-parse --short HEAD) + +docker push jeffdyke/hydra-headless-ts:latest +docker push jeffdyke/hydra-headless-ts:v1.0.0 +docker push jeffdyke/hydra-headless-ts:$(git rev-parse --short HEAD) +``` + +## Security Considerations + +### Docker Hub Repository Settings + +1. **Make Repository Public** (for open-source projects) + - Navigate to: https://hub.docker.com/r/jeffdyke/hydra-headless-ts/settings + - Set visibility to "Public" + +2. **Or Keep Private** (for proprietary code) + - Keep visibility as "Private" + - Team members need Docker Hub account to pull + +### Authentication for Private Repositories + +If the repository is private, users need to authenticate: + +```bash +# Login to Docker Hub +docker login + +# Pull private image +docker pull jeffdyke/hydra-headless-ts:latest +``` + +In docker-compose.yml: + +```bash +# Login before docker-compose +docker login + +# Then start services +docker-compose up -d +``` + +## Dockerfile Location + +The Dockerfile used to build this image: + +``` +build/Dockerfile.headless-ts +``` + +See the Dockerfile for build details and dependencies. + +## Image Contents + +The Docker image includes: + +- **Node.js Application**: OAuth2 headless login/consent provider +- **Effect-ts Runtime**: Functional effects framework +- **Dependencies**: All production npm packages +- **Configuration**: Environment-based config support +- **Healthcheck**: Built-in health monitoring + +### Environment Variables + +Required environment variables (set via `env_file` or `-e` flags): + +```bash +# Application +BASE_URL=https://auth.example.com +APP_ENV=production +PORT=3000 + +# Hydra OAuth2 Server +HYDRA_PUBLIC_URL=https://auth.example.com +HYDRA_ADMIN_HOST=hydra +HYDRA_ADMIN_PORT=4445 + +# Google OAuth +GOOGLE_CLIENT_ID=your-client-id +GOOGLE_CLIENT_SECRET=your-client-secret +GOOGLE_REDIRECT_URI=https://auth.example.com/callback + +# Database +DSN=postgres://hydra:password@postgres:5432/hydra +POSTGRES_HOST=postgres +POSTGRES_PORT=5432 + +# Redis +REDIS_HOST=redis +REDIS_PORT=6379 + +# Security +SESSION_SECRET=your-session-secret +COOKIE_SECRET=your-cookie-secret +``` + +## Troubleshooting + +### Image Pull Fails + +**Problem**: `Error response from daemon: pull access denied` + +**Solution**: +1. For public repository: No authentication needed, check image name spelling +2. For private repository: Login first with `docker login` + +### Wrong Architecture + +**Problem**: `WARNING: The requested image's platform (linux/amd64) does not match` + +**Solution**: Build multi-platform images + +```bash +# Enable buildx +docker buildx create --use + +# Build for multiple platforms +docker buildx build \ + --platform linux/amd64,linux/arm64 \ + -t jeffdyke/hydra-headless-ts:latest \ + -f build/Dockerfile.headless-ts \ + --push . +``` + +### Image Size Too Large + +**Problem**: Image size is very large (>1GB) + +**Solution**: Optimize Dockerfile with multi-stage build (already implemented in `build/Dockerfile.headless-ts`) + +## Monitoring and Maintenance + +### Check Image Size + +```bash +docker images jeffdyke/hydra-headless-ts +``` + +### View Image History + +```bash +docker history jeffdyke/hydra-headless-ts:latest +``` + +### Inspect Image Details + +```bash +docker inspect jeffdyke/hydra-headless-ts:latest +``` + +### Clean Up Old Images + +```bash +# Remove old ECR image locally +docker rmi 668874212870.dkr.ecr.us-east-1.amazonaws.com/drone-hydra-headless-ts:latest + +# Remove unused images +docker image prune -a +``` + +## Migration Checklist + +- [x] Run migration script to copy image from ECR to Docker Hub +- [x] Update docker-compose.yml to use Docker Hub image +- [x] Test pulling and running the new image +- [ ] Update CI/CD pipeline to push to Docker Hub +- [ ] Update deployment documentation +- [ ] Notify team of new image location +- [ ] (Optional) Deprecate ECR repository + +## Resources + +- **Docker Hub Repository**: https://hub.docker.com/r/jeffdyke/hydra-headless-ts +- **Docker Hub Documentation**: https://docs.docker.com/docker-hub/ +- **Dockerfile**: [build/Dockerfile.headless-ts](../build/Dockerfile.headless-ts) +- **OAuth2 Architecture**: [OAUTH2_ARCHITECTURE.md](../OAUTH2_ARCHITECTURE.md) + +## Support + +For issues with the Docker image: +1. Check image logs: `docker logs ` +2. Verify environment variables are set correctly +3. Check Docker Hub for latest version +4. Review Dockerfile for build configuration diff --git a/scripts/migrate-to-dockerhub.sh b/scripts/migrate-to-dockerhub.sh new file mode 100755 index 0000000..fa1d41e --- /dev/null +++ b/scripts/migrate-to-dockerhub.sh @@ -0,0 +1,84 @@ +#!/bin/bash +set -e + +# Migrate Docker image from AWS ECR to Docker Hub +# This script pulls the latest image from ECR and pushes it to Docker Hub + +# Configuration +ECR_IMAGE="668874212870.dkr.ecr.us-east-1.amazonaws.com/drone-hydra-headless-ts" +DOCKERHUB_IMAGE="jeffdyke/hydra-headless-ts" +TAG="${1:-latest}" + +echo "======================================" +echo "Docker Image Migration: ECR → Docker Hub" +echo "======================================" +echo "Source: ${ECR_IMAGE}:${TAG}" +echo "Destination: ${DOCKERHUB_IMAGE}:${TAG}" +echo "======================================" +echo "" + +# Step 1: Login to AWS ECR +echo "→ Step 1: Authenticating with AWS ECR..." +if ! aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 668874212870.dkr.ecr.us-east-1.amazonaws.com; then + echo "✗ Error: Failed to authenticate with AWS ECR" + echo " Make sure you have AWS CLI configured with proper credentials" + exit 1 +fi +echo "✓ Successfully authenticated with AWS ECR" +echo "" + +# Step 2: Pull image from ECR +echo "→ Step 2: Pulling image from ECR..." +if ! docker pull "${ECR_IMAGE}:${TAG}"; then + echo "✗ Error: Failed to pull image from ECR" + echo " Make sure the image exists: ${ECR_IMAGE}:${TAG}" + exit 1 +fi +echo "✓ Successfully pulled image from ECR" +echo "" + +# Step 3: Tag image for Docker Hub +echo "→ Step 3: Tagging image for Docker Hub..." +docker tag "${ECR_IMAGE}:${TAG}" "${DOCKERHUB_IMAGE}:${TAG}" +echo "✓ Successfully tagged image" +echo "" + +# Step 4: Login to Docker Hub +echo "→ Step 4: Authenticating with Docker Hub..." +echo " Please login to Docker Hub (username: jeffdyke)" +if ! docker login; then + echo "✗ Error: Failed to authenticate with Docker Hub" + exit 1 +fi +echo "✓ Successfully authenticated with Docker Hub" +echo "" + +# Step 5: Push image to Docker Hub +echo "→ Step 5: Pushing image to Docker Hub..." +if ! docker push "${DOCKERHUB_IMAGE}:${TAG}"; then + echo "✗ Error: Failed to push image to Docker Hub" + exit 1 +fi +echo "✓ Successfully pushed image to Docker Hub" +echo "" + +# Step 6: Verify image on Docker Hub +echo "→ Step 6: Verifying image on Docker Hub..." +echo " Image URL: https://hub.docker.com/r/${DOCKERHUB_IMAGE}" +echo " You can pull it with: docker pull ${DOCKERHUB_IMAGE}:${TAG}" +echo "" + +echo "======================================" +echo "✓ Migration Complete!" +echo "======================================" +echo "" +echo "Summary:" +echo " Source: ${ECR_IMAGE}:${TAG}" +echo " Destination: ${DOCKERHUB_IMAGE}:${TAG}" +echo " Status: Successfully migrated" +echo "" +echo "Next steps:" +echo " 1. Update docker-compose.yml to use: ${DOCKERHUB_IMAGE}:${TAG}" +echo " 2. Test the new image: docker-compose up -d" +echo " 3. Optionally remove ECR image to save storage" +echo ""