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

auth

package
v0.1.0-rc.3 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2026 License: MIT Imports: 32 Imported by: 0

README

auth

The Go binding of the developer profile — the directory stash auth login writes — read through the stack-profile Rust crate running inside a WASI guest under wazero, with CGO_ENABLED=0. It is the credential half of the Go SDK: it hands an encrypt client its client key and its bearer token without either package re-deriving the profile's layout, and without either importing the other.

The guest also runs the stack-auth strategies: access key, device session, OIDC federation, and automatic selection. Go supplies HTTP and holds the cross-process refresh lock for device sessions.

Use

Most applications never call this package directly: an encrypt client built with NewClient(ctx) and no options resolves its credentials with encrypt.AutoCredentials, which reads the environment first and then the profile, through this package. Use it directly to take the profile apart yourself:

import (
    "context"

    "github.com/cipherstash/stack/languages/golang/auth"
    "github.com/cipherstash/stack/languages/golang/encrypt"
)

func run(ctx context.Context) error {
    profile, err := auth.Resolve(ctx) // CS_CONFIG_PATH, else ~/.cipherstash
    if err != nil {
        return err
    }
    defer profile.Close()

    workspace, err := profile.CurrentWorkspaceStore(ctx)
    if err != nil {
        return err // auth.ErrNoCurrentWorkspace: run `stash auth login`
    }
    clientID, clientKey, err := workspace.SecretKey(ctx)
    if err != nil {
        return err
    }
    source, err := workspace.DeviceSession(ctx) // refreshes under the CLI's lock
    if err != nil {
        return err
    }
    defer source.Close()
    client, err := encrypt.NewClient(ctx,
        // The key is consumed and wiped by NewClient.
        encrypt.WithCredentials(encrypt.NewCredentials(clientID, clientKey, source)),
    )
    if err != nil {
        return err
    }
    defer client.Close()
    // ...
    return nil
}

auth.ClientKey and encrypt.ClientKey are one type, so the key goes straight from the profile into the credentials. The profile and the strategy are the caller's: the client asks the strategy for a token on every request but never closes it, so both stay open until the client is closed (the deferred calls above run in that order).

An encrypt client takes its token only from a strategy, never a raw string: a raw token cannot be refreshed when it expires, and would bypass the cross-process lock a device-session refresh holds with the stash CLI (the IdP revokes a whole refresh-token chain when one is used twice). workspace.Token(ctx) still reads the stored token, for inspection.

With no profile directory at all (CI, a container, a server authenticating by federation), auth.OpenWithoutProfile(ctx) runs the guest with nothing mounted: the access-key and OIDC strategies work, and every profile read is ErrNoProfile.

profile.AccessKey(ctx, crn, key), profile.OIDC(ctx, crn, provider), and profile.Auto(ctx) also return strategies that encrypt.NewCredentials takes. Auto checks CS_CLIENT_ACCESS_KEY and CS_WORKSPACE_CRN first, then the current workspace's stored device session. The OIDC provider is a one-method Token(context.Context) (string, error) interface, called on every token fetch for the JWT of the user the call is for; each distinct JWT is exchanged once while its CTS token lasts. Use auth.OAuth2TokenSource(source) to adapt a golang.org/x/oauth2.TokenSource. WithBaseURL(url) overrides service discovery for local tests or a custom CTS host; WithCacheCapacity(n) sets how many users' CTS tokens an OIDC strategy keeps (1024 unless set), sized to the users it serves within a CTS token's lifetime.

What the guest is given

Exactly one directory, mounted read-write at a fixed guest path, and no environment. It cannot name a path outside it: every path is built by the Rust crate from the store's directory and a validated filename or workspace id, and the mount itself is confined — a symlink inside the profile that leads outside it is refused, for reads and for the one write, rather than followed with the process's permissions as a plain directory mount would. Files it creates are mode 0600. It takes no file lock (WASI preview 1 has none); Go holds the same lock as the CLI across the device session refresh call, on the path ProfileStore.LockPath names. A fresh token is read without the lock; on refresh the guest re-reads auth.json after acquisition and saves refreshed tokens before release.

