Help us shape the future of Go! Take the 2026 Go Developer Survey

identity

package
v0.16.1 Latest Latest
Warning

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

Go to latest
Published: Oct 16, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

Documentation

Overview

Package identity derives Factory's principals from inbound credentials.

It is internal, and that split is deliberate rather than incidental. The vocabulary an external Authenticator must NAME -- Principal, Kind, ErrUnauthenticated -- lives in the public github.com/looprig/factory/identity package because Go forbids naming an internal type from outside the module. Everything here is the derivation itself: which header or cookie a credential is read from, how a tenant is decided, and which fields of a request reach an operation context. None of that has an external implementer, so none of it is vocabulary a deployer has to learn.

Two properties of this package are load-bearing and easy to lose in a later change.

A tenant comes ONLY from verified claims or from configuration. Nothing here reads the URL, the path, a path value, the body, a form or the Host header, and TestTheDerivationNamesNoTenantCarrier holds that structurally, over the parsed file, because a behavioural sweep can only cover the carriers somebody thought of. There is therefore nothing to "reject": a conflicting tenant in a request is not weighed against the credential and lost, it is never read.

An Authenticator holds no per-process state. Every decision is a function of the presented credential, the injected Verifier, and the injected Clock, so a browser whose WebSocket reconnects to a different Factory replica is authenticated there with no session handoff. Section 10 of the spec requires exactly that, and TestAuthenticatorDeclaresNoPerProcessState is the structural half of it -- a per-process cache is invisible to a two-replica behavioural test in every case where the cache misses.

Index

Constants

View Source
const (
	// SourceBearer is an Authorization: Bearer header.
	SourceBearer = factoryidentity.SourceBearer
	// SourceCookie is the browser session cookie.
	SourceCookie = factoryidentity.SourceCookie
	// SourceLink is a ClientLink connect token.
	SourceLink = factoryidentity.SourceLink
)
View Source
const DefaultCookieName = "factory_session"

DefaultCookieName is the browser session cookie a request is read from when it carries no Authorization header.

Variables

View Source
var ErrInvalidConfig = errors.New("identity: invalid authenticator configuration")

ErrInvalidConfig is the class of every NewAuthenticator rejection.

ErrUnauthorized aliases identity.ErrUnauthorized so internal callers and external seam implementations classify the same error value.

It is the complete result of a denied authorization decision. It deliberately carries no tenant, session, command, object or channel value: a denial must not disclose whether the named resource exists in another tenant. Callers may add operation names, but must not add those identifiers.

View Source
var ErrVerifierUnavailable = errors.New("identity: credential verifier is unavailable")

ErrVerifierUnavailable reports that the verifier could not decide, so nothing is known about the caller.

It deliberately does NOT wrap identity.ErrUnauthenticated. The two demand opposite responses: an unauthenticated caller is challenged, while a verifier that cannot answer is a fault on Factory's side, and reporting it as a rejected credential signs every user of a running deployment out for as long as the credential service is down.

Functions

func NewLinkOperationContext

func NewLinkOperationContext(ctx context.Context, principal factoryidentity.Principal) context.Context

NewLinkOperationContext derives it for a ClientLink RPC, which has no request headers to take a trace identifier from.

func NewOperationContextWithSource

func NewOperationContextWithSource(ctx context.Context, r *http.Request, principal factoryidentity.Principal, source Source) context.Context

NewOperationContextWithSource is the EXCEPTION, for a caller that authenticated by some route this Authenticator does not model and therefore has to state the source itself.

Prefer Authenticator.NewOperationContext. This function is where a wrong answer to "which credential authenticated this" enters, and a wrong answer here is a CSRF guard that skips rather than an error anybody sees.

Types

type Authenticator

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

Authenticator derives principals from requests and ClientLink tokens.

Its fields are the configuration and the two seams, and nothing else. A field able to accumulate state would break the reconnect property this package exists to hold, so TestAuthenticatorDeclaresNoPerProcessState refuses one.

func NewAuthenticator

func NewAuthenticator(cfg Config) (*Authenticator, error)

NewAuthenticator validates a configuration and returns the authenticator.

A rejection returns a nil Authenticator, so a caller that ignores the error cannot end up with one that authenticates against a half-built configuration.

func (a *Authenticator) AuthenticateLink(ctx context.Context, token string) (factoryidentity.Principal, error)

AuthenticateLink derives the principal a ClientLink connect token names. The token is the same kind of material as a bearer credential and is verified by the same Verifier, so a browser that reconnects to another replica presents what it already holds.

func (*Authenticator) AuthenticateRequest

func (a *Authenticator) AuthenticateRequest(ctx context.Context, r *http.Request) (factoryidentity.Principal, error)

AuthenticateRequest derives the principal a request's verified credentials name. It reads the Authorization header and the session cookie, and nothing else that a caller controls.

func (*Authenticator) CredentialSource

func (a *Authenticator) CredentialSource(r *http.Request) (Source, bool)

CredentialSource reports which credential a request presents, and whether it presents one at all. It does not verify anything.

It exists so that A1.3's CSRF guard, which applies to an AMBIENT credential and not to a bearer one, reads the answer from the one implementation of the precedence rule instead of re-deriving "is there an Authorization header" beside it. Two implementations of that rule would be two chances to disagree about which credential a request is authenticated with, and the disagreement would present as CSRF being skipped rather than as an error.

func (*Authenticator) NewOperationContext

func (a *Authenticator) NewOperationContext(ctx context.Context, r *http.Request, principal factoryidentity.Principal) context.Context

NewOperationContext derives the operation context for a request this Authenticator authenticated. It copies the principal and the trace identifier, and nothing else.

