Documentation
¶
Overview ¶
Package deviceauth provides reusable OAuth 2.0 device-login UX and credential storage for command-line applications.
Index ¶
- Variables
- func DefaultIssuerMovedNotice(moved *IssuerMovedError) string
- func OpenBrowser(rawURL string) error
- func ValidateRequiredScopes(granted, required []string) error
- type Authentication
- type Client
- func (c *Client) DeviceLogin(ctx context.Context, options DeviceLoginOptions) (LoginResult, error)
- func (c *Client) DeviceLoginAndStore(ctx context.Context, options DeviceLoginOptions, store Store) (Authentication, error)
- func (c *Client) Issuer() string
- func (c *Client) Logout(ctx context.Context, store Store) error
- func (c *Client) NewKeyringStore() (Store, error)
- func (c *Client) OAuthConfig() oauth2.Config
- func (c *Client) PreviousIssuers() []string
- func (c *Client) Revoke(ctx context.Context, token string) error
- func (c *Client) ScopedStore(store Store) Store
- func (c *Client) ServingIssuer() string
- func (c *Client) UserInfo(ctx context.Context, token *oauth2.Token) (Identity, error)
- type ClientConfig
- type Credential
- type DeviceInfo
- type DeviceLoginOptions
- type FileStore
- type Identity
- type IssuerMovedError
- type KeyringStore
- type LoginOptions
- type LoginResult
- type ReplacementRevocationWarning
- type ResponseError
- type Store
- type TokenTransformer
Constants ¶
This section is empty.
Variables ¶
var ErrCredentialIssuerMoved = errors.New("deviceauth: credential was saved for a previous issuer")
ErrCredentialIssuerMoved reports a stored credential that was issued while the authorization service still answered at one of the Client's PreviousIssuers. errors.Is also matches ErrCredentialScopeMismatch, because such a credential is never a session for the current issuer.
var ErrCredentialNotFound = errors.New("deviceauth: credential not found")
ErrCredentialNotFound reports that no credential exists for a configured service/account pair.
var ErrCredentialScopeMismatch = errors.New("deviceauth: credential issuer or client ID does not match")
ErrCredentialScopeMismatch reports a credential that was saved for a different issuer or OAuth client. Callers must not use it as a session for the current Client.
Functions ¶
func DefaultIssuerMovedNotice ¶ added in v0.2.0
func DefaultIssuerMovedNotice(moved *IssuerMovedError) string
DefaultIssuerMovedNotice is the line DeviceLoginAndStore writes to ErrorOutput when it replaces a credential bound to a previous issuer and the caller set no DeviceLoginOptions.IssuerMovedNotice.
func OpenBrowser ¶
OpenBrowser opens rawURL in the user's default browser. Callers should treat errors as a reason to show a manual URL, not as a failed authorization.
func ValidateRequiredScopes ¶ added in v0.1.0
ValidateRequiredScopes verifies that every explicitly required scope was granted by the issuer. With no requirements it succeeds; an absent or reduced granted scope claim never becomes a synthetic requested grant.
Types ¶
type Authentication ¶ added in v0.1.0
type Authentication struct {
Login LoginResult
SessionToken *oauth2.Token
Identity Identity
Credential Credential
// Warnings report completed logins that need operator attention but are
// safe to use. In particular, a new local credential remains valid when a
// replaced server token could not be revoked.
Warnings []error
}
Authentication is the complete, validated result of DeviceLoginAndStore.
type Client ¶ added in v0.1.0
type Client struct {
// contains filtered or unexported fields
}
Client provides the neutral client-side half of a device authorization service. It knows the standard endpoint layout below its issuer, but does not depend on a particular identity provider or product.
func NewClient ¶ added in v0.1.0
func NewClient(config ClientConfig) (*Client, error)
NewClient validates config and returns a client for an OAuth service.
func (*Client) DeviceLogin ¶ added in v0.1.0
func (c *Client) DeviceLogin(ctx context.Context, options DeviceLoginOptions) (LoginResult, error)
DeviceLogin starts and completes the RFC 8628 flow using this client's configured service. It preserves the package Login function for existing consumers such as OVDB.
func (*Client) DeviceLoginAndStore ¶ added in v0.1.0
func (c *Client) DeviceLoginAndStore(ctx context.Context, options DeviceLoginOptions, store Store) (Authentication, error)
DeviceLoginAndStore completes device authorization, validates userinfo, and saves a credential bound to this issuer and client. The supplied store is scoped automatically; this prevents accidental reuse of the same file or keyring account by another issuer/client pair.
func (*Client) Issuer ¶ added in v0.1.0
Issuer returns the canonical base URL configured for this client.
func (*Client) Logout ¶ added in v0.1.0
Logout revokes the stored access token at the issuer before removing the local credential. A failed revoke leaves the local credential in place so a caller can retry rather than falsely reporting a successful logout.
func (*Client) NewKeyringStore ¶ added in v0.1.0
NewKeyringStore returns an issuer/client-isolated keyring store. It requires the product-specific KeyringService and KeyringAccount from ClientConfig.
func (*Client) OAuthConfig ¶ added in v0.1.0
OAuthConfig returns the configuration needed by the existing Login API.
func (*Client) PreviousIssuers ¶ added in v0.2.0
PreviousIssuers returns the canonical previous issuers configured for this client.
func (*Client) ScopedStore ¶ added in v0.1.0
ScopedStore wraps store so every saved credential is bound to this issuer and client. Load rejects a credential saved for a different pair.
func (*Client) ServingIssuer ¶ added in v0.2.0
ServingIssuer returns the base URL that actually answered for this Client: the configured Issuer, or one of FallbackIssuers while Issuer is not serving yet. It returns Issuer before any request was made. Stored credentials are always bound to Issuer regardless of which base URL served them.
type ClientConfig ¶ added in v0.1.0
type ClientConfig struct {
Issuer string
ClientID string
Scopes []string
// RequiredScopes is the subset of requested Scopes a caller requires for a
// usable session. It is checked against the issuer's authoritative
// userinfo scope claim; an omitted scope claim is never treated as a grant.
RequiredScopes []string
KeyringService string
KeyringAccount string
// PreviousIssuers lists base URLs at which this same authorization service
// answered before it moved to Issuer. A stored credential bound to one of
// them (and to ClientID) loads as *IssuerMovedError rather than a plain
// scope mismatch, so a caller can explain the move and start a fresh
// login. NewKeyringStore also finds credentials saved under their keyring
// identities. New credentials are always bound to Issuer.
PreviousIssuers []string
// FallbackIssuers lists base URLs that serve this same authorization
// service while Issuer does not answer yet. Until Issuer has answered, a
// transport failure (DNS, connection, TLS) or HTTP 404 from it moves the
// request to the next fallback; the first base URL that answers is used
// for the rest of this Client's lifetime. Credentials stay bound to Issuer.
// Callers should configure fallbacks only for their default production
// issuer, never for an operator override.
FallbackIssuers []string
}
ClientConfig configures a reusable device-authorization client for one OAuth issuer and public CLI client. Issuer is the base URL, for example "https://oauth.sneat.co". KeyringService and KeyringAccount are product chosen names used only when NewKeyringStore is called.
type Credential ¶
type Credential struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"`
Expiry time.Time `json:"expiry,omitempty"`
// Issuer and ClientID bind a persisted credential to the authorization
// service and public OAuth client that issued it. They are set by a
// Client's ScopedStore; the fields remain optional for backwards
// compatibility with credentials saved by older callers.
Issuer string `json:"issuer,omitempty"`
ClientID string `json:"client_id,omitempty"`
AccountID string `json:"account_id,omitempty"`
AccountName string `json:"account_name,omitempty"`
Scopes []string `json:"scopes,omitempty"`
}
Credential is the persisted result of a device authorization.
type DeviceInfo ¶ added in v0.0.2
DeviceInfo describes the command-line device requesting authorization. Authorization servers may display these optional, untrusted values during consent and retain them with the resulting grant.
type DeviceLoginOptions ¶ added in v0.1.0
type DeviceLoginOptions struct {
DeviceInfo DeviceInfo
OpenBrowser func(string) error
Output io.Writer
ErrorOutput io.Writer
TokenTransformer TokenTransformer
// IssuerMovedNotice is written as one line to ErrorOutput when
// DeviceLoginAndStore replaces a credential bound to a previous issuer.
// Empty selects DefaultIssuerMovedNotice.
IssuerMovedNotice string
}
DeviceLoginOptions are the presentation and device details used during a device authorization. The issuer, client ID, scopes, and endpoints always come from Client.
type FileStore ¶
type FileStore struct {
// contains filtered or unexported fields
}
FileStore is an explicitly insecure 0600 JSON credential store. It is provided for headless environments only; callers should require a user flag before selecting it.
func NewFileStore ¶
NewFileStore returns a plaintext store at path.
func (*FileStore) Load ¶
func (s *FileStore) Load() (Credential, error)
Load reads and validates the plaintext credential file.
func (*FileStore) Save ¶
func (s *FileStore) Save(credential Credential) error
Save atomically writes a credential with directory mode 0700 and file mode 0600. The token remains plaintext and must be treated as sensitive.
type Identity ¶ added in v0.1.0
Identity is a validated response from the issuer's userinfo endpoint. Audience is always non-empty and contains ClientID after UserInfo returns.
type IssuerMovedError ¶ added in v0.2.0
type IssuerMovedError struct {
PreviousIssuer string
Issuer string
Credential Credential
}
IssuerMovedError is returned by a Client's scoped store when the stored credential is bound to one of the Client's PreviousIssuers. Credential holds the stored value so a caller can revoke it at the current issuer; Error never includes token material.
func (*IssuerMovedError) Error ¶ added in v0.2.0
func (e *IssuerMovedError) Error() string
func (*IssuerMovedError) Is ¶ added in v0.2.0
func (e *IssuerMovedError) Is(target error) bool
Is lets errors.Is match both ErrCredentialIssuerMoved and the more general ErrCredentialScopeMismatch.
type KeyringStore ¶
type KeyringStore struct {
// contains filtered or unexported fields
}
KeyringStore keeps a credential in the operating system credential store.
func NewKeyringStore ¶
func NewKeyringStore(service, account string) (*KeyringStore, error)
NewKeyringStore returns a secure credential store. service should identify the CLI and account should normally identify the authorization host.
func (*KeyringStore) Delete ¶
func (s *KeyringStore) Delete() error
Delete removes the credential. Deleting an absent credential succeeds.
func (*KeyringStore) Load ¶
func (s *KeyringStore) Load() (Credential, error)
Load reads and validates the credential in the operating system keyring.
func (*KeyringStore) Save ¶
func (s *KeyringStore) Save(credential Credential) error
Save stores credential as one JSON value in the operating system keyring.
type LoginOptions ¶
type LoginOptions struct {
OAuthConfig oauth2.Config
DeviceInfo DeviceInfo
OpenBrowser func(string) error
Output io.Writer
ErrorOutput io.Writer
}
LoginOptions configures one browser-approved OAuth 2.0 device login. Product-specific commands own the OAuth endpoints, client ID, and scopes.
type LoginResult ¶
type LoginResult struct {
Token *oauth2.Token
Authorization *oauth2.DeviceAuthResponse
BrowserOpened bool
}
LoginResult contains the issued token and the authorization prompt that led to it. BrowserOpened reports only whether the launcher succeeded; a failed launcher is intentionally non-fatal because the visible URL remains usable.
func Login ¶
func Login(ctx context.Context, options LoginOptions) (LoginResult, error)
Login starts an RFC 8628 device authorization, shows the user code and URL, attempts to open the verification page, and polls until approval, denial, expiry, or context cancellation.
type ReplacementRevocationWarning ¶ added in v0.1.0
type ReplacementRevocationWarning struct {
Cause error
}
ReplacementRevocationWarning reports that a new credential was saved but the previous credential could not be revoked. The authentication succeeded; callers may surface this warning and retry revocation later.
func (*ReplacementRevocationWarning) Error ¶ added in v0.1.0
func (e *ReplacementRevocationWarning) Error() string
func (*ReplacementRevocationWarning) Unwrap ¶ added in v0.1.0
func (e *ReplacementRevocationWarning) Unwrap() error
type ResponseError ¶ added in v0.1.0
ResponseError is a redacted non-success response from the authorization service. It deliberately excludes arbitrary response text, which may carry secrets or HTML from a proxy. OAuthError is populated only for a safe OAuth error code.
func (*ResponseError) Error ¶ added in v0.1.0
func (e *ResponseError) Error() string
type Store ¶
type Store interface {
Save(Credential) error
Load() (Credential, error)
Delete() error
}
Store persists and removes one credential selected by the concrete store's service/account configuration.
func NewMigratingStore ¶ added in v0.2.0
NewMigratingStore returns a store that reads current first and, only when current holds no credential, the first credential found in previous. Wrap it in Client.ScopedStore: a credential found in a previous location that is bound to one of the Client's PreviousIssuers then loads as *IssuerMovedError. A successful Save, and Delete, also remove the credentials in previous so a moved credential is reported at most until the next login or logout. Errors reading a previous location are ignored: they must never block a fresh login.
type TokenTransformer ¶ added in v0.1.0
TokenTransformer converts the token returned by the device token endpoint into the session token accepted by userinfo and the product API. It is deliberately identity-provider-neutral: a consumer may exchange a one-use bootstrap token for its own session without this package importing that provider's SDK.
The input is always the unmodified token returned by DeviceLogin. The transformer must return a non-nil token with a non-empty access token.