forgot password: email and paper key, reset password

This commit is contained in:
Philippe Loctaux 2023-04-19 18:03:38 +02:00
parent 6ddbe013bc
commit 751a21485f
21 changed files with 688 additions and 4 deletions

View file

@ -11,3 +11,4 @@ thiserror = { workspace = true }
chrono = { workspace = true, features = ["serde"] }
email_address = { workspace = true }
serde = { workspace = true }
gen_passphrase = { workspace = true, features = ["eff_large"] }

View file

@ -1,4 +1,5 @@
use crate::error::Error;
use crate::password_reset::PasswordResetToken;
use crate::User;
use database::sqlx::SqliteExecutor;
use database::Error as DatabaseError;
@ -53,7 +54,7 @@ impl User {
.map(Self::from))
}
async fn get_by_email(
pub async fn get_by_email(
conn: impl SqliteExecutor<'_>,
email: &EmailAddress,
) -> Result<Option<Self>, Error> {
@ -108,6 +109,17 @@ impl User {
.map(Self::from))
}
pub async fn get_one_from_password_reset_token(
conn: impl SqliteExecutor<'_>,
token: &PasswordResetToken,
) -> Result<Option<Self>, Error> {
Ok(
DatabaseUsers::get_one_from_password_reset_token(conn, token.to_string().as_str())
.await?
.map(Self::from),
)
}
pub async fn get_all(conn: impl SqliteExecutor<'_>) -> Result<Vec<Self>, Error> {
Ok(DatabaseUsers::get_all(conn)
.await?
@ -198,4 +210,19 @@ impl User {
Ok(())
}
pub async fn set_password_reset_token(
&self,
conn: impl SqliteExecutor<'_>,
token: Option<&PasswordResetToken>,
) -> Result<(), Error> {
DatabaseUsers::set_password_reset_token(
conn,
self.id.as_ref(),
token.map(|t| t.to_string()).as_deref(),
)
.await?;
Ok(())
}
}

View file

@ -1,5 +1,6 @@
mod database;
mod error;
pub mod password_reset;
use chrono::{DateTime, Utc};
use id::UserID;
@ -51,4 +52,7 @@ impl User {
pub fn updated_at(&self) -> DateTime<Utc> {
self.updated_at
}
pub fn paper_key_hashed(&self) -> Option<&str> {
self.paper_key.as_deref()
}
}

View file

@ -0,0 +1,55 @@
use chrono::{DateTime, Duration, TimeZone, Utc};
use serde::{Deserialize, Serialize};
use std::fmt;
// error
#[derive(thiserror::Error)]
// the rest
#[derive(Debug)]
pub enum Error {
#[error("Invalid token format")]
TokenFormat,
#[error("Invalid time format")]
TimeFormat,
}
#[derive(Serialize, Deserialize)]
pub struct PasswordResetToken {
token: String,
expires_at: DateTime<Utc>,
}
impl PasswordResetToken {
pub fn generate(duration_minutes: i64) -> Self {
use gen_passphrase::dictionary::EFF_LARGE;
use gen_passphrase::generate;
let token = generate(&[EFF_LARGE], 10, None);
let expires_at = Utc::now() + Duration::minutes(duration_minutes);
Self { token, expires_at }
}
pub fn parse(raw: &str) -> Result<Self, Error> {
let (token, timestamp_str) = raw.split_once('-').ok_or(Error::TokenFormat)?;
let expires_at = Utc
.datetime_from_str(timestamp_str, "%s")
.map_err(|_| Error::TimeFormat)?;
Ok(Self {
token: token.to_string(),
expires_at,
})
}
pub fn has_expired(&self) -> bool {
self.expires_at < Utc::now()
}
}
impl fmt::Display for PasswordResetToken {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}-{}", self.token, self.expires_at.timestamp())
}
}