Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

QUERES

A self-hosted, multi-tenant identity provider (IdP) with a built-in OpenID Connect / OAuth 2.0 authorization server. It owns login, MFA, RBAC, sessions, and token issuance for a whole platform of applications: it signs its own RS256 JWTs and publishes the corresponding public keys at /.well-known/jwks.json, so consumer services verify those tokens offline — with jose or passport-jwt + a JWKS provider — without sharing a database with it.

It is not a gateway or proxy sitting in front of some other auth system, and your traffic does not pass through it. Applications call it to authenticate a human or a machine client, get a token back, and verify that token themselves from then on.

A few internal identifiers still carry the project's earlier working name, auth-gateway — the admin panel's package (auth-gateway-web), the PostgreSQL database name, Docker service names, and the JWKS kid. That's intentional (some of those are baked into already-issued tokens), not leftover inconsistency.

Features

Authentication

  • Email + password, hashed with argon2id (@node-rs/argon2), with transparent rehash of legacy bcrypt hashes on successful login (ADR-0002), and password strength scoring via zxcvbn
  • Passwordless login: magic link (/api/auth/magic-link) and email OTP (/api/auth/email-otp/*)
  • Social SSO with Google and GitHub (Passport) — tenant-bound opaque state in Redis, provider tokens encrypted at rest with AES-256-GCM (ADR-0013/0014)
  • Email verification, password reset, single-use invitations, and tri-state registration policies per tenant (OPEN / INVITE_ONLY / CLOSED, ADR-0011)
  • Account lockout after 10 failed attempts (15 min), on top of a Redis-backed per-identity login limiter

MFA

  • TOTP (otplib) with secrets encrypted at rest (AES-256-GCM, ADR-0008) and replay protection in Redis
  • 8 single-use recovery codes, hashed, regenerable
  • Passkeys / WebAuthn (@simplewebauthn/server) usable at the login challenge
  • mfaRequired enforced per tenant

Tokens and sessions

  • RS256 access tokens; JWKS exposed at /.well-known/jwks.json with kid-based key rotation — current + previous key served simultaneously, driven by an operator runbook (make rotate-key, ADR-0025)
  • Refresh-token rotation with reuse detection: a token replayed outside the grace window revokes the whole token family and writes a TOKEN_REUSE_DETECTED audit event (ADR-0009/0010)
  • Immediate access-token revocation via a Redis denylist keyed by the stable sid claim (ADR-0003), so logout is selective and effective before expiry
  • Per-user session listing and revocation, plus scheduled cleanup of expired sessions

Multi-tenancy and authorization

  • A user can belong to many tenants with a different role in each
  • Roles + CASL permissions, system roles plus custom roles per tenant
  • PostgreSQL row-level security, enforced at the database through a dual-client setup: a BYPASSRLS client for the genuinely cross-tenant auth flows and an RLS-bound client for everything tenant-scoped (ADR-0007)
  • Cross-tenant super-admin endpoints under /api/admin/*

OIDC / OAuth 2.0

  • A full OIDC provider (node-oidc-provider) mounted at /oidc, discovery at /oidc/.well-known/openid-configuration, gated at runtime by OIDC_ENABLED (ADR-0012/0015)
  • authorization_code with PKCE required
  • client_credentials for machine-to-machine clients — the native OIDC grant plus a legacy POST /api/oauth/token endpoint kept alive during the migration window (ADR-0006/0023)
  • Per-tenant OIDC issuer resolved by domain (ADR-0019), with optional tenant vanity domains provisioned as Cloudflare custom hostnames

Operations

  • Event-driven audit log with a durable outbox table and a drain worker (ADR-0024)
  • Transactional email with per-tenant branding and per-tenant/locale copy overrides
  • /health (Terminus) with PostgreSQL and Redis checks
  • Rate limiting via @nestjs/throttler globally and per endpoint, plus Redis-backed limiters on login and the OIDC token endpoint

Stack

Layer Choice
API NestJS 11 (Express 5), TypeScript strict
Database PostgreSQL 16 + Prisma 7 (@prisma/adapter-pg), RLS policies
Cache / ephemeral state Redis 7 (ioredis)
OIDC node-oidc-provider 9
Frontends React 19 + Vite, Tailwind CSS 4
Tooling pnpm 10, Biome, Vitest 4, Node >= 24

Monorepo layout

.
├── src/            # NestJS API — the IdP itself, serves :3200
├── prisma/         # Prisma 7 schema, migrations, rls.sql, seed
├── apps/
│   ├── landing/    # @queres/landing — public site + end-user auth UI (login,
│   │               # register, MFA, passkeys, self-service tenant dashboard)
│   └── web/        # auth-gateway-web — admin panel (tenants, users, roles,
│                   # sessions, audit logs)
├── docs/           # architecture, API, integration, database, ADRs
├── docker/         # database init scripts
└── deploy/         # deployment scaffolding

Both frontends are independent SPAs — see apps/landing/README.md and apps/web/README.md. Neither is served by the Node container: the Dockerfile runtime only packages the NestJS dist/, and in a typical deployment a reverse proxy does path-based routing on one domain (/ → landing, /app/ → admin panel, /api/ → the API). docker compose up brings up PostgreSQL and Redis, not the frontends.

Quick start

Requires Node >= 24, pnpm >= 10, and Docker. Start from the example environment file (cp .env.example .env) and fill in at least the database URLs, TOTP_ENCRYPTION_KEY, and the Redis URL.

pnpm install && docker compose up -d
make generate-keys
DATABASE_URL="$DATABASE_URL_ADMIN" npx prisma migrate dev   # app_user has no CREATEDB → P3014
npx prisma db execute --file prisma/rls.sql && npx prisma db seed
pnpm run start:dev   # :3200

The two frontends run separately, each with its own Vite dev server proxying to the API on :3200:

pnpm --filter @queres/landing dev    # landing + end-user auth UI
pnpm --filter auth-gateway-web dev   # admin panel

Seed credentials: admin@auth-gateway.local / changeme.

In local development, email OTP / magic-link / invitation tokens never leave your machine — read them from the verification_tokens table or point SMTP_HOST at a local mailpit.

Tests

DATABASE_URL=postgres://x TOTP_ENCRYPTION_KEY=<64hex> pnpm test

Unit tests live next to the code. The test:integration, test:e2e, and test:security suites are separate Vitest configs and need a real PostgreSQL/Redis (docker compose up -d).

Documentation

Document What's in it
docs/ARCHITECTURE.md Module and guard chain, auth/refresh/reuse-detection flows, multi-tenant data model, RLS
docs/API.md REST endpoint reference with request/response shapes
docs/INTEGRATION.md + CONTRACT.md How a downstream app consumes this IdP: JWKS verification, JWT claims, the public contract
docs/DATABASE.md Schema and row-level security details
docs/adr/ Architecture decision records — the why behind the non-obvious choices
CLAUDE.md Living map of invariants and "don't do this" gotchas
ROADMAP.md Current status and backlog

Contributing

See CONTRIBUTING.md for dev setup, coding standards, test expectations, and the branch/PR flow. Auth, sessions, and tenant isolation are the core of what this service does, so changes there are held to a higher bar.

Found a security issue? Do not open a public issue or PR — follow SECURITY.md.

License

Copyright (C) 2026 soy-chrislo. Licensed under AGPL-3.0. Because of the AGPL's network-use clause (section 13), if you modify QUERES and run the modified version as a network service, you must offer its users the corresponding source of your modified version.

About

Self-hosted, multi-tenant identity provider (IdP) with a built-in OpenID Connect / OAuth 2.0 authorization server

Resources

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages