documents

package
v0.86.1 Latest Latest
Warning

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

Go to latest
Published: Jul 15, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Overview

Package documents defines AuthKit's generic immutable signed-document wire contract. It authenticates transport metadata and opaque JSON payload bytes; application schema and authorization remain the receiving application's job.

Index

Examples

Constants

View Source
const (
	JOSEType = "authkit-document+jws"

	MaxTypeBytes           = 256
	MaxIssuerBytes         = 2048
	MaxAudienceBytes       = 512
	MaxAudiences           = 32
	MaxPayloadBytes        = 1 << 20
	MaxSignedPayloadBytes  = MaxPayloadBytes + 16<<10
	MaxCompactJWSBytes     = 2 << 20
	MaxReferences          = 16
	MaxReferencesJSONBytes = 4 << 10
)
View Source
const PublicationPathPrefix = "/.well-known/authkit/documents/"

Variables

View Source
var (
	ErrInvalidReference     = errors.New("invalid_document_reference")
	ErrInvalidType          = errors.New("invalid_document_type")
	ErrInvalidDigest        = errors.New("invalid_document_digest")
	ErrDuplicateReference   = errors.New("duplicate_document_reference")
	ErrTooManyReferences    = errors.New("too_many_document_references")
	ErrReferencesTooLarge   = errors.New("document_references_too_large")
	ErrWrongTokenType       = errors.New("documents_wrong_token_type")
	ErrReservedAttribute    = errors.New("reserved_document_attribute")
	ErrInvalidEnvelope      = errors.New("invalid_document_envelope")
	ErrPayloadTooLarge      = errors.New("document_payload_too_large")
	ErrMalformedJWS         = errors.New("malformed_document_jws")
	ErrWrongJOSEType        = errors.New("wrong_document_jose_type")
	ErrUnsupportedAlgorithm = errors.New("unsupported_document_algorithm")
	ErrUnsupportedSigner    = errors.New("unsupported_document_signer")
	ErrUnknownKey           = errors.New("unknown_document_key")
	ErrInvalidSignature     = errors.New("invalid_document_signature")
	ErrDigestMismatch       = errors.New("document_digest_mismatch")
	ErrIssuerMismatch       = errors.New("document_issuer_mismatch")
	ErrAudienceMismatch     = errors.New("document_audience_mismatch")
	ErrTypeMismatch         = errors.New("document_type_mismatch")
	ErrUntrustedIssuer      = errors.New("untrusted_document_issuer")
	ErrUnauthorized         = errors.New("document_unauthorized")
	ErrNotFound             = errors.New("document_not_found")
	ErrFetch                = errors.New("document_fetch_failed")
	ErrRedirect             = errors.New("document_redirect_rejected")
)

Functions

func Digest

func Digest(payload []byte) string

func NewPublisher

func NewPublisher(lookup LookupDocument, authorize AuthorizeRequest) http.Handler

NewPublisher returns the framework-neutral well-known publication handler.

func NormalizeDigest

func NormalizeDigest(value string) (string, error)

func NormalizeReferences

func NormalizeReferences(in map[string]string) (map[string]string, error)

NormalizeReferences prepares a mint-time documents claim. It trims types, canonicalizes digests, and rejects normalization collisions.

func NormalizeType

func NormalizeType(value string) (string, error)

func ParseReferencesJSON

func ParseReferencesJSON(raw []byte) (map[string]string, error)

ParseReferencesJSON strictly parses a documents claim. Duplicate keys and non-canonical values are rejected instead of being overwritten by map decode.

func ValidateDigest

func ValidateDigest(value string) error

func ValidateReferences

func ValidateReferences(references map[string]string) error

func ValidateType

func ValidateType(value string) error

ValidateType requires a bounded opaque identifier ending in /vN. AuthKit does not interpret the namespace or version beyond enforcing that the version is present in the type itself.

Types

type AuthorizeRequest

type AuthorizeRequest func(*http.Request) error

AuthorizeRequest either authenticates an incoming publisher request or adds existing machine credentials to an outgoing resolver request. Nil always denies; AuthKit does not define a document-specific credential.

type DocumentVerifier

type DocumentVerifier interface {
	ValidateDocumentIssuer(context.Context, string) error
	VerifyDocument(context.Context, SignedDocument, VerifyOptions) (Envelope, error)
}

DocumentVerifier is implemented by verify.Verifier. The preflight trust check prevents an untrusted issuer from becoming a network destination.

type Envelope

type Envelope struct {
	Issuer    string          `json:"iss"`
	Audiences []string        `json:"aud"`
	Type      string          `json:"type"`
	Payload   json.RawMessage `json:"payload"`
}

Envelope is the signed JWS payload. Payload is intentionally opaque to AuthKit and may contain any valid JSON value.

func DecodeEnvelope

func DecodeEnvelope(payload []byte) (Envelope, error)

DecodeEnvelope strictly decodes the signed payload, rejecting duplicate or unknown fields before any application payload can be returned.

func NormalizeEnvelope

func NormalizeEnvelope(in Envelope) (Envelope, error)

