1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
#[cfg(feature = "password_hashing")]
use argonautica::{self, Hasher, Verifier};

use crate::ServiceError;

/// The `Authenticate` trait of the Aragog library.
/// This trait provides the possibility to authenticate a Type with some secret. Its main use
/// it to authenticate a user or client [`Record`] model instance.
///
/// [`Record`]: trait.Record.html
pub trait Authenticate {
    /// Authenticates the instance with a secret.
    ///
    /// # Arguments
    ///
    /// * `secret` - the value supposed to validate authentication like a password
    ///
    /// # Returns
    ///
    /// On success `()` is returned, on failure it will return a [`ServiceError`] according to
    /// the Authenticate implementation
    ///
    /// [`ServiceError`]: enum.ServiceError.html
    fn authenticate(&self, secret: &str) -> Result<(), ServiceError>;

    /// Returns a Argon2 encrypted password hash that can be safely stored in database.
    ///
    /// # Arguments
    ///
    /// * `password` - The actual password to encrypt, should be checked for length and complexity
    /// * `secret_key` - The key that Argon2 will use to hash the password, this key should be unique
    /// and set as an environment variable or retrieved from some vault.
    ///
    /// # Returns
    ///
    /// On success the hashed password is returned, if the hashing fails for some reason
    /// (password or secret key invalid format) a [`ServiceError`]::[`UnprocessableEntity`] will be returned.
    ///
    /// # Features
    ///
    /// This function requires the `password_hashing` feature to be enabled.
    ///
    /// [`ServiceError`]: enum.ServiceError.html
    /// [`UnprocessableEntity`]: enum.ServiceError.html#variant.UnprocessableEntity
    #[cfg(feature = "password_hashing")]
    fn hash_password(password: &str, secret_key: &str) -> Result<String, ServiceError> {
        let mut hasher = Hasher::new();

        let res = hasher
            .with_password(password)
            .with_secret_key(secret_key)
            .hash();
        match res {
            Ok(value) => Ok(value),
            // TODO: Use UnprocessableEntity when argonautica supports Error
            Err(error) => Err(ServiceError::InternalError {
                message: Some(error.to_string()),
            }),
        }
    }

    /// Verifies if the given password matches the Argon2 encrypted password hash.
    ///
    /// # Arguments
    ///
    /// * `password` - The actual password to verify
    /// * `password_hash` - The Argon2 encrypted hash password that should match the `password`
    /// * `secret_key` - The key that Argon2 will use to hash the password, this key should be unique
    /// and set as an environment variable or retrieved from some vault.
    ///
    /// # Returns
    ///
    /// On failure a [`ServiceError`]::[`Unauthorized`] will be returned.
    ///
    /// # Features
    ///
    /// This function requires the `password_hashing` feature to be enabled.
    ///
    /// [`ServiceError`]: enum.ServiceError.html
    /// [`Unauthorized`]: enum.ServiceError.html#variant.Unauthorized
    #[cfg(feature = "password_hashing")]
    fn verify_password(
        password: &str,
        password_hash: &str,
        secret_key: &str,
    ) -> Result<(), ServiceError> {
        let mut verifier = Verifier::new();

        match verifier
            .with_hash(password_hash)
            .with_password(password)
            .with_secret_key(secret_key)
            .verify()
        {
            Ok(value) => {
                if value {
                    Ok(())
                } else {
                    Err(ServiceError::Unauthorized)
                }
            }
            // TODO: Use UnprocessableEntity when argonautica supports Error
            Err(error) => Err(ServiceError::InternalError {
                message: Some(error.to_string()),
            }),
        }
    }
}