Merge pull request #5 from 23marabi/chat-enhancements

Merge Chat enhancements?
break-database
Zer04 2021-07-23 11:26:03 -04:00 committed by GitHub
commit d10db5d31b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 479 additions and 506 deletions

1
.gitignore vendored
View File

@ -2,4 +2,5 @@
.vscode
users.json
message.zsh
TODO.md
users_db/

View File

@ -1,3 +1,19 @@
## 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
- 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
- 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

28
Cargo.lock generated
View File

@ -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.6.0"
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",

View File

@ -1,6 +1,6 @@
[package]
name = "pogchat"
version = "0.5.0"
version = "0.6.0"
authors = ["Erin Nova <erin@the-system.eu.org>"]
edition = "2018"

129
README.md
View File

@ -1,54 +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 database 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 {"name":"<username>","pin":"<pin>","pronouns":"<pronouns>"}` Register a user if they don't already exist
Most API functions will return JSON in the following format:
`POST /api/register/<name>/<pin>/<pronouns>` Register the username with the pin provided if it doesn't already exist
Returns status & reason json.
`status`: either `ok` if action succeeded, or `fail` otherwise.
`GET /api/users/<name>` Check if the user exists
Returns either
`reason`: More info about why the action failed.
`{
"status": "fail",
"reason": "user not found",
}`
### Register & Login:
or
`POST /api/register` with JSON body values of: `name`, `pin`, `pronouns`.
`{
"status": "ok",
"user": {
"name": "<name>",
"pronouns": "<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/<name>` where `<name>` 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/<name>`
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/<name>` Check if the current token matches the user provided
## Chat API Documentation
`GET /api/users/<name>/<pin>` 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":"<username>","pin":"<pin>","changed_event":"name/pin/pronouns","new_event":"<new name/pin/pronouns>"` 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/<name>/<pin>/<new-name>/<new-pin>` Change a users pin/name
Returns status & reason json.
`POST /api/logout {"name":"<username>"}` to logout a user if the token matches
## Chat Documentation
`POST /api/message/send {"name":"username","body":"message body","date":"yyyy-mm-dd"}` 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
@ -65,10 +106,10 @@ 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
- [x] Error handling
- [x] Token generation & storage
- [x] Sets cookie
- [x] Store token in json
@ -79,11 +120,15 @@ 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
- [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.)
- [ ] Commands to affect users
- [ ] 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
- [ ] Types will display differently? eg. announcements pinned to top?
- [ ] Have different commands?
- [ ] Emote support?

View File

@ -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

View File

@ -1,66 +1,33 @@
extern crate log;
use crate::file_io::{db_add, db_write, db_read};
use rocket::http::{Cookie, Cookies, SameSite};
use crate::user::User;
use crate::file_io::*;
use rocket::http::{Cookie, Cookies};
use crate::user::*;
use rocket_contrib::json::{Json, JsonValue};
use random_string::generate;
extern crate sha1;
use serde::Deserialize;
#[get("/")]
pub fn index() -> &'static str {
"API Info:
`POST /api/register/<name>/<pin>/<pronouns>` Register the username with the pin provided if it doesn't already exist
`GET /api/users/<name>` Check if the user exists
`GET /api/users/<name>/<pin>` Check if the user exists, and if the pin provided matches
`POST /api/users/change/<name>/<pin>/<new-name>/<new-pin>` Change a users name and/or pin
`GET /api/about/name/<name>` Get the name of a user
`GET /api/about/pronouns/<name>` Get the pronouns of a user"
}
#[derive(Deserialize, Debug)]
pub struct RegisterEvent {
pub name: String,
pub pin: String,
pub pronouns: String,
}
// Post request to register a user and pin
#[post("/register", format = "json", data = "<info>")]
pub fn register_user(info: Json<RegisterEvent>) -> JsonValue {
let users: Vec<User> = db_read(); // Create an array of users out of parsed json
for i in &users {
// loop through elements of the vector
if i.name == info.name.to_lowercase() {
warn!("Cannot create user {}! User is already in system.", i.name);
return json!({
"status": "fail",
"reason": "user already exists",
});
#[post("/register", format = "json", data = "<data>")]
pub fn register(data: Json<RegisterEvent>) -> JsonValue {
// check if the user exists
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(&data.pin).digest().to_string(); // hash the pin
let new_user: User = User {
name: data.name.to_string().to_lowercase(),
pin_hashed,
pronouns: data.pronouns.to_string().to_lowercase(),
session_token: "NULL".to_string(),
role: UserType::Normal,
};
}
let pin_hashed = sha1::Sha1::from(&info.pin.to_string()).digest().to_string(); // hash the pin
let new_user: User = User {
name: info.name.to_string().to_lowercase(),
pin_hashed: pin_hashed,
pronouns: info.pronouns.to_string().to_lowercase(),
session_token: "NULL".to_string(),
}; // append the user to the vec
/*
// 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!(
@ -72,25 +39,20 @@ pub fn register_user(info: Json<RegisterEvent>) -> JsonValue {
"status": "ok",
"reason": format!("user {} registered", new_user.name.to_string().to_lowercase()),
});
}
}
fn create_token(name: String, mut users: Vec<User>) -> 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();
}
@ -98,159 +60,141 @@ fn create_token(name: String, mut users: Vec<User>) -> String {
// Check if user is properly logged in
#[get("/token/<name>")]
pub fn check_token(name: String, mut cookies: Cookies) -> JsonValue {
let users: Vec<User> = 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 event struct
#[derive(Deserialize, Debug)]
pub struct LogoutEvent {
pub name: String,
}
}
// Logout API
#[post("/logout", format = "json", data = "<info>")]
pub fn logout(info: Json<LogoutEvent>, mut cookies: Cookies) -> JsonValue {
let mut users: Vec<User> = 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/<name>/<pin>")]
pub fn login(mut cookies: Cookies, name: String, pin: i32) -> JsonValue {
let users: Vec<User> = 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);
#[post("/login", format = "json", data = "<data>")]
pub fn login(data: Json<LoginEvent>, 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();
// Create token for user & set a cookie
let token = create_token(i.name.clone(), users);
let cookie = Cookie::build("token", token)
.path("/")
.same_site(SameSite::Strict)
.secure(true)
.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",
data.name.to_string().to_lowercase()
);
return json!({
"status": "fail",
"reason": format!("user {} doesn't exist", data.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()),
});
}
#[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 = "<input>")]
#[post("/change", format = "json", data = "<input>")]
pub fn change_info(input: Json<ChangeEvent>, mut cookies: Cookies) -> JsonValue {
// read in the users & hash the pin
let mut users: Vec<User> = db_read();
@ -274,134 +218,61 @@ pub fn change_info(input: Json<ChangeEvent>, 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" {
// find the user
if let Some(mut user) = db_read_user(&input.name).ok().flatten() {
if token.value() == user.session_token { // & if token matches:
match input.changed_event {
ChangeEventType::Name => {
// remove the user first
db_remove(&user);
// change the name
users[i].name = input.new_event.clone();
user.name = input.new_event.clone();
info!("changed name of {} to {}", input.name, input.new_event);
db_write(&users);
db_add(&user);
return json!({
"status": "ok",
"reason": format!("changed name of {} to {}", input.name, input.new_event),
});
} else if input.changed_event == "pin" {
},
ChangeEventType::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);
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" {
},
ChangeEventType::Pronouns => {
// change the pronouns
users[i].pronouns = input.new_event.clone();
user.pronouns = input.new_event.clone();
info!("changed pronouns of {} to {}", input.name, input.new_event);
db_write(&users);
db_add(&user);
return json!({
"status": "ok",
"reason": "successfully changed pronouns",
});
};
} else {
warn!("incorrect pin for user {}", input.name);
return json!({
"status": "fail",
"reason": "incorrect pin",
});
},
};
};
};
warn!("couldn't change users info, user does not exist");
return json!({
"status": "fail",
"reason": "user doesn't exist",
});
}
// Change a users pin/name
#[post("/users/change/<name>/<pin>/<new_name>/<new_pin>")]
pub fn change(name: String, pin: i32, new_name: String, new_pin: i32) -> JsonValue {
let mut users: Vec<User> = 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",
});
}
}
} 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",
});
}
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()),
"reason": "idk",
});
}
@ -419,6 +290,7 @@ pub fn get_user(name: String) -> JsonValue {
"user": {
"name": user.name,
"pronouns": user.pronouns,
"role": user.role,
},
}),
None => json!({
@ -427,3 +299,59 @@ pub fn get_user(name: String) -> JsonValue {
}),
}
}
/* User Management */
#[post("/mod", format = "json", data = "<data>")]
pub fn moderation_actions(data: Json<ModerationAction>, 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,
};
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!("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",
})
};
} else {
warn!("user not found");
return json!({
"status": "fail",
"reason": "user not found"
});
}
}

View File

@ -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,24 @@ pub fn fetch_messages() -> Json<Vec<Message>> {
// Create full message object and write to file
fn create_message(message: Json<MessageInput>, 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> = Utc.ymd(year, month, day).and_hms(9, 10, 11);
let message_obj: Message = Message {
id: Uuid::new_v4(),
event_type,
user: user.name.to_owned(),
body: message.body.to_string(),
created_at: date,
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();
messages.push(message_obj.to_owned());
return json!({
@ -107,3 +96,20 @@ pub fn send_message(message: Json<MessageInput<'_>>, mut cookies: Cookies) -> Js
};
check_token(token, message)
}
// Delete a message
/*
#[post("/message/delete", format = "json", data = "<message>")]
pub fn delete_message(message: Json<MessageInput<'_>>, 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,
};
}
*/

View File

@ -1,123 +1,7 @@
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;
fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where
P: AsRef<Path>,
{
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<User> {
// Create path to file
let path = Path::new("users.json");
let display = path.display();
let mut users: Vec<User> = 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<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).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(())
}
// 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 => (),
}
}
type MyErrorType = Box<dyn std::error::Error>;
// add a user to the database
pub fn db_add(user: &User) {
@ -138,6 +22,12 @@ pub fn db_write(users_list: &Vec<User>) {
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<User> {
let db: sled::Db = sled::open("users_db").unwrap();
@ -149,3 +39,17 @@ pub fn db_read() -> Vec<User> {
}
return users;
}
// read one user from the database
pub fn db_read_user(user: &str) -> std::result::Result<Option<User>, 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)
}
}

View File

@ -28,16 +28,15 @@ fn main() {
.mount(
"/api",
routes![
auth::index,
auth::get_user,
auth::register_user,
auth::register,
auth::login,
auth::change,
chat::send_message,
chat::fetch_messages,
auth::change_info,
auth::check_token,
auth::logout
auth::logout,
auth::moderation_actions
],
)
.mount("/", StaticFiles::from("frontend"))

View File

@ -7,12 +7,21 @@ use uuid::Uuid;
pub struct MessageInput<'r> {
pub name: &'r str,
pub body: &'r str,
pub date: &'r str,
pub date: i64,
}
#[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<Utc>,

View File

@ -1,11 +1,76 @@
use serde::{Deserialize, Serialize};
/* User Data */
// enum of different user types
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
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 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: String, // who to take the action on
}
/* Miscellaneous Events */
// logout event struct
#[derive(Deserialize, Debug)]
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 changed_event: ChangeEventType, // which event to change
pub new_event: String, // the new value for the event
}