diff --git a/auth/migrations/20240725215330_create_everything.sql b/auth/migrations/20240725215330_create_everything.sql index 17a3c1a..0164f6b 100644 --- a/auth/migrations/20240725215330_create_everything.sql +++ b/auth/migrations/20240725215330_create_everything.sql @@ -5,6 +5,7 @@ CREATE TABLE IF NOT EXISTS user ( email VARCHAR(255) NOT NULL, new_email VARCHAR(255), avatar TEXT NOT NULL, + servers TEXT NOT NULL, login_code INT(6), tokens TEXT, google_oauth VARCHAR(255), diff --git a/auth/src/server.rs b/auth/src/server.rs index ef72e23..c1e5717 100644 --- a/auth/src/server.rs +++ b/auth/src/server.rs @@ -9,7 +9,7 @@ use regex::Regex; use sha3::{Digest, Sha3_256}; use sha3::digest::Update; use sqlx::{Pool, query, Sqlite}; -use sqlx::sqlite::SqliteQueryResult; +use sqlx::sqlite::{SqliteQueryResult, SqliteRow}; use tarpc::context::Context; use tracing::*; use tracing::log::__private_api::log; @@ -19,506 +19,584 @@ use realm_shared::types::ErrorCode::*; #[derive(Clone)] pub struct RealmAuthServer { - pub socket: SocketAddr, - pub db_pool: Pool, - pub auth_email: AuthEmail, - pub template_html: String, - pub template_txt: String, - pub domain: String, + pub socket: SocketAddr, + pub db_pool: Pool, + pub auth_email: AuthEmail, + pub template_html: String, + pub template_txt: String, + pub domain: String, } impl RealmAuthServer { - pub fn new(socket: SocketAddr, db_pool: Pool, auth_email: AuthEmail) -> RealmAuthServer { - RealmAuthServer { - socket, - db_pool, - auth_email, - template_html: std::fs::read_to_string("./login_email.html").expect("A login_email.html file is needed"), - template_txt: std::fs::read_to_string("./login_email.txt").expect("A login_email.txt file is needed"), - domain: env::var("DOMAIN").expect("DOMAIN must be set"), - } - } - - fn gen_login_code(&self) -> u16 { - let mut rng = rand::thread_rng(); - let mut login_code: u16 = 0; + pub fn new(socket: SocketAddr, db_pool: Pool, auth_email: AuthEmail) -> RealmAuthServer { + RealmAuthServer { + socket, + db_pool, + auth_email, + template_html: std::fs::read_to_string("./login_email.html").expect("A login_email.html file is needed"), + template_txt: std::fs::read_to_string("./login_email.txt").expect("A login_email.txt file is needed"), + domain: env::var("DOMAIN").expect("DOMAIN must be set"), + } + } - for n in 1..=6 { - if n == 1 { - login_code += rng.gen_range(1..=9); - } - login_code += rng.gen_range(0..=9) * (10^n); - } - - login_code - } - - async fn is_username_taken(&self, username: &str) -> Result { - let result = query!("SELECT NOT EXISTS (SELECT 1 FROM user WHERE username = ?) AS does_exist", username).fetch_one(&self.db_pool).await; - - match result { - Ok(row) => Ok(row.does_exist != 0), - Err(_) => Err(InvalidUsername) - } - } - - async fn is_email_taken(&self, email: &str) -> Result { - let result = query!("SELECT NOT EXISTS (SELECT 1 FROM user WHERE email = ?) AS does_exist", email).fetch_one(&self.db_pool).await; + fn gen_login_code(&self) -> u16 { + let mut rng = rand::thread_rng(); + let mut login_code: u16 = 0; - match result { - Ok(row) => Ok(row.does_exist != 0), - Err(_) => Err(InvalidUsername) - } - } - - async fn is_authorized(&self, username: &str, token: &str) -> Result { - let result = query!("SELECT tokens FROM user WHERE username = ?", username).fetch_one(&self.db_pool).await; + for n in 1..=6 { + if n == 1 { + login_code += rng.gen_range(1..=9); + } + login_code += rng.gen_range(0..=9) * (10^n); + } - match result { - Ok(row) => { - let token_long: &str = &row.tokens.unwrap(); - let tokens = token_long.split(',').collect::>(); + login_code + } - for i in 0..tokens.len() { - if tokens.get(i).unwrap() == &token { - return Ok(true) - } - } + async fn is_username_taken(&self, username: &str) -> Result { + let result = query!("SELECT NOT EXISTS (SELECT 1 FROM user WHERE username = ?) AS does_exist", username).fetch_one(&self.db_pool).await; - Ok(false) - }, - Err(_) => Err(InvalidUsername), - } - } - - async fn send_login_message(&self, username: &str, email: &str, login_code: u16) -> Result<(), ErrorCode> { - let message = MessageBuilder::new() - .from((self.auth_email.auth_name.clone(), self.auth_email.auth_username.clone())) - .to(vec![ - (username, email), - ]) - .subject(format!("Realm confirmation code: {}", &login_code)) - .html_body(self.template_html.replace("{}", &login_code.to_string())) - .text_body(self.template_txt.replace("{}", &login_code.to_string())); - - let result = SmtpClientBuilder::new(&self.auth_email.server_address, self.auth_email.server_port) - .implicit_tls(false) - .credentials(Credentials::new(&self.auth_email.auth_username, &self.auth_email.auth_password)) - .connect() - .await; - - match result { - Ok(mut client) => { - let result = client.send(message).await; - match result { - Ok(_) => { - Ok(()) - } - Err(_) => { - Err(UnableToSendMail) - } - } - } - Err(_) => { - Err(UnableToConnectToMail) - } - } - } + match result { + Ok(row) => Ok(row.does_exist != 0), + Err(_) => Err(InvalidUsername) + } + } - async fn is_login_code_valid(&self, username: &str, login_code: u16) -> Result { - let result = query!("SELECT login_code FROM user WHERE username = ?;", username).fetch_one(&self.db_pool).await; + async fn is_email_taken(&self, email: &str) -> Result { + let result = query!("SELECT NOT EXISTS (SELECT 1 FROM user WHERE email = ?) AS does_exist", email).fetch_one(&self.db_pool).await; - match result { - Ok(row) => { - if row.login_code.unwrap() as u16 != login_code { - return Ok(false) - } - Ok(true) - } - Err(_) => Err(InvalidUsername) - } - } + match result { + Ok(row) => Ok(row.does_exist != 0), + Err(_) => Err(InvalidUsername) + } + } - fn is_username_valid(&self, username: &str) -> bool { - if !username.starts_with('@') || !username.contains(':') { - return false - } + async fn is_authorized(&self, username: &str, token: &str) -> Result { + let result = query!("SELECT tokens FROM user WHERE username = ?", username).fetch_one(&self.db_pool).await; - let name = &username[1..username.find(':').unwrap()]; - let domain = &username[username.find(':').unwrap()+1..]; + match result { + Ok(row) => { + let token_long: &str = &row.tokens.unwrap(); + let tokens = token_long.split(',').collect::>(); - let re = Regex::new(r"^[a-zA-Z0-9]+$").unwrap(); - if !re.is_match(name) { - return false - } - - if !domain.eq(&self.domain) { - return false - } + for i in 0..tokens.len() { + if tokens.get(i).unwrap() == &token { + return Ok(true) + } + } - true - } - - async fn reset_login_code(&self, username: &str) -> Result<(), ErrorCode> { - let result = query!("UPDATE user SET login_code = NULL WHERE username = ?", username).execute(&self.db_pool).await; - - match result { - Ok(_) => Ok(()), - Err(_) => Err(InvalidUsername) - } - } + Ok(false) + }, + Err(_) => Err(InvalidUsername), + } + } + + async fn send_login_message(&self, username: &str, email: &str, login_code: u16) -> Result<(), ErrorCode> { + let message = MessageBuilder::new() + .from((self.auth_email.auth_name.clone(), self.auth_email.auth_username.clone())) + .to(vec![ + (username, email), + ]) + .subject(format!("Realm confirmation code: {}", &login_code)) + .html_body(self.template_html.replace("{}", &login_code.to_string())) + .text_body(self.template_txt.replace("{}", &login_code.to_string())); + + let result = SmtpClientBuilder::new(&self.auth_email.server_address, self.auth_email.server_port) + .implicit_tls(false) + .credentials(Credentials::new(&self.auth_email.auth_username, &self.auth_email.auth_password)) + .connect() + .await; + + match result { + Ok(mut client) => { + let result = client.send(message).await; + match result { + Ok(_) => { + Ok(()) + } + Err(_) => { + Err(UnableToSendMail) + } + } + } + Err(_) => { + Err(UnableToConnectToMail) + } + } + } + + async fn is_login_code_valid(&self, username: &str, login_code: u16) -> Result { + let result = query!("SELECT login_code FROM user WHERE username = ?;", username).fetch_one(&self.db_pool).await; + + match result { + Ok(row) => { + if row.login_code.unwrap() as u16 != login_code { + return Ok(false) + } + Ok(true) + } + Err(_) => Err(InvalidUsername) + } + } + + fn is_username_valid(&self, username: &str) -> bool { + if !username.starts_with('@') || !username.contains(':') { + return false + } + + let name = &username[1..username.find(':').unwrap()]; + let domain = &username[username.find(':').unwrap()+1..]; + + let re = Regex::new(r"^[a-zA-Z0-9]+$").unwrap(); + if !re.is_match(name) { + return false + } + + if !domain.eq(&self.domain) { + return false + } + + true + } + + async fn reset_login_code(&self, username: &str) -> Result<(), ErrorCode> { + let result = query!("UPDATE user SET login_code = NULL WHERE username = ?", username).execute(&self.db_pool).await; + + match result { + Ok(_) => Ok(()), + Err(_) => Err(InvalidUsername) + } + } } impl RealmAuth for RealmAuthServer { - async fn test(self, _: Context, name: String) -> String { - format!("Hello {} auth!", name) - } + async fn test(self, _: Context, name: String) -> String { + format!("Hello {} auth!", name) + } - async fn server_token_validation(self, _: Context, server_token: String, username: String, server_id: String, domain: String, tarpc_port: u16) -> bool { - info!("API Request: server_token_validation( server_token -> {}, username -> {}, server_id -> {}, domain -> {}, tarpc_port -> {} )", + async fn server_token_validation(self, _: Context, server_token: String, username: String, server_id: String, domain: String, tarpc_port: u16) -> bool { + info!("API Request: server_token_validation( server_token -> {}, username -> {}, server_id -> {}, domain -> {}, tarpc_port -> {} )", server_token, username, server_id, domain, tarpc_port); - let result = query!("SELECT tokens FROM user WHERE username = ?", username).fetch_one(&self.db_pool).await; + let result = query!("SELECT tokens FROM user WHERE username = ?", username).fetch_one(&self.db_pool).await; - match result { - Ok(row) => { - let token_long: &str = &row.tokens.unwrap(); - let tokens = token_long.split(',').collect::>(); + match result { + Ok(row) => { + let token_long: &str = &row.tokens.unwrap(); + let tokens = token_long.split(',').collect::>(); - for token in tokens { - let hash = Sha3_256::new().chain(format!("{}{}{}{}", token, server_id, domain, tarpc_port)).finalize(); - if hex::encode(hash) == server_token { - return true - } - } - - false - }, - Err(_) => false, - } - } + for token in tokens { + let hash = Sha3_256::new().chain(format!("{}{}{}{}", token, server_id, domain, tarpc_port)).finalize(); + if hex::encode(hash) == server_token { + return true + } + } - async fn create_account_flow(self, _: Context, username: String, email: String) -> Result<(), ErrorCode> { - info!("API Request: create_account_flow( username -> {}, email -> {} )", username, email); + false + }, + Err(_) => false, + } + } - if !self.is_username_valid(&username) { - return Err(InvalidUsername) - } - - if self.is_username_taken(&username).await? { - return Err(UsernameTaken) - } + async fn create_account_flow(self, _: Context, username: String, email: String) -> Result<(), ErrorCode> { + info!("API Request: create_account_flow( username -> {}, email -> {} )", username, email); - if self.is_email_taken(&email).await? { - return Err(EmailTaken) - } - - let code = self.gen_login_code(); - self.send_login_message(&username, &email, code).await?; - - let result = query!("INSERT INTO user (username, email, avatar, login_code, tokens) VALUES (?, ?, '', ?, '')", username, email, code) - .execute(&self.db_pool).await; - - match result { - Ok(_) => Ok(()), - Err(_) => Err(Error) - } - } + if !self.is_username_valid(&username) { + return Err(InvalidUsername) + } - async fn create_login_flow(self, _: Context, mut username: Option, mut email: Option) -> Result<(), ErrorCode> { - info!("API Request: create_login_flow( username -> {}, email -> {} )", username.clone().unwrap_or("None".to_string()), email.clone().unwrap_or("None".to_string())); + if self.is_username_taken(&username).await? { + return Err(UsernameTaken) + } - if username.is_none() && email.is_none() { - return Err(Error) - } - - if username.is_none() { - let tmp = email.clone().unwrap(); - let result = query!("SELECT username FROM user WHERE email = ?;", tmp) - .fetch_one(&self.db_pool).await; - - match result { - Ok(row) => { - username = Some(row.username); - } - Err(_) => return Err(InvalidEmail) - } - } + if self.is_email_taken(&email).await? { + return Err(EmailTaken) + } - if email.clone().is_none() { - let tmp = username.clone().unwrap(); - let result = query!("SELECT email FROM user WHERE username = ?;", tmp) - .fetch_one(&self.db_pool).await; + let code = self.gen_login_code(); + self.send_login_message(&username, &email, code).await?; - match result { - Ok(row) => { - email = Some(row.email); - } - Err(_) => return Err(InvalidUsername) - } - } - - let code = self.gen_login_code(); - - let result = query!("UPDATE user SET login_code = ? WHERE username = ?;", code, username) - .execute(&self.db_pool).await; - - match result { - Ok(_) => self.send_login_message(&username.unwrap(), &email.unwrap(), code).await, - Err(_) => Err(InvalidUsername) - } - } + let result = query!("INSERT INTO user (username, email, avatar, login_code, tokens) VALUES (?, ?, '', ?, '')", username, email, code) + .execute(&self.db_pool).await; - async fn finish_login_flow(self, _: Context, username: String, login_code: u16) -> Result { - info!("API Request: finish_login_flow( username -> {}, login_code -> {} )", username, login_code); + match result { + Ok(_) => Ok(()), + Err(_) => Err(Error) + } + } - if !self.is_login_code_valid(&username, login_code).await? { - error!("Unauthorized request made for finish_login_flow() (bad login code)! username -> {}, login_code -> {}", username, login_code); - return Err(InvalidLoginCode) - } + async fn create_login_flow(self, _: Context, mut username: Option, mut email: Option) -> Result<(), ErrorCode> { + info!("API Request: create_login_flow( username -> {}, email -> {} )", username.clone().unwrap_or("None".to_string()), email.clone().unwrap_or("None".to_string())); - self.reset_login_code(&username).await?; + if username.is_none() && email.is_none() { + return Err(Error) + } - let _ = query!("UPDATE user SET login_code = NULL WHERE username = ?", username).execute(&self.db_pool).await; + if username.is_none() { + let tmp = email.clone().unwrap(); + let result = query!("SELECT username FROM user WHERE email = ?;", tmp) + .fetch_one(&self.db_pool).await; - let hash = Sha3_256::new().chain(format!("{}{}{}", username, login_code, Utc::now().to_utc())).finalize(); - let token = hex::encode(hash); + match result { + Ok(row) => { + username = Some(row.username); + } + Err(_) => return Err(InvalidEmail) + } + } - let result = query!("SELECT tokens FROM user WHERE username = ?", username).fetch_one(&self.db_pool).await; - match result { - Ok(row) => { - let token_long: &str = &row.tokens.unwrap(); - let mut tokens = token_long.split(',').collect::>(); - tokens.push(&token); + if email.clone().is_none() { + let tmp = username.clone().unwrap(); + let result = query!("SELECT email FROM user WHERE username = ?;", tmp) + .fetch_one(&self.db_pool).await; - let mega_token = tokens.join(","); - let result = query!("UPDATE user SET tokens = ? WHERE username = ?", mega_token, username) - .execute(&self.db_pool).await; - match result { - Ok(_) => Ok(token), - Err(_) => Err(InvalidUsername) - } - } - Err(_) => Err(InvalidUsername) - } - } + match result { + Ok(row) => { + email = Some(row.email); + } + Err(_) => return Err(InvalidUsername) + } + } - async fn change_email_flow(self, _: Context, username: String, new_email: String, token: String) -> Result<(), ErrorCode> { - info!("API Request: change_email_flow( username -> {}, new_email -> {}, token -> {} )", username, new_email, token); + let code = self.gen_login_code(); - if !self.is_authorized(&username, &token).await? { - return Err(Unauthorized) - } + let result = query!("UPDATE user SET login_code = ? WHERE username = ?;", code, username) + .execute(&self.db_pool).await; - if self.is_email_taken(&new_email).await? { - return Err(EmailTaken) - } + match result { + Ok(_) => self.send_login_message(&username.unwrap(), &email.unwrap(), code).await, + Err(_) => Err(InvalidUsername) + } + } - let _ = query!("UPDATE user SET new_email = ? WHERE username = ?", new_email, username).execute(&self.db_pool).await.unwrap(); + async fn finish_login_flow(self, _: Context, username: String, login_code: u16) -> Result { + info!("API Request: finish_login_flow( username -> {}, login_code -> {} )", username, login_code); - let code = self.gen_login_code(); + if !self.is_login_code_valid(&username, login_code).await? { + error!("Unauthorized request made for finish_login_flow() (bad login code)! username -> {}, login_code -> {}", username, login_code); + return Err(InvalidLoginCode) + } - let result = query!("UPDATE user SET login_code = ? WHERE username = ?;", code, username) - .execute(&self.db_pool).await; + self.reset_login_code(&username).await?; - match result { - Ok(_) => self.send_login_message(&username, &new_email, code).await, - Err(_) => Err(InvalidUsername) - } - } + let _ = query!("UPDATE user SET login_code = NULL WHERE username = ?", username).execute(&self.db_pool).await; - async fn finish_change_email_flow(self, _: Context, username: String, new_email: String, token: String, login_code: u16) -> Result<(), ErrorCode> { - info!("API Request: finish_change_email_flow( username -> {}, new_email -> {}, token -> {}, login_code -> {} )", username, new_email, token, login_code); + let hash = Sha3_256::new().chain(format!("{}{}{}", username, login_code, Utc::now().to_utc())).finalize(); + let token = hex::encode(hash); - if !self.is_authorized(&username, &token).await? { - error!("Unauthorized request made for finish_change_email_flow() (bad token)! username -> {}, token -> {}", username, token); - return Err(Unauthorized) - } + let result = query!("SELECT tokens FROM user WHERE username = ?", username).fetch_one(&self.db_pool).await; + match result { + Ok(row) => { + let token_long: &str = &row.tokens.unwrap(); + let mut tokens = token_long.split(',').collect::>(); + tokens.push(&token); - if self.is_email_taken(&new_email).await? { - error!("Email already taken for email change (but its the end of the flow?!) username -> {}, new_email -> {}", username, new_email); - return Err(EmailTaken) - } + let mega_token = tokens.join(","); + let result = query!("UPDATE user SET tokens = ? WHERE username = ?", mega_token, username) + .execute(&self.db_pool).await; + match result { + Ok(_) => Ok(token), + Err(_) => Err(InvalidUsername) + } + } + Err(_) => Err(InvalidUsername) + } + } - if !self.is_login_code_valid(&username, login_code).await? { - error!("Unauthorized request made for finish_change_email_flow() (bad login code)! username -> {}, login_code -> {}", username, login_code); - return Err(InvalidLoginCode) - } + async fn change_email_flow(self, _: Context, username: String, new_email: String, token: String) -> Result<(), ErrorCode> { + info!("API Request: change_email_flow( username -> {}, new_email -> {}, token -> {} )", username, new_email, token); - let _ = query!("UPDATE user SET new_email = NULL WHERE username = ?", username).execute(&self.db_pool).await; + if !self.is_authorized(&username, &token).await? { + return Err(Unauthorized) + } - let _ = query!("UPDATE user SET email = ? WHERE username = ?", new_email, username).execute(&self.db_pool).await; + if self.is_email_taken(&new_email).await? { + return Err(EmailTaken) + } - self.reset_login_code(&username).await?; + let _ = query!("UPDATE user SET new_email = ? WHERE username = ?", new_email, username).execute(&self.db_pool).await.unwrap(); - Ok(()) - } + let code = self.gen_login_code(); - // async fn change_username(self, _: Context, username: String, token: String, new_username: String) -> Result<(), ErrorCode> { - // info!("API Request: change_username( username -> {}, token -> {}, new_username -> {} )", username, token, new_username); - // - // if !self.is_authorized(&username, &token).await? { - // error!("Unauthorized request made for change_username()! username -> {}, token -> {}", username, token); - // return Err(Unauthorized) - // } - // - // if !self.is_username_valid(&new_username) { - // error!("Malformed username in request for change_username()! new_username -> {}", new_username); - // return Err(InvalidUsername) - // } - // - // if self.is_username_taken(&new_username).await? { - // error!("Username is taken for change_username()! new_username -> {}", new_username); - // return Err(UsernameTaken) - // } - // - // let result = query!("UPDATE user SET username = ? WHERE username = ?", new_username, username).execute(&self.db_pool).await; - // match result { - // Ok(_) => Ok(()), - // Err(_) => Err(Error) - // } - // } + let result = query!("UPDATE user SET login_code = ? WHERE username = ?;", code, username) + .execute(&self.db_pool).await; - async fn change_avatar(self, _: Context, username: String, token: String, new_avatar: String) -> Result<(), ErrorCode> { - info!("API Request: change_avatar( username -> {}, token -> {}, new_avatar -> {} )", username, token, new_avatar); - - if !self.is_authorized(&username, &token).await? { - error!("Unauthorized request made for change_avatar()! username -> {}, token -> {}", username, token); - return Err(Unauthorized) - } + match result { + Ok(_) => self.send_login_message(&username, &new_email, code).await, + Err(_) => Err(InvalidUsername) + } + } - let result = query!("UPDATE user SET avatar = ? WHERE username = ?", new_avatar, username).execute(&self.db_pool).await; - match result { - Ok(_) => Ok(()), - Err(_) => Err(Error) - } - } + async fn finish_change_email_flow(self, _: Context, username: String, new_email: String, token: String, login_code: u16) -> Result<(), ErrorCode> { + info!("API Request: finish_change_email_flow( username -> {}, new_email -> {}, token -> {}, login_code -> {} )", username, new_email, token, login_code); - async fn get_all_data(self, _: Context, username: String, token: String) -> Result { - info!("API Request: get_all_data( username -> {}, token -> {} )", username, token); - - if !self.is_authorized(&username, &token).await? { - error!("Unauthorized request made for get_all_data()! username -> {}, token -> {}", username, token); - return Err(Unauthorized) - } + if !self.is_authorized(&username, &token).await? { + error!("Unauthorized request made for finish_change_email_flow() (bad token)! username -> {}, token -> {}", username, token); + return Err(Unauthorized) + } - let result = query!(r"SELECT * FROM user WHERE username = ?", username).fetch_one(&self.db_pool).await; - match result { - Ok(row) => { - Ok(AuthUser { - id: row.id, - username: row.username, - email: row.email, - avatar: row.avatar, - login_code: None, - bigtoken: row.tokens, - google_oauth: row.google_oauth, - apple_oauth: row.apple_oauth, - github_oauth: row.github_oauth, - discord_oauth: row.discord_oauth, - }) - } - Err(_) => { - error!("Invalid username in request for get_all_data()! username -> {}", username); - Err(InvalidUsername) - } - } - } + if self.is_email_taken(&new_email).await? { + error!("Email already taken for email change (but its the end of the flow?!) username -> {}, new_email -> {}", username, new_email); + return Err(EmailTaken) + } - async fn sign_out(self, _: Context, username: String, token: String) -> Result<(), ErrorCode> { - info!("API Request: sign_out( username -> {}, token -> {} )", username, token); - - let result = query!("SELECT tokens FROM user WHERE username = ?", username).fetch_one(&self.db_pool).await; + if !self.is_login_code_valid(&username, login_code).await? { + error!("Unauthorized request made for finish_change_email_flow() (bad login code)! username -> {}, login_code -> {}", username, login_code); + return Err(InvalidLoginCode) + } - match result { - Ok(row) => { - let token_long: &str = &row.tokens.unwrap(); - let mut tokens = token_long.split(',').collect::>(); - - for i in 0..tokens.len() { - if tokens.get(i).unwrap().eq(&token.as_str()) { - tokens.remove(i); - - let mega_token = tokens.join(",").to_string(); - let result = query!("UPDATE user SET tokens = ? WHERE username = ?", mega_token, username) - .execute(&self.db_pool).await; + let _ = query!("UPDATE user SET new_email = NULL WHERE username = ?", username).execute(&self.db_pool).await; - return match result { - Ok(_) => Ok(()), - Err(_) => { - error!("Unable to update tokens on sign_out()! \ + let _ = query!("UPDATE user SET email = ? WHERE username = ?", new_email, username).execute(&self.db_pool).await; + + self.reset_login_code(&username).await?; + + Ok(()) + } + + // TODO: find a way of supporting, post-capstone work + // async fn change_username(self, _: Context, username: String, token: String, new_username: String) -> Result<(), ErrorCode> { + // info!("API Request: change_username( username -> {}, token -> {}, new_username -> {} )", username, token, new_username); + // + // if !self.is_authorized(&username, &token).await? { + // error!("Unauthorized request made for change_username()! username -> {}, token -> {}", username, token); + // return Err(Unauthorized) + // } + // + // if !self.is_username_valid(&new_username) { + // error!("Malformed username in request for change_username()! new_username -> {}", new_username); + // return Err(InvalidUsername) + // } + // + // if self.is_username_taken(&new_username).await? { + // error!("Username is taken for change_username()! new_username -> {}", new_username); + // return Err(UsernameTaken) + // } + // + // let result = query!("UPDATE user SET username = ? WHERE username = ?", new_username, username).execute(&self.db_pool).await; + // match result { + // Ok(_) => Ok(()), + // Err(_) => Err(Error) + // } + // } + + async fn change_avatar(self, _: Context, username: String, token: String, new_avatar: String) -> Result<(), ErrorCode> { + info!("API Request: change_avatar( username -> {}, token -> {}, new_avatar -> {} )", username, token, new_avatar); + + if !self.is_authorized(&username, &token).await? { + error!("Unauthorized request made for change_avatar()! username -> {}, token -> {}", username, token); + return Err(Unauthorized) + } + + let result = query!("UPDATE user SET avatar = ? WHERE username = ?", new_avatar, username).execute(&self.db_pool).await; + match result { + Ok(_) => Ok(()), + Err(_) => Err(Error) + } + } + + async fn get_all_data(self, _: Context, username: String, token: String) -> Result { + info!("API Request: get_all_data( username -> {}, token -> {} )", username, token); + + if !self.is_authorized(&username, &token).await? { + error!("Unauthorized request made for get_all_data()! username -> {}, token -> {}", username, token); + return Err(Unauthorized) + } + + let result = query!(r"SELECT * FROM user WHERE username = ?", username).fetch_one(&self.db_pool).await; + match result { + Ok(row) => { + Ok(AuthUser { + id: row.id, + username: row.username, + email: row.email, + avatar: row.avatar, + servers: row.servers, + login_code: None, + bigtoken: row.tokens, + google_oauth: row.google_oauth, + apple_oauth: row.apple_oauth, + github_oauth: row.github_oauth, + discord_oauth: row.discord_oauth, + }) + } + Err(_) => { + error!("Invalid username in request for get_all_data()! username -> {}", username); + Err(InvalidUsername) + } + } + } + + async fn sign_out(self, _: Context, username: String, token: String) -> Result<(), ErrorCode> { + info!("API Request: sign_out( username -> {}, token -> {} )", username, token); + + let result = query!("SELECT tokens FROM user WHERE username = ?", username).fetch_one(&self.db_pool).await; + + match result { + Ok(row) => { + let token_long: &str = &row.tokens.unwrap(); + let mut tokens = token_long.split(',').collect::>(); + + for i in 0..tokens.len() { + if tokens.get(i).unwrap().eq(&token.as_str()) { + tokens.remove(i); + + let mega_token = tokens.join(",").to_string(); + let result = query!("UPDATE user SET tokens = ? WHERE username = ?", mega_token, username) + .execute(&self.db_pool).await; + + return match result { + Ok(_) => Ok(()), + Err(_) => { + error!("Unable to update tokens on sign_out()! \ username -> {}, (previous) token_long -> {}, mega_token -> {}, token -> {}", username, token_long, mega_token, token); - Err(Error) - } - }; - } - } + Err(Error) + } + }; + } + } - error!("Unauthorized request made for sign_out()! username -> {}, token -> {}", username, token); - Err(Unauthorized) - }, - Err(_) => { - error!("Invalid username in request for get_avatar_for_user()! username -> {}", username); - Err(InvalidUsername) - }, - } - } + error!("Unauthorized request made for sign_out()! username -> {}, token -> {}", username, token); + Err(Unauthorized) + }, + Err(_) => { + error!("Invalid username in request for get_avatar_for_user()! username -> {}", username); + Err(InvalidUsername) + }, + } + } - async fn delete_account_flow(self, _: Context, username: String, token: String) -> Result<(), ErrorCode> { - info!("API Request: delete_account_flow( username -> {}, token -> {} )", username, token); - - if !self.is_authorized(&username, &token).await? { - return Err(Unauthorized) - } + async fn delete_account_flow(self, _: Context, username: String, token: String) -> Result<(), ErrorCode> { + info!("API Request: delete_account_flow( username -> {}, token -> {} )", username, token); - let email = match query!("SELECT email FROM user WHERE username = ?;", username).fetch_one(&self.db_pool).await { - Ok(row) => Ok(row.email), - Err(_) => Err(InvalidUsername), - }?; + if !self.is_authorized(&username, &token).await? { + return Err(Unauthorized) + } - let code = self.gen_login_code(); + let email = match query!("SELECT email FROM user WHERE username = ?;", username).fetch_one(&self.db_pool).await { + Ok(row) => Ok(row.email), + Err(_) => Err(InvalidUsername), + }?; - let result = query!("UPDATE user SET login_code = ? WHERE username = ?;", code, username) - .execute(&self.db_pool).await; + let code = self.gen_login_code(); - match result { - Ok(_) => self.send_login_message(&username, &email, code).await, - Err(_) => Err(InvalidUsername) - } - } + let result = query!("UPDATE user SET login_code = ? WHERE username = ?;", code, username) + .execute(&self.db_pool).await; - async fn finish_delete_account_flow(self, _: Context, username: String, token: String, login_code: u16) -> Result<(), ErrorCode> { - info!("API Request: finish_delete_account_flow( username -> {}, token -> {}, login_code -> {} )", username, token, login_code); + match result { + Ok(_) => self.send_login_message(&username, &email, code).await, + Err(_) => Err(InvalidUsername) + } + } - if !self.is_authorized(&username, &token).await? { - return Err(Unauthorized) - } + async fn finish_delete_account_flow(self, _: Context, username: String, token: String, login_code: u16) -> Result<(), ErrorCode> { + info!("API Request: finish_delete_account_flow( username -> {}, token -> {}, login_code -> {} )", username, token, login_code); - if !self.is_login_code_valid(&username, login_code).await? { - return Err(InvalidLoginCode) - } - - self.reset_login_code(&username).await?; - - let result = query!("DELETE FROM user WHERE username = ?", username).execute(&self.db_pool).await; - match result { - Ok(_) => Ok(()), - Err(_) => Err(InvalidUsername) - } - } + if !self.is_authorized(&username, &token).await? { + return Err(Unauthorized) + } - async fn get_avatar_for_user(self, _: Context, username: String) -> Result { - info!("API Request: get_avatar_for_user( username -> {} )", username); - - let result = query!("SELECT avatar FROM user WHERE username = ?", username).fetch_one(&self.db_pool).await; + if !self.is_login_code_valid(&username, login_code).await? { + return Err(InvalidLoginCode) + } - match result { - Ok(row) => Ok(row.avatar), - Err(_) => { - error!("Invalid username in request for get_avatar_for_user()! username -> {}", username); - Err(InvalidUsername) - } - } - } + self.reset_login_code(&username).await?; + + let result = query!("DELETE FROM user WHERE username = ?", username).execute(&self.db_pool).await; + match result { + Ok(_) => Ok(()), + Err(_) => Err(InvalidUsername) + } + } + + async fn add_server(self, _: Context, username: String, token: String, domain: String) -> Result<(), ErrorCode> { + if !self.is_authorized(&username, &token).await? { + return Err(Unauthorized) + } + + let result = query!("SELECT servers FROM user WHERE username = ?", username).fetch_one(&self.db_pool).await; + match result { + Ok(row) => { + let mut vec_servers = row.servers.split('|').collect::>(); + for server in &vec_servers { + if server.eq(&domain) { + return Err(AlreadyJoinedServer) + } + } + + vec_servers.push(&domain); + let new_servers = vec_servers.join("|"); + + let result = query!("UPDATE user SET servers = ? WHERE username = ?", new_servers, username).fetch_one(&self.db_pool).await; + match result { + Ok(_) => Ok(()), + Err(_) => Err(Error) + } + } + Err(_) => Err(Error) + } + } + + async fn remove_server(self, _: Context, username: String, token: String, domain: String) -> Result<(), ErrorCode> { + if !self.is_authorized(&username, &token).await? { + return Err(Unauthorized) + } + + let result = query!("SELECT servers FROM user WHERE username = ?", username).fetch_one(&self.db_pool).await; + match result { + Ok(row) => { + let mut vec_servers = row.servers.split('|').collect::>(); + for i in 0..vec_servers.len() { + if vec_servers.get(i).unwrap().eq(&domain) { + vec_servers.remove(i); + + let new_servers = vec_servers.join("|"); + let result = query!("UPDATE user SET servers = ? WHERE username = ?", new_servers, username).fetch_one(&self.db_pool).await; + return match result { + Ok(_) => Ok(()), + Err(_) => Err(Error) + } + } + } + + Err(NotInServer) + } + Err(_) => Err(Error) + } + } + + async fn get_joined_servers(self, _: Context, username: String, token: String) -> Result, ErrorCode> { + if !self.is_authorized(&username, &token).await? { + return Err(Unauthorized) + } + + let result = query!("SELECT servers FROM user WHERE username = ?", username).fetch_one(&self.db_pool).await; + match result { + Ok(row) => { + let vec_servers = row.servers.split('|').collect::>(); + let mut servers = Vec::new(); + for server in vec_servers { + servers.push(server.to_string()) + } + + Ok(servers) + } + Err(_) => Err(Error) + } + } + + async fn get_avatar_for_user(self, _: Context, username: String) -> Result { + info!("API Request: get_avatar_for_user( username -> {} )", username); + + let result = query!("SELECT avatar FROM user WHERE username = ?", username).fetch_one(&self.db_pool).await; + + match result { + Ok(row) => Ok(row.avatar), + Err(_) => { + error!("Invalid username in request for get_avatar_for_user()! username -> {}", username); + Err(InvalidUsername) + } + } + } } \ No newline at end of file diff --git a/auth/src/types.rs b/auth/src/types.rs index 09ad1f0..ea53da6 100644 --- a/auth/src/types.rs +++ b/auth/src/types.rs @@ -18,6 +18,9 @@ pub trait RealmAuth { async fn sign_out(username: String, token: String) -> Result<(), ErrorCode>; async fn delete_account_flow(username: String, token: String) -> Result<(), ErrorCode>; async fn finish_delete_account_flow(username: String, token: String, login_code: u16) -> Result<(), ErrorCode>; + async fn add_server(username: String, token: String, domain: String) -> Result<(), ErrorCode>; + async fn remove_server(username: String, token: String, domain: String) -> Result<(), ErrorCode>; + async fn get_joined_servers(username: String, token: String) -> Result, ErrorCode>; //NOTE: Anyone can call async fn get_avatar_for_user(username: String) -> Result; @@ -30,6 +33,7 @@ pub struct AuthUser { pub username: String, pub email: String, pub avatar: String, + pub servers: String, pub login_code: Option, pub bigtoken: Option, pub google_oauth: Option, diff --git a/shared/src/types.rs b/shared/src/types.rs index 38198dc..26c2fc9 100644 --- a/shared/src/types.rs +++ b/shared/src/types.rs @@ -13,6 +13,8 @@ pub enum ErrorCode { InvalidToken, UnableToConnectToMail, UnableToSendMail, + AlreadyJoinedServer, + NotInServer, MessageNotFound, RoomNotFound,