aboutsummaryrefslogtreecommitdiffstats
path: root/src/remake/api.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/remake/api.rs')
-rw-r--r--src/remake/api.rs50
1 files changed, 41 insertions, 9 deletions
diff --git a/src/remake/api.rs b/src/remake/api.rs
index 7aeb675..d3df908 100644
--- a/src/remake/api.rs
+++ b/src/remake/api.rs
@@ -302,11 +302,12 @@ async fn wait_until_final<H: Host>(host: &H, id: &str) -> Option<Report> {
/// `POST /admin/remakes`.
pub(crate) async fn start<H: Host>(
State(host): State<H>,
+ axum::Extension(user): axum::Extension<crate::api::auth::AuthenticatedUser>,
Query(query): Query<StartQuery>,
headers: HeaderMap,
body: Bytes,
) -> Response {
- start_kind(host, query, &headers, body).await
+ start_kind(host, query, &headers, body, Some(user.0)).await
}
async fn start_kind<H: Host>(
@@ -314,6 +315,7 @@ async fn start_kind<H: Host>(
query: StartQuery,
headers: &HeaderMap,
body: Bytes,
+ user: Option<String>,
) -> Response {
if !host.is_node_ready().await {
return error_response(StatusCode::SERVICE_UNAVAILABLE, "node is not ready", None);
@@ -515,6 +517,7 @@ async fn start_kind<H: Host>(
Supersede {
force: force.clone(),
active_peers: active_peers(&host).await,
+ by: user,
},
now,
grace,
@@ -631,16 +634,18 @@ pub(crate) async fn get<H: Host>(
/// `DELETE /admin/remakes/{id}`.
pub(crate) async fn abort<H: Host>(
State(host): State<H>,
+ axum::Extension(user): axum::Extension<crate::api::auth::AuthenticatedUser>,
Path(id): Path<String>,
Query(query): Query<AbortQuery>,
) -> Response {
- abort_by_id(host, id, query).await
+ abort_by_id(host, id, query, Some(user.0)).await
}
/// `DELETE /admin/remakes`: aborts whichever remake of the local epoch is still
/// undecided (proposed or expired), the oldest if there are several.
pub(crate) async fn abort_current<H: Host>(
State(host): State<H>,
+ axum::Extension(user): axum::Extension<crate::api::auth::AuthenticatedUser>,
Query(query): Query<AbortQuery>,
) -> Response {
let local = EpochRecord::from(host.current_epoch());
@@ -657,7 +662,7 @@ pub(crate) async fn abort_current<H: Host>(
.await;
match open {
- Some(id) => abort_by_id(host, id, query).await,
+ Some(id) => abort_by_id(host, id, query, Some(user.0)).await,
None => error_response(
StatusCode::NOT_FOUND,
"no remake of the local epoch is in progress",
@@ -666,7 +671,12 @@ pub(crate) async fn abort_current<H: Host>(
}
}
-async fn abort_by_id<H: Host>(host: H, id: String, query: AbortQuery) -> Response {
+async fn abort_by_id<H: Host>(
+ host: H,
+ id: String,
+ query: AbortQuery,
+ user: Option<String>,
+) -> Response {
let force = match Force::parse(query.force.as_deref(), ABORT_FORCE) {
Ok(force) => force,
Err(reason) => return error_response(StatusCode::BAD_REQUEST, reason, None),
@@ -678,7 +688,7 @@ async fn abort_by_id<H: Host>(host: H, id: String, query: AbortQuery) -> Respons
match current.phase {
Phase::Proposed => {}
- Phase::Expired => return abort_expired(&host, current, &force).await,
+ Phase::Expired => return abort_expired(&host, current, &force, user).await,
Phase::Committed => {
return error_response(
StatusCode::CONFLICT,
@@ -750,7 +760,12 @@ async fn abort_by_id<H: Host>(host: H, id: String, query: AbortQuery) -> Respons
/// Aborts expired remake `current` from this node: its coordinator is gone, and can't
/// commit it any more (`coordinator`), unless `abort_obstacle` says otherwise. Answers
/// with the final report.
-async fn abort_expired<H: Host>(host: &H, current: Report, force: &Force) -> Response {
+async fn abort_expired<H: Host>(
+ host: &H,
+ current: Report,
+ force: &Force,
+ user: Option<String>,
+) -> Response {
// Whether it expired is judged by this node's clock: one far ahead would abort a
// remake its coordinator may still commit (a contested remake).
if let Some(refused) = clock_obstacle(host, force, "whether the remake expired").await {
@@ -785,8 +800,10 @@ async fn abort_expired<H: Host>(host: &H, current: Report, force: &Force) -> Res
decided_at: host.now_millis(),
};
let clock = host.clock();
+ // As the user who asks, who may write remakes (`api::access`): the rules take a
+ // decision but the coordinator's from one who may write it.
let written = host
- .commit_doc(move |doc| -> Result<bool, StoreError> {
+ .commit_doc_for(user, move |doc| -> Result<bool, StoreError> {
let now = clock.now_millis();
let expired = records::load(doc, &record_id).is_some_and(|record| {
record.phase(now, grace) == Phase::Expired && now >= abortable_at(&record, grace)
@@ -906,7 +923,8 @@ pub(crate) mod testing {
CONTENT_TYPE,
axum::http::HeaderValue::from_static("application/json"),
);
- into_json(start_kind(host, query, &headers, Bytes::from(body.to_string())).await).await
+ into_json(start_kind(host, query, &headers, Bytes::from(body.to_string()), None).await)
+ .await
}
/// `DELETE /admin/remakes/{id}` with `?force=`.
@@ -919,7 +937,21 @@ pub(crate) mod testing {
wait: false,
force: force.map(str::to_owned),
};
- into_json(abort_by_id(host, id.to_owned(), query).await).await
+ into_json(abort_by_id(host, id.to_owned(), query, None).await).await
+ }
+
+ /// `DELETE /admin/remakes/{id}` with `?force=`, asked by `user`.
+ pub(crate) async fn abort_as<H: Host>(
+ host: H,
+ id: &str,
+ force: Option<&str>,
+ user: &str,
+ ) -> (StatusCode, JsonValue) {
+ let query = AbortQuery {
+ wait: false,
+ force: force.map(str::to_owned),
+ };
+ into_json(abort_by_id(host, id.to_owned(), query, Some(user.to_owned())).await).await
}
/// `GET /admin/remakes/{id}`: the report, if the remake exists here.