deviceauth

package module
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Sep 27, 2026 License: MIT Imports: 18 Imported by: 0

README

deviceauth

deviceauth provides reusable OAuth 2.0 Device Authorization Grant UX and credential storage for Go command-line applications.

It deliberately stays below product and command-framework concerns:

  • RFC 8628 protocol behavior is delegated to golang.org/x/oauth2.
  • browser launch is best-effort and always has a visible manual fallback;
  • credentials use the operating system credential store by default;
  • plaintext JSON storage is available only when a caller explicitly selects it;
  • callers own product names, OAuth endpoints, client IDs, scopes, and commands.
result, err := deviceauth.Login(ctx, deviceauth.LoginOptions{
	OAuthConfig: oauth2.Config{
		ClientID: "example-cli",
		Scopes:   []string{"account:read"},
		Endpoint: oauth2.Endpoint{
			DeviceAuthURL: "https://cloud.example.com/oauth/device/code",
			TokenURL:      "https://cloud.example.com/oauth/token",
		},
	},
	DeviceInfo: deviceauth.DeviceInfo{
		Name: "Alex's MacBook Pro", OS: "darwin", Arch: "arm64", ClientVersion: "1.2.3",
	},
	OpenBrowser: deviceauth.OpenBrowser,
	Output:      os.Stdout,
	ErrorOutput: os.Stderr,
})

DeviceInfo is sent as optional device-authorization request parameters so a server can identify the requesting device during consent. It is informational, untrusted metadata; authorization state and timestamps remain server-owned.

The module is MIT licensed.

Documentation

Overview

Package deviceauth provides reusable OAuth 2.0 device-login UX and credential storage for command-line applications.

Index

Constants

This section is empty.

Variables

View Source
var ErrCredentialNotFound = errors.New("deviceauth: credential not found")

ErrCredentialNotFound reports that no credential exists for a configured service/account pair.

View Source
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 OpenBrowser

func OpenBrowser(rawURL string) error

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

func ValidateRequiredScopes(granted, required []string) error

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

func (c *Client) Issuer() string

Issuer returns the canonical base URL configured for this client.

func (*Client) Logout added in v0.1.0

func (c *Client) Logout(ctx context.Context, store Store) error

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

func (c *Client) NewKeyringStore() (Store, error)

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

func (c *Client) OAuthConfig() oauth2.Config

OAuthConfig returns the configuration needed by the existing Login API.

func (*Client) Revoke added in v0.1.0

func (c *Client) Revoke(ctx context.Context, token string) error

Revoke invalidates token at the configured OAuth service.

func (*Client) ScopedStore added in v0.1.0

func (c *Client) ScopedStore(store Store) Store

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) UserInfo added in v0.1.0

func (c *Client) UserInfo(ctx context.Context, token *oauth2.Token) (Identity, error)

UserInfo validates the identity associated with token. The service contract is an OAuth-style JSON response with a non-empty "sub" and an "aud" string or array that includes this client's ID. "scope" may be a space-separated string or an array.

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
}

ClientConfig configures a reusable device-authorization client for one OAuth issuer and public CLI client. Issuer is the base URL, for example "https://auth.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

type DeviceInfo struct {
	Name          string
	OS            string
	Arch          string
	ClientVersion string
}

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
}

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

func NewFileStore(path string) (*FileStore, error)

NewFileStore returns a plaintext store at path.

func (*FileStore) Delete

func (s *FileStore) Delete() error

Delete removes the plaintext credential file. Absence succeeds.

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

type Identity struct {
	Subject  string
	Name     string
	Email    string
	Audience []string
	Scopes   []string
}

Identity is a validated response from the issuer's userinfo endpoint. Audience is always non-empty and contains ClientID after UserInfo returns.

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 (*ReplacementRevocationWarning) Unwrap added in v0.1.0

func (e *ReplacementRevocationWarning) Unwrap() error

type ResponseError added in v0.1.0

type ResponseError struct {
	Operation  string
	StatusCode int
	OAuthError string
}

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.

type TokenTransformer added in v0.1.0

type TokenTransformer func(context.Context, *oauth2.Token) (*oauth2.Token, error)

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.

Jump to

Keyboard shortcuts

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