Deliver forum posting entitlements through managed group membership with identity linkage, periodic reconciliation, webhook handling, and an operator mapping surface. Include fake and live test environments, setup documentation, migrations, and end-to-end coverage.
7.5 KiB
title, audience, summary
| title | audience | summary | |
|---|---|---|---|
| Discourse Integration Setup |
|
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
- 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). - 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. - (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. - 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).
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.