idv

package
v1.801.464 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 5, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

Documentation

Overview

Package idv is identity verification: run a KYC or KYB check through a licensed provider.

It is the ONE identity/business verification seam the Hanzo cloud binary uses to orchestrate that check. It is deliberately small and provider-agnostic so every consumer wires the SAME contract: company formation (founder KYC) and the compliance product (org-side onboarding KYB/KYC) both drive verifications through a idv.Provider, and a real provider (Persona, Onfido, Stripe Identity) is a config-driven swap for the honest human-in-the-loop default — no consumer branches on which is wired.

THE BOUNDARY (a design invariant, not a comment). This package ORCHESTRATES a licensed verification provider and TRACKS what the provider reported. It never asserts a subject is "compliant" or "verified" on its own authority: the only terminal status it can produce is a status the PROVIDER returned. The default (Manual) provider NEVER auto-approves — it returns pending and waits for an out-of-band decision recorded by a human reviewer or a provider webhook. Every status in this package is provider-reported or pending, never platform-asserted.

FAIL-CLOSED. Every path that cannot obtain a positive provider decision resolves to a NON-terminal status (pending/review) or an error — never to verified. A transport error, a non-2xx response, an unparseable body, or an unrecognized provider status all map to pending. A verification can only reach provider_verified through an explicit, recognized provider success token (see classify).

Index

Constants

View Source
const WebhookRefHeader = "X-Idv-Signature"

WebhookRefHeader carries the payload signature: "sha256=<hex(HMAC_SHA256(secret,body))>".

Variables

This section is empty.

Functions

func ValidKind

func ValidKind(k Kind) bool

ValidKind reports whether k is a known subject kind.

Types

type EnvFn

type EnvFn func(name string) string

EnvFn reads a configuration value by name (os.Getenv in production; a map in tests). Injected so provider selection is testable without touching the process environment.

type Kind

type Kind string

Kind is the kind of subject a verification runs over.

const (
	KindIndividual Kind = "individual" // a natural person — KYC
	KindBusiness   Kind = "business"   // a legal entity — KYB
)

type Manual

type Manual struct{}

Manual is the honest human-in-the-loop provider and the default when no external provider is configured. It orchestrates a real manual flow: Start records a reference and returns pending; the terminal decision is supplied out-of-band by a reviewer (or a provider webhook) and recorded by the consumer, NOT invented here. It NEVER returns a terminal status on its own — Check stays pending — so a gate that requires verification can never be satisfied by the mere absence of a provider.

func (Manual) Check

func (Manual) Check(_ context.Context, _ string, ref string) (Result, error)

Check reports pending. The manual provider holds no decision state of its own; the consumer's store is the source of truth for a recorded reviewer/webhook decision, and until one is recorded the verification is honestly pending — never verified.

func (Manual) Name

func (Manual) Name() string

Name identifies the manual provider.

func (Manual) Start

func (Manual) Start(_ context.Context, _ string, subj Subject) (Session, error)

