{"id":39906904,"url":"https://github.com/puzed/darkauth","last_synced_at":"2026-05-30T22:01:49.643Z","repository":{"id":313071272,"uuid":"1049941804","full_name":"puzed/darkauth","owner":"puzed","description":"A zero-knowledge authentication system with OIDC compatibility","archived":false,"fork":false,"pushed_at":"2026-05-23T20:20:27.000Z","size":28863,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-23T22:22:42.825Z","etag":null,"topics":["oauth","oidc","opaque","rfc-9380","zero-knowledge"],"latest_commit_sha":null,"homepage":"https://darkauth.com","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/puzed.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":"AGENTS.md","dco":null,"cla":null}},"created_at":"2025-09-03T18:06:50.000Z","updated_at":"2026-05-23T20:08:29.000Z","dependencies_parsed_at":"2025-09-03T19:41:06.150Z","dependency_job_id":"7966d1e7-4812-46a1-8825-3c0a26187070","html_url":"https://github.com/puzed/darkauth","commit_stats":null,"previous_names":["puzed/darkauth"],"tags_count":46,"template":false,"template_full_name":null,"purl":"pkg:github/puzed/darkauth","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/puzed%2Fdarkauth","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/puzed%2Fdarkauth/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/puzed%2Fdarkauth/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/puzed%2Fdarkauth/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/puzed","download_url":"https://codeload.github.com/puzed/darkauth/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/puzed%2Fdarkauth/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33670211,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-29T02:00:06.066Z","response_time":107,"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":["oauth","oidc","opaque","rfc-9380","zero-knowledge"],"created_at":"2026-01-18T16:00:24.679Z","updated_at":"2026-05-30T22:01:49.637Z","avatar_url":"https://github.com/puzed.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# DarkAuth\n\n![DarkAuth screenshot](logos/hero.png)\n\nA zero-knowledge authentication system with OIDC compatibility. DarkAuth implements OPAQUE (RFC 9380) for password authentication where the server never learns the password, and provides optional zero-knowledge delivery of Data Root Keys (DRK) to trusted clients.\n\nDarkAuth is open source and self-hosted. There is no paid plan, subscription, or cloud service. A ready-to-run Docker image is available at `ghcr.io/puzed/darkauth:latest`.\n\n## Features\n\n- **Zero-Knowledge Password Auth**: OPAQUE protocol ensures passwords never reach the server\n- **OIDC Compatible**: Standard OAuth 2.0/OpenID Connect for universal compatibility\n- **Zero-Knowledge DRK Delivery**: Optional fragment-based JWE delivery for trusted clients\n- **TOTP MFA**: Time-based one-time passwords for users and admins with backup codes, rate limits, and per-organization enforcement\n- **Email Password Reset**: SMTP-gated self-service reset links with hashed one-time tokens and session invalidation\n- **Database-Backed Configuration**: Most settings stored in PostgreSQL; minimal `config.yaml` for bootstrap\n- **Two-Port Architecture**: Separate ports for user (9080) and admin (9081). First-run installer is served on the admin port until setup completes.\n- **Secure Key Storage**: Optional encryption of private keys at rest using Argon2id-derived KEK\n- **RBAC Support**: Fine-grained permissions and organization-scoped roles for users\n- **Production Ready**: CSP headers, rate limiting, session management\n\n## Quick Start\n\n### Run with Docker\n\n```bash\ndocker run -d -p 9080:9080 -p 9081:9081 ghcr.io/puzed/darkauth:latest\n```\n\nThen visit `http://localhost:9081` to complete installation.\n\n### Prerequisites\n\n- Node.js 24+\n- PostgreSQL 15+ (or use embedded PGLite)\n- Docker \u0026 Docker Compose (optional, for PostgreSQL or non-Docker setups)\n\n### Installation Options\n\nDarkAuth supports multiple database options:\n\n1. **Remote PostgreSQL** - Connect to an existing PostgreSQL instance\n2. **Embedded PGLite** - Use the built-in PGLite database (no external dependencies)\n\n### 1. Start PostgreSQL (if using remote PostgreSQL)\n\nUsing Docker Compose:\n```bash\ndocker-compose up -d\n```\n\nOr use an existing PostgreSQL instance and configure the `postgresUri` in `config.yaml`.\n\n### 2. Install Dependencies\n\n```bash\nnpm install\n```\n\n### 3. Run Database Migrations\n\n```bash\nnpm run db:push\n```\n\n### 4. Configuration\n\nCreate a `config.yaml` file in the project root:\n\n```yaml\n# Database configuration (choose one)\n# Option 1: Remote PostgreSQL\ndbMode: remote\npostgresUri: postgresql://username:password@localhost:5432/darkauth\n\n# Option 2: Embedded PGLite\n# dbMode: pglite\n# pgliteDir: ./data/pglite\n\n# Server ports\nuserPort: 9080\nadminPort: 9081\n\n# UI proxy (development only)\nproxyUi: false\n\n# Key encryption passphrase (required for secure mode)\nkekPassphrase: \"your-strong-passphrase\"\n```\n\n### 5. Initial Setup\n\n#### Option A: Interactive Web Installer (Recommended)\n\n```bash\nnpm start\n```\n\nVisit the installation URL shown in the console (includes a one-time token).\n\nThe installer will guide you through:\n1. Database selection (PostgreSQL or PGLite)\n2. KEK passphrase setup\n3. Admin user creation\n\n#### Option B: CLI Installation\n\n```bash\nnpm run install:script\n```\n\n### 6. Start the Server\n\n```bash\nnpm start\n```\n\n### 7. Access the System\n\n- **User/OIDC**: http://localhost:9080 (or configured `userPort`)\n- **Admin Panel**: http://localhost:9081 (or configured `adminPort`)\n- **OIDC Discovery**: http://localhost:9080/api/.well-known/openid-configuration\n\n## Development Mode\n\nRun with hot-reloading and Vite dev servers:\n\n```bash\n# Set proxyUi: true in config.yaml for proxied development\nnpm run dev\n```\n\nThis runs all three services concurrently:\n- API server with hot-reloading\n- User UI on Vite dev server\n- Admin UI on Vite dev server\n\n## Architecture\n\n### Port Allocation\n\n- **9080**: User-facing OIDC/Auth endpoints and UI\n- **9081**: Admin interface and API (also serves the installation wizard until setup completes)\n\n### Project Structure\n\nDarkAuth is organized as a monorepo with npm workspaces:\n\n```\npackages/\n├── api/           # Main server and API\n├── user-ui/       # User-facing React application\n├── admin-ui/      # Admin panel React application  \n├── test-suite/    # Playwright end-to-end tests\n└── ...\n```\n\n### URL Structure\n\n- `/api/*`: Backend API endpoints\n- `/`: React UI applications\n\n### Database Schema\n\nAll configuration and state stored in PostgreSQL:\n\n- **settings**: System configuration\n- **jwks**: Signing keys (EdDSA/Ed25519)\n- **clients**: OAuth/OIDC client registrations\n- **users**: User accounts\n- **opaque_records**: OPAQUE authentication data\n- **wrapped_root_keys**: Encrypted DRK storage\n- **auth_codes**: Authorization codes\n- **sessions**: Active sessions\n- **password_reset_tokens**: HMAC-hashed, single-use email password reset tokens\n- **otp_configs / otp_backup_codes**: OTP configuration and backup codes\n- **pending_auth**: In-progress auth requests\n- **admin_users**: Admin accounts (separate cohort)\n- **permissions/groups**: RBAC configuration\n- **audit_logs**: Audit trail of system events\n\n## Default Clients\n\nTwo clients are created during installation:\n\n### demo-public-client (Public Client)\n- **Type**: Public\n- **PKCE**: Required\n- **ZK Delivery**: fragment-jwe (enabled)\n- **Redirect URIs**: \n  - http://localhost:9092/\n  - http://localhost:9092/callback\n  - http://localhost:3000/\n  - http://localhost:3000/callback\n  - https://app.example.com/\n  - https://app.example.com/callback\n\n### demo-confidential-client (Confidential Client)\n- **Type**: Confidential\n- **Auth Method**: client_secret_basic\n- **Grant Types**: authorization_code, refresh_token, client_credentials\n- **ZK Delivery**: None\n- **Redirect URIs**: \n  - http://localhost:4000/callback\n  - https://support.example.com/callback\n\n## Security Modes\n\n- Private keys are always encrypted at rest using KEK\n- KEK derived from passphrase using Argon2id  \n- Passphrase configured in `config.yaml`\n- KDF parameters stored in database\n\n## Configuration\n\nAll configuration is managed via `config.yaml`. Create this file in the project root:\n\n```yaml\n# Database configuration (required)\ndbMode: remote | pglite\npostgresUri: postgresql://username:password@localhost:5432/darkauth  # For remote mode\npgliteDir: ./data/pglite  # For embedded mode\n\n# Server ports (optional, with defaults)\nuserPort: 9080            # User/OIDC server port\nadminPort: 9081           # Admin server port  \nproxyUi: false            # Proxy to Vite dev servers (development only)\n\n# Security (required for secure mode)\nkekPassphrase: \"your-strong-passphrase\"\n\n# Optional (with defaults)\npublicOrigin: \"http://localhost:9080\"  # Public-facing origin\nissuer: \"http://localhost:9080\"        # OIDC issuer URL\nrpId: \"localhost\"         # Relying party identifier\n```\n\n## API Endpoints\n\n### OIDC Discovery\n- `GET /api/.well-known/openid-configuration`\n- `GET /api/.well-known/jwks.json`\n\n### Authorization\n- `GET /api/authorize` - OAuth authorization endpoint\n- `POST /api/authorize/finalize` - Complete authorization (internal)\n- `POST /api/token` - Token exchange endpoint\n- `GET/POST /api/userinfo` - OIDC UserInfo endpoint for bearer access tokens\n- `POST /api/introspect` - OAuth token introspection for confidential clients\n- `POST /api/revoke` - OAuth refresh token revocation\n\nAuthorization codes are short-lived and single-use. Redemption at the token endpoint is enforced atomically so concurrent redemption attempts cannot both succeed.\n\n### OPAQUE Authentication\n- `POST /api/opaque/register/start`\n- `POST /api/opaque/register/finish`\n- `POST /api/opaque/login/start`\n- `POST /api/opaque/login/finish`\n\n### Email Password Reset\n- `POST /api/password/reset/request` - Request reset email with generic anti-enumeration response\n- `GET /api/password/reset/token?token=...` - Validate reset link and return only masked email\n- `POST /api/password/reset/start` - Start OPAQUE reset registration with reset token\n- `POST /api/password/reset/finish` - Finish reset, consume token, replace password record, and revoke sessions\n\n### OTP (TOTP) — User\n- `POST /api/otp/setup/init`\n- `POST /api/otp/setup/verify`\n- `GET /api/otp/status`\n- `POST /api/otp/verify`\n\n### OTP (TOTP) — Admin\n- `POST /api/admin/otp/setup/init`\n- `POST /api/admin/otp/setup/verify`\n- `GET /api/admin/otp/status`\n- `POST /api/admin/otp/verify`\n\n### DRK Management\n- `GET /api/crypto/wrapped-drk` - Retrieve wrapped DRK\n- `PUT /api/crypto/wrapped-drk` - Store wrapped DRK\n\n### Session\n- `GET /api/session` - Current session info\n- `POST /api/logout` - End session\n\nRefresh tokens are stored hashed at rest and rotated as single-use credentials. Rotation is enforced atomically so concurrent redemption attempts cannot both succeed.\n\nWhen OTP is enabled and required by the user organization policy (`organizations.force_otp=true`), login creates a partial session with `data.otp_required=true`. After successful OTP verification, the session includes `data.otp_verified=true`. AMR includes `otp` and ACR is `urn:ietf:params:acr:mfa`.\n\n### Admin API (Port 9081)\n- `/api/admin/users` - User management\n- `/api/admin/clients` - Client management\n- `/api/admin/settings` - System settings\n- `/api/admin/jwks` - Key management\n\n## Changelog\n\n- Markdown entries live in `changelog/` as `vX.Y.Z.md`\n- Changelog JSON is published to `https://release.darkauth.com/changelog.json`\n\n## Zero-Knowledge Flow\n\n### Registration\n1. Client generates OPAQUE registration request\n2. Server stores opaque envelope (never sees password)\n3. Client derives stable `export_key` from password\n4. Client generates random DRK (32 bytes)\n5. DRK wrapped with key derived from `export_key`\n6. Wrapped DRK stored on server\n\n### Login with ZK Delivery\n1. Client includes ephemeral ECDH public key in `/authorize`\n2. OPAQUE login produces `export_key`\n3. Client unwraps DRK using `export_key`\n4. DRK encrypted to app's ephemeral key (JWE)\n5. JWE delivered via URL fragment (never hits server)\n6. App verifies hash binding and decrypts DRK\n\n### Email Password Reset and Encrypted Data\n\nEmail reset restores account access by creating a new OPAQUE password record. It does not decrypt\nDRK-wrapped data that depends on the old password-derived export key. After reset, users may need to\nuse the existing old-password recovery flow or generate new keys.\n\n## Building for Production\n\n```bash\n# Build all packages\nnpm run build\n\n# Type checking\nnpm run typecheck\n\n# Linting\nnpm run lint\n\n# Code formatting\nnpm run format\n\n# Start production server (ensure config.yaml is configured)\nnpm start\n```\n\n## Testing\n\nThe project uses Playwright for end-to-end testing:\n\n```bash\n# Install Playwright browsers (first time only)\nnpm run test:install\n\n# Run all tests\nnpm test\n\n# Run with detailed output\nnpm run test:report\n\n# Run in headed mode (with browser UI)\nnpm run test:headed\n\n# Debug tests\nnpm run test:debug\n```\n\n## Security Considerations\n\n1. **Always use HTTPS in production** - Cookies are marked Secure\n2. **Strong KEK passphrase** - Use 32+ characters for production in `config.yaml`\n3. **Secure config.yaml** - Protect the config file as it contains the KEK passphrase\n4. **Database security** - Encrypt PostgreSQL connections and storage\n5. **CSP Headers** - Strict Content Security Policy enforced\n6. **Rate limiting** - Configurable per endpoint\n7. **Session security** - Short-lived sessions with CSRF protection\n8. **OTP hardening** - Secrets encrypted with KEK, backup codes hashed with Argon2, anti-replay via timestep tracking, cohort/organization enforcement, AMR/ACR reflect MFA\n9. **Password reset** - Enable only with working SMTP; reset requests use generic responses, HMAC-hashed one-time tokens, rate limits, audit events, and post-reset session revocation\n\n## Support\n\nFor issues and feature requests, please use the GitHub issue tracker.\n\n## License\n\n- Core and most packages are licensed under AGPL-3.0. See the root `LICENSE` and individual package LICENSE files.\n- `packages/demo-app` and `packages/darkauth-client` are licensed under MIT.\n- `packages/opaque-ts` is licensed under BSD-3-Clause.\n\nRefer to each package's `package.json` and `LICENSE` file for the authoritative license.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpuzed%2Fdarkauth","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpuzed%2Fdarkauth","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpuzed%2Fdarkauth/lists"}