Harden membership confirm with quote tx matching
This commit is contained in:
parent
66d5ea07cd
commit
04f2de2ccf
@ -415,6 +415,10 @@ func (a *app) handleMembershipConfirm(w http.ResponseWriter, r *http.Request) {
|
|||||||
writeError(w, http.StatusConflict, "quote expired")
|
writeError(w, http.StatusConflict, "quote expired")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if quote.ConfirmedAt != nil {
|
||||||
|
writeError(w, http.StatusConflict, "quote already confirmed")
|
||||||
|
return
|
||||||
|
}
|
||||||
if !strings.EqualFold(quote.Address, address) || !strings.EqualFold(quote.DesignationCode, req.DesignationCode) || quote.ChainID != req.ChainID {
|
if !strings.EqualFold(quote.Address, address) || !strings.EqualFold(quote.DesignationCode, req.DesignationCode) || quote.ChainID != req.ChainID {
|
||||||
writeError(w, http.StatusBadRequest, "quote context mismatch")
|
writeError(w, http.StatusBadRequest, "quote context mismatch")
|
||||||
return
|
return
|
||||||
@ -424,6 +428,22 @@ func (a *app) handleMembershipConfirm(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(quote.PayerAddress) == "" {
|
||||||
|
quote.PayerAddress = quote.Address
|
||||||
|
}
|
||||||
|
if err := verifyTransactionCallOnChain(
|
||||||
|
context.Background(),
|
||||||
|
a.cfg,
|
||||||
|
req.TxHash,
|
||||||
|
quote.PayerAddress,
|
||||||
|
quote.ContractAddress,
|
||||||
|
quote.Calldata,
|
||||||
|
quote.ValueHex,
|
||||||
|
); err != nil {
|
||||||
|
writeError(w, http.StatusConflict, fmt.Sprintf("tx verification pending/failed: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err := verifyMintedOnChain(context.Background(), a.cfg, req.TxHash, address); err != nil {
|
if err := verifyMintedOnChain(context.Background(), a.cfg, req.TxHash, address); err != nil {
|
||||||
writeError(w, http.StatusConflict, fmt.Sprintf("tx verification pending/failed: %v", err))
|
writeError(w, http.StatusConflict, fmt.Sprintf("tx verification pending/failed: %v", err))
|
||||||
return
|
return
|
||||||
|
|||||||
@ -329,6 +329,61 @@ func TestMembershipConfirmAcceptsOnrampAttestationAssurance(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMembershipConfirmRejectsAlreadyConfirmedQuote(t *testing.T) {
|
||||||
|
a, cfg, cleanup := newTestApp(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
ownerKey := mustKey(t)
|
||||||
|
ownerAddr := strings.ToLower(crypto.PubkeyToAddress(ownerKey.PublicKey).Hex())
|
||||||
|
|
||||||
|
intentRes := postJSONExpect[tWalletIntentResponse](t, a, "/secret/wallet/intent", walletIntentRequest{
|
||||||
|
Address: ownerAddr,
|
||||||
|
Origin: "https://edut.ai",
|
||||||
|
Locale: "en",
|
||||||
|
ChainID: cfg.ChainID,
|
||||||
|
}, http.StatusOK)
|
||||||
|
issuedAt, err := time.Parse(time.RFC3339Nano, intentRes.IssuedAt)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse issued_at: %v", err)
|
||||||
|
}
|
||||||
|
td := buildTypedData(cfg, designationRecord{
|
||||||
|
Code: intentRes.DesignationCode,
|
||||||
|
DisplayToken: intentRes.DisplayToken,
|
||||||
|
Nonce: intentRes.Nonce,
|
||||||
|
IssuedAt: issuedAt,
|
||||||
|
Origin: "https://edut.ai",
|
||||||
|
})
|
||||||
|
sig := signTypedData(t, ownerKey, td)
|
||||||
|
verifyRes := postJSONExpect[tWalletVerifyResponse](t, a, "/secret/wallet/verify", walletVerifyRequest{
|
||||||
|
IntentID: intentRes.IntentID,
|
||||||
|
Address: ownerAddr,
|
||||||
|
ChainID: cfg.ChainID,
|
||||||
|
Signature: sig,
|
||||||
|
}, http.StatusOK)
|
||||||
|
|
||||||
|
quote := postJSONExpect[membershipQuoteResponse](t, a, "/secret/membership/quote", membershipQuoteRequest{
|
||||||
|
DesignationCode: verifyRes.DesignationCode,
|
||||||
|
Address: ownerAddr,
|
||||||
|
ChainID: cfg.ChainID,
|
||||||
|
}, http.StatusOK)
|
||||||
|
|
||||||
|
_ = postJSONExpect[membershipConfirmResponse](t, a, "/secret/membership/confirm", membershipConfirmRequest{
|
||||||
|
DesignationCode: verifyRes.DesignationCode,
|
||||||
|
QuoteID: quote.QuoteID,
|
||||||
|
TxHash: "0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
|
||||||
|
Address: ownerAddr,
|
||||||
|
ChainID: cfg.ChainID,
|
||||||
|
}, http.StatusOK)
|
||||||
|
|
||||||
|
_ = postJSONExpect[map[string]string](t, a, "/secret/membership/confirm", membershipConfirmRequest{
|
||||||
|
DesignationCode: verifyRes.DesignationCode,
|
||||||
|
QuoteID: quote.QuoteID,
|
||||||
|
TxHash: "0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
|
||||||
|
Address: ownerAddr,
|
||||||
|
ChainID: cfg.ChainID,
|
||||||
|
}, http.StatusConflict)
|
||||||
|
}
|
||||||
|
|
||||||
func TestGovernanceInstallOwnerOnlyAndConfirm(t *testing.T) {
|
func TestGovernanceInstallOwnerOnlyAndConfirm(t *testing.T) {
|
||||||
a, cfg, cleanup := newTestApp(t)
|
a, cfg, cleanup := newTestApp(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user