essence 0.3.3

Essential models and database logic for the Adapt chat platform.
Documentation
use crate::db::DbExt;
use crate::models::UserFlags;

#[async_trait::async_trait]
pub trait AuthDbExt<'t>: DbExt<'t> {
    /// Fetches the password hash for the given user ID and verifies it against the given password.
    ///
    /// # Note
    /// This assumes that the user is not a bot account and has a password that is not NULL.
    ///
    /// # Errors
    /// * If an error occurs with fetching the user.
    /// * If the user is not found.
    #[cfg(feature = "auth")]
    async fn verify_password(&self, user_id: u64, password: String) -> crate::Result<bool> {
        let hashed: String = sqlx::query!(
            r#"SELECT password AS "password!" FROM users WHERE id = $1"#,
            user_id as i64,
        )
        .fetch_one(self.executor())
        .await?
        .password;

        Ok(crate::auth::verify_password(password, hashed).await?)
    }

    /// Fetches a user token from the database with the given user ID.
    ///
    /// # Errors
    /// * If an error occurs with fetching the user token. If the user token is not found,
    /// `Ok(None)` is returned.
    async fn fetch_token(&self, user_id: u64) -> sqlx::Result<Option<String>> {
        sqlx::query!(
            "SELECT token FROM tokens WHERE user_id = $1",
            user_id as i64
        )
        .fetch_optional(self.executor())
        .await
        .map(|r| r.map(|r| r.token))
    }

    /// Resolves a user ID and their user flags from a token. Returns `(user_id, flags)`.
    ///
    /// # Errors
    /// * If an error occurs with fetching the user token. If the user token is not found,
    /// `Ok(None)` is returned.
    async fn fetch_user_info_by_token(
        &self,
        token: impl AsRef<str> + Send,
    ) -> sqlx::Result<Option<(u64, UserFlags)>> {
        sqlx::query!(
            "SELECT id, flags FROM users WHERE id = (SELECT user_id FROM tokens WHERE token = $1)",
            token.as_ref(),
        )
        .fetch_optional(self.executor())
        .await
        .map(|r| r.map(|r| (r.id as u64, UserFlags::from_bits_truncate(r.flags as u32))))
    }

    /// Creates a new token for the given user ID.
    ///
    /// # Note
    /// This method uses transactions, on the event of an ``Err`` the transaction must be properly
    /// rolled back, and the transaction must be committed to save the changes.
    ///
    /// # Errors
    /// * If an error occurs with creating the token.
    async fn create_token(
        &mut self,
        user_id: u64,
        token: impl AsRef<str> + Send,
    ) -> sqlx::Result<()> {
        sqlx::query!(
            "INSERT INTO tokens (user_id, token) VALUES ($1, $2)",
            user_id as i64,
            token.as_ref(),
        )
        .execute(self.transaction())
        .await
        .map(|_| ())
    }

    /// Deletes all tokens associated with the given user ID.
    ///
    /// # Note
    /// This method uses transactions, on the event of an ``Err`` the transaction must be properly
    /// rolled back, and the transaction must be committed to save the changes.
    ///
    /// # Errors
    /// * If an error occurs with deleting the tokens.
    async fn delete_all_tokens(&mut self, user_id: u64) -> sqlx::Result<()> {
        sqlx::query!("DELETE FROM tokens WHERE user_id = $1", user_id as i64)
            .execute(self.transaction())
            .await
            .map(|_| ())
    }
}

impl<'t, T> AuthDbExt<'t> for T where T: DbExt<'t> {}