dynamic_token/
from_key.rs

1use simple_string_patterns::{ToSegments,ToSegmentsFromChars};
2use crate::utils::*;
3use crate::valid_auth_token::ValidAuthToken;
4use crate::auth_status::*;
5use crate::auth_options::*;
6use crate::MIN_VALID_UUID_LENGTH;
7
8/// Validate the dynamic key based on a shared API key, random characters, a timestamp and optional userID
9pub fn from_dynamic_key(
10  sample: &str,
11  options: &AuthOptions,
12) -> ValidAuthToken {
13  let mut uuid: Option<String> = None;
14  let mut valid = false;
15  let api_key = options.key();
16  let mut status = AuthStatus::None;
17  let decoded_string = decode_base64(sample);
18  let first_char = decoded_string.chars().nth(0).unwrap_or('-');
19  let mut age: Option<i64> = None;
20  if first_char.is_alphanumeric() {
21    let decoded_int = base_36_to_u8(&first_char).unwrap_or(0);
22    let offset = (decoded_int % 6) + 2;
23    let api_key_index = decoded_string.find(api_key).unwrap_or(255) as u8;
24    if api_key_index == offset {
25      let parts = decoded_string.to_parts("__");
26      // check userId if required
27      if options.should_check_uuid() && parts.len() > 1 {
28        if let Some(long_str) = parts.last() {
29          let (first, second) = long_str.to_head_tail("_");
30          if second.len() > 6 {
31            let (tail_end, int_str) = second.to_head_tail_on_any_char(&options.rand_chars());
32            let uid_str = [
33              base_36_parts_to_hex_dec(&first),
34              base_36_parts_to_hex_dec(&tail_end)
35            ].into_iter().map(|opt| opt.unwrap_or("".to_string())).collect::<Vec<String>>().concat();
36            let rand_int = base_36_str_to_u64(&int_str);
37            if uid_str.len() >= MIN_VALID_UUID_LENGTH {
38              uuid = Some(uid_str.clone());
39              valid = rand_int.is_some();
40              status = if valid {
41                AuthStatus::Ok
42              } else {
43                AuthStatus::UuidRequired
44              };
45            }
46          } 
47        }
48      } else {
49        valid = true;
50        status = AuthStatus::Ok;
51      }
52      // use only the base string before the user ID
53      let base_str = parts.get(0).unwrap();
54      let ts_parts = base_str.to_parts(api_key);
55      // the timestamp parts are either side of the decoded API key. Concatenate them to reassemble
56      // ts_str and base_suffix is base-36 encoded timestamps. The latter is randomised and must only be a valid integer
57      let (ts_str, base_suffix) = ts_parts.concat().to_head_tail_on_any_char(&options.rand_chars());
58      if valid {
59        // must have a valid base-36 format
60        valid = ts_str.chars().all(|c| c.is_alphanumeric());
61        if !valid {
62          status = AuthStatus::InvalidTimestamp;
63        }
64      }
65      // If a UUID is required and not present, the timestamp will not be authenticated
66      if valid {
67        if let Some(_suffix_int) = base_36_str_to_u64(&base_suffix) {
68          // reverse the the base-36 string
69          let characters: String = ts_str.chars().rev().collect();
70          // Cast it to u64 and then to i64 for comparison
71          let ts = base_36_str_to_u64(&characters).unwrap_or(0) as i64;
72          let curr_ts = milliseconds_now();
73          let age_ms = curr_ts - ts;
74          let max_age_ms = options.tolerance();
75          let min_age_ms = 0 - max_age_ms;
76          // the dynamic key is valid if it's within the time range allowing for HTTP query time and / or minor discrepancies current unix time
77          valid = age_ms >= min_age_ms && age_ms <= max_age_ms;
78          // assign the age in milliseconds
79          age = Some(age_ms);
80          // if a uuid is required, the token is only valid if it has been set
81          status = if valid {
82            AuthStatus::Ok
83          } else {
84            AuthStatus::TimedOut
85          };
86        }
87      }
88    }
89  }
90  ValidAuthToken(valid, uuid, age, status)
91}