A new password signs the account out everywhere (#334)
ipx user passwd, the one way to change a password and the step taken when one may be known to someone else, only replaced the hash. Every session stayed, and session_user moves `seen` on each use, so a session someone kept using never reached the idle cutoff. set_password now deletes the account's sessions too. API tokens stay: a reset that broke every script would be a reason not to reset, and the owner lists and removes tokens in Settings. The command says how many still work. Also corrects the first-start log line from #321: there is no way to change a password in the page, so it names the command alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2059,8 +2059,14 @@ impl Db {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Signs the account out everywhere, too: a password is changed because someone else may
|
||||
/// know it, and a session they signed in with stayed good, every use putting off its expiry
|
||||
/// (#334). API tokens stay; they are listed and removed in Settings.
|
||||
pub async fn set_password(&self, id: i64, hash: &str) -> Result<()> {
|
||||
self.update_user(id, users::ActiveModel { pass_hash: Set(Some(hash.to_owned())), ..Default::default() }).await
|
||||
self.update_user(id, users::ActiveModel { pass_hash: Set(Some(hash.to_owned())), ..Default::default() })
|
||||
.await?;
|
||||
sessions::Entity::delete_many().filter(sessions::Column::UserId.eq(id)).exec(&self.orm).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn set_admin(&self, id: i64, admin: bool) -> Result<()> {
|
||||
@@ -3003,6 +3009,22 @@ pub fn now() -> i64 {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_new_password_signs_the_account_out_everywhere_but_keeps_its_tokens() {
|
||||
let db = Db::memory().await.unwrap();
|
||||
let (me, you) =
|
||||
(db.create_user("me", None, false).await.unwrap(), db.create_user("you", None, false).await.unwrap());
|
||||
db.create_session(me, "mine").await.unwrap();
|
||||
db.create_session(you, "yours").await.unwrap();
|
||||
db.create_api_token(me, "script", "hash").await.unwrap();
|
||||
|
||||
db.set_password(me, "new-hash").await.unwrap();
|
||||
|
||||
assert!(db.session_user("mine", 3600).await.unwrap().is_none(), "a stolen cookie stops working (#334)");
|
||||
assert!(db.session_user("yours", 3600).await.unwrap().is_some(), "nobody else is signed out");
|
||||
assert!(db.api_token_user("hash").await.unwrap().is_some());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn deadair_looks_at_a_file_once_per_download_and_counts_an_episode_once() {
|
||||
let db = Db::memory().await.unwrap();
|
||||
|
||||
Reference in New Issue
Block a user