Skip to content

Commit 968b408

Browse files
chore: add validation to user and role creation
below validations are added to both user and role names at creation - 1. the length should be between 3-64 characters 2. the name should not be any of these - admin, user, role, null, undefined, none, empty, password, username 3. first and the last character must be alphanumeric 4. allow any of these special characters - `_, -,.` 5. no two consecutive characters should be special character
1 parent 48f97e7 commit 968b408

File tree

5 files changed

+86
-16
lines changed

5 files changed

+86
-16
lines changed

src/handlers/http/modal/query/querier_rbac.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,9 @@ pub async fn post_user(
4444
body: Option<web::Json<serde_json::Value>>,
4545
) -> Result<impl Responder, RBACError> {
4646
let username = username.into_inner();
47-
47+
validator::user_role_name(&username)?;
4848
let mut metadata = get_metadata().await?;
4949

50-
validator::user_name(&username)?;
5150
let user_roles: HashSet<String> = if let Some(body) = body {
5251
serde_json::from_value(body.into_inner())?
5352
} else {

src/handlers/http/modal/query/querier_role.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ use crate::{
3333
map::{mut_roles, mut_sessions, read_user_groups, users},
3434
role::model::DefaultPrivilege,
3535
},
36+
validator,
3637
};
3738

3839
// Handler for PUT /api/v1/role/{name}
@@ -42,6 +43,8 @@ pub async fn put(
4243
Json(privileges): Json<Vec<DefaultPrivilege>>,
4344
) -> Result<impl Responder, RoleError> {
4445
let name = name.into_inner();
46+
// validate the role name
47+
validator::user_role_name(&name).map_err(|e| RoleError::Anyhow(e.into()))?;
4548
let mut metadata = get_metadata().await?;
4649
metadata.roles.insert(name.clone(), privileges.clone());
4750

src/handlers/http/rbac.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,9 @@ pub async fn post_user(
101101
body: Option<web::Json<serde_json::Value>>,
102102
) -> Result<impl Responder, RBACError> {
103103
let username = username.into_inner();
104-
104+
validator::user_role_name(&username)?;
105105
let mut metadata = get_metadata().await?;
106106

107-
validator::user_name(&username)?;
108107
let user_roles: HashSet<String> = if let Some(body) = body {
109108
serde_json::from_value(body.into_inner())?
110109
} else {

src/handlers/http/role.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ use crate::{
3232
role::model::DefaultPrivilege,
3333
},
3434
storage::{self, ObjectStorageError, StorageMetadata},
35+
validator,
3536
};
3637

3738
// Handler for PUT /api/v1/role/{name}
@@ -41,6 +42,8 @@ pub async fn put(
4142
Json(privileges): Json<Vec<DefaultPrivilege>>,
4243
) -> Result<impl Responder, RoleError> {
4344
let name = name.into_inner();
45+
// validate the role name
46+
validator::user_role_name(&name).map_err(|e| RoleError::Anyhow(e.into()))?;
4447
let mut metadata = get_metadata().await?;
4548
metadata.roles.insert(name.clone(), privileges.clone());
4649

src/validator.rs

Lines changed: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616
*
1717
*/
1818

19+
use std::collections::HashSet;
20+
1921
use error::HotTierValidationError;
22+
use once_cell::sync::Lazy;
2023

2124
use self::error::{StreamNameValidationError, UsernameValidationError};
2225
use crate::hottier::MIN_STREAM_HOT_TIER_SIZE_BYTES;
@@ -67,21 +70,72 @@ pub fn stream_name(
6770
Ok(())
6871
}
6972

70-
// validate if username is valid
71-
// username should be between 3 and 64 characters long
72-
// username should contain only alphanumeric characters or underscores
73-
// username should be lowercase
74-
pub fn user_name(username: &str) -> Result<(), UsernameValidationError> {
75-
// Check if the username meets the required criteria
76-
if username.len() < 3 || username.len() > 64 {
73+
static RESERVED_NAMES: Lazy<HashSet<&'static str>> = Lazy::new(|| {
74+
[
75+
"admin",
76+
"user",
77+
"role",
78+
"null",
79+
"undefined",
80+
"none",
81+
"empty",
82+
"password",
83+
"username",
84+
]
85+
.into_iter()
86+
.collect()
87+
});
88+
89+
pub fn user_role_name(name: &str) -> Result<(), UsernameValidationError> {
90+
// Normalize username to lowercase for validation
91+
let name = name.to_lowercase();
92+
93+
// Check length constraints
94+
if name.len() < 3 || name.len() > 64 {
7795
return Err(UsernameValidationError::InvalidLength);
7896
}
79-
// Username should contain only alphanumeric characters or underscores
80-
if !username
81-
.chars()
82-
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
97+
98+
// Check if name is reserved
99+
if RESERVED_NAMES.contains(name.as_str()) {
100+
return Err(UsernameValidationError::ReservedName);
101+
}
102+
103+
let chars: Vec<char> = name.chars().collect();
104+
105+
// Check first character (must be alphanumeric)
106+
if let Some(first_char) = chars.first()
107+
&& !first_char.is_ascii_alphanumeric()
108+
{
109+
return Err(UsernameValidationError::InvalidStartChar);
110+
}
111+
112+
// Check last character (must be alphanumeric)
113+
if let Some(last_char) = chars.last()
114+
&& !last_char.is_ascii_alphanumeric()
83115
{
84-
return Err(UsernameValidationError::SpecialChar);
116+
return Err(UsernameValidationError::InvalidEndChar);
117+
}
118+
119+
// Check all characters and consecutive special chars
120+
let mut prev_was_special = false;
121+
for &ch in &chars {
122+
match ch {
123+
// Allow alphanumeric
124+
c if c.is_ascii_alphanumeric() => {
125+
prev_was_special = false;
126+
}
127+
// Allow specific special characters
128+
'_' | '-' | '.' => {
129+
if prev_was_special {
130+
return Err(UsernameValidationError::ConsecutiveSpecialChars);
131+
}
132+
prev_was_special = true;
133+
}
134+
// Reject any other character
135+
_ => {
136+
return Err(UsernameValidationError::InvalidCharacter);
137+
}
138+
}
85139
}
86140

87141
Ok(())
@@ -141,6 +195,18 @@ pub mod error {
141195
"Username contains invalid characters. Please use lowercase, alphanumeric or underscore"
142196
)]
143197
SpecialChar,
198+
#[error("Username should start with an alphanumeric character")]
199+
InvalidStartChar,
200+
#[error("Username should end with an alphanumeric character")]
201+
InvalidEndChar,
202+
#[error(
203+
"Username contains invalid characters. Only alphanumeric characters and _, -, . are allowed"
204+
)]
205+
InvalidCharacter,
206+
#[error("Username contains consecutive special characters")]
207+
ConsecutiveSpecialChars,
208+
#[error("Username is reserved")]
209+
ReservedName,
144210
}
145211

146212
#[derive(Debug, thiserror::Error)]

0 commit comments

Comments
 (0)