sealwd 0.5.0

Secure password and token management library for Rust, featuring hashing, encryption, and random generation.
Documentation
// Copyright 2026 Thomas Zuyev

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

//     http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::{
    fmt::impl_redacted_fmt,
    random,
    token::{Result, TokenMaterial},
};

/// Cryptographically random, dynamically-sized heap-allocated token material.
///
/// # Security Notes
/// - Leverages heap allocation via `Vec<u8>` wrapped with `zeroize::ZeroizeOnDrop`
///   to sanitize internal byte sequences from memory upon disposal.
/// - Designed for processing dynamic-length API keys, OAuth assertions, or runtime-sized payloads.
/// - Explicitly hides internal data formats during `Debug` or `Display` printing actions (`<redacted>`).
#[derive(zeroize::ZeroizeOnDrop, Clone)]
pub struct GenericToken(Vec<u8>);

impl GenericToken {
    /// Generates a new instance containing cryptographically secure random bytes of the specified length.
    ///
    /// # Errors
    /// Returns [`Error::Random`](crate::token::Error::Random) if system entropy collection fails.
    pub fn random(len: usize) -> Result<Self> {
        let mut buf = vec![0u8; len];
        random::fill_bytes(&mut buf)?;
        Ok(Self(buf))
    }

    /// Directly instantiates a token wrapper by duplicating memory fields from a target reference slice.
    #[must_use]
    pub fn from_bytes(data: &[u8]) -> Self {
        Self(data.to_vec())
    }
}

impl TokenMaterial for GenericToken {
    fn as_bytes(&self) -> &[u8] {
        &self.0
    }

    fn from_vec(bytes: Vec<u8>) -> Result<Self> {
        Ok(Self(bytes))
    }
}

impl_redacted_fmt!(GenericToken);

#[cfg(feature = "serde")]
impl serde::Serialize for GenericToken {
    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> serde::Deserialize<'de> for GenericToken {
    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 = <Vec<u8>>::deserialize(deserializer)?;
            Self::from_vec(bytes).map_err(serde::de::Error::custom)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn redacted_debug() {
        let token = GenericToken::from_bytes(b"abc");

        assert_eq!(format!("{:?}", token), "<redacted>");
    }

    #[test]
    fn redacted_display() {
        let token = GenericToken::from_bytes(b"abc");

        assert_eq!(format!("{token}"), "<redacted>");
    }

    #[test]
    #[cfg(feature = "serde")]
    fn serde_human_readable_roundtrip() {
        let token = GenericToken::from_bytes(b"abcdef");
        let json = serde_json::to_string(&token).unwrap();
        let deserialized = serde_json::from_str::<GenericToken>(&json).unwrap();

        assert_eq!(token.0, deserialized.0);
        assert_eq!(json, "\"YWJjZGVm\"");
    }
}