auth

package
v0.9.1 Latest Latest
Warning

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

Go to latest
Published: Sep 19, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package auth holds the credential store and resolution ladder for remote LLM providers. It is the runtime side of package provider: provider knows *which* providers exist and how to reach them; auth knows *how to authenticate* to them — API keys stored by /login, OAuth tokens with refresh, and the fallback chain that picks a credential at call time.

The store is a single JSON file (~/.config/nib/credentials.json), written atomically with 0o600 perms under a 0o700 directory — the same discipline as chat/sessionstore.go. No SQLite, no external deps.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func RegisterPostExchange

func RegisterPostExchange(providerID string, hook PostExchangeHook)

RegisterPostExchange registers a post-exchange hook for a provider ID. Intended to be called from init() in adapter packages.

Types

type Credential

type Credential struct {
	ProviderID string         `json:"provider_id"`
	Kind       CredentialKind `json:"kind"`

	// CredentialAPIKey fields.
	APIKey string `json:"api_key,omitempty"`
	// BaseURL overrides the provider's default endpoint, for providers that
	// have none (Azure: one resource URL per account).
	BaseURL string `json:"base_url,omitempty"`

	// CredentialOAuth fields.
	AccessToken  string    `json:"access_token,omitempty"`
	RefreshToken string    `json:"refresh_token,omitempty"`
	ExpiresAt    time.Time `json:"expires_at,omitempty"`
	Email        string    `json:"email,omitempty"`
	AccountID    string    `json:"account_id,omitempty"`
	OrgID        string    `json:"org_id,omitempty"`
	OrgName      string    `json:"org_name,omitempty"`
	// ProjectID is used by Cloud Code Assist (google-gemini-cli). Discovered
	// via loadCodeAssist/onboardUser after the OAuth exchange.
	ProjectID    string    `json:"project_id,omitempty"`
	AuthorizedAt time.Time `json:"authorized_at,omitempty"`
}

Credential is one stored login for one provider. The store keeps at most one credential per provider (single-account); multi-account is a Phase 3 goal. Fields not relevant to the Kind are left zero-valued and omitted from JSON.

func LoginAPIKey

func LoginAPIKey(store *Store, def provider.Definition, apiKey string) (Credential, error)

LoginAPIKey creates and stores an API-key credential for a provider. Returns the credential (with AuthorizedAt set).

func LoginAPIKeyAt added in v0.9.1

func LoginAPIKeyAt(store *Store, def provider.Definition, apiKey, baseURL string) (Credential, error)

LoginAPIKeyAt is LoginAPIKey with an endpoint override stored alongside the key, for providers without a default base URL (see provider.Definition.NeedsBaseURL). An empty baseURL keeps the default.

func LoginDeviceCode

func LoginDeviceCode(ctx context.Context, store *Store, def provider.Definition, onInstructions func(string, string)) (Credential, error)

LoginDeviceCode runs the RFC 8628 device-code flow and saves the credential. onInstructions is called with a human-readable instruction string (containing the user code and verification URL) before polling begins, so the caller can display it and optionally open the verification URL in a browser.

func LoginOAuth

func LoginOAuth(ctx context.Context, store *Store, def provider.Definition, onURL func(string)) (Credential, error)

LoginOAuth runs the full OAuth flow (start + complete) and saves the credential. onURL is called with the authorize URL before waiting for the callback, so the caller can display it or open a browser.

func (Credential) DisplayLabel

func (c Credential) DisplayLabel() string

DisplayLabel returns a human-readable label for the account picker ("user@example.com" for OAuth, "API key" for api-key creds).

func (Credential) IsExpired

func (c Credential) IsExpired() bool

IsExpired reports whether the access token has fully expired (no skew). Used to decide whether to attempt a refresh vs. demand re-login.

func (Credential) NeedsRefresh

func (c Credential) NeedsRefresh() bool

NeedsRefresh reports whether an OAuth credential's access token is expired or about to expire. Always false for non-OAuth credentials.

func (Credential) StatusLine

func (c Credential) StatusLine() string

StatusLine returns a one-line summary of a stored credential for display in `nib login --list`. Example: "user@example.com (OAuth, expires in 3d)".

type CredentialKind

type CredentialKind string

CredentialKind classifies how a credential authenticates.

const (
	// CredentialAPIKey is a plain API key entered via /login or stored from
	// config. The key is sent as-is in the Authorization header (OpenAI) or
	// x-api-key header (Anthropic).
	CredentialAPIKey CredentialKind = "api-key"
	// CredentialOAuth is an OAuth access/refresh token pair obtained via the
	// authorization-code flow with PKCE. The access token is sent as a Bearer
	// token; when it expires, the refresh token is used to get a new one.
	CredentialOAuth CredentialKind = "oauth"
)

type LoginFlow

type LoginFlow struct {
	ProviderID string
	Prompt     string // authorize URL or device-code instructions
	URL        string // URL to open in a browser (may differ from Prompt)
	// contains filtered or unexported fields
}

LoginFlow represents an in-progress login flow. The caller displays Prompt to the user (and optionally opens URL in a browser), then calls Complete to finish the flow asynchronously.

func NewLoginFlow

func NewLoginFlow(providerID, prompt, url string, complete func(context.Context) (Credential, error)) *LoginFlow

