1
- use color_eyre:: eyre:: ErrReport ;
1
+ use color_eyre:: eyre:: { eyre, ErrReport } ;
2
+ use derive_builder:: Builder ;
2
3
use std:: { fs:: File , io:: Write , vec} ;
3
4
4
5
use crate :: crypto:: cryptography:: { argon_derive_key, encrypt_string_with_key, gen_salt} ;
@@ -126,17 +127,30 @@ impl OTPDatabase {
126
127
}
127
128
}
128
129
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
+ ) ]
130
137
pub struct OTPElement {
138
+ #[ builder( setter( custom) ) ]
131
139
pub secret : String ,
132
140
pub issuer : String ,
133
141
pub label : String ,
142
+ #[ builder( default = "6" ) ]
134
143
pub digits : u64 ,
135
144
#[ serde( rename = "type" ) ]
145
+ #[ builder( default ) ]
136
146
pub type_ : OTPType ,
147
+ #[ builder( default ) ]
137
148
pub algorithm : OTPAlgorithm ,
149
+ #[ builder( default = "30" ) ]
138
150
pub period : u64 ,
151
+ #[ builder( setter( into) , default ) ]
139
152
pub counter : Option < u64 > ,
153
+ #[ builder( setter( into) , default ) ]
140
154
pub pin : Option < String > ,
141
155
}
142
156
@@ -212,11 +226,34 @@ impl OTPElement {
212
226
let s = ( value % exponential) . to_string ( ) ;
213
227
Ok ( "0" . repeat ( self . digits as usize - s. chars ( ) . count ( ) ) + s. as_str ( ) )
214
228
}
229
+ }
215
230
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) ,
220
257
}
221
258
}
222
259
}
@@ -230,8 +267,8 @@ fn get_label(issuer: &str, label: &str) -> String {
230
267
#[ cfg( test) ]
231
268
mod test {
232
269
use crate :: otp:: otp_element:: OTPAlgorithm :: Sha1 ;
233
- use crate :: otp:: otp_element:: OTPElement ;
234
270
use crate :: otp:: otp_element:: OTPType :: Totp ;
271
+ use crate :: otp:: otp_element:: { OTPElement , OTPElementBuilder } ;
235
272
236
273
use crate :: otp:: from_otp_uri:: FromOtpUri ;
237
274
use crate :: otp:: otp_error:: OtpError ;
@@ -315,4 +352,17 @@ mod test {
315
352
// Assert
316
353
assert_eq ! ( Err ( OtpError :: InvalidDigits ) , result)
317
354
}
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
+ }
318
368
}
0 commit comments