create-feltdb 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/package-versions.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// One release train keeps generated applications installable. The repository
|
|
2
2
|
// validation script checks these values against every workspace manifest.
|
|
3
|
-
export const FELTDB_PACKAGE_VERSION = '0.9.
|
|
3
|
+
export const FELTDB_PACKAGE_VERSION = '0.9.1';
|
|
4
4
|
export const feltdbPackageRange = `^${FELTDB_PACKAGE_VERSION}`;
|
|
@@ -7513,17 +7513,29 @@ async fn get_state_version(
|
|
|
7513
7513
|
json!({"application_id":target.application_id,"revision_id":contract.revision_id,"schema_version":contract.state.schema.schema_version,"state_version":state.db.sequence()?,"causal_cursor":state.db.operation_versions()?,"state_namespace":contract.environment.state_namespace}),
|
|
7514
7514
|
))
|
|
7515
7515
|
}
|
|
7516
|
+
fn require_bound_tenant(headers: &HeaderMap, tenant: &str) -> Result<(), ApiError> {
|
|
7517
|
+
let Some(bound) = headers.get("feltdb-tenant-id") else { return Ok(()); };
|
|
7518
|
+
if bound.to_str().ok() == Some(tenant) { return Ok(()); }
|
|
7519
|
+
Err(ApiError(StatusCode::FORBIDDEN, json!({
|
|
7520
|
+
"code":"AUTHORITY_SCOPE_MISMATCH",
|
|
7521
|
+
"message":"request tenant does not match the authenticated application authority",
|
|
7522
|
+
"http_status":403
|
|
7523
|
+
}).to_string()))
|
|
7524
|
+
}
|
|
7525
|
+
|
|
7516
7526
|
async fn execute_canonical_query(
|
|
7517
7527
|
State(state): State<AppState>,
|
|
7518
7528
|
Extension(principal): Extension<Principal>,
|
|
7529
|
+
headers: HeaderMap,
|
|
7519
7530
|
Json(input): Json<CanonicalQueryRequest>,
|
|
7520
7531
|
) -> Result<Json<Value>, ApiError> {
|
|
7521
|
-
let
|
|
7532
|
+
let tenant = application_scope(
|
|
7522
7533
|
&state,
|
|
7523
7534
|
&principal.key_id,
|
|
7524
7535
|
&input.application_id,
|
|
7525
7536
|
"state:read",
|
|
7526
7537
|
)?;
|
|
7538
|
+
require_bound_tenant(&headers, &tenant)?;
|
|
7527
7539
|
let contract = revision_contract(
|
|
7528
7540
|
&state,
|
|
7529
7541
|
&principal.key_id,
|
|
@@ -7718,6 +7730,7 @@ async fn execute_revision_recovery(
|
|
|
7718
7730
|
async fn execute_canonical_transaction(
|
|
7719
7731
|
State(state): State<AppState>,
|
|
7720
7732
|
Extension(principal): Extension<Principal>,
|
|
7733
|
+
headers: HeaderMap,
|
|
7721
7734
|
Json(mut input): Json<CanonicalTransactionRequest>,
|
|
7722
7735
|
) -> Result<Json<Value>, ApiError> {
|
|
7723
7736
|
let tenant = application_scope(
|
|
@@ -7726,6 +7739,7 @@ async fn execute_canonical_transaction(
|
|
|
7726
7739
|
&input.application_id,
|
|
7727
7740
|
"state:write",
|
|
7728
7741
|
)?;
|
|
7742
|
+
require_bound_tenant(&headers, &tenant)?;
|
|
7729
7743
|
let contract = revision_contract(
|
|
7730
7744
|
&state,
|
|
7731
7745
|
&principal.key_id,
|
|
@@ -7733,6 +7747,13 @@ async fn execute_canonical_transaction(
|
|
|
7733
7747
|
&input.revision_id,
|
|
7734
7748
|
&input.environment,
|
|
7735
7749
|
)?;
|
|
7750
|
+
if !input.transaction.tenant_id.is_empty() && input.transaction.tenant_id != tenant {
|
|
7751
|
+
return Err(ApiError(StatusCode::FORBIDDEN, json!({
|
|
7752
|
+
"code":"AUTHORITY_SCOPE_MISMATCH",
|
|
7753
|
+
"message":"transaction tenant does not match the bound authority",
|
|
7754
|
+
"http_status":403
|
|
7755
|
+
}).to_string()));
|
|
7756
|
+
}
|
|
7736
7757
|
input.transaction.tenant_id = tenant.clone();
|
|
7737
7758
|
input.transaction.application_id = input.application_id.clone();
|
|
7738
7759
|
input.transaction.revision_id = contract.revision_id.clone();
|
|
@@ -15002,8 +15023,8 @@ mod worker_identity_tests {
|
|
|
15002
15023
|
|
|
15003
15024
|
#[cfg(test)]
|
|
15004
15025
|
mod authority_gate_tests {
|
|
15005
|
-
use super::{api_error_body, authorize_transaction_collections, state_authorization};
|
|
15006
|
-
use axum::http::StatusCode;
|
|
15026
|
+
use super::{api_error_body, authorize_transaction_collections, require_bound_tenant, state_authorization};
|
|
15027
|
+
use axum::http::{HeaderMap, HeaderValue, StatusCode};
|
|
15007
15028
|
use feltdb::{
|
|
15008
15029
|
application::{
|
|
15009
15030
|
manifest_hash, ApplicationManifest, ApplicationRevision, PolicyDefinition,
|
|
@@ -15111,6 +15132,17 @@ mod authority_gate_tests {
|
|
|
15111
15132
|
assert!(!allowed);
|
|
15112
15133
|
}
|
|
15113
15134
|
|
|
15135
|
+
#[test]
|
|
15136
|
+
fn bound_tenant_header_fails_closed_on_mismatch() {
|
|
15137
|
+
let mut headers = HeaderMap::new();
|
|
15138
|
+
headers.insert("feltdb-tenant-id", HeaderValue::from_static("tenant-a"));
|
|
15139
|
+
assert!(require_bound_tenant(&headers, "tenant-a").is_ok());
|
|
15140
|
+
let error = require_bound_tenant(&headers, "tenant-b").unwrap_err();
|
|
15141
|
+
assert_eq!(error.0, StatusCode::FORBIDDEN);
|
|
15142
|
+
let body: serde_json::Value = serde_json::from_str(&error.1).unwrap();
|
|
15143
|
+
assert_eq!(body.get("code").and_then(serde_json::Value::as_str), Some("AUTHORITY_SCOPE_MISMATCH"));
|
|
15144
|
+
}
|
|
15145
|
+
|
|
15114
15146
|
#[test]
|
|
15115
15147
|
fn every_forbidden_error_uses_the_canonical_wire_contract() {
|
|
15116
15148
|
for internal_message in [
|