use crate::{
encoding::base64,
random,
token::{Error, GenericToken, Result, TokenMaterial},
};
#[derive(zeroize::ZeroizeOnDrop, Clone)]
pub struct Token<const LEN: usize>([u8; LEN]);
impl<const LEN: usize> Token<LEN> {
pub fn random() -> Result<Self> {
let mut s = [0u8; LEN];
random::fill_bytes(&mut s)?;
Ok(Self(s))
}
#[must_use]
pub fn from_bytes(data: [u8; LEN]) -> Self {
Self(data)
}
#[must_use]
pub fn to_generic_token(self) -> GenericToken {
GenericToken::from_bytes(self.as_bytes())
}
}
impl<const LEN: usize> TokenMaterial for Token<LEN> {
fn as_bytes(&self) -> &[u8] {
&self.0
}
fn from_vec(bytes: Vec<u8>) -> Result<Self> {
let provided = bytes.len();
let arr = bytes.try_into().map_err(|_| Error::LengthMismatch {
expected: LEN,
provided,
})?;
Ok(Self::from_bytes(arr))
}
fn from_base64(data: &str) -> Result<Self> {
let mut s = [0u8; LEN];
let written = base64::decode_slice(data, &mut s).map_err(|e| match e {
base64::DecodeSliceError::DecodeError(e) => e.into(),
base64::DecodeSliceError::OutputSliceTooSmall => Error::LengthMismatch {
expected: LEN,
provided: (LEN * 4).div_ceil(3),
},
})?;
if written == LEN {
Ok(Self(s))
} else {
Err(Error::LengthMismatch {
expected: LEN,
provided: written,
})
}
}
}
impl<const LEN: usize> std::fmt::Debug for Token<LEN> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "<redacted>")
}
}
impl<const LEN: usize> std::fmt::Display for Token<LEN> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "<redacted>")
}
}
#[cfg(feature = "serde")]
impl<const LEN: usize> serde::Serialize for Token<LEN> {
fn serialize<S>(&self, serializer: S) -> std::prelude::v1::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if serializer.is_human_readable() {
serializer.serialize_str(&self.to_base64())
} else {
serializer.serialize_bytes(self.as_bytes())
}
}
}
#[cfg(feature = "serde")]
impl<'de, const LEN: usize> serde::Deserialize<'de> for Token<LEN> {
fn deserialize<D>(deserializer: D) -> std::prelude::v1::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
if deserializer.is_human_readable() {
let s = String::deserialize(deserializer)?;
Self::from_base64(&s).map_err(serde::de::Error::custom)
} else {
let bytes = <serde_big_array::Array<u8, LEN>>::deserialize(deserializer)?;
Ok(Self::from_bytes(*bytes))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn redacted_debug() {
let token = Token::<64>::random().unwrap();
assert_eq!(format!("{:?}", token), "<redacted>");
}
#[test]
fn redacted_display() {
let token = Token::<64>::random().unwrap();
assert_eq!(format!("{token}"), "<redacted>");
}
#[test]
#[cfg(feature = "serde")]
fn serde_human_readable_roundtrip() {
let token = Token::<10>::from_bytes(*b"abcdefghij");
let json = serde_json::to_string(&token).unwrap();
let deserialized = serde_json::from_str::<Token<10>>(&json).unwrap();
assert_eq!(token.0, deserialized.0);
assert_eq!(json, "\"YWJjZGVmZ2hpag\"");
}
}