I'm basically done lmao

This commit is contained in:
Talha Qamar 2024-11-23 14:27:33 +05:00
parent 3ea28ac7ff
commit 00979cacff
9 changed files with 304 additions and 58 deletions

2
Cargo.lock generated
View file

@ -10,6 +10,7 @@ dependencies = [
"rand",
"ring",
"rusqlite",
"serde",
"sha2",
"tokio",
"warp",
@ -142,6 +143,7 @@ dependencies = [
"iana-time-zone",
"js-sys",
"num-traits",
"serde",
"wasm-bindgen",
"windows-targets",
]

View file

@ -6,8 +6,9 @@ edition = "2021"
[dependencies]
tokio = { version = "1", features = ["full"] }
warp = "0.3"
chrono = "0.4"
chrono = { version = "0.4", features = ["serde"] }
rusqlite = { version = "0.32", features = ["chrono", "bundled"] }
sha2 = "0.10.8"
ring = "0.17.8"
rand = "0.8.5"
serde = { version = "1.0", features = ["derive"] }

View file

@ -4,18 +4,93 @@ AbleOS and related projects. The rest of this document is documentation for
the API
## GET /abuelo/user/:username
## GET /user/:username
Return information about a particular user in the following format:
```json
{
"success" : Boolean,
"message" : String,
"username" : String,
"email" : String
"creation" : String,
"is_premium" : Boolean,
}
```
- **success**: if the user is found successfully then the value returned is
true
- **message**: if success is false, contains an error message to give to the
user
- **username**:
- **creation**: if success is true, contains the creation date of the account in the format
YYYY-MM-DD HH:MM
- **is_premium**: if succuss is true, contains whether or not the account is premium
## POST /user/:username/update
Updates the records in the database
Request Format:
```json
{
"username" : String?,
"password" : String,
"new_password" : String?
}
```
(question marks indicate the value is nullable)
- **username**: The new username of the user
- **password**: The (plain-text currently but in future RSA encrypted) password of the user
- **new_password**: The new (plain-text currently but in future RSA encrypted) password of the user
Response Format:
```json
{
"success" : Boolean,
"message" : String,
}
```
- **success**: if the user is updated successfully then the value returned is
true
- **message**: if success is false, contains an error message to give to the user
## POST /user/create
Adds a user to the database
Request Format:
```json
{
"username" : String,
"password" : String,
}
```
- **username**: The username of the newly created user
- **password**: The (plain-text currently but in future RSA encrypted) password of the newly created user
Response Format:
```json
{
"success" : Boolean,
"message" : String,
}
```
- **success**: if the user is created successfully then the value returned is
true
- **message**: if success is false, contains an error message to give to the user
## POST /user/auth
Authorizes the user
Request Format:
```json
{
"username" : String,
"password" : String,
}
```
- **username**: The username of the user
- **password**: The (plain-text currently but in future RSA encrypted) password of the user
Response Format:
```json
{
"success" : Boolean,
"message" : String,
}
```
- **success**: if the user is authed successfully then the value returned is
true
- **message**: if success is false, contains an error message to give to the user

View file

@ -1,26 +1,36 @@
use chrono::{DateTime, Utc};
pub type UserID = u128;
pub type UserID = u64;
pub enum AccountStatusState {
Offline,
Away,
DoNotDisturb,
Online,
}
pub struct AccountStatus{
state: AccountStatusState,
tagline: String,
}
pub struct Account{
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub struct Account {
username: String,
user_id: UserID,
password_hash: String,
status: AccountStatus,
created_date: DateTime<Utc>,
creation_time: DateTime<Utc>,
// Donator role
premium: bool,
}
impl Account {
pub fn new(
username: String,
user_id: UserID,
creation_time: DateTime<Utc>,
premium: bool,
) -> Self {
Self {
username,
user_id,
creation_time,
premium,
}
}
pub fn premium(&self) -> bool {
self.premium
}
pub fn creation_time(&self) -> DateTime<Utc> {
self.creation_time
}
}

View file

@ -1,13 +1,40 @@
use std::{rc::Rc, sync::Arc};
use std::{fmt::Display, rc::Rc};
use chrono::{DateTime, Utc};
use rusqlite::{Connection, Result};
use sha2::{Digest, Sha256};
use crate::account::{Account, UserID};
pub struct Database {
conn: Connection,
}
#[derive(Debug)]
pub enum UserCreationError {
UsernameTaken,
DBError(rusqlite::Error),
}
impl From<rusqlite::Error> for UserCreationError {
fn from(value: rusqlite::Error) -> Self {
Self::DBError(value)
}
}
impl std::error::Error for UserCreationError {}
impl Display for UserCreationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
UserCreationError::UsernameTaken => {
write!(f, "Username was taken")
}
UserCreationError::DBError(e) => {
write!(f, "DBError: {}", e)
}
}
}
}
impl Database {
pub fn new() -> Self {
let conn = Connection::open("user_db.db3").unwrap();
@ -16,63 +43,92 @@ impl Database {
// That's fine and we can just let it error and the other queries will
// use the existing table instead
// NOTE: Look at the other possible errors here
let _ = conn.execute(
let _val = conn.execute(
"CREATE TABLE users (
user_id INTEGER PRIMARY KEY,
username TINYTEXT NOT NULL,
password_hash TINYTEXT NOT NULL,
creation_time DATETIME NOT NULL
creation_time DATETIME NOT NULL,
is_premium BOOL NOT NULL,
random_value INTEGER NOT NULL
)",
());
(),
);
Self { conn }
}
pub fn add_user(&self, username : &str, password : &str) -> Result<()>{
let password_hash = self.hash_password(username, password)?;
pub fn get_user(&self, username: &str) -> Result<Account> {
let (user_id, creation_time, premium): (UserID, DateTime<Utc>, bool) =
self.conn.query_row(
"SELECT username, user_id, creation_time, is_premium FROM users WHERE username=?1",
[username],
|row| {
let user_id = row.get(1)?;
let creation_time = row.get(2)?;
let premium = row.get(3)?;
Ok((user_id, creation_time, premium))
},
)?;
Ok(Account::new(
username.to_string(),
user_id,
creation_time,
premium,
))
}
pub fn add_user(&self, username: &str, password: &str) -> Result<(), UserCreationError> {
if self.get_user(username).is_ok() {
return Err(UserCreationError::UsernameTaken);
}
let creation_time = Utc::now();
let num = rand::random::<u64>();
let password_hash = self.hash_password(password, creation_time, num);
self.conn.execute(
"INSERT INTO person (
"INSERT INTO users (
username,
password_hash,
creation_time,
is_premium,
random_value)
VALUES (?1, ?2, ?3, ?4, ?5)",
(username, password_hash, Utc::now(), false, rand::random::<u64>()),
(username, password_hash, creation_time, false, num),
)?;
Ok(())
}
fn hash_password(&self, username : &str, password : &str) -> Result<String>{
let (creation_time, num) : (DateTime<Utc>, u64) = self.conn.query_row(
"SELECT creation_time, random_value, name FROM person WHERE name=?1",
[username],
|row|{
let creation_time = row.get(0)?;
let num = row.get(1)?;
Ok((creation_time, num))
},
)?;
fn hash_password(&self, password: &str, creation_time: DateTime<Utc>, num: u64) -> String {
let mut hasher = Sha256::new();
hasher.update(password);
hasher.update(creation_time.format("%Y-%m-%d-%H-%M").to_string());
hasher.update(num.to_string());
Ok(format!("{:x}", hasher.finalize()))
format!("{:x}", hasher.finalize())
}
pub fn check_login(&self, username : &str, password : &str) -> bool{
let saved_password_hash : Result<Rc<str>> = self.conn.query_row(
"SELECT password_hash, name FROM person WHERE name=?1",
pub fn check_login(&self, username: &str, password: &str) -> bool {
let result = self.conn.query_row(
"SELECT creation_time, random_value, username FROM users WHERE username=?1",
[username],
|row| {
let creation_time = row.get(0)?;
let num = row.get(1)?;
Ok((creation_time, num))
},
);
if result.is_err() {
return false;
}
let (creation_time, num) = result.unwrap();
let saved_password_hash: Result<Rc<str>> = self.conn.query_row(
"SELECT password_hash, username FROM users WHERE username=?1",
[username],
|row| row.get::<usize, Rc<str>>(0),
);
if let Err(_) = saved_password_hash {
if saved_password_hash.is_err() {
return false;
}
*saved_password_hash.unwrap() == *password
*saved_password_hash.unwrap() == self.hash_password(password, creation_time, num)
}
}

1
src/handlers.rs Normal file
View file

@ -0,0 +1 @@

View file

@ -1,14 +1,11 @@
use warp::Filter;
use routes::get_routes;
mod account;
mod database;
mod handlers;
mod routes;
#[tokio::main]
async fn main() {
// GET /hello/warp => 200 OK with body "Hello, warp!"
let hello = warp::path!("hello" / String)
.map(|name| format!("Hello, {}!", name));
warp::serve(hello)
.run(([127, 0, 0, 1], 3030))
.await;
warp::serve(get_routes()).run(([127, 0, 0, 1], 3030)).await;
}

104
src/routes.rs Normal file
View file

@ -0,0 +1,104 @@
use chrono::{DateTime, Utc};
use warp::Filter;
use crate::database::Database;
pub fn get_routes() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
create_user().or(auth_user()).or(get_user())
}
#[derive(serde::Serialize, serde::Deserialize)]
struct UserCreateRequest {
username: String,
password: String,
}
#[derive(serde::Serialize, serde::Deserialize)]
struct UserCreateResponse {
success: bool,
message: String,
}
fn create_user() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::post()
.and(warp::path!("user" / "create"))
.and(warp::body::json())
.map(|body: UserCreateRequest| {
let db = Database::new();
let result = db.add_user(&body.username, &body.password);
let reply = if result.is_ok() {
UserCreateResponse {
success: true,
message: "".to_string(),
}
} else {
UserCreateResponse {
success: false,
message: format!("{:#?}", result.unwrap_err()),
}
};
warp::reply::json(&reply)
})
}
#[derive(serde::Serialize, serde::Deserialize)]
struct UserGetResponse {
success: bool,
message: String,
creation_time: Option<DateTime<Utc>>,
premium: Option<bool>,
}
fn get_user() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::get()
.and(warp::path!("user" / String))
.map(|username: String| {
let db = Database::new();
let acc = db.get_user(&username);
let reply = if acc.is_err() {
UserGetResponse {
success: false,
message: format!("{}", acc.unwrap_err()),
creation_time: None,
premium: None,
}
} else {
let acc = acc.unwrap();
UserGetResponse {
success: false,
message: "".to_string(),
creation_time: Some(acc.creation_time()),
premium: Some(acc.premium()),
}
};
warp::reply::json(&reply)
})
}
#[derive(serde::Serialize, serde::Deserialize)]
struct UserAuthRequest {
username: String,
password: String,
}
#[derive(serde::Serialize, serde::Deserialize)]
struct UserAuthResponse {
success: bool,
message: String,
}
fn auth_user() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::post()
.and(warp::path!("user" / "auth"))
.and(warp::body::json())
.map(|body: UserAuthRequest| {
let db = Database::new();
let reply = if db.check_login(&body.username, &body.password) {
UserAuthResponse {
success: true,
message: "".to_string(),
}
} else {
UserAuthResponse {
success: false,
message: "Username or Password is invalid".to_string(),
}
};
warp::reply::json(&reply)
})
}

BIN
user_db.db3 Normal file

Binary file not shown.