openauth-plugins 0.0.3

Official OpenAuth plugin modules.
Documentation
use ::http::{Method, StatusCode};
use openauth_core::api::{create_auth_endpoint, AsyncAuthEndpoint, AuthEndpointOptions};

use crate::organization::http;
use crate::organization::options::OrganizationOptions;
use crate::organization::store::OrganizationStore;

use super::team_members::retain_returned_team_member_fields;
use super::teams::retain_returned_team_fields;

pub(super) fn endpoints(options: OrganizationOptions) -> Vec<AsyncAuthEndpoint> {
    if !options.teams.enabled {
        return Vec::new();
    }
    vec![
        list_teams(options.clone()),
        list_user_teams(options.clone()),
        list_team_members(options),
    ]
}

fn list_teams(options: OrganizationOptions) -> AsyncAuthEndpoint {
    create_auth_endpoint(
        "/organization/list-teams",
        Method::GET,
        AuthEndpointOptions::new().operation_id("organizationListTeams"),
        move |context, request| {
            let options = options.clone();
            Box::pin(async move {
                let adapter = http::adapter(context)?;
                let store = OrganizationStore::new(adapter.as_ref());
                let session = require_session(context, &request, &store).await?;
                let Some(organization_id) = session.active_organization_id else {
                    return http::organization_error(
                        StatusCode::BAD_REQUEST,
                        "NO_ACTIVE_ORGANIZATION",
                    );
                };
                require_member(&store, &organization_id, &session.user.id).await?;
                let mut teams = store.teams_for_organization(&organization_id).await?;
                for team in &mut teams {
                    retain_returned_team_fields(team, &options);
                }
                http::json(StatusCode::OK, &teams)
            })
        },
    )
}

fn list_user_teams(options: OrganizationOptions) -> AsyncAuthEndpoint {
    create_auth_endpoint(
        "/organization/list-user-teams",
        Method::GET,
        AuthEndpointOptions::new().operation_id("organizationListUserTeams"),
        move |context, request| {
            let options = options.clone();
            Box::pin(async move {
                let adapter = http::adapter(context)?;
                let store = OrganizationStore::new(adapter.as_ref());
                let session = require_session(context, &request, &store).await?;
                let mut teams = Vec::new();
                for organization in store.organizations_for_user(&session.user.id).await? {
                    for team in store.teams_for_organization(&organization.id).await? {
                        if store
                            .team_member(&team.id, &session.user.id)
                            .await?
                            .is_some()
                        {
                            let mut team = team;
                            retain_returned_team_fields(&mut team, &options);
                            teams.push(team);
                        }
                    }
                }
                http::json(StatusCode::OK, &teams)
            })
        },
    )
}

fn list_team_members(options: OrganizationOptions) -> AsyncAuthEndpoint {
    create_auth_endpoint(
        "/organization/list-team-members",
        Method::GET,
        AuthEndpointOptions::new().operation_id("organizationListTeamMembers"),
        move |context, request| {
            let options = options.clone();
            Box::pin(async move {
                let adapter = http::adapter(context)?;
                let store = OrganizationStore::new(adapter.as_ref());
                let session = require_session(context, &request, &store).await?;
                let Some(team_id) = query_param(&request, "teamId")
                    .or_else(|| query_param(&request, "team_id"))
                    .or_else(|| session.active_team_id.clone())
                else {
                    return http::organization_error(
                        StatusCode::BAD_REQUEST,
                        "YOU_DO_NOT_HAVE_AN_ACTIVE_TEAM",
                    );
                };
                let Some(team) = store.team_by_id(&team_id).await? else {
                    return http::organization_error(StatusCode::BAD_REQUEST, "TEAM_NOT_FOUND");
                };
                require_member(&store, &team.organization_id, &session.user.id).await?;
                if store
                    .team_member(&team.id, &session.user.id)
                    .await?
                    .is_none()
                {
                    return http::organization_error(
                        StatusCode::FORBIDDEN,
                        "YOU_CAN_NOT_ACCESS_THE_MEMBERS_OF_THIS_TEAM",
                    );
                }
                let mut members = store.team_members(&team.id).await?;
                for member in &mut members {
                    retain_returned_team_member_fields(member, &options);
                }
                http::json(StatusCode::OK, &members)
            })
        },
    )
}

async fn require_session(
    context: &openauth_core::context::AuthContext,
    request: &openauth_core::api::ApiRequest,
    store: &OrganizationStore<'_>,
) -> Result<http::CurrentSession, openauth_core::error::OpenAuthError> {
    http::current_session(context, request, store)
        .await?
        .ok_or_else(|| openauth_core::error::OpenAuthError::Api("Unauthorized".to_owned()))
}

fn query_param(request: &openauth_core::api::ApiRequest, name: &str) -> Option<String> {
    request.uri().query().and_then(|query| {
        query.split('&').find_map(|pair| {
            let (key, value) = pair.split_once('=')?;
            (key == name).then(|| value.to_owned())
        })
    })
}

async fn require_member(
    store: &OrganizationStore<'_>,
    organization_id: &str,
    user_id: &str,
) -> Result<crate::organization::Member, openauth_core::error::OpenAuthError> {
    store
        .member_by_org_user(organization_id, user_id)
        .await?
        .ok_or_else(|| openauth_core::error::OpenAuthError::Api("Member not found".to_owned()))
}