* feat(oobe): add conditional auth provider bootstrap step (GRO-392) Backend: - GET /api/setup/status now returns showAuthProviderStep, authConfigExists, and authEnvVarsSet to inform the frontend whether to show the step - POST /api/setup/auth-provider: unauthenticated endpoint for first-time auth provider configuration during OOBE; guarded by needsSetup check (returns 403 after setup completes); encrypts clientSecret before storing Frontend: - SetupWizard fetches /api/setup/status on mount to determine if the auth provider step is needed (fresh install with no DB config and no OIDC env vars) - When needed, inserts the Auth Provider step after Welcome, before Business Name; includes full form with Test Connection button - Endpoint is POST /api/admin/auth-provider/test for connection testing Co-Authored-By: Paperclip <noreply@paperclip.ing> * fix(oobe): add test connection endpoint and fix EOF newline (GRO-392) - Add POST /api/setup/auth-provider/test endpoint for OOBE test connection - Guard with same !superUser check as bootstrap endpoint - Update SetupWizard to call /api/setup/auth-provider/test instead of /api/admin/auth-provider/test (which requires auth session) - Add trailing newline at EOF in setup.ts Co-Authored-By: Paperclip <noreply@paperclip.ing> * fix(oobe): remove unused catch variable in setup.ts (GRO-392) Co-Authored-By: Paperclip <noreply@paperclip.ing> * feat(api): auth provider CRUD endpoints + test-connection (GRO-388) Implement admin API endpoints for managing auth provider configuration: - GET /api/admin/auth-provider — get current config (secret redacted) - PUT /api/admin/auth-provider — create or update provider config - POST /api/admin/auth-provider/test — validate via OIDC discovery endpoint - DELETE /api/admin/auth-provider — remove DB config (falls back to env vars) All endpoints are gated by requireSuperUser(). The clientSecret is AES-256-GCM encrypted before DB write and always redacted on return. Test-connection fetches /.well-known/openid-configuration and returns metadata on success or error detail on failure. Includes 16 unit tests covering all endpoints and error paths. Co-Authored-By: Paperclip <noreply@paperclip.ing> * fix(api): requireRoleOrSuperUser for /admin/* routes (GRO-412) Fix bug where super users granted via Staff UI were blocked from admin routes because requireRole("manager") checked role before isSuperUser. Changed to requireRoleOrSuperUser("manager") so super users bypass the manager-role check. Also adds 7 unit tests for requireRoleOrSuperUser middleware covering: manager access, super user bypass, non-super-user blocking, and multi-role scenarios. Co-Authored-By: Paperclip <noreply@paperclip.ing> * fix(api): remove unused decryptSecret import and eslint-disable directives Fixes lint error exposed by merge with main (GRO-392 PR #214) Co-Authored-By: Paperclip <noreply@paperclip.ing> * fix(tests): use main's authProvider tests after rebase conflict resolution The rebase introduced incompatible test code from the pre-merge GRO-388 commit. Replaced with the canonical test file from main to ensure tests pass and reflect the actual router implementation. Co-Authored-By: Paperclip <noreply@paperclip.ing> * fix(api): remove duplicate authProviderRouter import and route registration Rebase introduced duplicate import from ./routes/admin/authProvider.js and duplicate route registration. Removed duplicates since the correct import is from ./routes/authProvider.js. Co-Authored-By: Paperclip <noreply@paperclip.ing> * fix(e2e): use lean schema for OIDC test endpoint; add trailing newline Fix CTO review comments on GRO-392: - POST /api/setup/auth-provider/test now uses authProviderTestSchema (only issuerUrl + internalBaseUrl) instead of full authProviderBootstrapSchema — clientSecret is not needed for OIDC discovery and was not being sent by the frontend handler - POST /api/admin/auth-provider/test already uses omit() correctly; no change needed - apps/api/src/routes/admin/authProvider.ts: added trailing newline Co-Authored-By: Paperclip <noreply@paperclip.ing> * feat(web): add auth provider section to settings page (GRO-391) Add Authentication Provider section to /admin/settings for super users. Implements: provider ID, display name, issuer URL, internal base URL (optional, collapsed), client ID, client secret (masked, only sent on change), scopes fields; Test Connection button; Save and Reset to Environment Defaults with confirmation dialog; warning banner about service restart; env config info banner when no DB config is set. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(api): move needsSetup guard before Zod parsing in setup endpoints POST /api/setup/auth-provider and POST /api/setup/auth-provider/test were returning 400 (Zod validation) instead of 403 when needsSetup was false, because zValidator middleware ran before the route handler body. Now manually parse the body after the needsSetup guard so 403 fires immediately for post-setup requests. Co-Authored-By: Paperclip <noreply@paperclip.ing> * fix(api): replace c.req.valid("json") with await c.req.json() Replace zValidator-orphaned c.req.valid("json") calls with await c.req.json() in the auth provider bootstrap and test endpoints per CTO review. Co-Authored-By: Paperclip <noreply@paperclip.ing> --------- Co-authored-by: groombook-engineer[bot] <3141748+groombook-engineer[bot]@users.noreply.github.com> Co-authored-by: Paperclip <noreply@paperclip.ing> Co-authored-by: Barkley Trimsworth <noreply@groombook> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
GroomBook
The open-source scheduling and client management platform built specifically for independent pet groomers — giving you the tools of enterprise software without the enterprise price tag or vendor lock-in.
Built for groomers, not corporations.
Key Features
Stop chasing confirmations
- Customer portal — Clients confirm or cancel appointments on their own. Reduce no-shows with an automated waitlist.
Your calendar, your way
- iCal calendar feed — Push GroomBook appointments directly into Google Calendar or Apple Calendar. No app switching.
Know every pet at a glance
- Client & pet records — Detailed profiles with grooming history, preferences, and breed-specific notes. Full appointment notes for context on every regular.
- Quick-find search — Find clients and pets instantly without digging through spreadsheets.
Staff access without stress
- Role-based access control (RBAC) — Front desk sees bookings; only you see financials. Right access for every role.
Everything else
- Appointment scheduling — Calendar management for single or multiple groomers
- Service management — Pricing, duration, and service catalog
- POS & invoicing — Payments, tips, and receipt generation
- Automated reminders — SMS and email notifications
- Reporting dashboard — Revenue, utilization, and trend analytics
- Staff impersonation — Managers can view the customer portal as any client, with full audit logging and session controls
- PWA — Installable on mobile devices, works offline
🚀 Try the Demo
Live Demo — explore GroomBook without installing anything.
Quick Start
Docker Compose (recommended for indie groomers)
Run GroomBook on your own hardware in minutes. Everything you need is in the box — no subscription, no vendor lock-in.
git clone https://github.com/groombook/groombook.git
cd groombook
# Start everything (Postgres + database migrations + API + web UI)
docker compose up --build
- Web UI: http://localhost:8080
- API: http://localhost:3000
The default docker-compose.yml sets AUTH_DISABLED=true so you can explore the app without configuring an OIDC provider. Important: Disable this in any internet-facing deployment.
Tech Stack
| Layer | Technology |
|---|---|
| Backend | Hono (TypeScript, Node.js) |
| Frontend | React 19 + Vite + vite-plugin-pwa |
| Database | PostgreSQL via CNPG + Drizzle ORM |
| Auth | OIDC via Authentik |
| Infra | Kubernetes (namespace: groombook), Flux GitOps |
| CI | GitHub Actions (self-hosted groombook-runners) |
Repository Structure
groombook/
├── apps/
│ ├── api/ # Hono REST API
│ └── web/ # React PWA
├── packages/
│ ├── db/ # Drizzle schema + migrations
│ └── types/ # Shared TypeScript types
├── .github/
│ └── workflows/ # CI/CD pipelines
└── docker-compose.yml
Getting Started
Prerequisites
- Node.js >= 20
- pnpm >= 9 (
npm install -g pnpm) - Docker & Docker Compose (for local Postgres)
Local Development
# Clone the repo
git clone https://github.com/groombook/groombook.git
cd groombook
# Install dependencies
pnpm install
# Start local Postgres
docker compose up postgres -d
# Run database migrations
DATABASE_URL=postgres://groombook:groombook@localhost:5432/groombook pnpm db:migrate
# Start API and Web in parallel
pnpm dev
API will be available at http://localhost:3000 Web will be available at http://localhost:5173
Environment Variables
API (apps/api/.env)
DATABASE_URL=postgres://groombook:groombook@localhost:5432/groombook
OIDC_ISSUER=https://authentik.example.com
OIDC_AUDIENCE=groombook
CORS_ORIGIN=http://localhost:5173
PORT=3000
Running Tests
# Unit tests (vitest)
pnpm test
# E2E tests (Playwright) — requires the full Docker Compose stack to be running
docker compose up -d --wait
pnpm --filter @groombook/e2e test
# Open the Playwright UI (interactive test runner)
pnpm --filter @groombook/e2e test:ui
# View the last E2E test report
pnpm --filter @groombook/e2e test:report
E2E tests target the Docker Compose stack (http://localhost:8080). They use API route mocking where needed so happy-path tests are deterministic without requiring seed data.
Building
pnpm build
Self-Hosting
Production Configuration
Copy .env.example to .env and configure:
cp .env.example .env
Key variables to update for production:
| Variable | Description |
|---|---|
DATABASE_URL |
PostgreSQL connection string |
AUTH_DISABLED |
Set to false in production |
OIDC_ISSUER |
Authentik issuer URL |
OIDC_AUDIENCE |
OAuth2 audience (default: groombook) |
CORS_ORIGIN |
Public URL of the web frontend |
To use your .env file with Docker Compose:
docker compose --env-file .env up --build
Kubernetes (production-grade deployments)
See the groombook/infra repository for Kubernetes manifests and Flux configuration.
Groom Book is deployed in the groombook Kubernetes namespace using:
- CNPG for PostgreSQL
- Authentik for OIDC authentication
- Flux for GitOps-managed deployments
Contributing
GroomBook thrives on contributions from the grooming community. Whether you're a groomer with a feature request, a developer fixing a bug, or someone improving docs — we'd love your help.
- Fork the repository
- Create a feature branch (
git checkout -b feature/my-feature) - Commit your changes
- Open a pull request
All PRs require CI to pass before merge. See CONTRIBUTING.md for details.
Why GroomBook?
- Open source — You own your data. No vendor lock-in.
- Purpose-built — Features designed for grooming workflows, not generic scheduling.
- Self-hosted or managed — Run it yourself for free, or pay for hosted support (coming soon).
- Community-driven — Used and built by actual groomers.
License
AGPL-3.0