Browse Source

Added jwt service

master
astria 3 months ago
parent
commit
128543eda8
  1. 36
      Cargo.lock
  2. 2
      Cargo.toml
  3. 8
      requests/auth.http
  4. 94
      src/controllers/auth.rs
  5. 1
      src/controllers/mod.rs
  6. 36
      src/http.rs
  7. 9
      src/main.rs
  8. 1
      src/middlewares/mod.rs
  9. 9
      src/middlewares/users.rs
  10. 7
      src/models/credentials.rs
  11. 2
      src/models/mod.rs
  12. 48
      src/routes/auth.rs
  13. 28
      src/routes/proxy.rs
  14. 11
      src/routes/statics.rs
  15. 1
      src/services/mod.rs
  16. 64
      src/services/users.rs
  17. 37
      src/utils/crypt.rs
  18. 3
      src/utils/db.rs
  19. 2
      src/utils/mod.rs

36
Cargo.lock

@ -105,6 +105,8 @@ checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
name = "chatty"
version = "0.1.0"
dependencies = [
"dotenv",
"futures",
"serde",
"serde_json",
"sqlx",
@ -209,6 +211,12 @@ dependencies = [
"syn",
]
[[package]]
name = "dotenv"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]]
name = "dotenvy"
version = "0.15.7"
@ -278,6 +286,21 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "futures"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.31"
@ -322,6 +345,17 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]]
name = "futures-macro"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.31"
@ -340,8 +374,10 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",

2
Cargo.toml

@ -8,3 +8,5 @@ serde = "1.0.225"
serde_json = { version = "1.0.145", features = ["default"] }
sqlx = {version = "0.8.6", features = ["postgres", "runtime-tokio"]}
tokio = {version = "1.47.1", features = ["full"]}
futures = "0.3"
dotenv = "0.15.0"

8
requests/auth.http

