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#[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
29impl Token {
31 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 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 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 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 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 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 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}