{"id":49072289,"url":"https://github.com/johnwmail/totp-gate","last_synced_at":"2026-04-20T08:04:07.011Z","repository":{"id":351023672,"uuid":"1209078607","full_name":"johnwmail/totp-gate","owner":"johnwmail","description":"Lightweight TOTP reverse proxy — add 2FA authentication in front of any web service.","archived":false,"fork":false,"pushed_at":"2026-04-13T10:01:01.000Z","size":20,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-13T10:27:29.372Z","etag":null,"topics":["2fa","auth-proxy","authentication","gateway","golang","middleware","otp","reverse-proxy","security","totp","two-factor-auth"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/johnwmail.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-13T04:26:54.000Z","updated_at":"2026-04-13T10:11:10.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/johnwmail/totp-gate","commit_stats":null,"previous_names":["johnwmail/totp-gate"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/johnwmail/totp-gate","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnwmail%2Ftotp-gate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnwmail%2Ftotp-gate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnwmail%2Ftotp-gate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnwmail%2Ftotp-gate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/johnwmail","download_url":"https://codeload.github.com/johnwmail/totp-gate/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnwmail%2Ftotp-gate/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32038456,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-20T00:18:06.643Z","status":"online","status_checked_at":"2026-04-20T02:00:06.527Z","response_time":94,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["2fa","auth-proxy","authentication","gateway","golang","middleware","otp","reverse-proxy","security","totp","two-factor-auth"],"created_at":"2026-04-20T08:04:05.162Z","updated_at":"2026-04-20T08:04:07.003Z","avatar_url":"https://github.com/johnwmail.png","language":"Go","readme":"# totp-gate\n\n[![Release](https://img.shields.io/github/v/release/johnwmail/totp-gate?sort=semver)](https://github.com/johnwmail/totp-gate/releases)\n\nA lightweight TOTP (Time-based One-Time Password) authentication gateway written in Go. It sits in front of any HTTP service, requiring users to enter a valid TOTP code before granting access.\n\n## Features\n\n- **TOTP Authentication** — RFC 6238 compliant, supports SHA1, SHA256, and SHA512\n- **Reverse Proxy** — forwards authenticated requests to an upstream service\n- **Session Management** — HMAC-signed cookies with sliding expiration and max lifetime\n- **Rate Limiting** — per-IP rate limiting to prevent brute-force attacks\n- **Secret Loading** — supports both file-based secrets (Docker secrets) and environment variables\n- **Graceful Shutdown** — handles SIGINT/SIGTERM with configurable timeout\n- **Zero Dependencies** — uses only Go standard library\n\n## Quick Start\n\n### Pre-built Binaries\n\nDownload pre-compiled binaries from the [GitHub Releases](https://github.com/johnwmail/totp-gate/releases) page. Binaries are available for `linux/amd64` and `linux/arm64`. Each release includes SHA256 checksums for verification.\n\n```bash\n# Download and verify\ncurl -LO \"https://github.com/johnwmail/totp-gate/releases/latest/download/totp-gate-linux-amd64\"\ncurl -LO \"https://github.com/johnwmail/totp-gate/releases/latest/download/totp-gate-linux-amd64.sha256\"\nsha256sum -c totp-gate-linux-amd64.sha256\nchmod +x totp-gate-linux-amd64\n\n# Run\nTOTPGATE_TOTP_SECRET=\"JBSWY3DPEHPK3PXP\" ./totp-gate-linux-amd64\n```\n\n### Build from Source\n\n```bash\ngo build -ldflags \"-s -w -X main.Version=v1.0.0\" -o totp-gate .\nTOTPGATE_TOTP_SECRET=\"JBSWY3DPEHPK3PXP\" ./totp-gate\n```\n\nAccess the gateway at `http://localhost:8080`. It will proxy requests to the upstream service after successful TOTP authentication.\n\n## Configuration\n\nAll configuration is done via environment variables:\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `TOTPGATE_AUTH_LISTEN` | `0.0.0.0:8080` | Address to listen on (port or ip:port) |\n| `TOTPGATE_UPSTREAM` | `http://localhost:3000` | Upstream service URL to proxy to |\n| `TOTPGATE_TARGETS` | *(empty)* | Multi-target routing: `host=upstream,host/path-prefix=upstream,default=upstream`. Overrides `TOTPGATE_UPSTREAM`. |\n| `TOTPGATE_AUTH_DISABLED` | `false` | Disable authentication (bypass mode) |\n| `TOTPGATE_TOTP_SECRET` | *(required)* | Base32-encoded TOTP secret (fallback) |\n| `TOTPGATE_TOTP_SECRET_FILE` | `/run/secrets/totpgate_totp_secret` | Path to file containing TOTP secret |\n| `TOTPGATE_TOTP_PERIOD` | `30` | TOTP time step in seconds |\n| `TOTPGATE_TOTP_DIGITS` | `6` | Number of digits in TOTP code |\n| `TOTPGATE_TOTP_ALGORITHM` | `SHA1` | Hash algorithm: `SHA1`, `SHA256`, `SHA512` |\n| `TOTPGATE_AUTH_COOKIE_TTL` | `86400` | Max session lifetime in seconds (24h) |\n| `TOTPGATE_AUTH_COOKIE_SECURE` | `true` | Set `Secure` flag on cookies (set `false` for local dev/test with HTTP-only) |\n| `TOTPGATE_AUTH_REFRESH_INTERVAL` | `600` | Activity refresh interval in seconds (10m) |\n| `TOTPGATE_TRUSTED_PROXIES` | *(see below)* | Comma-separated trusted proxy IPs or CIDRs for forwarded-header trust |\n| `TOTPGATE_INSECURE_SKIP_VERIFY` | `false` | Skip TLS certificate verification for `https://` upstream targets (dev/testing only) |\n\n### Secret Priority\n\nThe TOTP secret is loaded in this order:\n1. File specified by `TOTPGATE_TOTP_SECRET_FILE` (default: `/run/secrets/totpgate_totp_secret`)\n2. Environment variable `TOTPGATE_TOTP_SECRET`\n\nUsing file-based secrets is recommended for production, especially with Docker secrets.\n\n### Trusted Proxies\n\nThe `X-Real-IP` and `X-Forwarded-For` headers are only trusted when the immediate peer matches a configured trusted proxy.\n\n- **Default** (env var not set): `127.0.0.0/8`, `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`\n- **When set**: specified values + `127.0.0.1` (always included)\n- **Examples**:\n  - Cloudflare: `TOTPGATE_TRUSTED_PROXIES=\"173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,104.16.0.0/13,104.24.0.0/14,172.64.0.0/13,131.0.72.0/22\"`\n  - Local nginx: use defaults (nginx on `127.0.0.1` is already trusted)\n\nIf the request comes from an untrusted peer (e.g., direct internet connection), these headers are ignored and `r.RemoteAddr` is used.\n\n### Upstream TLS Verification\n\nWhen routing to `https://` upstream targets, totp-gate validates TLS certificates against the system CA store by default. For internal services with self-signed or private CA certificates, set:\n\n```bash\nTOTPGATE_INSECURE_SKIP_VERIFY=true\n```\n\n⚠️ **Warning**: Only use this in development or trusted networks. In production, mount your CA certificate into the container and ensure the system trust store includes it.\n\n### Multi-Target Routing\n\nWhen `TOTPGATE_TARGETS` is set, the gateway routes requests to different upstream services based on the `Host` header and optionally the request path prefix.\n\n#### Host-only routing\n\n```bash\nTOTPGATE_TARGETS=\"app1.example.com=http://localhost:3000,app2.example.com=http://localhost:4000\" ./totp-gate\n```\n\n#### Path-prefix routing\n\nRoute different paths on the same host to different backends:\n\n```bash\nTOTPGATE_TARGETS=\"app.example.com/api=http://api:8080,app.example.com/static=http://cdn:9000,app.example.com=http://web:3000\" ./totp-gate\n```\n\n#### Explicit default fallback\n\nUse the special `default` key to route unmatched requests to a fallback upstream:\n\n```bash\nTOTPGATE_TARGETS=\"app.example.com/api=http://api:8080,default=http://fallback:3000\" ./totp-gate\n```\n\n**Key format:**\n\n| Key | Matches |\n|-----|---------|\n| `host` | All requests on that host |\n| `host/path-prefix` | Requests on that host whose path starts with the prefix |\n| `default` | All requests not matched by any other rule |\n\n**Routing rules:**\n\n- The port is stripped from the `Host` header before matching (e.g., `app.example.com:8080` → `app.example.com`).\n- More specific (longer) rules take precedence — `/api/v2` wins over `/api`, which wins over a host-only entry.\n- `/apifoo` does **not** match the prefix `/api` — the next character after the prefix must be `/` or end-of-string.\n- Duplicate keys (same host+prefix) cause a startup error.\n- If no rule matches and no `default` is set, the request returns **HTTP 404**.\n- WebSocket upgrades are fully supported and routed to the correct backend.\n- `TOTPGATE_UPSTREAM` is ignored when `TOTPGATE_TARGETS` is set.\n\n**Docker Compose example:**\n\n```yaml\nservices:\n  totp-gate:\n    image: johnwmail/totp-gate:latest\n    ports:\n      - \"8080:8080\"\n    environment:\n      TOTPGATE_TARGETS: \"app.example.com/api=http://api:8080,app.example.com=http://web:3000,default=http://fallback:9000\"\n    secrets:\n      - totpgate_totp_secret\nsecrets:\n  totpgate_totp_secret:\n    file: ./secret.txt\n```\n\n## Architecture\n\n### Single Target (default)\n\n```\nClient → totp-gate (:8080) → Upstream Service (:3000)\n              ↑\n         TOTP Gate\n```\n\n### Multi-Target (via TOTPGATE_TARGETS)\n\n```\nClient → totp-gate (:8080) ──Host: app.example.com/api──→ API Service (:8080)\n              ↑               └──Host: app.example.com/static──→ CDN (:9000)\n         TOTP Gate             └──Host: app.example.com──→ Web Service (:3000)\n                               └──(no match)──→ 404 (or default fallback)\n```\n\n1. User accesses the gateway\n2. If no valid session cookie exists, user is redirected to `/totp-gate/login`\n3. User enters their TOTP code\n4. On success, a signed session cookie is set and user is redirected to the upstream service\n5. Subsequent requests validate the cookie and proxy to upstream\n\n### Session Management\n\n- **Max Lifetime**: Sessions expire after `TOTPGATE_AUTH_COOKIE_TTL` (default 24h) from login time\n- **Activity Refresh**: Cookie activity is refreshed every `TOTPGATE_AUTH_REFRESH_INTERVAL` (default 10m) of activity\n- **Security**: Cookies are HMAC-signed with a key derived from the TOTP secret + random nonce (regenerated on each restart, invalidating all sessions)\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `/health` | Health check, returns `OK` |\n| `/totp-gate/login` | TOTP login page (GET) and submission (POST) |\n| `/` | Authenticated reverse proxy |\n\n## Rate Limiting\n\nBuilt-in per-IP rate limiting: **5 attempts per minute**. Exceeding this returns HTTP 429.\n\n## Docker Usage\n\n```dockerfile\nFROM golang:1.26-alpine AS builder\nWORKDIR /app\nCOPY go.mod .\nRUN go mod download\nCOPY . .\nRUN go build -o totp-gate .\n\nFROM alpine:latest\nCOPY --from=builder /app/totp-gate /totp-gate\nEXPOSE 8080\nCMD [\"/totp-gate\"]\n```\n\nRun with Docker secrets:\n\n```bash\necho \"JBSWY3DPEHPK3PXP\" | docker secret create totpgate_totp_secret -\ndocker service create \\\n  --name totp-gate \\\n  --secret totpgate_totp_secret \\\n  -p 8080:8080 \\\n  -e TOTPGATE_UPSTREAM=http://myapp:3000 \\\n  totp-gate\n```\n\nOr with multi-target routing (path-prefix example):\n\n```bash\ndocker run -d \\\n  --name totp-gate \\\n  -p 8080:8080 \\\n  -e TOTPGATE_TARGETS=\"app.example.com/api=http://api:8080,app.example.com=http://web:3000\" \\\n  -e TOTPGATE_TOTP_SECRET=\"JBSWY3DPEHPK3PXP\" \\\n  johnwmail/totp-gate:latest\n```\n\n## License\n\nMIT License — see [LICENSE](LICENSE)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohnwmail%2Ftotp-gate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjohnwmail%2Ftotp-gate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohnwmail%2Ftotp-gate/lists"}