Skip to content

Commit 3afd3e3

Browse files
authored
Merge branch 'main' into feat/allow_override_user
2 parents 8335bc6 + d8fa8f7 commit 3afd3e3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1093
-1836
lines changed

.github/workflows/build.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ jobs:
271271

272272
- name: Build and push
273273
id: bake
274-
uses: docker/bake-action@v6.6.0
274+
uses: docker/bake-action@v6.8.0
275275
with:
276276
files: |
277277
./docker-bake.hcl

.github/workflows/coverage.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
run: make coverage
3434

3535
- name: Upload to codecov.io
36-
uses: codecov/[email protected].2
36+
uses: codecov/[email protected].3
3737
with:
3838
token: ${{ secrets.CODECOV_TOKEN }}
3939
files: policies/coverage.json
@@ -60,7 +60,7 @@ jobs:
6060
run: npm run coverage
6161

6262
- name: Upload to codecov.io
63-
uses: codecov/[email protected].2
63+
uses: codecov/[email protected].3
6464
with:
6565
token: ${{ secrets.CODECOV_TOKEN }}
6666
directory: frontend/coverage/
@@ -127,7 +127,7 @@ jobs:
127127
grcov . --binary-path ./target/debug/deps/ -s . -t lcov --branch --ignore-not-existing --ignore '../*' --ignore "/*" -o target/coverage/tests.lcov
128128
129129
- name: Upload to codecov.io
130-
uses: codecov/[email protected].2
130+
uses: codecov/[email protected].3
131131
with:
132132
token: ${{ secrets.CODECOV_TOKEN }}
133133
files: target/coverage/*.lcov

Cargo.lock

Lines changed: 17 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ version = "1.7.3"
9898

9999
# Packed bitfields
100100
[workspace.dependencies.bitflags]
101-
version = "2.9.0"
101+
version = "2.9.1"
102102

103103
# Bytes
104104
[workspace.dependencies.bytes]
@@ -124,7 +124,7 @@ features = ["serde", "clock"]
124124

125125
# CLI argument parsing
126126
[workspace.dependencies.clap]
127-
version = "4.5.37"
127+
version = "4.5.38"
128128
features = ["derive"]
129129

130130
# Cron expressions
@@ -309,7 +309,7 @@ features = ["oid"]
309309

310310
# Query builder
311311
[workspace.dependencies.sea-query]
312-
version = "0.32.5"
312+
version = "0.32.6"
313313
features = ["derive", "attr", "with-uuid", "with-chrono", "postgres-array"]
314314

315315
# Query builder
@@ -397,7 +397,7 @@ version = "0.3.3"
397397

398398
# Tower HTTP layers
399399
[workspace.dependencies.tower-http]
400-
version = "0.6.3"
400+
version = "0.6.4"
401401
features = ["cors", "fs", "add-extension", "set-header"]
402402

403403
# Logging and tracing

crates/cli/src/commands/manage.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use mas_storage_pg::{DatabaseError, PgRepository};
3131
use rand::{RngCore, SeedableRng};
3232
use sqlx::{Acquire, types::Uuid};
3333
use tracing::{error, info, info_span, warn};
34+
use zeroize::Zeroizing;
3435

3536
use crate::util::{
3637
database_connection_from_config, homeserver_connection_from_config,
@@ -210,7 +211,7 @@ impl Options {
210211
return Ok(ExitCode::from(1));
211212
}
212213

213-
let password = password.into_bytes().into();
214+
let password = Zeroizing::new(password);
214215

215216
let (version, hashed_password) = password_manager.hash(&mut rng, password).await?;
216217

@@ -566,7 +567,7 @@ impl Options {
566567

567568
// Hash the password if it's provided
568569
let hashed_password = if let Some(password) = password {
569-
let password = password.into_bytes().into();
570+
let password = Zeroizing::new(password);
570571
Some(password_manager.hash(&mut rng, password).await?)
571572
} else {
572573
None
@@ -641,7 +642,7 @@ impl Options {
641642
.interact()
642643
})
643644
.await??;
644-
let password = password.into_bytes().into();
645+
let password = Zeroizing::new(password);
645646
req.hashed_password =
646647
Some(password_manager.hash(&mut rng, password).await?);
647648
}

crates/cli/src/util.rs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,24 @@ pub async fn password_manager_from_config(
3636
return Ok(PasswordManager::disabled());
3737
}
3838

39-
let schemes = config
40-
.load()
41-
.await?
42-
.into_iter()
43-
.map(|(version, algorithm, cost, secret)| {
39+
let schemes = config.load().await?.into_iter().map(
40+
|(version, algorithm, cost, secret, unicode_normalization)| {
4441
use mas_handlers::passwords::Hasher;
4542
let hasher = match algorithm {
46-
mas_config::PasswordAlgorithm::Pbkdf2 => Hasher::pbkdf2(secret),
47-
mas_config::PasswordAlgorithm::Bcrypt => Hasher::bcrypt(cost, secret),
48-
mas_config::PasswordAlgorithm::Argon2id => Hasher::argon2id(secret),
43+
mas_config::PasswordAlgorithm::Pbkdf2 => {
44+
Hasher::pbkdf2(secret, unicode_normalization)
45+
}
46+
mas_config::PasswordAlgorithm::Bcrypt => {
47+
Hasher::bcrypt(cost, secret, unicode_normalization)
48+
}
49+
mas_config::PasswordAlgorithm::Argon2id => {
50+
Hasher::argon2id(secret, unicode_normalization)
51+
}
4952
};
5053

5154
(version, hasher)
52-
});
55+
},
56+
);
5357

5458
PasswordManager::new(config.minimum_complexity(), schemes)
5559
}
@@ -492,7 +496,7 @@ mod tests {
492496
#[tokio::test]
493497
async fn test_password_manager_from_config() {
494498
let mut rng = rand_chacha::ChaChaRng::seed_from_u64(42);
495-
let password = Zeroizing::new(b"hunter2".to_vec());
499+
let password = Zeroizing::new("hunter2".to_owned());
496500

497501
// Test a valid, enabled config
498502
let config = serde_json::from_value(serde_json::json!({

crates/config/src/sections/passwords.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ fn default_schemes() -> Vec<HashingScheme> {
2020
cost: None,
2121
secret: None,
2222
secret_file: None,
23+
unicode_normalization: false,
2324
}]
2425
}
2526

@@ -124,7 +125,7 @@ impl PasswordsConfig {
124125
/// not be read.
125126
pub async fn load(
126127
&self,
127-
) -> Result<Vec<(u16, Algorithm, Option<u32>, Option<Vec<u8>>)>, anyhow::Error> {
128+
) -> Result<Vec<(u16, Algorithm, Option<u32>, Option<Vec<u8>>, bool)>, anyhow::Error> {
128129
let mut schemes: Vec<&HashingScheme> = self.schemes.iter().collect();
129130
schemes.sort_unstable_by_key(|a| Reverse(a.version));
130131
schemes.dedup_by_key(|a| a.version);
@@ -151,13 +152,24 @@ impl PasswordsConfig {
151152
(None, None) => None,
152153
};
153154

154-
mapped_result.push((scheme.version, scheme.algorithm, scheme.cost, secret));
155+
mapped_result.push((
156+
scheme.version,
157+
scheme.algorithm,
158+
scheme.cost,
159+
secret,
160+
scheme.unicode_normalization,
161+
));
155162
}
156163

157164
Ok(mapped_result)
158165
}
159166
}
160167

168+
#[allow(clippy::trivially_copy_pass_by_ref)]
169+
const fn is_default_false(value: &bool) -> bool {
170+
!*value
171+
}
172+
161173
/// Parameters for a password hashing scheme
162174
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
163175
pub struct HashingScheme {
@@ -168,6 +180,14 @@ pub struct HashingScheme {
168180
/// The hashing algorithm to use
169181
pub algorithm: Algorithm,
170182

183+
/// Whether to apply Unicode normalization to the password before hashing
184+
///
185+
/// Defaults to `false`, and generally recommended to stay false. This is
186+
/// although recommended when importing password hashs from Synapse, as it
187+
/// applies an NFKC normalization to the password before hashing it.
188+
#[serde(default, skip_serializing_if = "is_default_false")]
189+
pub unicode_normalization: bool,
190+
171191
/// Cost for the bcrypt algorithm
172192
#[serde(skip_serializing_if = "Option::is_none")]
173193
#[schemars(default = "default_bcrypt_cost")]

crates/handlers/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,10 @@ chrono.workspace = true
7474
elliptic-curve.workspace = true
7575
hex.workspace = true
7676
governor.workspace = true
77+
icu_normalizer = "1.5.0"
7778
indexmap.workspace = true
7879
pkcs8.workspace = true
79-
psl = "2.1.106"
80+
psl = "2.1.111"
8081
sha2.workspace = true
8182
time = "0.3.41"
8283
url.workspace = true

crates/handlers/src/admin/v1/users/set_password.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ pub async fn handler(
122122
return Err(RouteError::PasswordTooWeak);
123123
}
124124

125-
let password = Zeroizing::new(params.password.into_bytes());
125+
let password = Zeroizing::new(params.password);
126126
let (version, hashed_password) = password_manager
127127
.hash(&mut rng, password)
128128
.await
@@ -184,7 +184,7 @@ mod tests {
184184
// Check that the user now has a password
185185
let mut repo = state.repository().await.unwrap();
186186
let user_password = repo.user_password().active(&user).await.unwrap().unwrap();
187-
let password = Zeroizing::new(b"this is a good enough password".to_vec());
187+
let password = Zeroizing::new(String::from("this is a good enough password"));
188188
state
189189
.password_manager
190190
.verify(
@@ -243,7 +243,7 @@ mod tests {
243243
// Check that the user now has a password
244244
let mut repo = state.repository().await.unwrap();
245245
let user_password = repo.user_password().active(&user).await.unwrap().unwrap();
246-
let password = Zeroizing::new(b"password".to_vec());
246+
let password = Zeroizing::new("password".to_owned());
247247
state
248248
.password_manager
249249
.verify(

crates/handlers/src/compat/login.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -574,7 +574,7 @@ async fn user_password_login(
574574
.ok_or(RouteError::NoPassword)?;
575575

576576
// Verify the password
577-
let password = Zeroizing::new(password.into_bytes());
577+
let password = Zeroizing::new(password);
578578

579579
let new_password_hash = password_manager
580580
.verify_and_upgrade(
@@ -787,7 +787,7 @@ mod tests {
787787
.unwrap();
788788
let (version, hash) = state
789789
.password_manager
790-
.hash(&mut rng, Zeroizing::new(password.as_bytes().to_vec()))
790+
.hash(&mut rng, Zeroizing::new(password.to_owned()))
791791
.await
792792
.unwrap();
793793

0 commit comments

Comments
 (0)