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")
|
||||
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 {
|
||||
writeError(w, http.StatusBadRequest, "quote context mismatch")
|
||||
return
|
||||
@ -424,6 +428,22 @@ func (a *app) handleMembershipConfirm(w http.ResponseWriter, r *http.Request) {
|
||||
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 {
|
||||
writeError(w, http.StatusConflict, fmt.Sprintf("tx verification pending/failed: %v", err))
|
||||
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) {
|
||||
a, cfg, cleanup := newTestApp(t)
|
||||
defer cleanup()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user