{"id":48753345,"url":"https://github.com/circle-rd/auth-service","last_synced_at":"2026-05-02T02:11:22.290Z","repository":{"id":350935289,"uuid":"1194851144","full_name":"circle-rd/auth-service","owner":"circle-rd","description":"The centralized 0Auth service embedded in docker infrastructure","archived":false,"fork":false,"pushed_at":"2026-04-30T10:49:55.000Z","size":688,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-30T12:16:55.114Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/circle-rd.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"ROADMAP.md","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-03-28T22:28:06.000Z","updated_at":"2026-04-30T10:49:21.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/circle-rd/auth-service","commit_stats":null,"previous_names":["circle-rd/auth-service"],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/circle-rd/auth-service","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/circle-rd%2Fauth-service","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/circle-rd%2Fauth-service/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/circle-rd%2Fauth-service/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/circle-rd%2Fauth-service/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/circle-rd","download_url":"https://codeload.github.com/circle-rd/auth-service/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/circle-rd%2Fauth-service/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32520155,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-02T01:12:54.858Z","status":"online","status_checked_at":"2026-05-02T02:00:05.923Z","response_time":132,"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":[],"created_at":"2026-04-12T22:20:56.075Z","updated_at":"2026-05-02T02:11:22.280Z","avatar_url":"https://github.com/circle-rd.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# auth-service\n\nSelf-hosted OAuth 2.1 / OIDC identity provider built on [BetterAuth](https://better-auth.com) v1.5.6.\n\nFeatures: email/password auth, MFA (TOTP + YubiKey FIDO2), multi-app RBAC, subscription plans with feature flags, consumption tracking, **organization management** (multi-tenant ready), and an embedded Vue 3 admin SPA.\n\n---\n\n## Stack\n\n| Layer         | Tech                                       |\n| ------------- | ------------------------------------------ |\n| Runtime       | Node.js 22, TypeScript (ESM)               |\n| Web framework | Fastify 5                                  |\n| Auth engine   | BetterAuth + `@better-auth/oauth-provider` |\n| ORM           | Drizzle ORM + `postgres` driver            |\n| Database      | PostgreSQL 17                              |\n| Frontend      | Vue 3 + Vite + Tailwind CSS v4             |\n| Tests         | Vitest + Supertest                         |\n| Container     | Docker (multi-stage build)                 |\n\n---\n\n## Project structure\n\n```\nauth-service/\n├── src/\n│   ├── index.ts              # Fastify server entry point\n│   ├── auth.ts               # BetterAuth configuration \u0026 plugins\n│   ├── config.ts             # Zod-validated env config\n│   ├── errors.ts             # ApiError class + error codes\n│   ├── bootstrap.ts          # Superadmin auto-creation at startup\n│   ├── migrate.ts            # Programmatic Drizzle migrations\n│   ├── db/\n│   │   ├── index.ts          # Drizzle + postgres connection\n│   │   └── schema.ts         # All custom table definitions\n│   ├── services/\n│   │   └── claims.ts         # OIDC custom claims builder (RBAC + features)\n│   └── routes/\n│       ├── health.ts         # GET /health\n│       ├── consumption.ts    # Consumption tracking API\n│       └── admin/\n│           ├── applications.ts\n│           ├── organizations.ts   # Organization CRUD + member \u0026 invitation management\n│           ├── roles.ts\n│           ├── plans.ts\n│           └── users.ts\n├── frontend/                 # Vue 3 SPA (built to frontend-dist/)\n│   └── src/\n│       ├── views/            # Login, register, profile, consent, admin pages\n│       ├── stores/           # Pinia auth store\n│       ├── router/           # Vue Router with auth guards\n│       └── locales/          # i18n (en, fr)\n├── drizzle/                  # Generated SQL migrations\n├── Dockerfile                # Multi-stage build\n├── docker-compose.yml        # Production\n├── docker-compose.dev.yml    # Dev (postgres only + hot-reload server)\n└── .env.example\n```\n\n---\n\n## Development setup\n\n### Prerequisites\n\n- Node.js 22+\n- pnpm 9+\n- Docker + Docker Compose\n\n### 1. Environment\n\n```sh\ncp .env.example .env\n# Edit .env — at minimum set BETTER_AUTH_SECRET and BETTER_AUTH_URL\nopenssl rand -base64 32   # use this as BETTER_AUTH_SECRET\n```\n\n### 2. Start the database\n\n```sh\ndocker compose -f docker-compose.dev.yml up -d postgres\n```\n\nThe postgres container is exposed on **port 5433** (to avoid conflicts with a local postgres on 5432).\n\n### 3. Push the schema\n\n```sh\npnpm install\npnpm db:push\n```\n\n`db:push` syncs the Drizzle schema directly to the DB without generating migration files — ideal for development.\n\n### 4. Start the backend\n\n```sh\npnpm dev      # tsx watch — hot-reload on file changes\n```\n\nServer: \u003chttp://localhost:3001\u003e\n\n### 5. Start the frontend (optional, for SPA development)\n\n```sh\ncd frontend\npnpm install\npnpm dev      # Vite dev server with HMR\n```\n\nFrontend: \u003chttp://localhost:5173\u003e (proxied to the backend at 3001)\n\n\u003e For quick iteration you can also just use the backend on port 3001 — the Vue SPA is served from `frontend-dist/` when built.\n\n---\n\n## API routes\n\n| Method             | Path                                        | Description                                              |\n| ------------------ | ------------------------------------------- | -------------------------------------------------------- |\n| `GET`              | `/health`                                   | Health check                                             |\n| `ALL`              | `/api/auth/*`                               | BetterAuth handler (login, register, OAuth2, OIDC, MFA…) |\n| `GET/POST`         | `/api/admin/applications`                   | List / create applications                               |\n| `GET/PATCH/DELETE` | `/api/admin/applications/:id`               | Application CRUD                                         |\n| `POST`             | `/api/admin/applications/:id/rotate-secret` | Rotate client secret                                     |\n| `GET/POST/DELETE`  | `/api/admin/applications/:id/roles`         | Per-app role management                                  |\n| `GET/POST/DELETE`  | `/api/admin/applications/:id/permissions`   | Per-app permission management                            |\n| `GET/POST/DELETE`  | `/api/admin/applications/:id/plans`         | Subscription plan management                             |\n| `GET/PATCH`        | `/api/admin/users`                          | User list / update                                       |\n| `POST`             | `/api/admin/users/:id/disable`              | Ban user                                                 |\n| `POST`             | `/api/admin/users/:id/enable`               | Unban user                                               |\n| `GET/POST`         | `/api/admin/organizations`                  | List / create organizations                              |\n| `GET/DELETE`       | `/api/admin/organizations/:id`              | Get / delete an organization                             |\n| `GET/POST`         | `/api/admin/organizations/:id/members`      | List members / add a member directly                     |\n| `DELETE`           | `/api/admin/organizations/:id/members/:uid` | Remove a member                                          |\n| `PATCH`            | `/api/admin/organizations/:id/members/:mid/role` | Update a member's role                              |\n| `GET/POST`         | `/api/admin/organizations/:id/invitations`  | List invitations / send an invitation by email           |\n| `DELETE`           | `/api/admin/organizations/:id/invitations/:iid` | Cancel a pending invitation                         |\n| `POST`             | `/api/consumption`                          | Record consumption (client_credentials token)            |\n| `GET`              | `/api/consumption/:userId/:appId`           | Get aggregates for user+app                              |\n| `DELETE`           | `/api/consumption/:userId/:appId/:key`      | Reset a counter                                          |\n\n### OIDC / OAuth2 endpoints (via BetterAuth)\n\n| Path                                         | Description            |\n| -------------------------------------------- | ---------------------- |\n| `/api/auth/oauth2/authorize`                 | Authorization endpoint |\n| `/api/auth/oauth2/token`                     | Token endpoint         |\n| `/api/auth/oauth2/userinfo`                  | UserInfo endpoint      |\n| `/api/auth/.well-known/openid-configuration` | Discovery document     |\n| `/api/auth/jwks`                             | JSON Web Key Set       |\n\n---\n\n## Organization management\n\nauth-service includes first-class support for **organizations** (companies / tenants) powered by the BetterAuth `organization()` plugin.\n\n### Purpose\n\nThe organization model is designed for a **two-tier distribution chain**:\n\n| Tier | Actor | Role |\n|------|-------|------|\n| 1 | **Reseller / distributor** | Creates and owns organizations representing their end-clients |\n| 2 | **Integrator** | Is a member of one or more organizations; manages IT resources scoped to those organizations |\n\nEach organization has a unique `slug` (used as its stable identifier) and optional `logo` + `metadata`. Members are assigned one of three roles: `owner`, `admin`, or `member`.\n\n### The `org_id` claim\n\nWhen a client requests the `org` scope during an OAuth2 authorization flow, the access token issued by auth-service contains an `org_id` claim set to the user's active organization ID (`session.activeOrganizationId`). Backend services can use this claim to:\n\n- Scope database queries to the organization's data\n- Enforce CASL (`@lagarde-cyber/acl`) abilities with `{ org_id }` conditions\n- Isolate resources between tenants\n\n### Admin operations\n\nOnly users with role `admin` or `superadmin` can create organizations. Member management (add, remove, change role) and invitation flows (invite by email, cancel) are also admin-only operations accessible via the `/api/admin/organizations/*` routes.\n\nRegular users can belong to multiple organizations as members but cannot create them.\n\n---\n\n## Database migrations\n\n```sh\n# Generate migration files from schema changes\npnpm db:generate\n\n# Apply migrations (production-style)\npnpm db:migrate\n\n# Push schema directly (dev only — no migration files)\npnpm db:push\n```\n\nIn production (Docker), migrations run automatically at container startup via `runMigrations()` in `src/migrate.ts`.\n\n---\n\n## Production deployment\n\n### Build \u0026 run with Docker Compose\n\n```sh\n# Fill in production values in .env (never commit this file)\ndocker compose build\ndocker compose up -d\n```\n\nThe service is exposed on the port defined by `PORT` (default 3001).  \nAdd a reverse proxy (Traefik, nginx, Caddy) in front of it for TLS termination.\n\n### Environment variables reference\n\n| Variable             | Required | Description                                       |\n| -------------------- | -------- | ------------------------------------------------- |\n| `PORT`               | no       | Server port (default: 3001)                       |\n| `HOST`               | no       | Bind address (default: 0.0.0.0)                   |\n| `BETTER_AUTH_SECRET` | **yes**  | 32-byte random secret — `openssl rand -base64 32` |\n| `BETTER_AUTH_URL`    | **yes**  | Public base URL, e.g. `https://auth.example.com`  |\n| `DATABASE_URL`       | **yes**  | Postgres connection string                        |\n| `POSTGRES_USER`      | **yes**  | Postgres user (also used by compose)              |\n| `POSTGRES_PASSWORD`  | **yes**  | Postgres password                                 |\n| `POSTGRES_DB`        | **yes**  | Database name                                     |\n| `POSTGRES_PORT`      | no       | Host port for postgres (default: 5433 in dev)     |\n| `ADMIN_EMAIL`        | no       | Superadmin email — created at first boot          |\n| `ADMIN_PASSWORD`     | no       | Superadmin password                               |\n| `CORS_ORIGINS`       | no       | Comma-separated allowed origins                   |\n| `APP_NAME`           | no       | Display name in UI + TOTP issuer (default: `CIRCLE Auth`) |\n| `APP_LOGO_URL`       | no       | Logo URL for UI + favicon (falls back to default) |\n| `SMTP_HOST`          | no       | SMTP server (email features disabled if empty)    |\n| `SMTP_PORT`          | no       | SMTP port (default: 587)                          |\n| `SMTP_USER`          | no       | SMTP username                                     |\n| `SMTP_PASS`          | no       | SMTP password                                     |\n| `SMTP_FROM`          | no       | From address for outgoing emails                  |\n| `NODE_ENV`           | no       | `development` or `production`                     |\n\n---\n\n## Tests\n\n```sh\npnpm test          # Run all tests once\npnpm test:watch    # Watch mode\n```\n\nTests use Vitest with a `node` environment. Integration tests requiring a live DB are separated in `vitest.integration.config.ts`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcircle-rd%2Fauth-service","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcircle-rd%2Fauth-service","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcircle-rd%2Fauth-service/lists"}