Skip to main content

keyhog_scanner/checksum/
slack.rs

1use std::sync::LazyLock;
2
3use super::{ChecksumResult, ChecksumValidator};
4
5/// Validates Slack token structure.
6///
7/// Slack tokens do not expose a public checksum algorithm, but their format is
8/// highly regular. This validator performs strict structural matching and
9/// rejects tokens that violate known segment rules.
10pub struct SlackTokenValidator;
11
12// Compile once, reuse across all validate() calls.
13static SLACK_BOT_RE: LazyLock<regex::Regex> = LazyLock::new(|| {
14    regex::Regex::new(r"^xoxb-[0-9]{10,15}-[0-9]{10,15}-[a-zA-Z0-9]{15,40}$")
15        .expect("hardcoded regex")
16});
17static SLACK_USER_RE: LazyLock<regex::Regex> = LazyLock::new(|| {
18    regex::Regex::new(r"^xoxp-[0-9]{10,15}-[0-9]{10,15}(?:-[0-9]{10,13})?-[a-zA-Z0-9]{24,40}$")
19        .expect("hardcoded regex")
20});
21
22impl SlackTokenValidator {
23    fn is_valid_slack_bot(credential: &str) -> bool {
24        SLACK_BOT_RE.is_match(credential)
25    }
26
27    fn is_valid_slack_user(credential: &str) -> bool {
28        SLACK_USER_RE.is_match(credential)
29    }
30}
31
32impl ChecksumValidator for SlackTokenValidator {
33    fn validator_id(&self) -> &str {
34        "slack-token"
35    }
36
37    fn validate(&self, credential: &str) -> ChecksumResult {
38        if credential.starts_with("xoxb-") {
39            if Self::is_valid_slack_bot(credential) {
40                ChecksumResult::Valid
41            } else {
42                ChecksumResult::Invalid
43            }
44        } else if credential.starts_with("xoxp-") {
45            if Self::is_valid_slack_user(credential) {
46                ChecksumResult::Valid
47            } else {
48                ChecksumResult::Invalid
49            }
50        } else {
51            ChecksumResult::NotApplicable
52        }
53    }
54}