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( setter( custom) , 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,50 @@ 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+ /// Makes the secret insertion case insensitive
239+ pub fn type_ < VALUE : Into < OTPType > > ( & mut self , value : VALUE ) -> & mut Self {
240+ let otp_type: OTPType = value. into ( ) ;
241+
242+ if otp_type == OTPType :: Motp {
243+ // Motp codes must be lowercase
244+ self . secret = self . secret . as_ref ( ) . map ( |s| s. to_lowercase ( ) )
245+ } else {
246+ // Base32 codes must be uppercase
247+ self . secret = self . secret . as_ref ( ) . map ( |s| s. to_uppercase ( ) )
248+ }
249+
250+ self . type_ = Some ( otp_type) ;
251+ self
252+ }
253+
254+ /// Check if the OTPElement is valid
255+ fn validate ( & self ) -> Result < ( ) , ErrReport > {
256+ if self . secret . is_none ( ) {
257+ return Err ( eyre ! ( "Secret must be set" , ) ) ;
258+ }
259+
260+ if self . secret . as_ref ( ) . unwrap ( ) . is_empty ( ) {
261+ return Err ( eyre ! ( "Secret must not be empty" , ) ) ;
262+ }
263+
264+ // Validate secret encoding
265+ match self . type_ . unwrap_or_default ( ) {
266+ OTPType :: Motp => hex:: decode ( self . secret . as_ref ( ) . unwrap ( ) )
267+ . map ( |_| { } )
268+ . map_err ( |e| eyre ! ( "Invalid hex secret: {e}" ) ) ,
269+ _ => BASE32_NOPAD
270+ . decode ( self . secret . as_ref ( ) . unwrap ( ) . as_bytes ( ) )
271+ . map ( |_| { } )
272+ . map_err ( |e| eyre ! ( "Invalid BASE32 secret: {e}" ) ) ,
220273 }
221274 }
222275}
@@ -230,11 +283,12 @@ fn get_label(issuer: &str, label: &str) -> String {
230283#[ cfg( test) ]
231284mod test {
232285 use crate :: otp:: otp_element:: OTPAlgorithm :: Sha1 ;
233- use crate :: otp:: otp_element:: OTPElement ;
234286 use crate :: otp:: otp_element:: OTPType :: Totp ;
287+ use crate :: otp:: otp_element:: { OTPElement , OTPElementBuilder } ;
235288
236289 use crate :: otp:: from_otp_uri:: FromOtpUri ;
237290 use crate :: otp:: otp_error:: OtpError ;
291+ use crate :: otp:: otp_type:: OTPType ;
238292
239293 #[ test]
240294 fn test_serialization_otp_uri_full_element ( ) {
@@ -315,4 +369,58 @@ mod test {
315369 // Assert
316370 assert_eq ! ( Err ( OtpError :: InvalidDigits ) , result)
317371 }
372+
373+ #[ test]
374+ fn test_lowercase_secret ( ) {
375+ // Arrange / Act
376+ let result = OTPElementBuilder :: default ( )
377+ . secret ( "aa" )
378+ . label ( "label" )
379+ . issuer ( "" )
380+ . build ( ) ;
381+
382+ // Assert
383+ assert_eq ! ( "AA" , result. unwrap( ) . secret) ;
384+ }
385+
386+ #[ test]
387+ fn test_invalid_secret_base32 ( ) {
388+ let result = OTPElementBuilder :: default ( )
389+ . secret ( "aaa" )
390+ . label ( "label" )
391+ . issuer ( "" )
392+ . build ( ) ;
393+
394+ assert_eq ! (
395+ "Invalid BASE32 secret: invalid length at 2" ,
396+ result. unwrap_err( ) . to_string( )
397+ )
398+ }
399+
400+ #[ test]
401+ fn valid_hex_secret ( ) {
402+ let result = OTPElementBuilder :: default ( )
403+ . secret ( "aAAf" )
404+ . label ( "label" )
405+ . issuer ( "" )
406+ . type_ ( OTPType :: Motp )
407+ . build ( ) ;
408+
409+ assert_eq ! ( "aaaf" , result. unwrap( ) . secret)
410+ }
411+
412+ #[ test]
413+ fn invalid_secret_hex ( ) {
414+ let result = OTPElementBuilder :: default ( )
415+ . secret ( "aaa" )
416+ . label ( "label" )
417+ . issuer ( "" )
418+ . type_ ( OTPType :: Motp )
419+ . build ( ) ;
420+
421+ assert_eq ! (
422+ "Invalid hex secret: Odd number of digits" ,
423+ result. unwrap_err( ) . to_string( )
424+ )
425+ }
318426}
0 commit comments