The crypto guest behind encrypt is not widened by this package existing: it still has no filesystem and no environment.

Build

mise run wasm:auth-guest:build   # the guest, with its import-surface gate
mise run go:test                 # the whole Go module, both packages

The guest module is embedded from wasm/. A tagged version of the module, as go get resolves it, carries it: the release workflow commits it to the commit the tag names, so nothing needs building. A pseudo-version (go get ...@main, or any commit rather than a tag) does not, and neither does a checkout of this repository, where the guest is not committed and the commands above build it. Without it, Open returns ErrGuestNotBuilt and the tests skip.

Licence

The Go code in this module is MIT-licensed (LICENSE). The credential guest it embeds is compiled from Rust crates that are not: stack-auth, stack-profile and stack-guest-abi are licensed under the PolyForm Internal Use License 1.0.0. Those terms allow use for your internal business operations only. Each crate's LICENSE in the CipherStash Stack repository has the full text.

Documentation

Overview

Package auth is the Go binding of the developer profile: the directory `stash auth login` writes (~/.cipherstash, or CS_CONFIG_PATH), read through the stack-profile crate running unmodified inside a WASI guest under wazero (CGO_ENABLED=0), so the on-disk layout is never re-derived by hand in Go.

Shape

A ProfileStore is one guest instance over one mounted directory. Resolve finds the profile directory the way the Rust crate does (CS_CONFIG_PATH, then ~/.cipherstash); Open takes one; OpenWithoutProfile mounts nothing, for the access-key and OIDC strategies where there is no profile. The guest is given that directory and nothing else: no environment, no other path, and no way out through a symlink inside it, which the mount refuses to follow. Authentication HTTP requests go through the Go host's transport import. Everything the napi binding of stack-profile exposes is a method here, named as in Rust: the current workspace (ProfileStore.CurrentWorkspace, ProfileStore.SetCurrentWorkspace, ProfileStore.ClearCurrentWorkspace), the workspaces on disk (ProfileStore.ListWorkspaces), a store scoped to one workspace (ProfileStore.WorkspaceStore, ProfileStore.CurrentWorkspaceStore), and the typed reads of the files a workspace holds: ProfileStore.SecretKey hands out the ZeroKMS client key as the opaque ClientKey that encrypt.NewCredentials takes, ProfileStore.Token the stored access token, ProfileStore.DeviceIdentity the identity the CLI created. ProfileStore.Close releases the guest; stores scoped from it are closed with it.

For authentication and refresh, use ProfileStore.AccessKey, ProfileStore.OIDC, ProfileStore.DeviceSession, or ProfileStore.Auto. Each returns a Strategy, which is what encrypt.NewCredentials takes for the bearer token: the only way a token reaches an encrypt client. A raw token — ProfileStore.Token's included — cannot be refreshed when it expires, and would bypass the cross-process lock a device-session refresh holds with the CLI, so encrypt does not accept one. A strategy lives in its store's guest, and closing the store closes it. A encrypt client given one never closes the strategy or the store: both are the caller's, and stay open until the client is closed. OAuth2TokenSource adapts an existing golang.org/x/oauth2.TokenSource into the OIDC provider interface.

A call whose context ends, Strategy.Token included, fails with the context's error and leaves the store and its strategies open: a request cancelled while its token is exchanged or refreshed does not stop the encrypt clients that read tokens from the same store. A request to the token service is cancelled with its caller, except a device session's refresh: the service rotates the refresh token when it answers one, so the caller returns at once while the refresh runs on, for up to 10 seconds, and the store keeps what it sends back for the next call. Strategy.Close waits for such a refresh. The store closes itself, and every later call is ErrState, only when its guest traps or a call runs on for 30 seconds after its context ended.

