1
0
mirror of https://gitlab.com/comunic/comunicapiv3 synced 2024-11-22 13:29:21 +00:00

Upgrade user tokens system

This commit is contained in:
Pierre HUBERT 2021-02-13 14:37:15 +01:00
parent 510f46910f
commit 985abc3e99
18 changed files with 217 additions and 154 deletions

View File

@ -39,9 +39,9 @@ CREATE TABLE `commentaires` (
PRIMARY KEY (`ID`) PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1; ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
DROP TABLE IF EXISTS `comunic_client`; DROP TABLE IF EXISTS `comunic_clients`;
CREATE TABLE `comunic_client` ( CREATE TABLE `comunic_clients` (
`ID` INT NOT NULL AUTO_INCREMENT, `id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(45) NOT NULL, `name` VARCHAR(45) NOT NULL,
`domain` VARCHAR(45) NULL COMMENT 'Use to check Referer & define Access-Control-Allow-Origin', `domain` VARCHAR(45) NULL COMMENT 'Use to check Referer & define Access-Control-Allow-Origin',
`comment` VARCHAR(45) NULL COMMENT 'Information about the client', `comment` VARCHAR(45) NULL COMMENT 'Information about the client',
@ -49,10 +49,11 @@ CREATE TABLE `comunic_client` (
PRIMARY KEY (`ID`)); PRIMARY KEY (`ID`));
DROP TABLE IF EXISTS `comunic_user_access_token`; DROP TABLE IF EXISTS `comunic_user_access_tokens`;
CREATE TABLE `comunic_user_access_token` ( CREATE TABLE `comunic_user_access_tokens` (
`id` INT NOT NULL AUTO_INCREMENT, `id` INT NOT NULL AUTO_INCREMENT,
`client_id` INT NOT NULL, `client_id` INT NOT NULL,
`user_id` INT NOT NULL,
`token` VARCHAR(255) NOT NULL, `token` VARCHAR(255) NOT NULL,
`last_refresh` INT NOT NULL, `last_refresh` INT NOT NULL,
`timeout` INT NOT NULL, `timeout` INT NOT NULL,

View File

@ -3,4 +3,21 @@ DROP TABLE IF EXISTS `comunic_api_limit_count`;
DROP TABLE IF EXISTS `comunic_api_users_tokens`; DROP TABLE IF EXISTS `comunic_api_users_tokens`;
DROP TABLE IF EXISTS `comunic_api_services_tokens`; DROP TABLE IF EXISTS `comunic_api_services_tokens`;
DROP TABLE IF EXISTS `comunic_calls_members`; DROP TABLE IF EXISTS `comunic_calls_members`;
DROP TABLE IF EXISTS `comunic_calls`; DROP TABLE IF EXISTS `comunic_calls`;
CREATE TABLE `comunic_clients` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(45) NOT NULL,
`domain` VARCHAR(45) NULL COMMENT 'Use to check Referer & define Access-Control-Allow-Origin',
`comment` VARCHAR(45) NULL COMMENT 'Information about the client',
`default_expiration_time` INT GENERATED ALWAYS AS (2592000) COMMENT '2592000 = 1 month',
PRIMARY KEY (`ID`));
CREATE TABLE `comunic_user_access_tokens` (
`id` INT NOT NULL AUTO_INCREMENT,
`client_id` INT NOT NULL,
`user_id` INT NOT NULL,
`token` VARCHAR(255) NOT NULL,
`last_refresh` INT NOT NULL,
`timeout` INT NOT NULL,
PRIMARY KEY (`id`));

View File

@ -9,10 +9,10 @@ use std::time::Duration;
/// The name of the tables /// The name of the tables
pub mod database_tables_names { pub mod database_tables_names {
/// API services tokens table /// API services tokens table
pub const SERVICES_TABLES: &str = "comunic_api_services_tokens"; pub const CLIENTS_TABLE: &str = "comunic_clients";
/// User access tokens table /// User access tokens table
pub const USER_ACCESS_TOKENS_TABLE: &str = "comunic_api_users_tokens"; pub const USER_ACCESS_TOKENS_TABLE: &str = "comunic_user_access_tokens";
/// User table /// User table
pub const USERS_TABLE: &str = "utilisateurs"; pub const USERS_TABLE: &str = "utilisateurs";
@ -108,4 +108,7 @@ pub const PASSWORD_MIN_LENGTH: usize = 3;
pub const SUPPORTED_LANGUAGES: &'static [&'static str] = &["en", "fr"]; pub const SUPPORTED_LANGUAGES: &'static [&'static str] = &["en", "fr"];
/// Interval at which last active time of user should be recorded /// Interval at which last active time of user should be recorded
pub const USER_LAST_ACTIVITY_REFRESH: Duration = Duration::from_secs(60); pub const USER_LAST_ACTIVITY_REFRESH: Duration = Duration::from_secs(60);
/// Interval at which last activity of an access token should be recorded
pub const USER_ACCESS_TOKEN_ACTIVITY_REFRESH: Duration = Duration::from_secs(60*60);

View File

@ -86,10 +86,9 @@ pub fn login_user(request: &mut HttpRequestHandler) -> RequestResult {
/// Sign out user /// Sign out user
pub fn logout_user(request: &mut HttpRequestHandler) -> RequestResult { pub fn logout_user(request: &mut HttpRequestHandler) -> RequestResult {
account_helper::destroy_login_tokens( if let Some(token) = request.user_access_token() {
&request.user_id()?, account_helper::destroy_login_tokens(token)?;
request.api_client(), }
)?;
request.success("User disconnected.") request.success("User disconnected.")
} }

View File

@ -50,7 +50,7 @@ impl UserWsRequestHandler {
let mut found = false; let mut found = false;
user_ws_controller::foreach_connection(|p| { user_ws_controller::foreach_connection(|p| {
if !found && p.user_id == peer_id && p.is_having_call_with_conversation(call_id) { if !found && p.user_id() == peer_id && p.is_having_call_with_conversation(call_id) {
found = true; found = true;
} }
@ -148,7 +148,7 @@ pub fn join_call(r: &mut UserWsRequestHandler) -> RequestResult {
// Remove any other active connection to current call of current user // Remove any other active connection to current call of current user
user_ws_controller::foreach_connection(|conn| { user_ws_controller::foreach_connection(|conn| {
if &conn.user_id != r.user_id_ref()? || conn.session.eq(&r.get_conn().session) { if conn.user_id() != r.user_id_ref()? || conn.session.eq(&r.get_conn().session) {
return Ok(()); return Ok(());
} }
@ -197,7 +197,7 @@ pub fn get_members_list(r: &mut UserWsRequestHandler) -> RequestResult {
user_ws_controller::foreach_connection(|conn| { user_ws_controller::foreach_connection(|conn| {
if conn.is_having_call_with_conversation(&conv_id) { if conn.is_having_call_with_conversation(&conv_id) {
list.push(CallMemberInfo::new(&conn.user_id, &conn.active_call.as_ref().unwrap())); list.push(CallMemberInfo::new(conn.user_id(), &conn.active_call.as_ref().unwrap()));
} }
Ok(()) Ok(())
@ -297,7 +297,7 @@ pub fn mark_user_ready(r: &mut UserWsRequestHandler) -> Res {
r.update_call(|call| call.ready = true)?; r.update_call(|call| call.ready = true)?;
user_ws_controller::send_message_to_specific_connections( user_ws_controller::send_message_to_specific_connections(
|c| c.user_id != &user_id && c.is_having_call_with_conversation(&call_id), |c| c.user_id() != &user_id && c.is_having_call_with_conversation(&call_id),
|_| UserWsMessage::no_id_message("call_peer_ready", CallPeerReadyAPI::new(&call_id, r.user_id_ref()?)), |_| UserWsMessage::no_id_message("call_peer_ready", CallPeerReadyAPI::new(&call_id, r.user_id_ref()?)),
None::<fn(&_) -> _>, None::<fn(&_) -> _>,
)?; )?;
@ -335,7 +335,7 @@ pub fn stop_streaming(r: &mut UserWsRequestHandler) -> Res {
// Notify all other users // Notify all other users
user_ws_controller::send_message_to_specific_connections( user_ws_controller::send_message_to_specific_connections(
|c| c.is_having_call_with_conversation(&call_id) && c.user_id != &user_id, |c| c.is_having_call_with_conversation(&call_id) && c.user_id() != &user_id,
|_| UserWsMessage::no_id_message("call_peer_interrupted_streaming", CallPeerInterruptedStreamingAPI::new(&call_id, &user_id)), |_| UserWsMessage::no_id_message("call_peer_interrupted_streaming", CallPeerInterruptedStreamingAPI::new(&call_id, &user_id)),
None::<fn(&_) -> _>, None::<fn(&_) -> _>,
)?; )?;
@ -362,17 +362,17 @@ pub fn make_user_leave_call(conv_id: &ConvID, connection: &UserWsConnection) ->
// Close main stream (sender) // Close main stream (sender)
events_helper::propagate_event(&Event::CloseCallStream(&CloseCallStream { events_helper::propagate_event(&Event::CloseCallStream(&CloseCallStream {
call_hash: gen_call_hash(&conv_id, &connection.user_id), call_hash: gen_call_hash(&conv_id, connection.user_id()),
peer_id: None, peer_id: None,
}))?; }))?;
// Close receiver streams (other users streams) // Close receiver streams (other users streams)
user_ws_controller::foreach_connection( user_ws_controller::foreach_connection(
|peer_conn| { |peer_conn| {
if peer_conn.is_having_call_with_conversation(conv_id) && &peer_conn.user_id != &connection.user_id { if peer_conn.is_having_call_with_conversation(conv_id) && peer_conn.user_id() != connection.user_id() {
events_helper::propagate_event(&Event::CloseCallStream(&CloseCallStream { events_helper::propagate_event(&Event::CloseCallStream(&CloseCallStream {
call_hash: gen_call_hash(&conv_id, &peer_conn.user_id), call_hash: gen_call_hash(&conv_id, peer_conn.user_id()),
peer_id: Some(connection.user_id.clone()), peer_id: Some(connection.user_id().clone()),
}))?; }))?;
} }
@ -381,7 +381,7 @@ pub fn make_user_leave_call(conv_id: &ConvID, connection: &UserWsConnection) ->
)?; )?;
// Create a notification // Create a notification
events_helper::propagate_event(&Event::UserLeftCall(conv_id, &connection.user_id))?; events_helper::propagate_event(&Event::UserLeftCall(conv_id, connection.user_id()))?;
Ok(()) Ok(())
} }
@ -391,7 +391,7 @@ pub fn handle_event(e: &events_helper::Event) -> Res {
match e { match e {
Event::UserJoinedCall(conv_id, user_id) => { Event::UserJoinedCall(conv_id, user_id) => {
user_ws_controller::send_message_to_specific_connections( user_ws_controller::send_message_to_specific_connections(
|c| c.is_having_call_with_conversation(conv_id) && &c.user_id != user_id, |c| c.is_having_call_with_conversation(conv_id) && c.user_id() != user_id,
|_| UserWsMessage::no_id_message("user_joined_call", JoinedCallMessage::new(conv_id, user_id)), |_| UserWsMessage::no_id_message("user_joined_call", JoinedCallMessage::new(conv_id, user_id)),
None::<fn(&_) -> _>, None::<fn(&_) -> _>,
)?; )?;
@ -429,7 +429,7 @@ pub fn handle_event(e: &events_helper::Event) -> Res {
user_ws_controller::send_message_to_specific_connections( user_ws_controller::send_message_to_specific_connections(
|c| c.user_id == target_user && c.is_having_call_with_conversation(&call_id), |c| c.user_id() == target_user && c.is_having_call_with_conversation(&call_id),
|_| UserWsMessage::no_id_message("new_call_signal", NewCallSignalAPI::new(&call_id, &peer_id, &msg.data)?), |_| UserWsMessage::no_id_message("new_call_signal", NewCallSignalAPI::new(&call_id, &peer_id, &msg.data)?),
None::<fn(&_) -> _>, None::<fn(&_) -> _>,
)?; )?;

View File

@ -94,7 +94,7 @@ pub fn handle_event(e: &events_helper::Event) -> Res {
|c| c.posts.contains(&comment.post_id), |c| c.posts.contains(&comment.post_id),
|c| UserWsMessage::no_id_message( |c| UserWsMessage::no_id_message(
"new_comment", "new_comment",
CommentAPI::new(comment, &Some(c.user_id.clone()))?, CommentAPI::new(comment, &Some(c.user_id().clone()))?,
), ),
None::<fn(&_) -> _>, None::<fn(&_) -> _>,
)?; )?;
@ -105,7 +105,7 @@ pub fn handle_event(e: &events_helper::Event) -> Res {
|c| c.posts.contains(&comment.post_id), |c| c.posts.contains(&comment.post_id),
|c| UserWsMessage::no_id_message( |c| UserWsMessage::no_id_message(
"comment_updated", "comment_updated",
CommentAPI::new(comment, &Some(c.user_id.clone()))?, CommentAPI::new(comment, &Some(c.user_id().clone()))?,
), ),
None::<fn(&_) -> _>, None::<fn(&_) -> _>,
)?; )?;

View File

@ -362,7 +362,7 @@ pub fn handle_event(e: &events_helper::Event) -> Res {
user_ws_controller::send_message_to_specific_connections( user_ws_controller::send_message_to_specific_connections(
|f| f.conversations.contains(&msg.conv_id), |f| f.conversations.contains(&msg.conv_id),
|_| UserWsMessage::no_id_message("new_conv_message", ConversationMessageAPI::new(msg)), |_| UserWsMessage::no_id_message("new_conv_message", ConversationMessageAPI::new(msg)),
Some(|conn: &UserWsConnection| conversations_helper::mark_user_seen(msg.conv_id, &conn.user_id)), Some(|conn: &UserWsConnection| conversations_helper::mark_user_seen(msg.conv_id, conn.user_id())),
)?; )?;
} }

View File

@ -15,12 +15,12 @@ use crate::api_data::res_get_ws_token::ResGetWsToken;
use crate::constants::{USER_LAST_ACTIVITY_REFRESH, WS_ACCESS_TOKEN_LENGTH}; use crate::constants::{USER_LAST_ACTIVITY_REFRESH, WS_ACCESS_TOKEN_LENGTH};
use crate::controllers::user_ws_controller::ws_connections_list::{add_connection, find_connection, get_ws_connections_list, remove_connection}; use crate::controllers::user_ws_controller::ws_connections_list::{add_connection, find_connection, get_ws_connections_list, remove_connection};
use crate::controllers::user_ws_routes::find_user_ws_route; use crate::controllers::user_ws_routes::find_user_ws_route;
use crate::data::api_client::APIClient;
use crate::data::base_request_handler::BaseRequestHandler; use crate::data::base_request_handler::BaseRequestHandler;
use crate::data::config::conf; use crate::data::config::conf;
use crate::data::error::{ExecError, Res, ResultBoxError}; use crate::data::error::{ExecError, Res, ResultBoxError};
use crate::data::http_request_handler::HttpRequestHandler; use crate::data::http_request_handler::HttpRequestHandler;
use crate::data::user::UserID; use crate::data::user::UserID;
use crate::data::user_token::UserAccessToken;
use crate::data::user_ws_connection::UserWsConnection; use crate::data::user_ws_connection::UserWsConnection;
use crate::data::user_ws_message::UserWsMessage; use crate::data::user_ws_message::UserWsMessage;
use crate::data::user_ws_request_handler::{UserWsRequestHandler, UserWsResponseType}; use crate::data::user_ws_request_handler::{UserWsRequestHandler, UserWsResponseType};
@ -41,15 +41,14 @@ mod ws_tokens_list {
use std::sync::Mutex; use std::sync::Mutex;
use crate::constants::WS_ACCESS_TOKEN_LIFETIME; use crate::constants::WS_ACCESS_TOKEN_LIFETIME;
use crate::data::user::UserID; use crate::data::user_token::UserAccessToken;
use crate::utils::date_utils::time; use crate::utils::date_utils::time;
#[derive(Debug)] #[derive(Debug)]
pub struct WsToken { pub struct WsToken {
pub time: u64, pub time: u64,
pub client_id: u32, pub user_token: UserAccessToken,
pub user_id: UserID, pub ws_token: String,
pub token: String,
pub incognito: bool, pub incognito: bool,
pub remote_ip: String, pub remote_ip: String,
} }
@ -89,7 +88,7 @@ mod ws_tokens_list {
let list = get_list(); let list = get_list();
let mut list = list.lock().unwrap(); let mut list = list.lock().unwrap();
for i in 0..list.len() { for i in 0..list.len() {
if list[i].token == t { if list[i].ws_token == t {
return Some(list.remove(i)); return Some(list.remove(i));
} }
} }
@ -178,10 +177,9 @@ pub fn get_token(r: &mut HttpRequestHandler) -> ResultBoxError {
let access_token = rand_str(WS_ACCESS_TOKEN_LENGTH); let access_token = rand_str(WS_ACCESS_TOKEN_LENGTH);
let token = ws_tokens_list::WsToken { let token = ws_tokens_list::WsToken {
user_id: r.user_id()?, user_token: r.some_or_internal_error(r.user_access_token().cloned(), "No access token!")?,
client_id: r.api_client().id,
time: time(), time: time(),
token: access_token.to_string(), ws_token: access_token.to_string(),
incognito: r.post_bool_opt("incognito", false), incognito: r.post_bool_opt("incognito", false),
remote_ip: r.remote_ip(), remote_ip: r.remote_ip(),
}; };
@ -195,10 +193,8 @@ pub fn get_token(r: &mut HttpRequestHandler) -> ResultBoxError {
pub struct WsSession { pub struct WsSession {
// NOTE : apart from hb, the values here won't change ! // NOTE : apart from hb, the values here won't change !
user_id: UserID, // Information about user connection
user_token: UserAccessToken,
// Client used for the connection
client_id: u32,
// Remote IP address // Remote IP address
remote_ip: String, remote_ip: String,
@ -232,16 +228,16 @@ impl WsSession {
}); });
} }
/// helper method that update user last activity at every specified amount of time /// Helper method that update user last activity at every specified amount of time
fn user_activity(&self, ctx: &mut actix_web_actors::ws::WebsocketContext<Self>) { fn user_activity(&self, ctx: &mut actix_web_actors::ws::WebsocketContext<Self>) {
if !self.incognito && account_helper::update_last_activity(&self.user_id).is_err() { if !self.incognito && account_helper::update_last_activity(&self.user_token.user_id).is_err() {
eprintln!("Failed to do initial refresh of last activity for user {} !", self.user_id.id()); eprintln!("Failed to do initial refresh of last activity for user {} !", self.user_token.user_id.id());
} }
ctx.run_interval(USER_LAST_ACTIVITY_REFRESH, |_, ctx| { ctx.run_interval(USER_LAST_ACTIVITY_REFRESH, |_, ctx| {
if let Some(conn) = find_connection(ctx.address()) { if let Some(conn) = find_connection(ctx.address()) {
if !conn.incognito && account_helper::update_last_activity(&conn.user_id).is_err() { if !conn.incognito && account_helper::update_last_activity(conn.user_id()).is_err() {
eprintln!("Failed to refresh last activity for user {} !", conn.user_id.id()); eprintln!("Failed to refresh last activity for user {} !", conn.user_id().id());
} }
} }
}); });
@ -312,8 +308,7 @@ impl Actor for WsSession {
self.user_activity(ctx); self.user_activity(ctx);
add_connection(UserWsConnection { add_connection(UserWsConnection {
user_id: self.user_id.clone(), user_token: self.user_token.clone(),
client_id: self.client_id,
remote_ip: self.remote_ip.clone(), remote_ip: self.remote_ip.clone(),
session: ctx.address(), session: ctx.address(),
incognito: self.incognito, incognito: self.incognito,
@ -445,10 +440,9 @@ pub async fn ws_route(
actix_web_actors::ws::start( actix_web_actors::ws::start(
WsSession { WsSession {
remote_ip: token.remote_ip, remote_ip: token.remote_ip,
user_id: token.user_id, user_token: token.user_token,
hb: std::time::Instant::now(), hb: std::time::Instant::now(),
incognito: token.incognito, incognito: token.incognito,
client_id: token.client_id,
}, },
&req, &req,
stream, stream,
@ -467,7 +461,7 @@ pub fn send_message_to_users(msg: &UserWsMessage, users: &Vec<UserID>) -> Res {
.lock() .lock()
.unwrap() .unwrap()
.iter() .iter()
.filter(|f| users.contains(&f.user_id)) .filter(|f| users.contains(f.user_id()))
.map(|f| f.session.clone()) .map(|f| f.session.clone())
.collect::<Vec<Addr<WsSession>>>(); .collect::<Vec<Addr<WsSession>>>();
@ -484,7 +478,7 @@ pub fn send_message_to_user(msg: &UserWsMessage, user: &UserID) -> Res {
.lock() .lock()
.unwrap() .unwrap()
.iter() .iter()
.filter(|f| user == &f.user_id) .filter(|f| user == f.user_id())
.map(|f| f.session.clone()) .map(|f| f.session.clone())
.collect::<Vec<Addr<WsSession>>>(); .collect::<Vec<Addr<WsSession>>>();
@ -527,22 +521,22 @@ pub fn send_message_to_specific_connections<F, M, A>(filter: F, msg_generator: M
/// Check out whether user is connected or not /// Check out whether user is connected or not
pub fn is_user_connected(user_id: &UserID) -> bool { pub fn is_user_connected(user_id: &UserID) -> bool {
get_ws_connections_list().lock().unwrap().iter().any(|c| &c.user_id == user_id) get_ws_connections_list().lock().unwrap().iter().any(|c| c.user_id() == user_id)
} }
/// Check out whether user is connected or not and has at list one not incognito connection /// Check out whether user is connected or not and has at list one not incognito connection
pub fn is_user_connected_not_incognito(user_id: &UserID) -> bool { pub fn is_user_connected_not_incognito(user_id: &UserID) -> bool {
get_ws_connections_list().lock().unwrap().iter().any(|c| &c.user_id == user_id && !c.incognito) get_ws_connections_list().lock().unwrap().iter().any(|c| c.user_id() == user_id && !c.incognito)
} }
/// Disconnect a user from all the WebSockets of a given client /// Disconnect a user from all the WebSockets of a given access token
pub fn disconnect_user_from_client(user_id: &UserID, client: &APIClient) -> Res { pub fn disconnect_from_user_token(token: &UserAccessToken) -> Res {
let connections = get_ws_connections_list() let connections = get_ws_connections_list()
.lock() .lock()
.unwrap() .unwrap()
.iter() .iter()
.filter(|f| &f.user_id == user_id && f.client_id == client.id) .filter(|f| &f.user_token == token)
.map(|f| f.session.clone()) .map(|f| f.session.clone())
.collect::<Vec<Addr<WsSession>>>(); .collect::<Vec<Addr<WsSession>>>();
@ -559,7 +553,7 @@ pub fn disconnect_user_from_all_sockets(user_id: &UserID) -> Res {
.lock() .lock()
.unwrap() .unwrap()
.iter() .iter()
.filter(|f| &f.user_id == user_id) .filter(|f| f.user_id() == user_id)
.map(|f| f.session.clone()) .map(|f| f.session.clone())
.collect::<Vec<Addr<WsSession>>>(); .collect::<Vec<Addr<WsSession>>>();
@ -585,8 +579,8 @@ pub fn foreach_connection<F>(mut f: F) -> Res
/// Events handler /// Events handler
pub fn handle_event(e: &events_helper::Event) -> Res { pub fn handle_event(e: &events_helper::Event) -> Res {
match e { match e {
Event::DestroyedLoginToken(user_id, client) => { Event::DestroyedLoginToken(token) => {
disconnect_user_from_client(user_id, client)?; disconnect_from_user_token(token)?;
} }
_ => {} _ => {}
} }

View File

@ -1,11 +1,11 @@
/// API client information /// API client information
/// ///
/// @author Pierre HUBERT /// @author Pierre HUBERT
#[derive(Debug)] #[derive(Debug)]
pub struct APIClient { pub struct APIClient {
pub id: u32, pub id: u64,
pub name: String, pub name: String,
pub token: String,
pub domain: Option<String>, pub domain: Option<String>,
pub comment: Option<String>,
pub default_expiration_time: u64,
} }

View File

@ -15,11 +15,12 @@ use crate::controllers::routes::RequestResult;
use crate::data::comment::Comment; use crate::data::comment::Comment;
use crate::data::conversation::ConvID; use crate::data::conversation::ConvID;
use crate::data::custom_emoji::CustomEmoji; use crate::data::custom_emoji::CustomEmoji;
use crate::data::error::{ExecError, ResultBoxError}; use crate::data::error::{ExecError, Res, ResultBoxError};
use crate::data::group::GroupAccessLevel; use crate::data::group::GroupAccessLevel;
use crate::data::group_id::GroupID; use crate::data::group_id::GroupID;
use crate::data::post::{Post, PostAccessLevel}; use crate::data::post::{Post, PostAccessLevel};
use crate::data::user::UserID; use crate::data::user::UserID;
use crate::data::user_token::UserAccessToken;
use crate::helpers::{account_helper, comments_helper, conversations_helper, custom_emojies_helper, friends_helper, groups_helper, posts_helper, user_helper, virtual_directory_helper}; use crate::helpers::{account_helper, comments_helper, conversations_helper, custom_emojies_helper, friends_helper, groups_helper, posts_helper, user_helper, virtual_directory_helper};
use crate::helpers::virtual_directory_helper::VirtualDirType; use crate::helpers::virtual_directory_helper::VirtualDirType;
use crate::utils::pdf_utils::is_valid_pdf; use crate::utils::pdf_utils::is_valid_pdf;
@ -58,8 +59,13 @@ pub trait BaseRequestHandler {
/// Get remote IP address /// Get remote IP address
fn remote_ip(&self) -> String; fn remote_ip(&self) -> String;
/// Current user access token
fn user_access_token(&self) -> Option<&UserAccessToken>;
/// Current user ID /// Current user ID
fn user_id_opt_ref(&self) -> Option<&UserID>; fn user_id_opt_ref(&self) -> Option<&UserID> {
self.user_access_token().map(|u| &u.user_id)
}
/// Success message /// Success message
@ -144,6 +150,17 @@ pub trait BaseRequestHandler {
} }
} }
/// Unwrap an option, returning an error if none is returned
fn some_or_internal_error<E>(&mut self, opt: Option<E>, msg: &str) -> Res<E> {
match opt {
None => {
self.internal_error(ExecError::boxed_new(msg))?;
unreachable!()
}
Some(e) => Ok(e)
}
}
/// Get a user ID, if available /// Get a user ID, if available
fn user_id_opt(&self) -> Option<UserID> { fn user_id_opt(&self) -> Option<UserID> {
@ -189,6 +206,18 @@ pub trait BaseRequestHandler {
self.post_string_opt(name, 1, true) self.post_string_opt(name, 1, true)
} }
/// Get a post string with a given name. If the value is not found, attempt to get the value
/// with another name
///
/// This function is useful to upgrade system
fn post_string_with_fallback(&mut self, name: &str, fallback: &str) -> Res<String> {
if self.has_post_parameter(name) {
self.post_string(name)
} else {
self.post_string(fallback)
}
}
/// Get a post string, specifying minimum length /// Get a post string, specifying minimum length
fn post_string_opt(&mut self, name: &str, min_length: usize, required: bool) fn post_string_opt(&mut self, name: &str, min_length: usize, required: bool)
-> ResultBoxError<String> { -> ResultBoxError<String> {

View File

@ -11,8 +11,8 @@ use crate::controllers::routes::RequestResult;
use crate::data::api_client::APIClient; use crate::data::api_client::APIClient;
use crate::data::base_request_handler::{BaseRequestHandler, RequestValue}; use crate::data::base_request_handler::{BaseRequestHandler, RequestValue};
use crate::data::config::conf; use crate::data::config::conf;
use crate::data::error::ResultBoxError; use crate::data::error::{Res, ResultBoxError};
use crate::data::user::UserID; use crate::data::user_token::UserAccessToken;
use crate::helpers::{account_helper, api_helper}; use crate::helpers::{account_helper, api_helper};
/// Http request handler /// Http request handler
@ -25,7 +25,7 @@ pub struct HttpRequestHandler {
response: Option<web::HttpResponse>, response: Option<web::HttpResponse>,
headers: HashMap<String, String>, headers: HashMap<String, String>,
client: Option<APIClient>, client: Option<APIClient>,
curr_user_id: Option<UserID>, curr_user_token: Option<UserAccessToken>,
} }
impl HttpRequestHandler { impl HttpRequestHandler {
@ -37,7 +37,7 @@ impl HttpRequestHandler {
response: None, response: None,
headers: HashMap::new(), headers: HashMap::new(),
client: None, client: None,
curr_user_id: None, curr_user_token: None,
} }
} }
@ -78,11 +78,10 @@ impl HttpRequestHandler {
/// Check API client tokens /// Check API client tokens
pub fn check_client_token(&mut self) -> RequestResult { pub fn check_client_token(&mut self) -> RequestResult {
let api_name = self.post_string("serviceName")?; let client_name = self.post_string_with_fallback("client", "serviceName")?;
let api_token = self.post_string("serviceToken")?;
let client = self.ok_or_bad_request( let client = self.ok_or_bad_request(
api_helper::get_client(&api_name, &api_token), api_helper::get_client(&client_name),
"Client not recognized!", "Client not recognized!",
)?; )?;
@ -111,13 +110,17 @@ impl HttpRequestHandler {
} }
/// Check login token /// Check login token
pub fn check_user_token(&mut self) -> RequestResult { pub fn check_user_token(&mut self) -> Res {
let token = self.post_string("userToken1")?; let token = self.post_string("userToken1")?;
// Find user // Find user
match account_helper::get_user_by_login_token(&token, self.api_client()) { match account_helper::find_user_by_login_token(&token, self.api_client()) {
Ok(id) => { Ok(token) => {
self.curr_user_id = Some(id); if token.need_refresh() {
account_helper::refresh_access_token(&token)?;
}
self.curr_user_token = Some(token);
Ok(()) Ok(())
} }
@ -177,7 +180,7 @@ impl BaseRequestHandler for HttpRequestHandler {
ip ip
} }
fn user_id_opt_ref(&self) -> Option<&UserID> { fn user_access_token(&self) -> Option<&UserAccessToken> {
self.curr_user_id.as_ref() self.curr_user_token.as_ref()
} }
} }

View File

@ -50,6 +50,12 @@ impl PartialEq<&UserID> for UserID {
} }
} }
impl PartialEq<UserID> for &UserID {
fn eq(&self, other: &UserID) -> bool {
self.0 == other.0
}
}
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum UserPageStatus { pub enum UserPageStatus {
OPEN, OPEN,

View File

@ -1,11 +1,29 @@
use crate::constants::USER_ACCESS_TOKEN_ACTIVITY_REFRESH;
use crate::data::user::UserID; use crate::data::user::UserID;
use crate::utils::date_utils::time;
/// User access token information /// User access token information
/// ///
/// Author : Pierre Hubert /// Author : Pierre Hubert
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct UserAccessToken { pub struct UserAccessToken {
pub id: u64,
pub client_id: u64,
pub user_id: UserID, pub user_id: UserID,
pub client_id: u32, pub token: String,
pub token: String pub last_refresh: u64,
pub timeout: u64,
}
impl UserAccessToken {
/// Check out whether access token should be refreshed
pub fn need_refresh(&self) -> bool {
self.last_refresh + USER_ACCESS_TOKEN_ACTIVITY_REFRESH.as_secs() < time()
}
}
impl PartialEq<UserAccessToken> for UserAccessToken {
fn eq(&self, other: &UserAccessToken) -> bool {
self.id == other.id
}
} }

View File

@ -4,6 +4,7 @@ use crate::controllers::user_ws_controller::WsSession;
use crate::data::conversation::ConvID; use crate::data::conversation::ConvID;
use crate::data::post::PostID; use crate::data::post::PostID;
use crate::data::user::UserID; use crate::data::user::UserID;
use crate::data::user_token::UserAccessToken;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ActiveCall { pub struct ActiveCall {
@ -14,8 +15,7 @@ pub struct ActiveCall {
/// This structure contains information about an active connection /// This structure contains information about an active connection
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct UserWsConnection { pub struct UserWsConnection {
pub user_id: UserID, pub user_token: UserAccessToken,
pub client_id: u32,
pub remote_ip: String, pub remote_ip: String,
pub session: actix::Addr<WsSession>, pub session: actix::Addr<WsSession>,
pub incognito: bool, pub incognito: bool,
@ -25,6 +25,11 @@ pub struct UserWsConnection {
} }
impl UserWsConnection { impl UserWsConnection {
pub fn user_id(&self) -> &UserID {
&self.user_token.user_id
}
/// Check out whether a connection is being used to make a call in a specific conversation or not /// Check out whether a connection is being used to make a call in a specific conversation or not
pub fn is_having_call_with_conversation(&self, conv_id: &ConvID) -> bool { pub fn is_having_call_with_conversation(&self, conv_id: &ConvID) -> bool {
if let Some(call_info) = &self.active_call { if let Some(call_info) = &self.active_call {

View File

@ -8,7 +8,7 @@ use crate::api_data::http_error::HttpError;
use crate::controllers::routes::RequestResult; use crate::controllers::routes::RequestResult;
use crate::data::base_request_handler::{BaseRequestHandler, RequestValue}; use crate::data::base_request_handler::{BaseRequestHandler, RequestValue};
use crate::data::error::ResultBoxError; use crate::data::error::ResultBoxError;
use crate::data::user::UserID; use crate::data::user_token::UserAccessToken;
use crate::data::user_ws_connection::UserWsConnection; use crate::data::user_ws_connection::UserWsConnection;
pub enum UserWsResponseType { pub enum UserWsResponseType {
@ -88,11 +88,7 @@ impl BaseRequestHandler for UserWsRequestHandler {
self.connection.remote_ip.to_string() self.connection.remote_ip.to_string()
} }
fn user_id_opt_ref(&self) -> Option<&UserID> { fn user_access_token(&self) -> Option<&UserAccessToken> {
Some(&self.connection.user_id) Some(&self.connection.user_token)
}
fn user_id(&self) -> ResultBoxError<UserID> {
Ok(self.connection.user_id.clone())
} }
} }

View File

@ -13,7 +13,7 @@ use crate::data::security_settings::SecuritySettings;
use crate::data::user::{AccountImageVisibility, User, UserID, UserPageStatus}; use crate::data::user::{AccountImageVisibility, User, UserID, UserPageStatus};
use crate::data::user_token::UserAccessToken; use crate::data::user_token::UserAccessToken;
use crate::helpers::{comments_helper, conversations_helper, custom_emojies_helper, database, events_helper, friends_helper, groups_helper, likes_helper, notifications_helper, posts_helper, survey_helper, user_helper}; use crate::helpers::{comments_helper, conversations_helper, custom_emojies_helper, database, events_helper, friends_helper, groups_helper, likes_helper, notifications_helper, posts_helper, survey_helper, user_helper};
use crate::helpers::database::{DeleteQuery, InsertQuery, QueryInfo}; use crate::helpers::database::{DeleteQuery, InsertQuery, QueryInfo, UpdateInfo};
use crate::helpers::events_helper::Event; use crate::helpers::events_helper::Event;
use crate::helpers::likes_helper::LikeType; use crate::helpers::likes_helper::LikeType;
use crate::utils::crypt_utils::{legacy_crypt_pass, rand_str}; use crate::utils::crypt_utils::{legacy_crypt_pass, rand_str};
@ -47,56 +47,45 @@ pub fn login_user(email: &str, password: &str, client: &APIClient) -> ResultBoxE
return Err(ExecError::boxed_new("The user gave an invalid password!")); return Err(ExecError::boxed_new("The user gave an invalid password!"));
} }
// Check if we already have a login token for this user
if let Ok(token) = get_client_tokens(&user.id, client) {
return Ok(token.token);
}
// Create new login tokens // Create new login tokens
let new_token = UserAccessToken { let new_token = UserAccessToken {
id: 0,
user_id: user.id.clone(), user_id: user.id.clone(),
client_id: client.id, client_id: client.id,
token: rand_str(150), token: rand_str(150),
last_refresh: time(),
timeout: client.default_expiration_time,
}; };
// Save it // Save it
database::insert( InsertQuery::new(USER_ACCESS_TOKENS_TABLE)
InsertQuery::new(USER_ACCESS_TOKENS_TABLE) .add_u64("client_id", client.id)
.add_user_id("user_id", &new_token.user_id) .add_user_id("user_id", &new_token.user_id)
.add_u32("service_id", client.id) .add_str("token", &new_token.token)
.add_str("token1", &new_token.token) .add_u64("last_refresh", new_token.last_refresh)
.add_str("token2", "dummy_data") .add_u64("timeout", new_token.timeout)
)?; .insert_drop_result()?;
Ok(new_token.token) Ok(new_token.token)
} }
/// Get user login tokens
fn get_client_tokens(user_id: &UserID, client: &APIClient) -> ResultBoxError<UserAccessToken> {
database::query_row(
QueryInfo::new(USER_ACCESS_TOKENS_TABLE)
.cond_user_id("user_id", user_id)
.cond_u32("service_id", client.id),
|res| {
Ok(UserAccessToken {
user_id: res.get_user_id("user_id")?,
client_id: res.get_int64("service_id")? as u32,
token: res.get_str("token1")?,
})
},
)
}
/// Find a user ID based on login token /// Find a user ID based on login token
pub fn get_user_by_login_token(token: &str, client: &APIClient) -> ResultBoxError<UserID> { pub fn find_user_by_login_token(token: &str, client: &APIClient) -> ResultBoxError<UserAccessToken> {
database::query_row( QueryInfo::new(USER_ACCESS_TOKENS_TABLE)
QueryInfo::new(USER_ACCESS_TOKENS_TABLE) .cond_u64("client_id", client.id)
.cond_u32("service_id", client.id) .cond("token", token)
.cond("token1", token) .set_custom_where("last_refresh + timeout > ?")
.add_field("user_id"), .add_custom_where_argument_u64(time())
|res| res.get_user_id("user_id"), .query_row(|res| {
) Ok(UserAccessToken {
id: res.get_u64("id")?,
client_id: res.get_u64("client_id")?,
user_id: res.get_user_id("user_id")?,
token: res.get_str("token")?,
last_refresh: res.get_u64("last_refresh")?,
timeout: res.get_u64("timeout")?,
})
})
} }
/// Check out whether an email address exists or not /// Check out whether an email address exists or not
@ -107,15 +96,22 @@ pub fn exists_mail(mail: &str) -> ResultBoxError<bool> {
.map(|r| r > 0) .map(|r| r > 0)
} }
/// Refresh a user access token
pub fn refresh_access_token(token: &UserAccessToken) -> Res {
UpdateInfo::new(USER_ACCESS_TOKENS_TABLE)
.cond_u64("id", token.id)
.set_u64("last_refresh", time())
.exec()
}
/// Destroy a given user login tokens /// Destroy a given user login tokens
pub fn destroy_login_tokens(id: &UserID, client: &APIClient) -> ResultBoxError<()> { pub fn destroy_login_tokens(access_tokens: &UserAccessToken) -> Res {
database::delete(DeleteQuery::new(USER_ACCESS_TOKENS_TABLE) DeleteQuery::new(USER_ACCESS_TOKENS_TABLE)
.cond_u32("service_id", client.id) .cond_u64("id", access_tokens.id)
.cond_user_id("user_id", id) .exec()?;
)?;
// Send an event (destroyed_login_tokens) // Send an event (destroyed_login_tokens)
events_helper::propagate_event(&Event::DestroyedLoginToken(id.clone(), client))?; events_helper::propagate_event(&Event::DestroyedLoginToken(access_tokens))?;
Ok(()) Ok(())
} }

View File

@ -1,27 +1,23 @@
use crate::constants::database_tables_names::CLIENTS_TABLE;
use crate::data::api_client::APIClient; use crate::data::api_client::APIClient;
use crate::helpers::database;
use crate::helpers::database::QueryInfo;
use crate::constants::database_tables_names::SERVICES_TABLES;
use crate::data::error::ResultBoxError; use crate::data::error::ResultBoxError;
use crate::helpers::database::QueryInfo;
/// API helper /// API helper
/// ///
/// @author Pierre Hubert /// @author Pierre Hubert
/// Get information about a client /// Get information about a client
pub fn get_client(name: &str, token: &str) -> ResultBoxError<APIClient> { pub fn get_client(name: &str) -> ResultBoxError<APIClient> {
database::query_row( QueryInfo::new(CLIENTS_TABLE)
QueryInfo::new(SERVICES_TABLES) .cond("name", name)
.cond("service_name", name) .query_row(|res| {
.cond("token", token), Ok(APIClient {
id: res.get_u64("id")?,
|res| { name: res.get_str("name")?,
Ok(APIClient { domain: res.get_optional_str("domain")?,
id: res.get_int64("id")? as u32, comment: res.get_optional_str("comment")?,
name: res.get_str("service_name")?, default_expiration_time: res.get_u64("default_expiration_time")?,
token: res.get_str("token")?, })
domain: res.get_optional_str("client_domain")?, })
})
}
)
} }

View File

@ -5,13 +5,13 @@
use crate::controllers::{calls_controller, comments_controller, conversations_controller, notifications_controller, rtc_relay_controller, user_ws_controller}; use crate::controllers::{calls_controller, comments_controller, conversations_controller, notifications_controller, rtc_relay_controller, user_ws_controller};
use crate::data::api_client::APIClient;
use crate::data::call_signal::{CloseCallStream, NewRtcRelayMessage, NewUserCallSignal, UserCallOfferRequest}; use crate::data::call_signal::{CloseCallStream, NewRtcRelayMessage, NewUserCallSignal, UserCallOfferRequest};
use crate::data::comment::Comment; use crate::data::comment::Comment;
use crate::data::conversation::ConvID; use crate::data::conversation::ConvID;
use crate::data::conversation_message::ConversationMessage; use crate::data::conversation_message::ConversationMessage;
use crate::data::error::Res; use crate::data::error::Res;
use crate::data::user::UserID; use crate::data::user::UserID;
use crate::data::user_token::UserAccessToken;
use crate::data::user_ws_connection::UserWsConnection; use crate::data::user_ws_connection::UserWsConnection;
pub enum Event<'a> { pub enum Event<'a> {
@ -21,7 +21,7 @@ pub enum Event<'a> {
UserWsClosed(&'a UserWsConnection), UserWsClosed(&'a UserWsConnection),
/// Destroyed a login token /// Destroyed a login token
DestroyedLoginToken(UserID, &'a APIClient), DestroyedLoginToken(&'a UserAccessToken),
/// Updated the number of notifications of one of multiple users user /// Updated the number of notifications of one of multiple users user
UpdatedNotificationsNumber(&'a Vec<UserID>), UpdatedNotificationsNumber(&'a Vec<UserID>),