Всегда возвращайте Ok HttpResponse, а затем работайте в обработчике actix-web - PullRequest
1 голос
/ 07 мая 2020

У меня есть обработчик для сброса пароля. Он всегда возвращает успешный код состояния 200, поэтому злоумышленник не может использовать его, чтобы узнать, какие адреса электронной почты хранятся в базе данных. Проблема в том, что если электронное письмо находится в базе данных, потребуется некоторое время, чтобы запрос был выполнен (блокировка поиска пользователя и отправка фактического электронного письма с токеном сброса). Если пользователь не находится в базе данных, запрос возвращается очень быстро, поэтому атакованный будет знать, что электронной почты там нет. фон?

pub async fn forgot_password_handler(
    email_from_path: web::Path<String>,
    pool: web::Data<Pool>,
    redis_client: web::Data<redis::Client>,
) -> HttpResponse {
    let conn: &PgConnection = &pool.get().unwrap();
    let email_address = &email_from_path.into_inner();
    // search for user with email address in users table
    match users.filter(email.eq(email_address)).first::<User>(conn) {
        Ok(user) => {
            // some stuff omitted.. this is what happens:
            // create random token for user and store a hash of it in redis (it'll expire after some time)
            // send email with password reset link and token (not hashed) to client
            // then return with 
            HttpResponse::Ok().finish(),
        }
        _ => HttpResponse::Ok().finish(),
    }   
}

1 Ответ

2 голосов
/ 07 мая 2020

Вы можете использовать Actix Arbiter для планирования асинхронной задачи:

use actix::Arbiter;

async fn do_the_database_stuff(
    email: String,
    pool: web::Data<Pool>,
    redis_client: web::Data<redis::Client>)
{
    // async database code here
}

pub async fn forgot_password_handler(
    email_from_path: web::Path<String>,
    pool: web::Data<Pool>,
    redis_client: web::Data<redis::Client>,
) -> HttpResponse {

    let email = email_from_path.clone();
    Arbiter::spawn(async {
        do_the_database_stuff(
            email,
            pool,
            redis_client
        );
    });

    HttpResponse::Ok().finish()
}

Если код вашей базы данных блокируется, чтобы предотвратить перегрузку долгоживущих рабочих потоков Actix, вместо создайте новый Arbiter со своим собственным потоком:

fn do_the_database_stuff(email: String) {
    // blocking database code here
}

pub async fn forgot_password_handler(email_from_path: String) -> HttpResponse {
    let email = email_from_path.clone();
    Arbiter::new().exec_fn(move || { 
        async move {               
            do_the_database_stuff(email).await; 
        };
    });

    HttpResponse::Ok().finish()
}

Это может потребовать немного больше работы, потому что Pool и redis::Client вряд ли будут безопасно делиться между потоками, поэтому у вас будет чтобы решить и это. Вот почему я не включил их в пример кода.

Лучше использовать Arbiter s, чем поддаваться соблазну создать новый собственный поток с std::thread. Если вы смешаете их, вы можете случайно включить код, который нарушит работу воркера. Например, использование std::thread::sleep в контексте async приведет к приостановке несвязанных задач, которые случайно были запланированы для одного и того же воркера, и может даже не повлиять на поставленную вами задачу.


Наконец , вы также можете подумать об изменении архитектуры. Если вы разложите задачи, связанные с базами данных, на их собственные микросервисы, вы решите эту проблему автоматически. Затем веб-обработчик может просто отправить сообщение (Kafka, RabbitMQ, ZMQ, HTTP или что угодно по вашему выбору) и немедленно вернуться. Это позволит вам масштабировать микросервисы независимо от веб-сервера - 10 экземпляров веб-сервера не обязательно означают 10 подключений к базе данных, если вам нужен только один экземпляр для службы сброса пароля.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...