diff --git a/crates/ezidam/src/guards/access_token.rs b/crates/ezidam/src/guards/access_token.rs index 9d3b86c..476d2d2 100644 --- a/crates/ezidam/src/guards/access_token.rs +++ b/crates/ezidam/src/guards/access_token.rs @@ -28,7 +28,7 @@ impl AccessToken { let (_, access_token) = raw.split_once(' ').ok_or(BearerAuthError::Empty)?; // Validate - match validate_jwt::(access_token.to_string(), request, None).await { + match validate_jwt::(access_token.to_string(), request, None, None).await { Ok(jwt_claims) => match jwt_claims { Some(jwt_claims) => Ok(Some(Self(jwt_claims))), None => Ok(None), diff --git a/crates/ezidam/src/guards/jwt.rs b/crates/ezidam/src/guards/jwt.rs index 2850ba7..9e4192d 100644 --- a/crates/ezidam/src/guards/jwt.rs +++ b/crates/ezidam/src/guards/jwt.rs @@ -3,13 +3,19 @@ use crate::guards::refresh_token::get_refresh_token_from_cookie; use crate::tokens::{ JWT_COOKIE_NAME, JWT_DURATION_MINUTES, REFRESH_TOKEN_COOKIE_NAME, REFRESH_TOKEN_DURATION_DAYS, }; -use id::KeyID; +use hash::SecretString; +use id::{KeyID, UserID}; use jwt::database::Key; use jwt::{JwtClaims, PrivateKey}; +use refresh_tokens::RefreshToken; use rocket::http::Status; +use rocket::http::{Cookie, CookieJar, SameSite}; use rocket::request::Outcome; +use rocket::time::Duration; use rocket::tokio::task; use rocket::Request; +use settings::Settings; +use users::User; mod admin; mod admin_not_current; @@ -30,6 +36,7 @@ pub enum Error { RevokeRefreshTokens(refresh_tokens::Error), MarkRefreshTokenUsed(refresh_tokens::Error), GetSettings(settings::Error), + FirstAdminNotSet, ServerUrlNotSet, UnknownIp, SaveRefreshToken(refresh_tokens::Error), @@ -48,6 +55,11 @@ pub enum Error { BlockingTask(String), } +pub struct SpecificUser<'a> { + allow_first_admin: bool, + requested_user: &'a UserID, +} + pub(super) fn get_access_token_from_cookie(request: &Request) -> Option { request .cookies() @@ -59,6 +71,7 @@ pub async fn validate_jwt( jwt: String, request: &Request<'_>, get_admin: Option, + specific_user: Option>, ) -> Result, Outcome> { // Get database let db = match request.guard::<&Database>().await { @@ -67,8 +80,18 @@ pub async fn validate_jwt( Outcome::Forward(f) => return Err(Outcome::Forward(f)), }; + let mut transaction = match db.begin().await { + Ok(transaction) => transaction, + Err(_e) => { + return Err(Outcome::Failure(( + Status::InternalServerError, + Error::StartTransaction, + ))); + } + }; + // Get keys - let keys = match Key::get_all(&**db, Some(false)).await { + let keys = match Key::get_all(&mut transaction, Some(false)).await { Ok(keys) => keys, Err(e) => { return Err(Outcome::Failure(( @@ -78,6 +101,42 @@ pub async fn validate_jwt( } }; + if let Some(specific_user) = specific_user { + // Get settings + let settings = match Settings::get(&mut transaction).await { + Ok(settings) => settings, + Err(e) => { + return Err(Outcome::Failure(( + Status::InternalServerError, + Error::GetSettings(e), + ))); + } + }; + + // Get first admin + let first_admin = match settings.first_admin() { + Some(home_page) => UserID(home_page.to_string()), + None => { + return Err(Outcome::Failure(( + Status::InternalServerError, + Error::FirstAdminNotSet, + ))); + } + }; + + // If requested user is the first admin and they're not allowed + if !specific_user.allow_first_admin && specific_user.requested_user == &first_admin { + return Ok(None); + } + } + + if let Err(_e) = transaction.commit().await { + return Err(Outcome::Failure(( + Status::InternalServerError, + Error::CommitTransaction, + ))); + } + match task::spawn_blocking(move || -> Result, Error> { // Parse jwt let parsed_jwt = jwt::parse(&jwt).map_err(Error::JwtParsing)?; @@ -147,13 +206,6 @@ pub async fn use_refresh_token( request: &Request<'_>, get_admin: Option, ) -> Outcome { - use hash::SecretString; - use refresh_tokens::RefreshToken; - use rocket::http::{Cookie, CookieJar, SameSite}; - use rocket::time::Duration; - use settings::Settings; - use users::User; - // Get database let db = match request.guard::<&Database>().await { Outcome::Success(database) => database, @@ -363,6 +415,7 @@ pub async fn use_refresh_token( pub async fn use_access_token_or_refresh_token( request: &Request<'_>, get_admin: Option, + specific_user: Option>, ) -> Outcome { let access_token = get_access_token_from_cookie(request); let refresh_token = get_refresh_token_from_cookie(request); @@ -370,7 +423,7 @@ pub async fn use_access_token_or_refresh_token( match (access_token, refresh_token) { (Some(access), _) => { // If there is an access token, attempt to validate it - match validate_jwt(access, request, get_admin).await { + match validate_jwt(access, request, get_admin, specific_user).await { Ok(jwt_claims) => match jwt_claims { Some(jwt_claims) => Outcome::Success(jwt_claims), None => Outcome::Forward(()), diff --git a/crates/ezidam/src/guards/jwt/admin.rs b/crates/ezidam/src/guards/jwt/admin.rs index d9bb62a..489f5a5 100644 --- a/crates/ezidam/src/guards/jwt/admin.rs +++ b/crates/ezidam/src/guards/jwt/admin.rs @@ -14,7 +14,7 @@ impl<'r> FromRequest<'r> for JwtAdmin { async fn from_request(request: &'r Request<'_>) -> Outcome { let get_admin: Option = Some(true); - use_access_token_or_refresh_token(request, get_admin) + use_access_token_or_refresh_token(request, get_admin, None) .await .map(Self) } diff --git a/crates/ezidam/src/guards/jwt/admin_not_current.rs b/crates/ezidam/src/guards/jwt/admin_not_current.rs index ddc54be..6ce90c8 100644 --- a/crates/ezidam/src/guards/jwt/admin_not_current.rs +++ b/crates/ezidam/src/guards/jwt/admin_not_current.rs @@ -1,12 +1,12 @@ use super::Error; -use crate::guards::use_access_token_or_refresh_token; +use crate::guards::{use_access_token_or_refresh_token, SpecificUser}; use crate::id::RocketUserID; use jwt::JwtClaims; use rocket::request::{FromRequest, Outcome}; use rocket::Request; #[derive(Debug)] -/// Use to allow access to an admin that is not the currently signed-in admin +/// Use to allow access to an admin that is not the currently signed-in admin, or the first admin pub struct JwtAdminNotCurrent(pub JwtClaims); #[rocket::async_trait] @@ -33,7 +33,13 @@ impl<'r> FromRequest<'r> for JwtAdminNotCurrent { None => return Outcome::Forward(()), }; - match use_access_token_or_refresh_token(request, get_admin) + // Don't allow first admin + let specific_user = SpecificUser { + allow_first_admin: false, + requested_user: &user_id.0, + }; + + match use_access_token_or_refresh_token(request, get_admin, Some(specific_user)) .await .map(Self) { diff --git a/crates/ezidam/src/guards/jwt/user.rs b/crates/ezidam/src/guards/jwt/user.rs index 5172afa..b6b4ca5 100644 --- a/crates/ezidam/src/guards/jwt/user.rs +++ b/crates/ezidam/src/guards/jwt/user.rs @@ -14,7 +14,7 @@ impl<'r> FromRequest<'r> for JwtUser { async fn from_request(request: &'r Request<'_>) -> Outcome { let get_admin: Option = None; - use_access_token_or_refresh_token(request, get_admin) + use_access_token_or_refresh_token(request, get_admin, None) .await .map(Self) }