Good progress

TOOD:
Maybe shared library for ErrorCode?
Emails,
Account Creation,
Username formatting,
OAuth Support for all the ones we want,
Testing
This commit is contained in:
2024-07-01 22:47:21 -04:00
Unverified
parent fbdcc4eb82
commit 8495d1a1ba
3 changed files with 206 additions and 19 deletions

View File

@@ -15,3 +15,4 @@ dotenvy = "0.15"
sqlx = { version = "0.7", features = [ "runtime-tokio", "tls-rustls", "mysql", "chrono" ] } sqlx = { version = "0.7", features = [ "runtime-tokio", "tls-rustls", "mysql", "chrono" ] }
sha3 = "0.10.8" sha3 = "0.10.8"
hex = "0.4.3" hex = "0.4.3"
rand = "0.8.5"

View File

@@ -1,11 +1,13 @@
use std::net::SocketAddr; use std::net::SocketAddr;
use rand::Rng;
use sha3::{Digest, Sha3_256}; use sha3::{Digest, Sha3_256};
use sha3::digest::Update; use sha3::digest::Update;
use sqlx::{MySql, Pool, Row}; use sqlx::{MySql, Pool, Row};
use tarpc::context::Context; use tarpc::context::Context;
use crate::types::{AuthUser, ErrorCode, RealmAuth}; use crate::types::{AuthUser, ErrorCode, RealmAuth};
use crate::types::ErrorCode::*;
#[derive(Clone)] #[derive(Clone)]
pub struct RealmAuthServer { pub struct RealmAuthServer {
@@ -13,6 +15,8 @@ pub struct RealmAuthServer {
pub db_pool: Pool<MySql>, pub db_pool: Pool<MySql>,
} }
//TODO: USERNAME FORMATTING!
impl RealmAuthServer { impl RealmAuthServer {
pub fn new(socket: SocketAddr, db_pool: Pool<MySql>) -> RealmAuthServer { pub fn new(socket: SocketAddr, db_pool: Pool<MySql>) -> RealmAuthServer {
RealmAuthServer { RealmAuthServer {
@@ -20,6 +24,63 @@ impl RealmAuthServer {
db_pool, 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<bool, ErrorCode> {
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<bool, ErrorCode> {
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<bool, 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 tokens = token_long.split(',').collect::<Vec<&str>>();
for i in 0..tokens.len() {
if tokens.get(i).unwrap() == &token {
return Ok(true)
}
}
Ok(false)
},
Err(_) => Err(InvalidUsername),
}
}
} }
impl RealmAuth for RealmAuthServer { 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<String, ErrorCode> { 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<String, ErrorCode> {
todo!() todo!()
} }
async fn create_login_flow(self, _: Context, username: String) -> ErrorCode { 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<String, ErrorCode> {
todo!() todo!()
} }
async fn create_token_from_login(self, _: Context, username: String, login_code: u16) -> Result<String, ErrorCode> { async fn change_email_flow(self, _: Context, username: String, new_email: String, token: String) -> ErrorCode {
todo!() todo!()
} }
async fn change_email_flow(self, _: Context, username: String, token: String) -> ErrorCode { async fn finish_change_email_flow(self, _: Context, username: String, token: String, login_code: u16) -> ErrorCode {
todo!()
}
async fn resolve_email_flow(self, _: Context, username: String, token: String, login_code: u16, new_email: String) -> ErrorCode {
todo!() todo!()
} }
async fn change_username(self, _: Context, username: String, token: String, new_username: String) -> ErrorCode { 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 { async fn change_avatar(self, _: Context, username: String, token: String, new_avatar: 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 = 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<AuthUser, ErrorCode> { async fn get_all_data(self, _: Context, username: String, token: String) -> Result<AuthUser, ErrorCode> {
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::<Vec<&str>>();
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<String, ErrorCode> { async fn get_avatar_for_user(self, _: Context, username: String) -> Result<String, ErrorCode> {
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)
}
} }
} }

View File

@@ -4,16 +4,18 @@ use serde::{Deserialize, Serialize};
pub trait RealmAuth { pub trait RealmAuth {
async fn test(name: String) -> String; 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 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<String, ErrorCode>; 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<String, ErrorCode>;
async fn create_login_flow(username: String) -> ErrorCode; async fn create_login_flow(username: String) -> ErrorCode;
async fn create_token_from_login(username: String, login_code: u16) -> Result<String, ErrorCode>; async fn finish_login_flow(username: String, login_code: u16) -> Result<String, ErrorCode>;
//NOTE: Need to be the user //NOTE: Need to be the user
async fn change_email_flow(username: String, token: String) -> ErrorCode; async fn change_email_flow(username: String, new_email: String, token: String) -> ErrorCode;
async fn resolve_email_flow(username: String, token: String, login_code: u16, new_email: 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_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<AuthUser, ErrorCode>; async fn get_all_data(username: String, token: String) -> Result<AuthUser, ErrorCode>;
async fn sign_out(username: String, token: String) -> ErrorCode;
//NOTE: Anyone can call //NOTE: Anyone can call
async fn get_avatar_for_user(username: String) -> Result<String, ErrorCode>; async fn get_avatar_for_user(username: String) -> Result<String, ErrorCode>;
@@ -27,17 +29,20 @@ pub trait RealmAuth {
// Get avatar // Get avatar
// Get all userdata if you are the user // Get all userdata if you are the user
// Server token validation // Server token validation
// Logout
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ErrorCode { pub enum ErrorCode {
None, NoError,
Error, Error,
Unauthorized,
EmailTaken, EmailTaken,
UsernameTaken, UsernameTaken,
InvalidLoginCode, InvalidLoginCode,
InvalidImage, InvalidImage,
InvalidUsername, InvalidUsername,
InvalidToken,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -47,7 +52,7 @@ pub struct AuthUser {
pub email: String, pub email: String,
pub avatar: String, pub avatar: String,
pub login_code: Option<u16>, pub login_code: Option<u16>,
pub tokens: Option<Vec<String>>, pub bigtoken: Option<String>,
pub google_oauth: Option<String>, pub google_oauth: Option<String>,
pub apple_oauth: Option<String>, pub apple_oauth: Option<String>,
pub github_oauth: Option<String>, pub github_oauth: Option<String>,