    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136

    /*
     * Copyright (C) 2022  Aravinth Manivannan <realaravinth@batsense.net>
     *
     * This program is free software: you can redistribute it and/or modify
     * it under the terms of the GNU Affero General Public License as
     * published by the Free Software Foundation, either version 3 of the
     * License, or (at your option) any later version.
     *
     * This program is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     * GNU Affero General Public License for more details.
     *
     * You should have received a copy of the GNU Affero General Public License
     * along with this program.  If not, see <https://www.gnu.org/licenses/>.
     */
    //! Account management utility datastructures and methods
    use serde::{Deserialize, Serialize};

    pub use super::auth;
    use crate::ctx::Ctx;
    use crate::db;
    use crate::errors::*;

    #[derive(Clone, Debug, Deserialize, Serialize)]
    /// Data structure used in `*_exists` methods
    pub struct AccountCheckResp {
        /// set to true if the attribute in question exists
        pub exists: bool,
    }

    /// Data structure used to change password of a registered user
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub struct ChangePasswordReqest {
        /// current password
        pub password: String,
        /// new password
        pub new_password: String,
        /// new password confirmation
        pub confirm_new_password: String,
    }

    impl Ctx {
        /// check if email exists on database
        pub async fn email_exists(&self, email: &str) -> ServiceResult<AccountCheckResp> {
            let resp = AccountCheckResp {
                exists: self.db.email_exists(email).await?,
            };

            Ok(resp)
        }

        /// update email
        pub async fn set_email(&self, username: &str, new_email: &str) -> ServiceResult<()> {
            self.creds.email(new_email)?;

            let username = self.creds.username(username)?;

            let payload = db::UpdateEmail {
                username: &username,
                new_email,
            };
            self.db.update_email(&payload).await?;
            Ok(())
        }

        /// check if email exists in database
        pub async fn username_exists(&self, username: &str) -> ServiceResult<AccountCheckResp> {
            let processed_uname = self.creds.username(username)?;
            let resp = AccountCheckResp {
                exists: self.db.username_exists(&processed_uname).await?,
            };
            Ok(resp)
        }

        /// update username of a registered user
        pub async fn update_username(
            &self,
            current_username: &str,
            new_username: &str,
        ) -> ServiceResult<String> {
            let processed_uname = self.creds.username(new_username)?;

            self.db
                .update_username(current_username, &processed_uname)
                .await?;

            Ok(processed_uname)
        }

        // returns Ok(()) upon successful authentication
        async fn authenticate(&self, username: &str, password: &str) -> ServiceResult<()> {
            use argon2_creds::Config;
            let username = self.creds.username(username)?;
            let resp = self
                .db
                .get_password(&db::Login::Username(&username))
                .await?;
            if Config::verify(&resp.hash, password)? {
                Ok(())
            } else {
                Err(ServiceError::WrongPassword)
            }
        }

        /// delete user
        pub async fn delete_user(&self, username: &str, password: &str) -> ServiceResult<()> {
            let username = self.creds.username(username)?;
            self.authenticate(&username, password).await?;
            self.db.delete_user(&username).await?;
            Ok(())
        }

        /// change password
        pub async fn change_password(
            &self,

            username: &str,
            payload: &ChangePasswordReqest,
        ) -> ServiceResult<()> {
            if payload.new_password != payload.confirm_new_password {
                return Err(ServiceError::PasswordsDontMatch);
            }

            self.authenticate(username, &payload.password).await?;

            let hash = self.creds.password(&payload.new_password)?;

            let username = self.creds.username(username)?;
            let db_payload = db::NameHash { username, hash };

            self.db.update_password(&db_payload).await?;

            Ok(())
        }
    }