aboutsummaryrefslogtreecommitdiff
path: root/server/src/routes/admin/users.rs
blob: ee8a0c91dee40ab9bb862d0a41e4b06ec0a0e47b (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
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
/*
    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) 2026 metamuffin <metamuffin.org>
*/

use std::str::FromStr;

use crate::{
    auth::hash_password, request_info::RequestInfo, responders::UiPage, routes::error::MyResult,
};
use anyhow::anyhow;
use base64::{Engine, prelude::BASE64_URL_SAFE};
use jellycommon::{
    jellyobject::{ObjectBufferBuilder, Path},
    routes::u_admin_users,
    *,
};
use jellydb::{Filter, Query};
use jellyui::{
    components::admin::{AdminUser, AdminUserList},
    tr,
};
use rand::random;
use rocket::{
    FromForm,
    form::Form,
    get, post,
    response::{Flash, Redirect},
};

#[get("/admin/users")]
pub fn r_admin_users(ri: RequestInfo) -> MyResult<UiPage> {
    ri.require_admin()?;

    let mut users = Vec::new();
    ri.state.database.transaction(&mut |txn| {
        users.clear();
        let rows = txn
            .query(Query::from_str("FILTER Ulgn")?)?
            .collect::<Vec<_>>();
        for row in rows {
            let (row, _) = row?;
            users.push(txn.get(row)?.unwrap());
        }
        Ok(())
    })?;

    Ok(ri.respond_ui(&AdminUserList {
        ri: &ri.render_info(),
        users: &users.iter().map(|u| &**u).collect::<Vec<_>>(),
    }))
}

#[derive(FromForm)]
pub struct NewUser {
    login: String,
}

#[post("/admin/new_user", data = "<form>")]
pub fn r_admin_new_user(ri: RequestInfo, form: Form<NewUser>) -> MyResult<Flash<Redirect>> {
    ri.require_admin()?;

    let password = BASE64_URL_SAFE.encode([(); 12].map(|()| random()));
    let password_hashed = hash_password(&form.login, &password);

    ri.state.database.transaction(&mut |txn| {
        let mut user = ObjectBufferBuilder::default();
        user.push(USER_LOGIN, &form.login);
        user.push(USER_PASSWORD, &password_hashed);
        user.push(USER_PASSWORD_REQUIRE_CHANGE, ());
        txn.insert(user.finish())?;
        Ok(())
    })?;

    Ok(Flash::success(
        Redirect::to(u_admin_users()),
        format!("User created; password: {password}"),
    ))
}

#[get("/admin/user/<name>")]
pub fn r_admin_user(ri: RequestInfo<'_>, name: &str) -> MyResult<UiPage> {
    ri.require_admin()?;
    let mut user = None;
    ri.state.database.transaction(&mut |txn| {
        if let Some(row) = txn.query_single(Query {
            filter: Filter::Match(Path(vec![USER_LOGIN.0]), name.into()),
            ..Default::default()
        })? {
            user = Some(txn.get(row)?.unwrap());
        }
        Ok(())
    })?;
    let Some(user) = user else {
        Err(anyhow!("no such user"))?
    };

    Ok(ri.respond_ui(&AdminUser {
        ri: &ri.render_info(),
        user: &user,
    }))
}

#[post("/admin/user/<name>/remove")]
pub fn r_admin_user_remove(ri: RequestInfo<'_>, name: &str) -> MyResult<Flash<Redirect>> {
    ri.require_admin()?;
    ri.state.database.transaction(&mut |txn| {
        if let Some(row) = txn.query_single(Query {
            filter: Filter::Match(Path(vec![USER_LOGIN.0]), name.into()),
            ..Default::default()
        })? {
            txn.remove(row)?;
        }
        Ok(())
    })?;
    Ok(Flash::success(
        Redirect::to(u_admin_users()),
        tr(ri.lang, "admin.users.remove_success"),
    ))
}