From 70f604e4f0ee9272da9fb25a5b6e9b354e15dabf Mon Sep 17 00:00:00 2001 From: Erin Nova Date: Thu, 22 Jul 2021 15:29:01 -0400 Subject: [PATCH 01/10] Add different message types, and check for them --- src/chat.rs | 34 +++++++++++----------------------- src/message.rs | 10 +++++++++- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index 7d1d3f4..3d650dc 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -4,7 +4,7 @@ use once_cell::sync::Lazy; use std::sync::Mutex; use crate::file_io::db_read; use rocket::http::{Cookie, Cookies}; -use crate::message::{Message, MessageInput}; +use crate::message::{Message, MessageInput, MessageType}; use rocket_contrib::json::{Json, JsonValue}; use chrono::prelude::*; use uuid::Uuid; @@ -23,35 +23,23 @@ pub fn fetch_messages() -> Json> { // Create full message object and write to file fn create_message(message: Json, file: &str, user: &User) -> JsonValue { + let event_type = match message.body.chars().nth(0).unwrap() { + '/' => MessageType::Command, + ':' => MessageType::Emote, + _ => MessageType::Normal, + }; + // create full message object - // append message to file - - // Create proper datetime format out of string - let date_split: Vec<&str> = message.date.split("-").collect(); - - let year: i32 = match date_split[0].trim().parse() { // extract year - Err(why) => panic!("could not extract year from given date: {}", why), - Ok(year) => year, - }; - - let month: u32 = match date_split[1].trim().parse() { // extract month - Err(why) => panic!("could not extract month from given date: {}", why), - Ok(month) => month, - }; - - let day: u32 = match date_split[2].trim().parse() { // extract day - Err(why) => panic!("could not extract day from given date: {}", why), - Ok(month) => month, - }; - - let date: DateTime = Utc.ymd(year, month, day).and_hms(9, 10, 11); let message_obj: Message = Message { id: Uuid::new_v4(), user: user.name.to_owned(), body: message.body.to_string(), - created_at: date, + event_type: event_type, + created_at: Utc.timestamp(1_500_000_000, 0), }; info!("created mesage: {:?}", message_obj); + + // append message to file let mut messages = MESSAGES.lock().unwrap(); messages.push(message_obj.to_owned()); return json!({ diff --git a/src/message.rs b/src/message.rs index dd2d70d..d7159ab 100644 --- a/src/message.rs +++ b/src/message.rs @@ -7,12 +7,20 @@ use uuid::Uuid; pub struct MessageInput<'r> { pub name: &'r str, pub body: &'r str, - pub date: &'r str, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum MessageType { + Normal, + Announcement, + Emote, + Command, } #[derive(Debug, Deserialize, Serialize, Clone)] pub struct Message { pub id: Uuid, + pub event_type: MessageType, pub user: String, pub body: String, pub created_at: DateTime, From 6328e05b161399c1b6f7ca03f8759972fafc1d5e Mon Sep 17 00:00:00 2001 From: Erin Nova Date: Thu, 22 Jul 2021 16:20:49 -0400 Subject: [PATCH 02/10] Add date info to message --- CHANGELOG.md | 6 ++++++ Cargo.toml | 2 +- README.md | 4 ++-- src/chat.rs | 21 ++++++++++++++++++++- src/message.rs | 1 + 5 files changed, 30 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 593c527..dcf02de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +### 0.5.1 +- `/api/logout` API to delete session token +- Add basic support for different message types +- Messages now use unix timestamps + - Backend finds timestamp + ## 0.5.0 - Most actions should now fail on a NULL token - Cookie should now expire after a week diff --git a/Cargo.toml b/Cargo.toml index 26d401a..33c85f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pogchat" -version = "0.5.0" +version = "0.5.1" authors = ["Erin Nova "] edition = "2018" diff --git a/README.md b/README.md index 6cd6bdd..d8f40de 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Returns status & reason json. ## Chat Documentation -`POST /api/message/send {"name":"username","body":"message body","date":"yyy-mm-dd","token":"USER_TOKEN"}` Post a json message. +`POST /api/message/send {"name":"username","body":"message body"}` Post a json message. Returns status & reason json. `GET /api/message/messages.json` Get a json file of all the messages @@ -83,5 +83,5 @@ Whenever user sends a message, client will send message & token and backend will - [ ] User management (banning, etc.) - [ ] Blacklist words from chat/names - [ ] More advanced chat features - - [ ] Different types of message events? eg. default, announcement, command + - [x] Different types of message events? eg. default, announcement, command - [ ] Emote support? diff --git a/src/chat.rs b/src/chat.rs index 3d650dc..fcc2d9c 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -9,6 +9,7 @@ use rocket_contrib::json::{Json, JsonValue}; use chrono::prelude::*; use uuid::Uuid; use crate::user::User; +use std::time::{Duration, SystemTime}; static MESSAGES: Lazy>> = Lazy::new(|| Mutex::new(Vec::new())); @@ -35,9 +36,10 @@ fn create_message(message: Json, file: &str, user: &User) -> JsonV user: user.name.to_owned(), body: message.body.to_string(), event_type: event_type, - created_at: Utc.timestamp(1_500_000_000, 0), + created_at: Utc::now(), }; info!("created mesage: {:?}", message_obj); + info!("Date is: {}", message_obj.created_at.to_rfc2822()); // append message to file let mut messages = MESSAGES.lock().unwrap(); @@ -95,3 +97,20 @@ pub fn send_message(message: Json>, mut cookies: Cookies) -> Js }; check_token(token, message) } + +// Delete a message +/* +#[post("/message/delete", format = "json", data = "")] +pub fn delete_message(message: Json>, mut cookies: Cookies) -> JsonValue { + let token = match cookies.get_private("token") { + None => { + warn!("couldn't get token cookie!"); + return json!({ + "status": "fail", + "reason": "could not read cookie", + }); + }, + Some(token) => token, + }; +} +*/ diff --git a/src/message.rs b/src/message.rs index d7159ab..58f1af3 100644 --- a/src/message.rs +++ b/src/message.rs @@ -7,6 +7,7 @@ use uuid::Uuid; pub struct MessageInput<'r> { pub name: &'r str, pub body: &'r str, + pub date: i64, } #[derive(Clone, Debug, Serialize, Deserialize)] From 49d307a3b6dbd747ac8c26ec1186346c635123d1 Mon Sep 17 00:00:00 2001 From: Erin Nova Date: Thu, 22 Jul 2021 17:17:55 -0400 Subject: [PATCH 03/10] Add user roles --- Cargo.lock | 28 ++++++++++++++-------------- README.md | 4 ++-- src/auth.rs | 22 +++++++++++++++++----- src/chat.rs | 3 +-- src/file_io.rs | 22 ---------------------- src/user.rs | 9 ++++++++- 6 files changed, 42 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0845d70..9aef55b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -228,9 +228,9 @@ dependencies = [ [[package]] name = "crypto-mac" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4857fd85a0c34b3c3297875b747c1e02e06b6a0ea32dd892d8192b9ce0813ea6" +checksum = "bff07008ec701e8028e2ceb8f83f0e4274ee62bd2dbdc4fefff2e9a91824081a" dependencies = [ "generic-array", "subtle", @@ -301,9 +301,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77b705829d1e87f762c2df6da140b26af5839e1033aa84aa5f56bb688e4e1bdb" +checksum = "b394ed3d285a429378d3b384b9eb1285267e7df4b166df24b7a6939a04dc392e" dependencies = [ "instant", ] @@ -572,9 +572,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.97" +version = "0.2.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" +checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790" [[package]] name = "lock_api" @@ -807,7 +807,7 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pogchat" -version = "0.5.0" +version = "0.5.1" dependencies = [ "bincode", "chrono", @@ -1062,7 +1062,7 @@ checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" dependencies = [ "proc-macro2 1.0.27", "quote 1.0.9", - "syn 1.0.73", + "syn 1.0.74", ] [[package]] @@ -1148,9 +1148,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.73" +version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" +checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" dependencies = [ "proc-macro2 1.0.27", "quote 1.0.9", @@ -1178,9 +1178,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" +checksum = "848a1e1181b9f6753b5e96a092749e29b11d19ede67dfbbd6c7dc7e0f49b5338" dependencies = [ "tinyvec_macros", ] @@ -1259,9 +1259,9 @@ checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "universal-hash" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" dependencies = [ "generic-array", "subtle", diff --git a/README.md b/README.md index d8f40de..fd2b5cb 100644 --- a/README.md +++ b/README.md @@ -77,10 +77,10 @@ Whenever user sends a message, client will send message & token and backend will - [x] Fail on NULL token - [x] Pronouns - [x] Set pronouns - - [ ] Change pronouns - - [ ] Multiple sets of pronouns + - [x] Change pronouns - [ ] Some form of plural support? - [ ] User management (banning, etc.) + - [ ] User roles (admin, mod, etc.) - [ ] Blacklist words from chat/names - [ ] More advanced chat features - [x] Different types of message events? eg. default, announcement, command diff --git a/src/auth.rs b/src/auth.rs index 5a11db3..37fefee 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,7 +1,7 @@ extern crate log; use crate::file_io::{db_add, db_write, db_read}; -use rocket::http::{Cookie, Cookies, SameSite}; -use crate::user::User; +use rocket::http::{Cookie, Cookies}; +use crate::user::{User, UserType}; use rocket_contrib::json::{Json, JsonValue}; use random_string::generate; extern crate sha1; @@ -40,13 +40,14 @@ pub fn register_user(name: String, pin: i32, pronouns: String) -> JsonValue { } let pin_hashed = sha1::Sha1::from(&pin.to_string()).digest().to_string(); // hash the pin - + let new_user: User = User { name: name.to_string().to_lowercase(), - pin_hashed: pin_hashed, + pin_hashed, pronouns: pronouns.to_string().to_lowercase(), session_token: "NULL".to_string(), - }; // append the user to the vec + role: UserType::Normal, + }; /* // append to the json file @@ -409,6 +410,7 @@ pub fn get_user(name: String) -> JsonValue { "user": { "name": user.name, "pronouns": user.pronouns, + "role": user.role, }, }), None => json!({ @@ -417,3 +419,13 @@ pub fn get_user(name: String) -> JsonValue { }), } } +/* +#[derive(Deserialize, Debug)] +pub struct ModerationAction { +} + +/* User Management */ +#[post("/mod", format = "json", data = "")] +pub fn moderation_actions(data: Json>, mut cookies: Cookies) -> JsonValue { + +}*/ diff --git a/src/chat.rs b/src/chat.rs index fcc2d9c..7912a65 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -9,7 +9,6 @@ use rocket_contrib::json::{Json, JsonValue}; use chrono::prelude::*; use uuid::Uuid; use crate::user::User; -use std::time::{Duration, SystemTime}; static MESSAGES: Lazy>> = Lazy::new(|| Mutex::new(Vec::new())); @@ -33,9 +32,9 @@ fn create_message(message: Json, file: &str, user: &User) -> JsonV // create full message object let message_obj: Message = Message { id: Uuid::new_v4(), + event_type, user: user.name.to_owned(), body: message.body.to_string(), - event_type: event_type, created_at: Utc::now(), }; info!("created mesage: {:?}", message_obj); diff --git a/src/file_io.rs b/src/file_io.rs index d173717..ab5161b 100644 --- a/src/file_io.rs +++ b/src/file_io.rs @@ -97,28 +97,6 @@ pub fn write_json(users_list: &Vec) -> Result<()> { Ok(()) } -// test sled funtion -pub fn test_sled() { - // create test user - let user = User { - name: "erin".to_string(), - pin_hashed: "nyaa".to_string(), - pronouns: "she/her".to_string(), - session_token: "NULL".to_string(), - }; - // open database - let db: sled::Db = sled::open("my_db").unwrap(); - let bytes = bincode::serialize(&user).unwrap(); - db.insert(&user.name, bytes).unwrap(); - match db.get(user.name).unwrap() { - Some(bytes) => { - let read_user: User = bincode::deserialize(&bytes).unwrap(); - println!("username: {}, pronouns: {}", read_user.name, read_user.pronouns); - }, - None => (), - } -} - // add a user to the database pub fn db_add(user: &User) { let db: sled::Db = sled::open("users_db").unwrap(); diff --git a/src/user.rs b/src/user.rs index 915bd4a..c886fef 100644 --- a/src/user.rs +++ b/src/user.rs @@ -1,11 +1,18 @@ use serde::{Deserialize, Serialize}; +#[derive(Clone, Serialize, Deserialize, Debug)] +pub enum UserType { + Normal, + Moderator, + Admin, +} + // Struct to store basic user data #[derive(Clone, Serialize, Deserialize, Debug)] pub struct User { pub name: String, pub pin_hashed: String, pub pronouns: String, - #[serde(rename = "sessionToken")] pub session_token: String, + pub role: UserType, } From d13e5cf8aec7e95b5a616080556acd1b2f0c82a5 Mon Sep 17 00:00:00 2001 From: Erin Nova Date: Thu, 22 Jul 2021 17:30:06 -0400 Subject: [PATCH 04/10] Add more goals to readme --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fd2b5cb..97d8e3a 100644 --- a/README.md +++ b/README.md @@ -78,10 +78,14 @@ Whenever user sends a message, client will send message & token and backend will - [x] Pronouns - [x] Set pronouns - [x] Change pronouns +- [ ] make changed_event Enum, use token instead of pin - [ ] Some form of plural support? - [ ] User management (banning, etc.) - - [ ] User roles (admin, mod, etc.) + - [x] User roles (admin, mod, etc.) + - [ ] Commands to affect users - [ ] Blacklist words from chat/names - [ ] More advanced chat features - [x] Different types of message events? eg. default, announcement, command + - [ ] Types will display differently? eg. announcements pinned to top? + - [ ] Have different commands? - [ ] Emote support? From 36846b2eabab5bf446d764c455aa1e72937f8dc0 Mon Sep 17 00:00:00 2001 From: Erin Nova Date: Thu, 22 Jul 2021 17:31:18 -0400 Subject: [PATCH 05/10] Add structs and enums in src/auth.rs into seperate file --- src/auth.rs | 31 +++++++++++-------------------- src/user.rs | 46 +++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 52 insertions(+), 25 deletions(-) diff --git a/src/auth.rs b/src/auth.rs index 37fefee..eef27f3 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,7 +1,7 @@ extern crate log; use crate::file_io::{db_add, db_write, db_read}; use rocket::http::{Cookie, Cookies}; -use crate::user::{User, UserType}; +use crate::user::*; use rocket_contrib::json::{Json, JsonValue}; use random_string::generate; extern crate sha1; @@ -133,12 +133,6 @@ pub fn check_token(name: String, mut cookies: Cookies) -> JsonValue { }); } -// logout event struct -#[derive(Deserialize, Debug)] -pub struct LogoutEvent { - pub name: String, -} - // Logout API #[post("/logout", format = "json", data = "")] pub fn logout(info: Json, mut cookies: Cookies) -> JsonValue { @@ -232,14 +226,6 @@ pub fn check_pin(mut cookies: Cookies, name: String, pin: i32) -> JsonValue { }); } -#[derive(Deserialize, Debug)] -pub struct ChangeEvent { - pub name: String, - pub pin: String, - pub changed_event: String, - pub new_event: String, -} - // Change info about a user #[post("/users/change", format = "json", data = "")] pub fn change_info(input: Json, mut cookies: Cookies) -> JsonValue { @@ -420,12 +406,17 @@ pub fn get_user(name: String) -> JsonValue { } } /* -#[derive(Deserialize, Debug)] -pub struct ModerationAction { -} - /* User Management */ #[post("/mod", format = "json", data = "")] pub fn moderation_actions(data: Json>, mut cookies: Cookies) -> JsonValue { - + let token = match cookies.get_private("token") { + None => { + warn!("couldn't get token cookie!"); + return json!({ + "status": "fail", + "reason": "could not read cookie", + }); + }, + Some(token) => token, + }; }*/ diff --git a/src/user.rs b/src/user.rs index c886fef..5c799c8 100644 --- a/src/user.rs +++ b/src/user.rs @@ -1,5 +1,7 @@ use serde::{Deserialize, Serialize}; +/* User Data */ +// enum of different user types #[derive(Clone, Serialize, Deserialize, Debug)] pub enum UserType { Normal, @@ -10,9 +12,43 @@ pub enum UserType { // Struct to store basic user data #[derive(Clone, Serialize, Deserialize, Debug)] pub struct User { - pub name: String, - pub pin_hashed: String, - pub pronouns: String, - pub session_token: String, - pub role: UserType, + pub name: String, // unique username + pub pin_hashed: String, // sha1 hash of the pin + pub pronouns: String, // user's pronouns + pub session_token: String, // generated session token + pub role: UserType, // type/role of user +} + +/* Moderation Data */ +// enum of different moderator actions +#[derive(Deserialize, Debug)] +pub enum ModActions { + Kick, // Log the user out of their current session + Ban, // Remove the user + Demote, // Demote a user to a lower role + Premote, // Premote a user to a higher role +} + +// struct to use for json input +#[derive(Deserialize, Debug)] +pub struct ModerationAction { + pub name: String, // name of the moderator + pub action: ModActions, // what action to take + pub target: User, // who to take the action on +} + +/* Miscellaneous Events */ +// logout event struct +#[derive(Deserialize, Debug)] +pub struct LogoutEvent { + pub name: String, +} + +// change info event struct +#[derive(Deserialize, Debug)] +pub struct ChangeEvent { + pub name: String, // name of the user + pub pin: String, // user's pin + pub changed_event: String, // which event to change + pub new_event: String, // the new value for the event } From 786a4100f2aa7ff65ba111f1f6e31adf1fc7edc4 Mon Sep 17 00:00:00 2001 From: Erin Nova Date: Fri, 23 Jul 2021 07:55:24 -0400 Subject: [PATCH 06/10] Add basic support for moderation actions, delete previous user before changing name --- README.md | 2 +- src/auth.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++++---- src/file_io.rs | 15 ++++++++++++++ src/main.rs | 3 ++- src/user.rs | 4 ++-- 5 files changed, 72 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 97d8e3a..2c7b2fb 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ Whenever user sends a message, client will send message & token and backend will - [x] Basic messaging system - [x] Finish up `chat::create_message()` - [x] Create `chat::fetch_messages()` - - [ ] Use unix timestamp for date + - [x] Use unix timestamp for date - [ ] Create `chat::delete_message()` - [x] Switch to using sled database to store users - [ ] Error handling diff --git a/src/auth.rs b/src/auth.rs index eef27f3..4a648ee 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,5 +1,5 @@ extern crate log; -use crate::file_io::{db_add, db_write, db_read}; +use crate::file_io::{db_add, db_write, db_read, db_read_user, db_remove}; use rocket::http::{Cookie, Cookies}; use crate::user::*; use rocket_contrib::json::{Json, JsonValue}; @@ -256,6 +256,8 @@ pub fn change_info(input: Json, mut cookies: Cookies) -> JsonValue if input.name.to_lowercase() == users[i].name { // if user found... if token.value() == users[i].session_token { // & if token matches: if input.changed_event == "name" { + // remove the user first + db_remove(&users[i]); // change the name users[i].name = input.new_event.clone(); info!("changed name of {} to {}", input.name, input.new_event); @@ -405,10 +407,10 @@ pub fn get_user(name: String) -> JsonValue { }), } } -/* + /* User Management */ #[post("/mod", format = "json", data = "")] -pub fn moderation_actions(data: Json>, mut cookies: Cookies) -> JsonValue { +pub fn moderation_actions(data: Json, mut cookies: Cookies) -> JsonValue { let token = match cookies.get_private("token") { None => { warn!("couldn't get token cookie!"); @@ -419,4 +421,50 @@ pub fn moderation_actions(data: Json>, mut cookies: Cookies }, Some(token) => token, }; -}*/ + let mut user = db_read_user(&data.name.to_lowercase()); + + let mut users: Vec = Vec::new(); + // loop through vector + for i in &users { + if i.name == data.name.to_lowercase() { // found the user! + if token.value() == "NULL" { // fail if token is NULL + warn!("NULL token!"); + return json!({ + "status": "fail", + "reason": "NULL token", + }); + } else if i.session_token == token.value() { // if token matches + if i.role == UserType::Normal { + match data.action { + ModActions::Kick => { + info!("kicked user {}", data.target) + }, + ModActions::Ban => info!("banned user {}", data.target), + _ => info!("F"), + }; + return json!({ + "status": "ok", + "reason": "completed action", + }); + } else { + warn!("user does not have sufficient permissions to perform that action!"); + return json!({ + "status": "fail", + "reason": "insufficient permissions", + }); + }; + } else { + warn!("token does not match!"); + return json!({ + "status": "fail", + "reason": "token does not match", + }) + }; + }; + }; + warn!("user not found"); + json!({ + "status": "fail", + "reason": "user not found" + }) +} diff --git a/src/file_io.rs b/src/file_io.rs index ab5161b..1460e8e 100644 --- a/src/file_io.rs +++ b/src/file_io.rs @@ -116,6 +116,12 @@ pub fn db_write(users_list: &Vec) { info!("wrote all users to db"); } +// remove a user from the database +pub fn db_remove(user: &User) { + let db: sled::Db = sled::open("users_db").unwrap(); + db.remove(&user.name); +} + // read all users from the database pub fn db_read() -> Vec { let db: sled::Db = sled::open("users_db").unwrap(); @@ -127,3 +133,12 @@ pub fn db_read() -> Vec { } return users; } + +// read one user from the database +pub fn db_read_user(user: &str) -> User { + let db: sled::Db = sled::open("users_db").unwrap(); + let bytes = db.get(user).unwrap().unwrap(); + let read_user: User = bincode::deserialize(&bytes).unwrap(); + info!("read user {} from db", read_user.name); + return read_user; +} diff --git a/src/main.rs b/src/main.rs index 42a9a1c..dd729d8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,7 +37,8 @@ fn main() { chat::fetch_messages, auth::change_info, auth::check_token, - auth::logout + auth::logout, + auth::moderation_actions ], ) .mount("/", StaticFiles::from("frontend")) diff --git a/src/user.rs b/src/user.rs index 5c799c8..a40e6f3 100644 --- a/src/user.rs +++ b/src/user.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; /* User Data */ // enum of different user types -#[derive(Clone, Serialize, Deserialize, Debug)] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] pub enum UserType { Normal, Moderator, @@ -34,7 +34,7 @@ pub enum ModActions { pub struct ModerationAction { pub name: String, // name of the moderator pub action: ModActions, // what action to take - pub target: User, // who to take the action on + pub target: String, // who to take the action on } /* Miscellaneous Events */ From 78cae7a5ac9c331f1da5eccbf0891479c86eb50b Mon Sep 17 00:00:00 2001 From: Erin Nova Date: Fri, 23 Jul 2021 09:42:33 -0400 Subject: [PATCH 07/10] Add error handling to database functions, use db_read_user() --- CHANGELOG.md | 5 + Cargo.toml | 2 +- src/auth.rs | 436 ++++++++++++++++++++++++------------------------- src/file_io.rs | 19 ++- 4 files changed, 228 insertions(+), 234 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dcf02de..411b2f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +### 0.5.2 +- When changing a username, it now deletes the previous user instead of creating a new one +- Database functions should now use some error handling +- Functions use `db_read_user()` instead of reading in the whole database + ### 0.5.1 - `/api/logout` API to delete session token - Add basic support for different message types diff --git a/Cargo.toml b/Cargo.toml index 33c85f0..b2ab2bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pogchat" -version = "0.5.1" +version = "0.5.2" authors = ["Erin Nova "] edition = "2018" diff --git a/src/auth.rs b/src/auth.rs index 4a648ee..4d49529 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -27,34 +27,24 @@ pub fn index() -> &'static str { // Post request to register a user and pin #[post("/register///")] pub fn register_user(name: String, pin: i32, pronouns: String) -> JsonValue { - let mut users: Vec = db_read(); // Create an array of users out of parsed json - for i in &users { - // loop through elements of the vector - if i.name == name.to_lowercase() { - warn!("Cannot create user {}! User is already in system.", i.name); - return json!({ - "status": "fail", - "reason": "user already exists", - }); - }; - } - - let pin_hashed = sha1::Sha1::from(&pin.to_string()).digest().to_string(); // hash the pin + // check if the user exists + if let Some(user) = db_read_user(&name).ok().flatten() { + warn!("Cannot create user {}! User is already in system.", name); + return json!({ + "status": "fail", + "reason": "user already exists", + }); + } else { + let pin_hashed = sha1::Sha1::from(&pin.to_string()).digest().to_string(); // hash the pin - let new_user: User = User { - name: name.to_string().to_lowercase(), - pin_hashed, - pronouns: pronouns.to_string().to_lowercase(), - session_token: "NULL".to_string(), - role: UserType::Normal, - }; + let new_user: User = User { + name: name.to_string().to_lowercase(), + pin_hashed, + pronouns: pronouns.to_string().to_lowercase(), + session_token: "NULL".to_string(), + role: UserType::Normal, + }; - /* - // append to the json file - match append_json(&new_user) { - Err(why) => panic!("couldn't append json: {}", why), - Ok(()) => info!("Succesfully appended to json"), - };*/ db_add(&new_user); info!( @@ -66,25 +56,20 @@ pub fn register_user(name: String, pin: i32, pronouns: String) -> JsonValue { "status": "ok", "reason": format!("user {} registered", new_user.name.to_string().to_lowercase()), }); + } } -fn create_token(name: String, mut users: Vec) -> String { +fn create_token(name: String, mut user: User) -> String { let charset = "1234567890abcdefghijklmnopqrstuvwxyz"; - for i in 0..users.len() { - if users[i].name == name { - users[i].session_token = generate(12, charset); - /* - match write_json(&users) { - Err(why) => panic!("coudln't write to file: {}", why), - Ok(()) => info!("succesfully wrote to file"), - };*/ - db_write(&users); - info!("succesfully created token for user {}", name); - let token = users[i].session_token.clone(); - return token; - }; + if user.name == name { + user.session_token = generate(12, charset); + db_add(&user); + info!("succesfully created token for user {}", name); + let token = user.session_token.clone(); + return token; }; + warn!("something bad happened while creating a token and idk what"); return "NULL".to_string(); } @@ -92,138 +77,137 @@ fn create_token(name: String, mut users: Vec) -> String { // Check if user is properly logged in #[get("/token/")] pub fn check_token(name: String, mut cookies: Cookies) -> JsonValue { - let users: Vec = db_read(); - for i in &users { - if i.name == name.to_lowercase() { - let token = match cookies.get_private("token") { - None => { - warn!("couldn't get token cookie!"); - return json!({ - "status": "fail", - "reason": "could not read cookie", - }); - }, - Some(token) => token, - }; - if token.value() == "NULL" { - warn!("NULL token!"); + // check if the user is in the system + if let Some(user) = db_read_user(&name).ok().flatten() { + // get the token from the cookie + let token = match cookies.get_private("token") { + None => { + warn!("couldn't get token cookie!"); return json!({ "status": "fail", - "reason": "NULL token", + "reason": "could not read cookie", }); - } else if token.value() == i.session_token { - info!("user {} has correct session token", name); - return json!({ - "status": "ok", - "reason": "correct token", - }); - } else { - info!("user {} has incorrect token!", name); - return json!({ - "status": "fail", - "reason": "incorrect token", - }); - } + }, + Some(token) => token, + }; + + // check the token value + if token.value() == "NULL" { + warn!("NULL token!"); + return json!({ + "status": "fail", + "reason": "NULL token", + }); + } else if token.value() == user.session_token { + info!("user {} has correct session token", name); + return json!({ + "status": "ok", + "reason": "correct token", + }); + } else { + info!("user {} has incorrect token!", name); + return json!({ + "status": "fail", + "reason": "incorrect token", + }); } - } + } else { warn!("user {} not found", name); return json!({ "status": "fail", "reason": "user not found", }); + } } // Logout API #[post("/logout", format = "json", data = "")] pub fn logout(info: Json, mut cookies: Cookies) -> JsonValue { - let mut users: Vec = db_read(); - for i in 0..users.len() { - if info.name.to_lowercase() == users[i].name { - let token = match cookies.get_private("token") { - None => { - warn!("couldn't get token cookie!"); - return json!({ - "status": "fail", - "reason": "could not read cookie", - }); - }, - Some(token) => token, - }; - if token.value() == "NULL" { - warn!("NULL token!"); + if let Some(mut user) = db_read_user(&info.name.to_lowercase()).ok().flatten() { + let token = match cookies.get_private("token") { + None => { + warn!("couldn't get token cookie!"); return json!({ "status": "fail", - "reason": "NULL token", + "reason": "could not read cookie", }); - } else if token.value() == users[i].session_token { - cookies.remove_private(Cookie::named("token")); - users[i].session_token = "NULL".to_string(); - info!("logged out user {}", info.name); - return json!({ - "status": "ok", - "reason": "logged out", - }); - } else { - warn!("token does not match! cannot logout"); - return json!({ - "status": "fail", - "reason": "token does not match", - }); - } + }, + Some(token) => token, + }; + if token.value() == "NULL" { + warn!("NULL token!"); + return json!({ + "status": "fail", + "reason": "NULL token", + }); + } else if token.value() == user.session_token { + cookies.remove_private(Cookie::named("token")); + user.session_token = "NULL".to_string(); + db_add(&user); + info!("logged out user {}", info.name); + return json!({ + "status": "ok", + "reason": "logged out", + }); + } else { + warn!("token does not match! cannot logout"); + return json!({ + "status": "fail", + "reason": "token does not match", + }); } + } else { + warn!("failed to log out user {}, user not found", info.name); + return json!({ + "status": "fail", + "reason": "user not found", + }); } - warn!("logged out user {}, user not found", info.name); - return json!({ - "status": "fail", - "reason": "user not found", - }); } // Check if pin matches user #[get("/users//")] pub fn check_pin(mut cookies: Cookies, name: String, pin: i32) -> JsonValue { - let users: Vec = db_read(); - let hashed_pin_input = sha1::Sha1::from(&pin.to_string()).digest().to_string(); - for i in &users { - // loop through the vector - if i.name == name.to_lowercase() { - if i.pin_hashed == hashed_pin_input { - info!("pin correct for user {}", i.name); + if let Some(user) = db_read_user(&name.to_lowercase()).ok().flatten() { + let hashed_pin_input = sha1::Sha1::from(&pin.to_string()).digest().to_string(); - // Create token for user & set a cookie - let token = create_token(i.name.clone(), users); - let cookie = Cookie::build("token", token) - .path("/") - .finish(); - cookies.remove_private(Cookie::named("token")); - cookies.add_private(cookie); - info!("set the token cookie"); + if user.pin_hashed == hashed_pin_input { // check if pin hash matches + info!("pin correct for user {}", &user.name); - return json!({ - "status": "ok", - "reason": "pin matches", - }); - } else { - cookies.remove_private(Cookie::named("token")); - info!("removed private cookie"); - warn!("pin incorrect for user {}", i.name); - return json!({ - "status": "fail", - "reason": "incorrect pin", - }); - }; + // Create token for user & set a cookie + let token = create_token(user.name.clone(), user); + let cookie = Cookie::build("token", token) + .path("/") + .finish(); + cookies.remove_private(Cookie::named("token")); + cookies.add_private(cookie); + info!("set the token cookie"); + + return json!({ + "status": "ok", + "reason": "pin matches", + }); + } else { + cookies.remove_private(Cookie::named("token")); + info!("removed private cookie"); + warn!("pin incorrect for user {}", user.name); + return json!({ + "status": "fail", + "reason": "incorrect pin", + }); }; + } else { + cookies.remove_private(Cookie::named("token")); + info!("removed private cookie"); + warn!( + "cannot check pin for user {} as they do not exist", + name.to_string().to_lowercase() + ); + return json!({ + "status": "fail", + "reason": format!("user {} doesn't exist", name.to_string().to_lowercase()), + }); } - cookies.remove_private(Cookie::named("token")); - info!("removed private cookie"); - warn!( - "cannot check pin for user {} as they do not exist", - name.to_string().to_lowercase() - ); - return json!({ - "status": "fail", - "reason": format!("user {} doesn't exist", name.to_string().to_lowercase()), - }); } // Change info about a user @@ -251,54 +235,57 @@ pub fn change_info(input: Json, mut cookies: Cookies) -> JsonValue }); } - // loop through the users - for i in 0..users.len() { - if input.name.to_lowercase() == users[i].name { // if user found... - if token.value() == users[i].session_token { // & if token matches: - if input.changed_event == "name" { - // remove the user first - db_remove(&users[i]); - // change the name - users[i].name = input.new_event.clone(); - info!("changed name of {} to {}", input.name, input.new_event); - db_write(&users); - return json!({ - "status": "ok", - "reason": format!("changed name of {} to {}", input.name, input.new_event), - }); - } else if input.changed_event == "pin" { - // change the pin - let new_hashed_pin = sha1::Sha1::from(&input.new_event).digest().to_string(); - users[i].pin_hashed = new_hashed_pin.clone(); - db_write(&users); - info!("changed pin of {}", input.name); - return json!({ - "status": "ok", - "reason": "changed pin", - }); - } else if input.changed_event == "pronouns" { - // change the pronouns - users[i].pronouns = input.new_event.clone(); - info!("changed pronouns of {} to {}", input.name, input.new_event); - db_write(&users); - return json!({ - "status": "ok", - "reason": "successfully changed pronouns", - }); - }; - } else { - warn!("incorrect pin for user {}", input.name); + // find the user + if let Some(mut user) = db_read_user(&input.name).ok().flatten() { + if token.value() == user.session_token { // & if token matches: + if input.changed_event == "name" { + // remove the user first + db_remove(&user); + // change the name + user.name = input.new_event.clone(); + info!("changed name of {} to {}", input.name, input.new_event); + db_add(&user); return json!({ - "status": "fail", - "reason": "incorrect pin", + "status": "ok", + "reason": format!("changed name of {} to {}", input.name, input.new_event), + }); + } else if input.changed_event == "pin" { + // change the pin + let new_hashed_pin = sha1::Sha1::from(&input.new_event).digest().to_string(); + user.pin_hashed = new_hashed_pin.clone(); + db_add(&user); + info!("changed pin of {}", input.name); + return json!({ + "status": "ok", + "reason": "changed pin", + }); + } else if input.changed_event == "pronouns" { + // change the pronouns + user.pronouns = input.new_event.clone(); + info!("changed pronouns of {} to {}", input.name, input.new_event); + db_add(&user); + return json!({ + "status": "ok", + "reason": "successfully changed pronouns", }); }; - }; - }; - warn!("couldn't change users info, user does not exist"); + } else { + warn!("incorrect pin for user {}", input.name); + return json!({ + "status": "fail", + "reason": "incorrect pin", + }); + }; + } else { + warn!("couldn't change users info, user does not exist"); + return json!({ + "status": "fail", + "reason": "user doesn't exist", + }); + } return json!({ "status": "fail", - "reason": "user doesn't exist", + "reason": "idk", }); } @@ -421,50 +408,45 @@ pub fn moderation_actions(data: Json, mut cookies: Cookies) -> }, Some(token) => token, }; - let mut user = db_read_user(&data.name.to_lowercase()); - - let mut users: Vec = Vec::new(); - // loop through vector - for i in &users { - if i.name == data.name.to_lowercase() { // found the user! - if token.value() == "NULL" { // fail if token is NULL - warn!("NULL token!"); - return json!({ - "status": "fail", - "reason": "NULL token", - }); - } else if i.session_token == token.value() { // if token matches - if i.role == UserType::Normal { - match data.action { - ModActions::Kick => { - info!("kicked user {}", data.target) - }, - ModActions::Ban => info!("banned user {}", data.target), - _ => info!("F"), - }; - return json!({ - "status": "ok", - "reason": "completed action", - }); - } else { - warn!("user does not have sufficient permissions to perform that action!"); - return json!({ - "status": "fail", - "reason": "insufficient permissions", - }); + if let Some(user) = db_read_user(&data.name.to_lowercase()).ok().flatten() { + if token.value() == "NULL" { // fail if token is NULL + warn!("NULL token!"); + return json!({ + "status": "fail", + "reason": "NULL token", + }); + } else if user.session_token == token.value() { // if token matches + if user.role == UserType::Normal { + match data.action { + ModActions::Kick => { + info!("kicked user {}", data.target) + }, + ModActions::Ban => info!("banned user {}", data.target), + _ => info!("F"), }; + return json!({ + "status": "ok", + "reason": "completed action", + }); } else { - warn!("token does not match!"); + warn!("user does not have sufficient permissions to perform that action!"); return json!({ "status": "fail", - "reason": "token does not match", - }) + "reason": "insufficient permissions", + }); }; + } else { + warn!("token does not match!"); + return json!({ + "status": "fail", + "reason": "token does not match", + }) }; - }; - warn!("user not found"); - json!({ - "status": "fail", - "reason": "user not found" - }) + } else { + warn!("user not found"); + return json!({ + "status": "fail", + "reason": "user not found" + }); + } } diff --git a/src/file_io.rs b/src/file_io.rs index 1460e8e..14f997e 100644 --- a/src/file_io.rs +++ b/src/file_io.rs @@ -6,6 +6,8 @@ extern crate log; use crate::user::User; use serde_json::Result; +type MyErrorType = Box; + fn read_lines

(filename: P) -> io::Result>> where P: AsRef, @@ -135,10 +137,15 @@ pub fn db_read() -> Vec { } // read one user from the database -pub fn db_read_user(user: &str) -> User { - let db: sled::Db = sled::open("users_db").unwrap(); - let bytes = db.get(user).unwrap().unwrap(); - let read_user: User = bincode::deserialize(&bytes).unwrap(); - info!("read user {} from db", read_user.name); - return read_user; +pub fn db_read_user(user: &str) -> std::result::Result, MyErrorType> { + let db: sled::Db = sled::open("users_db")?; + let entry = db.get(user)?; + if let Some(user_entry) = entry { + let read_user: User = bincode::deserialize(&user_entry)?; + info!("read user {} from db", read_user.name); + Ok(Some(read_user)) + } else { + warn!("user {} not found in db!", user); + Ok(None) + } } From a01cb8b489f42decef05c6536ad04ecb3e45cf28 Mon Sep 17 00:00:00 2001 From: Erin Nova Date: Fri, 23 Jul 2021 09:48:57 -0400 Subject: [PATCH 08/10] Remove deprecated functions --- .gitignore | 1 + Cargo.lock | 2 +- message.zsh | 2 +- src/auth.rs | 103 +------------------------------------------------ src/file_io.rs | 96 --------------------------------------------- src/main.rs | 4 +- 6 files changed, 6 insertions(+), 202 deletions(-) diff --git a/.gitignore b/.gitignore index 5a12ba4..753be59 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ .vscode users.json message.zsh +TODO.md users_db/ diff --git a/Cargo.lock b/Cargo.lock index 9aef55b..93fff42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -807,7 +807,7 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pogchat" -version = "0.5.1" +version = "0.5.2" dependencies = [ "bincode", "chrono", diff --git a/message.zsh b/message.zsh index 267cabb..915a2a2 100755 --- a/message.zsh +++ b/message.zsh @@ -1 +1 @@ -http POST 'http://localhost:8000/api/message/send' name=erin body="nyaa uwu" date="2021-07-21" +http POST 'http://localhost:8000/api/mod' name=erin action=Ban target=sarah diff --git a/src/auth.rs b/src/auth.rs index 4d49529..7baac90 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,5 +1,5 @@ extern crate log; -use crate::file_io::{db_add, db_write, db_read, db_read_user, db_remove}; +use crate::file_io::*; use rocket::http::{Cookie, Cookies}; use crate::user::*; use rocket_contrib::json::{Json, JsonValue}; @@ -7,23 +7,6 @@ use random_string::generate; extern crate sha1; use serde::Deserialize; -#[get("/")] -pub fn index() -> &'static str { - "API Info: - - `POST /api/register///` Register the username with the pin provided if it doesn't already exist - - `GET /api/users/` Check if the user exists - - `GET /api/users//` Check if the user exists, and if the pin provided matches - - `POST /api/users/change////` Change a users name and/or pin - - `GET /api/about/name/` Get the name of a user - - `GET /api/about/pronouns/` Get the pronouns of a user" -} - // Post request to register a user and pin #[post("/register///")] pub fn register_user(name: String, pin: i32, pronouns: String) -> JsonValue { @@ -167,7 +150,7 @@ pub fn logout(info: Json, mut cookies: Cookies) -> JsonValue { // Check if pin matches user #[get("/users//")] -pub fn check_pin(mut cookies: Cookies, name: String, pin: i32) -> JsonValue { +pub fn login(mut cookies: Cookies, name: String, pin: i32) -> JsonValue { if let Some(user) = db_read_user(&name.to_lowercase()).ok().flatten() { let hashed_pin_input = sha1::Sha1::from(&pin.to_string()).digest().to_string(); @@ -289,88 +272,6 @@ pub fn change_info(input: Json, mut cookies: Cookies) -> JsonValue }); } -// Change a users pin/name -#[post("/users/change////")] -pub fn change(name: String, pin: i32, new_name: String, new_pin: i32) -> JsonValue { - let mut users: Vec = db_read(); - - let hashed_pin_input = sha1::Sha1::from(&pin.to_string()).digest().to_string(); - - // Loop over elements in vector - for i in 0..users.len() { - if users[i].name == name.to_lowercase() { - // make sure name exists - if hashed_pin_input == users[i].pin_hashed { - // check if token is correct - // Check wether to change name or name+pin - if users[i].name == new_name.to_lowercase() { - // check if new name already exists - users[i].pin_hashed = sha1::Sha1::from(&new_pin.to_string()).digest().to_string(); - /* - match write_json(&users) { - Err(why) => panic!("Cannot write to json! {}", why), - Ok(()) => info!("succesfully wrote to json file"), - }*/ - db_write(&users); - info!("Changed pin of {}", name.to_string().to_lowercase()); - return json!({ - "status": "ok", - "reason": format!("changed {}'s pin", name.to_string().to_lowercase()), - }); - } else { - // check if new name already exists - for n in &users { - if n.name == new_name.to_lowercase() { - warn!( - "Could not change name of {} to {}, as new name is already taken.", - name.to_lowercase(), - new_name.to_lowercase() - ); - return json!({ - "status": "fail", - "reason": format!("new name {} is already taken", new_name.to_lowercase()), - }); - } - } - users[i].name = new_name.to_string().to_lowercase(); - users[i].pin_hashed = - sha1::Sha1::from(&new_pin.to_string()).digest().to_string(); - /* - match write_json(&users) { - Err(why) => panic!("couldn't write to json file! {}", why), - Ok(()) => info!("succesfully wrote to json file"), - }*/ - db_write(&users); - info!( - "Changed name of {} to {}. New pin hash is {}", - name.to_string(), - users[i].name.to_string(), - users[i].pin_hashed.to_string() - ); - return json!({ - "status": "ok", - "reason": "successfully changed name and/or pin", - }); - } - } else { - warn!("Incorrect token for user {}!", name.to_string()); - return json!({ - "status": "fail", - "reason": "incorrect token for user", - }); - } - } - } - warn!( - "User {} not found, could not change pin and/or name.", - name.to_string() - ); - return json!({ - "status": "fail", - "reason": format!("user {} not found", name.to_string().to_lowercase()), - }); -} - #[get("/users/")] pub fn get_user(name: String) -> JsonValue { let users: Vec = db_read(); diff --git a/src/file_io.rs b/src/file_io.rs index 14f997e..8a740c2 100644 --- a/src/file_io.rs +++ b/src/file_io.rs @@ -1,104 +1,8 @@ -use std::fs::{File, OpenOptions}; -use std::io::prelude::*; -use std::io::{self, BufRead}; -use std::path::Path; extern crate log; use crate::user::User; -use serde_json::Result; type MyErrorType = Box; -fn read_lines

(filename: P) -> io::Result>> -where - P: AsRef, -{ - let file = File::open(filename)?; - Ok(io::BufReader::new(file).lines()) -} - -// Function to read json from file into the vector -pub fn read_json() -> Vec { - // Create path to file - let path = Path::new("users.json"); - let display = path.display(); - - let mut users: Vec = Vec::new(); // Create an empty vector of users - - // Read through the lines and append them to the array - if let Ok(lines) = read_lines(&path) { - for line in lines { - if let Ok(user) = line { - info!("read {} from json file {}", display, &user); - // Parse line from file into a data structure - let user: User = serde_json::from_str(&user).unwrap(); - users.push(user); - } - } - } - return users; -} - -// Function to append the last value of the users vector to the file -pub fn append_json(user: &User) -> Result<()> { - // Create a file to write to - let path = Path::new("users.json"); - let display = path.display(); - - let mut file = match OpenOptions::new() - .write(true) - .create(true) - .append(true) - .open(&path) - { - Err(why) => panic!("couldn't create {}: {}", display, why), - Ok(file) => file, - }; - - // Serialize the last user value - let user_json = serde_json::to_string(&user)?; - - // Write to the file - match file.write_all(user_json.as_bytes()) { - Err(why) => panic!("couldn't write to {}: {}", display, why), - Ok(_) => info!("succesfully wrote to {}", display), - }; - // Add newline - match file.write_all("\n".as_bytes()) { - Err(why) => panic!("couldn't write to {}: {}", display, why), - Ok(_) => info!("succesfully wrote newline to {}", display), - }; - Ok(()) -} - -// Function to write whole vector of users to file -pub fn write_json(users_list: &Vec) -> Result<()> { - // Create a file to write to - let path = Path::new("users.json"); - let display = path.display(); - - let mut file = match OpenOptions::new().write(true).create(true).open(&path) { - Err(why) => panic!("couldn't create {}: {}", display, why), - Ok(file) => file, - }; - - let mut users_json = String::new(); - for i in 0..users_list.len() { - // Serialize the users - users_json += &serde_json::to_string(&users_list[i])?; - if i != users_list.len()-1 { - // don't append newline if it's the last element - users_json += "\n"; - } - } - - // Write to the file - match file.write_all(users_json.as_bytes()) { - Err(why) => panic!("couldn't write to {}: {}", display, why), - Ok(_) => info!("succesfully wrote to {}", display), - }; - Ok(()) -} - // add a user to the database pub fn db_add(user: &User) { let db: sled::Db = sled::open("users_db").unwrap(); diff --git a/src/main.rs b/src/main.rs index dd729d8..ca9d12a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,11 +28,9 @@ fn main() { .mount( "/api", routes![ - auth::index, auth::get_user, auth::register_user, - auth::check_pin, - auth::change, + auth::login, chat::send_message, chat::fetch_messages, auth::change_info, From 73193529505c06b37d01677a5d825691fe583799 Mon Sep 17 00:00:00 2001 From: Erin Nova Date: Fri, 23 Jul 2021 10:08:54 -0400 Subject: [PATCH 09/10] /register & /login now use json post requests, change event uses enum --- src/auth.rs | 90 ++++++++++++++++++++++++++++------------------------- src/main.rs | 2 +- src/user.rs | 25 ++++++++++++++- 3 files changed, 72 insertions(+), 45 deletions(-) diff --git a/src/auth.rs b/src/auth.rs index 7baac90..2e40581 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -8,22 +8,22 @@ extern crate sha1; use serde::Deserialize; // Post request to register a user and pin -#[post("/register///")] -pub fn register_user(name: String, pin: i32, pronouns: String) -> JsonValue { +#[post("/register", format = "json", data = "")] +pub fn register(data: Json) -> JsonValue { // check if the user exists - if let Some(user) = db_read_user(&name).ok().flatten() { - warn!("Cannot create user {}! User is already in system.", name); + if let Some(user) = db_read_user(&data.name).ok().flatten() { + warn!("Cannot create user {}! User is already in system.", data.name); return json!({ "status": "fail", "reason": "user already exists", }); } else { - let pin_hashed = sha1::Sha1::from(&pin.to_string()).digest().to_string(); // hash the pin + let pin_hashed = sha1::Sha1::from(&data.pin).digest().to_string(); // hash the pin let new_user: User = User { - name: name.to_string().to_lowercase(), + name: data.name.to_string().to_lowercase(), pin_hashed, - pronouns: pronouns.to_string().to_lowercase(), + pronouns: data.pronouns.to_string().to_lowercase(), session_token: "NULL".to_string(), role: UserType::Normal, }; @@ -149,10 +149,10 @@ pub fn logout(info: Json, mut cookies: Cookies) -> JsonValue { } // Check if pin matches user -#[get("/users//")] -pub fn login(mut cookies: Cookies, name: String, pin: i32) -> JsonValue { - if let Some(user) = db_read_user(&name.to_lowercase()).ok().flatten() { - let hashed_pin_input = sha1::Sha1::from(&pin.to_string()).digest().to_string(); +#[post("/login", format = "json", data = "")] +pub fn login(data: Json, mut cookies: Cookies) -> JsonValue { + if let Some(user) = db_read_user(&data.name.to_lowercase()).ok().flatten() { + let hashed_pin_input = sha1::Sha1::from(&data.pin.to_string()).digest().to_string(); if user.pin_hashed == hashed_pin_input { // check if pin hash matches info!("pin correct for user {}", &user.name); @@ -184,11 +184,11 @@ pub fn login(mut cookies: Cookies, name: String, pin: i32) -> JsonValue { info!("removed private cookie"); warn!( "cannot check pin for user {} as they do not exist", - name.to_string().to_lowercase() + data.name.to_string().to_lowercase() ); return json!({ "status": "fail", - "reason": format!("user {} doesn't exist", name.to_string().to_lowercase()), + "reason": format!("user {} doesn't exist", data.name.to_string().to_lowercase()), }); } } @@ -221,36 +221,40 @@ pub fn change_info(input: Json, mut cookies: Cookies) -> JsonValue // find the user if let Some(mut user) = db_read_user(&input.name).ok().flatten() { if token.value() == user.session_token { // & if token matches: - if input.changed_event == "name" { - // remove the user first - db_remove(&user); - // change the name - user.name = input.new_event.clone(); - info!("changed name of {} to {}", input.name, input.new_event); - db_add(&user); - return json!({ - "status": "ok", - "reason": format!("changed name of {} to {}", input.name, input.new_event), - }); - } else if input.changed_event == "pin" { - // change the pin - let new_hashed_pin = sha1::Sha1::from(&input.new_event).digest().to_string(); - user.pin_hashed = new_hashed_pin.clone(); - db_add(&user); - info!("changed pin of {}", input.name); - return json!({ - "status": "ok", - "reason": "changed pin", - }); - } else if input.changed_event == "pronouns" { - // change the pronouns - user.pronouns = input.new_event.clone(); - info!("changed pronouns of {} to {}", input.name, input.new_event); - db_add(&user); - return json!({ - "status": "ok", - "reason": "successfully changed pronouns", - }); + match input.changed_event { + ChangeEventType::Name => { + // remove the user first + db_remove(&user); + // change the name + user.name = input.new_event.clone(); + info!("changed name of {} to {}", input.name, input.new_event); + db_add(&user); + return json!({ + "status": "ok", + "reason": format!("changed name of {} to {}", input.name, input.new_event), + }); + }, + ChangeEventType::Pin => { + // change the pin + let new_hashed_pin = sha1::Sha1::from(&input.new_event).digest().to_string(); + user.pin_hashed = new_hashed_pin.clone(); + db_add(&user); + info!("changed pin of {}", input.name); + return json!({ + "status": "ok", + "reason": "changed pin", + }); + }, + ChangeEventType::Pronouns => { + // change the pronouns + user.pronouns = input.new_event.clone(); + info!("changed pronouns of {} to {}", input.name, input.new_event); + db_add(&user); + return json!({ + "status": "ok", + "reason": "successfully changed pronouns", + }); + }, }; } else { warn!("incorrect pin for user {}", input.name); diff --git a/src/main.rs b/src/main.rs index ca9d12a..fbba3eb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,7 +29,7 @@ fn main() { "/api", routes![ auth::get_user, - auth::register_user, + auth::register, auth::login, chat::send_message, chat::fetch_messages, diff --git a/src/user.rs b/src/user.rs index a40e6f3..7114ef3 100644 --- a/src/user.rs +++ b/src/user.rs @@ -44,11 +44,34 @@ pub struct LogoutEvent { pub name: String, } +// register event struct +#[derive(Deserialize, Debug)] +pub struct RegisterEvent { + pub name: String, + pub pin: String, + pub pronouns: String, +} + +// login event struct +#[derive(Deserialize, Debug)] +pub struct LoginEvent { + pub name: String, + pub pin: String, +} + +// change event type +#[derive(Deserialize, Debug)] +pub enum ChangeEventType { + Name, + Pin, + Pronouns, +} + // change info event struct #[derive(Deserialize, Debug)] pub struct ChangeEvent { pub name: String, // name of the user pub pin: String, // user's pin - pub changed_event: String, // which event to change + pub changed_event: ChangeEventType, // which event to change pub new_event: String, // the new value for the event } From f931db2720a47889b798bf7414131733a36b77d9 Mon Sep 17 00:00:00 2001 From: Erin Nova Date: Fri, 23 Jul 2021 10:35:14 -0400 Subject: [PATCH 10/10] Improve API docs --- CHANGELOG.md | 5 +++ Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 119 +++++++++++++++++++++++++++++++++++---------------- src/auth.rs | 2 +- src/user.rs | 1 - 6 files changed, 89 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 411b2f2..ac16f98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.6.0 +- Remove deprecated API +- `/api/register` & `/api/login` now use JSON data +- Changing user info now uses Enum (Name, Pin, or Pronouns) + ### 0.5.2 - When changing a username, it now deletes the previous user instead of creating a new one - Database functions should now use some error handling diff --git a/Cargo.lock b/Cargo.lock index 93fff42..2b12326 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -807,7 +807,7 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pogchat" -version = "0.5.2" +version = "0.6.0" dependencies = [ "bincode", "chrono", diff --git a/Cargo.toml b/Cargo.toml index b2ab2bc..f4a21b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pogchat" -version = "0.5.2" +version = "0.6.0" authors = ["Erin Nova "] edition = "2018" diff --git a/README.md b/README.md index 2c7b2fb..fcfc2a9 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,95 @@ # Chat Registration System -The basic backend code needed to register & login to a chat system (to be built). -Send it the unhashed username and pin, and it'll store it in the `users.json` file with the pin hashed with SHA1. +A simple chat system for built for maya's livestream. +Provides a simple API for user authentication, and chat functions. +Frontend & backend code stored here. -## API Documentation +## Auth API Documentation -`POST /api/register///` Register the username with the pin provided if it doesn't already exist -Returns status & reason json. +Most API functions will return JSON in the following format: -`GET /api/users/` Check if the user exists -Returns either +`status`: either `ok` if action succeeded, or `fail` otherwise. -`{ - "status": "fail", - "reason": "user not found", -}` +`reason`: More info about why the action failed. -or +### Register & Login: -`{ - "status": "ok", - "user": { - "name": "", - "pronouns": "", +`POST /api/register` with JSON body values of: `name`, `pin`, `pronouns`. + +Will return JSON with `status` and `reason`. + +`POST /api/login` with JSON body values of: `name`, `pin`. + +Will return JSON with `status` and `reason`. + +Will set a private cookie named `token` which is used for authentication. + +### Change User Information + +User information such as name, pin, and pronouns, can be changed currently one at a time. + +`POST /api/change` with JSON body values of: `name`, `changed_event`, `new_event`. + +`name` the user's current username. used for authentication. + +`changed_event` which event to change. value can be one of: `Name`, `Pin`, `Pronouns`. + +`new_event` the new value for the changed event. + +User is authenticated via token. + +### Check if User is Still Logged in + +Instead of having to save the pin and re-login every time to check wether they're logged in, you can just check via the token. + +`GET /api/token/` where `` is the current username. + +Will return JSON with `status` and `reason`. + +### Logout + +This API will remove the cookie from the client, as well as invalidating the token serverside. + +`POST /api/logout` with JSON body values of: `name`. + +Will use the current token as authentication. + +Will return JSON with `status` and `reason`. + +### Get Info About A User + +This API will return info about a user on success. + +`GET /api/users/` + +On success returns JSON in format: + + `status`: `ok` + `user`: + `name`: user's name + `pronouns`: user's pronouns + `role`: the users role, one of either `Normal`, `Moderator`, or `Admin + +eg: + +``` +{ + status: "ok", + user: { + name: "example", + pronouns: "they/them", + role: "Normal", }, -}` +} +``` -`GET /api/token/` Check if the current token matches the user provided +## Chat API Documentation -DEPRECATED `GET /api/users//` Check if the user exists, and if the pin provided matches -Returns status & reason json. +`POST /api/message/send {"name":"username","body":"message body"}` Post a message with JSON body values of: `name` & `body` -`POST /api/users/change {"name":"","pin":"","changed_event":"name/pin/pronouns","new_event":""` Change a users details via a json post. +Will return JSON with `status` and `reason`. -eg. `POST /api/users/change {"name":"example","pin":"10","changed_event":"name","new_event":"test"` to change the user "example"'s name to "test" - -DEPRECATED `POST /api/users/change////` Change a users pin/name -Returns status & reason json. - -`POST /api/logout {"name":""}` to logout a user if the token matches - - -## Chat Documentation - -`POST /api/message/send {"name":"username","body":"message body"}` Post a json message. -Returns status & reason json. - -`GET /api/message/messages.json` Get a json file of all the messages +`GET /api/message/messages.json` Returns a json file of all the messages ## Chat Planning @@ -66,7 +109,7 @@ Whenever user sends a message, client will send message & token and backend will - [x] Use unix timestamp for date - [ ] Create `chat::delete_message()` - [x] Switch to using sled database to store users - - [ ] Error handling + - [x] Error handling - [x] Token generation & storage - [x] Sets cookie - [x] Store token in json @@ -78,7 +121,7 @@ Whenever user sends a message, client will send message & token and backend will - [x] Pronouns - [x] Set pronouns - [x] Change pronouns -- [ ] make changed_event Enum, use token instead of pin +- [x] make changed_event Enum, use token instead of pin - [ ] Some form of plural support? - [ ] User management (banning, etc.) - [x] User roles (admin, mod, etc.) diff --git a/src/auth.rs b/src/auth.rs index 2e40581..798af4c 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -194,7 +194,7 @@ pub fn login(data: Json, mut cookies: Cookies) -> JsonValue { } // Change info about a user -#[post("/users/change", format = "json", data = "")] +#[post("/change", format = "json", data = "")] pub fn change_info(input: Json, mut cookies: Cookies) -> JsonValue { // read in the users & hash the pin let mut users: Vec = db_read(); diff --git a/src/user.rs b/src/user.rs index 7114ef3..c7fe77c 100644 --- a/src/user.rs +++ b/src/user.rs @@ -71,7 +71,6 @@ pub enum ChangeEventType { #[derive(Deserialize, Debug)] pub struct ChangeEvent { pub name: String, // name of the user - pub pin: String, // user's pin pub changed_event: ChangeEventType, // which event to change pub new_event: String, // the new value for the event }