alibi_api/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(in crate::plugins) async fn store_codes(
40 &self,
41 codes: &[String],
42 secret: &alibi_core::AuthConfig,
43 ) -> AuthResult<String> {
44 self.store_json(serde_json::to_string(codes)?, secret).await
45 }
46
47 pub(in crate::plugins) async fn store_json(
48 &self,
49 json: String,
50 secret: &alibi_core::AuthConfig,
51 ) -> AuthResult<String> {
52 match self {
53 Self::Encrypted => super::encrypt_value(secret, &json),
54 Self::Plain => Ok(json),
55 Self::CustomCipher(cipher) => cipher.encrypt(&json).await.map_err(callback_error),
56 }
57 }
58
59 pub(in crate::plugins) async fn load_value(
63 &self,
64 stored: &str,
65 secret: &alibi_core::AuthConfig,
66 ) -> AuthResult<Option<alibi_core::utils::json::JsValue>> {
67 let json = match self {
68 Self::Encrypted => super::decrypt_value(secret, stored)?,
69 Self::Plain => stored.to_owned(),
70 Self::CustomCipher(cipher) => cipher.decrypt(stored).await.map_err(callback_error)?,
71 };
72 Ok(alibi_core::utils::json::parse_value(&json).ok())
73 }
74}
75
76pub(super) fn json_date(value: &str) -> Option<String> {
77 alibi_core::utils::datetime::normalize_json_date(value)
78}
79
80pub(super) fn normalize_json_dates(value: &mut alibi_core::utils::json::JsValue) {
81 use alibi_core::utils::json::JsValue;
82 match value {
83 JsValue::String(text) => {
84 if let Some(date) = json_date(text) {
85 *text = date;
86 }
87 }
88 JsValue::Array(values) => values.iter_mut().for_each(normalize_json_dates),
89 JsValue::Object(values) => values.values_mut().for_each(normalize_json_dates),
90 _ => {}
91 }
92}
93
94pub(super) fn truthy(value: &alibi_core::utils::json::JsValue) -> bool {
95 use alibi_core::utils::json::JsValue;
96 match value {
97 JsValue::Null | JsValue::Bool(false) => false,
98 JsValue::Number(number) => *number != 0.0 && !number.is_nan(),
99 JsValue::String(text) => !text.is_empty(),
100 _ => true,
101 }
102}
103
104fn callback_error(error: AuthError) -> AuthError {
105 match error {
106 AuthError::Internal(_) => AuthError::CallbackFailure(Box::new(error)),
107 error => error,
108 }
109}