summaryrefslogtreecommitdiff
path: root/src/token/app.rs
diff options
context:
space:
mode:
authorOwen Jacobson <owen@grimoire.ca>2024-10-29 23:29:22 -0400
committerOwen Jacobson <owen@grimoire.ca>2024-10-29 23:29:22 -0400
commit66d3fcf2e22f057bacce8d97d43a13c1c5a9ad09 (patch)
tree60995943e14a6568cf2b37622ce97df121865a6d /src/token/app.rs
parente328d33fc7d6a0f2e3d260d8bddee3ef633318eb (diff)
Add `change password` UI + API.
The protocol here re-checks the caller's password, as a "I left myself logged in" anti-pranking check.
Diffstat (limited to 'src/token/app.rs')
-rw-r--r--src/token/app.rs43
1 files changed, 42 insertions, 1 deletions
diff --git a/src/token/app.rs b/src/token/app.rs
index c19d6a0..5c0aeb0 100644
--- a/src/token/app.rs
+++ b/src/token/app.rs
@@ -13,7 +13,7 @@ use super::{
use crate::{
clock::DateTime,
db::NotFound as _,
- login::{Login, Password},
+ login::{repo::Provider as _, Login, Password},
name::{self, Name},
};
@@ -61,6 +61,47 @@ impl<'a> Tokens<'a> {
Ok((snapshot, token))
}
+ pub async fn change_password(
+ &self,
+ login: &Login,
+ password: &Password,
+ to: &Password,
+ changed_at: &DateTime,
+ ) -> Result<(Login, Secret), LoginError> {
+ let mut tx = self.db.begin().await?;
+ let (login, stored_hash) = tx
+ .auth()
+ .for_login(login)
+ .await
+ .optional()?
+ .ok_or(LoginError::Rejected)?;
+ // Split the transaction here to avoid holding the tx open (potentially blocking
+ // other writes) while we do the fairly expensive task of verifying the
+ // password. It's okay if the token issuance transaction happens some notional
+ // amount of time after retrieving the login, as inserting the token will fail
+ // if the account is deleted during that time.
+ tx.commit().await?;
+
+ if !stored_hash.verify(password)? {
+ return Err(LoginError::Rejected);
+ }
+
+ let snapshot = login.as_snapshot().ok_or(LoginError::Rejected)?;
+ let to_hash = to.hash()?;
+
+ let mut tx = self.db.begin().await?;
+ let tokens = tx.tokens().revoke_all(&login).await?;
+ tx.logins().set_password(&login, &to_hash).await?;
+ let secret = tx.tokens().issue(&login, changed_at).await?;
+ tx.commit().await?;
+
+ for event in tokens.into_iter().map(TokenEvent::Revoked) {
+ self.token_events.broadcast(event);
+ }
+
+ Ok((snapshot, secret))
+ }
+
pub async fn validate(
&self,
secret: &Secret,