mirror of
https://github.com/luxfi/bank.git
synced 2026-10-30 17:02:07 +00:00
CRITICAL: - F01: Explicit API rules on all collections (owner-scoped list/view) - F02: HMAC-SHA256 webhook auth replaces RequireSuperuserAuth - F03: RunInTransaction for atomic balance check+hold (no overdraft) - F04: Audit log immutable (block update/delete unconditionally) HIGH: - F05: math.Round() before all float-to-int casts (no truncation) - F08: Transfer destination ownership verified - F09: Fixed daily/monthly limit filter queries - F12: Added "cancelled" to allowed processing transitions - F13: Balance floor check (reject if available < 0) New files: hooks/hmac.go (HMAC middleware), hooks/audit.go (immutable guard) Env: WEBHOOK_HMAC_SECRET required for webhook endpoints (from KMS)
52 lines
1.4 KiB
Go
52 lines
1.4 KiB
Go
package hooks
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
|
|
"github.com/hanzoai/base/core"
|
|
"github.com/hanzoai/base/tools/hook"
|
|
)
|
|
|
|
// RequireHMACAuth returns a middleware that validates the X-Signature header
|
|
// against HMAC-SHA256(secret, requestBody). The secret is read from
|
|
// WEBHOOK_HMAC_SECRET env var. Returns 401 if missing or invalid.
|
|
func RequireHMACAuth() *hook.Handler[*core.RequestEvent] {
|
|
return &hook.Handler[*core.RequestEvent]{
|
|
Id: "bankRequireHMACAuth",
|
|
Func: func(e *core.RequestEvent) error {
|
|
secret := os.Getenv("WEBHOOK_HMAC_SECRET")
|
|
if secret == "" {
|
|
return e.JSON(http.StatusUnauthorized, map[string]string{"error": "webhook secret not configured"})
|
|
}
|
|
|
|
sig := e.Request.Header.Get("X-Signature")
|
|
if sig == "" {
|
|
return e.JSON(http.StatusUnauthorized, map[string]string{"error": "missing X-Signature header"})
|
|
}
|
|
|
|
body, err := io.ReadAll(e.Request.Body)
|
|
if err != nil {
|
|
return e.JSON(http.StatusBadRequest, map[string]string{"error": "failed to read body"})
|
|
}
|
|
// Reset body so downstream handlers can read it.
|
|
e.Request.Body = io.NopCloser(bytes.NewReader(body))
|
|
|
|
mac := hmac.New(sha256.New, []byte(secret))
|
|
mac.Write(body)
|
|
expected := hex.EncodeToString(mac.Sum(nil))
|
|
|
|
if !hmac.Equal([]byte(sig), []byte(expected)) {
|
|
return e.JSON(http.StatusUnauthorized, map[string]string{"error": "invalid signature"})
|
|
}
|
|
|
|
return e.Next()
|
|
},
|
|
}
|
|
}
|