diff --git a/crates/handlers/src/views/register.rs b/crates/handlers/src/views/register.rs index 77c02693..fbf5a436 100644 --- a/crates/handlers/src/views/register.rs +++ b/crates/handlers/src/views/register.rs @@ -133,7 +133,7 @@ pub(crate) async fn post( let mut policy = policy_factory.instantiate().await?; let res = policy - .evaluate_register(&form.username, &form.email) + .evaluate_register(&form.username, &form.password, &form.email) .await?; for violation in res.violations { @@ -150,6 +150,12 @@ pub(crate) async fn post( message: violation.msg, }, ), + Some("password") => state.add_error_on_field( + RegisterFormField::Password, + FieldError::Policy { + message: violation.msg, + }, + ), _ => state.add_error_on_form(FormError::Policy { message: violation.msg, }), diff --git a/crates/policy/policies/register.rego b/crates/policy/policies/register.rego index 720fb263..391fc37b 100644 --- a/crates/policy/policies/register.rego +++ b/crates/policy/policies/register.rego @@ -16,6 +16,26 @@ violation[{"field": "username", "msg": "username too long"}] { count(input.user.username) >= 15 } +violation[{"field": "password", "msg": msg}] { + count(input.user.password) < data.passwords.min_length + msg := sprintf("needs to be at least %d characters", [data.passwords.min_length]) +} + +violation[{"field": "password", "msg": "requires at least one number"}] { + data.passwords.require_number + not regex.match("[0-9]", input.user.password) +} + +violation[{"field": "password", "msg": "requires at least one lowercase letter"}] { + data.passwords.require_lowercase + not regex.match("[a-z]", input.user.password) +} + +violation[{"field": "password", "msg": "requires at least one uppercase letter"}] { + data.passwords.require_uppercase + not regex.match("[A-Z]", input.user.password) +} + # Allow any domains if the data.allowed_domains array is not set email_domain_allowed { not data.allowed_domains diff --git a/crates/policy/policies/register_test.rego b/crates/policy/policies/register_test.rego index 740e22cc..d2b042fe 100644 --- a/crates/policy/policies/register_test.rego +++ b/crates/policy/policies/register_test.rego @@ -1,6 +1,6 @@ package register -mock_user := {"username": "hello", "email": "hello@staging.element.io"} +mock_user := {"username": "hello", "password": "Hunter2", "email": "hello@staging.element.io"} test_allow_all_domains { allow with input.user as mock_user @@ -34,3 +34,39 @@ test_short_username { test_long_username { not allow with input.user as {"username": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "email": "hello@element.io"} } + +test_password_require_number { + allow with input.user as mock_user + with data.passwords.require_number as true + + not allow with input.user as mock_user + with input.user.password as "hunter" + with data.passwords.require_number as true +} + +test_password_require_lowercase { + allow with input.user as mock_user + with data.passwords.require_lowercase as true + + not allow with input.user as mock_user + with input.user.password as "HUNTER2" + with data.passwords.require_lowercase as true +} + +test_password_require_uppercase { + allow with input.user as mock_user + with data.passwords.require_uppercase as true + + not allow with input.user as mock_user + with input.user.password as "hunter2" + with data.passwords.require_uppercase as true +} + +test_password_min_length { + allow with input.user as mock_user + with data.passwords.min_length as 6 + + not allow with input.user as mock_user + with input.user.password as "short" + with data.passwords.min_length as 6 +} diff --git a/crates/policy/src/lib.rs b/crates/policy/src/lib.rs index fd2eb36d..78c44fed 100644 --- a/crates/policy/src/lib.rs +++ b/crates/policy/src/lib.rs @@ -122,13 +122,13 @@ impl PolicyFactory { } } -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] pub struct Violation { pub msg: String, pub field: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] pub struct EvaluationResult { #[serde(rename = "result")] pub violations: Vec, @@ -153,11 +153,13 @@ impl Policy { pub async fn evaluate_register( &mut self, username: &str, + password: &str, email: &str, ) -> Result { let input = serde_json::json!({ "user": { "username": username, + "password": password, "email": email } });