Skip to main content

hvz/
token.rs

1use crate::{
2    establish_connection,
3    player::{Player, PlayerRole},
4    schema::players,
5    schema::tokens,
6};
7
8use actix_web::HttpRequest;
9use base16::encode_upper;
10use chrono::{Days, NaiveDateTime, Utc};
11use diesel::prelude::*;
12use diesel::{
13    ExpressionMethods, QueryDsl, RunQueryDsl,
14    prelude::{Insertable, Queryable, QueryableByName},
15};
16use log::{debug, info};
17use rand::{RngCore, SeedableRng, rngs::StdRng};
18
19/// Token Struct
20#[derive(Clone, Debug, Selectable, Queryable, QueryableByName, Insertable)]
21#[diesel(table_name = crate::schema::tokens)]
22#[diesel(check_for_backend(diesel::mysql::Mysql))]
23pub struct Token {
24    pub token: String,
25    pub user_id: String,
26    pub expiration: NaiveDateTime,
27}
28
29/// Methods related to the token struct
30impl Token {
31    /// Generate a new token for a player
32    ///
33    /// # Arguments
34    /// * `player_id` - The ID of the player to generate a token for
35    ///
36    /// # Returns
37    /// A Result containing the generated token or an error message
38    pub fn generate(player_id: &str) -> Result<Token, String> {
39        debug!("Generating token for: {}", player_id);
40
41        let mut rng = StdRng::from_rng(&mut rand::rng());
42        let mut token = String::new();
43        for _ in 0..4 {
44            let num: u64 = rng.next_u64();
45            token.push_str(&encode_upper(&num.to_be_bytes()));
46        }
47
48        info!("Token: {}", token);
49        let token_result = Self {
50            token: token,
51            user_id: player_id.to_string(),
52            expiration: match Utc::now().naive_utc().checked_add_days(Days::new(3)) {
53                Some(d) => d,
54                None => return Err("Failed to add days".to_owned()),
55            },
56        };
57
58        token_result.store()?;
59
60        Ok(token_result)
61    }
62
63    /// Store the token in the database
64    ///
65    /// # Arguments
66    /// * `self` - The token to store
67    ///
68    /// # Returns
69    /// A Result containing the stored token or an error message
70    fn store(&self) -> Result<String, String> {
71        Token::clear_tokens()?;
72
73        debug!("Storing token: {}", self.token);
74        let connection = &mut establish_connection();
75        diesel::insert_into(tokens::table)
76            .values(self.clone())
77            .execute(connection)
78            .expect("Error inserting token");
79
80        info!("Token stored successfully");
81        Ok(self.token.clone())
82    }
83
84    /// Check if the token is valid and belongs to the user
85    ///
86    /// # Arguments
87    /// * `self` - The token to check
88    ///
89    /// # Returns
90    /// A Result containing the token or an error message
91    pub async fn check_token(req: &HttpRequest, user_id: &String) -> Result<Token, String> {
92        let token_id = req
93            .headers()
94            .get("Authorization-Header")
95            .and_then(|header| header.to_str().ok());
96        if let Some(id) = token_id {
97            let token = match Token::get_token_by_token(id) {
98                Ok(token) => token,
99                Err(_) => return Err("Invalid token".to_owned()),
100            };
101            if user_id == &token.user_id {
102                Ok(token)
103            } else {
104                Err("Token does not correspond to user".to_owned())
105            }
106        } else {
107            Err("Authorization header not found".to_owned())
108        }
109    }
110
111    /// Get a token by player ID
112    ///
113    /// # Arguments
114    /// * `id` - The player ID to get the token for
115    ///
116    /// # Returns
117    /// A Result containing the token or an error message
118    pub fn get_token_by_player(id: &str) -> Result<Token, String> {
119        Token::clear_tokens()?;
120        let connection = &mut establish_connection();
121        match tokens::table
122            .filter(tokens::user_id.eq(&id))
123            .first::<Token>(connection)
124        {
125            Ok(t) => Ok(t),
126            Err(_) => Err("Player not found on token database".to_owned()),
127        }
128    }
129
130    /// Get a token by token ID
131    ///
132    /// # Arguments
133    /// * `token` - The token ID to get the token for
134    ///
135    /// # Returns
136    /// A Result containing the token or an error message
137    pub fn get_token_by_token(token: &str) -> Result<Token, String> {
138        Token::clear_tokens()?;
139        let connection = &mut establish_connection();
140        match tokens::table
141            .filter(tokens::token.eq(&token))
142            .first::<Token>(connection)
143        {
144            Ok(t) => Ok(t),
145            Err(_) => Err("Token not found on token database".to_owned()),
146        }
147    }
148
149    /// Clears the tokens for a certain player
150    ///
151    /// # Arguments
152    /// The player who is logging out
153    ///
154    /// # Returns
155    /// Whether the token deletion was successful or not
156    pub fn clear_player_tokens(player: &Player) -> Result<usize, String> {
157        Token::clear_tokens()?;
158        let connection = &mut establish_connection();
159        let n = diesel::delete(tokens::table.filter(tokens::user_id.eq(&player.id)))
160            .execute(connection)
161            .expect("Could not delete all tokens for player");
162        Ok(n)
163    }
164
165    /// Attempts to clear all expired tokens
166    ///
167    /// # Returns
168    /// * The number of rows deleted
169    /// * An error message
170    pub fn clear_tokens() -> Result<usize, String> {
171        let connection = &mut establish_connection();
172
173        let n = diesel::delete(tokens::table.filter(tokens::expiration.le(Utc::now().naive_utc())))
174            .filter(
175                tokens::user_id.eq_any(
176                    players::table
177                        .filter(players::role.ne(PlayerRole::Mod))
178                        .select(players::id),
179                ),
180            )
181            .execute(connection)
182            .expect("Failed to delete expired tokens");
183
184        info!("Deleted {} expired tokens\n", n);
185        Ok(n)
186    }
187}