diff --git a/services/identity/src/client_service.rs b/services/identity/src/client_service.rs --- a/services/identity/src/client_service.rs +++ b/services/identity/src/client_service.rs @@ -42,10 +42,10 @@ is_valid_ethereum_address, parse_and_verify_siwe_message, SocialProof, }; use crate::token::{AccessTokenData, AuthType}; - pub use crate::grpc_services::protos::unauth::identity_client_service_server::{ IdentityClientService, IdentityClientServiceServer, }; +use crate::regex::is_valid_username; #[derive(Clone, Serialize, Deserialize)] pub enum WorkflowInProgress { @@ -102,6 +102,12 @@ let message = request.into_inner(); debug!("Received registration request for: {}", message.username); + if !is_valid_username(&message.username) + || is_valid_ethereum_address(&message.username) + { + return Err(tonic::Status::invalid_argument("invalid username")); + } + self.check_username_taken(&message.username).await?; let username_in_reserved_usernames_table = self .client @@ -113,9 +119,7 @@ return Err(tonic::Status::already_exists("username already exists")); } - if RESERVED_USERNAME_SET.contains(&message.username) - || is_valid_ethereum_address(&message.username) - { + if RESERVED_USERNAME_SET.contains(&message.username) { return Err(tonic::Status::invalid_argument("username reserved")); } diff --git a/services/identity/src/constants.rs b/services/identity/src/constants.rs --- a/services/identity/src/constants.rs +++ b/services/identity/src/constants.rs @@ -228,3 +228,8 @@ ]; pub const ALLOW_ORIGIN_LIST: &str = "ALLOW_ORIGIN_LIST"; } + +// Regex + +pub const VALID_USERNAME_REGEX_STRING: &str = + r"^[a-zA-Z0-9][a-zA-Z0-9-_]{0,190}$"; diff --git a/services/identity/src/main.rs b/services/identity/src/main.rs --- a/services/identity/src/main.rs +++ b/services/identity/src/main.rs @@ -16,6 +16,7 @@ mod id; mod keygen; mod nonce; +mod regex; mod reserved_users; mod siwe; mod sync_identity_search; diff --git a/services/identity/src/regex.rs b/services/identity/src/regex.rs new file mode 100644 --- /dev/null +++ b/services/identity/src/regex.rs @@ -0,0 +1,54 @@ +use regex::Regex; + +use crate::constants::VALID_USERNAME_REGEX_STRING; + +pub fn is_valid_username(candidate: &str) -> bool { + let valid_username_regex = Regex::new(VALID_USERNAME_REGEX_STRING) + .expect("regex pattern should be valid"); + valid_username_regex.is_match(candidate) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_valid_username_with_dash_and_underscore() { + assert!(is_valid_username("WRx16rC_VWX-"),); + } + + #[test] + fn test_valid_username_with_minimum_length() { + assert!(is_valid_username("a")); + } + + #[test] + fn test_valid_username_with_maximum_length() { + assert!(is_valid_username( + "Dg75raz1KL0rzqFbnUDAtSp0KfvDThPGcYGHiEin9m7LCWYJ7vQYNkXpbTNJ7MYaaTeqApgYDr3XdNX0BmKetP0iJeKRZuDdSEuybnGrdivwSQPLrJ8rv2WMTGvNjjDypvMMbDXmQ7wt0jPFfKD41Uc07SkPFcBJkwBJ8WJuJWmm7m0nvxhFRieQn319qHk" + )); + } + + #[test] + fn test_invalid_username_too_short() { + assert_eq!(is_valid_username(""), false); + } + + #[test] + fn test_invalid_username_too_long() { + assert_eq!( + is_valid_username("XVnYhmjPrVpSwLZnSGjdnx5mmRFLbSh5RjjqU7Wf62LXe9Hyy0CfJQNXgTy62Uxzip538MMYtwvaqqA0fukYijWAuZdVVpYRMuaZ0gzxLgV6hzF9amvnjZGYgdKeEX45WErYq2nN7Q2ivcFfftWnpudPcPr0pxWRLdmqS37jNVHWMX919vXz0PdzeLNHx0TW"), + false + ); + } + + #[test] + fn test_invalid_username_first_char_non_alphanumeric() { + assert_eq!(is_valid_username("-asdf"), false); + } + + #[test] + fn test_invalid_username_invalid_symbol() { + assert_eq!(is_valid_username("asdf$"), false); + } +}