AtAuth
7

Configure Feed

Select the types of activity you want to include in your feed.

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 <noreply@anthropic.com>

Bryan Brooks (Mar 9, 2026, 11:45 AM -0500) 70cf65ea fb3360a1

+74 -252
+38
.github/workflows/ci.yml
··· 1 + name: CI 2 + 3 + on: 4 + push: 5 + branches: [main] 6 + paths-ignore: 7 + - '*.md' 8 + - 'docs/**' 9 + pull_request: 10 + branches: [main] 11 + 12 + jobs: 13 + test: 14 + name: Test 15 + runs-on: ubuntu-latest 16 + defaults: 17 + run: 18 + working-directory: gateway 19 + steps: 20 + - name: Checkout code 21 + uses: actions/checkout@v4 22 + 23 + - name: Setup Node.js 24 + uses: actions/setup-node@v4 25 + with: 26 + node-version: '20' 27 + 28 + - name: Install dependencies 29 + run: npm ci 30 + 31 + - name: Type check 32 + run: npm run typecheck 33 + 34 + - name: Lint 35 + run: npm run lint 36 + 37 + - name: Run tests 38 + run: npm run test:run
+35 -9
CHANGELOG.md
··· 5 5 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 6 6 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 7 8 + ## [2.3.0] - 2026-03-09 9 + 10 + ### Added 11 + 12 + - Passkey login support for forward-auth proxy flow (previously only available in OIDC flow) 13 + - New endpoint `POST /auth/proxy/passkey` for WebAuthn authentication in proxy-auth 14 + - Passkey button and WebAuthn JavaScript on proxy login page (feature-detected) 15 + - GitHub Actions CI workflow for the FOSS repository 16 + 17 + ### Security 18 + 19 + - MFA verify endpoints (`/auth/mfa/totp/verify`, `/auth/mfa/backup-codes/verify`) now require authentication; DID comes from session, not request body 20 + - Session expiry enforced at the database query level (`getSession()` checks `expires_at`) 21 + - OIDC logout redirect validation uses origin comparison instead of prefix matching (prevents open redirect) 22 + - PKCE `plain` method rejected; only `S256` accepted with constant-time comparison 23 + - Empty DID guard in OIDC token endpoint prevents minting tokens for unauthenticated authorization codes 24 + - Handle format validation in OIDC authorize endpoint 25 + - `trust proxy` configured for correct client IP behind reverse proxies 26 + - `user_id` NaN validation in auth link endpoint 27 + 28 + ### Changed 29 + 30 + - Removed `uuid` package dependency (replaced with `crypto.randomUUID()`) 31 + - Test suite expanded to 404 tests 32 + 8 33 ## [2.2.0] - 2026-02-24 9 34 10 35 ### Added ··· 208 233 - Input validation and sanitization 209 234 - Rate limiting on all endpoints 210 235 211 - [2.2.0]: https://gitea.cloudforest-basilisk.ts.net/Arcnode.xyz/atauth/compare/v2.1.0...v2.2.0 212 - [2.1.0]: https://gitea.cloudforest-basilisk.ts.net/Arcnode.xyz/atauth/compare/v2.0.3...v2.1.0 213 - [2.0.3]: https://gitea.cloudforest-basilisk.ts.net/Arcnode.xyz/atauth/compare/v2.0.2...v2.0.3 214 - [2.0.2]: https://gitea.cloudforest-basilisk.ts.net/Arcnode.xyz/atauth/compare/v2.0.1...v2.0.2 215 - [2.0.1]: https://gitea.cloudforest-basilisk.ts.net/Arcnode.xyz/atauth/compare/v2.0.0...v2.0.1 216 - [2.0.0]: https://gitea.cloudforest-basilisk.ts.net/Arcnode.xyz/atauth/compare/v1.3.0...v2.0.0 217 - [1.3.0]: https://gitea.cloudforest-basilisk.ts.net/Arcnode.xyz/atauth/compare/v1.2.0...v1.3.0 218 - [1.2.0]: https://gitea.cloudforest-basilisk.ts.net/Arcnode.xyz/atauth/compare/v1.0.0...v1.2.0 219 - [1.0.0]: https://gitea.cloudforest-basilisk.ts.net/Arcnode.xyz/atauth/releases/tag/v1.0.0 236 + [2.3.0]: https://github.com/Cache8063/atauth/compare/v2.2.0...v2.3.0 237 + [2.2.0]: https://github.com/Cache8063/atauth/compare/v2.1.0...v2.2.0 238 + [2.1.0]: https://github.com/Cache8063/atauth/compare/v2.0.3...v2.1.0 239 + [2.0.3]: https://github.com/Cache8063/atauth/compare/v2.0.2...v2.0.3 240 + [2.0.2]: https://github.com/Cache8063/atauth/compare/v2.0.1...v2.0.2 241 + [2.0.1]: https://github.com/Cache8063/atauth/compare/v2.0.0...v2.0.1 242 + [2.0.0]: https://github.com/Cache8063/atauth/compare/v1.3.0...v2.0.0 243 + [1.3.0]: https://github.com/Cache8063/atauth/compare/v1.2.0...v1.3.0 244 + [1.2.0]: https://github.com/Cache8063/atauth/compare/v1.0.0...v1.2.0 245 + [1.0.0]: https://github.com/Cache8063/atauth/releases/tag/v1.0.0
-242
docs/DO-HANDOFF.md
··· 1 - # ATAuth - DigitalOcean Kubernetes Handoff 2 - 3 - This document provides everything you need to connect to and manage the ATAuth deployment on DigitalOcean managed Kubernetes. 4 - 5 - ## Cluster Overview 6 - 7 - | Item | Value | 8 - |------|-------| 9 - | Provider | DigitalOcean Managed Kubernetes | 10 - | Cluster | `do-nyc1-storm-dr-cluster` (nyc1 region) | 11 - | Kubernetes | v1.34.1 | 12 - | Namespace | `atauth` | 13 - | Nodes | 3x worker (4 vCPU, 8GB RAM each) | 14 - | Container registry | `registry.digitalocean.com/ghostmesh-registry` | 15 - 16 - ## Connecting with kubectl 17 - 18 - 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. 19 - 20 - ### Setup 21 - 22 - ```bash 23 - # Option 1: Set KUBECONFIG env var 24 - export KUBECONFIG=/path/to/atauth-team-kubeconfig.yaml 25 - 26 - # Option 2: Merge into your default config 27 - cp ~/.kube/config ~/.kube/config.backup 28 - KUBECONFIG=~/.kube/config:/path/to/atauth-team-kubeconfig.yaml kubectl config view --merge --flatten > /tmp/merged && mv /tmp/merged ~/.kube/config 29 - kubectl config use-context atauth-team@do-nyc1-storm-dr-cluster 30 - ``` 31 - 32 - ### Verify 33 - 34 - ```bash 35 - kubectl get pods -n atauth 36 - # Should show the atauth pod running 37 - 38 - kubectl get deployments -n atauth 39 - # NAME READY UP-TO-DATE AVAILABLE 40 - # atauth 1/1 1 1 41 - ``` 42 - 43 - ## What You Have Access To 44 - 45 - Your ServiceAccount (`atauth-team`) has full CRUD access within the `atauth` namespace: 46 - 47 - - Pods (including logs, exec, port-forward) 48 - - Deployments and ReplicaSets 49 - - Services 50 - - Ingresses 51 - - ConfigMaps 52 - - Secrets 53 - - PersistentVolumeClaims 54 - - Events 55 - 56 - You do **not** have access to cluster-scoped resources or other namespaces. 57 - 58 - ## Current Deployment Architecture 59 - 60 - ``` 61 - ┌──────────────────────┐ 62 - Internet ──> nginx │ Ingress (nginx) │ 63 - ingress ──┤ apricot.workingtitle │ 64 - │ .zip │ 65 - └──────────┬───────────┘ 66 - 67 - ┌──────────▼───────────┐ 68 - │ Service (ClusterIP) │ 69 - │ atauth:3100 │ 70 - └──────────┬───────────┘ 71 - 72 - ┌──────────▼───────────┐ 73 - │ Deployment │ 74 - │ 1 replica, Recreate │ 75 - │ strategy │ 76 - └──────────┬───────────┘ 77 - 78 - ┌──────────▼───────────┐ 79 - │ PVC (1Gi, RWO) │ 80 - │ do-block-storage │ 81 - │ SQLite DB at │ 82 - │ /app/data/gateway.db │ 83 - └──────────────────────┘ 84 - ``` 85 - 86 - ### Key Resources 87 - 88 - | Resource | Name | Notes | 89 - |----------|------|-------| 90 - | Deployment | `atauth` | 1 replica, `Recreate` strategy (required -- RWO PVC) | 91 - | Service | `atauth` | ClusterIP, port 3100 | 92 - | Ingress | `atauth` | Host: `apricot.workingtitle.zip`, TLS via `atauth-tls` secret | 93 - | PVC | `atauth-data` | 1Gi, `do-block-storage`, holds SQLite DB | 94 - | ConfigMap | `atauth-config` | Non-sensitive env vars (CORS, OIDC issuer, WebAuthn, etc.) | 95 - | Secret | `atauth-secrets` | `ATAUTH_ADMIN_TOKEN`, `ATAUTH_OIDC_KEY_SECRET`, `ATAUTH_FORWARD_AUTH_SESSION_SECRET` | 96 - | Secret | `atauth-tls` | TLS cert/key for the ingress | 97 - | Secret | `registry-ghostmesh-registry` | Docker registry pull credentials | 98 - 99 - ### Container Image 100 - 101 - - Registry: `registry.digitalocean.com/ghostmesh-registry/atauth` 102 - - Tags: short git SHA (e.g., `43d2471`) + `latest` 103 - - Platform: `linux/amd64` (required -- DO nodes are amd64) 104 - - Dockerfile: `gateway/Dockerfile` 105 - 106 - ### Environment Variables 107 - 108 - Sourced from `atauth-config` ConfigMap: 109 - 110 - | Env Var | ConfigMap Key | Description | 111 - |---------|---------------|-------------| 112 - | `OAUTH_CLIENT_ID` | `ATAUTH_OAUTH_CLIENT_ID` | AT Protocol OAuth client metadata URL | 113 - | `OAUTH_REDIRECT_URI` | `ATAUTH_OAUTH_REDIRECT_URI` | OAuth callback URL | 114 - | `CORS_ORIGINS` | `ATAUTH_CORS_ORIGINS` | Comma-separated allowed origins | 115 - | `OIDC_ENABLED` | `ATAUTH_OIDC_ENABLED` | Enable OIDC provider | 116 - | `OIDC_ISSUER` | `ATAUTH_OIDC_ISSUER` | OIDC issuer URL | 117 - | `FORWARD_AUTH_ENABLED` | `ATAUTH_FORWARD_AUTH_ENABLED` | Enable nginx forward-auth proxy | 118 - | `FORWARD_AUTH_SESSION_TTL` | `ATAUTH_FORWARD_AUTH_SESSION_TTL` | Session TTL in seconds | 119 - | `FORWARD_AUTH_PROXY_COOKIE_TTL` | `ATAUTH_FORWARD_AUTH_PROXY_COOKIE_TTL` | Proxy cookie TTL in seconds | 120 - | `MFA_ENABLED` | `ATAUTH_MFA_ENABLED` | Enable passkey/WebAuthn MFA | 121 - | `WEBAUTHN_RP_ID` | `ATAUTH_WEBAUTHN_RP_ID` | WebAuthn relying party ID | 122 - | `WEBAUTHN_ORIGIN` | `ATAUTH_WEBAUTHN_ORIGIN` | WebAuthn origin URL | 123 - | `WEBAUTHN_RP_NAME` | `ATAUTH_WEBAUTHN_RP_NAME` | WebAuthn relying party display name | 124 - 125 - Sourced from `atauth-secrets` Secret: 126 - 127 - | Env Var | Secret Key | Description | 128 - |---------|------------|-------------| 129 - | `ADMIN_TOKEN` | `ATAUTH_ADMIN_TOKEN` | Admin API bearer token | 130 - | `OIDC_KEY_SECRET` | `ATAUTH_OIDC_KEY_SECRET` | OIDC signing key secret | 131 - | `FORWARD_AUTH_SESSION_SECRET` | `ATAUTH_FORWARD_AUTH_SESSION_SECRET` | Session encryption secret | 132 - 133 - Hardcoded in deployment spec: 134 - 135 - | Env Var | Value | 136 - |---------|-------| 137 - | `PORT` | `3100` | 138 - | `HOST` | `0.0.0.0` | 139 - | `NODE_ENV` | `production` | 140 - | `DB_PATH` | `/app/data/gateway.db` | 141 - 142 - ## Common Operations 143 - 144 - ### View logs 145 - 146 - ```bash 147 - kubectl logs -n atauth deployment/atauth -f 148 - ``` 149 - 150 - ### Restart the deployment 151 - 152 - ```bash 153 - kubectl rollout restart deployment/atauth -n atauth 154 - kubectl rollout status deployment/atauth -n atauth 155 - ``` 156 - 157 - ### Deploy a new image 158 - 159 - ```bash 160 - kubectl set image deployment/atauth atauth=registry.digitalocean.com/ghostmesh-registry/atauth:<tag> -n atauth 161 - kubectl rollout status deployment/atauth -n atauth --timeout=120s 162 - ``` 163 - 164 - ### Update config 165 - 166 - ```bash 167 - # Edit a ConfigMap value 168 - kubectl patch configmap atauth-config -n atauth --type merge \ 169 - -p '{"data":{"ATAUTH_CORS_ORIGINS":"https://apricot.workingtitle.zip,https://new-app.example.com"}}' 170 - 171 - # Restart to pick up changes (env vars are read at startup) 172 - kubectl rollout restart deployment/atauth -n atauth 173 - ``` 174 - 175 - ### Update secrets 176 - 177 - ```bash 178 - # Update a secret value (must be base64 encoded) 179 - kubectl patch secret atauth-secrets -n atauth --type merge \ 180 - -p '{"data":{"ATAUTH_ADMIN_TOKEN":"'$(echo -n "new-token-value" | base64)'"}}' 181 - 182 - kubectl rollout restart deployment/atauth -n atauth 183 - ``` 184 - 185 - ### Port-forward for local access 186 - 187 - ```bash 188 - kubectl port-forward -n atauth svc/atauth 3100:3100 189 - # ATAuth is now accessible at http://localhost:3100 190 - ``` 191 - 192 - ### Shell into the container 193 - 194 - ```bash 195 - kubectl exec -it -n atauth deployment/atauth -- /bin/sh 196 - ``` 197 - 198 - ### Check health 199 - 200 - ```bash 201 - # Via port-forward 202 - curl http://localhost:3100/health 203 - 204 - # Or check readiness probe status 205 - kubectl describe pod -n atauth -l app=atauth | grep -A5 "Conditions:" 206 - ``` 207 - 208 - ## CI/CD Pipeline 209 - 210 - The existing Gitea Actions workflow (`.gitea/workflows/deploy.yml`) runs on push to `main`: 211 - 212 - 1. **Test job** (`ubuntu-latest` runner): `npm ci`, typecheck, lint, vitest 213 - 2. **Build and deploy job** (`host` runner): Docker build, push to DO registry, `kubectl set image`, rollout status 214 - 215 - The pipeline uses these Gitea org-level secrets: 216 - - `DO_REGISTRY_TOKEN` -- DO container registry API token 217 - - `DO_KUBECONFIG` -- base64-encoded kubeconfig for the `ci-deployer` ServiceAccount 218 - 219 - ## Important Gotchas 220 - 221 - 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. 222 - 223 - 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. 224 - 225 - 3. **Docker images must target `linux/amd64`.** The DO worker nodes run amd64. ARM images will crash with exec format errors. 226 - 227 - 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. 228 - 229 - 5. **`req.accepts('json')` matches `*/*`.** Use `req.is('json')` for Content-Type checks in Express routes. 230 - 231 - 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. 232 - 233 - 7. **SQLite WAL mode.** The DB runs in WAL mode on DO block storage. Single-writer only -- do not scale beyond 1 replica. 234 - 235 - ## Live URLs 236 - 237 - | URL | Description | 238 - |-----|-------------| 239 - | `https://apricot.workingtitle.zip` | ATAuth gateway (public) | 240 - | `https://apricot.workingtitle.zip/.well-known/openid-configuration` | OIDC discovery | 241 - | `https://apricot.workingtitle.zip/admin/login` | Admin dashboard | 242 - | `https://apricot.workingtitle.zip/health` | Health check endpoint |
+1 -1
docs/HOMELAB.md
··· 12 12 ## Deploy 13 13 14 14 ```bash 15 - git clone https://gitea.cloudforest-basilisk.ts.net/Arcnode.xyz/atauth.git 15 + git clone https://github.com/Cache8063/atauth.git 16 16 cd atauth 17 17 cp gateway/.env.example .env 18 18 ```