pub mod structs; use axum::{ body::Body, extract::{Json, Request}, http::{self, Response, StatusCode}, middleware::Next, }; use bcrypt::{DEFAULT_COST, hash, verify}; use chrono::Utc; use jsonwebtoken::{Header, TokenData, Validation, decode, encode}; use serde_json::{Value, json}; use unshell_lib::{debug, info}; use crate::{EXPIRE_DURATION, JWT_DECODING_KEY, JWT_ENCODING_KEY}; use structs::{AuthError, Cliams, CurrentUser, SignInData}; pub fn hash_password(password: &str) -> Result { let hash = hash(password, DEFAULT_COST)?; Ok(hash) } pub fn encode_jwt(email: String) -> Result<(String, usize), StatusCode> { let now = Utc::now(); let exp = (now + EXPIRE_DURATION).timestamp() as usize; let iat = now.timestamp() as usize; let claim = Cliams { iat, exp, email }; let token = encode(&Header::default(), &claim, &JWT_ENCODING_KEY) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; Ok((token, exp)) } pub fn decode_jwt(jwt: String) -> Result, StatusCode> { decode(&jwt, &JWT_DECODING_KEY, &Validation::default()) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR) } pub async fn authorize(mut req: Request, next: Next) -> Result, AuthError> { let auth_header = req.headers_mut().get(http::header::AUTHORIZATION); let auth_header = match auth_header { Some(header) => header.to_str().map_err(|_| AuthError { message: "Empty header is not allowed".to_string(), status_code: StatusCode::FORBIDDEN, })?, None => { return Err(AuthError { message: "Please add the JWT token to the header".to_string(), status_code: StatusCode::FORBIDDEN, }); } }; let mut header = auth_header.split_whitespace(); let (_, token) = (header.next(), header.next()); let _token_data: TokenData = match decode_jwt(token.unwrap().to_string()) { Ok(data) => data, Err(_) => { return Err(AuthError { message: "Invalid Session".to_string(), status_code: StatusCode::UNAUTHORIZED, }); } }; // // Fetch the user details from the database // let current_user = match retrieve_user_by_email(&token_data.claims.email) { // Some(user) => user, // None => { // return Err(AuthError { // message: "Unauthorized".to_string(), // status_code: StatusCode::UNAUTHORIZED, // }); // } // }; // req.extensions_mut().insert(current_user); Ok(next.run(req).await) } pub async fn sign_in(Json(user_data): Json) -> Result, StatusCode> { // 1. Retrieve user from the database let user = match retrieve_user_by_email(&user_data.username) { Some(user) => user, None => { debug!( "Denied user {}: Could not find user data", user_data.username ); return Err(StatusCode::UNAUTHORIZED); } // User not found }; // 2. Compare the password if !verify(&user_data.password, &user.password_hash) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? // Handle bcrypt errors { debug!("Denied user {}: Incorrect password hash", user.username); return Err(StatusCode::UNAUTHORIZED); // Wrong password } info!( "Authenticated user {} for {}", user_data.username, EXPIRE_DURATION ); // 3. Generate JWT let (token, experation) = encode_jwt(user.username).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; // 4. Return the token Ok(Json(json!({ "token": token, "expiration": experation, }))) } fn retrieve_user_by_email(_email: &str) -> Option { let current_user: CurrentUser = CurrentUser { username: "foo".to_string(), password_hash: hash_password("bar").unwrap(), }; Some(current_user) }