feat(security): harden gateway boundaries and usage policies

Consolidate subscription usage policy enforcement, privacy-safe persistence, and gateway security hardening into one reviewable change.

Includes bounded HTTP and execution envelopes, header and protocol guards, DNS and relay validation, authentication and secret projection hardening, secure backup/install paths, and regression coverage.
This commit is contained in:
elky
2026-09-04 03:45:52 +08:00
parent ddcbeb3ae9
commit 579f2c7cc1
1019 changed files with 190437 additions and 26080 deletions
@@ -4,8 +4,9 @@ use sqlx::{PgPool, Row};
use aether_data_contracts::repository::billing::{
AdminBillingCollectorRecord, AdminBillingCollectorWriteInput, AdminBillingMutationOutcome,
AdminBillingPresetApplyResult, AdminBillingRuleRecord, AdminBillingRuleWriteInput,
BillingPlanRecord, BillingPlanWriteInput, BillingReadRepository, PaymentGatewayConfigRecord,
PaymentGatewayConfigWriteInput, StoredBillingModelContext, UserDailyQuotaAvailabilityRecord,
BillingPlanRecord, BillingPlanWriteInput, BillingReadRepository,
PaymentGatewayConfigCasWriteInput, PaymentGatewayConfigRecord, PaymentGatewayConfigWriteInput,
PaymentGatewaySecretCasUpdate, StoredBillingModelContext, UserDailyQuotaAvailabilityRecord,
UserPlanEntitlementRecord,
};
use aether_data_contracts::DataLayerError;
@@ -718,6 +719,120 @@ LIMIT 1
row.as_ref().map(map_payment_gateway_config_row).transpose()
}
async fn compare_and_swap_payment_gateway_secret(
&self,
update: &PaymentGatewaySecretCasUpdate,
) -> Result<bool, DataLayerError> {
let result = sqlx::query(
r#"
UPDATE payment_gateway_configs
SET merchant_key_encrypted = $3
WHERE provider = $1
AND merchant_key_encrypted = $2
"#,
)
.bind(update.provider.trim().to_ascii_lowercase())
.bind(&update.expected_merchant_key_encrypted)
.bind(&update.merchant_key_encrypted)
.execute(&self.pool)
.await
.map_postgres_err()?;
Ok(result.rows_affected() == 1)
}
async fn compare_and_swap_payment_gateway_config(
&self,
mutation: &PaymentGatewayConfigCasWriteInput,
) -> Result<AdminBillingMutationOutcome<PaymentGatewayConfigRecord>, DataLayerError> {
let input = &mutation.input;
let provider = input.provider.trim().to_ascii_lowercase();
let row = if mutation.expected_existing {
sqlx::query(
r#"
UPDATE payment_gateway_configs
SET
enabled = $2,
endpoint_url = $3,
callback_base_url = $4,
merchant_id = $5,
merchant_key_encrypted = CASE
WHEN $11::BOOL THEN payment_gateway_configs.merchant_key_encrypted
ELSE $6
END,
pay_currency = $7,
usd_exchange_rate = $8,
min_recharge_usd = $9,
channels_json = $10,
updated_at = NOW()
WHERE provider = $1
AND merchant_key_encrypted IS NOT DISTINCT FROM $12
RETURNING
provider, enabled, endpoint_url, callback_base_url, merchant_id,
merchant_key_encrypted, pay_currency,
CAST(usd_exchange_rate AS DOUBLE PRECISION) AS usd_exchange_rate,
CAST(min_recharge_usd AS DOUBLE PRECISION) AS min_recharge_usd,
channels_json,
CAST(EXTRACT(EPOCH FROM created_at) AS BIGINT) AS created_at_unix_secs,
CAST(EXTRACT(EPOCH FROM updated_at) AS BIGINT) AS updated_at_unix_secs
"#,
)
.bind(&provider)
.bind(input.enabled)
.bind(&input.endpoint_url)
.bind(input.callback_base_url.as_deref())
.bind(&input.merchant_id)
.bind(input.merchant_key_encrypted.as_deref())
.bind(&input.pay_currency)
.bind(input.usd_exchange_rate)
.bind(input.min_recharge_usd)
.bind(&input.channels_json)
.bind(input.preserve_existing_secret)
.bind(mutation.expected_merchant_key_encrypted.as_deref())
.fetch_optional(&self.pool)
.await
.map_postgres_err()?
} else {
sqlx::query(
r#"
INSERT INTO payment_gateway_configs (
provider, enabled, endpoint_url, callback_base_url, merchant_id,
merchant_key_encrypted, pay_currency, usd_exchange_rate, min_recharge_usd,
channels_json, created_at, updated_at
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, NOW(), NOW())
ON CONFLICT (provider) DO NOTHING
RETURNING
provider, enabled, endpoint_url, callback_base_url, merchant_id,
merchant_key_encrypted, pay_currency,
CAST(usd_exchange_rate AS DOUBLE PRECISION) AS usd_exchange_rate,
CAST(min_recharge_usd AS DOUBLE PRECISION) AS min_recharge_usd,
channels_json,
CAST(EXTRACT(EPOCH FROM created_at) AS BIGINT) AS created_at_unix_secs,
CAST(EXTRACT(EPOCH FROM updated_at) AS BIGINT) AS updated_at_unix_secs
"#,
)
.bind(&provider)
.bind(input.enabled)
.bind(&input.endpoint_url)
.bind(input.callback_base_url.as_deref())
.bind(&input.merchant_id)
.bind(input.merchant_key_encrypted.as_deref())
.bind(&input.pay_currency)
.bind(input.usd_exchange_rate)
.bind(input.min_recharge_usd)
.bind(&input.channels_json)
.fetch_optional(&self.pool)
.await
.map_postgres_err()?
};
match row.as_ref() {
Some(row) => Ok(AdminBillingMutationOutcome::Applied(
map_payment_gateway_config_row(row)?,
)),
None => Ok(AdminBillingMutationOutcome::NotFound),
}
}
async fn upsert_payment_gateway_config(
&self,
input: &PaymentGatewayConfigWriteInput,