NormalizeEnvelope returns a detached, normalized copy suitable for one-time marshaling and signing.

func (Envelope) HasAudience

func (e Envelope) HasAudience(audience string) bool

func (Envelope) Validate

func (e Envelope) Validate() error
type Header struct {
	Algorithm string
	KeyID     string
	Type      string
}

Header is the security-relevant subset of an inspected compact JWS header. Inspecting a header or payload does not verify its signature.

func DecodeCompact

func DecodeCompact(compact string) (Header, []byte, error)

DecodeCompact strictly inspects a compact JWS and returns its exact decoded payload. It does not verify the signature.

type LookupDocument

type LookupDocument func(context.Context, string) (SignedDocument, error)

LookupDocument returns a retained document by its immutable payload digest. Its compact JWS representation may change when the payload is re-signed.

type Reference

type Reference struct {
	Type   string `json:"type"`
	Digest string `json:"digest"`
}

Reference identifies one exact signed envelope. Type carries the application schema version (for example, example.catalog/v1); Digest covers the exact JWS payload bytes, not a decoded/re-encoded JSON value.

func NormalizeReference

func NormalizeReference(r Reference) (Reference, error)

func ReferenceFor

func ReferenceFor(documentType string, signedPayload []byte) (Reference, error)

func (Reference) Validate

func (r Reference) Validate() error

type Resolver

type Resolver struct {
	// contains filtered or unexported fields
}
Example (TwoSites)
package main

import (
	"context"
	"crypto"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"

	"github.com/open-rails/authkit/documents"
	"github.com/open-rails/authkit/jwtkit"
	"github.com/open-rails/authkit/verify"
)

const machineAuthorization = "Bearer existing-machine-credential"

func requireMachine(request *http.Request) error {
	if request.Header.Get("Authorization") != machineAuthorization {
		return errors.New("unauthorized")
	}
	return nil
}

func addMachine(request *http.Request) error {
	request.Header.Set("Authorization", machineAuthorization)
	return nil
}

func main() {
	signer, _ := jwtkit.NewRSASigner(2048, "site-a-key")
	var document documents.SignedDocument
	siteA := httptest.NewServer(documents.NewPublisher(func(context.Context, string) (documents.SignedDocument, error) {
		return document, nil
	}, requireMachine))
	defer siteA.Close()
	document, _ = documents.Sign(context.Background(), signer, documents.Envelope{
		Issuer: siteA.URL, Audiences: []string{"site-b"}, Type: "example.entitlements/v1", Payload: json.RawMessage(`{"plan":"starter"}`),
	})

	v := verify.NewVerifier()
	_ = v.AddIssuer(siteA.URL, nil, verify.IssuerOptions{RawKeys: map[string]crypto.PublicKey{signer.KID(): signer.PublicKey()}})
	resolver := documents.NewResolver(v, siteA.Client(), addMachine, documents.ResolverOptions{AllowHTTP: true})
	siteB := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		payload, err := resolver.Resolve(r.Context(), siteA.URL, document.Reference, "site-b")
		if err != nil {
			http.Error(w, err.Error(), http.StatusUnauthorized)
			return
		}
		_, _ = w.Write(payload) // site B owns schema/processing from here.
	}))
	defer siteB.Close()

	response, _ := siteB.Client().Get(siteB.URL)
	body, _ := io.ReadAll(response.Body)
	response.Body.Close()
	fmt.Println(string(body))
}
Output:
{"plan":"starter"}

func NewResolver

func NewResolver(verifier DocumentVerifier, client *http.Client, authorize AuthorizeRequest, opts ResolverOptions) *Resolver

func (*Resolver) Resolve

func (r *Resolver) Resolve(ctx context.Context, issuer string, reference Reference, audience string) (json.RawMessage, error)

Resolve authenticates, fetches, verifies, and returns only the opaque application payload. Successful results are cached by issuer+type+digest.

type ResolverOptions

type ResolverOptions struct {
	// AllowHTTP is for local tests and development only. Production defaults to
	// HTTPS-only publication endpoints.
	AllowHTTP        bool
	Timeout          time.Duration
	MaxResponseBytes int64
	MaxCacheEntries  int
}

type SignedDocument

type SignedDocument struct {
	CompactJWS    string    `json:"jws"`
	Reference     Reference `json:"reference"`
	SignedPayload []byte    `json:"signed_payload"`
}

SignedDocument retains both the compact JWS and the exact bytes used as its payload so callers can publish them without a parse/re-encode step.

func FromCompact

func FromCompact(compact string, reference Reference) (SignedDocument, error)

func Sign

func Sign(ctx context.Context, signer jwtkit.Signer, envelope Envelope) (SignedDocument, error)

Sign marshals the normalized envelope once, signs those exact bytes, and returns the retained bytes and their content-addressed reference.

type VerifyOptions

type VerifyOptions struct {
	Issuer    string
	Audience  string
	Type      string
	Reference Reference
}

VerifyOptions are the caller's authenticated expectations. None may be inferred from unsigned request metadata.

Jump to

Keyboard shortcuts

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