Remediate six confirmed security issues: deployment-only config keys, bounded provider responses, short-lived registration sessions, private init file mode, FedWiki workflow authorization, and switch preview gates. - Add DeploymentOnly config key declaration; refuse runtime overrides for keys that decide where secrets are sent - Create httplimit package; bound all provider response reads at 8 MiB - Set fifteen-minute deadline on /register sessions - Write mc-config.yaml with 0600 permissions - Derive FedWiki workflow IDs from site IDs; re-authorize sites before mutating activities - Apply switch authorization gates to the proration preview
142 lines
7.6 KiB
Markdown
142 lines
7.6 KiB
Markdown
---
|
|
title: "Discourse Integration Setup"
|
|
audience: [operator]
|
|
summary: "Connecting the member console to a Discourse forum: forum-side prerequisites, linkage-mode selection (email / oidc / discourseconnect), group mapping, webhooks, and the settings that must stay off."
|
|
---
|
|
|
|
# Discourse integration — operator setup
|
|
|
|
The Discourse integration delivers the org-held `discourse_posting`
|
|
entitlement as membership in Discourse groups you choose. The console
|
|
computes who should be in each mapped group (everyone with an active
|
|
membership in any org that currently holds the entitlement), converges the
|
|
forum to that set on a periodic sweep, and corrects drift. What those
|
|
groups *gate* (category permissions, posting rights) stays entirely in
|
|
your Discourse admin — the console manages membership, never permissions.
|
|
|
|
Works against any Discourse instance. Wiki.cafe's deployment is the tested
|
|
reference, not an assumption.
|
|
|
|
## 1. Forum-side prerequisites
|
|
|
|
1. **A group to manage.** Create a *manually-managed* (non-automatic) group
|
|
per mapped entitlement (e.g. `members`). Automatic groups
|
|
(`trust_level_*`, `staff`, ...) are refused at mapping time — Discourse
|
|
computes their membership itself.
|
|
**Treat mapped groups as console-owned:** membership changes made by
|
|
forum admins are corrected on the next sweep (and logged console-side).
|
|
2. **An admin API key.** Discourse admin → API → New API Key (global key;
|
|
note the key and the acting username, usually `system`).
|
|
Rate-limit note: Discourse's admin API budget (default 60 requests/min)
|
|
is one **site-wide** bucket shared with human admins. The console caps
|
|
itself well below it (30/min) and backs off on 429.
|
|
3. **(Recommended) A webhook.** Admin → API → Webhooks: payload URL
|
|
`https://<console-host>/webhooks/discourse`, a shared secret, events
|
|
**User** (created) and **Group User** (added/removed). Webhooks only
|
|
reduce latency — a missed event costs at most one sweep interval.
|
|
4. **Never enable `discourse_connect_overrides_groups`.** It full-replaces
|
|
a user's non-automatic groups at every login, which fights any external
|
|
group management (this integration's and your admins' alike).
|
|
|
|
## 2. Choose a linkage mode
|
|
|
|
The console must map its people to forum users. `discourse-linkage-mode`
|
|
selects how:
|
|
|
|
| mode | forum-side requirement | linkage guarantee |
|
|
|------|------------------------|-------------------|
|
|
| `email` | none | verified primary email = forum primary email (exact, case-insensitive). Fragile if emails drift. |
|
|
| `oidc` (default) | enable the bundled OpenID Connect plugin against the same IdP the console uses (`openid_connect_enabled` + discovery URL + client credentials) | deterministic: forum association ↔ OIDC subject. Existing forum accounts self-link at first SSO login (`match_by_email`, default on). Additive — local logins keep working. |
|
|
| `discourseconnect` | DiscourseConnect SSO with the console's IdP as provider | deterministic via `external_id`. **Warning: exclusive** — enabling DiscourseConnect disables local and all other login methods for every forum user, and takes over existing accounts by email match. A forum-wide auth decision, not just integration plumbing. |
|
|
|
|
Members without a resolvable forum account appear on the operator page as
|
|
**entitled but unlinked** — a normal state, not an error. In `oidc` mode
|
|
they link automatically seconds after their first forum SSO login (via the
|
|
`user_created` webhook) or at the next sweep.
|
|
|
|
`discourse-auto-create-users=true` additionally creates forum accounts
|
|
(active, approved, identity-bound in `oidc` mode) for entitled persons who
|
|
have none. Leave it off unless your deployment wants forum accounts to
|
|
exist before first visit. Not supported in `discourseconnect` mode.
|
|
|
|
## 3. Console configuration
|
|
|
|
```
|
|
discourse-base-url https://forum.example.com (required)
|
|
discourse-api-key <admin API key> (required, secret; -file variant available)
|
|
discourse-api-username system (default)
|
|
discourse-linkage-mode oidc (default; email | oidc | discourseconnect)
|
|
discourse-auto-create-users false (default)
|
|
discourse-webhook-secret <shared secret> (secret, optional; empty = webhook endpoint unmounted)
|
|
discourse-sync-interval 15m (default; sweep cadence)
|
|
```
|
|
|
|
`discourse-base-url` and `discourse-api-key` must be set together (or both
|
|
left empty: the integration stays dormant — registered, migrations
|
|
applied, no forum calls, and the operator page says so). Both are set with
|
|
the deployment: the settings page shows the URL and offers no control,
|
|
because the API key is sent to whatever that URL names.
|
|
|
|
## 4. Map entitlements to groups
|
|
|
|
Operator panel → Integrations → Discourse:
|
|
|
|
- **Map group**: choose the entitlement (`discourse_posting`) and enter the
|
|
forum group's name. The console validates against the forum (group must
|
|
exist and not be automatic) and stores the mapping; the group is now
|
|
managed and converges on the next sweep.
|
|
- **Remove mapping**: stops management. Forum-side membership is left
|
|
as-is — removal never bulk-kicks members.
|
|
- The page also shows the last sweep's outcome, entitled-but-unlinked
|
|
members, and any **link conflicts** (one forum account claimed by two
|
|
identities — the console freezes the link and never reassigns; resolve
|
|
forum-side or in member data, then the next sweep relinks).
|
|
|
|
## 5. Test stack / development
|
|
|
|
Two forum options exist; unit/DB tests always use the fake.
|
|
|
|
**Fake (default — fast, no containers).** An in-process fake implementing
|
|
the consumed API surface lives at
|
|
`internal/integrations/discourse/discoursetest`:
|
|
|
|
```
|
|
go run ./internal/integrations/discourse/discoursetest/fakeserver \
|
|
-addr 127.0.0.1:9457 -api-key test-key -seed-group walkthrough-members
|
|
cd test && go run .. start --config mc-config.yaml \
|
|
--discourse-base-url http://127.0.0.1:9457 --discourse-api-key test-key
|
|
```
|
|
|
|
**Live Discourse (opt-in compose profile).** A real Discourse — the
|
|
official `discourse/discourse` image with `discourse/postgres` + redis
|
|
sidecars, mirroring the Co-op Cloud recipe's topology — runs behind the
|
|
`discourse` profile so the default stack stays fast (~1.5 GB image,
|
|
minutes-long first boot):
|
|
|
|
```
|
|
cd test && docker compose --profile discourse up -d
|
|
# discourse-seed migrates the DB, creates admin (admin@example.com /
|
|
# discourse-admin-pw1), enables the bundled OIDC authenticator, creates the
|
|
# walkthrough-members group, registers a webhook pointed at the app on the
|
|
# host (secret test-only-webhook-secret; the discourse service maps
|
|
# member-console.localhost to the docker host-gateway), and writes the
|
|
# master API key to testdata/discourse/api-key. Then, per test/.env's
|
|
# commented lines:
|
|
# MC_DISCOURSE_BASE_URL=http://localhost:${DISCOURSE_PORT}
|
|
# MC_DISCOURSE_API_KEY_FILE=testdata/discourse/api-key
|
|
# MC_DISCOURSE_WEBHOOK_SECRET=test-only-webhook-secret
|
|
go run .. start --config mc-config.yaml
|
|
```
|
|
|
|
Re-running the seed (any `--profile discourse up`) re-mints the API key —
|
|
restart the app afterwards so it reads the fresh key file.
|
|
|
|
`test/seed/discourse/seed-discourse.sh` seeds an org conferred
|
|
`discourse_posting` so the sweep has someone to deliver. The browser
|
|
walkthrough (`test/e2e/operator-walkthroughs/discourse_test.go`) exercises
|
|
the mapping surface end-to-end against either forum (both seed the
|
|
`walkthrough-members` group) and skips when Discourse is unconfigured.
|
|
Browser SSO into the live forum additionally needs a `discourse` client in
|
|
the Keycloak realm — not seeded yet; the OIDC authenticator is enabled for
|
|
API-level identity binding (`external_ids`) regardless.
|