use crate::validators::utils::{
has_ascii_len, is_obvious_placeholder, is_opaque_token_byte, non_empty_ascii_with,
};
const MIN_PAYLOAD_LEN: usize = 16;
const MAX_TOKEN_LEN: usize = 255;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub(crate) enum StripeTokenKind {
LiveSecretKey,
TestSecretKey,
LiveRestrictedKey,
TestRestrictedKey,
WebhookSigningSecret,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub(crate) struct StripeValidation {
kind: StripeTokenKind,
}
impl StripeValidation {
pub(crate) const fn kind(self) -> StripeTokenKind {
self.kind
}
}
pub(crate) fn validate_stripe_token(candidate: &str) -> Option<StripeValidation> {
if !has_ascii_len(candidate, 1, MAX_TOKEN_LEN) || is_obvious_placeholder(candidate) {
return None;
}
let (payload, kind) = if let Some(payload) = candidate.strip_prefix("sk_live_") {
(payload, StripeTokenKind::LiveSecretKey)
} else if let Some(payload) = candidate.strip_prefix("sk_test_") {
(payload, StripeTokenKind::TestSecretKey)
} else if let Some(payload) = candidate.strip_prefix("rk_live_") {
(payload, StripeTokenKind::LiveRestrictedKey)
} else if let Some(payload) = candidate.strip_prefix("rk_test_") {
(payload, StripeTokenKind::TestRestrictedKey)
} else {
let payload = candidate.strip_prefix("whsec_")?;
(payload, StripeTokenKind::WebhookSigningSecret)
};
if payload.len() < MIN_PAYLOAD_LEN
|| is_obvious_placeholder(payload)
|| !non_empty_ascii_with(payload, is_opaque_token_byte)
{
return None;
}
Some(StripeValidation { kind })
}
#[cfg(test)]
mod tests {
use super::*;
const BODY: &str = "AbCdEf0123456789_AbCdEf0123456789";
#[test]
fn accepts_documented_secret_prefixes() {
let cases = [
(format!("sk_live_{BODY}"), StripeTokenKind::LiveSecretKey),
(format!("sk_test_{BODY}"), StripeTokenKind::TestSecretKey),
(
format!("rk_live_{BODY}"),
StripeTokenKind::LiveRestrictedKey,
),
(
format!("rk_test_{BODY}"),
StripeTokenKind::TestRestrictedKey,
),
(
format!("whsec_{BODY}"),
StripeTokenKind::WebhookSigningSecret,
),
];
for (candidate, expected) in cases {
assert_eq!(
validate_stripe_token(&candidate).map(StripeValidation::kind),
Some(expected),
);
}
}
#[test]
fn rejects_publishable_keys() {
assert!(validate_stripe_token(&format!("pk_live_{BODY}")).is_none());
assert!(validate_stripe_token(&format!("pk_test_{BODY}")).is_none());
}
#[test]
fn rejects_short_or_placeholder_payloads() {
assert!(validate_stripe_token("sk_live_short").is_none());
assert!(validate_stripe_token("sk_live_your_api_key_here").is_none());
}
#[test]
fn rejects_invalid_alphabet() {
assert!(validate_stripe_token(&format!("sk_live_{BODY}-bad")).is_none());
}
}