clickclack/docs/features/auth.md
2026-05-08 09:45:07 +01:00

133 lines
4.6 KiB
Markdown

---
read_when:
- changing how requests are authenticated
- touching magic-link, GitHub OAuth, or session cookie code
- adding a new auth provider or revising the bootstrap flow
---
# Auth
ClickClack accepts four ways to identify a caller, in order of precedence. The
resolver lives in `apps/api/internal/httpapi/server.go` (`currentUser`).
1. `Authorization: Bearer <token>` — bearer session token.
2. `cc_session` cookie — HTTP-only session cookie set by magic-link consume and
GitHub OAuth callback.
3. `X-ClickClack-User: usr_...` header — explicit user impersonation for local
development and tests.
4. Dev fallback — the very first user in the database. Enabled by default by
`clickclack serve --dev-bootstrap=true` so a fresh checkout boots into a
working app without any token plumbing.
The dev fallback is the one to disable in any non-local deployment. Set
`--dev-bootstrap=false` and require real sessions.
## Local owner bootstrap
`clickclack serve` calls `Store.EnsureBootstrap` when `--dev-bootstrap` is on.
That helper:
- Returns the first user if one exists.
- Otherwise creates a `Local Captain` user, a `ClickClack` workspace, and a
`general` channel, then returns the new user.
To pin the owner identity instead, run the CLI before serving:
```sh
clickclack admin bootstrap --name "Peter" --email steipete@gmail.com
```
That prints the new `usr_...` ID. Pass it back via `X-ClickClack-User` or use
the magic-link flow to mint a session.
## Magic links
Magic-link tokens are short-lived bearer credentials. They can be created over
HTTP or from the CLI; the consume endpoint exchanges them for a durable
session.
```http
POST /api/auth/magic/request
{ "email": "steipete@gmail.com", "display_name": "Peter" }
POST /api/auth/magic/consume
{ "token": "<token>" }
```
Or from the CLI, which is the V0 delivery path:
```sh
clickclack admin magic-link create --email steipete@gmail.com --name "Peter"
```
The client CLI can consume that token directly:
```sh
clickclack login --magic-token mgt_...
```
For remote agents and bots, use the resulting bearer session token. The CLI
will not send a stored bearer token to a different `--server`, and it skips
stored bearer tokens when `--user` / `CLICKCLACK_USER_ID` is set without an
explicit `--token`.
`ConsumeMagicLink` returns `{user, session, token}` and sets `cc_session` as an
HTTP-only cookie. Browsers can drop the body; bots should hold the
`session.token` for the `Authorization` header.
## GitHub OAuth (optional)
GitHub OAuth is opt-in. Set all three env vars (or the equivalent config keys)
before serving:
```sh
CLICKCLACK_PUBLIC_URL=https://chat.example.com
CLICKCLACK_GITHUB_CLIENT_ID=...
CLICKCLACK_GITHUB_CLIENT_SECRET=...
CLICKCLACK_GITHUB_ALLOWED_ORG=openclaw
```
Without those, `GET /api/auth/github/start` returns `501`.
Flow:
1. `GET /api/auth/github/start` sets a state cookie and redirects to GitHub.
2. GitHub redirects back to `GET /api/auth/github/callback?code&state`.
3. The handler exchanges the code, fetches `/user` and primary `/user/emails`,
checks org membership when `CLICKCLACK_GITHUB_ALLOWED_ORG` is set, upserts a
user keyed by `(provider="github", provider_subject=<github id>)`, creates a
session, sets `cc_session`, redirects to `/`.
The redirect URL is derived from `CLICKCLACK_PUBLIC_URL` when set, otherwise
from the request scheme/host. Configure GitHub with `<public-url>/api/auth/github/callback`.
Org-gated deployments request `read:org`. GitHub only returns private org
membership after the user grants that scope, so OpenClaw-only hosting should set
`CLICKCLACK_GITHUB_ALLOWED_ORG=openclaw` and `CLICKCLACK_DEV_BOOTSTRAP=false`.
When the org check passes, the user is automatically joined to the first
workspace; if no workspace exists yet, ClickClack creates a default workspace
with a `general` channel.
## Authorization
Every store mutation that touches a workspace runs `requireMembership` (or the
in-tx variant). API handlers do not duplicate that check — trust the store
layer for it. WebSocket subscriptions revalidate `GetWorkspace` before
upgrading.
Roles today are limited to `owner` and `member`, used only by the bootstrap
helper. There is no role enforcement on writes yet beyond membership.
## Sessions
`sessions` are bearer tokens with an `expires_at`. `GetSessionUser` resolves the
token to a `User`. There is no refresh flow — issue a new session when one
expires.
## What is intentionally missing
- Email/password login.
- Password reset.
- SMTP delivery for magic links (V0 prints the token; V1 will add delivery).
- Per-channel ACLs, role-based permissions, audit logs.