Add db migration and further origin-overwrite rectifications. (6bed0d38f) (#313)

Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
Jason Volk
2026-03-03 01:02:34 +00:00
parent d00cfcb85a
commit 59791db213
3 changed files with 75 additions and 13 deletions

View File

@@ -34,7 +34,7 @@ use tuwunel_service::{
oauth::{ oauth::{
CODE_VERIFIER_LENGTH, Provider, SESSION_ID_LENGTH, Session, UserInfo, unique_id_sub, CODE_VERIFIER_LENGTH, Provider, SESSION_ID_LENGTH, Session, UserInfo, unique_id_sub,
}, },
users::Register, users::{PASSWORD_SENTINEL, Register},
}; };
use url::Url; use url::Url;
@@ -480,7 +480,7 @@ async fn register_user(
.users .users
.full_register(Register { .full_register(Register {
user_id: Some(user_id), user_id: Some(user_id),
password: Some("*"), password: Some(PASSWORD_SENTINEL),
origin: Some("sso"), origin: Some("sso"),
displayname: userinfo.name.as_deref(), displayname: userinfo.name.as_deref(),
grant_first_user_admin: true, grant_first_user_admin: true,

View File

@@ -9,10 +9,11 @@ use ruma::{
push::Ruleset, push::Ruleset,
}; };
use tuwunel_core::{ use tuwunel_core::{
Err, Result, debug, debug_info, debug_warn, error, info, Err, Result, debug, debug_info, debug_warn, err, error, info,
itertools::Itertools, itertools::Itertools,
matrix::PduCount, matrix::PduCount,
result::NotFound, result::NotFound,
utils,
utils::{ utils::{
IterStream, ReadyExt, IterStream, ReadyExt,
stream::{TryExpect, TryIgnore}, stream::{TryExpect, TryIgnore},
@@ -66,6 +67,7 @@ async fn fresh(services: &Services) -> Result {
db["global"].insert(b"retroactively_fix_bad_data_from_roomuserid_joined", []); db["global"].insert(b"retroactively_fix_bad_data_from_roomuserid_joined", []);
db["global"].insert(b"fix_referencedevents_missing_sep", []); db["global"].insert(b"fix_referencedevents_missing_sep", []);
db["global"].insert(b"fix_readreceiptid_readreceipt_duplicates", []); db["global"].insert(b"fix_readreceiptid_readreceipt_duplicates", []);
db["global"].insert(b"fix_hashed_sentinel_passwords", []);
// Create the admin room and server user on first run // Create the admin room and server user on first run
if services.config.create_admin_room { if services.config.create_admin_room {
@@ -145,6 +147,14 @@ async fn migrate(services: &Services) -> Result {
fix_readreceiptid_readreceipt_duplicates(services).await?; fix_readreceiptid_readreceipt_duplicates(services).await?;
} }
if db["global"]
.get(b"fix_hashed_sentinel_passwords")
.await
.is_not_found()
{
fix_hashed_sentinel_passwords(services).await?;
}
if services.globals.db.database_version().await < 17 { if services.globals.db.database_version().await < 17 {
services.globals.db.bump_database_version(17); services.globals.db.bump_database_version(17);
info!("Migration: Bumped database version to 17"); info!("Migration: Bumped database version to 17");
@@ -582,3 +592,52 @@ async fn fix_readreceiptid_readreceipt_duplicates(services: &Services) -> Result
db["global"].insert(b"fix_readreceiptid_readreceipt_duplicates", []); db["global"].insert(b"fix_readreceiptid_readreceipt_duplicates", []);
db.engine.sort() db.engine.sort()
} }
async fn fix_hashed_sentinel_passwords(services: &Services) -> Result {
use tuwunel_core::utils::hash::verify_password;
const PASSWORD_SENTINEL: &str = "*";
let db = &services.db;
let cork = db.cork_and_sync();
let userid_password = db["userid_password"].clone();
let hashed_sentinel = utils::hash::password(PASSWORD_SENTINEL).map_err(|e| {
err!("Could not apply migration: failed to hash sentinel password: {e:?}")
})?;
warn!(
"Fixing occurrences of password-hash {hashed_sentinel:?} generated from \
{PASSWORD_SENTINEL:?}"
);
let (checked, good, bad) = userid_password
.stream()
.expect_ok()
.ready_fold(
(0, 0, 0),
|(mut checked, mut good, mut bad): (usize, usize, usize),
(key, val): (&str, &str)| {
let good_sentinel = val == PASSWORD_SENTINEL;
let bad_sentinel = !val.is_empty()
&& !good_sentinel
&& verify_password(PASSWORD_SENTINEL, val).is_ok();
checked = checked.saturating_add(usize::from(true));
good = good.saturating_add(usize::from(good_sentinel));
bad = bad.saturating_add(usize::from(bad_sentinel));
if bad_sentinel {
userid_password.insert(key, PASSWORD_SENTINEL);
}
(checked, good, bad)
},
)
.await;
drop(cork);
info!(?checked, ?good, ?bad, "Fixed any occurrences of hashed sentinel passwords");
db["global"].insert(b"fix_hashed_sentinel_passwords", []);
db.engine.sort()
}

View File

@@ -24,6 +24,9 @@ use tuwunel_database::{Deserialized, Json, Map};
pub use self::{keys::parse_master_key, register::Register}; pub use self::{keys::parse_master_key, register::Register};
pub const PASSWORD_SENTINEL: &str = "*";
pub const PASSWORD_DISABLED: &str = "";
pub struct Service { pub struct Service {
services: Arc<crate::services::OnceServices>, services: Arc<crate::services::OnceServices>,
db: Data, db: Data,
@@ -222,10 +225,7 @@ impl Service {
// exception is made for that origin in the condition below. Note that users // exception is made for that origin in the condition below. Note that users
// with no origin are also password-origin users. // with no origin are also password-origin users.
let allowed_origins = ["password", "sso"]; let allowed_origins = ["password", "sso"];
if password.is_some() && password != Some(PASSWORD_SENTINEL) {
if let Some(password) = password
&& password != "*"
{
let origin = self.origin(user_id).await; let origin = self.origin(user_id).await;
let origin = origin.as_deref().unwrap_or("password"); let origin = origin.as_deref().unwrap_or("password");
@@ -236,17 +236,20 @@ impl Service {
} }
} }
let is_sentinel = password.is_some_and(|p| p == "*");
match password.map(utils::hash::password) { match password.map(utils::hash::password) {
| None => { | None => {
self.db.userid_password.insert(user_id, b""); self.db
.userid_password
.insert(user_id, PASSWORD_DISABLED);
},
| Some(Ok(_)) if password == Some(PASSWORD_SENTINEL) => {
self.db
.userid_password
.insert(user_id, PASSWORD_SENTINEL);
}, },
| Some(Ok(hash)) => { | Some(Ok(hash)) => {
self.db.userid_password.insert(user_id, hash); self.db.userid_password.insert(user_id, hash);
if !is_sentinel { self.db.userid_origin.insert(user_id, "password");
self.db.userid_origin.insert(user_id, "password");
}
}, },
| Some(Err(e)) => { | Some(Err(e)) => {
return Err!(Request(InvalidParam( return Err!(Request(InvalidParam(