|
16 | 16 | * |
17 | 17 | */ |
18 | 18 |
|
| 19 | +use std::collections::HashSet; |
| 20 | + |
19 | 21 | use error::HotTierValidationError; |
| 22 | +use once_cell::sync::Lazy; |
20 | 23 |
|
21 | 24 | use self::error::{StreamNameValidationError, UsernameValidationError}; |
22 | 25 | use crate::hottier::MIN_STREAM_HOT_TIER_SIZE_BYTES; |
@@ -67,21 +70,65 @@ pub fn stream_name( |
67 | 70 | Ok(()) |
68 | 71 | } |
69 | 72 |
|
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 { |
77 | 95 | return Err(UsernameValidationError::InvalidLength); |
78 | 96 | } |
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 last character (must be alphanumeric) |
| 106 | + if let Some(last_char) = chars.last() |
| 107 | + && !last_char.is_ascii_alphanumeric() |
83 | 108 | { |
84 | | - return Err(UsernameValidationError::SpecialChar); |
| 109 | + return Err(UsernameValidationError::InvalidEndChar); |
| 110 | + } |
| 111 | + |
| 112 | + // Check all characters and consecutive special chars |
| 113 | + let mut prev_was_special = false; |
| 114 | + for &ch in &chars { |
| 115 | + match ch { |
| 116 | + // Allow alphanumeric |
| 117 | + c if c.is_ascii_alphanumeric() => { |
| 118 | + prev_was_special = false; |
| 119 | + } |
| 120 | + // Allow specific special characters |
| 121 | + '_' | '-' | '.' => { |
| 122 | + if prev_was_special { |
| 123 | + return Err(UsernameValidationError::ConsecutiveSpecialChars); |
| 124 | + } |
| 125 | + prev_was_special = true; |
| 126 | + } |
| 127 | + // Reject any other character |
| 128 | + _ => { |
| 129 | + return Err(UsernameValidationError::InvalidCharacter); |
| 130 | + } |
| 131 | + } |
85 | 132 | } |
86 | 133 |
|
87 | 134 | Ok(()) |
@@ -138,9 +185,21 @@ pub mod error { |
138 | 185 | #[error("Username should be between 3 and 64 chars long")] |
139 | 186 | InvalidLength, |
140 | 187 | #[error( |
141 | | - "Username contains invalid characters. Please use lowercase, alphanumeric or underscore" |
| 188 | + "Username contains invalid characters. Only alphanumeric characters and special characters (underscore, hyphen and dot) are allowed" |
142 | 189 | )] |
143 | 190 | SpecialChar, |
| 191 | + #[error("Username should start with an alphanumeric character")] |
| 192 | + InvalidStartChar, |
| 193 | + #[error("Username should end with an alphanumeric character")] |
| 194 | + InvalidEndChar, |
| 195 | + #[error( |
| 196 | + "Username contains invalid characters. Only alphanumeric characters and special characters (underscore, hyphen and dot) are allowed" |
| 197 | + )] |
| 198 | + InvalidCharacter, |
| 199 | + #[error("Username contains consecutive special characters")] |
| 200 | + ConsecutiveSpecialChars, |
| 201 | + #[error("Username is reserved")] |
| 202 | + ReservedName, |
144 | 203 | } |
145 | 204 |
|
146 | 205 | #[derive(Debug, thiserror::Error)] |
|
0 commit comments