@ -1,8 +1,14 @@
###
POST /api/auth/login/ HTTP/1.1
POST /api/auth/login HTTP/1.1
Host: localhost:7878
User-Agent: curl/7.68.0
Accept: */*
Content-Type: application/json
{
"email": "astria@example.com",
"password": "securepassword"
}
###
POST /api/auth/register HTTP/1.1

94
src/controllers/auth.rs

@ -0,0 +1,94 @@
use tokio::net::TcpStream as TokioTcpStream;
use tokio::io::AsyncWriteExt;
use crate::models::user;
use crate::parsers::parse_json_body;
use crate::models::{HttpRequest::HttpRequest, user::User, credentials::Credentials};
use crate::middlewares::users::do_user_already_exist;
use crate::utils::crypt;
pub async fn register(http_request: HttpRequest, stream: &mut TokioTcpStream) {
let response = "HTTP/1.1 200 OK\r\n\r\nRegistration Successful";
// MIDDLEWARE: Check if user already exists
if http_request.headers.content_type.as_ref().expect("") != "application/json" {
let response = "HTTP/1.1 400 BAD REQUEST\r\n\r\nContent-Type must be application/json";
if let Err(e) = stream.write_all(response.as_bytes()).await {
eprintln!("Error writing response: {}", e);
}
return;
}
let mut user = parse_json_body::<User>(&http_request.body.expect("")).unwrap();
user.password = crate::utils::crypt::encrypt(&user.password);
if do_user_already_exist(user.clone()).await {
let response = "HTTP/1.1 409 CONFLICT\r\n\r\nUser already exists";
if let Err(e) = stream.write_all(response.as_bytes()).await {
eprintln!("Error writing response: {}", e);
}
return;
}
// SERVICE: Register the user
match crate::services::users::register(user).await {
Ok(_) => {
if let Err(e) = stream.write_all(response.as_bytes()).await {
eprintln!("Error writing response: {}", e);
}
}
Err(e) => {
let response = format!("HTTP/1.1 500 INTERNAL SERVER ERROR\r\n\r\nError: {}", e);
if let Err(e) = stream.write_all(response.as_bytes()).await {
eprintln!("Error writing response: {}", e);
}
}
}
}
pub async fn login(http_request: HttpRequest, stream: &mut TokioTcpStream) {
if http_request.headers.content_type.as_ref().expect("") != "application/json" {
let response = "HTTP/1.1 400 BAD REQUEST\r\n\r\nContent-Type must be application/json";
if let Err(e) = stream.write_all(response.as_bytes()).await {
eprintln!("Error writing response: {}", e);
}
return;
}
let body = http_request.body.as_ref().expect("");
let creds = parse_json_body::<Credentials>(&http_request.body.expect("")).unwrap();
let user = match crate::services::users::get_user_by_email(&creds.email).await {
Ok(Some(user)) => user,
Ok(None) => {
let response = "HTTP/1.1 401 UNAUTHORIZED\r\n\r\nInvalid email or password";
if let Err(e) = stream.write_all(response.as_bytes()).await {
eprintln!("Error writing response: {}", e);
}
return;
}
Err(e) => {
let response = format!("HTTP/1.1 500 INTERNAL SERVER ERROR\r\n\r\nError: {}", e);
if let Err(e) = stream.write_all(response.as_bytes()).await {
eprintln!("Error writing response: {}", e);
}
return;
}
};
if user.password != crate::utils::crypt::encrypt(&creds.password) {
let response = "HTTP/1.1 401 UNAUTHORIZED\r\n\r\nInvalid email or password";
if let Err(e) = stream.write_all(response.as_bytes()).await {
eprintln!("Error writing response: {}", e);
}
return;
}
let token = crypt::encrypt(&format!("jklhsdfhjkdfsjkhlfzerhjsdf_{}:{}", user.username, user.email));
let response = format!("HTTP/1.1 200 OK\r\n\r\nLogin Successful\r\nAuthorization: Bearer {}", token);
if let Err(e) = stream.write_all(response.as_bytes()).await {
eprintln!("Error writing response: {}", e);
}
}

1
src/controllers/mod.rs

@ -0,0 +1 @@
pub mod auth;

36
src/http.rs

@ -1,19 +1,18 @@
use std::net::{TcpListener, TcpStream};
use std::thread;
use std::io::{BufReader, BufRead, Write, prelude::*};
use crate::models::{http_header::HttpHeader, HttpRequest::HttpRequest, user::User};
use crate::parsers::{parse_headers, parse_json_body};
use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader};
use crate::models::{HttpRequest::HttpRequest};
use crate::parsers::{parse_headers};
use crate::routes::proxy::proxy;
use tokio::net::{TcpListener as TokioTcpListener, TcpStream as TokioTcpStream};
pub fn http_server() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
pub async fn http_server() {
let listener = TokioTcpListener::bind("127.0.0.1:7878").await.unwrap();
println!("Listening on port 7878");
for stream in listener.incoming() {
match stream {
Ok(stream) => {
thread::spawn(move || {
handle_connection(stream);
loop {
match listener.accept().await {
Ok((stream, _)) => {
tokio::spawn(async move {
handle_connection(stream).await;
});
}
Err(e) => {
@ -23,14 +22,15 @@ pub fn http_server() {
}
}
fn handle_connection(mut stream: TcpStream) {
async fn handle_connection(mut stream: TokioTcpStream) {
// Convert TokioTcpStream to std::io types for reading
let mut reader = BufReader::new(&mut stream);
let mut header_lines: Vec<String> = Vec::new();
// Read the HTTP request line by line
loop {
let mut line = String::new();
match reader.read_line(&mut line) {
match reader.read_line(&mut line).await {
Ok(0) => break, // EOF
Ok(_) => {
println!("Received line: {}", line.trim());
@ -45,14 +45,14 @@ fn handle_connection(mut stream: TcpStream) {
}
}
}
println!("Finished!");
let headers = parse_headers(header_lines);
let mut body = String::new();
if let Some(length) = headers.content_length {
let mut body_buffer = vec![0; length];
if let Err(e) = reader.read_exact(&mut body_buffer) {
if let Err(e) = reader.read_exact(&mut body_buffer).await {
eprintln!("Error reading body: {}", e);
} else {
body = String::from_utf8_lossy(&body_buffer).to_string();
@ -64,7 +64,5 @@ fn handle_connection(mut stream: TcpStream) {
body: if body.is_empty() { None } else { Some(body) },
};
proxy(request, &stream);
proxy(request, &mut stream).await;
}
// FUNCTION TO PARSE HTTP REQUESTS

9
src/main.rs

@ -1,14 +1,19 @@
mod http;
mod models;
mod parsers;
mod controllers;
mod utils;
mod services;
mod routes;
mod middlewares;
use dotenv::dotenv;
#[tokio::main]
async fn main() {
dotenv().ok();
println!("Starting application...");
http::http_server();
http::http_server().await;
}

1
src/middlewares/mod.rs

@ -0,0 +1 @@
pub mod users;

9
src/middlewares/users.rs

@ -0,0 +1,9 @@
use crate::models::user::User;
pub async fn do_user_already_exist(user: User) -> bool {
// Placeholder for middleware logic
let do_exist = crate::services::users::get_user_by_username(&user.username).await;
do_exist.is_ok()
}

7
src/models/credentials.rs

@ -0,0 +1,7 @@
use serde::{Serialize, Deserialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Credentials {
pub email: String,
pub password: String,
}

2
src/models/mod.rs

@ -1,4 +1,4 @@
pub mod HttpRequest;
pub mod http_header;
pub mod credentials;
pub mod user;

48
src/routes/auth.rs

@ -1,59 +1,47 @@
use crate::models::{HttpRequest::HttpRequest, user::User};
use crate::parsers::parse_json_body;
use std::net::TcpStream;
use std::io::prelude::*;
use crate::models::HttpRequest::HttpRequest;
use crate::controllers::auth;
use tokio::net::TcpStream as TokioTcpStream;
use tokio::io::AsyncWriteExt;
pub fn handle_auth_request(http_request: HttpRequest, mut stream: &TcpStream) {
pub async fn handle_auth_request(http_request: HttpRequest, stream: &mut TokioTcpStream) {
match http_request.headers.method.as_ref().expect("").as_str() {
"POST" => {
// Handle login or registration
post(http_request, &stream);
post(http_request, stream).await;
}
"GET" => {
// Handle fetching user info or logout
println!("Handling auth GET request for path: {}", http_request.headers.path.as_ref().expect("").join("/"));
let response = "HTTP/1.1 200 OK\r\n\r\nAuth GET Response";
stream.write_all(response.as_bytes()).unwrap();
if let Err(e) = stream.write_all(response.as_bytes()).await {
eprintln!("Error writing response: {}", e);
}
}
_ => {
// Method not allowed
let response = "HTTP/1.1 405 METHOD NOT ALLOWED\r\n\r\nMethod Not Allowed";
stream.write_all(response.as_bytes()).unwrap();
if let Err(e) = stream.write_all(response.as_bytes()).await {
eprintln!("Error writing response: {}", e);
}
return;
}
}
}
pub fn post(http_request: HttpRequest, mut stream: &TcpStream) {
pub async fn post(http_request: HttpRequest, stream: &mut TokioTcpStream) {
println!("Handling auth POST request: {}", http_request.headers.path.as_ref().expect("").join("/"));
match http_request.headers.path.clone().expect("").join("/").as_str() {
"/api/auth/login" => {
println!("Handling login");
let response = "HTTP/1.1 200 OK\r\n\r\nLogin Successful";
stream.write_all(response.as_bytes()).unwrap();
auth::login(http_request, stream).await;
}
"/api/auth/register" => {
register(http_request, &stream);
auth::register(http_request, stream).await;
}
_ => {
let response = "HTTP/1.1 404 NOT FOUND\r\n\r\nAuth Endpoint Not Found";
stream.write_all(response.as_bytes()).unwrap();
if let Err(e) = stream.write_all(response.as_bytes()).await {
eprintln!("Error writing response: {}", e);
}
}
}
}
pub fn register(http_request: HttpRequest, mut stream: &TcpStream) {
let response = "HTTP/1.1 200 OK\r\n\r\nRegistration Successful";
if http_request.headers.content_type.as_ref().expect("") != "application/json" {
let response = "HTTP/1.1 400 BAD REQUEST\r\n\r\nContent-Type must be application/json";
stream.write_all(response.as_bytes()).unwrap();
return;
}
let user = parse_json_body::<User>(&http_request.body.expect("")).unwrap();
println!("Registered user: {}", user.username);
stream.write_all(response.as_bytes()).unwrap();
}

28
src/routes/proxy.rs

@ -1,33 +1,33 @@
use crate::models::HttpRequest::HttpRequest;
use crate::routes::statics;
use std::net::TcpStream;
use tokio::net::TcpStream as TokioTcpStream;
use crate::routes::auth;
fn is_file(file: &str) -> bool {
file.contains('.')
}
pub fn proxy(http_request: HttpRequest, stream: &TcpStream) {
pub async fn proxy(http_request: HttpRequest, stream: &mut TokioTcpStream) {
println!("Proxying request...");
if is_file(http_request.clone().headers.path.expect("").join("/").as_str()) {
statics::serve_static_file(http_request.clone(), stream);
statics::serve_static_file(http_request.clone(), stream).await;
} else {
handle_api_request(http_request.clone(), stream);
handle_api_request(http_request.clone(), stream).await;
}
}
pub fn handle_api_request(http_request: HttpRequest, stream: &TcpStream) {
pub async fn handle_api_request(http_request: HttpRequest, stream: &mut TokioTcpStream) {
println!("{:?}", http_request.headers.path.clone().expect("")[0].as_str());
match http_request.headers.path.clone().expect("")[1].as_str() {
"api" => {
match http_request.headers.path.clone().expect("")[2].as_str() {
"auth" => auth::handle_auth_request(http_request, &stream),
_ => (),
}
},
_ => (),
};
match http_request.headers.path.clone().expect("")[1].as_str() {
"api" => {
match http_request.headers.path.clone().expect("")[2].as_str() {
"auth" => auth::handle_auth_request(http_request, stream).await,
_ => futures::future::ready(()).await,
}
},
_ => futures::future::ready(()).await,
};
}

11
src/routes/statics.rs

@ -1,9 +1,8 @@
use std::net::TcpStream;
use std::io::prelude::*;
use tokio::net::TcpStream as TokioTcpStream;
use tokio::io::AsyncWriteExt;
use crate::models::HttpRequest::HttpRequest;
pub fn serve_static_file(file_path: HttpRequest, mut stream: &TcpStream) {
pub async fn serve_static_file(file_path: HttpRequest, stream: &mut TokioTcpStream) {
let file = std::fs::read_to_string(format!("./static/{}", file_path.headers.path.expect("").join("/").trim_start_matches('/')));
@ -22,5 +21,7 @@ pub fn serve_static_file(file_path: HttpRequest, mut stream: &TcpStream) {
}
};
stream.write_all(response.as_bytes()).unwrap();
if let Err(e) = stream.write_all(response.as_bytes()).await {
eprintln!("Error writing response: {}", e);
}
}

1
src/services/mod.rs

@ -0,0 +1 @@
pub mod users;

64
src/services/users.rs

@ -0,0 +1,64 @@
use crate::models::user::User;
use sqlx::Row;
pub async fn register(user: User) -> Result<(), sqlx::Error> {
// Here you would normally insert the user into the database
// For demonstration, we'll just print the user info
println!("Registering user: {}", user.username);
let db = crate::utils::db::establish_connection().await?;
sqlx::query(
"INSERT INTO users (username, email, password) VALUES ($1, $2, $3)"
)
.bind(user.username)
.bind(user.email)
.bind(user.password)
.execute(&db)
.await?;
Ok(())
}
pub async fn get_user_by_username(username: &str) -> Result<Option<User>, sqlx::Error> {
let db = crate::utils::db::establish_connection().await?;
let user_query = sqlx::query(
"SELECT id, username, email, password FROM users WHERE username = $1"
)
.bind(username)
.fetch_optional(&db)
.await?;
let user = if let Some(row) = user_query {
Some(User {
id: row.get("id"),
username: row.get("username"),
email: row.get("email"),
password: row.get("password"),
})
} else {
None
};
Ok(user)
}
pub async fn get_user_by_email(email: &str) -> Result<Option<User>, sqlx::Error> {
let db = crate::utils::db::establish_connection().await?;
let user_query = sqlx::query(
"SELECT id, username, email, password FROM users WHERE email = $1"
)
.bind(email)
.fetch_optional(&db)
.await?;
let user = if let Some(row) = user_query {
Some(User {
id: row.get("id"),
username: row.get("username"),
email: row.get("email"),
password: row.get("password"),
})
} else {
None
};
Ok(user)
}

37
src/utils/crypt.rs

@ -0,0 +1,37 @@
pub fn encrypt(plaintext: &str) -> String {
let data = plaintext.as_bytes();
let key = std::env::var("ENCRYPTION_KEY").unwrap();
let key = key.as_bytes();
let mut encrypted = Vec::new();
for (i, byte) in data.iter().enumerate() {
let key_byte = key[i % key.len()];
let encrypted_byte = byte ^ key_byte;
print!("{:02x}", encrypted_byte);
encrypted.push(encrypted_byte);
}
println!();
encrypted.iter().map(|b| format!("{:02x}", b)).collect()
}
pub fn decrypt(ciphertext: &str) -> String {
let bytes = (0..ciphertext.len())
.step_by(2)
.map(|i| u8::from_str_radix(&ciphertext[i..i + 2], 16).unwrap())
.collect::<Vec<u8>>();
let key = std::env::var("ENCRYPTION_KEY").unwrap();
let key = key.as_bytes();
let mut decrypted = Vec::new();
for (i, byte) in bytes.iter().enumerate() {
let key_byte = key[i % key.len()];
let decrypted_byte = byte ^ key_byte;
decrypted.push(decrypted_byte);
}
String::from_utf8(decrypted).unwrap()
}

3
src/utils/db.rs

@ -1,7 +1,8 @@
use sqlx;
use sqlx::PgPool;
pub async fn establish_connection() -> Result<PgPool, sqlx::Error> {
let database_url = "non";
let database_url = "postgres://astria:Rwqfsfasxc_974@192.168.1.109/chatty";
let pool = PgPool::connect(database_url).await?;
Ok(pool)
}

2
src/utils/mod.rs

@ -0,0 +1,2 @@
pub mod db;
pub mod crypt;
Loading…
Cancel
Save