diff --git a/crates/handlers/src/views/register.rs b/crates/handlers/src/views/register.rs index 4afee4fd..d16d6f2a 100644 --- a/crates/handlers/src/views/register.rs +++ b/crates/handlers/src/views/register.rs @@ -14,7 +14,7 @@ #![allow(clippy::trait_duplication_in_bounds)] -use std::str::FromStr; +use std::{str::FromStr, sync::Arc}; use argon2::Argon2; use axum::{ @@ -29,6 +29,7 @@ use mas_axum_utils::{ }; use mas_config::Encrypter; use mas_email::Mailer; +use mas_policy::PolicyFactory; use mas_router::Route; use mas_storage::user::{ add_user_email, add_user_email_verification_code, register_user, start_session, username_exists, @@ -87,6 +88,7 @@ pub(crate) async fn get( pub(crate) async fn post( Extension(mailer): Extension, + Extension(policy_factory): Extension>, Extension(templates): Extension, Extension(pool): Extension, Query(query): Query, @@ -129,6 +131,15 @@ pub(crate) async fn post( state.add_error_on_field(RegisterFormField::PasswordConfirm, FieldError::Unspecified); } + let mut policy = policy_factory.instanciate().await?; + let res = policy + .evaluate_register(&form.username, &form.email) + .await?; + + if !res { + state.add_error_on_form(FormError::Policy); + } + state }; diff --git a/crates/templates/src/forms.rs b/crates/templates/src/forms.rs index f6e30cce..0ff8583e 100644 --- a/crates/templates/src/forms.rs +++ b/crates/templates/src/forms.rs @@ -52,6 +52,9 @@ pub enum FormError { /// There was an internal error Internal, + + /// Denied by the policy + Policy, } #[derive(Debug, Default, Serialize)] diff --git a/policies/policy.wasm b/policies/policy.wasm index 2e50bf6e..86dc6622 100644 Binary files a/policies/policy.wasm and b/policies/policy.wasm differ diff --git a/policies/register.rego b/policies/register.rego index 8e6aa412..c918288e 100644 --- a/policies/register.rego +++ b/policies/register.rego @@ -1,3 +1,41 @@ package register -allow := true +import future.keywords.in + +default allow := false +allow := true { + count(violation) == 0 +} + +violation[{"field": "username", "msg": "username too short"}] { + count(input.user.username) <= 2 +} + +violation[{"field": "username", "msg": "username too long"}] { + count(input.user.username) >= 15 +} + +# Allow any domains if the data.allowed_domains array is not set +email_domain_allowed { + not data.allowed_domains +} + +# Allow an email only if its domain is in the list of allowed domains +email_domain_allowed { + [_, domain] := split(input.user.email, "@") + some allowed_domain in data.allowed_domains + glob.match(allowed_domain, ["."], domain) +} + +violation[{"field": "email", "msg": "email domain not allowed"}] { + not email_domain_allowed +} + +# Deny emails with their domain in the domains banlist +violation[{"field": "email", "msg": "email domain not allowed"}] { + [_, domain] := split(input.user.email, "@") + some banned_domain in data.banned_domains + glob.match(banned_domain, ["."], domain) +} + + diff --git a/policies/register_test.rego b/policies/register_test.rego new file mode 100644 index 00000000..c84a4676 --- /dev/null +++ b/policies/register_test.rego @@ -0,0 +1,32 @@ +package register + +mock_user := {"username": "hello", "email": "hello@staging.element.io"} + +test_allow_all_domains { + allow with input.user as mock_user +} + +test_allowed_domain { + allow + with input.user as mock_user + with data.allowed_domains as ["*.element.io"] +} + +test_not_allowed_domain { + not allow + with input.user as mock_user + with data.allowed_domains as ["example.com"] +} + +test_banned_domain { + not allow + with input.user as mock_user + with data.banned_domains as ["*.element.io"] +} + +test_banned_subdomain { + not allow + with input.user as mock_user + with data.allowed_domains as ["*.element.io"] + with data.banned_domains as ["staging.element.io"] +}