Skip to content
Draft
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
12 changes: 12 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,15 @@ updates:
slack:
patterns:
- "com.slack.api:*"
- package-ecosystem: "maven"
directory: "standardized-auth"
schedule:
interval: "daily"
cooldown:
default-days: 7
exclude:
- "com.slack.api:*"
groups:
slack:
patterns:
- "com.slack.api:*"
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ jobs:
showcase:
- "block-kit"
- "methods"
- "standardized-auth"
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ This collections of examples highlights features of a Slack app in the language

- **[Block Kit](./block-kit)**: The framework of visual components arranged to create app layouts.
- **[Methods](./methods)**: An interface for querying information from and enacting change in a Slack workspace.
- **[Standardized Auth](./standardized-auth)**: Connect user accounts with the Slack-owned Connect and Manage connection controls.
6 changes: 6 additions & 0 deletions standardized-auth/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.classpath
.project
.settings
target
.env*
acme.properties
2 changes: 2 additions & 0 deletions standardized-auth/.slack/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
apps.dev.json
cache/
6 changes: 6 additions & 0 deletions standardized-auth/.slack/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"manifest": {
"source": "local"
},
"project_id": "00000000-0000-0000-0000-000000000000"
}
8 changes: 8 additions & 0 deletions standardized-auth/.slack/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"hooks": {
"start": "mvn --quiet compile exec:java"
},
"config": {
"sdk-managed-connection-enabled": true
}
}
27 changes: 27 additions & 0 deletions standardized-auth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Standardized Auth

Connect your Acme account to Slack with the Slack-owned **Connect** and **Manage connection** controls. Rocket skates, anvils, and giant magnets not included.

Read the docs: https://docs.slack.dev/authentication/standardized-auth

## How it works

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).

- **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.
- **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`.
- **Disconnect** forgets the connection and reports the user as disconnected.
- **`user_connection_status_requested`** answers with `apps.user.connection.update` whenever Slack asks about a user it doesn't know yet.
- **App Home** shows an `auth_provider` block with the user's current connection.

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**.

## Setup

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.

```sh
$ slack run
```

Then open the app's **Home** tab and click **Connect**.
39 changes: 39 additions & 0 deletions standardized-auth/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"display_information": {
"name": "Acme",
"description": "Rocket skates, anvils, and giant magnets for every plan"
},
"features": {
"app_home": {
"home_tab_enabled": true
},
"bot_user": {
"display_name": "Acme",
"always_online": true
},
"user_connections": {
"is_enabled": true,
"auth_hosts": ["example.com"]
}
},
"oauth_config": {
"scopes": {
"bot": ["users:read"]
}
},
"settings": {
"event_subscriptions": {
"bot_events": [
"app_home_opened",
"user_connection",
"user_connection_status_requested"
]
},
"interactivity": {
"is_enabled": true
},
"org_deploy_enabled": true,
"socket_mode_enabled": true,
"token_rotation_enabled": false
}
}
111 changes: 111 additions & 0 deletions standardized-auth/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.slack.api</groupId>
<artifactId>standardized-auth-example</artifactId>
<version>0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<exec.mainClass>acme.Main</exec.mainClass>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.16.0</version>
<configuration>
<release>17</release>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.6.4</version>
<configuration>
<cleanupDaemonThreads>false</cleanupDaemonThreads>
</configuration>
</plugin>
<plugin>
<groupId>com.diffplug.spotless</groupId>
<artifactId>spotless-maven-plugin</artifactId>
<version>3.10.4</version>
<configuration>
<formats>
<format>
<includes>
<include>.gitignore</include>
</includes>
<trimTrailingWhitespace/>
<endWithNewline/>
<indent>
<tabs>true</tabs>
<spacesPerTab>4</spacesPerTab>
</indent>
</format>
</formats>
<java>
<palantirJavaFormat/>
</java>
<pom/>
<markdown>
<includes>
<include>**/*.md</include>
</includes>
<flexmark/>
</markdown>
</configuration>
<executions>
<execution>
<goals>
<goal>apply</goal>
</goals>
<phase>compile</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.slack.api</groupId>
<artifactId>bolt</artifactId>
<version>1.52.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.slack.api</groupId>
<artifactId>bolt-socket-mode</artifactId>
<version>1.52.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.tyrus.bundles</groupId>
<artifactId>tyrus-standalone-client</artifactId>
<version>1.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.36</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>6.1.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.23.0</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
48 changes: 48 additions & 0 deletions standardized-auth/src/main/java/acme/Acme.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package acme;

import static com.slack.api.model.block.Blocks.asBlocks;
import static com.slack.api.model.block.Blocks.authProvider;
import static com.slack.api.model.block.Blocks.section;
import static com.slack.api.model.block.composition.BlockCompositions.markdownText;
import static com.slack.api.model.block.composition.BlockCompositions.plainText;
import static com.slack.api.model.view.Views.view;

import com.slack.api.methods.request.apps.user.connection.AppsUserConnectionRequestAuthRequest.PrimaryProvider;
import com.slack.api.model.view.View;

public final class Acme {
public static final String PROVIDER_VALUE = "acme";

private Acme() {}

public static PrimaryProvider provider(String userId) {
PrimaryProvider acme = PrimaryProvider.builder()
.value(PROVIDER_VALUE)
.name(plainText("Acme account"))
.description(plainText("Order rocket skates, anvils, and giant magnets without leaving Slack."))
.isConnected(Connections.isConnected(userId))
.build();
if (acme.getIsConnected()) {
acme.setConnectedIdentity(plainText(userId));
}
return acme;
}

public static View homeView(PrimaryProvider acme) {
return view(view -> view.type("home")
.blocks(asBlocks(
section(section -> section.text(markdownText("*Welcome to Acme!* :rocket:\n"
+ "Anvils ship same day, gravity permitting. "
+ "Please keep giant magnets away from your laptop."))),
authProvider(block -> block.value(acme.getValue())
.name(acme.getName())
.description(acme.getDescription())
.iconUrl(acme.getIconUrl())
.authUrl(acme.getAuthUrl())
.disconnectUrl(acme.getDisconnectUrl())
.isConnected(acme.getIsConnected())
.connectedIdentity(acme.getConnectedIdentity())
.connectedContext(acme.getConnectedContext())
.accessory(acme.getAccessory())))));
}
}
52 changes: 52 additions & 0 deletions standardized-auth/src/main/java/acme/Connections.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package acme;

import java.io.IOException;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Properties;

public final class Connections {
static Path filename = Path.of("acme.properties");

private Connections() {}

public static void connect(String userId) {
Properties db = read();
db.setProperty(userId, "true");
write(db);
}

public static void disconnect(String userId) {
Properties db = read();
db.remove(userId);
write(db);
}

public static boolean isConnected(String userId) {
return read().containsKey(userId);
}

private static Properties read() {
Properties db = new Properties();
if (Files.notExists(filename)) {
return db;
}
try (Reader reader = Files.newBufferedReader(filename)) {
db.load(reader);
} catch (IOException error) {
throw new UncheckedIOException(error);
}
return db;
}

private static void write(Properties db) {
try (Writer writer = Files.newBufferedWriter(filename)) {
db.store(writer, null);
} catch (IOException error) {
throw new UncheckedIOException(error);
}
}
}
Loading
Loading