Skip to content

Commit

Permalink
fix: improve the RSA encryption so that it can parse hex strings
Browse files Browse the repository at this point in the history
  • Loading branch information
7086cmd committed May 1, 2024
1 parent d840274 commit 3c4be71
Show file tree
Hide file tree
Showing 11 changed files with 197 additions and 27 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ socketioxide = { version = "0.12.0", features = ["state", "extensions", "tracing
tokio = { version = "1.37.0", features = ["full"] }
tower = "0.4.13"
tower-http = "0.5.2"
tracing = { version = "0.1.40", features = ["log"] }
tracing-subscriber = { version = "0.3.18", features = ["tracing", "time", "serde", "serde_json", "json", "regex"] }
uuid = "1.8.0"
zerocopy = "0.7.32"
Expand Down
34 changes: 15 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
# ZVMS 4 Backend Implementation with Rust

The first version of backend of ZVMS 4 is a `shit mountain` of code.

Since I am learning `Rust`, I decided to rewrite the backend in `Rust`.

Technologies used:

- `axum`: Web framework
- `tokio`: Async runtime
- `mongodb`: Database

> You should have `Rust` installed in your system to run this project.
> You need to create `src/config.rs` with following code:
```rust
pub const MONGO_URL: &str = "<MONGO_URL>";
```
# ZVMS 4 Backend Implementation with Rust

[![JWT](https://jwt.io/img/badge-compatible.svg)](https://jwt.io/)

![Test](https://github.com/zvms/zvms4-backend-rust/actions/workflows/test.yml/badge.svg) ![Build](https://github.com/zvms/zvms4-backend-rust/actions/workflows/release.yml/badge.svg)

The first version of backend of ZVMS 4 is a `shit mountain` of code.

Since I am learning `Rust`, I decided to rewrite the backend in `Rust`.

Technologies used:

- `axum`: Web framework
- `tokio`: Async runtime
- `mongodb`: Database
1 change: 0 additions & 1 deletion aes256.key

This file was deleted.

10 changes: 7 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ fn on_connect(socket: SocketRef, Data(data): Data<Value>) {

#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
tracing_subscriber::fmt().init();

let client = database::create_client()
.await
Expand All @@ -64,6 +64,10 @@ async fn main() {
post(routers::activities::insert::insert_activity),
)
.route("/activity/:id", get(routers::activities::read::read_one))
.route(
"/activity/:id",
delete(routers::activities::remove::remove_activity),
)
.route(
"/activity/:id/name",
put(routers::activities::update::update_activity_name),
Expand All @@ -73,8 +77,8 @@ async fn main() {
put(routers::activities::update::update_activity_description),
)
.route(
"/activity/:id",
delete(routers::activities::remove::remove_activity),
"/activity/:id/member",
post(routers::activities::members::insert::insert_member_into_activity),
)
.route("/user/auth", post(routers::auth::login))
.layer(Extension(shared_client.clone()));
Expand Down
111 changes: 111 additions & 0 deletions src/routers/activities/members/insert.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
use crate::{
models::{
activities::{Activity, ActivityMember},
groups::GroupPermission,
response::{ErrorResponse, ResponseStatus, SuccessResponse},
},
utils::jwt::UserData,
};
use axum::{
extract::{Extension, Json, Path},
http::StatusCode,
response::IntoResponse,
};
use bson::{doc, oid::ObjectId};
use mongodb::Database;
use std::{str::FromStr, sync::Arc};
use tokio::sync::Mutex;

pub async fn insert_member_into_activity(
Extension(db): Extension<Arc<Mutex<Database>>>,
user: UserData,
Path(id): Path<String>,
Json(activity_member): Json<ActivityMember>,
) -> impl IntoResponse {
let db = db.lock().await;
let collection = db.collection("activities");
let activity_id = ObjectId::from_str(&id).unwrap();
let activity = collection.find_one(doc! {"_id": activity_id}, None).await;
if let Err(_) = activity {
let response = ErrorResponse {
status: ResponseStatus::Error,
code: 404,
message: "Activity not found".to_string(),
};
let response = serde_json::to_string(&response).unwrap();
return (StatusCode::NOT_FOUND, Json(response));
}
let activity = activity.unwrap();
if let None = activity {
let response = ErrorResponse {
status: ResponseStatus::Error,
code: 404,
message: "Activity not found".to_string(),
};
let response = serde_json::to_string(&response).unwrap();
return (StatusCode::NOT_FOUND, Json(response));
}
let activity: Activity = bson::from_document(activity.unwrap()).unwrap();
let members = activity.members.unwrap_or_default();
// Check if the activity contains the member
if members
.iter()
.any(|member| member._id == ObjectId::from_str(&activity_member._id.to_hex()).unwrap())
{
let response = ErrorResponse {
status: ResponseStatus::Error,
code: 400,
message: "Member already exists".to_string(),
};
let response = serde_json::to_string(&response).unwrap();
return (StatusCode::BAD_REQUEST, Json(response));
}
if user.perms.contains(&GroupPermission::Admin) || user.perms.contains(&GroupPermission::Department) {} else {
let response = ErrorResponse {
status: ResponseStatus::Error,
code: 403,
message: "Permission denied".to_string(),
};
let response = serde_json::to_string(&response).unwrap();
return (StatusCode::FORBIDDEN, Json(response));
}
let member = bson::to_document(&activity_member);
if let Err(_) = member {
let response = ErrorResponse {
status: ResponseStatus::Error,
code: 400,
message: "Invalid member".to_string(),
};
let response = serde_json::to_string(&response).unwrap();
return (StatusCode::BAD_REQUEST, Json(response));
}
let result = collection
.update_one(
doc! {"_id": activity_id},
doc! {
"$push": {
"members": member.unwrap()
}
},
None,
)
.await;
if let Ok(_) = result {
let response: SuccessResponse<_, ()> = SuccessResponse {
status: ResponseStatus::Success,
code: 200,
data: (),
metadata: None,
};
let response = serde_json::to_string(&response).unwrap();
(StatusCode::OK, Json(response))
} else {
let response = ErrorResponse {
status: ResponseStatus::Error,
code: 500,
message: "Failed to insert member".to_string(),
};
let response = serde_json::to_string(&response).unwrap();
(StatusCode::INTERNAL_SERVER_ERROR, Json(response))
}
}
1 change: 1 addition & 0 deletions src/routers/activities/members/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod insert;
1 change: 1 addition & 0 deletions src/routers/activities/members/read.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
use crate::{models::{activities::Activity}};
15 changes: 13 additions & 2 deletions src/routers/auth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ pub struct LoginCredentials {
pub async fn login(
Extension(client): Extension<Arc<Mutex<Database>>>,
Json(body): Json<LoginRequest>,

) -> impl IntoResponse {
let client = client.lock().await;
let collection = client.collection("users");
Expand Down Expand Up @@ -67,7 +66,18 @@ pub async fn login(
let user: Option<User> = user.unwrap();
if let Some(user) = user {
let keypair = load_keypair().await;
let credentials = decrypt(&keypair.0, &body.credentials.as_bytes()).await;
let credentials = hex::decode(&body.credentials);
if let Err(_) = credentials {
let response = ErrorResponse {
status: ResponseStatus::Error,
code: 400,
message: "Invalid credentials".to_string(),
};
let response = json!(response);
return (StatusCode::BAD_REQUEST, Json(response));
}
let credentials = credentials.unwrap();
let credentials = decrypt(&keypair.0, &credentials).await;
let credentials = serde_json::from_str(&credentials);
if let Err(_) = credentials {
let response = ErrorResponse {
Expand All @@ -92,6 +102,7 @@ pub async fn login(
return (StatusCode::INTERNAL_SERVER_ERROR, Json(response));
}
let token = token.unwrap();
println!("{:?}", token.clone());
let response: SuccessResponse<String, ()> = SuccessResponse {
status: ResponseStatus::Success,
code: 200,
Expand Down
45 changes: 45 additions & 0 deletions src/tests/apis/auth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
Use user: 105
password: 105
ObjectId: 65e6fa210edc81d012ec483a
*/

#[cfg(test)]
mod tests {
use crate::database::create_client;
use crate::routers::auth::{login, LoginCredentials, LoginRequest};
use crate::utils::jwt::TokenType;
use crate::utils::rsa::{encrypt, load_keypair};
use axum::extract::Extension;
use axum::response::IntoResponse;
use axum::Json;
use std::sync::Arc;
use std::time::SystemTime;
use tokio::sync::Mutex;

#[tokio::test]
async fn test_login() {
let client = create_client().await.unwrap();
let client = Arc::new(Mutex::new(client));
let client = Extension(client);
let credential = LoginCredentials {
password: "105".to_string(),
timestamp: SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_millis() as u64,
};
let credential = serde_json::to_string(&credential).unwrap();
let (_, public_key) = load_keypair().await;
let credential = encrypt(&public_key, credential.as_str());
let credential = hex::encode(credential);
let request = LoginRequest {
credentials: credential,
userid: "65e6fa210edc81d012ec483a".to_string(),
term: TokenType::LongTerm
};
let result = login(client, Json(request)).await;
let result = result.into_response();
assert!(result.status().is_success())
}
}
4 changes: 2 additions & 2 deletions src/utils/aes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ pub fn generate_aes256_key() -> String {
}

pub fn read_aes256_key() -> String {
let key = std::fs::read_to_string("aes256.key");
let key = std::fs::read_to_string("aes.key");
if let Err(_) = key {
let key = generate_aes256_key();
let _ = std::fs::write("aes256.key", key.as_bytes());
let _ = std::fs::write("aes.key", key.as_bytes());
return key;
}
key.unwrap()
Expand Down

0 comments on commit 3c4be71

Please sign in to comment.