1- use color_eyre:: eyre:: ErrReport ;
1+ use color_eyre:: eyre:: { eyre, ErrReport } ;
2+ use derive_builder:: Builder ;
23use std:: { fs:: File , io:: Write , vec} ;
34
45use crate :: crypto:: cryptography:: { argon_derive_key, encrypt_string_with_key, gen_salt} ;
@@ -126,17 +127,30 @@ impl OTPDatabase {
126127 }
127128}
128129
129- #[ derive( Serialize , Deserialize , Clone , PartialEq , Eq , Debug , Hash , Zeroize , ZeroizeOnDrop ) ]
130+ #[ derive(
131+ Serialize , Deserialize , Builder , Clone , PartialEq , Eq , Debug , Hash , Zeroize , ZeroizeOnDrop ,
132+ ) ]
133+ #[ builder(
134+ setter( into) ,
135+ build_fn( validate = "Self::validate" , error = "ErrReport" )
136+ ) ]
130137pub struct OTPElement {
138+ #[ builder( setter( custom) ) ]
131139 pub secret : String ,
132140 pub issuer : String ,
133141 pub label : String ,
142+ #[ builder( default = "6" ) ]
134143 pub digits : u64 ,
135144 #[ serde( rename = "type" ) ]
145+ #[ builder( default ) ]
136146 pub type_ : OTPType ,
147+ #[ builder( default ) ]
137148 pub algorithm : OTPAlgorithm ,
149+ #[ builder( default = "30" ) ]
138150 pub period : u64 ,
151+ #[ builder( setter( into) , default ) ]
139152 pub counter : Option < u64 > ,
153+ #[ builder( setter( into) , default ) ]
140154 pub pin : Option < String > ,
141155}
142156
@@ -212,11 +226,34 @@ impl OTPElement {
212226 let s = ( value % exponential) . to_string ( ) ;
213227 Ok ( "0" . repeat ( self . digits as usize - s. chars ( ) . count ( ) ) + s. as_str ( ) )
214228 }
229+ }
215230
216- pub fn valid_secret ( & self ) -> bool {
217- match self . type_ {
218- OTPType :: Motp => hex:: decode ( & self . secret ) . is_ok ( ) ,
219- _ => BASE32_NOPAD . decode ( self . secret . as_bytes ( ) ) . is_ok ( ) ,
231+ impl OTPElementBuilder {
232+ /// Makes the secret insertion case insensitive
233+ pub fn secret < VALUE : Into < String > > ( & mut self , value : VALUE ) -> & mut Self {
234+ self . secret = Some ( value. into ( ) . to_uppercase ( ) ) ;
235+ self
236+ }
237+
238+ /// Check if the OTPElement is valid
239+ fn validate ( & self ) -> Result < ( ) , ErrReport > {
240+ if self . secret . is_none ( ) {
241+ return Err ( eyre ! ( "Secret must be set" , ) ) ;
242+ }
243+
244+ if self . secret . as_ref ( ) . unwrap ( ) . is_empty ( ) {
245+ return Err ( eyre ! ( "Secret must not be empty" , ) ) ;
246+ }
247+
248+ // Validate secret encoding
249+ match self . type_ . unwrap_or_default ( ) {
250+ OTPType :: Motp => hex:: decode ( & self . secret . as_ref ( ) . unwrap ( ) )
251+ . map ( |_| { } )
252+ . map_err ( ErrReport :: from) ,
253+ _ => BASE32_NOPAD
254+ . decode ( self . secret . as_ref ( ) . unwrap ( ) . as_bytes ( ) )
255+ . map ( |_| { } )
256+ . map_err ( ErrReport :: from) ,
220257 }
221258 }
222259}
@@ -230,8 +267,8 @@ fn get_label(issuer: &str, label: &str) -> String {
230267#[ cfg( test) ]
231268mod test {
232269 use crate :: otp:: otp_element:: OTPAlgorithm :: Sha1 ;
233- use crate :: otp:: otp_element:: OTPElement ;
234270 use crate :: otp:: otp_element:: OTPType :: Totp ;
271+ use crate :: otp:: otp_element:: { OTPElement , OTPElementBuilder } ;
235272
236273 use crate :: otp:: from_otp_uri:: FromOtpUri ;
237274 use crate :: otp:: otp_error:: OtpError ;
@@ -315,4 +352,17 @@ mod test {
315352 // Assert
316353 assert_eq ! ( Err ( OtpError :: InvalidDigits ) , result)
317354 }
355+
356+ #[ test]
357+ fn test_lowercase_secret ( ) {
358+ // Arrange / Act
359+ let result = OTPElementBuilder :: default ( )
360+ . secret ( "aa" )
361+ . label ( "label" )
362+ . issuer ( "" )
363+ . build ( ) ;
364+
365+ // Assert
366+ assert_eq ! ( "AA" , result. unwrap( ) . secret) ;
367+ }
318368}
0 commit comments