1- use argon2:: { Argon2 , PasswordHash , PasswordHasher , PasswordVerifier } ;
21use argon2:: password_hash:: rand_core:: OsRng ;
32use argon2:: password_hash:: SaltString ;
3+ use argon2:: { Argon2 , PasswordHash , PasswordHasher , PasswordVerifier } ;
4+ use regex:: Regex ;
45use serde:: { Deserialize , Serialize } ;
56use thiserror:: Error ;
6- use regex:: Regex ;
77
88#[ derive( Error , Debug ) ]
99pub enum ApplicationError {
@@ -65,7 +65,7 @@ impl User {
6565 pub fn new ( email_address : & str , name : & str , password : & str ) -> Result < User , ApplicationError > {
6666 User :: email_is_valid ( email_address) ?;
6767 User :: password_is_valid ( password) ?;
68-
68+
6969 Ok ( User :: Standard {
7070 user_details : UserDetails {
7171 email_address : email_address. to_string ( ) ,
@@ -90,12 +90,15 @@ impl User {
9090 fn hash ( password : & str ) -> Result < String , ApplicationError > {
9191 let argon2 = Argon2 :: default ( ) ;
9292 let salt = SaltString :: generate ( & mut OsRng ) ;
93- let hash = argon2. hash_password ( password. as_bytes ( ) , & salt)
94- . map_err ( |_| ApplicationError :: ApplicationError ( "Failed to hash password" . to_string ( ) ) ) ?;
93+ let hash = argon2
94+ . hash_password ( password. as_bytes ( ) , & salt)
95+ . map_err ( |_| {
96+ ApplicationError :: ApplicationError ( "Failed to hash password" . to_string ( ) )
97+ } ) ?;
9598
9699 Ok ( hash. to_string ( ) )
97100 }
98-
101+
99102 pub fn details ( & self ) -> & UserDetails {
100103 match self {
101104 User :: Standard { user_details } => user_details,
@@ -105,7 +108,7 @@ impl User {
105108 } => user_details,
106109 }
107110 }
108-
111+
109112 pub fn email_address ( & self ) -> String {
110113 match self {
111114 User :: Standard { user_details } => user_details. email_address . clone ( ) ,
@@ -115,7 +118,7 @@ impl User {
115118 } => user_details. email_address . clone ( ) ,
116119 }
117120 }
118-
121+
119122 pub fn name ( & self ) -> String {
120123 match self {
121124 User :: Standard { user_details } => user_details. name . clone ( ) ,
@@ -125,7 +128,7 @@ impl User {
125128 } => user_details. name . clone ( ) ,
126129 }
127130 }
128-
131+
129132 pub fn password ( & self ) -> String {
130133 match self {
131134 User :: Standard { user_details } => user_details. password . clone ( ) ,
@@ -183,40 +186,52 @@ impl User {
183186
184187 pub fn verify_password ( & self , password : & str ) -> Result < ( ) , ApplicationError > {
185188 let users_password = & self . password ( ) . clone ( ) ;
186-
187- let parsed_hash = PasswordHash :: new ( users_password) . map_err ( |_| ApplicationError :: ApplicationError ( "Failed to parse password hash" . to_string ( ) ) ) ?;
188-
189- let verified_password = Argon2 :: default ( )
190- . verify_password ( password. as_bytes ( ) , & parsed_hash) ;
191-
189+
190+ let parsed_hash = PasswordHash :: new ( users_password) . map_err ( |_| {
191+ ApplicationError :: ApplicationError ( "Failed to parse password hash" . to_string ( ) )
192+ } ) ?;
193+
194+ let verified_password =
195+ Argon2 :: default ( ) . verify_password ( password. as_bytes ( ) , & parsed_hash) ;
196+
192197 match verified_password {
193198 Ok ( _) => Ok ( ( ) ) ,
194- Err ( _) => Err ( ApplicationError :: IncorrectPassword )
195- }
199+ Err ( _) => Err ( ApplicationError :: IncorrectPassword ) ,
200+ }
196201 }
197202
198203 fn password_is_valid ( password : & str ) -> Result < ( ) , ApplicationError > {
199204 if password. len ( ) < 8 {
200- return Err ( ApplicationError :: ApplicationError ( "Password must be at least 8 characters long" . to_string ( ) ) ) ;
205+ return Err ( ApplicationError :: ApplicationError (
206+ "Password must be at least 8 characters long" . to_string ( ) ,
207+ ) ) ;
201208 }
202209 if !password. chars ( ) . any ( |c| c. is_uppercase ( ) ) {
203- return Err ( ApplicationError :: ApplicationError ( "Password must contain at least one uppercase letter" . to_string ( ) ) ) ;
210+ return Err ( ApplicationError :: ApplicationError (
211+ "Password must contain at least one uppercase letter" . to_string ( ) ,
212+ ) ) ;
204213 }
205214 if !password. chars ( ) . any ( |c| c. is_lowercase ( ) ) {
206- return Err ( ApplicationError :: ApplicationError ( "Password must contain at least one lowercase letter" . to_string ( ) ) ) ;
215+ return Err ( ApplicationError :: ApplicationError (
216+ "Password must contain at least one lowercase letter" . to_string ( ) ,
217+ ) ) ;
207218 }
208219 if !password. chars ( ) . any ( |c| c. is_ascii_digit ( ) ) {
209- return Err ( ApplicationError :: ApplicationError ( "Password must contain at least one digit" . to_string ( ) ) ) ;
220+ return Err ( ApplicationError :: ApplicationError (
221+ "Password must contain at least one digit" . to_string ( ) ,
222+ ) ) ;
210223 }
211224 Ok ( ( ) )
212225 }
213-
226+
214227 fn email_is_valid ( input : & str ) -> Result < ( ) , ApplicationError > {
215228 let re = Regex :: new ( r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$" ) . unwrap ( ) ;
216229 if re. is_match ( input) {
217230 Ok ( ( ) )
218231 } else {
219- Err ( ApplicationError :: ApplicationError ( "Invalid email address" . to_string ( ) ) )
232+ Err ( ApplicationError :: ApplicationError (
233+ "Invalid email address" . to_string ( ) ,
234+ ) )
220235 }
221236 }
222237}
@@ -228,7 +243,7 @@ mod tests {
228243 #[ test]
229244 fn when_new_user_is_created_should_be_standard ( ) {
230245 let user =
User :: new ( "[email protected] " , "James" , "James!23" ) . unwrap ( ) ; 231-
246+
232247 if let User :: Standard { user_details } = user {
233248 assert_eq ! ( user_details
. email_address
, "[email protected] " ) ; 234249 assert_eq ! ( user_details. name, "James" ) ;
@@ -240,10 +255,14 @@ mod tests {
240255 #[ test]
241256 fn when_user_is_updated_to_premium_should_be_premium_user ( ) {
242257 let user =
User :: new ( "[email protected] " , "James" , "James!23" ) . unwrap ( ) ; 243-
258+
244259 let premium_user = user. update_to_premium ( ) ;
245260
246- if let User :: Premium { user_details, is_premium } = premium_user {
261+ if let User :: Premium {
262+ user_details,
263+ is_premium,
264+ } = premium_user
265+ {
247266 assert_eq ! ( user_details
. email_address
, "[email protected] " ) ; 248267 assert_eq ! ( user_details. name, "James" ) ;
249268 } else {
@@ -256,7 +275,7 @@ mod tests {
256275 let mut user =
User :: new ( "[email protected] " , "James" , "James!23" ) . unwrap ( ) ; 257276
258277 assert_eq ! ( user. details( ) . age, None ) ;
259-
278+
260279 user. update_age ( 10 ) ;
261280
262281 assert_eq ! ( user. details( ) . age. unwrap( ) , 10 ) ;
@@ -267,7 +286,7 @@ mod tests {
267286 let mut user =
User :: new ( "[email protected] " , "James" , "James!23" ) . unwrap ( ) ; 268287
269288 assert_eq ! ( user. details( ) . name, "James" ) ;
270-
289+
271290 user. update_name ( "John" ) ;
272291
273292 assert_eq ! ( user. details( ) . name, "John" ) ;
@@ -290,11 +309,11 @@ mod tests {
290309 #[ test]
291310 fn when_user_is_created_should_verify_a_matching_password ( ) {
292311 let user =
User :: new ( "[email protected] " , "James" , "James!23" ) . unwrap ( ) ; 293-
312+
294313 assert_ne ! ( user. password( ) , "Test!23" ) ;
295-
314+
296315 let is_password_valid = user. verify_password ( "James!23" ) ;
297-
316+
298317 assert ! ( is_password_valid. is_ok( ) ) ;
299318 }
300319
0 commit comments