Implement replay-safe authorized public HTTP API

This commit is contained in:
ms2k 2026-07-17 21:23:18 +09:00
commit 4c93c25692

View file

@ -4,7 +4,8 @@ use remora_credentials::{
};
use remora_domain::{OpaqueId, Target, fingerprint, ports::MutationScope};
use remora_durable::{
DurableError, MutationDisposition, MutationStatus, enqueue, execute_mutation, with_transaction,
DurableError, MutationDisposition, MutationStatus, PgTx, enqueue, execute_mutation,
with_transaction,
};
use remora_identity::{
DefaultTarget, Principal, TargetOverride, may_request_execution_grant_revocation,
@ -99,56 +100,12 @@ impl AdmissionService {
if !may_request_execution_grant_revocation(principal) {
return Err(AdmissionError::Unauthorized);
}
let principal = principal.clone();
let grant_id = grant_id.clone();
with_transaction(&self.pool, move |tx| {
Box::pin(async move {
let row = sqlx::query_as::<_, (String, Option<String>)>(
"SELECT status, credential_id
FROM execution_grants
WHERE grant_id = $1
FOR UPDATE",
)
.bind(grant_id.as_str())
.fetch_optional(tx.as_mut())
.await?
.ok_or_else(|| DurableError::Effect("grant_not_found".into()))?;
if row.0 == "revoked" {
return Ok(());
}
let now = OffsetDateTime::now_utc();
sqlx::query(
"UPDATE execution_grants
SET status = 'revoked', revoked_at = $2
WHERE grant_id = $1 AND status = 'active'",
)
.bind(grant_id.as_str())
.bind(now)
.execute(tx.as_mut())
.await?;
if row.1.is_some() {
let changed = sqlx::query(
"UPDATE execution_grant_credential_pins
SET status = 'revoked', revoked_at = COALESCE(revoked_at, $2)
WHERE grant_id = $1 AND status = 'active'",
)
.bind(grant_id.as_str())
.bind(now)
.execute(tx.as_mut())
.await?;
if changed.rows_affected() != 1 {
return Err(DurableError::Effect(
"active credential grant is missing its pin".into(),
));
}
}
enqueue(
tx,
"execution_grant.revoked",
json!({ "grant_id": grant_id }),
)
.await?;
Ok(())
})
Box::pin(
async move { Self::revoke_execution_grant_in_tx(tx, &principal, &grant_id).await },
)
})
.await
.map_err(|error| match &error {
@ -158,6 +115,62 @@ impl AdmissionService {
_ => AdmissionError::Durable(error),
})
}
pub async fn revoke_execution_grant_in_tx(
tx: &mut PgTx<'_>,
principal: &Principal,
grant_id: &OpaqueId,
) -> Result<(), DurableError> {
if !may_request_execution_grant_revocation(principal) {
return Err(DurableError::Effect("grant_unauthorized".into()));
}
let row = sqlx::query_as::<_, (String, Option<String>)>(
"SELECT status, credential_id
FROM execution_grants
WHERE grant_id = $1
FOR UPDATE",
)
.bind(grant_id.as_str())
.fetch_optional(tx.as_mut())
.await?
.ok_or_else(|| DurableError::Effect("grant_not_found".into()))?;
if row.0 == "revoked" {
return Ok(());
}
let now = OffsetDateTime::now_utc();
sqlx::query(
"UPDATE execution_grants
SET status = 'revoked', revoked_at = $2
WHERE grant_id = $1 AND status = 'active'",
)
.bind(grant_id.as_str())
.bind(now)
.execute(tx.as_mut())
.await?;
if row.1.is_some() {
let changed = sqlx::query(
"UPDATE execution_grant_credential_pins
SET status = 'revoked', revoked_at = COALESCE(revoked_at, $2)
WHERE grant_id = $1 AND status = 'active'",
)
.bind(grant_id.as_str())
.bind(now)
.execute(tx.as_mut())
.await?;
if changed.rows_affected() != 1 {
return Err(DurableError::Effect(
"active credential grant is missing its pin".into(),
));
}
}
enqueue(
tx,
"execution_grant.revoked",
json!({ "grant_id": grant_id }),
)
.await?;
Ok(())
}
}
async fn decide_submission(