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::{
    crypto::{CipherSuite, EncryptedSecret},
    token::{Result, TokenMaterial},
};

#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};

/// A type-safe wrapper that guarantees the enclosed cryptographic ciphertext belongs to token material `T`.
///
/// Combines an underlying [`EncryptedSecret`] container with compile-time marker safety using [`std::marker::PhantomData`].
/// This ensures developers map retrieved plaintext objects back onto their precise native token layouts.
pub struct EncryptedToken<T: TokenMaterial> {
    secret: EncryptedSecret,
    _marker: std::marker::PhantomData<T>,
}

impl<T: TokenMaterial> EncryptedToken<T> {
    /// Wraps an existing [`EncryptedSecret`] directly with type markers.
    pub fn from_secret(secret: EncryptedSecret) -> Self {
        Self {
            secret,
            _marker: std::marker::PhantomData,
        }
    }

    /// Reconstructs an encrypted token type-safe wrapper out of serialized cryptographic byte buffers.
    ///
    /// # Errors
    /// Returns [`Error::Crypto`](crate::token::Error::Crypto) if payload structures are corrupt.
    pub fn from_encrypted_bytes(input: &[u8]) -> Result<Self> {
        let secret = EncryptedSecret::from_encrypted_bytes(input)?;
        Ok(Self::from_secret(secret))
    }

    /// Emits the unified packed binary serialization output from the underlying ciphertext data structures.
    pub fn to_encrypted_bytes(&self) -> Vec<u8> {
        self.secret.to_encrypted_bytes()
    }

    /// Decrypts internal assets with the given key, mapping the unpacked result securely back into type `T`.
    ///
    /// # Errors
    /// Returns an error if the key is incorrect, authentication tags mismatch, or deserialization bounds
    /// of type `T` fail inside [`TokenMaterial::from_vec`].
    pub fn decrypt(&self, key: &[u8]) -> Result<T> {
        let plaintext = self.secret.decrypt(key)?;
        T::from_vec(plaintext)
    }

    /// Exposes details regarding the specific [`CipherSuite`] used to process the underlying payload.
    pub fn algorithm(&self) -> CipherSuite {
        self.secret.algorithm()
    }
}

impl<T: TokenMaterial> std::fmt::Debug for EncryptedToken<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:?}", self.secret)
    }
}

impl<T: TokenMaterial> std::fmt::Display for EncryptedToken<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.secret)
    }
}

#[cfg(feature = "serde")]
impl<T: TokenMaterial> Serialize for EncryptedToken<T> {
    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        self.secret.serialize(serializer)
    }
}

#[cfg(feature = "serde")]
impl<'de, T: TokenMaterial> Deserialize<'de> for EncryptedToken<T> {
    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let secret = EncryptedSecret::deserialize(deserializer)?;
        Ok(Self::from_secret(secret))
    }
}

#[cfg(feature = "sqlx")]
mod sqlx_impl {
    use super::*;
    use sqlx::{Database, Decode, Encode, Type, encode::IsNull, error::BoxDynError};

    #[cfg(feature = "sqlx_postgres")]
    impl<T: TokenMaterial> sqlx::postgres::PgHasArrayType for EncryptedToken<T> {
        fn array_type_info() -> sqlx::postgres::PgTypeInfo {
            <EncryptedSecret as sqlx::postgres::PgHasArrayType>::array_type_info()
        }
    }

    impl<'a, T: TokenMaterial, DB: Database> Type<DB> for EncryptedToken<T>
    where
        &'a [u8]: Type<DB>,
    {
        fn type_info() -> <DB as Database>::TypeInfo {
            <EncryptedSecret as Type<DB>>::type_info()
        }

        fn compatible(ty: &<DB as Database>::TypeInfo) -> bool {
            <EncryptedSecret as Type<DB>>::compatible(ty)
        }
    }

    impl<'a, T: TokenMaterial, DB: Database> Encode<'a, DB> for EncryptedToken<T>
    where
        Vec<u8>: Encode<'a, DB>,
    {
        fn encode_by_ref(
            &self,
            buf: &mut <DB as Database>::ArgumentBuffer<'a>,
        ) -> std::result::Result<IsNull, BoxDynError> {
            <EncryptedSecret as Encode<'a, DB>>::encode_by_ref(&self.secret, buf)
        }
        fn produces(&self) -> Option<<DB as Database>::TypeInfo> {
            <EncryptedSecret as Encode<'a, DB>>::produces(&self.secret)
        }
        fn size_hint(&self) -> usize {
            <EncryptedSecret as Encode<'a, DB>>::size_hint(&self.secret)
        }
    }

    impl<'a, T: TokenMaterial, DB: Database> Decode<'a, DB> for EncryptedToken<T>
    where
        Vec<u8>: Decode<'a, DB>,
    {
        fn decode(value: <DB as Database>::ValueRef<'a>) -> std::result::Result<Self, BoxDynError> {
            let secret = <EncryptedSecret as Decode<'a, DB>>::decode(value)?;
            Ok(Self::from_secret(secret))
        }
    }
}