Why a second guest

The crypto guest behind encrypt has no filesystem and no environment: a bug or compromise inside it cannot read credentials off disk. Mounting the profile into it would trade that away, and it is the module that handles plaintext and data keys. So the profile lives in its own module with its own, smaller blast radius: one directory of credentials. ADR-0005 in packages/stack-encrypt/docs/adr records the decision and the alternatives.

What the guest cannot do

WASI preview 1 has no file locking, so the guest takes none. Go holds the same cross-process lock as the Rust CLI, on the path ProfileStore.LockPath names, across each device-session call. The guest re-reads auth.json after the lock and saves a rotated token before Go releases it. Creating a device identity is native-only in the crate and CLI territory; this package only reads one. Files the guest creates are mode 0600, which is wazero's create mode rather than the crate's own (skipped on wasm32), so a test pins it.

Memory

The guest's memory holds the client key and the token while a read is in flight. It is supplied the way encrypt's is — reserved once so it never moves, locked in RAM and excluded from core dumps where the platform allows, wiped before release, none of it depending on Close running — and ProfileStore.MemoryLocked reports whether the lock was granted. RequireLockedMemory makes a refused lock an error from Open.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNotFound is a profile file that does not exist in the store asked:
	// no secretkey.json, auth.json or device.json there. For the workspace's
	// files that means nothing has logged in to it on this machine.
	ErrNotFound = guest.ErrProfileNotFound
	// ErrInvalid is a profile file that is not the JSON its type expects.
	ErrInvalid = guest.ErrProfileJSON
	// ErrIO is a profile file that could not be read or written.
	ErrIO = guest.ErrProfileIO
	// ErrInvalidFilename is a filename the store refuses: empty, absolute,
	// or naming a path.
	ErrInvalidFilename = guest.ErrInvalidFilename
	// ErrNoCurrentWorkspace is a workspace-scoped operation with no current
	// workspace set: run `stash auth login`.
	ErrNoCurrentWorkspace = guest.ErrNoCurrentWorkspace
	// ErrInvalidWorkspaceID is a workspace id that is not sixteen base32
	// characters. Refused before any path is built from it.
	ErrInvalidWorkspaceID = guest.ErrInvalidWorkspaceID
	// ErrWorkspaceNotFound is a workspace with no directory under
	// workspaces/: nothing has logged in to it on this machine.
	ErrWorkspaceNotFound = guest.ErrWorkspaceNotFound
	// ErrEncoding is an input the guest refused: a directory, id or filename
	// that is not UTF-8.
	ErrEncoding = guest.ErrEncoding
	// ErrState is a call on a store that has been closed.
	ErrState = guest.ErrState
	// ErrInternal is a guest panic or any other unexpected guest failure.
	ErrInternal = guest.ErrInternal
	// ErrInvalidGrant is an OAuth refresh grant the auth server rejected.
	ErrInvalidGrant = guest.ErrAuthInvalidGrant
	// ErrInvalidClient is a client credential the auth server rejected.
	ErrInvalidClient = guest.ErrAuthInvalidClient
	// ErrUsageLimit is an account blocked by its usage allowance.
	ErrUsageLimit = guest.ErrAuthUsageLimit
	// ErrNotAuthenticated means no usable auth credential is available.
	ErrNotAuthenticated = guest.ErrAuthNotAuthenticated
	// ErrTransport is a failed auth HTTP exchange or response read.
	ErrTransport = guest.ErrAuthTransport
	// ErrConfig is invalid auth configuration or token data.
	ErrConfig = guest.ErrAuthConfig
	// ErrOther is an auth failure outside the actionable categories above.
	ErrOther = guest.ErrAuthOther
	// ErrMemoryLock is guest memory that could not be locked in RAM (or, on
	// Linux, excluded from core dumps). Open returns it under
	// [RequireLockedMemory]; otherwise [ProfileStore.MemoryLockError]
	// reports it and the store works on with unlocked memory.
	ErrMemoryLock = guest.ErrMemoryLock

	// ErrNoProfile is a profile directory that does not exist: nothing has
	// logged in on this machine, or CS_CONFIG_PATH names the wrong place.
	ErrNoProfile = errors.New("auth: no profile directory; run `stash auth login`")
)

