-
Notifications
You must be signed in to change notification settings - Fork 20
MM-68853: Confluence Cloud install path via OAuth 2.0 (3LO) + Forge bridge POC #228
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
4a1b0b0
MM-68853: Confluence Cloud install path via OAuth 2.0 (3LO) + Forge b…
73eabaa
MM-68853: Confluence Cloud install path via OAuth 2.0 (3LO) + Forge b…
73d08ee
coderabbit comments
44156f2
MM-68853: deliver Forge events to subscribed channels end-to-end
9058133
coderabbit feedbadck
e8194d5
PR feedback
836a6dc
MM-67455: DM connected users when mentioned in confluence (#229)
nang2049 df8caed
Update README.md
dd8e8c9
fix broen link
9b50f2e
Send a DM notifying admins when the secret was regenerated and access…
2af0b64
fix spam
6b1bb34
fix forge docs
f58b3d1
Merge remote-tracking branch 'origin/master' into MM-68853-forge-clou…
43af723
switch app to production
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| name: forge-ci | ||
| on: | ||
| workflow_dispatch: | ||
| pull_request: | ||
| paths: | ||
| - 'forge/**' | ||
| - '.github/workflows/forge-ci.yml' | ||
| push: | ||
| branches: | ||
| - master | ||
| paths: | ||
| - 'forge/**' | ||
| - '.github/workflows/forge-ci.yml' | ||
|
|
||
| defaults: | ||
| run: | ||
| working-directory: forge | ||
|
|
||
| jobs: | ||
| build: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: '20' | ||
| cache: 'npm' | ||
| cache-dependency-path: forge/package.json | ||
| - run: npm install --omit=optional --no-audit --no-fund | ||
| - run: npm audit --omit=optional --audit-level=moderate | ||
| - run: npm run typecheck | ||
| - run: npm run validate-manifest | ||
| - run: npm run build | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,217 @@ | ||
| # Mattermost Confluence Forge bridge | ||
|
|
||
| This Forge app is the GA replacement for the Atlassian Connect webhook | ||
| descriptor that Atlassian closed for new installs on March 31, 2026. | ||
| It is required for new Confluence **Cloud** customers to receive | ||
| events in Mattermost. Confluence **Server / Data Center** installs are | ||
| unaffected and continue to use the existing webhook path. | ||
|
|
||
| ## Who runs the bridge | ||
|
|
||
| Each customer runs their own bridge under their own Atlassian developer | ||
| account. Mattermost does not publish a shared bridge for customer use: | ||
| Atlassian's free Forge usage limits would cap the number of tenants we | ||
| could realistically support from a single Mattermost-owned bridge, and | ||
| self-hosting also gives customers full control over the bridge's | ||
| storage and the install consent screen presented to their Confluence | ||
| admins. | ||
|
|
||
| The plugin is agnostic to who owns the Forge app — the setup wizard | ||
| accepts any valid Forge web trigger URL. | ||
|
|
||
| ## Self-host runbook | ||
|
|
||
| ### Prerequisites | ||
|
|
||
| - An [Atlassian developer account](https://developer.atlassian.com/console/myapps/) | ||
| for your organisation. Free to create; one account can host the | ||
| bridge app for all your Confluence Cloud tenants. | ||
| - Node.js 22 (LTS) or newer. | ||
| - The Forge CLI: `npm install -g @forge/cli`. Run `forge login` once | ||
| with an API token from | ||
| [id.atlassian.com/manage-profile/security/api-tokens](https://id.atlassian.com/manage-profile/security/api-tokens). | ||
| - Confluence Cloud Site Admin on the target tenant. | ||
| - Mattermost System Admin on the target server. | ||
|
|
||
| ### Step 1 — Get the bridge source | ||
|
|
||
| Clone the plugin repository and change into the `forge/` directory: | ||
|
|
||
| ```bash | ||
| git clone https://github.com/mattermost/mattermost-plugin-confluence.git | ||
| cd mattermost-plugin-confluence/forge | ||
| npm install | ||
| ``` | ||
|
|
||
| ### Step 2 — Register the app under your developer account | ||
|
|
||
| ```bash | ||
| forge register | ||
| ``` | ||
|
|
||
| The CLI will prompt for an app name (e.g. `acme-mattermost-confluence-bridge`) | ||
| and write the generated app ID into `manifest.yml` under `app.id`. This | ||
| ID is yours; do not share the file or the ID outside your organisation. | ||
|
|
||
| ### Step 3 — Deploy | ||
|
|
||
| ```bash | ||
| forge deploy --environment production | ||
| ``` | ||
|
|
||
| This publishes the app to your developer account. It is not visible | ||
| to anyone outside your organisation and is not listed on the Atlassian | ||
| Marketplace. | ||
|
|
||
| ### Step 4 — Generate a private install link | ||
|
|
||
| In the | ||
| [Atlassian developer console](https://developer.atlassian.com/console/myapps/), | ||
| open the app you just deployed → **Distribution** → **Sharing** → | ||
| generate a private install link. This link is what your Confluence | ||
| Site Admins (or you, if you are the only tenant) will click to install | ||
| the bridge. | ||
|
|
||
| ### Step 5 — Tell the Mattermost plugin where the install link lives | ||
|
|
||
| In Mattermost: **System Console → Plugins → Confluence → Forge Bridge | ||
| Install URL**. Paste the install link from the previous step. Save. | ||
|
|
||
| You only need to do this once per Mattermost server; the wizard will | ||
| surface this URL to the admin running the Cloud setup. | ||
|
|
||
| ### Step 6 — Install the bridge on your Confluence Cloud site | ||
|
|
||
| The Confluence Site Admin clicks the install link from step 4, reviews | ||
| the consent screen (read access to pages and comments, no outbound | ||
| network), and approves the install. Atlassian provisions a per-tenant | ||
| copy of the app: isolated storage, unique web trigger URLs, no shared | ||
| state with any other tenant. | ||
|
|
||
| ### Step 7 — Get the bridge's web trigger URLs | ||
|
|
||
| From a terminal authenticated with `forge login` and the same developer | ||
| account that owns the app: | ||
|
|
||
| ```bash | ||
| forge webtrigger --environment production | ||
| ``` | ||
|
|
||
| Pick the installed tenant when prompted. The CLI prints two URLs: | ||
| - `drain` → the URL Mattermost will poll | ||
| - `register` → a one-shot URL used to set the shared secret | ||
|
|
||
| ### Step 8 — Run the Mattermost setup wizard | ||
|
|
||
| In Mattermost, run `/confluence install cloud`. Step through the | ||
| wizard; when it reaches the **Forge bridge** step, click **Register | ||
| bridge** and paste: | ||
| - `drain` URL from step 7 → "Drain URL" | ||
| - `register` URL from step 7 → "Register URL" | ||
|
|
||
| The plugin POSTs its auto-generated shared secret to your bridge's | ||
| register endpoint, stores the drain URL in plugin config, and starts | ||
| polling on a 30-second tick. | ||
|
|
||
| ### Verify | ||
|
|
||
| In Confluence, edit a page in a space subscribed in Mattermost. Within | ||
| ~30 seconds, the subscribed channel should receive the page-edit | ||
| notification. | ||
|
|
||
| ### Operational notes | ||
|
|
||
| - `register` is one-shot. If you need to rotate the shared secret, | ||
| clear `mm.registered` from Forge storage first (use `forge install | ||
| --upgrade` after manually wiping the entry), then re-run the | ||
| Mattermost wizard. | ||
| - Forge storage values are capped at 240 KiB per entry. The bridge | ||
| drops the inline page body for events that would exceed this; the | ||
| channel notification still fires but @-mention DMs are skipped for | ||
| that single oversized event. | ||
| - Forge web trigger throttle is 1000 req/min per app/environment. At | ||
| a 30-second poll cadence that is 2 req/min per tenant, so one bridge | ||
| accommodates ~500 Confluence Cloud tenants before throttling. | ||
| - Forge storage is wiped 28 days after the app is uninstalled. The | ||
| bridge is a buffer, not a system of record; the Mattermost plugin | ||
| is the durable side. | ||
|
|
||
| ## Shape | ||
|
|
||
| This is a **pull** bridge: | ||
|
|
||
| - The Forge app subscribes to 8 Confluence events via `trigger` modules | ||
| (page `created`/`updated`/`trashed`/`restored`/`deleted`, comment | ||
| `created`/`updated`/`deleted`) and enqueues each event payload into Forge | ||
| storage under `evt:<cloudId>:<ts>:<rand>`. | ||
| - The Mattermost plugin periodically POSTs to the `drain` web trigger to | ||
| read queued events and ack them. Requests are HMAC-SHA256 signed using a | ||
| shared secret the admin sets via the one-shot `register` web trigger. | ||
| - No `permissions.external.fetch` is declared. The Forge app never makes | ||
| outbound calls. This keeps the install consent screen clean and removes | ||
| the per-customer `manifest.yml` editing the previous push design required. | ||
|
|
||
| Trade-off: Atlassian's Forge `trigger` module already has up to 3 minutes | ||
| of delivery delay, so the additional ~30s polling latency we add on the | ||
| plugin side is small in context. | ||
|
|
||
| ## Mattermost-internal QA bridge | ||
|
|
||
| A Mattermost-owned copy of this app lives in our Atlassian developer | ||
| space for internal QA and demos only. It is **not** distributed to | ||
| customers, not surfaced in the marketplace, and not the path documented | ||
| to end users. Customers always self-host (see runbook above). | ||
|
|
||
| Internal deploy: | ||
|
|
||
| 1. `npm install` in this directory. | ||
| 2. `forge login` against the shared Mattermost Atlassian developer | ||
| account, then `forge deploy --environment production`. | ||
| 3. `forge install --site https://<internal-test-tenant>.atlassian.net`. | ||
| 4. Smoke-test with a Mattermost dev server pointed at the resulting | ||
| `drain` / `register` web trigger URLs. | ||
|
|
||
| ## Develop | ||
|
|
||
| This directory is its own Node project, independent of the plugin's | ||
| `server/` and `webapp/` builds. CI lives in `.github/workflows/forge-ci.yml` | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| and only fires when `forge/**` changes. | ||
|
|
||
| ```bash | ||
| npm install --omit=optional # CI install path | ||
| npm install # developer install path, pulls @forge/cli | ||
|
|
||
| npm run typecheck | ||
| npm run validate-manifest | ||
| npm run build | ||
| npm run ci # all of the above | ||
| npm run deploy # forge deploy | ||
| ``` | ||
|
|
||
| ## Why these 8 events | ||
|
|
||
| Direct one-to-one mapping with what the legacy Atlassian Connect descriptor | ||
| used to subscribe to (page + comment lifecycle), re-validated against | ||
| [the Forge Confluence events list](https://developer.atlassian.com/platform/forge/events-reference/confluence/). | ||
| Forge collapses Connect's `removed` onto `deleted`. See | ||
| `server/forge_event_mapping.go` on the plugin side for the explicit mapping. | ||
|
|
||
| ## Known limits | ||
|
|
||
| - Forge `trigger` delivery is best-effort (up to ~3 min delay, occasional | ||
| drops). Connect webhooks had the same property, so we are not | ||
| regressing. If drops show up in production we will add a plugin-side | ||
| reconciliation poll over `/wiki/api/v2/pages?sort=-modified-date`. | ||
| - Forge storage is wiped 28 days after uninstall — the queue is buffer, | ||
| not durable state. The plugin is the system of record. | ||
| - Forge web trigger limit: 1000 req/min per app/env/context. At a 30s | ||
| poll cadence, that's 2 req/min per tenant → headroom for ~500 | ||
| installations per environment before throttling. | ||
|
|
||
| ## Not in scope here | ||
|
|
||
| - Plugin-side Cloud 3LO OAuth (lives in `server/instance_cloud.go` and | ||
| the Cloud branches of `server/user.go` / `server/flow.go`). | ||
| - Plugin-side polling loop (lives in `server/forge_poller.go`). | ||
| - Migration of existing Connect installs (we leave those running until | ||
| Atlassian's Q4 2026 EOS). | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| modules: | ||
| trigger: | ||
| - key: page-created | ||
| function: enqueueFn | ||
| events: | ||
| - avi:confluence:created:page | ||
| - key: page-updated | ||
| function: enqueueFn | ||
| events: | ||
| - avi:confluence:updated:page | ||
| - key: page-trashed | ||
| function: enqueueFn | ||
| events: | ||
| - avi:confluence:trashed:page | ||
| - key: page-restored | ||
| function: enqueueFn | ||
| events: | ||
| - avi:confluence:restored:page | ||
| - key: page-deleted | ||
| function: enqueueFn | ||
| events: | ||
| - avi:confluence:deleted:page | ||
| - key: comment-created | ||
| function: enqueueFn | ||
| events: | ||
| - avi:confluence:created:comment | ||
| - key: comment-updated | ||
| function: enqueueFn | ||
| events: | ||
| - avi:confluence:updated:comment | ||
| - key: comment-deleted | ||
| function: enqueueFn | ||
| events: | ||
| - avi:confluence:deleted:comment | ||
| - key: app-installed | ||
| function: onInstalledFn | ||
| events: | ||
| - avi:forge:installed:app | ||
|
|
||
| webtrigger: | ||
| - key: drain | ||
| function: drainFn | ||
| urlFormat: v2 | ||
| response: | ||
| type: dynamic | ||
| - key: register | ||
| function: registerFn | ||
| urlFormat: v2 | ||
| response: | ||
| type: dynamic | ||
|
|
||
| function: | ||
| - key: enqueueFn | ||
| handler: index.enqueue | ||
| - key: drainFn | ||
| handler: index.drain | ||
| - key: registerFn | ||
| handler: index.register | ||
| - key: onInstalledFn | ||
| handler: index.onInstalled | ||
|
|
||
| app: | ||
| runtime: | ||
| name: nodejs22.x | ||
| id: ari:cloud:ecosystem::app/ecfbedb0-1bc1-4ade-b983-96d8e6be1cb8 | ||
|
|
||
| permissions: | ||
| scopes: | ||
| - storage:app | ||
| - read:confluence-content.summary | ||
| - write:confluence-content | ||
| - read:page:confluence | ||
| - read:comment:confluence |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.