ipx user rename: give an account the name the proxy signs it in as
An account made by hand before the proxy was set up is called what it was
given ('rays'), while Cloudflare Access vouches for an email address. With
auto_create_users on, the first visit through the tunnel would make a
second, empty account. Renaming keeps the id, so feeds, read state and
admin rights go with it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TAC7sLVqfKmY6rsTLXzNgk
This commit is contained in:
23
src/db.rs
23
src/db.rs
@@ -1069,6 +1069,15 @@ impl Db {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The proxy signs people in by the name it vouches for, so an account made before the proxy
|
||||
/// was set up has to take that name to be found by it. The name is UNIQUE, so a taken one is
|
||||
/// refused here as well as by the caller.
|
||||
pub fn rename_user(&self, id: i64, name: &str) -> Result<()> {
|
||||
let conn = self.conn.lock().unwrap();
|
||||
conn.execute("UPDATE users SET name = ?2 WHERE id = ?1", params![id, name])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Records a sign-in, to the hour: the proxy vouches for every request, and writing each one
|
||||
/// would buy nothing.
|
||||
pub fn signed_in(&self, id: i64) -> Result<()> {
|
||||
@@ -1571,6 +1580,20 @@ mod tests {
|
||||
assert_eq!(kept, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_renamed_account_keeps_everything_but_its_name() {
|
||||
let db = Db::memory().unwrap();
|
||||
let ray = db.create_user("rays", None, true).unwrap();
|
||||
db.create_user("sam", None, false).unwrap();
|
||||
db.subscribe(ray, "f").unwrap();
|
||||
db.rename_user(ray, "rays@sdf1.net").unwrap();
|
||||
assert!(db.user_by_name("rays").unwrap().is_none());
|
||||
let renamed = db.user_by_name("RAYS@sdf1.net").unwrap().unwrap();
|
||||
assert_eq!((renamed.id, renamed.is_admin), (ray, true), "same account, still the admin");
|
||||
assert_eq!(db.subscriptions_for(ray).unwrap().len(), 1, "and still subscribed");
|
||||
assert!(db.rename_user(ray, "sam").is_err(), "a taken name is refused");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_account_knows_when_it_was_made_and_last_signed_in() {
|
||||
let db = Db::memory().unwrap();
|
||||
|
||||
19
src/main.rs
19
src/main.rs
@@ -99,6 +99,9 @@ enum UserCmd {
|
||||
Passwd { name: String },
|
||||
/// Delete an account and everything it knows: its subscriptions and read state
|
||||
Rm { name: String },
|
||||
/// Rename an account, keeping its feeds, read state and admin rights. This is how an
|
||||
/// account made before the proxy takes the name the proxy signs it in as
|
||||
Rename { name: String, new_name: String },
|
||||
}
|
||||
|
||||
/// What a brand new database starts with, so there is always a way in. Announced loudly
|
||||
@@ -297,6 +300,22 @@ fn user_cmd(ctx: &Arc<Ctx>, cmd: UserCmd) -> Result<()> {
|
||||
println!("password changed for {name}");
|
||||
Ok(())
|
||||
}
|
||||
UserCmd::Rename { name, new_name } => {
|
||||
let name = name.trim().to_ascii_lowercase();
|
||||
// The same rules as a name the proxy vouches for, or the proxy would never find it.
|
||||
let new_name = crate::auth::name_from_header(&new_name)
|
||||
.ok_or_else(|| anyhow::anyhow!("not a usable name: no commas, semicolons or line breaks"))?;
|
||||
let user = ctx
|
||||
.db
|
||||
.user_by_name(&name)?
|
||||
.ok_or_else(|| anyhow::anyhow!("no such account: {name}"))?;
|
||||
if ctx.db.user_by_name(&new_name)?.is_some() {
|
||||
anyhow::bail!("{new_name} already exists");
|
||||
}
|
||||
ctx.db.rename_user(user.id, &new_name)?;
|
||||
println!("renamed {name} to {new_name}");
|
||||
Ok(())
|
||||
}
|
||||
UserCmd::Rm { name } => {
|
||||
let name = name.trim().to_ascii_lowercase();
|
||||
let user = ctx
|
||||
|
||||
Reference in New Issue
Block a user