Gate scoped BTCPay connect to sandbox + non-mainnet
Slices 3-4 of agent-payment-connect: a scoped key carrying the a-la-carte payment_providers:write scope may connect a BTCPay provider, but only on a sandbox daemon (KEYSAT_SANDBOX_MODE) and only for a non-mainnet (regtest/testnet/signet) store. Master may connect any network; disconnect and production/mainnet reconnect stay master-only. A credential that can repoint settlement is a fund-redirection key, so the gate is deliberately narrow and fails closed. - require_provider_connect: outer gate (sandbox flag) at start_connect - btcpay/network.rs classify_address_network + client::fetch_onchain_network: resolve the store network at finish_connect, fail-closed to mainnet on any ambiguity (no on-chain method, non-2xx, non-JSON, unknown prefix), before any webhook/persist side effect - initiator carried across the OAuth round-trip via btcpay_authorize_state (migration 0025: scoped_initiator + initiator_actor_hash); scoped connects are audited - the GET callback now returns the error's HTTP status (was a misleading 200 on a denied connect) - openapi.rs documents the BTCPay connect/callback/status/disconnect paths and the key-creation scopes field Validated end-to-end against a live regtest BTCPay. Full suite green; adds gate + network unit/integration tests.
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
-- Carry the connect *initiator* through the BTCPay OAuth round trip.
|
||||
--
|
||||
-- agent-payment-connect (plans/agent-payment-connect-scope.md): a scoped key
|
||||
-- bearing `payment_providers:write` may start a BTCPay connect, but only on a
|
||||
-- sandbox daemon (outer gate) AND only for a non-mainnet store (inner gate).
|
||||
-- The inner gate can only be evaluated at callback time — that's the first
|
||||
-- moment we know the store and can resolve its network. So the connect handler
|
||||
-- must remember, across the operator's browser round-trip to BTCPay, whether
|
||||
-- the initiator was the master key (may connect any network) or a scoped key
|
||||
-- (restricted to non-mainnet).
|
||||
--
|
||||
-- `scoped_initiator`: 0 = master (no network restriction), 1 = scoped key
|
||||
-- (callback enforces non-mainnet, fail-closed). Default 0 keeps any in-flight
|
||||
-- pre-upgrade state token behaving as a master connect (the only kind that
|
||||
-- existed before this migration).
|
||||
-- `initiator_actor_hash`: sha256 of the initiating credential, so the callback
|
||||
-- can write an audit row attributing the scoped connect without a header.
|
||||
--
|
||||
-- Additive, one-way (consistent with 0020-0022). The table is also pruned by
|
||||
-- timestamp, so any pre-migration rows expire within 30 minutes regardless.
|
||||
|
||||
PRAGMA foreign_keys = ON;
|
||||
|
||||
ALTER TABLE btcpay_authorize_state
|
||||
ADD COLUMN scoped_initiator INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE btcpay_authorize_state
|
||||
ADD COLUMN initiator_actor_hash TEXT;
|
||||
Reference in New Issue
Block a user