Ankusa.Verifier.Token (ankusa v0.7.0)

Copy Markdown View Source

A static credential the provider sends with every request: a bearer token, a basic-auth login, a token in a header of its choosing, or a token in the query string. For providers that cannot sign (many internal systems, and several SaaS "webhook secret" fields that are just a header value). It proves the sender knows the secret, nothing about the body: a captured request can be replayed and a body can be swapped. Prefer Ankusa.Verifier.Hmac or Ankusa.Verifier.Asymmetric whenever the provider can sign.

opts:

  • :secret — the credential, or a list of them (newest first; every one is tried, so a rotation is secret: [new, old]). A missing or empty secret, an empty list, more than 8 secrets, or an empty element fails every hook with :bad_secret, and a source declared in config refuses to boot: an empty credential would accept the empty request.
  • :location — where the credential travels:
    • :bearer (default): Authorization: Bearer <secret>;
    • :basic: Authorization: Basic base64(<secret>), the secret being user:password (it must contain a :, as every client's does);
    • {:header, name}: the value of header name, which is the secret;
    • {:query, param}: the value of query parameter param. A URL is logged by every proxy on the way, so this is the weakest option.

The comparison is Ankusa.Verifier.secure_compare/2 over the SHA-256 of both sides, so neither the content nor the length of the secret is observable by timing, and every configured secret is compared whether or not an earlier one matched.

The credential is not stored with the hook: credential_headers/1 names the header it travels in, and Ankusa.Edge.Ingest adds it to the stored-header drop list for this source, so it never reaches the store, an archive segment, a claim pack or a sink. A quarantined hook is stored without it too, which means a quarantine replay cannot re-verify it (it fails closed with :missing_signature and stays in the pen): use on_verify_failure: reject for a token source.

Summary

Functions

The lowercase names of the request headers that carry this verifier's credential: what the source's stored-header policy must drop. [] for a location that is not a header, or opts that do not validate.

Human name of the scheme for telemetry: bearer, basic, header or query.

Check opts the way verify/2 would use them, without a request: the location is one of the four, and every secret is a non-empty binary (with a : for :basic).

Types

location()

@type location() :: :bearer | :basic | {:header, String.t()} | {:query, String.t()}

Functions

credential_headers(opts)

@spec credential_headers(keyword()) :: [String.t()]

The lowercase names of the request headers that carry this verifier's credential: what the source's stored-header policy must drop. [] for a location that is not a header, or opts that do not validate.

scheme_name(opts)

@spec scheme_name(keyword()) :: String.t()

Human name of the scheme for telemetry: bearer, basic, header or query.

validate_opts(opts)

@spec validate_opts(keyword()) :: :ok | {:error, :bad_scheme | :bad_secret}

Check opts the way verify/2 would use them, without a request: the location is one of the four, and every secret is a non-empty binary (with a : for :basic).