feat: implement service users

This commit is contained in:
2025-10-28 06:51:34 +04:00
parent b635750e28
commit d1ef5c032e
20 changed files with 450 additions and 126 deletions

View File

@@ -1,18 +1,64 @@
use crate::extractors::authorized_user;
use crate::extractors::base::FromRequestAsync;
use crate::state::AppState;
use crate::utility::req_auth::get_claims_from_req;
use actix_web::body::{BoxBody, EitherBody};
use actix_web::dev::{forward_ready, Payload, Service, ServiceRequest, ServiceResponse, Transform};
use actix_web::{Error, HttpRequest, ResponseError};
use database::entity::User;
use actix_web::dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform};
use actix_web::{web, Error, HttpRequest, ResponseError};
use database::entity::sea_orm_active_enums::UserRole;
use database::query::Query;
use futures_util::future::LocalBoxFuture;
use std::future::{ready, Ready};
use std::ops::Deref;
use std::rc::Rc;
use std::sync::Arc;
use database::entity::UserType;
#[derive(Default, Clone)]
pub struct ServiceConfig {
/// Allow service users to access endpoints.
pub allow_service: bool,
/// List of required roles to access endpoints.
pub user_roles: Option<&'static [UserRole]>,
}
type ServiceKV = (Arc<[&'static str]>, Option<ServiceConfig>);
pub struct JWTAuthorizationBuilder {
pub default_config: Option<ServiceConfig>,
pub path_configs: Vec<ServiceKV>,
}
impl JWTAuthorizationBuilder {
pub fn new() -> Self {
JWTAuthorizationBuilder {
default_config: Some(ServiceConfig::default()),
path_configs: vec![],
}
}
pub fn with_default(mut self, default: Option<ServiceConfig>) -> Self {
self.default_config = default;
self
}
pub fn add_paths(mut self, paths: impl AsRef<[&'static str]>, config: Option<ServiceConfig>) -> Self {
self.path_configs.push((Arc::from(paths.as_ref()), config));
self
}
pub fn build(self) -> JWTAuthorization {
JWTAuthorization {
default_config: Arc::new(self.default_config),
path_configs: Arc::from(self.path_configs),
}
}
}
/// Middleware guard working with JWT tokens.
#[derive(Default)]
pub struct JWTAuthorization {
/// List of ignored endpoints.
pub ignore: &'static [&'static str],
pub default_config: Arc<Option<ServiceConfig>>,
pub path_configs: Arc<[ServiceKV]>,
}
impl<S, B> Transform<S, ServiceRequest> for JWTAuthorization
@@ -30,15 +76,17 @@ where
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(JWTAuthorizationMiddleware {
service: Rc::new(service),
ignore: self.ignore,
default_config: self.default_config.clone(),
path_configs: self.path_configs.clone(),
}))
}
}
pub struct JWTAuthorizationMiddleware<S> {
service: Rc<S>,
/// List of ignored endpoints.
ignore: &'static [&'static str],
default_config: Arc<Option<ServiceConfig>>,
path_configs: Arc<[ServiceKV]>,
}
impl<S, B> JWTAuthorizationMiddleware<S>
@@ -48,29 +96,68 @@ where
B: 'static,
{
/// Checking the validity of the token.
async fn check_authorization(req: &HttpRequest) -> Result<(), authorized_user::Error> {
let mut payload = Payload::None;
async fn check_authorization(
req: &HttpRequest,
allow_service_user: bool,
required_user_roles: Option<&'static [UserRole]>,
) -> Result<(), authorized_user::Error> {
let claims = get_claims_from_req(req).map_err(authorized_user::Error::from)?;
User::from_request_async(req, &mut payload)
.await
.map(|_| ())
.map_err(|e| e.as_error::<authorized_user::Error>().unwrap().clone())
let db = req
.app_data::<web::Data<AppState>>()
.unwrap()
.get_database();
let user_type = claims.user_type.unwrap_or(UserType::Default);
match user_type {
UserType::Default => {
if let Some(required_user_roles) = required_user_roles {
let Ok(Some(user)) = Query::find_user_by_id(db, &claims.id).await else {
return Err(authorized_user::Error::NoUser);
};
if !required_user_roles.contains(&user.role) {
return Err(authorized_user::Error::InsufficientRights);
}
return Ok(());
}
match Query::is_user_exists_by_id(db, &claims.id).await {
Ok(true) => Ok(()),
_ => Err(authorized_user::Error::NoUser),
}
}
UserType::Service => {
if !allow_service_user {
return Err(authorized_user::Error::NonDefaultUserType);
}
match Query::is_service_user_exists_by_id(db, &claims.id).await {
Ok(true) => Ok(()),
_ => Err(authorized_user::Error::NoUser),
}
}
}
}
fn should_skip(&self, req: &ServiceRequest) -> bool {
let path = req.match_info().unprocessed();
fn find_config(
current_path: &str,
per_route: &[ServiceKV],
default: &Option<ServiceConfig>,
) -> Option<ServiceConfig> {
for (service_paths, config) in per_route {
for service_path in service_paths.deref() {
if !service_path.eq(&current_path) {
continue;
}
self.ignore.iter().any(|ignore| {
if !path.starts_with(ignore) {
return false;
return config.clone();
}
}
if let Some(other) = path.as_bytes().get(ignore.len()) {
return [b'?', b'/'].contains(other);
}
true
})
default.clone()
}
}
@@ -87,15 +174,24 @@ where
forward_ready!(service);
fn call(&self, req: ServiceRequest) -> Self::Future {
if self.should_skip(&req) {
let fut = self.service.call(req);
return Box::pin(async move { Ok(fut.await?.map_into_left_body()) });
}
let service = Rc::clone(&self.service);
let Some(config) = Self::find_config(
req.match_info().unprocessed(),
&self.path_configs,
&self.default_config,
) else {
let fut = self.service.call(req);
return Box::pin(async move { Ok(fut.await?.map_into_left_body()) });
};
let allow_service_user = config.allow_service;
let required_user_roles = config.user_roles;
Box::pin(async move {
match Self::check_authorization(req.request()).await {
match Self::check_authorization(req.request(), allow_service_user, required_user_roles)
.await
{
Ok(_) => {
let fut = service.call(req).await?;
Ok(fut.map_into_left_body())