Files
zeekayandHanzo Dev d8a1a7eff9 provider: prove a webhook came from the provider before reading it
A webhook carries a verdict about a person's identity and arrives on an
endpoint reachable from the public internet, so authenticity is the first
thing ParseWebhook establishes. Two primitives, one policy:

  verifyHMAC — hex-encoded HMAC-SHA256 over the raw body, constant-time
  over the decoded digest. An empty secret refuses rather than skips:
  HMAC under a known-empty key is computable by any caller.

  header — case-insensitive lookup, because net/http canonicalizes the
  wire's X-SHA2-Signature to X-Sha2-Signature and an exact match finds
  nothing.

Onfido now checks X-SHA2-Signature against WebhookToken before decoding;
the field was declared for that purpose and never read, so any caller who
could reach the endpoint could inject an approved verdict. Jumio keeps
Callback-Sig but routes through the shared primitives, which closes the
empty-secret case its own comparison admitted. Plaid refuses: its verdicts
are signed by a JWT whose key must be fetched from Plaid, so CheckStatus
is the authenticated path and the unreachable result fetch is gone.
LexisNexis, Intellicheck, IDMerit and Berbix refuse for the same reason —
no scheme implemented, no claim believed — and now satisfy the Provider
interface they were written against, which the compile-time assertions in
provider.go keep true.

TestProviderWebhookAuth drives the property from every provider in the
package plus a sweep of the factory registry, so one added later inherits
it: no signature, a wrong one, a valid one over different bytes, and an
empty secret each yield an error and no event. The positive tests pin the
other failure mode — a guard that refuses everything, including genuine
callbacks — across both header spellings and either hex case.

The service binary passes IDV_API_SECRET to Jumio and Plaid, which it
previously dropped; without it the guard can never admit a real callback
and polling cannot authenticate.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-28 09:42:47 -07:00

31 lines
1.0 KiB
Go

// Package berbix implements the IDV provider for Berbix.
package provider
import (
"context"
"fmt"
)
type BerbixConfig struct {
BaseURL string
APIKey string
APISecret string
}
type Berbix struct{ cfg BerbixConfig }
func NewBerbix(cfg BerbixConfig) *Berbix { return &Berbix{cfg: cfg} }
func (p *Berbix) Name() string { return ProviderBerbix }
func (p *Berbix) InitiateVerification(ctx context.Context, user *VerificationRequest) (*VerificationResponse, error) {
return nil, fmt.Errorf("berbix: not yet implemented")
}
func (p *Berbix) CheckStatus(ctx context.Context, sessionID string) (*VerificationStatusResult, error) {
return nil, fmt.Errorf("berbix: not yet implemented")
}
// ParseWebhook refuses every payload: with no Berbix callback signature scheme
// implemented here, an arriving payload is a caller's claim, not a verdict.
func (p *Berbix) ParseWebhook(body []byte, headers map[string]string) (*WebhookEvent, error) {
return nil, fmt.Errorf("berbix webhook verification is not implemented: %w", ErrWebhookUnsigned)
}