rauthy-client 0.14.0

Client for the Rauthy OIDC IAM project
use crate::principal::PrincipalOidc;
use axum::extract::OptionalFromRequestParts;
use axum::response::IntoResponse;
use axum::{body::Body, extract::FromRequestParts, http::request::Parts, response::Response};
use axum_extra::{
    TypedHeader,
    headers::{Authorization, authorization::Bearer},
};

impl PrincipalOidc {
    #[allow(clippy::result_large_err)]
    pub fn is_admin(&self) -> Result<(), Response> {
        if self.is_admin {
            Ok(())
        } else {
            Err(Response::builder()
                .status(403)
                .body(Body::from("Admin access only"))
                .unwrap()
                .into_response())
        }
    }

    #[allow(clippy::result_large_err)]
    pub fn is_user(&self) -> Result<(), Response> {
        if self.is_user {
            Ok(())
        } else {
            Err(Response::builder()
                .status(403)
                .body(Body::from("Access Forbidden"))
                .unwrap()
                .into_response())
        }
    }

    #[allow(clippy::result_large_err)]
    pub fn has_any_group(&self, group: Vec<&str>) -> Result<(), Response> {
        for g in &self.groups {
            if group.contains(&g.as_str()) {
                return Ok(());
            }
        }
        Err(Response::builder()
            .status(403)
            .body(Body::from("Groups do not match"))
            .unwrap()
            .into_response())
    }

    #[allow(clippy::result_large_err)]
    pub fn has_any_role(&self, roles: Vec<&str>) -> Result<(), Response> {
        for r in &self.roles {
            if roles.contains(&r.as_str()) {
                return Ok(());
            }
        }
        Err(Response::builder()
            .status(403)
            .body(Body::from("Roles do not match"))
            .unwrap()
            .into_response())
    }
}

impl<S> FromRequestParts<S> for PrincipalOidc
where
    S: Send + Sync,
{
    type Rejection = Response<Body>;

    #[inline(always)]
    async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
        let TypedHeader(Authorization(bearer)) =
            <TypedHeader<Authorization<Bearer>> as FromRequestParts<S>>::from_request_parts(
                parts, state,
            )
            .await
            .map_err(|_| Response::builder().status(401).body(Body::empty()).unwrap())?;

        match PrincipalOidc::from_token_validated(bearer.token()).await {
            Ok(p) => {
                p.is_user()?;
                Ok(p)
            }
            Err(_err) => Err(Response::builder().status(401).body(Body::empty()).unwrap()),
        }
    }
}

impl<S> OptionalFromRequestParts<S> for PrincipalOidc
where
    S: Send + Sync,
{
    type Rejection = Response<Body>;

    #[inline(always)]
    async fn from_request_parts(
        parts: &mut Parts,
        state: &S,
    ) -> Result<Option<Self>, Self::Rejection> {
        // Extract the token from the authorization header
        if let Ok(TypedHeader(Authorization(bearer))) =
            <TypedHeader<Authorization<Bearer>> as FromRequestParts<S>>::from_request_parts(
                parts, state,
            )
            .await
            && let Ok(p) = PrincipalOidc::from_token_validated(bearer.token()).await
            && p.is_user().is_ok()
        {
            return Ok(Some(p));
        }

        Ok(None)
    }
}