From 942a520f4ddb1dabb78b014ac1beee3f871a66f0 Mon Sep 17 00:00:00 2001 From: Trezy Date: Mon, 11 May 2026 14:27:58 -0500 Subject: [PATCH] fix: prevent unauthorized privilege escalation Signed-off-by: Trezy --- src/auth/middleware.rs | 6 +----- src/spaces/routes.rs | 49 +++++++++++++++++++++++++++++++----------- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src/auth/middleware.rs b/src/auth/middleware.rs index c64a42e..4d9814f 100644 --- a/src/auth/middleware.rs +++ b/src/auth/middleware.rs @@ -254,12 +254,8 @@ impl FromRequestParts for XrpcClaims { let token = &h[7..]; match crate::spaces::credential::peek_jwt_typ(token) { Some(typ) if typ == "space_credential" => { - let sub = - crate::spaces::credential::peek_credential_sub(token).ok_or_else( - || AppError::Auth("invalid space credential payload".into()), - )?; Ok(XrpcClaims { - identity: Some(Claims::internal(sub)), + identity: None, space_credential: Some(token.to_string()), }) } diff --git a/src/spaces/routes.rs b/src/spaces/routes.rs index bf9c033..02b4dcf 100644 --- a/src/spaces/routes.rs +++ b/src/spaces/routes.rs @@ -281,6 +281,32 @@ fn require_auth(claims: &XrpcClaims) -> Result<&crate::auth::Claims, AppError> { .ok_or_else(|| AppError::Auth("This endpoint requires authentication".into())) } +/// Like `require_auth`, but also accepts a verified space credential as an +/// identity source. Use this in space endpoints that support `Bearer +/// ` in addition to DPoP auth. +async fn require_auth_or_credential( + state: &AppState, + claims: &XrpcClaims, +) -> Result { + if let Some(identity) = &claims.identity { + return Ok(identity.did().to_string()); + } + + if let Some(token) = &claims.space_credential { + let verified = crate::spaces::credential::verify_external_credential( + token, + &state.http, + &state.config.plc_url, + ) + .await?; + return Ok(verified.sub); + } + + Err(AppError::Auth( + "This endpoint requires authentication".into(), + )) +} + async fn resolve_space(state: &AppState, space_uri: &str) -> Result { let uri = SpaceUri::parse(space_uri)?; db::get_space_by_address( @@ -566,8 +592,7 @@ async fn create_record( xrpc_claims: XrpcClaims, Json(input): Json, ) -> Result { - let claims = require_auth(&xrpc_claims)?; - let did = claims.did().to_string(); + let did = require_auth_or_credential(&state, &xrpc_claims).await?; let space = resolve_space(&state, &input.space).await?; require_membership( &state, @@ -616,8 +641,7 @@ async fn put_record( xrpc_claims: XrpcClaims, Json(input): Json, ) -> Result { - let claims = require_auth(&xrpc_claims)?; - let did = claims.did().to_string(); + let did = require_auth_or_credential(&state, &xrpc_claims).await?; let space = resolve_space(&state, &input.space).await?; require_membership( &state, @@ -708,8 +732,7 @@ async fn apply_writes( xrpc_claims: XrpcClaims, Json(input): Json, ) -> Result, AppError> { - let claims = require_auth(&xrpc_claims)?; - let did = claims.did().to_string(); + let did = require_auth_or_credential(&state, &xrpc_claims).await?; let space = resolve_space(&state, &input.space).await?; require_membership( &state, @@ -838,12 +861,12 @@ async fn get_record( xrpc_claims: XrpcClaims, Query(query): Query, ) -> Result, AppError> { - let claims = require_auth(&xrpc_claims)?; + let did = require_auth_or_credential(&state, &xrpc_claims).await?; let space = resolve_space(&state, &query.space).await?; require_membership( &state, &space, - claims.did(), + &did, false, xrpc_claims.space_credential.as_deref(), ) @@ -871,12 +894,12 @@ async fn list_records( xrpc_claims: XrpcClaims, Query(query): Query, ) -> Result, AppError> { - let claims = require_auth(&xrpc_claims)?; + let did = require_auth_or_credential(&state, &xrpc_claims).await?; let space = resolve_space(&state, &query.space).await?; require_membership( &state, &space, - claims.did(), + &did, false, xrpc_claims.space_credential.as_deref(), ) @@ -886,7 +909,7 @@ async fn list_records( if xrpc_claims.space_credential.is_some() { None } else { - Some(claims.did()) + Some(did.as_str()) } }); @@ -935,11 +958,11 @@ async fn list_members( let space = resolve_space(&state, &query.space).await?; if !space.config.membership_public { - let claims = require_auth(&xrpc_claims)?; + let did = require_auth_or_credential(&state, &xrpc_claims).await?; require_membership( &state, &space, - claims.did(), + &did, false, xrpc_claims.space_credential.as_deref(), ) -- 2.51.2