Skip to content

Commit 6be43df

Browse files
WilliamBergaminClaude
andcommitted
refactor(standardized-auth): mirror the python sample over socket mode
Match slack-samples/bolt-python-examples#305: run over Socket Mode, keep connections in a local properties file, handle the user_connection event and the fixed auth_provider_connect and auth_provider_disconnect action IDs, and answer user_connection_status_requested. The OAuth flow, installation service, tokens_revoked, and token revocation are gone. The SDK has no user_connection event model yet, so the sample defines UserConnectionEvent with a null default subtype to match Bolt's routing key. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
1 parent 61f9bd1 commit 6be43df

23 files changed

Lines changed: 625 additions & 982 deletions

‎standardized-auth/.env.example‎

Lines changed: 0 additions & 5 deletions
This file was deleted.

‎standardized-auth/.gitignore‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,5 @@
22
.project
33
.settings
44
target
5-
data/
65
.env*
7-
!.env.example
6+
acme.properties

‎standardized-auth/README.md‎

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,22 @@ Read the docs: https://docs.slack.dev/authentication/standardized-auth
66

77
## How it works
88

9-
Acme keeps every connection in Bolt's installation service: a user is connected when it holds their user token.
9+
Acme runs over Socket Mode and keeps every connection in a local properties file ([`Connections.java`](./src/main/java/acme/Connections.java)): a user is connected when their Slack user ID is in it. The provider object and the App Home view live in [`Acme.java`](./src/main/java/acme/Acme.java), and every listener and Slack API call lives in [`Main.java`](./src/main/java/acme/Main.java).
1010

11-
- **Connect** opens `/slack/install`. When the OAuth flow saves the user's token, the app reports the connection with `apps.user.connection.requestAuth` and refreshes App Home.
12-
- **Disconnect** forgets the user's token, reports the user as disconnected, then revokes the token.
11+
- **Connect** connects the Slack user who clicked it, with their user ID as the connected identity. It reports the connection with `apps.user.connection.requestAuth` and refreshes App Home.
12+
- **Connect** or **Manage** on the app's profile sends the `user_connection` event, and Acme answers within 3 seconds by opening the connection modal with `apps.user.connection.requestAuth` and the event's `trigger_id`.
13+
- **Disconnect** forgets the connection and reports the user as disconnected.
1314
- **`user_connection_status_requested`** answers with `apps.user.connection.update` whenever Slack asks about a user it doesn't know yet.
14-
- **`tokens_revoked`** forgets users who remove Acme from their Slack settings and reports them as disconnected.
1515
- **App Home** shows an `auth_provider` block with the user's current connection.
1616

17+
Connecting in one click stands in for your service's real login. Slack requires at least one host in the manifest's `features.user_connections.auth_hosts`, so `example.com` holds the place until you have one. To sign users in to your own service, set `auth_url` on the provider to your OAuth or SSO page, replace `example.com` with its host, and carry the Slack user through the browser in a signed `state` so a connection always lands on the user who clicked **Connect**.
18+
1719
## Setup
1820

21+
Everything arrives over Socket Mode, so the app needs no public URL. The manifest subscribes to the `user_connection` and `user_connection_status_requested` events, which every app with `features.user_connections` needs. Use a Slack CLI newer than v4.10.0: older versions don't send `features.user_connections` with the manifest.
22+
1923
```sh
20-
$ ngrok http 3000 # Replace 1234-56-78-90-0.ngrok-free.app in manifest.json
21-
$ slack install --environment local # Create a new app
22-
$ slack app settings # Gather the client ID, client secret, and signing secret
23-
$ slack env init # Update defaults with them and your ngrok URL
24-
$ slack manifest # Validate fields
2524
$ slack run
26-
$ open https://1234-56-78-90-0.ngrok-free.app/slack/install # Install the app
2725
```
2826

2927
Then open the app's **Home** tab and click **Connect**.
30-
31-
Swap the `users:read` and `users.profile:read` scopes for the ones your connection needs.

‎standardized-auth/manifest.json‎

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,39 @@
11
{
22
"display_information": {
33
"name": "Acme",
4-
"description": "Rocket skates, anvils, and giant magnets for every plan",
5-
"background_color": "#4A154B"
4+
"description": "Rocket skates, anvils, and giant magnets for every plan"
65
},
76
"features": {
87
"app_home": {
9-
"home_tab_enabled": true,
10-
"messages_tab_enabled": false
8+
"home_tab_enabled": true
119
},
1210
"bot_user": {
1311
"display_name": "Acme",
1412
"always_online": true
1513
},
1614
"user_connections": {
1715
"is_enabled": true,
18-
"auth_hosts": ["1234-56-78-90-0.ngrok-free.app"]
16+
"auth_hosts": ["example.com"]
1917
}
2018
},
2119
"oauth_config": {
22-
"redirect_urls": ["https://1234-56-78-90-0.ngrok-free.app/slack/oauth_redirect"],
2320
"scopes": {
24-
"bot": ["users:read"],
25-
"user": ["users.profile:read"]
21+
"bot": ["users:read"]
2622
}
2723
},
2824
"settings": {
2925
"event_subscriptions": {
30-
"request_url": "https://1234-56-78-90-0.ngrok-free.app/slack/events",
3126
"bot_events": [
3227
"app_home_opened",
33-
"tokens_revoked",
28+
"user_connection",
3429
"user_connection_status_requested"
3530
]
3631
},
3732
"interactivity": {
38-
"is_enabled": true,
39-
"request_url": "https://1234-56-78-90-0.ngrok-free.app/slack/events"
33+
"is_enabled": true
4034
},
4135
"org_deploy_enabled": true,
42-
"socket_mode_enabled": false,
36+
"socket_mode_enabled": true,
4337
"token_rotation_enabled": false
4438
}
4539
}

