1#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
22use bb8_redis::redis::{AsyncCommands, LposOptions};
23
24#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
25use cc_utils::results::MResult;
26
27#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
28use chrono::Duration;
29
30#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
31use passwords::PasswordGenerator;
32
33use chrono::{DateTime, Utc, serde::ts_seconds};
34use serde::{Deserialize, Serialize};
35use sha3::{Digest, Sha3_256};
36
37#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
39const TOKEN_LENGTH: usize = 64;
40
41const TOKEN_PREFIX: &str = "user_tokens";
43
44pub const MAX_TOKENS_PER_USER: isize = 3;
46
47pub const DAYS_VALID: i64 = 28;
49
50pub type UserId = u64;
54
55#[derive(Deserialize, Serialize)]
57pub struct UserToken {
58 pub user_id: UserId,
59 token_str: String,
60 #[serde(with = "ts_seconds")]
61 birth: DateTime<Utc>,
62}
63
64#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
65impl UserToken {
66 pub fn new(id: UserId) -> MResult<Self> { generate_token(id) }
67}
68
69pub type ApiToken = String;
71
72pub fn hash_password(user_password: &[u8], user_salt: &[u8]) -> Vec<u8> {
74 let mut hasher = Sha3_256::new();
75 hasher.update([user_password, user_salt].concat());
76 hasher.finalize().to_vec()
77}
78
79pub fn hashes_eq(user_password: &[u8], salt_from_db: &[u8], hash_from_db: &[u8]) -> bool {
81 hash_password(user_password, salt_from_db).eq(hash_from_db)
82}
83
84pub fn get_user_tokens_list_name(user_id: UserId) -> String {
86 format!("{}:id{}", TOKEN_PREFIX, user_id)
87}
88
89#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
91pub async fn log_in(
92 user_login: String,
93 salt_db: &[u8],
94 hash_db: &[u8],
95 possible_user_id: UserId,
96 cacher: &bb8_redis::bb8::Pool<bb8_redis::RedisConnectionManager>,
97) -> MResult<UserToken> {
98 if !hashes_eq(user_login.as_bytes(), salt_db, hash_db) { return Err("Hashes are not equal.".into()) } ;
99 let utl_name = get_user_tokens_list_name(possible_user_id);
100 let mut cacher_conn = cacher.get().await?;
101 let user_tokens_list_len: isize = cacher_conn.llen(&utl_name).await?;
102 let token = generate_token(possible_user_id)?;
103 if user_tokens_list_len >= MAX_TOKENS_PER_USER { cacher_conn.ltrim(&utl_name, 0, MAX_TOKENS_PER_USER - 1).await?; }
104 cacher_conn.lpush(&utl_name, &serde_json::to_string(&token)?).await?;
105 Ok(token)
106}
107
108#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
110pub async fn check_token(
111 token: &ApiToken,
112 cacher: &bb8_redis::bb8::Pool<bb8_redis::RedisConnectionManager>,
113) -> MResult<UserId> {
114 let token_data = serde_json::from_str::<UserToken>(&token)?;
115 let user_tokens_list = get_user_tokens_list_name(token_data.user_id);
116 let mut cacher_conn = cacher.get().await?;
117 let idx: Option<i32> = cacher_conn.lpos(&user_tokens_list, &token, LposOptions::default()).await?;
118 if idx.is_none() { return Err("There is no such tokens.".into()) }
119 let duration: Duration = Utc::now() - token_data.birth;
120 if duration.num_days() >= DAYS_VALID {
121 cacher_conn.lrem(user_tokens_list, 1, &token).await?;
122 return Err("The token is expired.".into())
123 }
124 Ok(token_data.user_id)
125}
126
127#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
129pub async fn check_and_remove_token(
130 token: &ApiToken,
131 cacher: &bb8_redis::bb8::Pool<bb8_redis::RedisConnectionManager>,
132) -> MResult<()> {
133 let token_data = serde_json::from_str::<UserToken>(&token)?;
134 let user_tokens_list = get_user_tokens_list_name(token_data.user_id);
135 let mut cacher_conn = cacher.get().await?;
136 let idx: Option<i32> = cacher_conn.lpos(&user_tokens_list, &token, LposOptions::default()).await?;
137 if idx.is_none() { return Err("There is no such tokens.".into()) }
138 cacher_conn.lrem(user_tokens_list, 1, &token).await?;
139 Ok(())
140}
141
142#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
144fn get_password_generator(length: usize) -> PasswordGenerator {
145 PasswordGenerator {
146 length,
147 numbers: true,
148 lowercase_letters: true,
149 uppercase_letters: true,
150 symbols: true,
151 strict: true,
152 exclude_similar_characters: true,
153 spaces: false,
154 }
155}
156
157#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
159pub fn generate_token(user_id: UserId) -> MResult<UserToken> {
160 Ok(UserToken {
161 user_id,
162 token_str: get_password_generator(TOKEN_LENGTH).generate_one()?,
163 birth: Utc::now(),
164 })
165}
166
167#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
169pub fn generate_salt() -> MResult<String> {
170 Ok(get_password_generator(16).generate_one()?)
171}