Module: Silas::Slack

Defined in:
lib/silas/slack.rb

Overview

Thin Slack Web API helper — no gem dependency. Posting (chat.postMessage), approval Block Kit, and inbound request-signature verification.

Constant Summary collapse

POST_MESSAGE =
"https://slack.com/api/chat.postMessage".freeze
REPLAY_WINDOW =

seconds

300

Class Method Summary collapse

Class Method Details

.approval_blocks(invocation) ⇒ Object

Approve/Decline buttons; the button value is the invocation id, so the actions webhook maps a click straight to invocation.approve!/decline!.



29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/silas/slack.rb', line 29

def approval_blocks(invocation)
  [
    { "type" => "section",
      "text" => { "type" => "mrkdwn",
                  "text" => "*Approval needed:* `#{invocation.tool_name}`\n```#{JSON.generate(invocation.arguments)}```" } },
    { "type" => "actions", "block_id" => "silas_approval",
      "elements" => [
        { "type" => "button", "action_id" => "silas_approve", "style" => "primary",
          "text" => { "type" => "plain_text", "text" => "Approve" }, "value" => invocation.id.to_s },
        { "type" => "button", "action_id" => "silas_decline", "style" => "danger",
          "text" => { "type" => "plain_text", "text" => "Decline" }, "value" => invocation.id.to_s }
      ] }
  ]
end

.post_message(channel:, text:, blocks: nil, thread_ts: nil, token: Silas.config.slack_bot_token) ⇒ Object

Raises:



14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/silas/slack.rb', line 14

def post_message(channel:, text:, blocks: nil, thread_ts: nil, token: Silas.config.slack_bot_token)
  raise Error, "no Slack bot token configured (credentials.silas.slack.bot_token)" if token.blank?

  body = { channel: channel, text: text }
  body[:blocks] = blocks if blocks
  body[:thread_ts] = thread_ts if thread_ts

  res = Net::HTTP.post(URI(POST_MESSAGE), body.to_json,
                       "content-type" => "application/json; charset=utf-8",
                       "authorization" => "Bearer #{token}")
  JSON.parse(res.body)
end

.verify_signature(signing_secret:, timestamp:, body:, signature:, now: Time.now.to_i) ⇒ Object

Slack signs each request (v0 scheme) — HMAC-SHA256 over "v0:ts:body" plus a 5-minute replay window. Returns true only for a genuine, fresh request.



46
47
48
49
50
51
52
53
# File 'lib/silas/slack.rb', line 46

def verify_signature(signing_secret:, timestamp:, body:, signature:, now: Time.now.to_i)
  return false if signing_secret.blank? || signature.blank? || timestamp.blank?
  return false if (now - timestamp.to_i).abs > REPLAY_WINDOW

  basestring = "v0:#{timestamp}:#{body}"
  expected = "v0=" + OpenSSL::HMAC.hexdigest("SHA256", signing_secret, basestring)
  ActiveSupport::SecurityUtils.secure_compare(expected, signature)
end