# Knowtation Hub REST API + OAuth (Google/GitHub) + JWT for the Knowtation vault and proposals. Phase 11. Run self-hosted (Node or Docker) or use the same API contract on ICP. ## Run locally (from repo root) 1. Install hub dependencies: `cd hub && npm install && cd ..` 2. Set env: `KNOWTATION_VAULT_PATH` (required), `HUB_JWT_SECRET` (required in production), optional OAuth and port (see below). 3. Start: `npm run hub` or `node hub/server.mjs` Default port: 3333. **Open `http://localhost:3333/` in a browser** for the Rich Hub UI (served by the same process). Health: `GET http://localhost:3333/health`. API base: `http://localhost:3333/api/v1` (all routes require `Authorization: Bearer ` except health, static UI, and auth). ## Environment | Variable | Required | Description | |----------|----------|-------------| | `KNOWTATION_VAULT_PATH` | Yes | Absolute path to vault directory (or set in config/local.yaml). | | `HUB_JWT_SECRET` | Yes (prod) | Secret for signing JWTs. Use a long random string in production. | | `HUB_PORT` | No | Port (default 3333). | | `HUB_BASE_URL` | No | Base URL for OAuth callbacks (default http://localhost:3333). | | `HUB_UI_ORIGIN` | No | Redirect after login (defaults to Hub base URL, same tab). | | `GOOGLE_CLIENT_ID` | For Google login | From Google Cloud Console. | | `GOOGLE_CLIENT_SECRET` | For Google login | From Google Cloud Console. | | `GITHUB_CLIENT_ID` | For GitHub login | From GitHub OAuth App. | | `GITHUB_CLIENT_SECRET` | For GitHub login | From GitHub OAuth App. | | `KNOWTATION_HUB_PROPOSAL_ENRICH` | No | Set to `1` to enable `POST /api/v1/proposals/:id/enrich` (LLM summary + suggested labels; needs OpenAI or Ollama chat). | ## Log in (OAuth) — required for the Hub UI There is **no separate “Sign up”**. Identity is **Google or GitHub**: the first time you sign in, that account is tied to your session; there is no email/password form on Knowtation. **Until OAuth is configured, Log in will not work.** Add at least one provider to `.env`, restart the Hub, then use **Continue with Google** or **Continue with GitHub**. ### Google 1. [Google Cloud Console](https://console.cloud.google.com/) → APIs & Services → **Credentials** → **Create credentials** → **OAuth client ID** (configure consent screen if asked). Application type: **Web application**. 2. **Authorized redirect URIs** must match **exactly** what the server sends (scheme, host, port, path). There are two stacks: - **This server** (`npm run hub`, port **3333** by default): `http://localhost:3333/api/v1/auth/callback/google` - **Gateway** (hosted OAuth; see [hub/gateway/README.md](./gateway/README.md)): path is **`/auth/callback/google`**, not `/api/v1/...`. Example local gateway on port 3340: `http://localhost:3340/auth/callback/google` If you run the gateway on port **3333**, set **`HUB_BASE_URL=http://localhost:3333`** and add **`http://localhost:3333/auth/callback/google`** to Google. Production gateway: `https://YOUR-GATEWAY-HOST/auth/callback/google`. You can add **multiple** redirect URIs to one OAuth client (local full Hub + local gateway + production). 3. Copy **Client ID** and **Client secret** into `.env`: `GOOGLE_CLIENT_ID=...` `GOOGLE_CLIENT_SECRET=...` 4. Restart `npm run hub` and open `http://localhost:3333/` → **Continue with Google**. **Error 400: `redirect_uri_mismatch`:** The URI Google receives is not in the list above. Compare with what your process uses: full Hub → `/api/v1/auth/callback/google`; gateway → `/auth/callback/google`. Port must match **`HUB_BASE_URL`** (gateway) or **`HUB_PORT`** (full Hub). ### GitHub 1. GitHub → **Settings** → **Developer settings** → **OAuth Apps** → **New OAuth App**. **Authorization callback URL(s)** — add both (or the one you use): - `http://localhost:3333/api/v1/auth/callback/github` (login) - `http://localhost:3333/api/v1/auth/callback/github-connect` (Connect GitHub in Settings, for vault push) 2. Create the app; copy **Client ID** and generate a **Client secret** into `.env`: `GITHUB_CLIENT_ID=...` `GITHUB_CLIENT_SECRET=...` 3. Restart the Hub → **Continue with GitHub**. **If you see GitHub’s “Be careful! … redirect_uri is not associated with this application”:** the **Authorization callback URL** in your GitHub OAuth App must match **exactly** what this server uses — for `npm run hub` that is **`http://localhost:3333/api/v1/auth/callback/github`** (note **`/api/v1/`**; not `/auth/callback/github`, which is the **gateway** path). Add both callback URLs from step 1 if anything is missing. **If you use Connect GitHub:** the same OAuth App must also list `http://localhost:3333/api/v1/auth/callback/github-connect`. **Production:** set `HUB_BASE_URL=https://your-domain.com` and add `https://your-domain.com/api/v1/auth/callback/github` and `https://your-domain.com/api/v1/auth/callback/github-connect` to the GitHub app. **Hosted product (gateway + bridge):** the **Connect GitHub** callback lives on the **bridge** (`/auth/callback/github-connect` on the bridge origin), not on the full Hub server. Copy-paste table: [docs/CONNECT-GITHUB-AND-STORAGE-CHECK.md](../docs/CONNECT-GITHUB-AND-STORAGE-CHECK.md) §0. ## Docker From repo root: ```bash docker build -f hub/Dockerfile -t knowtation-hub . docker run -p 3333:3333 \ -e KNOWTATION_VAULT_PATH=/data/vault \ -e HUB_JWT_SECRET=your-secret \ -e GOOGLE_CLIENT_ID=... \ -e GOOGLE_CLIENT_SECRET=... \ -v /path/to/vault:/data/vault \ -v /path/to/data:/app/data \ knowtation-hub ``` Proposals are stored under `data_dir` (e.g. `/app/data/hub_proposals.json`). Mount a volume for persistence. ## Roles (Phase 13) ### First-run: no roles file (works for everyone) **When there is no `data/hub_roles.json` file** (or the file has no entries), **everyone who logs in receives role admin**. No manual setup or hardcoded IDs. You see the **Team** tab in Settings and have full access. This is the default so every new install works without intervention. When you want to **restrict** who can do what, use **Settings → Team** and add the first role (e.g. yourself as admin, or a teammate as viewer). That creates the roles file. From then on, only users listed in the file get the role you set; anyone not listed gets **member** (editor). To avoid locking yourself out, add your own User ID as admin in the Team tab before adding others with more restricted roles. ### Why roles? Roles let you **control who can do what** on a shared vault without giving everyone full access. For example: give teammates **viewer** (read-only), **editor** (can add notes and create proposals but not approve them or change Setup), or **admin** (full access). That way you can share the Hub URL and still decide who can change backup settings or approve agent proposals. ### How assignment works (user ID, not email or handle) Role assignment uses a **user ID** of the form **`provider:id`** — for example `github:12345678` or `google:109876543210987654321`. This is the OAuth subject ID from Google or GitHub, **not** the person’s email or GitHub username. So you do **not** put `alice@company.com` or `alice-github` in the file; you put the ID the Hub uses internally (e.g. `github:12345`). **How to get someone’s user ID** **How to assign a role:** A **backup repo is not required** for roles. Two options: **Option A — From the Hub (recommended)** If you are an admin: open **Settings → Team** (only admins see it). Have the other person copy **Your user ID** from their Settings and send it to you. Paste it in the Team tab, choose Role (viewer / editor / admin), click **Add / update role**. **Option B — Edit the file** On the server, create or edit `data/hub_roles.json` (see format below); restart the Hub or save Setup once to reload. ### The roles file **No file or empty file:** Everyone is admin (see "First-run" above). **File with entries:** Only listed users get the assigned role; others get member (editor). To restrict access, create or edit `data/hub_roles.json` (or use Settings → Team). Format: ```json { "roles": { "github:12345": "admin", "google:67890": "editor", "github:11111": "viewer" } } ``` - **Viewer** — Can only read (notes, search, proposals). Cannot write, delete, propose, approve, or change Setup. - **Editor** — Can read, write and delete notes, and create proposals; cannot change Setup or approve/discard proposals. - **Admin** — Full access: everything above plus Setup and approve/discard. The Hub UI shows **Your role** and **Your user ID** in Settings so users know their role and can share their ID with an admin. A future **invite flow** (Phase 13) may allow assigning by email or invite link so you don’t have to manage the JSON file by hand. **Role changes vs JWT:** The JWT still stores whatever role you had at login. For API checks and **GET /api/v1/settings**, the Hub uses **`data/hub_roles.json` (current file)** as the source of truth, so Team changes (e.g. promoting someone to evaluator or toggling **May approve**) apply on the next request without logging out. ## Note provenance (self-hosted Hub) On **`POST /api/v1/notes`**, **`POST /api/v1/import`** (second pass on each imported path), **`POST /api/v1/capture`**, and **proposal approve**, the Hub merges **server-controlled** YAML frontmatter so clients cannot forge identity: | Key | When | |-----|------| | `knowtation_editor` | OAuth `sub` (`provider:id`) for the acting user, when known | | `knowtation_edited_at` | ISO-8601 UTC timestamp of the write | | `author_kind` | `human` \| `webhook` \| `agent` \| `import` | | `knowtation_proposed_by` | Set on **approve** from the stored proposal | | `knowtation_approved_by` | Set on **approve** to the admin’s `sub` | Webhook capture does **not** set `knowtation_editor` (no logged-in user). New proposals store **`proposed_by`** in `hub_proposals.json`. **Hosted (canister):** note writes through the gateway/canister do **not** use this Node Hub path. To get the same fields in hosted backups, mirror this merge where hosted notes are persisted (Motoko / bridge) using the authenticated principal or JWT `sub`. ## Optional: signed Git commits (single Hub identity) Signed commits prove a **private key** possessed by the backup process signed the commit — they do **not** identify which OAuth user edited a note (use frontmatter for that). To get GitHub “Verified” on backup commits: 1. Create a **dedicated** GPG or SSH signing key for the machine that runs `npm run hub`. 2. Register the public key with GitHub; use a **verified** email for `git config user.email` in the vault (or global config for that user). 3. In the vault directory (or globally): `git config commit.gpgsign true`, `git config user.signingkey ` (GPG) or use [SSH signing](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key) with `gpg.format ssh` and a public key path. 4. Ensure non-interactive signing (`gpg-agent` / `ssh-agent`) so [`lib/vault-git-sync.mjs`](../lib/vault-git-sync.mjs) commits from the Hub do not hang. All backup commits will show **one** signer (the Hub), not per-editor identities. ## API contract See [docs/HUB-API.md](../docs/HUB-API.md) for routes, request/response shapes, and auth (JWT, OAuth, ICP).