mirror of
https://github.com/n08i40k/schedule-parser-rusted.git
synced 2026-01-26 13:35:13 +03:00
feat: implement service users
This commit is contained in:
@@ -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(¤t_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())
|
||||
|
||||
Reference in New Issue
Block a user