sfr_core/
validation.rs

1//! A process to validate a request from Slack.
2
3use sfr_types as st;
4
5use chrono::Utc;
6use derive_new::new;
7use hmac::{Hmac, Mac};
8use sha2::Sha256;
9
10/// The type that validates a request from Slack.
11#[derive(Clone, new)]
12pub struct Validator {
13    /// The Signing Secret.
14    ///
15    /// from [`crate::AppCredentials::signing_secret`]
16    signing_secret: String,
17
18    /// The Verification Token.
19    ///
20    /// from [`crate::AppCredentials::verification_token`]
21    verification_token: String,
22
23    /// The range to allow the difference between the timestamp in the request and the timestamp in the server.
24    ///
25    /// from [`crate::Config::timestamp_range`].
26    timestamp_range: i64,
27}
28
29impl Validator {
30    /// Validates the request from Slack.
31    ///
32    /// <https://api.slack.com/authentication/verifying-requests-from-slack>
33    pub fn validate_request(
34        &self,
35        signature: &str,
36        timestamp: &str,
37        body: &[u8],
38    ) -> Result<(), st::Error> {
39        self.validate_timestamp(timestamp)?;
40
41        let mut mac = Hmac::<Sha256>::new_from_slice(self.signing_secret.as_bytes())
42            .map_err(st::Error::failed_to_initialize_hmac)?;
43
44        mac.update(b"v0:");
45        mac.update(timestamp.as_bytes());
46        mac.update(b":");
47        mac.update(body);
48
49        let result = mac.finalize().into_bytes();
50        let result = format!("v0={}", hex::encode(result));
51        if signature == result {
52            Ok(())
53        } else {
54            Err(st::CoreError::FailedToVerifySignature.into())
55        }
56    }
57
58    /// Returns [`Ok(())`][`Ok`] if `token` from args matches the Slack App's Verification Token.
59    pub fn verification_token(&self, token: &str) -> Result<(), st::Error> {
60        if self.verification_token == token {
61            Ok(())
62        } else {
63            Err(st::CoreError::MismatchVerificationTokens.into())
64        }
65    }
66
67    /// Validates that the difference between the timestamp in the request and the server time is not too large.
68    ///
69    /// - threshold: [`Config::timestamp_range`][`crate::Config::timestamp_range`]
70    fn validate_timestamp(&self, timestamp: &str) -> Result<(), st::Error> {
71        let timestamp: i64 = timestamp
72            .parse()
73            .map_err(|e| st::Error::parsing_number("failed to paese `timestamp`", e))?;
74        let now = Utc::now().timestamp();
75        let diff = (timestamp - now).abs();
76        if self.timestamp_range < diff {
77            Err(st::CoreError::TimestampExpired.into())
78        } else {
79            Ok(())
80        }
81    }
82}