jwt guard: option to allow or not first admin

This commit is contained in:
Philippe Loctaux 2023-05-05 23:41:37 +02:00
parent 05373a2800
commit 85facf7dc6
5 changed files with 75 additions and 16 deletions

View file

@ -28,7 +28,7 @@ impl AccessToken {
let (_, access_token) = raw.split_once(' ').ok_or(BearerAuthError::Empty)?;
// Validate
match validate_jwt::<JwtClaims>(access_token.to_string(), request, None).await {
match validate_jwt::<JwtClaims>(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),

View file

@ -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<String> {
request
.cookies()
@ -59,6 +71,7 @@ pub async fn validate_jwt<T>(
jwt: String,
request: &Request<'_>,
get_admin: Option<bool>,
specific_user: Option<SpecificUser<'_>>,
) -> Result<Option<JwtClaims>, Outcome<T, Error>> {
// Get database
let db = match request.guard::<&Database>().await {
@ -67,8 +80,18 @@ pub async fn validate_jwt<T>(
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<T>(
}
};
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<Option<JwtClaims>, 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<bool>,
) -> Outcome<JwtClaims, Error> {
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<bool>,
specific_user: Option<SpecificUser<'_>>,
) -> Outcome<JwtClaims, Error> {
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(()),

View file

@ -14,7 +14,7 @@ impl<'r> FromRequest<'r> for JwtAdmin {
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
let get_admin: Option<bool> = Some(true);
use_access_token_or_refresh_token(request, get_admin)
use_access_token_or_refresh_token(request, get_admin, None)
.await
.map(Self)
}

View file

@ -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)
{

View file

@ -14,7 +14,7 @@ impl<'r> FromRequest<'r> for JwtUser {
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
let get_admin: Option<bool> = None;
use_access_token_or_refresh_token(request, get_admin)
use_access_token_or_refresh_token(request, get_admin, None)
.await
.map(Self)
}