ecluse
Safe HaskellNone
LanguageGHC2021

Ecluse.Credential

Description

The outbound-credential handle: minting the bearer token Écluse uses to write approved packages to the mirror target.

This is one of the two cloud handles (the other is Ecluse.Queue); it is separate from the protocol handle Ecluse.Registry because protocol and authentication are orthogonal axes — every managed npm registry (AWS CodeArtifact, GCP Artifact Registry, a self-hosted Verdaccio) speaks the same npm protocol and differs only in how its bearer token is obtained (see docs/architecture/cloud-backends.md → "Credential Provider").

A CredentialProvider is used only for the mirror-target write, never to read on a user's behalf: private-upstream reads forward the client's own credential and public reads are anonymous (see docs/architecture/registry-model.md → "Credential flow and authority"). So a deployment configures exactly one provider.

Like the other handles, the effectful field returns IO, not App: an adapter closes over its own backend state (an amazonka env, an HTTP manager) and never imports the proxy's Env/App, so backends stay decoupled from the core (see docs/architecture/technology-stack.md → "Key Decisions").

This module provides the handle and its payload types. staticProvider is the in-memory leaf: a fixed token with no expiry. The generic refresh/cache/expiry policy that wraps a per-cloud token mint lives in Ecluse.Credential.Refresh.

Synopsis

Provider handle

newtype CredentialProvider Source #

The credential handle: yields the bearer token currently valid for the mirror target, refreshing it before expiry internally (a caller never sees a stale token in the common case, and never blocks on a mint on the request hot path).

It is a record of functions (the Handle pattern): the single field is the operation, and a backend's smart constructor returns a CredentialProvider whose closure captures that backend's private state. currentToken returns IO, not App so adapters stay decoupled from the core (see the module header).

Constructors

CredentialProvider 

Fields

  • currentToken :: IO AuthToken

    The bearer token to use now. An adapter refreshes before expiry behind this field, so the caller just uses the returned token.

Tokens

data AuthToken Source #

A bearer token for a registry endpoint, with its expiry when known.

The expiry is what a refresh wrapper schedules against (cloud token lifetimes range from CodeArtifact's ~12h to ADC's ~1h, so refresh is driven off the token's own authExpiresAt rather than a fixed interval). A static token has no expiry (Nothing).

Constructors

AuthToken 

Fields

Instances

Instances details
Show AuthToken Source # 
Instance details

Defined in Ecluse.Credential

Eq AuthToken Source # 
Instance details

Defined in Ecluse.Credential

Secrets

data Secret Source #

A short-lived bearer secret (an access token).

Opaque, and its Show is redacted: the underlying token text is never rendered, so a Secret can be embedded in any value — an AuthToken, a log record, an error — without risking disclosure (token material must never reach a log, metric, or trace; see docs/architecture/observability.md). This redaction is a load-bearing security property.

Build one with mkSecret and read the real value back only at the point of use with unSecret (e.g. when setting the Authorization header).

Equality is constant-time: two secrets are compared over their UTF-8 bytes without a content-dependent early out (see the Eq instance). The default derived equality would be Text's short-circuiting compare, which returns as soon as two tokens first differ and so leaks, through timing, how long a shared prefix is. Folding that property into the type itself means no comparison on a Secret — the inbound edge-auth gate above all — can accidentally become non-constant-time. (A constant-time compare can still reveal the token length; that residual leak is accepted, but the content is never short-circuited on.)

Instances

Instances details
Show Secret Source #

Renders a fixed placeholder, never the secret text. This is the whole point of the type: it makes accidental disclosure through any show-based signal (logs, errors, deriving Show on an enclosing record) impossible.

Defined via showsPrec (the Show class method) rather than show, because relude re-exports a polymorphic show that is not the class method.

Instance details

Defined in Ecluse.Credential

Eq Secret Source #

Constant-time equality over the UTF-8 encoding of the wrapped token.

constEq compares every byte regardless of where the inputs first diverge, so a near-miss token cannot be distinguished from a far-miss one by how long the comparison takes. This is the security property the whole type exists to make unmissable: the PROXY_AUTH_TOKEN edge gate compares the client's bearer token against the configured one through this instance, and a short-circuiting compare there would leak the secret's prefix length to a remote attacker.

Instance details

Defined in Ecluse.Credential

Methods

(==) :: Secret -> Secret -> Bool #

(/=) :: Secret -> Secret -> Bool #

mkSecret :: Text -> Secret Source #

Wrap raw token text as a Secret.

unSecret :: Secret -> Text Source #

Recover the raw token text from a Secret. Call this only at the point of use (setting the auth header); never log or otherwise render the result.

In-memory double

staticProvider :: AuthToken -> CredentialProvider Source #

An in-memory CredentialProvider that always returns a fixed token.

This is the static leaf: it never expires and never refreshes, so it is the right provider for a registry reached with a long-lived credential, and it is the trivial double for tests of code that consumes a CredentialProvider.