SeaORM: accounts, sessions and themes
The fourteen user and session functions move from rusqlite to SeaORM and become async; their callers await them (auth, admin_user, user_cmd, the account handlers). Checked against a copy of production, where the yes/no columns are still INTEGER: the admin flag reads back right. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
36
src/main.rs
36
src/main.rs
@@ -226,7 +226,7 @@ async fn main() -> Result<()> {
|
||||
add(&ctx, &config_path, &url, folder, keywords).await
|
||||
}
|
||||
Command::Rm { feed } => rm(&ctx, &config_path, &feed),
|
||||
Command::User { cmd } => user_cmd(&ctx, cmd),
|
||||
Command::User { cmd } => user_cmd(&ctx, cmd).await,
|
||||
Command::Import { file } => import(&ctx, &config_path, &file).await,
|
||||
Command::Export { file } => export(&ctx, &file),
|
||||
_ => run(&ctx, wire_cmd.expect("only List and Daemon have no wire form")).await,
|
||||
@@ -235,7 +235,7 @@ async fn main() -> Result<()> {
|
||||
|
||||
/// Accounts. Passwords come in on stdin so they never reach a shell history or a `ps`
|
||||
/// listing.
|
||||
fn user_cmd(ctx: &Arc<Ctx>, cmd: UserCmd) -> Result<()> {
|
||||
async fn user_cmd(ctx: &Arc<Ctx>, cmd: UserCmd) -> Result<()> {
|
||||
let read_password = || -> Result<String> {
|
||||
use std::io::Read;
|
||||
let mut buf = String::new();
|
||||
@@ -253,7 +253,7 @@ fn user_cmd(ctx: &Arc<Ctx>, cmd: UserCmd) -> Result<()> {
|
||||
if name.is_empty() {
|
||||
anyhow::bail!("a name is required");
|
||||
}
|
||||
if ctx.db.user_by_name(&name)?.is_some() {
|
||||
if ctx.db.user_by_name(&name).await?.is_some() {
|
||||
anyhow::bail!("{name} already exists");
|
||||
}
|
||||
let hash = if no_password {
|
||||
@@ -262,8 +262,8 @@ fn user_cmd(ctx: &Arc<Ctx>, cmd: UserCmd) -> Result<()> {
|
||||
Some(crate::auth::hash_password(&read_password()?)?)
|
||||
};
|
||||
// The first account runs the place; there is nobody else to grant it.
|
||||
let first = ctx.db.users()?.is_empty();
|
||||
ctx.db.create_user(&name, hash.as_deref(), admin || first)?;
|
||||
let first = ctx.db.users().await?.is_empty();
|
||||
ctx.db.create_user(&name, hash.as_deref(), admin || first).await?;
|
||||
println!(
|
||||
"added {name}{}{}",
|
||||
if admin || first { " (admin)" } else { "" },
|
||||
@@ -272,7 +272,7 @@ fn user_cmd(ctx: &Arc<Ctx>, cmd: UserCmd) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
UserCmd::List => {
|
||||
let users = ctx.db.users()?;
|
||||
let users = ctx.db.users().await?;
|
||||
if users.is_empty() {
|
||||
println!("no accounts yet: ipx user add <name>");
|
||||
}
|
||||
@@ -295,9 +295,9 @@ fn user_cmd(ctx: &Arc<Ctx>, cmd: UserCmd) -> Result<()> {
|
||||
let name = name.trim().to_ascii_lowercase();
|
||||
let user = ctx
|
||||
.db
|
||||
.user_by_name(&name)?
|
||||
.user_by_name(&name).await?
|
||||
.ok_or_else(|| anyhow::anyhow!("no such account: {name}"))?;
|
||||
ctx.db.set_password(user.id, &crate::auth::hash_password(&read_password()?)?)?;
|
||||
ctx.db.set_password(user.id, &crate::auth::hash_password(&read_password()?)?).await?;
|
||||
println!("password changed for {name}");
|
||||
Ok(())
|
||||
}
|
||||
@@ -308,12 +308,12 @@ fn user_cmd(ctx: &Arc<Ctx>, cmd: UserCmd) -> Result<()> {
|
||||
.ok_or_else(|| anyhow::anyhow!("not a usable name: no commas, semicolons or line breaks"))?;
|
||||
let user = ctx
|
||||
.db
|
||||
.user_by_name(&name)?
|
||||
.user_by_name(&name).await?
|
||||
.ok_or_else(|| anyhow::anyhow!("no such account: {name}"))?;
|
||||
if ctx.db.user_by_name(&new_name)?.is_some() {
|
||||
if ctx.db.user_by_name(&new_name).await?.is_some() {
|
||||
anyhow::bail!("{new_name} already exists");
|
||||
}
|
||||
ctx.db.rename_user(user.id, &new_name)?;
|
||||
ctx.db.rename_user(user.id, &new_name).await?;
|
||||
println!("renamed {name} to {new_name}");
|
||||
Ok(())
|
||||
}
|
||||
@@ -321,9 +321,9 @@ fn user_cmd(ctx: &Arc<Ctx>, cmd: UserCmd) -> Result<()> {
|
||||
let name = name.trim().to_ascii_lowercase();
|
||||
let user = ctx
|
||||
.db
|
||||
.user_by_name(&name)?
|
||||
.user_by_name(&name).await?
|
||||
.ok_or_else(|| anyhow::anyhow!("no such account: {name}"))?;
|
||||
ctx.db.delete_user(user.id)?;
|
||||
ctx.db.delete_user(user.id).await?;
|
||||
println!("removed {name}");
|
||||
Ok(())
|
||||
}
|
||||
@@ -370,15 +370,15 @@ async fn daemon(
|
||||
}
|
||||
|
||||
// A database with nobody in it cannot be signed into.
|
||||
if ctx.db.users()?.is_empty() {
|
||||
ctx.db.create_user("admin", Some(&crate::auth::hash_password(DEFAULT_PASSWORD)?), true)?;
|
||||
if ctx.db.users().await?.is_empty() {
|
||||
ctx.db.create_user("admin", Some(&crate::auth::hash_password(DEFAULT_PASSWORD)?), true).await?;
|
||||
tracing::warn!(
|
||||
"no accounts yet: created 'admin' with the default password '{DEFAULT_PASSWORD}'. \
|
||||
Change it with `echo -n <password> | ipx user passwd admin`"
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(admin) = ctx.db.users()?.into_iter().find(|u| u.is_admin) {
|
||||
if let Some(admin) = ctx.db.users().await?.into_iter().find(|u| u.is_admin) {
|
||||
let catalogue: Vec<String> = ctx.cfg().feeds.keys().cloned().collect();
|
||||
match ctx.db.adopt_catalogue(admin.id, &catalogue) {
|
||||
Ok(0) => {}
|
||||
@@ -670,7 +670,7 @@ async fn import(ctx: &Ctx, config_path: &std::path::Path, file: &std::path::Path
|
||||
// The CLI speaks for the operator, as the shared web token does.
|
||||
let admin = ctx
|
||||
.db
|
||||
.users()?
|
||||
.users().await?
|
||||
.into_iter()
|
||||
.find(|u| u.is_admin)
|
||||
.ok_or_else(|| anyhow::anyhow!("no admin account to subscribe: ipx user add <name> --admin"))?;
|
||||
@@ -1317,7 +1317,7 @@ async fn sync_group(
|
||||
.map(|m| m.id.clone())
|
||||
.chain(std::iter::once(parent_id.to_string()))
|
||||
{
|
||||
for user in ctx.db.users()? {
|
||||
for user in ctx.db.users().await? {
|
||||
if ctx.db.subscription(user.id, parent_id)?.is_some() {
|
||||
ctx.db.subscribe(user.id, &id)?;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user