Implement the rest of the user management CLI commands
This commit is contained in:
parent
08021e2547
commit
f54f3e0f24
15
.sqlx/query-5531ef5a44d20da0d6286d101218deaed394f50e9cb38f4131dd5ce556878bae.json
generated
Normal file
15
.sqlx/query-5531ef5a44d20da0d6286d101218deaed394f50e9cb38f4131dd5ce556878bae.json
generated
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE users SET locked = $1 WHERE user_id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Bool",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "5531ef5a44d20da0d6286d101218deaed394f50e9cb38f4131dd5ce556878bae"
|
||||
}
|
32
.sqlx/query-bbaa41c568bee22eae314186e0d7402f235f8eba9a4ed6adb7bfb6824ec86396.json
generated
Normal file
32
.sqlx/query-bbaa41c568bee22eae314186e0d7402f235f8eba9a4ed6adb7bfb6824ec86396.json
generated
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT user_name, user_id, locked FROM users ORDER BY user_name",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "user_name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "user_id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "locked",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "bbaa41c568bee22eae314186e0d7402f235f8eba9a4ed6adb7bfb6824ec86396"
|
||||
}
|
14
.sqlx/query-dfa520877c017cd5808d02c24ef2d71938b68093974f335a4d89df91874fdaa2.json
generated
Normal file
14
.sqlx/query-dfa520877c017cd5808d02c24ef2d71938b68093974f335a4d89df91874fdaa2.json
generated
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM users WHERE user_id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "dfa520877c017cd5808d02c24ef2d71938b68093974f335a4d89df91874fdaa2"
|
||||
}
|
60
Cargo.lock
generated
60
Cargo.lock
generated
@ -612,6 +612,18 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "comfy-table"
|
||||
version = "7.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c64043d6c7b7a4c58e39e7efccfdea7b93d885a795d0c054a69dbbf4dd52686"
|
||||
dependencies = [
|
||||
"crossterm",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "confique"
|
||||
version = "0.2.4"
|
||||
@ -759,6 +771,28 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"crossterm_winapi",
|
||||
"libc",
|
||||
"parking_lot",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm_winapi"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.2"
|
||||
@ -1561,6 +1595,7 @@ dependencies = [
|
||||
"base64",
|
||||
"chrono",
|
||||
"clap",
|
||||
"comfy-table",
|
||||
"confique",
|
||||
"eyre",
|
||||
"futures",
|
||||
@ -3149,6 +3184,25 @@ version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.25.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
|
||||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.5.0"
|
||||
@ -3559,6 +3613,12 @@ version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
|
||||
|
||||
[[package]]
|
||||
name = "unicode_categories"
|
||||
version = "0.1.1"
|
||||
|
@ -15,6 +15,7 @@ axum_csrf = { version = "0.7.2", features = ["layer"] }
|
||||
base64 = "0.21.5"
|
||||
chrono = "0.4.31"
|
||||
clap = { version = "4.4.6", features = ["derive"] }
|
||||
comfy-table = "7.1.0"
|
||||
confique = { version = "0.2.4", features = ["toml"], default-features = false }
|
||||
eyre = "0.6.8"
|
||||
futures = "0.3.29"
|
||||
|
@ -3,8 +3,10 @@ use std::sync::Arc;
|
||||
use std::{net::SocketAddr, path::PathBuf};
|
||||
|
||||
use clap::Parser;
|
||||
use comfy_table::presets::UTF8_FULL;
|
||||
use comfy_table::{Attribute, Cell, Color, ContentArrangement, Row, Table};
|
||||
use confique::{Config, Partial};
|
||||
use eyre::{bail, Context};
|
||||
use eyre::{bail, Context, ContextCompat};
|
||||
use idcoop::config::SecretConfig;
|
||||
use idcoop::passwords::create_password_hash;
|
||||
use idcoop::store::{CreateUser, IdCoopStore};
|
||||
@ -124,7 +126,11 @@ enum UserCommand {
|
||||
},
|
||||
/// Lists all users that are registered.
|
||||
#[clap(alias = "ls")]
|
||||
ListAll {},
|
||||
ListAll {
|
||||
/// Only show a list of usernames, without table formatting characters and one per line. May be useful in scripts.
|
||||
#[clap(long = "usernames")]
|
||||
usernames: bool,
|
||||
},
|
||||
}
|
||||
|
||||
async fn handle_user_command(command: UserCommand, config: &Configuration) -> eyre::Result<()> {
|
||||
@ -147,9 +153,48 @@ async fn handle_user_command(command: UserCommand, config: &Configuration) -> ey
|
||||
.await
|
||||
.context("failed to add user")?;
|
||||
}
|
||||
UserCommand::Delete { username } => todo!(),
|
||||
UserCommand::Lock { username } => todo!(),
|
||||
UserCommand::Unlock { username } => todo!(),
|
||||
UserCommand::Delete { username } => {
|
||||
store
|
||||
.txn(|mut txn| {
|
||||
Box::pin(async move {
|
||||
let user_id = txn
|
||||
.lookup_user_by_name(username)
|
||||
.await?
|
||||
.context("No user by that name")?
|
||||
.user_id;
|
||||
txn.delete_user(user_id).await
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
UserCommand::Lock { username } => {
|
||||
store
|
||||
.txn(|mut txn| {
|
||||
Box::pin(async move {
|
||||
let user_id = txn
|
||||
.lookup_user_by_name(username)
|
||||
.await?
|
||||
.context("No user by that name")?
|
||||
.user_id;
|
||||
txn.set_user_locked(user_id, true).await
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
UserCommand::Unlock { username } => {
|
||||
store
|
||||
.txn(|mut txn| {
|
||||
Box::pin(async move {
|
||||
let user_id = txn
|
||||
.lookup_user_by_name(username)
|
||||
.await?
|
||||
.context("No user by that name")?
|
||||
.user_id;
|
||||
txn.set_user_locked(user_id, false).await
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
UserCommand::ChangePassword { username } => {
|
||||
let Some(user) = store.txn(|mut txn| { Box::pin(async move {
|
||||
txn.lookup_user_by_name(username).await
|
||||
@ -175,7 +220,43 @@ async fn handle_user_command(command: UserCommand, config: &Configuration) -> ey
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
UserCommand::ListAll {} => todo!(),
|
||||
UserCommand::ListAll { usernames } => {
|
||||
let user_infos = store
|
||||
.txn(|mut txn| Box::pin(async move { txn.list_user_info().await }))
|
||||
.await?;
|
||||
|
||||
if usernames {
|
||||
for user_info in user_infos {
|
||||
println!("{}", user_info.user_name);
|
||||
}
|
||||
} else {
|
||||
let mut table = Table::new();
|
||||
table
|
||||
.load_preset(UTF8_FULL)
|
||||
.set_content_arrangement(ContentArrangement::Dynamic)
|
||||
.set_width(80)
|
||||
.set_header(vec![
|
||||
Cell::new("Name").add_attribute(Attribute::Bold),
|
||||
Cell::new("UUID").add_attribute(Attribute::Bold),
|
||||
Cell::new("Locked").add_attribute(Attribute::Bold),
|
||||
]);
|
||||
|
||||
for user_info in user_infos {
|
||||
let mut row = Row::new();
|
||||
row.add_cell(Cell::new(user_info.user_name));
|
||||
row.add_cell(Cell::new(user_info.user_id).fg(Color::Grey));
|
||||
let (lock_str, lock_colour) = if user_info.locked {
|
||||
("yes", Color::Red)
|
||||
} else {
|
||||
("no", Color::White)
|
||||
};
|
||||
row.add_cell(Cell::new(lock_str).fg(lock_colour));
|
||||
table.add_row(row);
|
||||
}
|
||||
|
||||
println!("{}", table);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
36
src/store.rs
36
src/store.rs
@ -64,6 +64,12 @@ pub struct CreateUser {
|
||||
pub locked: bool,
|
||||
}
|
||||
|
||||
pub struct UserInfo {
|
||||
pub user_name: String,
|
||||
pub user_id: Uuid,
|
||||
pub locked: bool,
|
||||
}
|
||||
|
||||
pub struct IdCoopStoreTxn<'a, 'txn> {
|
||||
txn: &'a mut Transaction<'txn, Postgres>,
|
||||
}
|
||||
@ -118,4 +124,34 @@ impl<'a, 'txn> IdCoopStoreTxn<'a, 'txn> {
|
||||
.context("failed to set user password in DB")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn set_user_locked(&mut self, user_id: Uuid, locked: bool) -> eyre::Result<()> {
|
||||
sqlx::query!(
|
||||
"UPDATE users SET locked = $1 WHERE user_id = $2",
|
||||
locked,
|
||||
user_id
|
||||
)
|
||||
.execute(&mut **self.txn)
|
||||
.await
|
||||
.context("failed to set user (un)locked")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_user(&mut self, user_id: Uuid) -> eyre::Result<()> {
|
||||
sqlx::query!("DELETE FROM users WHERE user_id = $1", user_id)
|
||||
.execute(&mut **self.txn)
|
||||
.await
|
||||
.context("failed to delete user")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn list_user_info(&mut self) -> eyre::Result<Vec<UserInfo>> {
|
||||
sqlx::query_as!(
|
||||
UserInfo,
|
||||
"SELECT user_name, user_id, locked FROM users ORDER BY user_name"
|
||||
)
|
||||
.fetch_all(&mut **self.txn)
|
||||
.await
|
||||
.context("failed to list users")
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user