view src/configdb.rs @ 77:fd0237049be0

Show paths instead of tags & apply rust fixes
author Lewin Bormann <lbo@spheniscida.de>
date Mon, 31 Jul 2023 09:31:37 +0200
parents a58e1922e173
children d3368a7142ef
line wrap: on
line source

use crate::db::{PoolType};

use anyhow::Error;
use log::{warn};

use rand::distributions::{Alphanumeric, DistString};

use rocket_db_pools::sqlx::{Executor, Row, Sqlite};
use rocket_db_pools::{Database, Pool};
use sqlx::prelude::FromRow;

fn generate_salt() -> String {
    Alphanumeric.sample_string(&mut rand::thread_rng(), 16)
}

// TO DO: use other databases?
#[derive(Database)]
#[database("sqlite_main")]
pub struct ConfigDB(pub PoolType);

#[derive(Default, Clone, Debug, FromRow)]
pub struct UserRecord {
    pub name: String,
    pub tz_offset: i64,
}

pub struct ConfigDBSession<'p, DB: sqlx::Database>(pub &'p mut sqlx::pool::PoolConnection<DB>);

impl<'p> ConfigDBSession<'p, Sqlite> {
    pub async fn check_user_password<S0: AsRef<str>, S1: AsRef<str>>(
        &mut self,
        user: S0,
        password: S1,
    ) -> Result<bool, Error> {
        // TODO: salt passwords.
        let salt: String = match sqlx::query("SELECT salt FROM users WHERE username = ? LIMIT 1;")
            .bind(user.as_ref())
            .fetch_one(&mut *self.0)
            .await
        {
            Ok(r) => r.get(0),
            Err(e) => {
                warn!("Error querying salt: {}", e);
                return Ok(false);
            }
        };
        let pwdhash = sha256::digest(format!("{}{}", salt, password.as_ref()));
        let q = sqlx::query("SELECT username FROM users WHERE username = ? AND password_hash = ?;")
            .bind(user.as_ref())
            .bind(pwdhash);
        let result = self.0.fetch_all(q).await?;
        Ok(result.len() == 1)
    }

    pub async fn get_user_details(&mut self, user: &str) -> Result<UserRecord, Error> {
        let entry = sqlx::query("SELECT name, tz_offset FROM users WHERE username = ? LIMIT 1")
            .bind(user)
            .fetch_one(&mut *self.0)
            .await?;
        Ok(UserRecord::from_row(&entry)?)
    }

    pub async fn update_user_password<S0: AsRef<str>, S1: AsRef<str>, S2: AsRef<str>>(
        &mut self,
        user: S0,
        oldpass: S1,
        newpass: S2,
    ) -> Result<(), Error> {
        if !self.check_user_password(user, oldpass).await? {
            anyhow::bail!("User not authorized (wrong password?)")
        }

        // Old password is ok.
        let salt = generate_salt();
        let newpass_hash = sha256::digest(format!("{}{}", salt, newpass.as_ref()));

        sqlx::query("UPDATE users SET salt = ?, password_hash = ?;")
            .bind(salt)
            .bind(newpass_hash)
            .execute(&mut *self.0)
            .await?;

        Ok(())
    }

    pub async fn update_user_tz<S0: AsRef<str>>(
        &mut self,
        user: S0,
        tz_offset: i64,
    ) -> Result<(), Error> {
        if sqlx::query("SELECT username FROM users WHERE username = ?")
            .bind(user.as_ref())
            .fetch_all(&mut *self.0)
            .await?
            .len()
            != 1
        {
            anyhow::bail!("User not found, or duplicate user {}", user.as_ref())
        }
        sqlx::query("UPDATE users SET tz_offset = ? WHERE username = ?")
            .bind(tz_offset)
            .bind(user.as_ref())
            .execute(&mut *self.0)
            .await?;
        Ok(())
    }

    pub async fn is_admin<S0: AsRef<str>>(&mut self, user: S0) -> Result<bool, Error> {
        Ok(sqlx::query("SELECT is_admin FROM users JOIN permissions ON (users.id = permissions.user_id) WHERE username = ?")
            .bind(user.as_ref())
            .fetch_one(&mut *self.0)
            .await?
            .get(0))
    }
}