1use super::Schema;
2use std::fmt::Display;
3use zino_core::{
4 LazyLock, Map, crypto,
5 encoding::base64,
6 error::Error,
7 extension::{JsonObjectExt, TomlTableExt},
8 state::State,
9 warn,
10};
11
12pub trait ModelHelper<K>: Schema<PrimaryKey = K>
14where
15 K: Default + Display + PartialEq,
16{
17 #[inline]
24 fn secret_key() -> &'static [u8] {
25 SECRET_KEY.as_slice()
26 }
27
28 fn encrypt_password(password: &str) -> Result<String, Error> {
30 let key = Self::secret_key();
31 let password = password.as_bytes();
32 if base64::decode(password).is_ok_and(|bytes| bytes.len() == 256) {
33 crypto::encrypt_hashed_password(password, key)
34 .map_err(|err| warn!("fail to encrypt hashed password: {}", err.message()))
35 } else {
36 crypto::encrypt_raw_password(password, key)
37 .map_err(|err| warn!("fail to encrypt raw password: {}", err.message()))
38 }
39 }
40
41 fn verify_password(password: &str, encrypted_password: &str) -> Result<bool, Error> {
43 let key = Self::secret_key();
44 let password = password.as_bytes();
45 let encrypted_password = encrypted_password.as_bytes();
46 if base64::decode(password).is_ok_and(|bytes| bytes.len() == 256) {
47 crypto::verify_hashed_password(password, encrypted_password, key)
48 .map_err(|err| warn!("fail to verify hashed password: {}", err.message()))
49 } else {
50 crypto::verify_raw_password(password, encrypted_password, key)
51 .map_err(|err| warn!("fail to verify raw password: {}", err.message()))
52 }
53 }
54
55 fn translate_model(model: &mut Map) {
57 #[cfg(feature = "openapi")]
58 zino_openapi::translate_model_entry(model, Self::model_name());
59 for col in Self::columns() {
60 if let Some(translated_field) = col.extra().get_str("translate_as") {
61 let field = [col.name(), "_translated"].concat();
62 if let Some(value) = model.remove(&field) {
63 model.upsert(translated_field, value);
64 }
65 }
66 }
67 }
68}
69
70impl<M, K> ModelHelper<K> for M
71where
72 M: Schema<PrimaryKey = K>,
73 K: Default + Display + PartialEq,
74{
75}
76
77static SECRET_KEY: LazyLock<[u8; 64]> = LazyLock::new(|| {
79 let app_config = State::shared().config();
80 let config = app_config.get_table("database").unwrap_or(app_config);
81 let checksum: [u8; 32] = config
82 .get_str("checksum")
83 .and_then(|checksum| checksum.as_bytes().try_into().ok())
84 .unwrap_or_else(|| {
85 let secret = config
86 .get_str("secret")
87 .map(|s| s.to_owned())
88 .unwrap_or_else(|| {
89 tracing::warn!("auto-generated `secret` is used for deriving a secret key");
90 format!("{}{}", *super::TABLE_PREFIX, super::DRIVER_NAME)
91 });
92 crypto::digest(secret.as_bytes())
93 });
94 let info = config.get_str("info").unwrap_or("ZINO:ORM");
95 crypto::derive_key(info, &checksum)
96});