diff --git a/src/auth.rs b/src/auth.rs index efd991d..cb4e4b5 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -12,7 +12,7 @@ use crate::{ tokens::{NewRefreshToken, RefreshToken}, users::{NewUser, User}, }, - errors::RBError, + errors::RbError, schema::{refresh_tokens::dsl as refresh_tokens, users::dsl as users}, }; @@ -22,16 +22,16 @@ pub fn verify_user(conn: &PgConnection, username: &str, password: &str) -> crate let user = users::users .filter(users::username.eq(username)) .first::(conn) - .map_err(|_| RBError::UnknownUser)?; + .map_err(|_| RbError::AuthUnknownUser)?; // Check if a user is blocked if user.blocked { - return Err(RBError::BlockedUser); + return Err(RbError::AuthBlockedUser); } match verify_encoded(user.password.as_str(), password.as_bytes()) { Ok(true) => Ok(user), - _ => Err(RBError::InvalidPassword), + _ => Err(RbError::AuthInvalidPassword), } } @@ -54,9 +54,9 @@ pub struct Claims pub fn generate_jwt_token(conn: &PgConnection, user: &User) -> crate::Result { - let secret = std::env::var("JWT_KEY").map_err(|_| RBError::MissingJWTKey)?; - let key: Hmac = - Hmac::new_from_slice(secret.as_bytes()).map_err(|_| RBError::JWTCreationError)?; + let secret = std::env::var("JWT_KEY").map_err(|_| RbError::Custom("Missing JWT key."))?; + let key: Hmac = Hmac::new_from_slice(secret.as_bytes()) + .map_err(|_| RbError::Custom("Couldn't create Hmac key."))?; let current_time = Utc::now(); @@ -71,7 +71,7 @@ pub fn generate_jwt_token(conn: &PgConnection, user: &User) -> crate::Result crate::Result crate::Result // Encode the actual password let config = argon2::Config::default(); - argon2::hash_encoded(password.as_bytes(), &salt, &config).map_err(|_| RBError::PWSaltError) + argon2::hash_encoded(password.as_bytes(), &salt, &config) + .map_err(|_| RbError::Custom("Couldn't hash password.")) } pub fn create_admin_user(conn: &PgConnection, username: &str, password: &str) @@ -123,21 +124,22 @@ pub fn create_admin_user(conn: &PgConnection, username: &str, password: &str) .do_update() .set(&new_user) .execute(conn) - .map_err(|_| RBError::AdminCreationError)?; + .map_err(|_| RbError::Custom("Couldn't create admin."))?; Ok(true) } pub fn refresh_token(conn: &PgConnection, refresh_token: &str) -> crate::Result { - let token_bytes = base64::decode(refresh_token).map_err(|_| RBError::InvalidRefreshToken)?; + let token_bytes = + base64::decode(refresh_token).map_err(|_| RbError::AuthInvalidRefreshToken)?; // First, we request the token from the database to see if it's really a valid token let (token_entry, user) = refresh_tokens::refresh_tokens .inner_join(users::users) .filter(refresh_tokens::token.eq(token_bytes)) .first::<(RefreshToken, User)>(conn) - .map_err(|_| RBError::InvalidRefreshToken)?; + .map_err(|_| RbError::AuthInvalidRefreshToken)?; // If we see that the token has already been used before, we block the user. if token_entry.last_used_at.is_some() { @@ -145,16 +147,16 @@ pub fn refresh_token(conn: &PgConnection, refresh_token: &str) -> crate::Result< diesel::update(target) .set(users::blocked.eq(true)) .execute(conn) - .map_err(|_| RBError::DBError)?; + .map_err(|_| RbError::Custom("Couldn't block user."))?; - return Err(RBError::DuplicateRefreshToken); + return Err(RbError::AuthDuplicateRefreshToken); } // Now we check if the token has already expired let cur_time = Utc::now().naive_utc(); if token_entry.expires_at < cur_time { - return Err(RBError::TokenExpired); + return Err(RbError::AuthTokenExpired); } // We update the last_used_at value for the refresh token @@ -162,7 +164,7 @@ pub fn refresh_token(conn: &PgConnection, refresh_token: &str) -> crate::Result< diesel::update(target) .set(refresh_tokens::last_used_at.eq(cur_time)) .execute(conn) - .map_err(|_| RBError::DBError)?; + .map_err(|_| RbError::Custom("Couldn't update last used time."))?; generate_jwt_token(conn, &user) } diff --git a/src/db/mod.rs b/src/db/mod.rs index 8979d35..9c831dd 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1,5 +1,5 @@ pub mod tokens; pub mod users; -pub use users::{User, NewUser}; -pub use tokens::{RefreshToken, NewRefreshToken}; +pub use tokens::{NewRefreshToken, RefreshToken}; +pub use users::{NewUser, User}; diff --git a/src/db/users.rs b/src/db/users.rs index b005295..79d337a 100644 --- a/src/db/users.rs +++ b/src/db/users.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::{ - errors::RBError, + errors::RbError, schema::{users, users::dsl::*}, }; @@ -30,7 +30,9 @@ pub struct NewUser pub fn all(conn: &PgConnection) -> crate::Result> { - users.load::(conn).map_err(|_| RBError::DBError) + users + .load::(conn) + .map_err(|_| RbError::DbError("Couldn't get all users.")) } pub fn find(conn: &PgConnection, user_id: Uuid) -> Option @@ -43,10 +45,10 @@ pub fn create(conn: &PgConnection, new_user: &NewUser) -> crate::Result<()> let count = diesel::insert_into(users) .values(new_user) .execute(conn) - .map_err(|_| RBError::DBError)?; + .map_err(|_| RbError::DbError("Couldn't create user."))?; if count == 0 { - return Err(RBError::DuplicateUser); + return Err(RbError::UMDuplicateUser); } Ok(()) @@ -56,7 +58,7 @@ pub fn delete(conn: &PgConnection, user_id: Uuid) -> crate::Result<()> { diesel::delete(users.filter(id.eq(user_id))) .execute(conn) - .map_err(|_| RBError::DBError)?; + .map_err(|_| RbError::DbError("Couldn't delete user."))?; Ok(()) } diff --git a/src/errors.rs b/src/errors.rs index 969e573..886b6fe 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,9 +1,8 @@ -use std::io; - use rocket::{ http::Status, request::Request, - response::{self, Responder, Response}, + response::{self, Responder}, + serde::json::json, }; #[derive(Debug)] @@ -18,51 +17,70 @@ pub enum RbError AuthInvalidRefreshToken, AuthDuplicateRefreshToken, - Custom(&'static str), + // UM = User Management + UMDuplicateUser, + UMUnknownUser, - AdminCreationError, - DBError, - DuplicateUser, + DbError(&'static str), + Custom(&'static str), } -impl RbError { - pub fn status(&self) -> Status { - Status::NotFound +impl RbError +{ + pub fn status(&self) -> Status + { + // Every entry gets its own line for easy editing later when needed + match self { + RbError::AuthUnknownUser => Status::NotFound, + RbError::AuthBlockedUser => Status::Forbidden, + RbError::AuthInvalidPassword => Status::Unauthorized, + RbError::AuthUnauthorized => Status::Unauthorized, + RbError::AuthTokenExpired => Status::Unauthorized, + RbError::AuthRefreshTokenExpired => Status::Unauthorized, + RbError::AuthInvalidRefreshToken => Status::Unauthorized, + RbError::AuthDuplicateRefreshToken => Status::Unauthorized, + + RbError::UMDuplicateUser => Status::Conflict, + + RbError::Custom(_) => Status::InternalServerError, + _ => Status::InternalServerError, + } } - pub fn message(&self) -> &'static str { + pub fn message(&self) -> &'static str + { match self { + RbError::AuthUnknownUser => "This user doesn't exist.", + RbError::AuthBlockedUser => "This user is blocked.", + RbError::AuthInvalidPassword => "Invalid credentials.", + RbError::AuthUnauthorized => "You are not authorized to access this resource.", + RbError::AuthTokenExpired => "This token is not valid anymore.", + RbError::AuthRefreshTokenExpired => "This refresh token is not valid anymore.", + RbError::AuthInvalidRefreshToken => "This refresh token is not valid.", + RbError::AuthDuplicateRefreshToken => { + "This refresh token has already been used. The user has been blocked." + } + RbError::UMDuplicateUser => "This user already exists.", + + RbError::Custom(message) => message, + _ => "", } } } -impl<'r> Responder<'r, 'static> for RBError +impl<'r> Responder<'r, 'static> for RbError { - fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> + fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> { - let (status, message): (Status, &'static str) = match self { - RBError::UnknownUser => (Status::NotFound, "Unknown user"), - RBError::BlockedUser => (Status::Unauthorized, "This user is blocked"), - RBError::InvalidPassword => (Status::Unauthorized, "Invalid password"), - RBError::Unauthorized => (Status::Unauthorized, "Unauthorized"), - RBError::JWTTokenExpired => (Status::Unauthorized, "Token expired"), - RBError::JWTCreationError | RBError::MissingJWTKey => { - (Status::InternalServerError, "Failed to create tokens.") - } - RBError::InvalidRefreshToken | RBError::DuplicateRefreshToken => { - (Status::Unauthorized, "Invalid refresh token.") - } - RBError::DuplicateUser => (Status::Conflict, "User already exists"), - _ => (Status::InternalServerError, "Internal server error"), - }; + let status = self.status(); + let content = json!({ + "status": status.code, + "message": self.message(), + }); - let mut res = Response::new(); - res.set_status(status); - res.set_sized_body(message.len(), io::Cursor::new(message)); - - Ok(res) + content.respond_to(req) } } -pub type Result = std::result::Result; +pub type Result = std::result::Result; diff --git a/src/guards.rs b/src/guards.rs index 2994313..ae6782b 100644 --- a/src/guards.rs +++ b/src/guards.rs @@ -14,7 +14,7 @@ pub struct Bearer<'a>(&'a str); #[rocket::async_trait] impl<'r> FromRequest<'r> for Bearer<'r> { - type Error = rb::errors::RBError; + type Error = rb::errors::RbError; async fn from_request(req: &'r Request<'_>) -> Outcome { @@ -44,7 +44,7 @@ pub struct JWT(Claims); #[rocket::async_trait] impl<'r> FromRequest<'r> for JWT { - type Error = rb::errors::RBError; + type Error = rb::errors::RbError; async fn from_request(req: &'r Request<'_>) -> Outcome { @@ -54,19 +54,27 @@ impl<'r> FromRequest<'r> for JWT let secret = match std::env::var("JWT_KEY") { Ok(key) => key, Err(_) => { - return Outcome::Failure((Status::InternalServerError, Self::Error::MissingJWTKey)) + return Outcome::Failure(( + Status::InternalServerError, + Self::Error::AuthUnauthorized, + )) } }; let key: Hmac = match Hmac::new_from_slice(secret.as_bytes()) { Ok(key) => key, Err(_) => { - return Outcome::Failure((Status::InternalServerError, Self::Error::JWTError)) + return Outcome::Failure(( + Status::InternalServerError, + Self::Error::Custom("Failed to do Hmac thing."), + )) } }; // Verify token using key let claims: Claims = match bearer.verify_with_key(&key) { Ok(claims) => claims, - Err(_) => return Outcome::Failure((Status::Unauthorized, Self::Error::Unauthorized)), + Err(_) => { + return Outcome::Failure((Status::Unauthorized, Self::Error::AuthUnauthorized)) + } }; Outcome::Success(Self(claims)) @@ -79,7 +87,7 @@ pub struct User(Claims); #[rocket::async_trait] impl<'r> FromRequest<'r> for User { - type Error = rb::errors::RBError; + type Error = rb::errors::RbError; async fn from_request(req: &'r Request<'_>) -> Outcome { @@ -87,7 +95,7 @@ impl<'r> FromRequest<'r> for User // Verify key hasn't yet expired if chrono::Utc::now().timestamp() > claims.exp { - return Outcome::Failure((Status::Forbidden, Self::Error::TokenExpired)); + return Outcome::Failure((Status::Forbidden, Self::Error::AuthTokenExpired)); } Outcome::Success(Self(claims)) @@ -100,7 +108,7 @@ pub struct Admin(Claims); #[rocket::async_trait] impl<'r> FromRequest<'r> for Admin { - type Error = rb::errors::RBError; + type Error = rb::errors::RbError; async fn from_request(req: &'r Request<'_>) -> Outcome { diff --git a/src/routes/admin.rs b/src/routes/admin.rs index 87396ca..bd6f53f 100644 --- a/src/routes/admin.rs +++ b/src/routes/admin.rs @@ -1,7 +1,4 @@ -use rb::{ - db, - errors::RBError, -}; +use rb::{db, errors::RbError}; use rocket::serde::json::Json; use uuid::Uuid; @@ -27,12 +24,16 @@ async fn create_user(admin: Admin, conn: RbDbConn, user: Json) -> r } #[get("/users/")] -async fn get_user_info(_admin: Admin, conn: RbDbConn, user_id_str: &str) -> rb::Result> +async fn get_user_info( + _admin: Admin, + conn: RbDbConn, + user_id_str: &str, +) -> rb::Result> { - let user_id = Uuid::parse_str(user_id_str).map_err(|_| RBError::UnknownUser)?; + let user_id = Uuid::parse_str(user_id_str).map_err(|_| RbError::UMUnknownUser)?; match conn.run(move |c| db::users::find(c, user_id)).await { Some(user) => Ok(Json(user)), - None => Err(RBError::UnknownUser), + None => Err(RbError::UMUnknownUser), } }