From 70cf65ea63419f8988dba7fd4442b5ff6fef2438 Mon Sep 17 00:00:00 2001 From: Bryan Brooks Date: Mon, 9 Mar 2026 11:45:44 -0500 Subject: [PATCH] chore: sanitize docs for FOSS release, add GitHub CI - Remove DO-HANDOFF.md (deployment-specific ops document) - Update HOMELAB.md clone URL to GitHub - Update CHANGELOG.md comparison links to GitHub - Add v2.3.0 changelog entry - Add GitHub Actions CI workflow (test on push/PR) Co-Authored-By: Claude Opus 4.6 --- .github/workflows/ci.yml | 38 ++++++ CHANGELOG.md | 44 +++++-- docs/DO-HANDOFF.md | 242 --------------------------------------- docs/HOMELAB.md | 2 +- 4 files changed, 74 insertions(+), 252 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 docs/DO-HANDOFF.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..625440a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,38 @@ +name: CI + +on: + push: + branches: [main] + paths-ignore: + - '*.md' + - 'docs/**' + pull_request: + branches: [main] + +jobs: + test: + name: Test + runs-on: ubuntu-latest + defaults: + run: + working-directory: gateway + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install dependencies + run: npm ci + + - name: Type check + run: npm run typecheck + + - name: Lint + run: npm run lint + + - name: Run tests + run: npm run test:run diff --git a/CHANGELOG.md b/CHANGELOG.md index e924e30..f263457 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.3.0] - 2026-03-09 + +### Added + +- Passkey login support for forward-auth proxy flow (previously only available in OIDC flow) +- New endpoint `POST /auth/proxy/passkey` for WebAuthn authentication in proxy-auth +- Passkey button and WebAuthn JavaScript on proxy login page (feature-detected) +- GitHub Actions CI workflow for the FOSS repository + +### Security + +- MFA verify endpoints (`/auth/mfa/totp/verify`, `/auth/mfa/backup-codes/verify`) now require authentication; DID comes from session, not request body +- Session expiry enforced at the database query level (`getSession()` checks `expires_at`) +- OIDC logout redirect validation uses origin comparison instead of prefix matching (prevents open redirect) +- PKCE `plain` method rejected; only `S256` accepted with constant-time comparison +- Empty DID guard in OIDC token endpoint prevents minting tokens for unauthenticated authorization codes +- Handle format validation in OIDC authorize endpoint +- `trust proxy` configured for correct client IP behind reverse proxies +- `user_id` NaN validation in auth link endpoint + +### Changed + +- Removed `uuid` package dependency (replaced with `crypto.randomUUID()`) +- Test suite expanded to 404 tests + ## [2.2.0] - 2026-02-24 ### Added @@ -208,12 +233,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Input validation and sanitization - Rate limiting on all endpoints -[2.2.0]: https://gitea.cloudforest-basilisk.ts.net/Arcnode.xyz/atauth/compare/v2.1.0...v2.2.0 -[2.1.0]: https://gitea.cloudforest-basilisk.ts.net/Arcnode.xyz/atauth/compare/v2.0.3...v2.1.0 -[2.0.3]: https://gitea.cloudforest-basilisk.ts.net/Arcnode.xyz/atauth/compare/v2.0.2...v2.0.3 -[2.0.2]: https://gitea.cloudforest-basilisk.ts.net/Arcnode.xyz/atauth/compare/v2.0.1...v2.0.2 -[2.0.1]: https://gitea.cloudforest-basilisk.ts.net/Arcnode.xyz/atauth/compare/v2.0.0...v2.0.1 -[2.0.0]: https://gitea.cloudforest-basilisk.ts.net/Arcnode.xyz/atauth/compare/v1.3.0...v2.0.0 -[1.3.0]: https://gitea.cloudforest-basilisk.ts.net/Arcnode.xyz/atauth/compare/v1.2.0...v1.3.0 -[1.2.0]: https://gitea.cloudforest-basilisk.ts.net/Arcnode.xyz/atauth/compare/v1.0.0...v1.2.0 -[1.0.0]: https://gitea.cloudforest-basilisk.ts.net/Arcnode.xyz/atauth/releases/tag/v1.0.0 +[2.3.0]: https://github.com/Cache8063/atauth/compare/v2.2.0...v2.3.0 +[2.2.0]: https://github.com/Cache8063/atauth/compare/v2.1.0...v2.2.0 +[2.1.0]: https://github.com/Cache8063/atauth/compare/v2.0.3...v2.1.0 +[2.0.3]: https://github.com/Cache8063/atauth/compare/v2.0.2...v2.0.3 +[2.0.2]: https://github.com/Cache8063/atauth/compare/v2.0.1...v2.0.2 +[2.0.1]: https://github.com/Cache8063/atauth/compare/v2.0.0...v2.0.1 +[2.0.0]: https://github.com/Cache8063/atauth/compare/v1.3.0...v2.0.0 +[1.3.0]: https://github.com/Cache8063/atauth/compare/v1.2.0...v1.3.0 +[1.2.0]: https://github.com/Cache8063/atauth/compare/v1.0.0...v1.2.0 +[1.0.0]: https://github.com/Cache8063/atauth/releases/tag/v1.0.0 diff --git a/docs/DO-HANDOFF.md b/docs/DO-HANDOFF.md deleted file mode 100644 index 6302c3c..0000000 --- a/docs/DO-HANDOFF.md +++ /dev/null @@ -1,242 +0,0 @@ -# ATAuth - DigitalOcean Kubernetes Handoff - -This document provides everything you need to connect to and manage the ATAuth deployment on DigitalOcean managed Kubernetes. - -## Cluster Overview - -| Item | Value | -|------|-------| -| Provider | DigitalOcean Managed Kubernetes | -| Cluster | `do-nyc1-storm-dr-cluster` (nyc1 region) | -| Kubernetes | v1.34.1 | -| Namespace | `atauth` | -| Nodes | 3x worker (4 vCPU, 8GB RAM each) | -| Container registry | `registry.digitalocean.com/ghostmesh-registry` | - -## Connecting with kubectl - -You will receive a kubeconfig file (`atauth-team-kubeconfig.yaml`) separately. This kubeconfig is scoped to the `atauth` namespace only -- you cannot access other namespaces on the cluster. - -### Setup - -```bash -# Option 1: Set KUBECONFIG env var -export KUBECONFIG=/path/to/atauth-team-kubeconfig.yaml - -# Option 2: Merge into your default config -cp ~/.kube/config ~/.kube/config.backup -KUBECONFIG=~/.kube/config:/path/to/atauth-team-kubeconfig.yaml kubectl config view --merge --flatten > /tmp/merged && mv /tmp/merged ~/.kube/config -kubectl config use-context atauth-team@do-nyc1-storm-dr-cluster -``` - -### Verify - -```bash -kubectl get pods -n atauth -# Should show the atauth pod running - -kubectl get deployments -n atauth -# NAME READY UP-TO-DATE AVAILABLE -# atauth 1/1 1 1 -``` - -## What You Have Access To - -Your ServiceAccount (`atauth-team`) has full CRUD access within the `atauth` namespace: - -- Pods (including logs, exec, port-forward) -- Deployments and ReplicaSets -- Services -- Ingresses -- ConfigMaps -- Secrets -- PersistentVolumeClaims -- Events - -You do **not** have access to cluster-scoped resources or other namespaces. - -## Current Deployment Architecture - -``` - ┌──────────────────────┐ -Internet ──> nginx │ Ingress (nginx) │ - ingress ──┤ apricot.workingtitle │ - │ .zip │ - └──────────┬───────────┘ - │ - ┌──────────▼───────────┐ - │ Service (ClusterIP) │ - │ atauth:3100 │ - └──────────┬───────────┘ - │ - ┌──────────▼───────────┐ - │ Deployment │ - │ 1 replica, Recreate │ - │ strategy │ - └──────────┬───────────┘ - │ - ┌──────────▼───────────┐ - │ PVC (1Gi, RWO) │ - │ do-block-storage │ - │ SQLite DB at │ - │ /app/data/gateway.db │ - └──────────────────────┘ -``` - -### Key Resources - -| Resource | Name | Notes | -|----------|------|-------| -| Deployment | `atauth` | 1 replica, `Recreate` strategy (required -- RWO PVC) | -| Service | `atauth` | ClusterIP, port 3100 | -| Ingress | `atauth` | Host: `apricot.workingtitle.zip`, TLS via `atauth-tls` secret | -| PVC | `atauth-data` | 1Gi, `do-block-storage`, holds SQLite DB | -| ConfigMap | `atauth-config` | Non-sensitive env vars (CORS, OIDC issuer, WebAuthn, etc.) | -| Secret | `atauth-secrets` | `ATAUTH_ADMIN_TOKEN`, `ATAUTH_OIDC_KEY_SECRET`, `ATAUTH_FORWARD_AUTH_SESSION_SECRET` | -| Secret | `atauth-tls` | TLS cert/key for the ingress | -| Secret | `registry-ghostmesh-registry` | Docker registry pull credentials | - -### Container Image - -- Registry: `registry.digitalocean.com/ghostmesh-registry/atauth` -- Tags: short git SHA (e.g., `43d2471`) + `latest` -- Platform: `linux/amd64` (required -- DO nodes are amd64) -- Dockerfile: `gateway/Dockerfile` - -### Environment Variables - -Sourced from `atauth-config` ConfigMap: - -| Env Var | ConfigMap Key | Description | -|---------|---------------|-------------| -| `OAUTH_CLIENT_ID` | `ATAUTH_OAUTH_CLIENT_ID` | AT Protocol OAuth client metadata URL | -| `OAUTH_REDIRECT_URI` | `ATAUTH_OAUTH_REDIRECT_URI` | OAuth callback URL | -| `CORS_ORIGINS` | `ATAUTH_CORS_ORIGINS` | Comma-separated allowed origins | -| `OIDC_ENABLED` | `ATAUTH_OIDC_ENABLED` | Enable OIDC provider | -| `OIDC_ISSUER` | `ATAUTH_OIDC_ISSUER` | OIDC issuer URL | -| `FORWARD_AUTH_ENABLED` | `ATAUTH_FORWARD_AUTH_ENABLED` | Enable nginx forward-auth proxy | -| `FORWARD_AUTH_SESSION_TTL` | `ATAUTH_FORWARD_AUTH_SESSION_TTL` | Session TTL in seconds | -| `FORWARD_AUTH_PROXY_COOKIE_TTL` | `ATAUTH_FORWARD_AUTH_PROXY_COOKIE_TTL` | Proxy cookie TTL in seconds | -| `MFA_ENABLED` | `ATAUTH_MFA_ENABLED` | Enable passkey/WebAuthn MFA | -| `WEBAUTHN_RP_ID` | `ATAUTH_WEBAUTHN_RP_ID` | WebAuthn relying party ID | -| `WEBAUTHN_ORIGIN` | `ATAUTH_WEBAUTHN_ORIGIN` | WebAuthn origin URL | -| `WEBAUTHN_RP_NAME` | `ATAUTH_WEBAUTHN_RP_NAME` | WebAuthn relying party display name | - -Sourced from `atauth-secrets` Secret: - -| Env Var | Secret Key | Description | -|---------|------------|-------------| -| `ADMIN_TOKEN` | `ATAUTH_ADMIN_TOKEN` | Admin API bearer token | -| `OIDC_KEY_SECRET` | `ATAUTH_OIDC_KEY_SECRET` | OIDC signing key secret | -| `FORWARD_AUTH_SESSION_SECRET` | `ATAUTH_FORWARD_AUTH_SESSION_SECRET` | Session encryption secret | - -Hardcoded in deployment spec: - -| Env Var | Value | -|---------|-------| -| `PORT` | `3100` | -| `HOST` | `0.0.0.0` | -| `NODE_ENV` | `production` | -| `DB_PATH` | `/app/data/gateway.db` | - -## Common Operations - -### View logs - -```bash -kubectl logs -n atauth deployment/atauth -f -``` - -### Restart the deployment - -```bash -kubectl rollout restart deployment/atauth -n atauth -kubectl rollout status deployment/atauth -n atauth -``` - -### Deploy a new image - -```bash -kubectl set image deployment/atauth atauth=registry.digitalocean.com/ghostmesh-registry/atauth: -n atauth -kubectl rollout status deployment/atauth -n atauth --timeout=120s -``` - -### Update config - -```bash -# Edit a ConfigMap value -kubectl patch configmap atauth-config -n atauth --type merge \ - -p '{"data":{"ATAUTH_CORS_ORIGINS":"https://apricot.workingtitle.zip,https://new-app.example.com"}}' - -# Restart to pick up changes (env vars are read at startup) -kubectl rollout restart deployment/atauth -n atauth -``` - -### Update secrets - -```bash -# Update a secret value (must be base64 encoded) -kubectl patch secret atauth-secrets -n atauth --type merge \ - -p '{"data":{"ATAUTH_ADMIN_TOKEN":"'$(echo -n "new-token-value" | base64)'"}}' - -kubectl rollout restart deployment/atauth -n atauth -``` - -### Port-forward for local access - -```bash -kubectl port-forward -n atauth svc/atauth 3100:3100 -# ATAuth is now accessible at http://localhost:3100 -``` - -### Shell into the container - -```bash -kubectl exec -it -n atauth deployment/atauth -- /bin/sh -``` - -### Check health - -```bash -# Via port-forward -curl http://localhost:3100/health - -# Or check readiness probe status -kubectl describe pod -n atauth -l app=atauth | grep -A5 "Conditions:" -``` - -## CI/CD Pipeline - -The existing Gitea Actions workflow (`.gitea/workflows/deploy.yml`) runs on push to `main`: - -1. **Test job** (`ubuntu-latest` runner): `npm ci`, typecheck, lint, vitest -2. **Build and deploy job** (`host` runner): Docker build, push to DO registry, `kubectl set image`, rollout status - -The pipeline uses these Gitea org-level secrets: -- `DO_REGISTRY_TOKEN` -- DO container registry API token -- `DO_KUBECONFIG` -- base64-encoded kubeconfig for the `ci-deployer` ServiceAccount - -## Important Gotchas - -1. **Recreate strategy is required.** The PVC is RWO (ReadWriteOnce). RollingUpdate will deadlock if the new pod lands on a different node than the old one. - -2. **Domain must be `workingtitle.zip`, not `arcnode.xyz`.** The `arcnode.xyz` domain hosts a PDS (AT Protocol Personal Data Server). Using it for ATAuth causes same-site cookie/header conflicts. - -3. **Docker images must target `linux/amd64`.** The DO worker nodes run amd64. ARM images will crash with exec format errors. - -4. **HMAC token encoding.** ATAuth signs HMAC tokens with `createHmac('sha256', secretString)` using the UTF-8 encoding of the hex secret. Downstream services verifying tokens must match this encoding. - -5. **`req.accepts('json')` matches `*/*`.** Use `req.is('json')` for Content-Type checks in Express routes. - -6. **Registry pull secret must exist in namespace.** The `registry-ghostmesh-registry` secret is already present. If you recreate the namespace, you must re-add it or image pulls will fail. - -7. **SQLite WAL mode.** The DB runs in WAL mode on DO block storage. Single-writer only -- do not scale beyond 1 replica. - -## Live URLs - -| URL | Description | -|-----|-------------| -| `https://apricot.workingtitle.zip` | ATAuth gateway (public) | -| `https://apricot.workingtitle.zip/.well-known/openid-configuration` | OIDC discovery | -| `https://apricot.workingtitle.zip/admin/login` | Admin dashboard | -| `https://apricot.workingtitle.zip/health` | Health check endpoint | diff --git a/docs/HOMELAB.md b/docs/HOMELAB.md index 96d7444..ba0c76d 100644 --- a/docs/HOMELAB.md +++ b/docs/HOMELAB.md @@ -12,7 +12,7 @@ ATAuth provides OIDC-based authentication for your homelab using AT Protocol ide ## Deploy ```bash -git clone https://gitea.cloudforest-basilisk.ts.net/Arcnode.xyz/atauth.git +git clone https://github.com/Cache8063/atauth.git cd atauth cp gateway/.env.example .env ``` -- 2.51.2