本文同步于Rust中文社区
验证用户电子邮件
从第一部分开始,我们现在拥有一个服务器,它从请求中获取一个电子邮件地址,并使用invitation
(邀请)对象发出JSON响应。在第一部分中,我说我们将向用户发送一封电子邮件,经过一番思考和反馈,我们现在将跳过这一部分(请注意第3部分)。我使用的服务是sparkpost,你作为本教程的读者可能没有他们的帐户(免费用于小用途)。
警告:如果没有正确的电子邮件验证,请不要在任何真实应用中使用此解决方法
解决方法
现在我们将使用来自服务器的http响应来验证电子邮件。创建电子邮件验证的最简单方法是让我们的服务器使用通过电子邮件发送到用户电子邮件的某种秘密,并让他们单击带有秘密的链接进行验证。我们可以使用UUID
邀请对象作为秘密。假设客户在使用uuid输入电子邮件后收到邀请67a68837-a059-43e6-a0b8-6e57e6260f0d
。
我们可以发送请求UUID
在网址中注册具有上述内容的新用户。我们的服务器可以获取该id并在数据库中找到Invitation对象,然后将到期日期与当前时间进行比较。如果所有这些条件都成立,我们将让用户注册,否则返回错误响应。现在我们将邀请对象作为解决方法返回给客户端
。电子邮件支持将在第3部分
中实现。
我们可以发送请求UUID在网址中注册具有上述内容的新用户。我们的服务器可以获取该id并在数据库中找到Invitation对象,然后将到期日期与当前时间进行比较。如果所有这些条件都成立,我们将让用户注册,否则返回错误响应。现在我们将邀请对象作为解决方法返回给客户端。电子邮件支持将在第3部分中实现。
错误处理和FROM
Trait
Rust提供了非常强大的工具,我们可以使用它们将一种错误转换为另一种错误。在这个应用程序中,我们将使用不同的插入操作进行一些操作,即使用柴油保存数据,使用bcrypt保存密码等。这些操作可能会返回错误,但我们需要将它们转换为我们的自定义错误类型。std::convert::From
是一个Trait,允许我们转换它。在这里阅读更多有关From
特征的信息。通过实现From
特征,我们可以使用?
运算符来传播将转换为我们的ServiceError
类型的许多不同类型的错误。
我们的错误定义在errors.rs
,让我们通过为uuid
和diesel
错误添加impl From
来实现一些From
特性,我们还将为ServiceError
枚举添加一个Unauthorized
变量。该文件如下所示:
// errors.rs
use actix_web::{error::ResponseError, HttpResponse};
use std::convert::From;
use diesel::result::{DatabaseErrorKind, Error};
use uuid::ParseError;
#[derive(Fail, Debug)]
pub enum ServiceError {
#[fail(display = "Internal Server Error")]
InternalServerError,
#[fail(display = "BadRequest: {}", _0)]
BadRequest(String),
#[fail(display = "Unauthorized")]
Unauthorized,
}
// impl ResponseError trait allows to convert our errors into http responses with appropriate data
impl ResponseError for ServiceError {
fn error_response(&self) -> HttpResponse {
match *self {
ServiceError::InternalServerError => HttpResponse::InternalServerError().json("Internal Server Error, Please try later"),
ServiceError::BadRequest(ref message) => HttpResponse::BadRequest().json(message),
ServiceError::Unauthorized => HttpResponse::Unauthorized().json("Unauthorized")
}
}
}
// we can return early in our handlers if UUID provided by the user is not valid
// and provide a custom message
impl From<ParseError> for ServiceError {
fn from(_: ParseError) -> ServiceError {
ServiceError::BadRequest("Invalid UUID".into())
}
}
impl From<Error> for ServiceError {
fn from(error: Error) -> ServiceError {
// Right now we just care about UniqueViolation from diesel
// But this would be helpful to easily map errors as our app grows
match error {
Error::DatabaseError(kind, info) => {
if let DatabaseErrorKind::UniqueViolation = kind {
let message = info.details().unwrap_or_else(|| info.message()).to_string();
return ServiceError::BadRequest(message);
}
ServiceError::InternalServerError
}
_ => ServiceError::InternalServerError
}
}
}
这一切都将让我们做事变得方便。
得到一些帮助
我们有时需要一些帮助。在将密码存储到数据库之前,我们需要对密码进行哈希处理。在Reddit rust community有一个建议可以使用什么算法。在这里建议argon2
。但为了简单起见,我决定使用bcrypt
。bcrypt算法在生产中被广泛使用,并且bcrypt
crate提供了一个非常好的接口来散列和验证密码。
为了将一些问题分开,我们创建一个新文件src/utils.rs并定义一个帮助程序哈希函数,如下所示。
//utils.rs
use bcrypt::{hash, DEFAULT_COST};
use errors::ServiceError;
use std::env;
pub fn hash_password(plain: &str) -> Result<String, ServiceError> {
// get the hashing cost from the env variable or use default
let hashing_cost: u32 = match env::var("HASH_ROUNDS") {
Ok(cost) => cost.parse().unwrap_or(DEFAULT_COST),