better_auth_api/plugins/username/
config.rs1use std::sync::Arc;
2
3use async_trait::async_trait;
4use better_auth_core::{AuthError, AuthResult};
5
6pub type UsernameNormalizer = dyn Fn(&str) -> AuthResult<String> + Send + Sync;
8
9#[async_trait]
11pub trait UsernameValidator: Send + Sync {
12 async fn validate(&self, value: &str) -> AuthResult<bool>;
14}
15
16#[derive(Clone, Default)]
18pub enum UsernameNormalization {
19 #[default]
21 Default,
22 Disabled,
24 Custom(Arc<UsernameNormalizer>),
26}
27
28#[derive(Clone, Copy, PartialEq, Eq)]
30pub enum UsernameValidationOrder {
31 PreNormalization,
33 PostNormalization,
35}
36
37#[derive(Clone)]
39pub struct UsernameConfig {
40 pub min_username_length: f64,
42 pub max_username_length: f64,
44 pub username_validator: Option<Arc<dyn UsernameValidator>>,
46 pub display_username_validator: Option<Arc<dyn UsernameValidator>>,
48 pub username_normalization: UsernameNormalization,
50 pub display_username_normalization: UsernameNormalization,
52 pub username_validation_order: Option<UsernameValidationOrder>,
54 pub display_username_validation_order: Option<UsernameValidationOrder>,
56 pub immutable_username: bool,
58 pub display_username: bool,
60 pub username_field_name: Option<String>,
62 pub display_username_field_name: Option<String>,
64}
65
66impl Default for UsernameConfig {
67 fn default() -> Self {
68 Self {
69 min_username_length: 3.0,
70 max_username_length: 30.0,
71 username_validator: None,
72 display_username_validator: None,
73 username_normalization: UsernameNormalization::Default,
74 display_username_normalization: UsernameNormalization::Default,
75 username_validation_order: None,
76 display_username_validation_order: None,
77 immutable_username: false,
78 display_username: true,
79 username_field_name: None,
80 display_username_field_name: None,
81 }
82 }
83}
84
85pub(super) fn error(status: u16, code: &'static str, message: &'static str) -> AuthError {
86 AuthError::Upstream {
87 status,
88 code,
89 message,
90 }
91}
92
93impl UsernameConfig {
94 pub(super) fn normalize(&self, value: &str) -> AuthResult<String> {
95 match &self.username_normalization {
96 UsernameNormalization::Default => Ok(value.to_lowercase()),
97 UsernameNormalization::Disabled => Ok(value.to_owned()),
98 UsernameNormalization::Custom(callback) => callback(value),
99 }
100 }
101
102 pub(super) fn normalize_display(&self, value: &str) -> AuthResult<String> {
103 match &self.display_username_normalization {
104 UsernameNormalization::Default | UsernameNormalization::Disabled => {
105 Ok(value.to_owned())
106 }
107 UsernameNormalization::Custom(callback) => callback(value),
108 }
109 }
110
111 pub(super) async fn validate_raw(
112 &self,
113 value: &str,
114 ) -> AuthResult<Option<(&'static str, &'static str)>> {
115 let length = value.encode_utf16().count() as f64;
116 let minimum = if self.min_username_length == 0.0 || self.min_username_length.is_nan() {
117 3.0
118 } else {
119 self.min_username_length
120 };
121 let maximum = if self.max_username_length == 0.0 || self.max_username_length.is_nan() {
122 30.0
123 } else {
124 self.max_username_length
125 };
126 if length < minimum {
127 return Ok(Some(("USERNAME_TOO_SHORT", "Username is too short")));
128 }
129 if length > maximum {
130 return Ok(Some(("USERNAME_TOO_LONG", "Username is too long")));
131 }
132 let valid = match &self.username_validator {
133 Some(callback) => callback.validate(value).await?,
134 None => {
135 !value.is_empty()
136 && value
137 .bytes()
138 .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'.'))
139 }
140 };
141 Ok((!valid).then_some(("INVALID_USERNAME", "Username is invalid")))
142 }
143
144 pub(super) async fn validate_input(
145 &self,
146 value: &str,
147 ) -> AuthResult<Option<(&'static str, &'static str)>> {
148 if self.username_validation_order == Some(UsernameValidationOrder::PostNormalization) {
149 self.validate_raw(&self.normalize(value)?).await
150 } else {
151 self.validate_raw(value).await
152 }
153 }
154
155 pub(super) async fn validate_display(&self, value: &str) -> AuthResult<()> {
156 if let Some(callback) = &self.display_username_validator {
157 let valid = if self.display_username_validation_order
158 == Some(UsernameValidationOrder::PostNormalization)
159 {
160 callback.validate(&self.normalize_display(value)?).await?
161 } else {
162 callback.validate(value).await?
163 };
164 if !valid {
165 return Err(error(
166 400,
167 "INVALID_DISPLAY_USERNAME",
168 "Display username is invalid",
169 ));
170 }
171 }
172 Ok(())
173 }
174}