-
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
Changes from 6 commits
4a1b0b0
73eabaa
73d08ee
44156f2
9058133
e8194d5
836a6dc
df8caed
dd8e8c9
9b50f2e
2af0b64
6b1bb34
f58b3d1
43af723
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| # 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. | ||
|
|
||
| ## 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. | ||
|
|
||
| ## Install (operator workflow) | ||
|
|
||
| We `forge deploy` this app once into the Mattermost Atlassian developer | ||
| account, then share a private install link. Customers do NOT run | ||
| `forge deploy` themselves. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I imagine we'll be adding a workflow with secret env vars that can potentially handle the forge deployments to a specific version for the production deployments? I'm wondering whether we want to have something made in the delivery platform repo that can be shared between Confluence and Jira but we can figure this out as a follow-up
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thats also a good point, we need to keep in mind before merging and when we have the Forge app. Question is who might own this?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I could look into that (likely with some help from someone in SRE), we might need to discuss the current game plan for how we'll deploy and maintain this in the next team meeting beforehand |
||
|
|
||
| 1. `npm install` in this directory. | ||
| 2. `forge login` and `forge register` (one-time, generates the app ID — | ||
| paste it into `manifest.yml` under `app.id`). | ||
| 3. `forge deploy --environment production`. | ||
| 4. `forge install --site https://<test-tenant>.atlassian.net` to verify on | ||
| a test tenant. For end-customer distribution, generate a private | ||
| distribution link from the Atlassian developer console. | ||
|
|
||
| ## Wire-up (customer workflow) | ||
|
|
||
| 1. Confluence admin clicks the install link → app installs on their site. | ||
| 2. Confluence admin runs `forge webtrigger` (or reads the install logs) | ||
| to get the `register` and `drain` URLs. We'll wrap this in a UI Kit | ||
| admin page in a follow-up. | ||
| 3. In Mattermost System Console under Plugins > Confluence: | ||
| - Paste the `drain` URL into "Forge Drain URL". | ||
| - Copy the auto-generated "Forge Bridge Shared Secret". | ||
| 4. POST the secret to the `register` URL once: | ||
|
|
||
| ```bash | ||
| curl -X POST -H 'Content-Type: application/json' \ | ||
| -d '{"secret":"<paste shared secret>"}' \ | ||
| '<register-web-trigger-url>' | ||
| ``` | ||
|
|
||
| `register` is one-shot — it refuses subsequent calls so a leaked URL | ||
| can't be used to repoint the bridge. To re-register (e.g. rotate the | ||
| secret), clear `mm.registered` from Forge storage first. | ||
|
|
||
| The Mattermost plugin then polls `drain` on a ticker, verifies each | ||
| event, posts to subscribed channels, and acks drained keys so Forge can | ||
| delete them. | ||
|
|
||
| ## 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). | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| 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/e78f4d26-6f49-4a3e-a591-18fe466fc5b0 | ||
|
|
||
| permissions: | ||
| scopes: | ||
| - storage:app | ||
| - read:confluence-content.summary | ||
| - write:confluence-content |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| { | ||
| "name": "mattermost-confluence-forge", | ||
| "version": "0.1.0", | ||
| "private": true, | ||
| "description": "Forge bridge that forwards Confluence Cloud events to the Mattermost Confluence plugin. Replaces the legacy Atlassian Connect descriptor path that Atlassian deprecated in 2026.", | ||
| "scripts": { | ||
| "build": "tsc", | ||
| "typecheck": "tsc --noEmit", | ||
| "validate-manifest": "tsc -p tsconfig.scripts.json && node scripts-dist/validate-manifest.js", | ||
| "ci": "npm run typecheck && npm run validate-manifest && npm run build", | ||
| "deploy": "forge deploy", | ||
| "install:dev": "forge install --upgrade" | ||
| }, | ||
| "dependencies": { | ||
| "@forge/api": "^4.0.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/js-yaml": "^4.0.9", | ||
| "@types/node": "^20.0.0", | ||
| "js-yaml": "^4.1.1", | ||
| "typescript": "^5.5.0" | ||
| }, | ||
| "optionalDependencies": { | ||
| "@forge/cli": "^11.0.0" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| { | ||
| "extends": "../tsconfig.scripts.json" | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.