mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-07-27 10:05:41 +00:00
security(p2p): close escrow-RPC anon-drain (release/lock behind signed edge functions)
Round 2 — fixes the CRITICAL discovered during round 1 (as severe as the withdrawal BOLA): lock_/release_/refund_escrow_internal had default PUBLIC EXECUTE and the client called release_escrow_internal directly with the anon key, so anyone could drain any victim's LOCKED balance. - release_escrow_internal now reachable only via new signed confirm-payment edge function: verifies seller wallet signature (raw + <Bytes>), asserts the wallet owns the seller identity, derived user_id == trade.seller_id, trade == payment_sent, single-use nonce, then release runs with the service role. confirmPaymentReceived now invokes confirm-payment (no more anon rpc). - lock_escrow_internal now behind new signed lock-escrow edge function (a caller can only lock its own balance; stops griefing a victim's balance). Removed a zero-amount no-op lock call. - Migration 20260725030000: REVOKE EXECUTE on lock_/release_/refund_escrow_internal from PUBLIC/anon/authenticated; GRANT to service_role only. refund has no direct client caller (service-role + admin_resolve_dispute only). - DEPLOY_RUNBOOK.md consolidates the ordered migrations, edge functions and secrets for both security rounds. Migration 20260725030000 must ship WITH the two new edge functions + frontend or offer-create/payment-release break. Remaining (non-fund, Round 2+ follow-up): read RPCs still key on a non-secret user_id (binding to a signed session would force a sign-prompt on every passive balance read — deferred); p2p_fiat_offers/trades/messages retain USING(true) (status labels move no funds now that all escrow movement is service-role-gated).
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { usePezkuwi } from '@/contexts/PezkuwiContext';
|
||||
import { useWallet } from '@/contexts/WalletContext';
|
||||
import { useP2PIdentity } from '@/contexts/P2PIdentityContext';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
@@ -25,7 +26,8 @@ interface CreateAdProps {
|
||||
export function CreateAd({ onAdCreated }: CreateAdProps) {
|
||||
const { t } = useTranslation();
|
||||
const { selectedAccount } = usePezkuwi();
|
||||
const { userId } = useP2PIdentity();
|
||||
const { signMessage } = useWallet();
|
||||
const { userId, identityId } = useP2PIdentity();
|
||||
|
||||
const [paymentMethods, setPaymentMethods] = useState<PaymentMethod[]>([]);
|
||||
const [selectedPaymentMethod, setSelectedPaymentMethod] = useState<PaymentMethod | null>(null);
|
||||
@@ -77,7 +79,7 @@ export function CreateAd({ onAdCreated }: CreateAdProps) {
|
||||
};
|
||||
|
||||
const handleCreateAd = async () => {
|
||||
if (!selectedAccount || !userId) {
|
||||
if (!selectedAccount || !userId || !identityId) {
|
||||
toast.error(t('p2p.connectWalletAndLogin'));
|
||||
return;
|
||||
}
|
||||
@@ -128,6 +130,8 @@ export function CreateAd({ onAdCreated }: CreateAdProps) {
|
||||
try {
|
||||
await createFiatOffer({
|
||||
userId,
|
||||
identityId: identityId!,
|
||||
signMessage,
|
||||
sellerWallet: selectedAccount.address,
|
||||
token,
|
||||
amountCrypto: cryptoAmt,
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
Star,
|
||||
} from 'lucide-react';
|
||||
import { useP2PIdentity } from '@/contexts/P2PIdentityContext';
|
||||
import { useWallet } from '@/contexts/WalletContext';
|
||||
import { toast } from 'sonner';
|
||||
import { supabase } from '@/lib/supabase';
|
||||
import {
|
||||
@@ -74,7 +75,8 @@ export default function P2PTrade() {
|
||||
const { tradeId } = useParams<{ tradeId: string }>();
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation();
|
||||
const { userId } = useP2PIdentity();
|
||||
const { userId, identityId, walletAddress } = useP2PIdentity();
|
||||
const { signMessage } = useWallet();
|
||||
|
||||
const [trade, setTrade] = useState<TradeWithDetails | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -295,14 +297,14 @@ export default function P2PTrade() {
|
||||
|
||||
// Handle release crypto
|
||||
const handleReleaseCrypto = async () => {
|
||||
if (!trade || !userId) {
|
||||
if (!trade || !userId || !identityId || !walletAddress) {
|
||||
toast.error(t('p2p.connectWalletAndLogin'));
|
||||
return;
|
||||
}
|
||||
|
||||
setActionLoading(true);
|
||||
try {
|
||||
await confirmPaymentReceived(trade.id, userId);
|
||||
await confirmPaymentReceived(trade.id, identityId, walletAddress, signMessage);
|
||||
toast.success(t('p2pTrade.cryptoReleasedToast'));
|
||||
fetchTrade();
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user