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
//! [`Authenticator`] implementation that authenticates against a JSON file.
//!
//! [`Authenticator`]: libunftp::auth::Authenticator

use async_trait::async_trait;
use libunftp::auth::{AuthenticationError, Authenticator, DefaultUser};
use serde::Deserialize;
use std::{fs, time::Duration};
use tokio::time::sleep;

#[derive(Deserialize, Clone, Debug)]
struct Credentials {
    username: String,
    password: String,
}

/// [`Authenticator`](libunftp::auth::Authenticator) implementation that authenticates against a JSON file.
///
/// Example credentials file format:
/// [
//   {
//     "username": "alice",
//     "password": "12345678"
//   },
//   {
//     "username": "bob",
//     "password": "secret"
//   }
// ]
#[derive(Clone, Debug)]
pub struct JsonFileAuthenticator {
    credentials_list: Vec<Credentials>,
}

impl JsonFileAuthenticator {
    /// Initialize a new [`JsonFileAuthenticator`] from file.
    pub fn new<T: Into<String>>(filename: T) -> Result<Self, Box<dyn std::error::Error>> {
        let s = fs::read_to_string(filename.into())?;
        let credentials_list: Vec<Credentials> = serde_json::from_str(&s)?;
        Ok(JsonFileAuthenticator { credentials_list })
    }
}

#[async_trait]
impl Authenticator<DefaultUser> for JsonFileAuthenticator {
    #[allow(clippy::type_complexity)]
    #[tracing_attributes::instrument]
    async fn authenticate(&self, username: &str, password: &str) -> Result<DefaultUser, AuthenticationError> {
        let credentials_list = self.credentials_list.clone();

        for c in credentials_list.iter() {
            if username == &*c.username {
                if password == &*c.password {
                    return Ok(DefaultUser {});
                } else {
                    // punish the failed login with a 1500ms delay before returning the error
                    sleep(Duration::from_millis(1500)).await;
                    return Err(AuthenticationError::BadPassword);
                }
            }
        }
        // punish the failed login with a 1500ms delay before returning the error
        sleep(Duration::from_millis(1500)).await;
        Err(AuthenticationError::BadUser)
    }
}