alibi_plugins/two_factor/
backup_storage.rs1use alibi_core::{AuthError, AuthResult};
2use async_trait::async_trait;
3use std::sync::Arc;
4
5#[async_trait]
11pub trait TwoFactorBackupCipher: Send + Sync {
12 async fn encrypt(&self, json: &str) -> AuthResult<String>;
13 async fn decrypt(&self, stored: &str) -> AuthResult<String>;
14}
15
16#[derive(Clone, Default)]
18pub enum TwoFactorBackupStorage {
19 #[default]
20 Encrypted,
21 Plain,
22 CustomCipher(Arc<dyn TwoFactorBackupCipher>),
23}
24
25impl std::fmt::Debug for TwoFactorBackupStorage {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 f.write_str(match self {
28 Self::Encrypted => "Encrypted",
29 Self::Plain => "Plain",
30 Self::CustomCipher(_) => "CustomCipher",
31 })
32 }
33}
34
35impl TwoFactorBackupStorage {
36 pub(crate) async fn store_codes(
37 &self,
38 codes: &[String],
39 secret: &alibi_core::AuthConfig,
40 ) -> AuthResult<String> {
41 self.store_json(serde_json::to_string(codes)?, secret).await
42 }
43
44 pub(crate) async fn store_json(
45 &self,
46 json: String,
47 secret: &alibi_core::AuthConfig,
48 ) -> AuthResult<String> {
49 match self {
50 Self::Encrypted => super::encrypt_value(secret, &json),
51 Self::Plain => Ok(json),
52 Self::CustomCipher(cipher) => cipher.encrypt(&json).await.map_err(callback_error),
53 }
54 }
55
56 pub(crate) async fn load_value(
60 &self,
61 stored: &str,
62 secret: &alibi_core::AuthConfig,
63 ) -> AuthResult<Option<alibi_core::utils::json::JsValue>> {
64 let json = match self {
65 Self::Encrypted => super::decrypt_value(secret, stored)?,
66 Self::Plain => stored.to_owned(),
67 Self::CustomCipher(cipher) => cipher.decrypt(stored).await.map_err(callback_error)?,
68 };
69 Ok(alibi_core::utils::json::parse_value(&json).ok())
70 }
71}
72
73pub(super) fn json_date(value: &str) -> Option<String> {
74 alibi_core::utils::datetime::normalize_json_date(value)
75}
76
77pub(super) fn normalize_json_dates(value: &mut alibi_core::utils::json::JsValue) {
78 use alibi_core::utils::json::JsValue;
79 match value {
80 JsValue::String(text) => {
81 if let Some(date) = json_date(text) {
82 *text = date;
83 }
84 }
85 JsValue::Array(values) => values.iter_mut().for_each(normalize_json_dates),
86 JsValue::Object(values) => values.values_mut().for_each(normalize_json_dates),
87 _ => {}
88 }
89}
90
91pub(super) fn truthy(value: &alibi_core::utils::json::JsValue) -> bool {
92 use alibi_core::utils::json::JsValue;
93 match value {
94 JsValue::Null | JsValue::Bool(false) => false,
95 JsValue::Number(number) => *number != 0.0 && !number.is_nan(),
96 JsValue::String(text) => !text.is_empty(),
97 _ => true,
98 }
99}
100
101fn callback_error(error: AuthError) -> AuthError {
102 match error {
103 AuthError::Internal(_) => AuthError::CallbackFailure(Box::new(error)),
104 error => error,
105 }
106}