‎standardized-auth/pom.xml‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,24 @@
7171
<dependencies>
7272
<dependency>
7373
<groupId>com.slack.api</groupId>
74-
<artifactId>bolt-jetty</artifactId>
74+
<artifactId>bolt</artifactId>
7575
<version>1.52.1-SNAPSHOT</version>
7676
</dependency>
77+
<dependency>
78+
<groupId>com.slack.api</groupId>
79+
<artifactId>bolt-socket-mode</artifactId>
80+
<version>1.52.1-SNAPSHOT</version>
81+
</dependency>
82+
<dependency>
83+
<groupId>javax.websocket</groupId>
84+
<artifactId>javax.websocket-api</artifactId>
85+
<version>1.1</version>
86+
</dependency>
87+
<dependency>
88+
<groupId>org.glassfish.tyrus.bundles</groupId>
89+
<artifactId>tyrus-standalone-client</artifactId>
90+
<version>1.21</version>
91+
</dependency>
7792
<dependency>
7893
<groupId>org.slf4j</groupId>
7994
<artifactId>slf4j-simple</artifactId>
@@ -86,5 +101,11 @@
86101
<version>6.1.3</version>
87102
<scope>test</scope>
88103
</dependency>
104+
<dependency>
105+
<groupId>org.mockito</groupId>
106+
<artifactId>mockito-junit-jupiter</artifactId>
107+
<version>5.23.0</version>
108+
<scope>test</scope>
109+
</dependency>
89110
</dependencies>
90111
</project>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package acme;
2+
3+
import static com.slack.api.model.block.Blocks.asBlocks;
4+
import static com.slack.api.model.block.Blocks.authProvider;
5+
import static com.slack.api.model.block.Blocks.section;
6+
import static com.slack.api.model.block.composition.BlockCompositions.markdownText;
7+
import static com.slack.api.model.block.composition.BlockCompositions.plainText;
8+
import static com.slack.api.model.view.Views.view;
9+
10+
import com.slack.api.methods.request.apps.user.connection.AppsUserConnectionRequestAuthRequest.PrimaryProvider;
11+
import com.slack.api.model.view.View;
12+
13+
public final class Acme {
14+
public static final String PROVIDER_VALUE = "acme";
15+
16+
private Acme() {}
17+
18+
public static PrimaryProvider provider(String userId) {
19+
PrimaryProvider acme = PrimaryProvider.builder()
20+
.value(PROVIDER_VALUE)
21+
.name(plainText("Acme account"))
22+
.description(plainText("Order rocket skates, anvils, and giant magnets without leaving Slack."))
23+
.isConnected(Connections.isConnected(userId))
24+
.build();
25+
if (acme.getIsConnected()) {
26+
acme.setConnectedIdentity(plainText(userId));
27+
}
28+
return acme;
29+
}
30+
31+
public static View homeView(PrimaryProvider acme) {
32+
return view(view -> view.type("home")
33+
.blocks(asBlocks(
34+
section(section -> section.text(markdownText("*Welcome to Acme!* :rocket:\n"
35+
+ "Anvils ship same day, gravity permitting. "
36+
+ "Please keep giant magnets away from your laptop."))),
37+
authProvider(block -> block.value(acme.getValue())
38+
.name(acme.getName())
39+
.description(acme.getDescription())
40+
.iconUrl(acme.getIconUrl())
41+
.authUrl(acme.getAuthUrl())
42+
.disconnectUrl(acme.getDisconnectUrl())
43+
.isConnected(acme.getIsConnected())
44+
.connectedIdentity(acme.getConnectedIdentity())
45+
.connectedContext(acme.getConnectedContext())
46+
.accessory(acme.getAccessory())))));
47+
}
48+
}

‎standardized-auth/src/main/java/acme/AcmeAccounts.java‎

Lines changed: 0 additions & 33 deletions
This file was deleted.

‎standardized-auth/src/main/java/acme/AcmeApp.java‎

Lines changed: 0 additions & 58 deletions
This file was deleted.

‎standardized-auth/src/main/java/acme/AcmeListeners.java‎

Lines changed: 0 additions & 116 deletions
This file was deleted.

0 commit comments

Comments
 (0)