use std::str::FromStr;
use crate::SecretToken;
use vitaminc::protected::OpaqueDebug;
const ACCESS_KEY_PREFIX: &str = "CSAK";
#[derive(OpaqueDebug)]
pub struct AccessKey(SecretToken);
impl AccessKey {
pub(crate) fn into_secret_token(self) -> SecretToken {
self.0
}
}
impl FromStr for AccessKey {
type Err = InvalidAccessKey;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let rest = s
.strip_prefix(ACCESS_KEY_PREFIX)
.ok_or(InvalidAccessKey::MissingPrefix)?;
let (id, secret) = rest.split_once('.').ok_or(InvalidAccessKey::MissingDot)?;
if id.is_empty() {
return Err(InvalidAccessKey::EmptyKeyId);
}
if secret.is_empty() {
return Err(InvalidAccessKey::EmptySecret);
}
Ok(Self(SecretToken::new(s)))
}
}
#[derive(Debug, thiserror::Error)]
pub enum InvalidAccessKey {
#[error("access key must start with \"{ACCESS_KEY_PREFIX}\"")]
MissingPrefix,
#[error("access key must contain a \".\" separator")]
MissingDot,
#[error("access key ID must not be empty")]
EmptyKeyId,
#[error("access key secret must not be empty")]
EmptySecret,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn valid_key() {
let key: AccessKey =
"CSAKT4ZMT2AUPXI7TCD2.ZAQRW2BWXP3Z6SHR4YG2TP3N35LLU46ZAWLR3BL5WUR4IIGA"
.parse()
.unwrap();
assert_eq!(
key.0.as_str(),
"CSAKT4ZMT2AUPXI7TCD2.ZAQRW2BWXP3Z6SHR4YG2TP3N35LLU46ZAWLR3BL5WUR4IIGA"
);
}
#[test]
fn missing_prefix() {
let err = "key_id.key_secret".parse::<AccessKey>().unwrap_err();
assert!(matches!(err, InvalidAccessKey::MissingPrefix));
}
#[test]
fn missing_dot() {
let err = "CSAKnodot".parse::<AccessKey>().unwrap_err();
assert!(matches!(err, InvalidAccessKey::MissingDot));
}
#[test]
fn empty_key_id() {
let err = "CSAK.secret".parse::<AccessKey>().unwrap_err();
assert!(matches!(err, InvalidAccessKey::EmptyKeyId));
}
#[test]
fn empty_secret() {
let err = "CSAKid.".parse::<AccessKey>().unwrap_err();
assert!(matches!(err, InvalidAccessKey::EmptySecret));
}
#[test]
fn empty_string() {
let err = "".parse::<AccessKey>().unwrap_err();
assert!(matches!(err, InvalidAccessKey::MissingPrefix));
}
#[test]
fn into_secret_token() {
let key: AccessKey = "CSAKmyKeyId.myKeySecret".parse().unwrap();
let secret = key.into_secret_token();
assert_eq!(secret.as_str(), "CSAKmyKeyId.myKeySecret");
}
#[test]
fn debug_does_not_leak() {
let key: AccessKey = "CSAKid.secret".parse().unwrap();
let debug = format!("{key:?}");
assert!(!debug.contains("secret"));
assert!(
debug.contains("AccessKey") && debug.contains("***"),
"debug should hide secret: {debug}"
);
}
}