Start records a pending verification. It requires something to correlate the eventual decision to (an email or the caller's ref); it returns no hosted URL and ALWAYS the pending status — never verified.

type Provider

type Provider interface {
	Name() string
	Start(ctx context.Context, org string, subj Subject) (Session, error)
	Check(ctx context.Context, org, ref string) (Result, error)
}

Provider is the provider-agnostic verification seam. Start begins a verification for a subject and returns a Session; Check polls the current status. A real provider implements this over its REST API (see REST); Manual is the honest default. Name identifies the wired provider for audit and display.

func FromConfig

func FromConfig(getSecret SecretFn, env EnvFn) (Provider, error)

FromConfig returns the configured verification provider. With no provider named it returns Manual (never an error — the honest default). With a real provider named it returns a REST adapter after PROVING the key resolves now; an unknown provider name or an unresolvable key is an error the caller propagates to fail the mount.

type REST

type REST struct {
	// contains filtered or unexported fields
}

REST is a hosted-inquiry verification provider. name is the provider label (persona|onfido|stripe); base is its API root; key resolves the KMS-sealed bearer token; classify maps the provider's status vocabulary to our honest Status (fail-closed — an unrecognized token is pending, never verified).

func (*REST) Check

func (r *REST) Check(ctx context.Context, org, ref string) (Result, error)

Check polls the current provider decision for a reference and maps it to our honest Status. A transport/parse failure is an error, never a verified result.

func (*REST) Name

func (r *REST) Name() string

Name identifies the wired provider.

func (*REST) Start

func (r *REST) Start(ctx context.Context, org string, subj Subject) (Session, error)

Start creates a verification inquiry for the subject and returns the hosted flow. The returned status is classify(provider status): on a fresh inquiry that is pending or review, NEVER verified. Any failure to obtain a well-formed provider response is returned as an error (the consumer surfaces 502) — it never degrades to a verified result.

type Result

type Result struct {
	Ref    string
	Status Status
}

Result is the current state of a verification when polled: the reference and the provider-reported status.

type SecretFn

type SecretFn func(ctx context.Context, ref string) ([]byte, error)

SecretFn resolves a KMS-sealed secret by reference. FromConfig is handed one wired to deps.KMS.GetSecret so this package never imports the KMS client type.

type Session

type Session struct {
	Ref       string
	VerifyURL string
	Status    Status
}

Session is the result of starting a verification: the provider's reference for the check, a hosted URL the subject visits to complete it (empty for the manual provider, which has no hosted flow), and the INITIAL status — which is always non-terminal (a provider cannot decide before the subject has acted).

type Status

type Status string

Status is the honest lifecycle of a verification. Every value except StatusReviewerConfirmed is PROVIDER-REPORTED: pending (no decision yet), a settled provider decision (verified/rejected), a provider-flagged human-review state, or an aged-out prior result. There is deliberately no "compliant" value.

StatusReviewerConfirmed is the ONE value that is NOT provider-reported: it is produced ONLY by a consumer's attributed, role-gated human reviewer (a manual pass), never by an idv Provider. No Provider in this package — Manual, REST, or the classify table — can EVER emit it, so the boundary "the only terminal status a provider produces is one the provider returned" is unchanged. A gate treats it as a pass exactly like StatusVerified (see Pass), but the two are distinct on the wire so a manual confirmation is never dressed up as a provider decision.

const (
	StatusPending           Status = "pending"            // started; awaiting the subject and the provider
	StatusVerified          Status = "provider_verified"  // the provider reported a passing decision
	StatusRejected          Status = "provider_rejected"  // the provider reported a failing decision
	StatusReview            Status = "manual_review"      // the provider flagged the case for human review
	StatusExpired           Status = "expired"            // a prior provider result has aged out
	StatusReviewerConfirmed Status = "reviewer_confirmed" // a privileged human reviewer confirmed the subject (NOT provider-reported)
)

func (Status) Pass

func (s Status) Pass() bool

Pass reports whether s is a PASSING terminal decision — the ONE predicate a gate uses. It admits a provider verify OR an attributed reviewer confirmation, and NOTHING else: a rejection, a review flag, or a pending status never satisfies it, so fail-closed holds by construction.

func (Status) Terminal

func (s Status) Terminal() bool

Terminal reports whether s is a SETTLED decision — a provider verify/reject or an attributed reviewer confirmation. A pending or review status is not terminal.

func (Status) Valid

func (s Status) Valid() bool

Valid reports whether s is a known status value.

type Subject

type Subject struct {
	Kind  Kind   // individual (KYC) or business (KYB)
	Name  string // legal name of the person or entity
	Email string // contact for the hosted flow invitation
	// Ref is the caller's OWN opaque reference for this subject (an internal id),
	// echoed to the provider as the inquiry's external reference so a webhook can be
	// correlated without carrying PII. Never a government id or secret.
	Ref string
}

Subject is the identifying information a verification is started for. It is PII: callers custody it sealed at rest and never place it in logs, URLs, or audit records. The provider collects the sensitive documents (ID image, proof of address, incorporation papers) directly from the subject via the hosted flow — this seam carries only the minimum needed to open that flow.

type Webhook

type Webhook struct {
	// contains filtered or unexported fields
}

Webhook authenticates a provider webhook and extracts the verification reference to reconcile. It holds only a resolver for the KMS-sealed secret (fetched per call, short-lived in memory, never logged) — mirroring how REST custodies its bearer.

func WebhookFromConfig

func WebhookFromConfig(getSecret SecretFn, env EnvFn) (*Webhook, error)

WebhookFromConfig returns the configured webhook receiver, or nil when none is configured (the default — the endpoint is then not served). It is FAIL-CLOSED at mount: a named secret reference that does not resolve is an error the caller propagates to fail the mount, exactly like a named provider whose key is missing — never a silent downgrade to an unauthenticated endpoint.

Configuration (env, resolved by the composition root):

CLOUD_IDV_WEBHOOK_KEY_REF  KMS secret reference for the webhook HMAC secret. Unset
                           ⟹ the webhook is disabled (Manual deployments).

func (*Webhook) Reference

func (w *Webhook) Reference(body []byte) (string, bool)

Reference extracts the provider reference the (verified) payload names — WHICH verification to reconcile. The receiver reconciles that reference against the provider API for the authoritative status; the body carries no trusted decision, so this reads only the correlating reference. A relay in front of a specific provider maps that provider's webhook shape onto this minimal envelope.

func (*Webhook) Verify

func (w *Webhook) Verify(ctx context.Context, sigHeader string, body []byte) (bool, error)

Verify reports whether sigHeader is a valid signature over body under the sealed secret. Fail-closed: an unresolvable secret is an error; an empty/malformed header, or any mismatch, is a plain false (never a panic, never a partial-credit accept). Constant-time compare.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL