2022-01-04 19:15:51 +01:00
|
|
|
use crate::{
|
|
|
|
db_conn::DbConn,
|
|
|
|
schema::email_signups,
|
|
|
|
users::{NewUser, Role, User},
|
|
|
|
Error, Result,
|
|
|
|
};
|
|
|
|
use chrono::{offset::Utc, Duration, NaiveDateTime};
|
|
|
|
use diesel::{
|
|
|
|
Connection as _, ExpressionMethods, Identifiable, Insertable, QueryDsl, Queryable, RunQueryDsl,
|
|
|
|
};
|
|
|
|
use plume_common::utils::random_hex;
|
|
|
|
use std::ops::Deref;
|
|
|
|
|
|
|
|
const TOKEN_VALIDITY_HOURS: i64 = 2;
|
|
|
|
|
2022-01-06 10:40:24 +01:00
|
|
|
#[repr(transparent)]
|
2022-01-04 19:15:51 +01:00
|
|
|
pub struct Token(String);
|
|
|
|
|
|
|
|
impl From<String> for Token {
|
|
|
|
fn from(string: String) -> Self {
|
|
|
|
Token(string)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<Token> for String {
|
|
|
|
fn from(token: Token) -> Self {
|
|
|
|
token.0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Deref for Token {
|
|
|
|
type Target = String;
|
|
|
|
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
&self.0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Token {
|
|
|
|
fn generate() -> Self {
|
|
|
|
Self(random_hex())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Identifiable, Queryable)]
|
|
|
|
pub struct EmailSignup {
|
|
|
|
pub id: i32,
|
|
|
|
pub email: String,
|
|
|
|
pub token: String,
|
|
|
|
pub expiration_date: NaiveDateTime,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Insertable)]
|
|
|
|
#[table_name = "email_signups"]
|
|
|
|
pub struct NewEmailSignup<'a> {
|
|
|
|
pub email: &'a str,
|
|
|
|
pub token: &'a str,
|
|
|
|
pub expiration_date: NaiveDateTime,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl EmailSignup {
|
|
|
|
pub fn start(conn: &DbConn, email: &str) -> Result<Token> {
|
|
|
|
conn.transaction(|| {
|
|
|
|
Self::ensure_user_not_exist_by_email(conn, email)?;
|
|
|
|
let _rows = Self::delete_existings_by_email(conn, email)?;
|
|
|
|
let token = Token::generate();
|
|
|
|
let expiration_date = Utc::now()
|
|
|
|
.naive_utc()
|
|
|
|
.checked_add_signed(Duration::hours(TOKEN_VALIDITY_HOURS))
|
|
|
|
.expect("could not calculate expiration date");
|
|
|
|
let new_signup = NewEmailSignup {
|
|
|
|
email,
|
|
|
|
token: &token,
|
|
|
|
expiration_date,
|
|
|
|
};
|
|
|
|
let _rows = diesel::insert_into(email_signups::table)
|
|
|
|
.values(new_signup)
|
|
|
|
.execute(&**conn)?;
|
|
|
|
|
|
|
|
Ok(token)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-01-06 12:18:20 +01:00
|
|
|
pub fn find_by_token(conn: &DbConn, token: Token) -> Result<Self> {
|
|
|
|
let signup = email_signups::table
|
2022-01-04 19:15:51 +01:00
|
|
|
.filter(email_signups::token.eq(token.as_str()))
|
2022-01-06 12:18:20 +01:00
|
|
|
.first::<Self>(&**conn)
|
2022-01-04 19:15:51 +01:00
|
|
|
.map_err(Error::from)?;
|
2022-01-06 12:18:20 +01:00
|
|
|
Ok(signup)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn confirm(&self, conn: &DbConn) -> Result<()> {
|
2022-01-06 12:27:55 +01:00
|
|
|
conn.transaction(|| {
|
|
|
|
Self::ensure_user_not_exist_by_email(conn, &self.email)?;
|
|
|
|
if self.expired() {
|
|
|
|
Self::delete_existings_by_email(conn, &self.email)?;
|
|
|
|
return Err(Error::Expired);
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
})
|
2022-01-04 19:15:51 +01:00
|
|
|
}
|
|
|
|
|
2022-01-06 12:18:20 +01:00
|
|
|
pub fn complete(&self, conn: &DbConn, username: String, password: String) -> Result<User> {
|
2022-01-06 12:27:55 +01:00
|
|
|
conn.transaction(|| {
|
|
|
|
Self::ensure_user_not_exist_by_email(conn, &self.email)?;
|
|
|
|
let user = NewUser::new_local(
|
|
|
|
conn,
|
|
|
|
username,
|
|
|
|
"".to_string(),
|
|
|
|
Role::Normal,
|
|
|
|
"",
|
|
|
|
self.email.clone(),
|
|
|
|
Some(User::hash_pass(&password)?),
|
|
|
|
)?;
|
|
|
|
self.delete(conn)?;
|
|
|
|
Ok(user)
|
|
|
|
})
|
2022-01-04 19:15:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
fn delete(&self, conn: &DbConn) -> Result<()> {
|
|
|
|
let _rows = diesel::delete(self).execute(&**conn).map_err(Error::from)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn ensure_user_not_exist_by_email(conn: &DbConn, email: &str) -> Result<()> {
|
|
|
|
if User::email_used(conn, email)? {
|
|
|
|
let _rows = Self::delete_existings_by_email(conn, email)?;
|
|
|
|
return Err(Error::UserAlreadyExists);
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn delete_existings_by_email(conn: &DbConn, email: &str) -> Result<usize> {
|
|
|
|
let existing_signups = email_signups::table.filter(email_signups::email.eq(email));
|
|
|
|
diesel::delete(existing_signups)
|
|
|
|
.execute(&**conn)
|
|
|
|
.map_err(Error::from)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn expired(&self) -> bool {
|
|
|
|
self.expiration_date < Utc::now().naive_utc()
|
|
|
|
}
|
|
|
|
}
|