From 8495d1a1ba4d733b6fd41804bab2d03525c0ee09 Mon Sep 17 00:00:00 2001 From: Joshua Higgins Date: Mon, 1 Jul 2024 22:47:21 -0400 Subject: [PATCH] Good progress TOOD: Maybe shared library for ErrorCode? Emails, Account Creation, Username formatting, OAuth Support for all the ones we want, Testing --- auth/Cargo.toml | 1 + auth/src/server.rs | 205 ++++++++++++++++++++++++++++++++++++++++++--- auth/src/types.rs | 19 +++-- 3 files changed, 206 insertions(+), 19 deletions(-) diff --git a/auth/Cargo.toml b/auth/Cargo.toml index dfdeca2..d9c44a6 100644 --- a/auth/Cargo.toml +++ b/auth/Cargo.toml @@ -15,3 +15,4 @@ dotenvy = "0.15" sqlx = { version = "0.7", features = [ "runtime-tokio", "tls-rustls", "mysql", "chrono" ] } sha3 = "0.10.8" hex = "0.4.3" +rand = "0.8.5" diff --git a/auth/src/server.rs b/auth/src/server.rs index 51a1667..d26f683 100644 --- a/auth/src/server.rs +++ b/auth/src/server.rs @@ -1,11 +1,13 @@ use std::net::SocketAddr; +use rand::Rng; use sha3::{Digest, Sha3_256}; use sha3::digest::Update; use sqlx::{MySql, Pool, Row}; use tarpc::context::Context; use crate::types::{AuthUser, ErrorCode, RealmAuth}; +use crate::types::ErrorCode::*; #[derive(Clone)] pub struct RealmAuthServer { @@ -13,6 +15,8 @@ pub struct RealmAuthServer { pub db_pool: Pool, } +//TODO: USERNAME FORMATTING! + impl RealmAuthServer { pub fn new(socket: SocketAddr, db_pool: Pool) -> RealmAuthServer { RealmAuthServer { @@ -20,6 +24,63 @@ impl RealmAuthServer { db_pool, } } + + pub fn gen_login_code(&self) -> u16 { + let mut rng = rand::thread_rng(); + let mut login_code: u16 = 0; + + 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 + } + + pub async fn is_username_taken(&self, username: &str) -> Result { + let result = sqlx::query("SELECT NOT EXISTS (SELECT 1 FROM user WHERE username = ?) AS does_exist") + .bind(username) + .fetch_one(&self.db_pool).await; + + match result { + Ok(row) => Ok(row.try_get("does_exist").unwrap()), + Err(_) => Err(InvalidUsername) + } + } + + pub async fn is_email_taken(&self, email: &str) -> Result { + let result = sqlx::query("SELECT NOT EXISTS (SELECT 1 FROM user WHERE email = ?) AS does_exist") + .bind(email) + .fetch_one(&self.db_pool).await; + + match result { + Ok(row) => Ok(row.try_get("does_exist").unwrap()), + Err(_) => Err(InvalidUsername) + } + } + + pub async fn is_authorized(&self, username: &str, token: &str) -> Result { + let result = sqlx::query("SELECT tokens FROM user WHERE username = ?") + .bind(username).fetch_one(&self.db_pool).await; + + match result { + Ok(row) => { + let token_long: &str = row.try_get("tokens").unwrap(); + let tokens = token_long.split(',').collect::>(); + + for i in 0..tokens.len() { + if tokens.get(i).unwrap() == &token { + return Ok(true) + } + } + + Ok(false) + }, + Err(_) => Err(InvalidUsername), + } + } } impl RealmAuth for RealmAuthServer { @@ -48,39 +109,159 @@ impl RealmAuth for RealmAuthServer { } } - async fn create_account(self, _: Context, username: String, email: String, avatar: String) -> Result { + async fn create_account_flow(self, _: Context, username: String, email: String) -> ErrorCode { + todo!() + } + + async fn finish_account_flow(self, _: Context, username: String, login_code: u16, avatar: String) -> Result { todo!() } async fn create_login_flow(self, _: Context, username: String) -> ErrorCode { + let result = sqlx::query("UPDATE user SET login_code = ? WHERE username = ?;") + .bind(self.gen_login_code()) + .bind(username) + .execute(&self.db_pool).await; + + match result { + Ok(_) => { + todo!() //TODO: Emails! + }, + Err(_) => InvalidUsername + } + } + + async fn finish_login_flow(self, _: Context, username: String, login_code: u16) -> Result { todo!() } - async fn create_token_from_login(self, _: Context, username: String, login_code: u16) -> Result { + async fn change_email_flow(self, _: Context, username: String, new_email: String, token: String) -> ErrorCode { todo!() } - async fn change_email_flow(self, _: Context, username: String, token: String) -> ErrorCode { - todo!() - } - - async fn resolve_email_flow(self, _: Context, username: String, token: String, login_code: u16, new_email: String) -> ErrorCode { + async fn finish_change_email_flow(self, _: Context, username: String, token: String, login_code: u16) -> ErrorCode { todo!() } async fn change_username(self, _: Context, username: String, token: String, new_username: String) -> ErrorCode { - todo!() + let result = self.is_authorized(&username, &token).await; + match result { + Ok(authorized) => { + if !authorized { + return Unauthorized + } + } + Err(error) => return error + } + + let result = self.is_username_taken(&new_username).await; + match result { + Ok(is_taken) => { + if is_taken { + return UsernameTaken + } + } + Err(error) => return error + } + + let result = sqlx::query("UPDATE user SET username = ? WHERE username = ?") + .bind(&new_username) + .bind(&username).execute(&self.db_pool).await; + match result { + Ok(_) => NoError, + Err(_) => Error + } } - async fn change_avatar(self, _: Context, username: String, token: String, avatar: String) -> ErrorCode { - todo!() + async fn change_avatar(self, _: Context, username: String, token: String, new_avatar: String) -> ErrorCode { + let result = self.is_authorized(&username, &token).await; + match result { + Ok(authorized) => { + if !authorized { + return Unauthorized + } + } + Err(error) => return error + } + + let result = sqlx::query("UPDATE user SET avatar = ? WHERE username = ?") + .bind(&new_avatar) + .bind(&username).execute(&self.db_pool).await; + match result { + Ok(_) => NoError, + Err(_) => Error + } } async fn get_all_data(self, _: Context, username: String, token: String) -> Result { - todo!() + let result = self.is_authorized(&username, &token).await; + match result { + Ok(authorized) => { + if !authorized { + return Err(Unauthorized) + } + } + Err(error) => return Err(error) + } + + let result = sqlx::query("SELECT * FROM user WHERE username = ?") + .bind(&username).fetch_one(&self.db_pool).await; + match result { + Ok(row) => { + Ok(AuthUser { + id: row.try_get("id").unwrap(), + username: row.try_get("username").unwrap(), + email: row.try_get("email").unwrap(), + avatar: row.try_get("avatar").unwrap(), + login_code: None, + bigtoken: row.try_get("tokens").unwrap(), + google_oauth: row.try_get("google_oauth").unwrap(), + apple_oauth: row.try_get("apple_oauth").unwrap(), + github_oauth: row.try_get("github_oauth").unwrap(), + discord_oauth: row.try_get("discord_oauth").unwrap(), + }) + } + Err(_) => Err(InvalidUsername) + } + } + + async fn sign_out(self, _: Context, username: String, token: String) -> ErrorCode { + let result = sqlx::query("SELECT tokens FROM user WHERE username = ?") + .bind(&username).fetch_one(&self.db_pool).await; + + match result { + Ok(row) => { + let token_long: &str = row.try_get("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 result = sqlx::query("UPDATE user SET tokens = ? WHERE username = ?") + .bind(tokens.join(",")) + .bind(&username) + .execute(&self.db_pool).await; + + match result { + Ok(_) => NoError, + Err(_) => Error + }; + } + } + + Unauthorized + }, + Err(_) => InvalidUsername, + } } async fn get_avatar_for_user(self, _: Context, username: String) -> Result { - todo!() + let result = sqlx::query("SELECT tokens FROM user WHERE username = ?").bind(username).fetch_one(&self.db_pool).await; + + match result { + Ok(row) => Ok(row.try_get("avatar").unwrap_or("".to_string())), + Err(_) => Err(InvalidUsername) + } } } \ No newline at end of file diff --git a/auth/src/types.rs b/auth/src/types.rs index b529379..9c39f6c 100644 --- a/auth/src/types.rs +++ b/auth/src/types.rs @@ -4,16 +4,18 @@ use serde::{Deserialize, Serialize}; pub trait RealmAuth { async fn test(name: String) -> String; async fn server_token_validation(server_token: String, username: String, server_id: String, domain: String, tarpc_port: u16) -> bool; - async fn create_account(username: String, email: String, avatar: String) -> Result; + async fn create_account_flow(username: String, email: String) -> ErrorCode; //NOTE: Still require sign in flow + async fn finish_account_flow(username: String, login_code: u16, avatar: String) -> Result; async fn create_login_flow(username: String) -> ErrorCode; - async fn create_token_from_login(username: String, login_code: u16) -> Result; + async fn finish_login_flow(username: String, login_code: u16) -> Result; //NOTE: Need to be the user - async fn change_email_flow(username: String, token: String) -> ErrorCode; - async fn resolve_email_flow(username: String, token: String, login_code: u16, new_email: String) -> ErrorCode; + async fn change_email_flow(username: String, new_email: String, token: String) -> ErrorCode; + async fn finish_change_email_flow(username: String, token: String, login_code: u16) -> ErrorCode; async fn change_username(username: String, token: String, new_username: String) -> ErrorCode; - async fn change_avatar(username: String, token: String, avatar: String) -> ErrorCode; + async fn change_avatar(username: String, token: String, new_avatar: String) -> ErrorCode; async fn get_all_data(username: String, token: String) -> Result; + async fn sign_out(username: String, token: String) -> ErrorCode; //NOTE: Anyone can call async fn get_avatar_for_user(username: String) -> Result; @@ -27,17 +29,20 @@ pub trait RealmAuth { // Get avatar // Get all userdata if you are the user // Server token validation + // Logout } #[derive(Debug, Clone, Serialize, Deserialize)] pub enum ErrorCode { - None, + NoError, Error, + Unauthorized, EmailTaken, UsernameTaken, InvalidLoginCode, InvalidImage, InvalidUsername, + InvalidToken, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -47,7 +52,7 @@ pub struct AuthUser { pub email: String, pub avatar: String, pub login_code: Option, - pub tokens: Option>, + pub bigtoken: Option, pub google_oauth: Option, pub apple_oauth: Option, pub github_oauth: Option,