Implement replay-safe authorized public HTTP API
This commit is contained in:
parent
791e00f4c0
commit
4c93c25692
1 changed files with 3940 additions and 345 deletions
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Reference in a new issue