From 7297dc0616ac55ae93b3e7eca78724491fca4338 Mon Sep 17 00:00:00 2001 From: Frederic Cabassut Date: Mon, 9 Mar 2026 11:18:25 +0100 Subject: [PATCH] docs: add dedicated AUTH.md and deduplicate auth info across docs Create AUTH.md as the canonical reference for authentication: password auth, OAuth/SSO setup, provider-specific guides (Authentik, Keycloak, Okta/Auth0), env vars, callback URLs, security notes, and troubleshooting. Fixes incorrect env var names and callback URL patterns that had drifted in DEPLOYMENT.md. Other docs now cross-link to AUTH.md instead of duplicating content. Co-Authored-By: Claude Opus 4.6 --- CLAUDE.md | 1 + docs/ARCHITECTURE.md | 5 +- docs/AUTH.md | 209 +++++++++++++++++++++++++++++++++++++++++++ docs/DEPLOYMENT.md | 66 +------------- docs/FEATURES.md | 20 ++--- 5 files changed, 222 insertions(+), 79 deletions(-) create mode 100644 docs/AUTH.md diff --git a/CLAUDE.md b/CLAUDE.md index 5b304fd..b8767ea 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -215,5 +215,6 @@ Keep docs in `docs/` up to date when making changes. If a feature, API endpoint, |-----|--------| | `docs/FEATURES.md` | User-facing feature list | | `docs/ARCHITECTURE.md` | System design, data flow, schema, tech stack | +| `docs/AUTH.md` | Authentication setup, OAuth/SSO provider guides (Authentik, Keycloak, etc.) | | `docs/DEPLOYMENT.md` | Docker, env vars, reverse proxy, backups | | `docs/openapi.yaml` | API spec (run `pnpm docs:api` to regenerate `docs/API.md`) | diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 9898589..ec6fa50 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -83,9 +83,8 @@ For a single-user app with multiple devices: - New v4 uses Vite plugin (faster builds) ### Argon2 -- Current best practice for password hashing -- Memory-hard (resists GPU attacks) -- Winner of the Password Hashing Competition +- Memory-hard password hashing (resists GPU attacks) +- See [AUTH.md](AUTH.md) for authentication details ## Database Schema diff --git a/docs/AUTH.md b/docs/AUTH.md new file mode 100644 index 0000000..37ccfb8 --- /dev/null +++ b/docs/AUTH.md @@ -0,0 +1,209 @@ +# Authentication + +Crumbs supports password-based authentication and optional OAuth/SSO via Google, GitHub, or any OpenID Connect (OIDC) provider (e.g. Authentik, Keycloak, Okta, Auth0). + +## First-run setup + +On first launch, Crumbs presents a setup screen to create the initial admin account with email and password (minimum 8 characters). This account becomes the instance administrator. + +## Password authentication + +- Passwords are hashed with **Argon2** (argon2id variant) +- Sessions use httpOnly cookies with 30-day expiry +- Admins can reset passwords and revoke sessions for any user via Settings + +## OAuth / SSO + +OAuth providers are **auto-enabled** when their environment variables are set — no code changes needed. The login page dynamically shows buttons for each configured provider. + +### How it works + +1. User clicks an OAuth button on the login page +2. Crumbs redirects to the provider's authorization endpoint (with PKCE) +3. Provider authenticates the user and redirects back to Crumbs +4. Crumbs validates the response, extracts the user's email and name +5. If a matching user exists (by provider ID or email), a session is created +6. If no matching user exists, the login is rejected (invite-only model) + +### Invite-only model + +OAuth does **not** auto-create accounts. Users must be pre-created by an admin: + +1. Go to **Settings > Users > Invite User** +2. Create the user with the **same email** they use on the OAuth provider +3. The user can now log in via OAuth — their account is automatically linked on first sign-in + +After initial linking, the user is identified by their provider-specific ID (`sub` claim), so email changes on the provider side won't break login. + +### Environment variables + +#### Google OAuth + +| Variable | Description | +|----------|-------------| +| `AUTH_GOOGLE_CLIENT_ID` | Google OAuth 2.0 client ID | +| `AUTH_GOOGLE_CLIENT_SECRET` | Google OAuth 2.0 client secret | + +**Callback URL:** `https:///api/auth/oauth/google/callback` + +Setup: +1. Go to [Google Cloud Console](https://console.cloud.google.com/apis/credentials) +2. Create an OAuth 2.0 Client ID (Web application) +3. Add the callback URL above as an authorized redirect URI +4. Copy the client ID and secret into your environment + +#### GitHub OAuth + +| Variable | Description | +|----------|-------------| +| `AUTH_GITHUB_CLIENT_ID` | GitHub OAuth app client ID | +| `AUTH_GITHUB_CLIENT_SECRET` | GitHub OAuth app client secret | + +**Callback URL:** `https:///api/auth/oauth/github/callback` + +Setup: +1. Go to [GitHub Developer Settings](https://github.com/settings/developers) +2. Create a new OAuth App +3. Set the authorization callback URL to the callback URL above +4. Copy the client ID and secret into your environment + +#### Generic OIDC + +Any OpenID Connect-compliant provider works via the generic OIDC integration. Crumbs automatically discovers endpoints via the provider's `/.well-known/openid-configuration` document. + +| Variable | Description | +|----------|-------------| +| `AUTH_OIDC_ISSUER` | OIDC issuer URL (must serve `/.well-known/openid-configuration`) | +| `AUTH_OIDC_CLIENT_ID` | OIDC client ID | +| `AUTH_OIDC_CLIENT_SECRET` | OIDC client secret | +| `AUTH_OIDC_DISPLAY_NAME` | Button label on login page (default: `SSO`) | + +**Callback URL:** `https:///api/auth/oauth/oidc/callback` + +**Required scopes:** `openid`, `profile`, `email` + +### Provider guides + +#### Authentik + +[Authentik](https://goauthentik.io/) is a self-hosted identity provider that works with Crumbs via the generic OIDC integration. + +**1. Create a provider in Authentik:** + +1. Open the Authentik admin panel +2. Go to **Applications > Providers > Create** +3. Select **OAuth2/OpenID Connect** +4. Configure: + - **Name:** `Crumbs` + - **Authorization flow:** `default-provider-authorization-implicit-consent` (or `explicit-consent` to prompt users) + - **Client type:** `Confidential` + - **Redirect URIs:** `https:///api/auth/oauth/oidc/callback` + - **Scopes:** `openid`, `profile`, `email` +5. Save and note the **Client ID** and **Client Secret** + +**2. Create an application in Authentik:** + +1. Go to **Applications > Applications > Create** +2. Configure: + - **Name:** `Crumbs` + - **Slug:** `crumbs` + - **Provider:** Select the `Crumbs` provider you just created + - **Launch URL:** `https://` (optional, for Authentik's app dashboard) +3. Save + +**3. Configure Crumbs:** + +```yaml +# docker-compose.yml +services: + crumbs: + image: ghcr.io/bretzel-app/crumbs:latest + environment: + - ORIGIN=https://notes.example.com + - AUTH_OIDC_ISSUER=https://authentik.example.com/application/o/crumbs/ + - AUTH_OIDC_CLIENT_ID= + - AUTH_OIDC_CLIENT_SECRET= + - AUTH_OIDC_DISPLAY_NAME=Authentik +``` + +> **Note:** The issuer URL for Authentik follows the pattern `https:///application/o//`. This URL must serve a valid `/.well-known/openid-configuration` document — you can verify by visiting `https:///application/o/crumbs/.well-known/openid-configuration` in your browser. + +**4. Pre-create users:** + +In Crumbs, go to **Settings > Users > Invite User** and create accounts with emails matching your Authentik users. They can then log in via the "Authentik" button on the login page. + +#### Keycloak + +1. Create a new client in your Keycloak realm +2. Set **Client type** to `OpenID Connect`, **Client authentication** to `On` +3. Add the callback URL: `https:///api/auth/oauth/oidc/callback` +4. Set the issuer URL to `https:///realms/` + +```env +AUTH_OIDC_ISSUER=https://keycloak.example.com/realms/myrealm +AUTH_OIDC_CLIENT_ID=crumbs +AUTH_OIDC_CLIENT_SECRET= +AUTH_OIDC_DISPLAY_NAME=Keycloak +``` + +#### Okta / Auth0 + +Use the issuer URL from your Okta org or Auth0 tenant: + +```env +# Okta +AUTH_OIDC_ISSUER=https://your-org.okta.com + +# Auth0 +AUTH_OIDC_ISSUER=https://your-tenant.auth0.com +``` + +### Docker example with multiple providers + +```yaml +services: + crumbs: + image: ghcr.io/bretzel-app/crumbs:latest + ports: + - "3000:3000" + volumes: + - crumbs-data:/data + environment: + - ORIGIN=https://notes.example.com + # Google + - AUTH_GOOGLE_CLIENT_ID=123456789.apps.googleusercontent.com + - AUTH_GOOGLE_CLIENT_SECRET=GOCSPX-xxxxxxxxxxxxx + # GitHub + - AUTH_GITHUB_CLIENT_ID=Iv1.abc123 + - AUTH_GITHUB_CLIENT_SECRET=abc123secret + # Authentik (OIDC) + - AUTH_OIDC_ISSUER=https://authentik.example.com/application/o/crumbs/ + - AUTH_OIDC_CLIENT_ID=crumbs-client-id + - AUTH_OIDC_CLIENT_SECRET=crumbs-client-secret + - AUTH_OIDC_DISPLAY_NAME=Authentik + restart: unless-stopped + +volumes: + crumbs-data: +``` + +All configured providers will appear as buttons on the login page. + +## Security notes + +- All OAuth flows use **PKCE** (Proof Key for Code Exchange) with S256 challenge method +- OAuth state and code verifier are stored as httpOnly cookies (10-minute expiry) +- Passwords are hashed with **Argon2** (never stored in plain text) +- Sessions expire after **30 days** +- The `ORIGIN` environment variable must match your actual domain for CSRF protection +- Always use **HTTPS** in production (via reverse proxy) + +## Troubleshooting + +| Symptom | Cause | Fix | +|---------|-------|-----| +| OAuth button doesn't appear | Provider env vars not set or missing a required variable | Check all required env vars are set and restart | +| "No account found" after OAuth | User doesn't exist in Crumbs | Create the user via Settings with matching email | +| OIDC discovery fails | Issuer URL is wrong or unreachable | Verify `AUTH_OIDC_ISSUER` serves `/.well-known/openid-configuration` | +| Redirect URI mismatch | Callback URL in provider doesn't match `ORIGIN` | Ensure `ORIGIN` matches your domain and the callback URL uses the same origin | +| PKCE error | Provider doesn't support S256 PKCE | Most modern providers support it; check provider docs | diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index 7a9a10b..2265f33 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -110,48 +110,9 @@ node build | `PORT` | `3000` | HTTP port | | `NODE_ENV` | `development` | Set to `production` for deployment | -### OAuth Providers (optional) +### OAuth / SSO (optional) -Enable social login by setting the client ID and secret for any provider. Users who sign in via OAuth are created automatically (invite-only when combined with admin approval). - -| Variable | Description | -|----------|-------------| -| `AUTH_GOOGLE_ID` | Google OAuth client ID | -| `AUTH_GOOGLE_SECRET` | Google OAuth client secret | -| `AUTH_GITHUB_ID` | GitHub OAuth client ID | -| `AUTH_GITHUB_SECRET` | GitHub OAuth client secret | -| `AUTH_OIDC_ISSUER` | Generic OIDC issuer URL (e.g. `https://accounts.example.com`) | -| `AUTH_OIDC_ID` | OIDC client ID | -| `AUTH_OIDC_SECRET` | OIDC client secret | -| `AUTH_OIDC_NAME` | Display name for the OIDC button (default: "SSO") | - -**Callback URLs** — register these in your provider's dashboard: - -| Provider | Callback URL | -|----------|-------------| -| Google | `https://your-domain/api/auth/callback/google` | -| GitHub | `https://your-domain/api/auth/callback/github` | -| OIDC | `https://your-domain/api/auth/callback/oidc` | - -#### Docker example with Google OAuth - -```yaml -services: - crumbs: - image: ghcr.io/bretzel-app/crumbs:latest - ports: - - "3000:3000" - volumes: - - crumbs-data:/data - environment: - - ORIGIN=https://notes.example.com - - AUTH_GOOGLE_ID=123456789.apps.googleusercontent.com - - AUTH_GOOGLE_SECRET=GOCSPX-xxxxxxxxxxxxx - restart: unless-stopped - -volumes: - crumbs-data: -``` +Google, GitHub, and generic OIDC (Authentik, Keycloak, Okta, etc.) are supported. Providers are auto-enabled when their env vars are set. See **[AUTH.md](AUTH.md)** for environment variables, callback URLs, provider-specific setup guides, and troubleshooting. ## Health Check @@ -163,7 +124,7 @@ docker inspect --format='{{.State.Health.Status}}' crumbs ## MCP Server Configuration -Crumbs includes a built-in MCP (Model Context Protocol) server that allows AI assistants to interact with your notes. +Crumbs includes a built-in MCP (Model Context Protocol) server that allows AI assistants to interact with your notes. See [ARCHITECTURE.md](ARCHITECTURE.md#mcp-server) for technical details and the full tool list. ### Setup @@ -186,28 +147,9 @@ Crumbs includes a built-in MCP (Model Context Protocol) server that allows AI as } ``` -### Available Tools - -| Tool | Description | -|------|-------------| -| `list_notes` | List notes (filter by status/tag) | -| `get_note` | Get a note by ID | -| `create_note` | Create a new note | -| `update_note` | Update a note | -| `trash_note` / `restore_note` | Move to/from trash | -| `archive_note` / `unarchive_note` | Archive/unarchive | -| `delete_note` | Permanently delete | -| `search_notes` | Full-text search | -| `list_tags` | List all tags | -| `pin_note` | Pin/unpin a note | -| `reorder_notes` | Set sort orders | -| `upload_image` | Attach image from URL | - ## Security Notes - Always use HTTPS in production (via reverse proxy) -- Set a strong password (min 8 characters recommended) - The `ORIGIN` variable must match your actual domain for CSRF protection -- Sessions expire after 30 days -- Passwords are hashed with Argon2 - API keys are SHA-256 hashed (never stored in plain text) +- See [AUTH.md](AUTH.md#security-notes) for authentication security details (password hashing, session expiry, PKCE) diff --git a/docs/FEATURES.md b/docs/FEATURES.md index 0d0613b..4680d8e 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -86,20 +86,12 @@ - Clear search to restore original view - Case-insensitive matching -### Authentication -- Multi-user support with role-based access (admin / user) -- First-run setup: create admin account (min 8 chars password) -- Argon2 password hashing (industry standard) -- Session-based auth with 30-day expiry -- Active session management (view, revoke individual or all other sessions) -- All routes protected except /login and /setup -- Account deletion with password confirmation - -### OAuth / SSO -- Optional social login via Google, GitHub, or generic OIDC -- OAuth users created automatically on first sign-in -- Invite-only mode when combined with admin controls -- Configure via environment variables (`AUTH_GOOGLE_ID`, `AUTH_GITHUB_ID`, `AUTH_OIDC_ISSUER`, etc.) +### Authentication & OAuth / SSO +- Multi-user with role-based access (admin / user) +- Password auth (Argon2) + optional OAuth/SSO (Google, GitHub, any OIDC provider) +- Session-based auth with active session management +- Invite-only OAuth model with PKCE-secured flows +- See [AUTH.md](AUTH.md) for full details and provider setup guides ### User Management (admin) - Admin dashboard to create, list, and delete users