aboutsummaryrefslogtreecommitdiff
path: root/logic/src/login.rs
blob: 26a6b7f464f443a2c8c7f6f5812263f51f997643 (plain)
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
/*
    This file is part of jellything (https://codeberg.org/metamuffin/jellything)
    which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
    Copyright (C) 2025 metamuffin <metamuffin.org>
*/
use crate::{CONF, session::create};
use anyhow::{Result, anyhow};
use argon2::{Argon2, PasswordHasher, password_hash::Salt};
use jellybase::database::Database;
use jellycommon::user::UserPermission;
use log::info;
use std::{collections::HashSet, time::Duration};

pub fn create_admin_account(database: &Database) -> Result<()> {
    if let Some(username) = &CONF.admin_username
        && let Some(password) = &CONF.admin_password
    {
        database
            .create_admin_user(username, hash_password(username, password))
            .unwrap();
    } else {
        info!("admin account disabled")
    }
    Ok(())
}

pub fn login_logic(
    database: &Database,
    username: &str,
    password: &str,
    expire: Option<i64>,
    drop_permissions: Option<HashSet<UserPermission>>,
) -> Result<String> {
    // hashing the password regardless if the accounts exists to better resist timing attacks
    let password = hash_password(username, password);

    let mut user = database
        .get_user(username)?
        .ok_or(anyhow!("invalid password"))?;

    if user.password != password {
        Err(anyhow!("invalid password"))?
    }

    if let Some(ep) = drop_permissions {
        // remove all grant perms that are in `ep`
        user.permissions
            .0
            .retain(|p, val| if *val { !ep.contains(p) } else { true })
    }

    Ok(create(
        user.name,
        user.permissions,
        Duration::from_days(
            CONF.login_expire
                .min(expire.unwrap_or(i64::MAX))
                .try_into()
                .unwrap(),
        ),
    ))
}

pub fn hash_password(username: &str, password: &str) -> Vec<u8> {
    Argon2::default()
        .hash_password(
            format!("{username}\0{password}").as_bytes(),
            <&str as TryInto<Salt>>::try_into("IYMa13osbNeLJKnQ1T8LlA").unwrap(),
        )
        .unwrap()
        .hash
        .unwrap()
        .as_bytes()
        .to_vec()
}