openauth-plugins 0.0.4

Official OpenAuth plugin modules.
Documentation
use data_encoding::BASE64URL_NOPAD;
use openauth_core::crypto::{symmetric_decrypt, symmetric_encrypt};
use openauth_core::error::OpenAuthError;
use sha2::{Digest, Sha256};
use subtle::ConstantTimeEq;

use super::options::{OtpOptions, OtpStorage};

pub fn generate_otp(digits: usize) -> String {
    use rand::Rng;

    let mut rng = rand::thread_rng();
    (0..digits)
        .map(|_| char::from(b'0' + rng.gen_range(0..10)))
        .collect()
}

pub fn store_otp(otp: &str, secret: &str, options: &OtpOptions) -> Result<String, OpenAuthError> {
    match options.storage {
        OtpStorage::Plain => Ok(otp.to_owned()),
        OtpStorage::Encrypted => symmetric_encrypt(secret, otp),
        OtpStorage::Hashed => Ok(hash_otp(otp)),
    }
}

pub fn verify_stored_otp(
    stored: &str,
    input: &str,
    secret: &str,
    options: &OtpOptions,
) -> Result<bool, OpenAuthError> {
    let expected = match options.storage {
        OtpStorage::Plain => stored.to_owned(),
        OtpStorage::Encrypted => symmetric_decrypt(secret, stored)?,
        OtpStorage::Hashed => stored.to_owned(),
    };
    Ok(expected
        .as_bytes()
        .ct_eq(input_for_compare(input, options).as_bytes())
        .into())
}

fn input_for_compare(input: &str, options: &OtpOptions) -> String {
    match options.storage {
        OtpStorage::Hashed => hash_otp(input),
        OtpStorage::Plain | OtpStorage::Encrypted => input.to_owned(),
    }
}

fn hash_otp(otp: &str) -> String {
    BASE64URL_NOPAD.encode(&Sha256::digest(otp.as_bytes()))
}