diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..10cfcd8 --- /dev/null +++ b/TODO.md @@ -0,0 +1,12 @@ +# Backend +-[x] Migrate to sqlx as backend database queryer +-[ ] Allow Private Accounts +-[ ] Secrets to authenicate that its the user + +# Frontend +-[ ] Make js api asynchronous +-[ ] Hash passwords on front end so server plaintext password is never sent over the internet +-[ ] Use secret to uniquely identify +-[ ] Scrollbars +-[ ] Change Color Scheme +-[ ] Be able to view others accounts \ No newline at end of file diff --git a/database/dev/database.sqlite b/database/dev/database.sqlite index 915d40c..04cbb34 100755 Binary files a/database/dev/database.sqlite and b/database/dev/database.sqlite differ diff --git a/done.md b/done.md deleted file mode 100644 index 77fc017..0000000 --- a/done.md +++ /dev/null @@ -1 +0,0 @@ -- Migrated to sqlx \ No newline at end of file diff --git a/src/typing/leaderboard.rs b/src/typing/leaderboard.rs index b9232d1..6e808d2 100644 --- a/src/typing/leaderboard.rs +++ b/src/typing/leaderboard.rs @@ -1,4 +1,4 @@ -use rocket::{ serde::json::Json, State, Data }; +use rocket::{ serde::json::Json, State }; use crate::typing::sql::{ Database, LeaderBoardTest diff --git a/src/typing/sql.rs b/src/typing/sql.rs index 352047e..42f9b73 100644 --- a/src/typing/sql.rs +++ b/src/typing/sql.rs @@ -13,6 +13,7 @@ use sqlx::sqlite::{SqlitePool, SqlitePoolOptions}; use rocket::serde::Serialize; +/// Contains the database connection pool pub struct Database(SqlitePool); /// gets a connection to the database and returns it as @@ -71,11 +72,11 @@ impl Database { /// takes a username and password and creates a database /// entry for a new user - pub async fn create_user(&self, username: &str, password: &str) -> Result<(), sqlx::Error> { + pub async fn create_user(&self, username: &str, password: &str, secret: &str) -> Result<(), sqlx::Error> { sqlx::query!(" - INSERT INTO Users (username, password) - VALUES (?1, ?2)", - username, password + INSERT INTO Users (username, password, secret) + VALUES (?1, ?2, ?3)", + username, password, secret ).execute(&self.0).await?; Ok(()) @@ -87,12 +88,12 @@ impl Database { let user = sqlx::query!(" SELECT user_id, secret FROM Users - WHERE username=:username AND password=:password", + WHERE username=? AND password=?", username, password - ).fetch_all(&self.0).await?; + ).fetch_one(&self.0).await?; - let user_id = user[0].user_id.unwrap() as u32; - let secret = user[0].secret.clone(); + let user_id = user.user_id.unwrap() as u32; + let secret = user.secret.clone(); Ok(Some((user_id, secret))) } @@ -104,10 +105,12 @@ impl Database { SELECT test_type, test_length, test_time, test_seed, quote_id, wpm, accuracy FROM tests INNER JOIN users ON users.user_id = tests.user_id - WHERE users.user_id=:user_id AND users.secret=:secret", + WHERE users.user_id=? AND users.secret=?", user_id, secret ).fetch_all(&self.0).await?; + println!("{}", tests.len()); + let user_tests = tests.iter() .map(|test| Test { test_type: test.test_type.clone(), diff --git a/src/typing/user.rs b/src/typing/user.rs index 7c998f7..8006171 100644 --- a/src/typing/user.rs +++ b/src/typing/user.rs @@ -3,6 +3,8 @@ use rocket::{ State }; +use rand::{ distributions::Alphanumeric, Rng }; + use crate::typing::sql::{ Database, Test }; /// Struct representing the user @@ -18,9 +20,15 @@ pub struct User<'r> { /// Acessible from http://url/api/create_user #[post("/create_user", data = "")] pub async fn sign_up(user: Json>, database: &State) { - match database.create_user( user.username, &sha256::digest(user.password) ).await { + let secret: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(50) + .map(char::from) + .collect(); + + match database.create_user( user.username, &sha256::digest(user.password), &secret ).await { Err(why) => { println!("A database error occured during signup, {why}"); } - Ok(()) => { println!("Succesfully Signed up user {}", user.username); } + Ok(()) => { println!("Succesfully Signed up User: {}", user.username); } } } @@ -45,16 +53,16 @@ pub async fn get_tests(user_id: u32, secret: String, database: &State) /// which can be used to identify their tests etc. /// Accessible from http://url/api/login #[get("/login//")] -pub async fn login(username: &str, password: &str, database: &State) -> Option> { +pub async fn login(username: &str, password: &str, database: &State) -> Json> { match database.find_user(username, &sha256::digest(password)).await { Err(why) => { println!("A database error occured during login for {username}, {why}"); - None + Json(None) } Ok(user) => { match user { - None => None, - Some(user) => { Some(Json(LoginResponse { user_id: user.0, secret: user.1 })) } + None => Json(None), + Some(user) => { Json(Some(LoginResponse { user_id: user.0, secret: user.1 })) } } } } @@ -62,7 +70,7 @@ pub async fn login(username: &str, password: &str, database: &State) - #[derive(Serialize)] #[serde(crate = "rocket::serde")] -struct LoginResponse { +pub struct LoginResponse { user_id: u32, secret: String, } diff --git a/websites/Typing/api/api.js b/websites/Typing/api/api.js index 30eb95e..b9a2300 100644 --- a/websites/Typing/api/api.js +++ b/websites/Typing/api/api.js @@ -16,7 +16,8 @@ class API { constructor() { - this.url = "https://arlofilley.com/api/"; + // this.url = "https://arlofilley.com/api/"; + this.url = "../api"; // this is the url of the server // this may have to change later on } @@ -33,16 +34,7 @@ class API { * @param {int} accuracy * @param {int} userId */ - postTest( - pTestType, - pTestLength, - pTestTime, - pTestSeed, - pQuoteId, - pWpm, - pAccuracy, - pUserId - ) { + postTest(pTestType, pTestLength, pTestTime, pTestSeed, pQuoteId, pWpm, pAccuracy, pUserId) { const data = { 'test_type': pTestType, 'test_length': pTestLength, @@ -57,7 +49,7 @@ class API { const xhr = new XMLHttpRequest(); xhr.open( "POST", - this.url+"post_test" + `${this.url}/post_test/` ); xhr.send( @@ -174,16 +166,7 @@ class API { // there will be other tests here in later iterations but for now these tests should suffice - this.postTest( - testType, - testLength, - testTime, - testSeed, - quoteId, - wpm, - accuracy, - userId - ); + this.postTest(testType, testLength, testTime, testSeed, quoteId, wpm, accuracy, userId); } /** @@ -194,59 +177,73 @@ class API { * @param {String} password * @returns */ - createUser( - username, - password - ) { - console.log(username, password); + createUser( username, password ) { + console.log( username, password ); const user = { username: username, password: password }; const xhr = new XMLHttpRequest(); - xhr.open( - "POST", - `${this.url}create_user/` - ); + xhr.open( "POST", `${this.url}/create_user/` ); - xhr.send( - JSON.stringify(user) - ); + xhr.send( JSON.stringify(user) ); xhr.onload = () => { if (xhr.status === 500) { alert("Sorry, looks like your username isn't unique"); + console.error("Sorry, looks like your username isn't unique") } else { - this.login(username,password); + this.login(username, password); } }; } - login(pUsername, pPassword) { - if (localStorage.userId === null || localStorage.userId === 0 || localStorage.userId === undefined) { - let xhr = new XMLHttpRequest(); - xhr.open('GET', `${this.url}login/${pUsername}/${pPassword}`); - xhr.send(); - xhr.onload = () => { - user.userId = Number(xhr.response); - if (user.userId > 0) { - user.username = pUsername - localStorage.setItem("userId", user.userId); - localStorage.setItem("username", pUsername); - localStorage.setItem("password", pPassword); - } else { - user.username = "no one"; - user.password = "none"; - user.userId = 0; - user.tests = []; - } - }; - } else if (localStorage.userId > 0) { - user.userId = localStorage.userId; - user.username = localStorage.username; - user.password = localStorage.password; + + /** + * takes a validated name and password and sends + * a post request to make a user with the given + * username and password + * @param {String} username + * @param {String} password + * @param {boolean} initial + * @returns + */ + login(pUsername, pPassword, initial = false) { + // Variable Validation + if (pUsername == undefined || pPassword == undefined) { + return } + + // If Local Storage has the information we need there is no need to make a request to the server + if (localStorage.getItem("username") == pUsername) { + user.userId = localStorage.getItem("userId"); + user.secret = localStorage.getItem("secret"); + user.username = localStorage.getItem("username"); + + return + } + + let xhr = new XMLHttpRequest(); + xhr.open('GET', `${this.url}/login/${pUsername}/${pPassword}`); + xhr.send(); + xhr.onload = () => { + let response = JSON.parse(xhr.response); + + // If there is an error with the login we need + if (xhr.response == null) { + alert("Error Logging in, maybe check your password"); + return + } + + user.userId = response.user_id; + user.username = pUsername + user.secret = response.secret; + + localStorage.setItem("userId", user.userId); + localStorage.setItem("username", pUsername); + localStorage.setItem("secret", user.secret); + }; } logout() { @@ -265,8 +262,8 @@ class API { return; } let xhr = new XMLHttpRequest(); - let userId = Number(user.userId); - xhr.open('GET', `${this.url}get_user_tests/${userId}/`); + + xhr.open('GET', `${this.url}/get_tests/${user.userId}/${user.secret}`); xhr.send(); xhr.onload = () => { user.tests = JSON.parse(xhr.response); @@ -275,7 +272,7 @@ class API { getLeaderBoard() { let xhr = new XMLHttpRequest(); - xhr.open('GET', `${this.url}leaderboard/`); + xhr.open('GET', `${this.url}/leaderboard/`); xhr.send(); xhr.onload = () => { user.leaderboard = JSON.parse(xhr.response); @@ -284,7 +281,7 @@ class API { getTest() { let xhr = new XMLHttpRequest(); - xhr.open('GET', `${this.url}new_test/`); + xhr.open('GET', `${this.url}/new_test/`); xhr.send(); xhr.onload = () =>{ const effectiveWidth = (windowWidth - 200) / 13; diff --git a/websites/Typing/api/user.js b/websites/Typing/api/user.js index 5fc0442..587fe3a 100644 --- a/websites/Typing/api/user.js +++ b/websites/Typing/api/user.js @@ -16,8 +16,9 @@ class User { constructor() { this.username = "not logged in"; - this.password = "there"; this.userId = 0; + this.secret = ""; + this.leaderboard; this.time = 15;