It is a METHOD because the credential source must not be stated by the caller. Passing it in put the answer to "which credential authenticated this" back in the edge's hands one call further out than AuthenticateRequest -- the exact drift CredentialSource was exported to prevent -- and an edge that wrote SourceBearer for a cookie-authenticated operation would make A1.3's CSRF guard skip, silently and in the safe-looking direction. Deriving it here through the same presented() the authentication used means there is one implementation of the rule and no order for a caller to get wrong.

A request whose credential cannot be read records an empty source, which no guard may read as permission.

type Authorizer

type Authorizer struct{}

Authorizer applies Factory's tenant boundary to its public edges.

Its zero value is ready to use and holds no resource directory. Tenant-local identifiers are not globally unique and therefore are not authority: after a successful decision, handlers must build every SessionStore request with the authenticated Principal's Tenant. In particular, looking up a SessionID, CommandID or ObjectID before authorizing would both cross that boundary and disclose existence.

func (Authorizer) AuthorizeAuditRead added in v0.12.0

func (Authorizer) AuthorizeAuditRead(_ context.Context, principal factoryidentity.Principal, _ sessionwire.SessionID) error

func (Authorizer) AuthorizeControl

AuthorizeControl authorizes a command within the principal's tenant. The session and command kind are validated by the public protocol and admission layers; neither selects a tenant here.

func (Authorizer) AuthorizeObjectRead

AuthorizeObjectRead authorizes an object read within the principal's tenant. The session and object identifiers are intentionally not inspected: both are opaque and tenant-local.

func (Authorizer) AuthorizeServiceSweep

func (Authorizer) AuthorizeServiceSweep(_ context.Context, principal factoryidentity.Principal) error

AuthorizeServiceSweep authorizes the cross-tenant reconciliation sweep. It is the only decision that is not confined to the principal's own tenant and consequently requires a service identity.

func (Authorizer) AuthorizeSessionList

func (Authorizer) AuthorizeSessionList(_ context.Context, principal factoryidentity.Principal) error

AuthorizeSessionList authorizes the principal's own tenant-scoped list.

func (Authorizer) AuthorizeSessionRead

func (Authorizer) AuthorizeSessionRead(_ context.Context, principal factoryidentity.Principal, _ sessionwire.SessionID) error

AuthorizeSessionRead authorizes a read within the principal's tenant. The session is intentionally not inspected: it is opaque and tenant-local.

func (Authorizer) AuthorizeSubscribe

func (Authorizer) AuthorizeSubscribe(_ context.Context, principal factoryidentity.Principal, channel string) error

AuthorizeSubscribe authorizes a canonical session:{tenant}:{session} channel. Parsing is completed independently before the parsed tenant is compared with the principal; a syntactically valid channel is not a grant.

It is the one tenant-scoped seam that does not call authorizeTenantPrincipal, and it needs no separate empty-tenant check because the grammar subsumes one: sessionChannelPattern's [^:]+ capture cannot match an empty string, so a parsed tenant that is "" is never valid, and an unconstructed Principal -- whose tenant is "" -- therefore never equals a valid parsed tenant. The "subscribe" row of TestAuthorizerRejectsAnUnconstructedPrincipalForOperationsWithoutOpaqueValues exercises that case.

type Claims

type Claims = factoryidentity.Claims

Claims is what a verified credential asserts.

type Clock

type Clock interface {
	Now() time.Time
}

Clock is the time seam. It is narrower than Factory's public Clock because expiry is the only time this package reads; the public seam satisfies it with no adapter, which server_test.go holds.

type Config

type Config struct {
	// Verifier decides credentials. It is required.
	Verifier Verifier

	// Clock decides expiry. It defaults to the system clock.
	Clock Clock

	// CookieName is the browser session cookie. It defaults to
	// DefaultCookieName.
	CookieName string

	// DefaultTenant is the tenant an actor credential that names none is
	// scoped to. Leaving it empty requires every credential to name its
	// tenant, which is the cloud deployment's shape.
	DefaultTenant sessionwire.TenantID
}

Config configures credential derivation.

type Credential

type Credential = factoryidentity.Credential

Credential is presented authentication material on its way to a Verifier.

func NewCredential

func NewCredential(source Source, value string) Credential

NewCredential builds a credential presented at source.

type OperationContext

type OperationContext struct {
	// Principal is the authenticated caller.
	Principal factoryidentity.Principal
	// CredentialSource is which credential authenticated this operation. It is
	// approved because CSRF applies to an AMBIENT credential and not to a
	// bearer one, so A1.3's guard needs the answer and must not recompute it;
	// see Authenticator.CredentialSource. It carries no authority of its own.
	CredentialSource Source
	// TraceID is the W3C trace-id of the inbound request, or empty. It is a
	// correlation identifier and carries no authority.
	TraceID string
}

OperationContext is the whole set of request-derived fields a handler may read back off a context.

The set is the point. "Copy only approved principal and trace fields" is a claim about a set, so the set is a type with two fields rather than a convention about what to put in a context, and TestOperationContextCarriesOnlyApprovedFields enumerates it. A field holding a header, a cookie or a raw token cannot be added without failing that test.

It is comparable and shares no memory, for the same reason Principal is: a handler holding one cannot widen its own tenant.

func OperationContextFrom

func OperationContextFrom(ctx context.Context) (OperationContext, bool)

OperationContextFrom reads the operation context back. The boolean is false for a context no authenticated edge built, which a handler must treat as unauthenticated rather than as an empty principal.

type Source

type Source = factoryidentity.Source

Source names where a credential was presented.

type Verifier

type Verifier = factoryidentity.Verifier

Verifier verifies a presented credential and reports what it asserts.

Jump to

Keyboard shortcuts

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