jwt guard: option to allow or not first admin
This commit is contained in:
parent
05373a2800
commit
85facf7dc6
5 changed files with 75 additions and 16 deletions
|
|
@ -28,7 +28,7 @@ impl AccessToken {
|
||||||
let (_, access_token) = raw.split_once(' ').ok_or(BearerAuthError::Empty)?;
|
let (_, access_token) = raw.split_once(' ').ok_or(BearerAuthError::Empty)?;
|
||||||
|
|
||||||
// Validate
|
// 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 {
|
Ok(jwt_claims) => match jwt_claims {
|
||||||
Some(jwt_claims) => Ok(Some(Self(jwt_claims))),
|
Some(jwt_claims) => Ok(Some(Self(jwt_claims))),
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,19 @@ use crate::guards::refresh_token::get_refresh_token_from_cookie;
|
||||||
use crate::tokens::{
|
use crate::tokens::{
|
||||||
JWT_COOKIE_NAME, JWT_DURATION_MINUTES, REFRESH_TOKEN_COOKIE_NAME, REFRESH_TOKEN_DURATION_DAYS,
|
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::database::Key;
|
||||||
use jwt::{JwtClaims, PrivateKey};
|
use jwt::{JwtClaims, PrivateKey};
|
||||||
|
use refresh_tokens::RefreshToken;
|
||||||
use rocket::http::Status;
|
use rocket::http::Status;
|
||||||
|
use rocket::http::{Cookie, CookieJar, SameSite};
|
||||||
use rocket::request::Outcome;
|
use rocket::request::Outcome;
|
||||||
|
use rocket::time::Duration;
|
||||||
use rocket::tokio::task;
|
use rocket::tokio::task;
|
||||||
use rocket::Request;
|
use rocket::Request;
|
||||||
|
use settings::Settings;
|
||||||
|
use users::User;
|
||||||
|
|
||||||
mod admin;
|
mod admin;
|
||||||
mod admin_not_current;
|
mod admin_not_current;
|
||||||
|
|
@ -30,6 +36,7 @@ pub enum Error {
|
||||||
RevokeRefreshTokens(refresh_tokens::Error),
|
RevokeRefreshTokens(refresh_tokens::Error),
|
||||||
MarkRefreshTokenUsed(refresh_tokens::Error),
|
MarkRefreshTokenUsed(refresh_tokens::Error),
|
||||||
GetSettings(settings::Error),
|
GetSettings(settings::Error),
|
||||||
|
FirstAdminNotSet,
|
||||||
ServerUrlNotSet,
|
ServerUrlNotSet,
|
||||||
UnknownIp,
|
UnknownIp,
|
||||||
SaveRefreshToken(refresh_tokens::Error),
|
SaveRefreshToken(refresh_tokens::Error),
|
||||||
|
|
@ -48,6 +55,11 @@ pub enum Error {
|
||||||
BlockingTask(String),
|
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> {
|
pub(super) fn get_access_token_from_cookie(request: &Request) -> Option<String> {
|
||||||
request
|
request
|
||||||
.cookies()
|
.cookies()
|
||||||
|
|
@ -59,6 +71,7 @@ pub async fn validate_jwt<T>(
|
||||||
jwt: String,
|
jwt: String,
|
||||||
request: &Request<'_>,
|
request: &Request<'_>,
|
||||||
get_admin: Option<bool>,
|
get_admin: Option<bool>,
|
||||||
|
specific_user: Option<SpecificUser<'_>>,
|
||||||
) -> Result<Option<JwtClaims>, Outcome<T, Error>> {
|
) -> Result<Option<JwtClaims>, Outcome<T, Error>> {
|
||||||
// Get database
|
// Get database
|
||||||
let db = match request.guard::<&Database>().await {
|
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)),
|
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
|
// 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,
|
Ok(keys) => keys,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Err(Outcome::Failure((
|
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> {
|
match task::spawn_blocking(move || -> Result<Option<JwtClaims>, Error> {
|
||||||
// Parse jwt
|
// Parse jwt
|
||||||
let parsed_jwt = jwt::parse(&jwt).map_err(Error::JwtParsing)?;
|
let parsed_jwt = jwt::parse(&jwt).map_err(Error::JwtParsing)?;
|
||||||
|
|
@ -147,13 +206,6 @@ pub async fn use_refresh_token(
|
||||||
request: &Request<'_>,
|
request: &Request<'_>,
|
||||||
get_admin: Option<bool>,
|
get_admin: Option<bool>,
|
||||||
) -> Outcome<JwtClaims, Error> {
|
) -> 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
|
// Get database
|
||||||
let db = match request.guard::<&Database>().await {
|
let db = match request.guard::<&Database>().await {
|
||||||
Outcome::Success(database) => database,
|
Outcome::Success(database) => database,
|
||||||
|
|
@ -363,6 +415,7 @@ pub async fn use_refresh_token(
|
||||||
pub async fn use_access_token_or_refresh_token(
|
pub async fn use_access_token_or_refresh_token(
|
||||||
request: &Request<'_>,
|
request: &Request<'_>,
|
||||||
get_admin: Option<bool>,
|
get_admin: Option<bool>,
|
||||||
|
specific_user: Option<SpecificUser<'_>>,
|
||||||
) -> Outcome<JwtClaims, Error> {
|
) -> Outcome<JwtClaims, Error> {
|
||||||
let access_token = get_access_token_from_cookie(request);
|
let access_token = get_access_token_from_cookie(request);
|
||||||
let refresh_token = get_refresh_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) {
|
match (access_token, refresh_token) {
|
||||||
(Some(access), _) => {
|
(Some(access), _) => {
|
||||||
// If there is an access token, attempt to validate it
|
// 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 {
|
Ok(jwt_claims) => match jwt_claims {
|
||||||
Some(jwt_claims) => Outcome::Success(jwt_claims),
|
Some(jwt_claims) => Outcome::Success(jwt_claims),
|
||||||
None => Outcome::Forward(()),
|
None => Outcome::Forward(()),
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ impl<'r> FromRequest<'r> for JwtAdmin {
|
||||||
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||||
let get_admin: Option<bool> = Some(true);
|
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
|
.await
|
||||||
.map(Self)
|
.map(Self)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
use super::Error;
|
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 crate::id::RocketUserID;
|
||||||
use jwt::JwtClaims;
|
use jwt::JwtClaims;
|
||||||
use rocket::request::{FromRequest, Outcome};
|
use rocket::request::{FromRequest, Outcome};
|
||||||
use rocket::Request;
|
use rocket::Request;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[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);
|
pub struct JwtAdminNotCurrent(pub JwtClaims);
|
||||||
|
|
||||||
#[rocket::async_trait]
|
#[rocket::async_trait]
|
||||||
|
|
@ -33,7 +33,13 @@ impl<'r> FromRequest<'r> for JwtAdminNotCurrent {
|
||||||
None => return Outcome::Forward(()),
|
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
|
.await
|
||||||
.map(Self)
|
.map(Self)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ impl<'r> FromRequest<'r> for JwtUser {
|
||||||
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||||
let get_admin: Option<bool> = None;
|
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
|
.await
|
||||||
.map(Self)
|
.map(Self)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue