Automatic merge back to main (#5156)

This commit is contained in:
matrixbot
2025-10-16 16:31:52 +02:00
committed by GitHub
6 changed files with 369 additions and 66 deletions

56
Cargo.lock generated
View File

@@ -3104,7 +3104,7 @@ dependencies = [
[[package]]
name = "mas-axum-utils"
version = "1.4.0"
version = "1.4.1"
dependencies = [
"anyhow",
"axum",
@@ -3138,7 +3138,7 @@ dependencies = [
[[package]]
name = "mas-cli"
version = "1.4.0"
version = "1.4.1"
dependencies = [
"anyhow",
"axum",
@@ -3211,7 +3211,7 @@ dependencies = [
[[package]]
name = "mas-config"
version = "1.4.0"
version = "1.4.1"
dependencies = [
"anyhow",
"camino",
@@ -3243,7 +3243,7 @@ dependencies = [
[[package]]
name = "mas-context"
version = "1.4.0"
version = "1.4.1"
dependencies = [
"console",
"opentelemetry",
@@ -3259,7 +3259,7 @@ dependencies = [
[[package]]
name = "mas-data-model"
version = "1.4.0"
version = "1.4.1"
dependencies = [
"base64ct",
"chrono",
@@ -3282,7 +3282,7 @@ dependencies = [
[[package]]
name = "mas-email"
version = "1.4.0"
version = "1.4.1"
dependencies = [
"async-trait",
"lettre",
@@ -3293,7 +3293,7 @@ dependencies = [
[[package]]
name = "mas-handlers"
version = "1.4.0"
version = "1.4.1"
dependencies = [
"aide",
"anyhow",
@@ -3373,7 +3373,7 @@ dependencies = [
[[package]]
name = "mas-http"
version = "1.4.0"
version = "1.4.1"
dependencies = [
"futures-util",
"headers",
@@ -3394,7 +3394,7 @@ dependencies = [
[[package]]
name = "mas-i18n"
version = "1.4.0"
version = "1.4.1"
dependencies = [
"camino",
"icu_calendar",
@@ -3416,7 +3416,7 @@ dependencies = [
[[package]]
name = "mas-i18n-scan"
version = "1.4.0"
version = "1.4.1"
dependencies = [
"camino",
"clap",
@@ -3430,7 +3430,7 @@ dependencies = [
[[package]]
name = "mas-iana"
version = "1.4.0"
version = "1.4.1"
dependencies = [
"schemars 0.8.22",
"serde",
@@ -3438,7 +3438,7 @@ dependencies = [
[[package]]
name = "mas-iana-codegen"
version = "1.4.0"
version = "1.4.1"
dependencies = [
"anyhow",
"async-trait",
@@ -3454,7 +3454,7 @@ dependencies = [
[[package]]
name = "mas-jose"
version = "1.4.0"
version = "1.4.1"
dependencies = [
"base64ct",
"chrono",
@@ -3484,7 +3484,7 @@ dependencies = [
[[package]]
name = "mas-keystore"
version = "1.4.0"
version = "1.4.1"
dependencies = [
"aead",
"base64ct",
@@ -3512,7 +3512,7 @@ dependencies = [
[[package]]
name = "mas-listener"
version = "1.4.0"
version = "1.4.1"
dependencies = [
"anyhow",
"bytes",
@@ -3537,7 +3537,7 @@ dependencies = [
[[package]]
name = "mas-matrix"
version = "1.4.0"
version = "1.4.1"
dependencies = [
"anyhow",
"async-trait",
@@ -3547,7 +3547,7 @@ dependencies = [
[[package]]
name = "mas-matrix-synapse"
version = "1.4.0"
version = "1.4.1"
dependencies = [
"anyhow",
"async-trait",
@@ -3564,7 +3564,7 @@ dependencies = [
[[package]]
name = "mas-oidc-client"
version = "1.4.0"
version = "1.4.1"
dependencies = [
"assert_matches",
"async-trait",
@@ -3600,7 +3600,7 @@ dependencies = [
[[package]]
name = "mas-policy"
version = "1.4.0"
version = "1.4.1"
dependencies = [
"anyhow",
"arc-swap",
@@ -3617,7 +3617,7 @@ dependencies = [
[[package]]
name = "mas-router"
version = "1.4.0"
version = "1.4.1"
dependencies = [
"axum",
"serde",
@@ -3628,7 +3628,7 @@ dependencies = [
[[package]]
name = "mas-spa"
version = "1.4.0"
version = "1.4.1"
dependencies = [
"camino",
"serde",
@@ -3637,7 +3637,7 @@ dependencies = [
[[package]]
name = "mas-storage"
version = "1.4.0"
version = "1.4.1"
dependencies = [
"async-trait",
"chrono",
@@ -3659,7 +3659,7 @@ dependencies = [
[[package]]
name = "mas-storage-pg"
version = "1.4.0"
version = "1.4.1"
dependencies = [
"async-trait",
"chrono",
@@ -3687,7 +3687,7 @@ dependencies = [
[[package]]
name = "mas-tasks"
version = "1.4.0"
version = "1.4.1"
dependencies = [
"anyhow",
"async-trait",
@@ -3719,7 +3719,7 @@ dependencies = [
[[package]]
name = "mas-templates"
version = "1.4.0"
version = "1.4.1"
dependencies = [
"anyhow",
"arc-swap",
@@ -3749,7 +3749,7 @@ dependencies = [
[[package]]
name = "mas-tower"
version = "1.4.0"
version = "1.4.1"
dependencies = [
"http",
"opentelemetry",
@@ -4019,7 +4019,7 @@ dependencies = [
[[package]]
name = "oauth2-types"
version = "1.4.0"
version = "1.4.1"
dependencies = [
"assert_matches",
"base64ct",
@@ -6101,7 +6101,7 @@ dependencies = [
[[package]]
name = "syn2mas"
version = "1.4.0"
version = "1.4.1"
dependencies = [
"anyhow",
"arc-swap",

View File

@@ -9,7 +9,7 @@ members = ["crates/*"]
resolver = "2"
# Updated in the CI with a `sed` command
package.version = "1.4.0"
package.version = "1.4.1"
package.license = "AGPL-3.0-only OR LicenseRef-Element-Commercial"
package.authors = ["Element Backend Team"]
package.edition = "2024"
@@ -34,35 +34,35 @@ broken_intra_doc_links = "deny"
[workspace.dependencies]
# Workspace crates
mas-axum-utils = { path = "./crates/axum-utils/", version = "=1.4.0" }
mas-cli = { path = "./crates/cli/", version = "=1.4.0" }
mas-config = { path = "./crates/config/", version = "=1.4.0" }
mas-context = { path = "./crates/context/", version = "=1.4.0" }
mas-data-model = { path = "./crates/data-model/", version = "=1.4.0" }
mas-email = { path = "./crates/email/", version = "=1.4.0" }
mas-graphql = { path = "./crates/graphql/", version = "=1.4.0" }
mas-handlers = { path = "./crates/handlers/", version = "=1.4.0" }
mas-http = { path = "./crates/http/", version = "=1.4.0" }
mas-i18n = { path = "./crates/i18n/", version = "=1.4.0" }
mas-i18n-scan = { path = "./crates/i18n-scan/", version = "=1.4.0" }
mas-iana = { path = "./crates/iana/", version = "=1.4.0" }
mas-iana-codegen = { path = "./crates/iana-codegen/", version = "=1.4.0" }
mas-jose = { path = "./crates/jose/", version = "=1.4.0" }
mas-keystore = { path = "./crates/keystore/", version = "=1.4.0" }
mas-listener = { path = "./crates/listener/", version = "=1.4.0" }
mas-matrix = { path = "./crates/matrix/", version = "=1.4.0" }
mas-matrix-synapse = { path = "./crates/matrix-synapse/", version = "=1.4.0" }
mas-oidc-client = { path = "./crates/oidc-client/", version = "=1.4.0" }
mas-policy = { path = "./crates/policy/", version = "=1.4.0" }
mas-router = { path = "./crates/router/", version = "=1.4.0" }
mas-spa = { path = "./crates/spa/", version = "=1.4.0" }
mas-storage = { path = "./crates/storage/", version = "=1.4.0" }
mas-storage-pg = { path = "./crates/storage-pg/", version = "=1.4.0" }
mas-tasks = { path = "./crates/tasks/", version = "=1.4.0" }
mas-templates = { path = "./crates/templates/", version = "=1.4.0" }
mas-tower = { path = "./crates/tower/", version = "=1.4.0" }
oauth2-types = { path = "./crates/oauth2-types/", version = "=1.4.0" }
syn2mas = { path = "./crates/syn2mas", version = "=1.4.0" }
mas-axum-utils = { path = "./crates/axum-utils/", version = "=1.4.1" }
mas-cli = { path = "./crates/cli/", version = "=1.4.1" }
mas-config = { path = "./crates/config/", version = "=1.4.1" }
mas-context = { path = "./crates/context/", version = "=1.4.1" }
mas-data-model = { path = "./crates/data-model/", version = "=1.4.1" }
mas-email = { path = "./crates/email/", version = "=1.4.1" }
mas-graphql = { path = "./crates/graphql/", version = "=1.4.1" }
mas-handlers = { path = "./crates/handlers/", version = "=1.4.1" }
mas-http = { path = "./crates/http/", version = "=1.4.1" }
mas-i18n = { path = "./crates/i18n/", version = "=1.4.1" }
mas-i18n-scan = { path = "./crates/i18n-scan/", version = "=1.4.1" }
mas-iana = { path = "./crates/iana/", version = "=1.4.1" }
mas-iana-codegen = { path = "./crates/iana-codegen/", version = "=1.4.1" }
mas-jose = { path = "./crates/jose/", version = "=1.4.1" }
mas-keystore = { path = "./crates/keystore/", version = "=1.4.1" }
mas-listener = { path = "./crates/listener/", version = "=1.4.1" }
mas-matrix = { path = "./crates/matrix/", version = "=1.4.1" }
mas-matrix-synapse = { path = "./crates/matrix-synapse/", version = "=1.4.1" }
mas-oidc-client = { path = "./crates/oidc-client/", version = "=1.4.1" }
mas-policy = { path = "./crates/policy/", version = "=1.4.1" }
mas-router = { path = "./crates/router/", version = "=1.4.1" }
mas-spa = { path = "./crates/spa/", version = "=1.4.1" }
mas-storage = { path = "./crates/storage/", version = "=1.4.1" }
mas-storage-pg = { path = "./crates/storage-pg/", version = "=1.4.1" }
mas-tasks = { path = "./crates/tasks/", version = "=1.4.1" }
mas-templates = { path = "./crates/templates/", version = "=1.4.1" }
mas-tower = { path = "./crates/tower/", version = "=1.4.1" }
oauth2-types = { path = "./crates/oauth2-types/", version = "=1.4.1" }
syn2mas = { path = "./crates/syn2mas", version = "=1.4.1" }
# OpenAPI schema generation and validation
[workspace.dependencies.aide]

View File

@@ -84,7 +84,7 @@ async fn verify_password_if_needed(
password,
user_password.hashed_password,
)
.await;
.await?;
Ok(res.is_ok())
Ok(res.is_success())
}

View File

@@ -737,13 +737,14 @@ impl UserMutations {
));
};
if let Err(_err) = password_manager
if !password_manager
.verify(
active_password.version,
Zeroizing::new(current_password_attempt),
active_password.hashed_password,
)
.await
.await?
.is_success()
{
return Ok(SetPasswordPayload {
status: SetPasswordStatus::WrongPassword,

View File

@@ -6,6 +6,7 @@
use axum::http::Request;
use hyper::StatusCode;
use mas_axum_utils::SessionInfoExt;
use mas_data_model::{AccessToken, Client, TokenType, User};
use mas_matrix::{HomeserverConnection, ProvisionRequest};
use mas_router::SimpleRoute;
@@ -19,11 +20,9 @@ use oauth2_types::{
scope::{OPENID, Scope, ScopeToken},
};
use sqlx::PgPool;
use zeroize::Zeroizing;
use crate::{
test_utils,
test_utils::{RequestBuilderExt, ResponseExt, TestState, setup},
};
use crate::test_utils::{self, CookieHelper, RequestBuilderExt, ResponseExt, TestState, setup};
async fn create_test_client(state: &TestState) -> Client {
let mut repo = state.repository().await.unwrap();
@@ -781,3 +780,301 @@ async fn test_add_user(pool: PgPool) {
})
);
}
/// Test the setPassword mutation where the current password provided is
/// wrong.
#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
async fn test_set_password_rejected_wrong_password(pool: PgPool) {
setup();
let state = TestState::from_pool(pool).await.unwrap();
let mut rng = state.rng();
let mut repo = state.repository().await.unwrap();
let user = repo
.user()
.add(&mut rng, &state.clock, "alice".to_owned())
.await
.unwrap();
let password = Zeroizing::new("current.password.123".to_owned());
let (version, hashed_password) = state
.password_manager
.hash(&mut rng, password)
.await
.unwrap();
repo.user_password()
.add(
&mut rng,
&state.clock,
&user,
version,
hashed_password,
None,
)
.await
.unwrap();
let browser_session = repo
.browser_session()
.add(&mut rng, &state.clock, &user, None)
.await
.unwrap();
repo.save().await.unwrap();
let cookie_jar = state.cookie_jar();
let cookie_jar = cookie_jar.set_session(&browser_session);
let user_id = user.id;
let request = Request::post("/graphql").json(serde_json::json!({
"query": format!(r#"
mutation {{
setPassword(input: {{
userId: "user:{user_id}",
currentPassword: "wrong.password.123",
newPassword: "new.password.123"
}}) {{
status
}}
}}
"#),
}));
let cookies = CookieHelper::new();
cookies.import(cookie_jar);
let request = cookies.with_cookies(request);
let response = state.request(request).await;
response.assert_status(StatusCode::OK);
let response: GraphQLResponse = response.json();
assert!(response.errors.is_empty(), "{:?}", response.errors);
assert_eq!(
response.data["setPassword"]["status"].as_str(),
Some("WRONG_PASSWORD"),
"{:?}",
response.data
);
}
/// Test the startEmailAuthentication mutation where the current password
/// provided is invalid.
#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
async fn test_start_email_authentication_rejected_wrong_password(pool: PgPool) {
setup();
let state = TestState::from_pool(pool).await.unwrap();
let mut rng = state.rng();
let mut repo = state.repository().await.unwrap();
let user = repo
.user()
.add(&mut rng, &state.clock, "alice".to_owned())
.await
.unwrap();
let password = Zeroizing::new("current.password.123".to_owned());
let (version, hashed_password) = state
.password_manager
.hash(&mut rng, password)
.await
.unwrap();
repo.user_password()
.add(
&mut rng,
&state.clock,
&user,
version,
hashed_password,
None,
)
.await
.unwrap();
let browser_session = repo
.browser_session()
.add(&mut rng, &state.clock, &user, None)
.await
.unwrap();
repo.save().await.unwrap();
let cookie_jar = state.cookie_jar();
let cookie_jar = cookie_jar.set_session(&browser_session);
let request = Request::post("/graphql").json(serde_json::json!({
"query": r#"
mutation {
startEmailAuthentication(input: {
email: "alice@example.org",
password: "wrong.password.123"
}) {
status
}
}
"#,
}));
let cookies = CookieHelper::new();
cookies.import(cookie_jar);
let request = cookies.with_cookies(request);
let response = state.request(request).await;
response.assert_status(StatusCode::OK);
let response: GraphQLResponse = response.json();
assert!(response.errors.is_empty(), "{:?}", response.errors);
assert_eq!(
response.data["startEmailAuthentication"]["status"].as_str(),
Some("INCORRECT_PASSWORD"),
"{:?}",
response.data
);
}
/// Test the removeEmail mutation where the current password
/// provided is invalid.
#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
async fn test_remove_email_rejected_wrong_password(pool: PgPool) {
setup();
let state = TestState::from_pool(pool).await.unwrap();
let mut rng = state.rng();
let mut repo = state.repository().await.unwrap();
let user = repo
.user()
.add(&mut rng, &state.clock, "alice".to_owned())
.await
.unwrap();
let password = Zeroizing::new("current.password.123".to_owned());
let (version, hashed_password) = state
.password_manager
.hash(&mut rng, password)
.await
.unwrap();
repo.user_password()
.add(
&mut rng,
&state.clock,
&user,
version,
hashed_password,
None,
)
.await
.unwrap();
let user_email_id = repo
.user_email()
.add(
&mut rng,
&state.clock,
&user,
"alice@example.org".to_owned(),
)
.await
.unwrap()
.id;
let browser_session = repo
.browser_session()
.add(&mut rng, &state.clock, &user, None)
.await
.unwrap();
repo.save().await.unwrap();
let cookie_jar = state.cookie_jar();
let cookie_jar = cookie_jar.set_session(&browser_session);
let request = Request::post("/graphql").json(serde_json::json!({
"query": format!(r#"
mutation {{
removeEmail(input: {{
userEmailId: "user_email:{user_email_id}",
password: "wrong.password.123"
}}) {{
status
}}
}}
"#),
}));
let cookies = CookieHelper::new();
cookies.import(cookie_jar);
let request = cookies.with_cookies(request);
let response = state.request(request).await;
response.assert_status(StatusCode::OK);
let response: GraphQLResponse = response.json();
assert!(response.errors.is_empty(), "{:?}", response.errors);
assert_eq!(
response.data["removeEmail"]["status"].as_str(),
Some("INCORRECT_PASSWORD"),
"{:?}",
response.data
);
}
/// Test the deactivateUser mutation where the current password
/// provided is invalid.
#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
async fn test_deactivate_user_rejected_wrong_password(pool: PgPool) {
setup();
let state = TestState::from_pool(pool).await.unwrap();
let mut rng = state.rng();
let mut repo = state.repository().await.unwrap();
let user = repo
.user()
.add(&mut rng, &state.clock, "alice".to_owned())
.await
.unwrap();
let password = Zeroizing::new("current.password.123".to_owned());
let (version, hashed_password) = state
.password_manager
.hash(&mut rng, password)
.await
.unwrap();
repo.user_password()
.add(
&mut rng,
&state.clock,
&user,
version,
hashed_password,
None,
)
.await
.unwrap();
let browser_session = repo
.browser_session()
.add(&mut rng, &state.clock, &user, None)
.await
.unwrap();
repo.save().await.unwrap();
let cookie_jar = state.cookie_jar();
let cookie_jar = cookie_jar.set_session(&browser_session);
let request = Request::post("/graphql").json(serde_json::json!({
"query": r#"
mutation {
deactivateUser(input: {
hsErase: true,
password: "wrong.password.123"
}) {
status
}
}
"#,
}));
let cookies = CookieHelper::new();
cookies.import(cookie_jar);
let request = cookies.with_cookies(request);
let response = state.request(request).await;
response.assert_status(StatusCode::OK);
let response: GraphQLResponse = response.json();
assert!(response.errors.is_empty(), "{:?}", response.errors);
assert_eq!(
response.data["deactivateUser"]["status"].as_str(),
Some("INCORRECT_PASSWORD"),
"{:?}",
response.data
);
}

View File

@@ -49,6 +49,11 @@ impl<T> PasswordVerificationResult<T> {
Self::Failure => PasswordVerificationResult::Failure,
}
}
#[must_use]
pub fn is_success(&self) -> bool {
matches!(self, Self::Success(_))
}
}
impl From<bool> for PasswordVerificationResult<()> {