Failure kinds the profile reports. The guest reports a status code from the one table every guest shares, so these are the sentinels of the shared decoder exposed under this package's names; an error from encrypt of the same kind is the same value. Check one with errors.Is; the detail behind it is a Diagnostic.

View Source
var ErrGuestNotBuilt = errors.New("auth: guest module not built — run `mise run wasm:auth-guest:build`")

ErrGuestNotBuilt is returned by Open when no guest module is embedded and none was supplied with WithGuest.

Functions

This section is empty.

Types

type Cause

type Cause = guest.Cause

Cause is one error in a Diagnostic's cause chain: a Code and a Message. A cause from a library outside the stack crates has no Code, and its Message is a description the guest vouches for, never that library's own text.

type ClientKey

type ClientKey = guest.ClientKey

ClientKey is the ZeroKMS client key as ProfileStore.SecretKey reads it out of secretkey.json: opaque (it prints a redaction under every verb and hands its bytes to no caller) and wiped once consumed. It is the same type as encrypt.ClientKey, by identity, so a key read here goes straight into encrypt.NewCredentials. This package does not import encrypt: a binary that only wants the profile does not carry the crypto guest. (encrypt imports this one, for AutoCredentials.)

type DeviceIdentity

type DeviceIdentity struct {
	// DeviceInstanceID uniquely identifies this CLI installation.
	DeviceInstanceID string
	// DeviceName is a human-readable name, the hostname by default.
	DeviceName string
}

DeviceIdentity is the identity the CLI created for this machine, read from device.json in this store (the profile root). Read-only: creating one is the CLI's.

type Diagnostic

type Diagnostic = guest.Diagnostic

Diagnostic is the full error behind a failure the guest reports, beside the kind: every such failure is a *Diagnostic wrapping one of the sentinels above, so errors.Is matches the kind and errors.As reads the rest:

var d *auth.Diagnostic
if errors.As(err, &d) {
	log.Printf("%s: %s (%s)", d.Code, d.Message, d.Help)
}

