diff --git a/requests/auth.http b/requests/auth.http index 799c611..a0573ea 100644 --- a/requests/auth.http +++ b/requests/auth.http @@ -21,4 +21,10 @@ Content-Type: application/json "username": "astria", "email": "astria@example.com", "password": "securepassword" -} \ No newline at end of file +} + +### +GET /api/auth HTTP/1.1 +Host: localhost:7878 +User-Agent: curl/7.68.0 +Accept: */* \ No newline at end of file diff --git a/requests/user.http b/requests/user.http new file mode 100644 index 0000000..490629f --- /dev/null +++ b/requests/user.http @@ -0,0 +1,6 @@ +### +GET /api/user HTTP/1.1 +Host: localhost:7878 +User-Agent: curl/7.68.0 +Accept: */* +Authorization: Bearer 3e1d1902151506191008255c46523e1f1b0d150217001f0937525b503406020d1f0c092c19102b4b5e5568160212010f00331d1b3e544758375912091e \ No newline at end of file diff --git a/src/controllers/auth.rs b/src/controllers/auth.rs index 75c374c..917cb12 100644 --- a/src/controllers/auth.rs +++ b/src/controllers/auth.rs @@ -84,7 +84,7 @@ pub async fn login(http_request: HttpRequest, stream: &mut TokioTcpStream) { return; } - let token = crypt::encrypt(&format!("jklhsdfhjkdfsjkhlfzerhjsdf_{}:{}", user.username, user.email)); + let token = crypt::encrypt(&format!("{}_{}:{}", std::env::var("JWT_KEY").unwrap_or_default(), 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 { diff --git a/src/controllers/mod.rs b/src/controllers/mod.rs index 5696e21..becf829 100644 --- a/src/controllers/mod.rs +++ b/src/controllers/mod.rs @@ -1 +1,2 @@ -pub mod auth; \ No newline at end of file +pub mod auth; +pub mod user_controller; \ No newline at end of file diff --git a/src/controllers/user_controller.rs b/src/controllers/user_controller.rs new file mode 100644 index 0000000..e4ca293 --- /dev/null +++ b/src/controllers/user_controller.rs @@ -0,0 +1,63 @@ +use tokio::net::TcpStream; +use tokio::io::AsyncWriteExt; + +use crate::models::token::Token; +use crate::models::user_info::UserInfo; +use crate::models::HttpRequest::HttpRequest; +use crate::utils::{crypt, jwt}; + +pub async fn get_user_info(http_request: HttpRequest, stream: &mut TcpStream) { + // Placeholder for actual user info retrieval logic + + let authorization = http_request.headers.authorization.clone().unwrap_or_default(); + let token_str = authorization.strip_prefix("Bearer ").unwrap_or(""); + println!("Extracted token: {}", token_str); + let token = crate::utils::crypt::decrypt(token_str); + println!("Decrypted token: {:?}", token); + + if !jwt::verify_token(&token) { + let response = "HTTP/1.1 401 Unauthorized\r\n\r\nInvalid or missing token"; + if let Err(e) = stream.write_all(response.as_bytes()).await { + eprintln!("Error writing response: {}", e); + } + return; + } + + let token = match jwt::extract(&token) { + Some(token) => token, + None => { + let response = "HTTP/1.1 400 Bad Request\r\n\r\nMalformed token"; + if let Err(e) = stream.write_all(response.as_bytes()).await { + eprintln!("Error writing response: {}", e); + } + return; + } + }; + + + let user_info = match crate::services::users::get_user_profile(token).await { + Ok(Some(info)) => info, + Ok(None) => { + let response = "HTTP/1.1 404 Not Found\r\n\r\nUser not found"; + 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; + } + }; + + let response = format!( + "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n{{\"id\": {}, \"username\": \"{}\", \"email\": \"{}\"}}", + user_info.id, user_info.username, user_info.email + ); + if let Err(e) = stream.write_all(response.as_bytes()).await { + eprintln!("Error writing response: {}", e); + } +} \ No newline at end of file diff --git a/src/models/http_header.rs b/src/models/http_header.rs index 3f007a1..23bc892 100644 --- a/src/models/http_header.rs +++ b/src/models/http_header.rs @@ -6,4 +6,5 @@ pub struct HttpHeader { pub version: Option, pub content_length: Option, pub content_type: Option, + pub authorization: Option, } \ No newline at end of file diff --git a/src/models/mod.rs b/src/models/mod.rs index 60e573f..974a4ac 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,4 +1,6 @@ pub mod HttpRequest; pub mod http_header; pub mod credentials; -pub mod user; \ No newline at end of file +pub mod user; +pub mod user_info; +pub mod token; \ No newline at end of file diff --git a/src/models/token.rs b/src/models/token.rs new file mode 100644 index 0000000..e499fec --- /dev/null +++ b/src/models/token.rs @@ -0,0 +1,7 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Token { + pub email: String, + pub username: String +} \ No newline at end of file diff --git a/src/models/user_info.rs b/src/models/user_info.rs new file mode 100644 index 0000000..1589e8e --- /dev/null +++ b/src/models/user_info.rs @@ -0,0 +1,8 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct UserInfo { + pub id: i32, + pub username: String, + pub email: String, +} \ No newline at end of file diff --git a/src/parsers.rs b/src/parsers.rs index 8d5a053..cfefe5d 100644 --- a/src/parsers.rs +++ b/src/parsers.rs @@ -8,6 +8,7 @@ pub fn parse_headers(header_lines: Vec) -> HttpHeader { content_type: None, path: None, version: None, + authorization: None, }; let mut iter = 0; @@ -44,6 +45,9 @@ pub fn parse_headers(header_lines: Vec) -> HttpHeader { "content-type" => { headers.content_type = Some(splitted[1].trim().to_string()); } + "authorization" => { + headers.authorization = Some(splitted[1].trim().to_string()); + } _ => { println!("Unknown header: {}", splitted[0]); } diff --git a/src/routes/auth.rs b/src/routes/auth.rs index 5145b58..d20fd23 100644 --- a/src/routes/auth.rs +++ b/src/routes/auth.rs @@ -4,19 +4,12 @@ use tokio::net::TcpStream as TokioTcpStream; use tokio::io::AsyncWriteExt; pub async fn handle_auth_request(http_request: HttpRequest, stream: &mut TokioTcpStream) { + println!("Received auth request: {} {}", http_request.headers.method.as_ref().expect(""), http_request.headers.path.as_ref().expect("").join("/")); match http_request.headers.method.as_ref().expect("").as_str() { "POST" => { // Handle login or registration 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"; - 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"; @@ -44,4 +37,4 @@ pub async fn post(http_request: HttpRequest, stream: &mut TokioTcpStream) { } } } -} \ No newline at end of file +} diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 4a8dc69..77eacf5 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1,3 +1,4 @@ pub mod proxy; pub mod statics; -pub mod auth; \ No newline at end of file +pub mod auth; +pub mod user_routes; \ No newline at end of file diff --git a/src/routes/proxy.rs b/src/routes/proxy.rs index c6a194c..f6b199a 100644 --- a/src/routes/proxy.rs +++ b/src/routes/proxy.rs @@ -1,7 +1,7 @@ use crate::models::HttpRequest::HttpRequest; use crate::routes::statics; -use tokio::net::TcpStream as TokioTcpStream; -use crate::routes::auth; +use crate::routes::{auth, user_routes}; +use tokio::{io::AsyncWriteExt, net::TcpStream as TokioTcpStream}; fn is_file(file: &str) -> bool { file.contains('.') @@ -10,7 +10,15 @@ fn is_file(file: &str) -> bool { 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()) { + if is_file( + http_request + .clone() + .headers + .path + .expect("") + .join("/") + .as_str(), + ) { statics::serve_static_file(http_request.clone(), stream).await; } else { handle_api_request(http_request.clone(), stream).await; @@ -18,16 +26,24 @@ pub async fn proxy(http_request: HttpRequest, stream: &mut TokioTcpStream) { } pub async fn handle_api_request(http_request: HttpRequest, stream: &mut TokioTcpStream) { - - println!("{:?}", http_request.headers.path.clone().expect("")[0].as_str()); + 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).await, - _ => futures::future::ready(()).await, + "api" => match http_request.headers.path.clone().expect("")[2].as_str() { + "auth" => auth::handle_auth_request(http_request, stream).await, + "user" => user_routes::handle_user_request(http_request, stream).await, + _ => { + if let Err(e) = stream.write_all("HTTP/1.1 404 Not Found\r\n\r\n".as_bytes()).await { + eprintln!("Error writing response: {}", e); + } } }, - _ => futures::future::ready(()).await, + _ => { + if let Err(e) = stream.write_all("HTTP/1.1 404 Not Found\r\n\r\n".as_bytes()).await { + eprintln!("Error writing response: {}", e); + } + } }; - -} \ No newline at end of file +} diff --git a/src/routes/user_routes.rs b/src/routes/user_routes.rs new file mode 100644 index 0000000..79b4d3e --- /dev/null +++ b/src/routes/user_routes.rs @@ -0,0 +1,22 @@ +use crate::models::HttpRequest::HttpRequest; +use tokio::net::TcpStream as TokioTcpStream; +use tokio::io::AsyncWriteExt; + +pub async fn handle_user_request(http_request: HttpRequest, stream: &mut TokioTcpStream) { + // Placeholder for user request handling logic + let path = http_request.headers.path.clone().unwrap_or_default().join("/"); + + match path.as_str() { + "/api/user" => { + // Call the get_user_info function from user_controller + crate::controllers::user_controller::get_user_info(http_request, stream).await; + } + _ => { + let response = "HTTP/1.1 404 Not Found\r\n\r\n"; + if let Err(e) = stream.write_all(response.as_bytes()).await { + eprintln!("Error writing response: {}", e); + } + } + } + +} \ No newline at end of file diff --git a/src/services/users.rs b/src/services/users.rs index c80181d..1de2c07 100644 --- a/src/services/users.rs +++ b/src/services/users.rs @@ -1,4 +1,4 @@ -use crate::models::user::User; +use crate::models::{ user::User, user_info::UserInfo, token::Token}; use sqlx::Row; pub async fn register(user: User) -> Result<(), sqlx::Error> { @@ -61,4 +61,26 @@ pub async fn get_user_by_email(email: &str) -> Result, sqlx::Error> }; Ok(user) +} + +pub async fn get_user_profile(token: Token) -> Result, sqlx::Error> { + let db = crate::utils::db::establish_connection().await?; + let user_query = sqlx::query( + "SELECT id, username, email FROM users WHERE id = $1" + ) + .bind(token.id) + .fetch_optional(&db) + .await?; + + let user_info = if let Some(row) = user_query { + Some(UserInfo { + id: row.get("id"), + username: row.get("username"), + email: row.get("email"), + }) + } else { + None + }; + + Ok(user_info) } \ No newline at end of file diff --git a/src/utils/jwt.rs b/src/utils/jwt.rs new file mode 100644 index 0000000..972a6c2 --- /dev/null +++ b/src/utils/jwt.rs @@ -0,0 +1,28 @@ +use crate::models::token::Token; + +pub fn verify_token(token: &str) -> bool { + let token_parts: Vec<&str> = token.split('_').collect(); + if token_parts.len() != 2 { + return false; + } + + if token_parts[0] != std::env::var("JWT_KEY").unwrap_or_default() { + return false; + } + true +} + +pub fn extract(token: &str) -> Option { + let token_parts: Vec<&str> = token.split('_').collect(); + if token_parts.len() != 2 { + return None; + } + let user_info: Vec<&str> = token_parts[1].split(':').collect(); + if user_info.len() != 2 { + return None; + } + Some(Token { + username: user_info[0].to_string(), + email: user_info[1].to_string() + }) +} \ No newline at end of file diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 349ffb3..524a9bd 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,2 +1,3 @@ pub mod db; -pub mod crypt; \ No newline at end of file +pub mod crypt; +pub mod jwt; \ No newline at end of file