Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions docs/slack-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,12 @@ public channels and rich Slack UX are deferred to v1.5.1.
2. An authorized administrator imports the generated manifest into the private
Slack app. Preserve exactly the bot `commands` scope, no user scopes or Events
API subscriptions, no organization-wide install, and token rotation. The
hosted command/interactivity/OAuth routes are fixed in the manifest; do not
substitute previews, localhost or private URLs. Verify the installed settings
match: a generated file does not prove installation. See Slack's official
hosted routes are fixed in the manifest: command and interactivity requests go
to the application, while the OAuth redirect goes only through the dedicated,
owner-controlled query-scrubbing relay before a query-free browser handoff to
the application.
Do not substitute previews, localhost or private URLs. Verify the installed
settings match: a generated file does not prove installation. See Slack's official
[manifest](https://docs.slack.dev/reference/app-manifest/),
[OAuth](https://docs.slack.dev/authentication/installing-with-oauth/) and
[token rotation](https://docs.slack.dev/authentication/using-token-rotation/)
Expand Down
6 changes: 6 additions & 0 deletions scripts/rehearse_v150.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,12 @@ def cli(python, *args, expected=0):
assert output.stat().st_mode & 0o777 == 0o600
hosted_manifest = json.loads(output.read_text())
assert hosted_manifest["oauth_config"]["scopes"]["bot"] == ["commands"]
assert hosted_manifest["oauth_config"]["redirect_urls"] == [
"https://slack-oauth.messagebridge.app/callback"]
assert hosted_manifest["features"]["slash_commands"][0]["url"] == \
"https://codemower.com/api/slack/commands"
assert hosted_manifest["settings"]["interactivity"]["request_url"] == \
"https://codemower.com/api/slack/interactions"
cli(py, "slack", "setup", "--manifest", output, "--yes", expected=1)
checks.append("explicit_slack_setup_exclusive_private_manifest")
report = json.loads(cli(py, "slack", "doctor", "--json", expected=1))
Expand Down
2 changes: 1 addition & 1 deletion src/code_mower/templates/slack/hosted-app-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
}]
},
"oauth_config": {
"redirect_urls": ["https://codemower.com/api/slack/oauth/callback"],
"redirect_urls": ["https://slack-oauth.messagebridge.app/callback"],
"scopes": {"bot": ["commands"]}
},
"settings": {
Expand Down
7 changes: 7 additions & 0 deletions tests/test_slack_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,13 @@ def test_scripted_opt_in_exports_only_hosted_manifest_and_never_overwrites(self)
self.assertEqual(self.call([*args, "--yes"])[0], 0)
manifest = json.loads(path.read_bytes())
self.assertEqual(manifest["oauth_config"]["scopes"], {"bot": ["commands"]})
self.assertEqual(manifest["oauth_config"]["redirect_urls"], [
"https://slack-oauth.messagebridge.app/callback"
])
self.assertEqual(manifest["features"]["slash_commands"][0]["url"],
"https://codemower.com/api/slack/commands")
self.assertEqual(manifest["settings"]["interactivity"]["request_url"],
"https://codemower.com/api/slack/interactions")
self.assertTrue(manifest["settings"]["token_rotation_enabled"])
self.assertNotIn("event_subscriptions", manifest["settings"])
self.assertEqual(manifest["features"]["slash_commands"][0]["command"], "/codemower")
Expand Down
Loading