diff --git a/README.md b/README.md index f2cd53b..c6c7d22 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,23 @@ than most of your human colleagues 😏 4. Copy the `Bot User OAuth Access Token` and add it to your environment as `SLACK_BOT_TOKEN`. 5. Create a new `App-Level Tokens` under `Basic Infomation` and add it to your environment as `SLACK_APP_TOKEN`. +### User Whitelisting (Optional) + +Sam supports user whitelisting to restrict who can interact with the bot. This is useful for controlling access in larger Slack workspaces. + +To enable whitelisting: +1. Add the `WHITELISTED_USERS` environment variable to your `.env` file +2. Set it to a comma-separated list of Slack user IDs +3. Example: `WHITELISTED_USERS=U1234567890,U0987654321,U1122334455` + +**Finding Slack User IDs:** +- In Slack, click on a user's profile +- Click "More" → "Copy member ID" +- Or use the format from user mentions in Slack (the part after `@`) + +If `WHITELISTED_USERS` is empty or not set, all users can interact with Sam (default behavior). + +When a non-whitelisted user tries to interact with Sam, the bot will silently ignore their messages. ### How it works diff --git a/sam/config.py b/sam/config.py index 0a22377..e9747f3 100644 --- a/sam/config.py +++ b/sam/config.py @@ -41,6 +41,10 @@ SLACK_BOT_TOKEN: str = os.getenv("SLACK_BOT_TOKEN") #: The Slack app token, prefixed with `xapp-`. SLACK_APP_TOKEN: str = os.getenv("SLACK_APP_TOKEN") +#: Comma-separated list of Slack user IDs allowed to interact with the bot. If empty, all users are allowed. +WHITELISTED_USERS: list[str] = [ + user.strip() for user in os.getenv("WHITELISTED_USERS", "").split(",") if user.strip() +] # Sentry #: The Sentry DSN for Sentry based error reporting. diff --git a/sam/slack.py b/sam/slack.py index da2f6cf..555fe65 100644 --- a/sam/slack.py +++ b/sam/slack.py @@ -55,6 +55,13 @@ async def handle_message(event: {str, Any}, say: AsyncSay): if event.get("subtype") in ["message_changed", "message_deleted"]: logger.debug("Ignoring `%s` event", event["subtype"]) return + + # Check if user is whitelisted (if whitelist is configured) + user_id = event.get("user") + if config.WHITELISTED_USERS and user_id not in config.WHITELISTED_USERS: + logger.debug("Ignoring message from non-whitelisted user: %s", user_id) + return + bot_id = await get_bot_user_id() channel_id = event["channel"] channel_type = event["channel_type"] @@ -103,9 +110,14 @@ async def send_response( voice_response: bool = False, ): """Send a response to a message event from Slack.""" + # Check if user is whitelisted (if whitelist is configured) + user_id = event["user"] + if config.WHITELISTED_USERS and user_id not in config.WHITELISTED_USERS: + logger.debug("Ignoring response request from non-whitelisted user: %s", user_id) + return + logger.debug("process_run=%s", json.dumps(event)) channel_id = event["channel"] - user_id = event["user"] try: timestamp = event["ts"] except KeyError: diff --git a/template.env b/template.env index 62a9fa2..1237b97 100644 --- a/template.env +++ b/template.env @@ -5,3 +5,4 @@ SLACK_APP_TOKEN= SLACK_BOT_TOKEN= OPENAI_API_KEY= REDIS_URL=redis://redis:6379/1 +WHITELISTED_USERS=