I'm basically done lmao
This commit is contained in:
parent
3ea28ac7ff
commit
00979cacff
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -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",
|
||||
]
|
||||
|
|
|
@ -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"] }
|
||||
|
|
83
README.md
83
README.md
|
@ -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
|
||||
|
|
|
@ -1,26 +1,36 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
|
||||
pub type UserID = u128;
|
||||
|
||||
pub enum AccountStatusState {
|
||||
Offline,
|
||||
Away,
|
||||
DoNotDisturb,
|
||||
Online,
|
||||
}
|
||||
|
||||
pub struct AccountStatus{
|
||||
state: AccountStatusState,
|
||||
tagline: String,
|
||||
}
|
||||
|
||||
pub type UserID = u64;
|
||||
|
||||
#[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
|
||||
}
|
||||
}
|
||||
|
|
104
src/database.rs
104
src/database.rs
|
@ -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",
|
||||
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());
|
||||
format!("{:x}", hasher.finalize())
|
||||
}
|
||||
|
||||
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))
|
||||
},
|
||||
)?;
|
||||
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()))
|
||||
);
|
||||
if result.is_err() {
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn check_login(&self, username : &str, password : &str) -> bool{
|
||||
let (creation_time, num) = result.unwrap();
|
||||
let saved_password_hash: Result<Rc<str>> = self.conn.query_row(
|
||||
"SELECT password_hash, name FROM person WHERE name=?1",
|
||||
"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
1
src/handlers.rs
Normal file
|
@ -0,0 +1 @@
|
|||
|
13
src/main.rs
13
src/main.rs
|
@ -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
104
src/routes.rs
Normal 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
BIN
user_db.db3
Normal file
Binary file not shown.
Loading…
Reference in a new issue