Implement EmailSignup
This commit is contained in:
		
							parent
							
								
									192c7677c3
								
							
						
					
					
						commit
						a65775d85b
					
				
							
								
								
									
										141
									
								
								plume-models/src/email_signups.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								plume-models/src/email_signups.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,141 @@
 | 
				
			|||||||
 | 
					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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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)
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn confirm(conn: &DbConn, token: &Token, email: &str) -> Result<Self> {
 | 
				
			||||||
 | 
					        Self::ensure_user_not_exist_by_email(conn, email)?;
 | 
				
			||||||
 | 
					        let signup: Self = email_signups::table
 | 
				
			||||||
 | 
					            .filter(email_signups::token.eq(token.as_str()))
 | 
				
			||||||
 | 
					            .first(&**conn)
 | 
				
			||||||
 | 
					            .map_err(Error::from)?;
 | 
				
			||||||
 | 
					        if signup.expired() {
 | 
				
			||||||
 | 
					            Self::delete_existings_by_email(conn, email)?;
 | 
				
			||||||
 | 
					            return Err(Error::Expired);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Ok(signup)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn complete(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        conn: &DbConn,
 | 
				
			||||||
 | 
					        username: String,
 | 
				
			||||||
 | 
					        display_name: String,
 | 
				
			||||||
 | 
					        summary: &str,
 | 
				
			||||||
 | 
					        password: String,
 | 
				
			||||||
 | 
					    ) -> Result<User> {
 | 
				
			||||||
 | 
					        Self::ensure_user_not_exist_by_email(conn, &self.email)?;
 | 
				
			||||||
 | 
					        let user = NewUser::new_local(
 | 
				
			||||||
 | 
					            conn,
 | 
				
			||||||
 | 
					            username,
 | 
				
			||||||
 | 
					            display_name,
 | 
				
			||||||
 | 
					            Role::Normal,
 | 
				
			||||||
 | 
					            summary,
 | 
				
			||||||
 | 
					            self.email.clone(),
 | 
				
			||||||
 | 
					            Some(password),
 | 
				
			||||||
 | 
					        )?;
 | 
				
			||||||
 | 
					        self.delete(conn)?;
 | 
				
			||||||
 | 
					        Ok(user)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -67,6 +67,7 @@ pub enum Error {
 | 
				
			|||||||
    Url,
 | 
					    Url,
 | 
				
			||||||
    Webfinger,
 | 
					    Webfinger,
 | 
				
			||||||
    Expired,
 | 
					    Expired,
 | 
				
			||||||
 | 
					    UserAlreadyExists,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl From<bcrypt::BcryptError> for Error {
 | 
					impl From<bcrypt::BcryptError> for Error {
 | 
				
			||||||
@ -376,6 +377,7 @@ pub mod blogs;
 | 
				
			|||||||
pub mod comment_seers;
 | 
					pub mod comment_seers;
 | 
				
			||||||
pub mod comments;
 | 
					pub mod comments;
 | 
				
			||||||
pub mod db_conn;
 | 
					pub mod db_conn;
 | 
				
			||||||
 | 
					pub mod email_signups;
 | 
				
			||||||
pub mod follows;
 | 
					pub mod follows;
 | 
				
			||||||
pub mod headers;
 | 
					pub mod headers;
 | 
				
			||||||
pub mod inbox;
 | 
					pub mod inbox;
 | 
				
			||||||
 | 
				
			|||||||
@ -202,6 +202,22 @@ impl User {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * TODO: Should create user record with normalized(lowercased) email
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    pub fn email_used(conn: &DbConn, email: &str) -> Result<bool> {
 | 
				
			||||||
 | 
					        use diesel::dsl::{exists, select};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        select(exists(
 | 
				
			||||||
 | 
					            users::table
 | 
				
			||||||
 | 
					                .filter(users::instance_id.eq(Instance::get_local()?.id))
 | 
				
			||||||
 | 
					                .filter(users::email.eq(email))
 | 
				
			||||||
 | 
					                .or_filter(users::email.eq(email.to_ascii_lowercase())),
 | 
				
			||||||
 | 
					        ))
 | 
				
			||||||
 | 
					        .get_result(&**conn)
 | 
				
			||||||
 | 
					        .map_err(Error::from)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn fetch_from_webfinger(conn: &DbConn, acct: &str) -> Result<User> {
 | 
					    fn fetch_from_webfinger(conn: &DbConn, acct: &str) -> Result<User> {
 | 
				
			||||||
        let link = resolve(acct.to_owned(), true)?
 | 
					        let link = resolve(acct.to_owned(), true)?
 | 
				
			||||||
            .links
 | 
					            .links
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user