Wire scoped API keys and add advisory settle-amount tripwire
Scoped API keys (P1): migrate 58 admin endpoints from require_admin to
require_scope so ks_ keys with Read-only/License-issuer/Support/Full-admin roles
work as intended. 12 sensitive endpoints stay master-key-only (issuer key,
provider connect/disconnect, web password, api-key CRUD, db-info, operator-name,
per-license tier change). require_scope is re-exported from api::admin so both
auth gates import from one place. Adds role-boundary tests.
Settle-amount tripwire (P1): get_invoice_status now returns
ProviderInvoiceSnapshot { status, amount }. On a confirmed settle,
audit_settle_amount (shared by the webhook and reconcile issue paths) compares
the provider-reported sat amount against the invoice's amount_sats and, on drift,
logs a warning + writes an invoice.amount_mismatch audit row, then issues anyway.
Advisory by design: a hard gate would fight an operator's BTCPay payment
tolerance, and Settled already implies paid-in-full. SAT-only — skips non-SAT
settles (fiat subscription renewals) and unparseable amounts.
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
use crate::analytics::{
|
||||
self, SETTING_COLLECTOR_URL, SETTING_ENABLED, SETTING_INSTALL_UUID,
|
||||
};
|
||||
use crate::api::admin::{request_context, require_admin};
|
||||
use crate::api::admin::{request_context, require_scope};
|
||||
use crate::api::AppState;
|
||||
use crate::db::repo;
|
||||
use crate::error::{AppError, AppResult};
|
||||
@@ -31,7 +31,7 @@ pub async fn get(
|
||||
State(state): State<AppState>,
|
||||
headers: HeaderMap,
|
||||
) -> AppResult<Json<Value>> {
|
||||
require_admin(&state, &headers)?;
|
||||
require_scope(&state, &headers, "community:read").await?;
|
||||
let enabled = analytics::is_enabled(&state).await;
|
||||
let collector_url = repo::settings_get(&state.db, SETTING_COLLECTOR_URL).await?;
|
||||
let install_uuid = repo::settings_get(&state.db, SETTING_INSTALL_UUID).await?;
|
||||
@@ -84,7 +84,7 @@ pub async fn set(
|
||||
headers: HeaderMap,
|
||||
Json(req): Json<SetReq>,
|
||||
) -> AppResult<Json<Value>> {
|
||||
let actor_hash = require_admin(&state, &headers)?;
|
||||
let actor_hash = require_scope(&state, &headers, "community:write").await?;
|
||||
let (ip, ua) = request_context(&headers);
|
||||
|
||||
// Validate URL shape if one was supplied. We don't try to reach
|
||||
@@ -154,7 +154,7 @@ pub async fn reset(
|
||||
State(state): State<AppState>,
|
||||
headers: HeaderMap,
|
||||
) -> AppResult<Json<Value>> {
|
||||
let actor_hash = require_admin(&state, &headers)?;
|
||||
let actor_hash = require_scope(&state, &headers, "community:write").await?;
|
||||
let (ip, ua) = request_context(&headers);
|
||||
repo::settings_set(&state.db, SETTING_INSTALL_UUID, None).await?;
|
||||
let _ = repo::insert_audit(
|
||||
|
||||
Reference in New Issue
Block a user