mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-07 09:57:47 +08:00
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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user