Its fields are Code ("stack_profile::not_found", "stack_auth::invalid_crn", ...; stable), Message (what Error returns after the sentinel's text), Help, URL, Severity, Fields (the structured fields, by name: a profile file's "path", a JSON error's "line" and "column") and Causes (the errors behind it, outermost first).

What one may carry is fixed: workspace ids, CRNs and regions, profile file paths, HTTP statuses and the auth server's error descriptions. Never a token, an access key, a client key or a response body.

Errors the store raises itself carry none: a closed store (ErrState), a guest that did not return, ErrMemoryLock, ErrNoProfile. A guest built before the detail existed returns the bare sentinel too.

It is the same type as encrypt.Diagnostic, by identity.

type OIDCProvider

type OIDCProvider interface {
	Token(context.Context) (string, error)
}

OIDCProvider supplies the identity-provider JWT of the user a call is for. The Rust federation strategy asks on every Strategy.Token call: the JWT is how it tells one user from another, and it keeps one CTS token per distinct JWT, exchanging a JWT only when it has no unexpired CTS token. So a server that serves many users through one client must return the JWT of the user behind ctx, not a value captured once, and should keep the call cheap, as identity-provider SDKs that cache their session are.

Token runs inside the guest call that needs it, while the ProfileStore that owns the strategy is locked. It must not call back into that ProfileStore or any strategy of it (reading a stored token, building another strategy): the call would wait on the same lock and deadlock rather than fail. OAuth2TokenSource does not touch the store, so it is safe to use from here.

func OAuth2TokenSource

func OAuth2TokenSource(source oauth2.TokenSource) OIDCProvider

OAuth2TokenSource adapts an existing x/oauth2 provider to OIDCProvider. Its AccessToken is read afresh whenever the Rust federation strategy asks.

type OIDCProviderFunc

type OIDCProviderFunc func(context.Context) (string, error)

OIDCProviderFunc adapts a function to OIDCProvider.

func (OIDCProviderFunc) Token

func (f OIDCProviderFunc) Token(ctx context.Context) (string, error)

type Option

type Option func(*options)

Option configures Resolve and Open.

func RequireLockedMemory

func RequireLockedMemory() Option

RequireLockedMemory makes Open fail with ErrMemoryLock when the guest's memory cannot be locked in RAM or, on Linux, excluded from core dumps, instead of continuing with memory that may be swapped or dumped and reporting so through ProfileStore.MemoryLocked. It holds for the life of the store, as encrypt's WithRequireLockedMemory does for a client.

func WithGuest

func WithGuest(wasm []byte) Option

WithGuest overrides the embedded wasm module.

func WithRoundTripper

func WithRoundTripper(rt http.RoundTripper) Option

WithRoundTripper sends the guest's authentication requests through rt. The default is http.DefaultTransport.

type ProfileStore

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

ProfileStore is a profile directory: the root Resolve or Open returns, or a workspace directory under it from ProfileStore.WorkspaceStore. All stores from one root share one guest instance over the one mounted directory; Close on any of them releases it. It is safe for concurrent use; calls are serialised, because a wasm instance is single-threaded.

A call whose context ends fails with the context's error, and only that call: a request cancelled while its token is fetched or refreshed never closes the profile, or the strategies and encrypt clients that read tokens from it. A device session's refresh already sent finishes in the background, for up to 10 seconds, so a rotated refresh token is never lost (see refreshGrace). The profile closes itself, and every later call is ErrState, only when the guest's state is unknown: it trapped, or it went on running long after its caller gave up, which is a bug.

func Open

func Open(ctx context.Context, dir string, opts ...Option) (*ProfileStore, error)

Open instantiates the guest over dir, an existing profile directory, mounted as the one directory the guest can see. Nothing is read until a method asks; nothing is written unless a method writes.

func OpenWithoutProfile

func OpenWithoutProfile(ctx context.Context, opts ...Option) (*ProfileStore, error)

OpenWithoutProfile instantiates the guest with no directory mounted: no filesystem at all. It is for the strategies that need no profile — ProfileStore.AccessKey and ProfileStore.OIDC — where there is none to open: CI, a container, a server authenticating by federation. Every profile read on it fails with ErrNoProfile, and ProfileStore.Auto on it is the environment's access key or ErrNotAuthenticated, as stack-auth's AutoStrategy is with no profile store.

func Resolve

func Resolve(ctx context.Context, opts ...Option) (*ProfileStore, error)

Resolve opens the profile directory the Rust crate would: CS_CONFIG_PATH when set and not blank, else ~/.cipherstash. The directory must exist; `stash auth login` creates it.

func (*ProfileStore) AccessKey

func (s *ProfileStore) AccessKey(ctx context.Context, crn, key string, opts ...StrategyOption) (*Strategy, error)

AccessKey constructs stack-auth's access-key strategy for crn. The key stays in the guest after construction; it is not sent on each Token call.

func (*ProfileStore) Auto

func (s *ProfileStore) Auto(ctx context.Context, opts ...StrategyOption) (*Strategy, error)

Auto follows stack-auth's detection order against the Go host's environment: access key first, then the current workspace's device token.

func (*ProfileStore) ClearCurrentWorkspace

func (s *ProfileStore) ClearCurrentWorkspace(ctx context.Context) error

ClearCurrentWorkspace removes the current workspace selection. Nothing to remove is not an error.

func (*ProfileStore) Close

func (s *ProfileStore) Close() error

Close releases the guest: its shutdown wipes every buffer it still holds, and the runtime close wipes and frees its memory. Idempotent. Every store of this profile is closed by it; a call on any of them after it fails with ErrState.

func (*ProfileStore) CurrentWorkspace

func (s *ProfileStore) CurrentWorkspace(ctx context.Context) (string, error)

CurrentWorkspace is the current workspace id, or ErrNoCurrentWorkspace.

func (*ProfileStore) CurrentWorkspaceStore

func (s *ProfileStore) CurrentWorkspaceStore(ctx context.Context) (*ProfileStore, error)

CurrentWorkspaceStore is ProfileStore.WorkspaceStore for the current workspace, or ErrNoCurrentWorkspace.

func (*ProfileStore) DeviceIdentity

func (s *ProfileStore) DeviceIdentity(ctx context.Context) (DeviceIdentity, error)

DeviceIdentity reads device.json in this store.

func (*ProfileStore) DeviceSession

func (s *ProfileStore) DeviceSession(ctx context.Context, opts ...StrategyOption) (*Strategy, error)

DeviceSession uses this workspace store's auth.json. Fresh tokens are read without a lock. A refresh takes the same cross-process lock as the Rust CLI, then the guest re-reads and saves before the lock is released.

func (*ProfileStore) Dir

func (s *ProfileStore) Dir() string

Dir is the store's directory on the host: the profile root, or the workspace directory under it. Empty for a store from OpenWithoutProfile.

func (*ProfileStore) ListWorkspaces

func (s *ProfileStore) ListWorkspaces(ctx context.Context) ([]string, error)

ListWorkspaces is the workspace ids with profile data on disk, sorted.

func (*ProfileStore) LockPath

func (s *ProfileStore) LockPath(ctx context.Context, filename string) (string, error)

LockPath is the host path of the lock file the Rust crate takes for filename in this store: a sibling `.<filename>.lock`. Nothing is created or locked. DeviceSession holds this lock across the guest's refresh call; the guest cannot lock under WASI. This package never composes a profile path itself.

func (*ProfileStore) LogValue

func (s *ProfileStore) LogValue() slog.Value

LogValue implements slog.LogValuer: the directory, and the memory state.

func (*ProfileStore) MemoryLockError

func (s *ProfileStore) MemoryLockError() error

MemoryLockError is why MemoryLocked is false: an error wrapping ErrMemoryLock that names what was refused and the limit that refused it. Nil while the memory is locked.

func (*ProfileStore) MemoryLocked

func (s *ProfileStore) MemoryLocked() bool

MemoryLocked reports whether the guest's memory — where the client key and the token pass through — is locked in RAM and, on Linux, excluded from core dumps. See encrypt's Client.MemoryLocked for what false means and what to do about it.

func (*ProfileStore) OIDC

func (s *ProfileStore) OIDC(ctx context.Context, crn string, provider OIDCProvider, opts ...StrategyOption) (*Strategy, error)

OIDC constructs stack-auth's federation strategy. provider is called on every Token call for the IdP JWT of the user that call is for; the strategy keeps one CTS token per distinct JWT and mints a new one only for a JWT that has no unexpired token. WithCacheCapacity sets how many JWTs that cache holds.

func (*ProfileStore) SecretKey

func (s *ProfileStore) SecretKey(ctx context.Context) (clientID string, key *ClientKey, err error)

SecretKey reads secretkey.json in this store (a workspace store; the root holds none): the ZeroKMS client id and the client key, the latter as the opaque ClientKey encrypt.NewCredentials takes. The transport copy of the key is wiped once it is in the ClientKey; the key is then the caller's to consume.

func (*ProfileStore) SetCurrentWorkspace

func (s *ProfileStore) SetCurrentWorkspace(ctx context.Context, id string) error

SetCurrentWorkspace makes id the current workspace. The workspace must already have profile data on this machine (ErrWorkspaceNotFound otherwise): a login creates it, this package never does.

func (*ProfileStore) String

func (s *ProfileStore) String() string

String prints the store's directory and memory state. Nothing secret.

func (*ProfileStore) Token

func (s *ProfileStore) Token(ctx context.Context) (Token, error)

Token reads auth.json in this store (a workspace store; the root holds none). The transport copy of the token is wiped once it is read out.

func (*ProfileStore) WorkspaceStore

func (s *ProfileStore) WorkspaceStore(ctx context.Context, id string) (*ProfileStore, error)

WorkspaceStore is the store scoped to workspace id: the directory workspaces/<id> under this store, as the guest names it after validating the id (ErrInvalidWorkspaceID otherwise). The directory need not exist yet; reads from it report ErrNotFound. It shares this store's guest and is closed with it.

type Strategy

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

Strategy is a Rust stack-auth strategy retained inside the credential guest: the source of the bearer token encrypt.NewCredentials takes. Close drops its cached credential; closing the parent profile closes all its strategies. It is the caller's to close: an encrypt client that was given it asks it for tokens but never closes it, so it must stay open until the client is closed.

func (*Strategy) Close

func (s *Strategy) Close() error

Close drops the strategy's cached token and unregisters its provider. It waits for a call in flight, including a device session's refresh that a cancelled Token left running: that refresh finishes, for up to 10 seconds, so the rotated refresh token it brings is saved rather than lost.

func (*Strategy) MemoryLockError

func (s *Strategy) MemoryLockError() error

MemoryLockError is why the memory the strategy lives in is not locked in RAM: its ProfileStore's guest, where its token, its access key or refresh token, and any client key read through the same store are held. It is ProfileStore.MemoryLockError of that store, asked now rather than when the strategy was made: under best-effort locking the guest can commit unlocked memory later, on a growth for a token exchange or a refresh. Nil while the memory is locked. It can be asked after Close.

func (*Strategy) Token

func (s *Strategy) Token(ctx context.Context) (string, error)

Token gets the current CTS bearer credential. Device sessions read a fresh token without a file lock; only a refresh call takes the cross-process lock.

type StrategyOption

type StrategyOption func(*strategyOptions)

StrategyOption configures an auth strategy.

func WithBaseURL

func WithBaseURL(url string) StrategyOption

WithBaseURL overrides CTS service discovery for one strategy.

func WithCacheCapacity

func WithCacheCapacity(n uint32) StrategyOption

WithCacheCapacity sets how many distinct IdP JWTs an OIDC strategy keeps a CTS token for (1024 unless set). When the cache is full the least recently used JWT's token is dropped and that user is exchanged again on their next call, so size it to the users one strategy serves within a CTS token's lifetime (about 15 minutes); each entry holds that user's JWT and CTS token. Zero caches nothing. Only ProfileStore.OIDC reads it.

type Token

type Token struct {
	// AccessToken is the bearer credential.
	AccessToken string
	// TokenType is the token's type, "Bearer".
	TokenType string
	// ExpiresAt is when the token stops being usable, from the stored
	// epoch timestamp.
	ExpiresAt time.Time
	// Region is the region the token was issued for, if stored.
	Region string
	// ClientID is the ZeroKMS client id the login provisioned, if stored.
	ClientID string
	// DeviceInstanceID is the identity of the device that logged in, if
	// stored.
	DeviceInstanceID string
}

Token is the stored access token, as auth.json holds it and ProfileStore.Token reads it through the Rust crate's own type. The refresh token is not in it: the guest handles refresh through a Strategy.

func (Token) Usable

func (t Token) Usable(now time.Time) bool

Usable reports whether the token is still usable at now: before its real expiry. The Rust crate's is_usable, not its is_expired, which subtracts a refresh-ahead margin that belongs to refreshing.

Jump to

Keyboard shortcuts

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