NewLoginFlow constructs a LoginFlow with the given display fields and completion function. It is intended for providers that finish the flow synchronously (e.g. Copilot token import) and need a no-op Complete.

func StartLogin

func StartLogin(ctx context.Context, store *Store, def provider.Definition) (*LoginFlow, error)

StartLogin begins an OAuth-code or device-code login flow for def. The returned LoginFlow's Prompt should be displayed to the user and URL opened in a browser; then Complete should be called to finish the flow. For LoginOAuthCode and LoginDeviceCode only. For other login kinds, use LoginAPIKey or the provider-specific importer.

func (*LoginFlow) Complete

func (f *LoginFlow) Complete(ctx context.Context) (Credential, error)

Complete finishes the login flow, returning the saved credential. It blocks until the OAuth callback arrives, the device-code poll succeeds, or the context is cancelled.

type OAuthFlow

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

OAuthFlow holds the state of an in-progress OAuth authorization-code flow: the PKCE verifier, the CSRF state, and the running callback server. Start with StartOAuthFlow, display AuthorizeURL to the user, then call Complete to wait for the callback and exchange the code.

func StartOAuthFlow

func StartOAuthFlow(def provider.Definition) (*OAuthFlow, error)

StartOAuthFlow generates PKCE parameters, starts the loopback callback server, and returns a flow ready to display the authorize URL.

func (*OAuthFlow) AuthorizeURL

func (f *OAuthFlow) AuthorizeURL() string

AuthorizeURL returns the URL the user must open to authorize the app.

func (*OAuthFlow) Complete

func (f *OAuthFlow) Complete(ctx context.Context) (Credential, error)

Complete waits for the OAuth callback, exchanges the code for tokens, bootstraps identity, and returns a credential ready to save. The caller is responsible for persisting it via Store.Save.

type PostExchangeHook

type PostExchangeHook func(ctx context.Context, cred Credential, def provider.Definition) (Credential, error)

PostExchangeHook is called after a successful OAuth token exchange but before the credential is saved. It can enrich the credential (e.g., discover a Cloud Code Assist project ID) or validate the exchange (e.g., require a refresh token). Returning an error aborts the login.

Hooks are registered per provider ID via RegisterPostExchange, typically from an adapter package's init().

func GetPostExchange

func GetPostExchange(providerID string) PostExchangeHook

GetPostExchange returns the post-exchange hook for a provider, or nil.

type Resolved

type Resolved struct {
	// APIKey is the credential: an API key for CredentialAPIKey creds, or an
	// OAuth access token for CredentialOAuth creds. Empty if nothing resolved.
	APIKey string
	// IsOAuth is true when APIKey is an OAuth access token. The adapter uses
	// this to pick Authorization: Bearer + anthropic-beta vs x-api-key.
	IsOAuth bool
}

Resolved is the output of the credential resolution ladder: the key or token to present on the wire, and whether it is an OAuth bearer token (which changes the headers the adapter sends).

func Resolve

func Resolve(store *Store, def provider.Definition, configAPIKey string) (Resolved, error)

Resolve runs the credential resolution ladder for a provider:

  1. Stored credential from /login (OAuth with refresh, or API key)
  2. configAPIKey (from types.Config.APIKey — the existing YAML/env path)
  3. Environment variable (def.EnvVar — e.g. ANTHROPIC_API_KEY)
  4. Fallback (empty Resolved)

A stored OAuth credential whose access token is about to expire is refreshed in-place and persisted back to the store before returning. If the refresh fails, the resolver falls through to configAPIKey / env rather than returning an error — a stale OAuth token is worse than a working API key.

func ResolveOrError

func ResolveOrError(store *Store, def provider.Definition, configAPIKey string) (Resolved, error)

ResolveOrError is like Resolve but returns an error when no credential is found, for call sites that require auth (no anonymous access).

type Store

type Store struct {
	Path string
}

Store persists credentials as a single JSON file at Path. The file is written atomically (temp + rename, same directory), with 0o600 file mode under a 0o700 directory — the same discipline as chat/sessionstore.go. Concurrent writers are not serialized by the store; nib is single-process.

func NewStore

func NewStore(path string) *Store

NewStore returns a store backed by path. The file is not created until the first Save; Load and Delete tolerate a missing file.

func (*Store) All

func (s *Store) All() ([]Credential, error)

All returns every stored credential, sorted by providerID for stable output. Used by `nib login --list` and the /logout picker.

func (*Store) Delete

func (s *Store) Delete(providerID string) error

Delete removes the stored credential for providerID. A missing entry is not an error — the caller (logout) wants it gone, and it already is.

func (*Store) Get

func (s *Store) Get(providerID string) (Credential, bool, error)

Get returns the stored credential for providerID, or ok=false if none.

func (*Store) Save

func (s *Store) Save(cred Credential) error

Save stores cred for cred.ProviderID, replacing any existing credential for that provider (single-account model). Sets AuthorizedAt to now for OAuth credentials that do not already have it.

Directories

Path Synopsis
Package oauth implements the OAuth authorization-code flow with PKCE for remote providers that support it (currently Anthropic).
Package oauth implements the OAuth authorization-code flow with PKCE for remote providers that support it (currently Anthropic).

Jump to

Keyboard shortcuts

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