# HandleSlackAppUninstalled

## Permission Scope

slackWorkspaceIntegration

## Overview

handleSlackAppUninstalled processes Slack's `app_uninstalled` event. The input is the raw webhook material — `rawBody` (unparsed request body), `timestamp` (`X-Slack-Request-Timestamp`), `signature` (`X-Slack-Signature`), and the app's `signingSecret` — and the command first **verifies the Slack request signature** (`v0=HMAC-SHA256(signingSecret, "v0:{timestamp}:{rawBody}")`, constant-time compare, ±5-minute timestamp tolerance for replay protection). Only after verification does it parse `team_id` out of the signed body — never from a separately supplied (spoofable) field — then looks up the `SlackWorkspaceIntegration` by `teamId` and, if found, marks it `REVOKED` and stamps `revokedAt`, so subsequent DESTINATION posts fail fast with `workspace_not_connected` until the workspace is reinstalled. The row is kept (not deleted) so a later re-install reuses the same row.

## Business Rules

- Missing signature material (`rawBody`, `timestamp`, `signature`, or `signingSecret` blank) is rejected with `SIGNATURE_INVALID`
- A timestamp that is non-numeric or more than 5 minutes away from now is rejected with `SIGNATURE_INVALID` (replay protection)
- A signature that fails HMAC verification against `v0:{timestamp}:{rawBody}` is rejected with `SIGNATURE_INVALID`
- `teamId` is taken from the signature-verified body only; a malformed body or a body without a `team_id` is rejected with `INVALID_EVENT`
- An unknown `teamId` (no matching connection) is a no-op success returning `revoked = false`
- A matched connection is set to `status = REVOKED` with `revokedAt = now`; the row is never deleted
- Reactivation requires a fresh successful OAuth install, never a delivery retry

## Process Flow

```mermaid
flowchart TD
    A[Receive handleSlackAppUninstalled] --> B{rawBody, timestamp, signature, signingSecret all present?}
    B -->|No| C[Return SIGNATURE_INVALID]
    B -->|Yes| D{timestamp within 5 minutes of now?}
    D -->|No| C
    D -->|Yes| E{HMAC signature verifies against v0:timestamp:rawBody?}
    E -->|No| C
    E -->|Yes| F{Signed body parses and carries team_id?}
    F -->|No| G[Return INVALID_EVENT]
    F -->|Yes| H[Look up SlackWorkspaceIntegration by teamId]
    H --> I{connection found?}
    I -->|No| J[Return revoked=false no-op success]
    I -->|Yes| K[Set status=REVOKED, revokedAt=now]
    K --> L[Return revoked=true with connection]
```

## External Dependencies

- [SlackWorkspaceIntegration](../model/SlackWorkspaceIntegration.md) - the connection revoked by the event

## Error Scenarios

- **INVALID_EVENT**: the event payload is missing a `teamId`
- **SIGNATURE_INVALID**: the Slack request signature is missing, stale, or fails HMAC verification

## Test Cases

- marks the matching SlackWorkspaceIntegration REVOKED and stamps revokedAt
- returns a no-op success when no workspace matches the teamId
- rejects an event body without a team_id with INVALID_EVENT
- rejects an incorrect signature with SIGNATURE_INVALID
- rejects a timestamp older than the 5 minute tolerance with SIGNATURE_INVALID (replay protection)
- rejects a rawBody that was tampered with after signing with SIGNATURE_INVALID
- rejects missing signature material with SIGNATURE_INVALID
