use super::claims::OrgClaims;
use crate::error::TidewayError;
use crate::organizations::storage::{MembershipStore, OrganizationStore};
use axum::extract::FromRequestParts;
use axum::http::request::Parts;
use std::future::Future;
use std::marker::PhantomData;
pub struct CurrentOrg<O: OrganizationStore>(pub O::Organization, PhantomData<O>);
impl<O: OrganizationStore> CurrentOrg<O> {
pub fn new(org: O::Organization) -> Self {
Self(org, PhantomData)
}
pub fn org(&self) -> &O::Organization {
&self.0
}
pub fn into_inner(self) -> O::Organization {
self.0
}
}
impl<O, S> FromRequestParts<S> for CurrentOrg<O>
where
O: OrganizationStore + Clone + 'static,
S: Send + Sync,
{
type Rejection = TidewayError;
fn from_request_parts(
parts: &mut Parts,
_state: &S,
) -> impl Future<Output = Result<Self, Self::Rejection>> + Send {
Box::pin(async move {
let store = parts.extensions.get::<O>().cloned().ok_or_else(|| {
TidewayError::internal("OrganizationStore not found in request extensions")
})?;
let claims = parts
.extensions
.get::<OrgClaims>()
.ok_or_else(|| TidewayError::unauthorized("No organization context in token"))?;
let org = store
.find_by_id(&claims.org_id)
.await
.map_err(|e| TidewayError::internal(format!("Failed to load organization: {e}")))?
.ok_or_else(|| {
TidewayError::not_found(format!("Organization not found: {}", claims.org_id))
})?;
Ok(CurrentOrg::new(org))
})
}
}
pub struct CurrentMembership<O, M>
where
O: OrganizationStore,
M: MembershipStore,
{
pub user_id: String,
pub org: O::Organization,
pub membership: M::Membership,
pub role: M::Role,
}
impl<O, M, S> FromRequestParts<S> for CurrentMembership<O, M>
where
O: OrganizationStore + Clone + 'static,
M: MembershipStore + Clone + 'static,
S: Send + Sync,
{
type Rejection = TidewayError;
fn from_request_parts(
parts: &mut Parts,
_state: &S,
) -> impl Future<Output = Result<Self, Self::Rejection>> + Send {
Box::pin(async move {
let org_store = parts.extensions.get::<O>().cloned().ok_or_else(|| {
TidewayError::internal("OrganizationStore not found in request extensions")
})?;
let membership_store = parts.extensions.get::<M>().cloned().ok_or_else(|| {
TidewayError::internal("MembershipStore not found in request extensions")
})?;
let claims = parts
.extensions
.get::<OrgClaims>()
.ok_or_else(|| TidewayError::unauthorized("No organization context in token"))?;
let user_id = parts
.extensions
.get::<AuthenticatedUserId>()
.map(|u| u.0.clone())
.ok_or_else(|| TidewayError::unauthorized("User not authenticated"))?;
let org = org_store
.find_by_id(&claims.org_id)
.await
.map_err(|e| TidewayError::internal(format!("Failed to load organization: {e}")))?
.ok_or_else(|| {
TidewayError::not_found(format!("Organization not found: {}", claims.org_id))
})?;
let membership = membership_store
.get_membership(&claims.org_id, &user_id)
.await
.map_err(|e| TidewayError::internal(format!("Failed to load membership: {e}")))?
.ok_or_else(|| TidewayError::forbidden("Not a member of this organization"))?;
let role = membership_store.membership_role(&membership);
Ok(CurrentMembership {
user_id,
org,
membership,
role,
})
})
}
}
#[derive(Clone, Debug)]
pub struct AuthenticatedUserId(pub String);
impl AuthenticatedUserId {
#[must_use]
pub fn new(user_id: impl Into<String>) -> Self {
Self(user_id.into())
}
}