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",
|
"rand",
|
||||||
"ring",
|
"ring",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
|
"serde",
|
||||||
"sha2",
|
"sha2",
|
||||||
"tokio",
|
"tokio",
|
||||||
"warp",
|
"warp",
|
||||||
|
@ -142,6 +143,7 @@ dependencies = [
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
"serde",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-targets",
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,8 +6,9 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
warp = "0.3"
|
warp = "0.3"
|
||||||
chrono = "0.4"
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
rusqlite = { version = "0.32", features = ["chrono", "bundled"] }
|
rusqlite = { version = "0.32", features = ["chrono", "bundled"] }
|
||||||
sha2 = "0.10.8"
|
sha2 = "0.10.8"
|
||||||
ring = "0.17.8"
|
ring = "0.17.8"
|
||||||
rand = "0.8.5"
|
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
|
the API
|
||||||
|
|
||||||
|
|
||||||
## GET /abuelo/user/:username
|
## GET /user/:username
|
||||||
Return information about a particular user in the following format:
|
Return information about a particular user in the following format:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"success" : Boolean,
|
"success" : Boolean,
|
||||||
"message" : String,
|
"message" : String,
|
||||||
"username" : String,
|
"creation" : String,
|
||||||
"email" : String
|
"is_premium" : Boolean,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- **success**: if the user is found successfully then the value returned is
|
- **success**: if the user is found successfully then the value returned is
|
||||||
true
|
true
|
||||||
- **message**: if success is false, contains an error message to give to the
|
- **message**: if success is false, contains an error message to give to the
|
||||||
user
|
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};
|
use chrono::{DateTime, Utc};
|
||||||
|
|
||||||
pub type UserID = u128;
|
pub type UserID = u64;
|
||||||
|
|
||||||
pub enum AccountStatusState {
|
#[derive(serde::Serialize, serde::Deserialize, Debug)]
|
||||||
Offline,
|
pub struct Account {
|
||||||
Away,
|
|
||||||
DoNotDisturb,
|
|
||||||
Online,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct AccountStatus{
|
|
||||||
state: AccountStatusState,
|
|
||||||
tagline: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub struct Account{
|
|
||||||
username: String,
|
username: String,
|
||||||
user_id: UserID,
|
user_id: UserID,
|
||||||
password_hash: String,
|
creation_time: DateTime<Utc>,
|
||||||
status: AccountStatus,
|
|
||||||
created_date: DateTime<Utc>,
|
|
||||||
// Donator role
|
// Donator role
|
||||||
premium: bool,
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
110
src/database.rs
110
src/database.rs
|
@ -1,78 +1,134 @@
|
||||||
use std::{rc::Rc, sync::Arc};
|
use std::{fmt::Display, rc::Rc};
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use rusqlite::{Connection, Result};
|
use rusqlite::{Connection, Result};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
|
use crate::account::{Account, UserID};
|
||||||
|
|
||||||
pub struct Database {
|
pub struct Database {
|
||||||
conn: Connection,
|
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 {
|
impl Database {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let conn = Connection::open("user_db.db3").unwrap();
|
let conn = Connection::open("user_db.db3").unwrap();
|
||||||
|
|
||||||
// If this returns an error; it's prolly cuz the table already exists.
|
// If this returns an error; it's prolly cuz the table already exists.
|
||||||
// That's fine and we can just let it error and the other queries will
|
// That's fine and we can just let it error and the other queries will
|
||||||
// use the existing table instead
|
// use the existing table instead
|
||||||
// NOTE: Look at the other possible errors here
|
// NOTE: Look at the other possible errors here
|
||||||
let _ = conn.execute(
|
let _val = conn.execute(
|
||||||
"CREATE TABLE users (
|
"CREATE TABLE users (
|
||||||
user_id INTEGER PRIMARY KEY,
|
user_id INTEGER PRIMARY KEY,
|
||||||
username TINYTEXT NOT NULL,
|
username TINYTEXT NOT NULL,
|
||||||
password_hash TINYTEXT NOT NULL,
|
password_hash TINYTEXT NOT NULL,
|
||||||
creation_time DATETIME NOT NULL
|
creation_time DATETIME NOT NULL,
|
||||||
is_premium BOOL NOT NULL,
|
is_premium BOOL NOT NULL,
|
||||||
random_value INTEGER NOT NULL
|
random_value INTEGER NOT NULL
|
||||||
)",
|
)",
|
||||||
());
|
(),
|
||||||
|
);
|
||||||
|
|
||||||
Self { conn }
|
Self { conn }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_user(&self, username : &str, password : &str) -> Result<()>{
|
pub fn get_user(&self, username: &str) -> Result<Account> {
|
||||||
let password_hash = self.hash_password(username, password)?;
|
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(
|
self.conn.execute(
|
||||||
"INSERT INTO person (
|
"INSERT INTO users (
|
||||||
username,
|
username,
|
||||||
password_hash,
|
password_hash,
|
||||||
creation_time,
|
creation_time,
|
||||||
is_premium,
|
is_premium,
|
||||||
random_value)
|
random_value)
|
||||||
VALUES (?1, ?2, ?3, ?4, ?5)",
|
VALUES (?1, ?2, ?3, ?4, ?5)",
|
||||||
(username, password_hash, Utc::now(), false, rand::random::<u64>()),
|
(username, password_hash, creation_time, false, num),
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_password(&self, username : &str, password : &str) -> Result<String>{
|
fn hash_password(&self, password: &str, creation_time: DateTime<Utc>, num: u64) -> 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))
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
hasher.update(password);
|
hasher.update(password);
|
||||||
hasher.update(creation_time.format("%Y-%m-%d-%H-%M").to_string());
|
hasher.update(creation_time.format("%Y-%m-%d-%H-%M").to_string());
|
||||||
hasher.update(num.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{
|
pub fn check_login(&self, username: &str, password: &str) -> bool {
|
||||||
let saved_password_hash : Result<Rc<str>> = self.conn.query_row(
|
let result = self.conn.query_row(
|
||||||
"SELECT password_hash, name FROM person WHERE name=?1",
|
"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],
|
[username],
|
||||||
|row| row.get::<usize, Rc<str>>(0),
|
|row| row.get::<usize, Rc<str>>(0),
|
||||||
);
|
);
|
||||||
if let Err(_) = saved_password_hash {
|
if saved_password_hash.is_err() {
|
||||||
return false;
|
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 account;
|
||||||
mod database;
|
mod database;
|
||||||
|
mod handlers;
|
||||||
|
mod routes;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
// GET /hello/warp => 200 OK with body "Hello, warp!"
|
warp::serve(get_routes()).run(([127, 0, 0, 1], 3030)).await;
|
||||||
let hello = warp::path!("hello" / String)
|
|
||||||
.map(|name| format!("Hello, {}!", name));
|
|
||||||
|
|
||||||
warp::serve(hello)
|
|
||||||
.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