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

cognito

package module
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Oct 2, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package cognito implements the Cognito authorization-code-with-PKCE login flow a command-line tool uses to obtain a human operator's tokens.

It is built for CLIs rather than services: the flow opens a browser, receives the authorization code on a loopback listener, and hands back the token set for the caller to cache. A service verifying an incoming token wants github.com/wego/pkg/http/jwt instead.

Two deliberate constraints shape this package:

  • It depends on nothing outside the standard library (bar Wego's string helpers). Hand-rolling the OAuth exchange keeps every wire parameter visible and auditable, which matters more here than the convenience an OAuth library would buy.
  • It holds no package-level mutable state. Every dependency - the clock, the HTTP client, the browser opener - arrives through Config, so tests and callers never race over shared globals.

Index

Constants

This section is empty.

Variables

View Source
var ErrInsecureEndpoint = errors.New("insecure cognito endpoint")

ErrInsecureEndpoint is returned for a configured URL or bind address that must not carry OAuth credentials.

Functions

This section is empty.

Types

type Config

type Config struct {
	// AuthorizeURL is the Cognito hosted-UI authorize endpoint.
	AuthorizeURL string

	// TokenURL is the Cognito token endpoint.
	TokenURL string

	// ClientID is the Cognito app client id.
	ClientID string

	// RedirectURI is the callback URL registered on the app client. Its path
	// determines where the local callback server listens.
	RedirectURI string

	// Scopes is the space-separated scope list to request.
	Scopes string

	// AllowedDomain, when set, is the email suffix an operator must sign in
	// with, e.g. "@wego.com".
	AllowedDomain string

	// CallbackAddr is the host:port the local callback server binds, e.g.
	// "127.0.0.1:8110". It must agree with RedirectURI.
	CallbackAddr string

	// IdentityProvider, when set, is forwarded as identity_provider so Cognito
	// jumps straight to that IdP instead of showing its own chooser.
	IdentityProvider string

	// OpenBrowser launches the authorize URL. Nil uses the OS default opener.
	OpenBrowser func(string) error

	// NoBrowser suppresses the browser launch. Login reports the authorize URL
	// through PromptURL instead and then waits on the loopback listener exactly
	// as it otherwise would, for a headless shell, a terminal on a remote host,
	// or an operator who would rather open the URL themselves.
	//
	// The redirect still lands on CallbackAddr, so when the browser runs on a
	// different machine than the CLI that port has to be reachable from it —
	// usually `ssh -L <port>:localhost:<port>`. Suppressing the launch does not
	// move where the code is delivered.
	NoBrowser bool

	// PromptURL receives the authorize URL in place of a browser launch, so the
	// caller decides how to surface it: print it, render a QR code, hand it to
	// another process. Required when NoBrowser or ReadRedirect is set — a
	// sign-in whose URL the operator never sees cannot complete — and ignored
	// otherwise.
	PromptURL func(url string) error

	// ReadRedirect turns the sign-in into a paste-back exchange: instead of a
	// loopback listener receiving the redirect, the caller returns the URL the
	// browser was redirected to and Login reads the code out of it.
	//
	// This is the only path that works with NO browser on this machine AND no
	// way to reach its callback port. Cognito redirects to a loopback URL that
	// nothing is listening on, the browser shows a connection error, and the
	// address bar holds ?code=...&state=... for the operator to copy back.
	//
	// Cognito has no device authorization grant (RFC 8628): its discovery
	// document advertises no device_authorization_endpoint, /oauth2/device_
	// authorization is 404, and the token endpoint answers a device_code grant
	// with unsupported_grant_type. So this is the substitute, and it needs no
	// extra Cognito configuration because it is still the authorization code
	// grant with PKCE, only with the redirect carried by hand.
	//
	// Setting it implies NoBrowser: no browser is launched here. PromptURL is
	// required alongside it; CallbackAddr is not, since nothing binds a port.
	// The pasted URL carries a single-use authorization code, so it should not
	// travel through a shared channel; state is still checked, so a code from a
	// different attempt is rejected.
	//
	// LIFETIME CONTRACT. Read this before wiring a reader to it.
	//
	// The ctx is the one passed to Login, and honouring it is the caller's
	// responsibility. Login itself stops waiting when ctx is done, so a
	// cancelled or timed-out Login always returns — but it CANNOT interrupt a
	// read already blocked in this function. A reader that ignores ctx (a bare
	// os.Stdin.Read, or bufio.Reader over it) therefore stays blocked after
	// Login has returned, holding whatever it reads from.
	//
	// Two obligations follow:
	//
	//   - On cancellation, tear the reader down yourself if you need the read to
	//     stop — close the file or pipe, or use a reader that selects on ctx.
	//     Nothing in this package can do it for you.
	//   - Do NOT start another Login while a previous invocation may still be
	//     blocked in here. Two live invocations reading the same stdin race for
	//     the operator's next line: either may win, so a retry can consume the
	//     paste meant for the other and the losing attempt sees a truncated or
	//     empty read. Wait for the previous reader to yield, or give the retry
	//     its own reader.
	//
	// The abandoned invocation is otherwise harmless: Login sends its result
	// into a buffered channel and exits, so it parks rather than leaks and never
	// blocks on a receiver that has gone.
	ReadRedirect func(ctx context.Context) (redirectedURL string, err error)

	// HTTPClient calls the token endpoint. Nil uses a client with a timeout.
	HTTPClient *http.Client

	// Now supplies the current time, for computing token expiry. Nil uses
	// time.Now; tests inject a fixed clock.
	Now func() time.Time
}

Config carries everything the login flow needs. Nothing is read from the environment or from package state, so a caller holds the whole contract.

type TokenSet

type TokenSet struct {
	AccessToken  string    `json:"access_token"` //nolint:gosec // G117: this type exists to carry tokens; stored only in the local keychain, never logged.
	IDToken      string    `json:"id_token"`
	RefreshToken string    `json:"refresh_token"` //nolint:gosec // G117: as above - the refresh token is the credential this type is for.
	ExpiresAt    time.Time `json:"expires_at"`
}

TokenSet is the set of Cognito tokens a signed-in operator holds.

Holding tokens is the whole point of the type, so gosec's secret-field warning is expected here rather than a finding: these values are never logged, and the only place they are serialized to is the operator's own keychain (see github.com/wego/pkg/cognito/storage).

func Login

func Login(ctx context.Context, cfg Config) (*TokenSet, error)

Login runs the browser-based authorization-code-with-PKCE flow and returns the operator's tokens.

func Refresh

func Refresh(ctx context.Context, cfg Config, refreshToken string) (*TokenSet, error)

Refresh exchanges a refresh token for a fresh access and id token.

func (*TokenSet) Email

func (t *TokenSet) Email() (string, error)

Email returns the email claim from the id_token.

It reads the JWT payload WITHOUT verifying the signature, which is correct here: the token came from our own keychain, and every server that accepts it verifies the signature itself. Treat this as decoding for display and client-side UX, NOT as validation - a caller must never make an authorization decision on the strength of these claims.

func (*TokenSet) IsExpired

func (t *TokenSet) IsExpired(now time.Time) bool

IsExpired reports whether the access token is spent as of now, treating anything inside expiryLeeway of the deadline as already gone. A nil set, or one with no recorded expiry, counts as expired so callers refresh rather than send a token that may already be dead.

Directories

Path Synopsis
Package storage persists the Cognito token sets obtained by github.com/wego/pkg/cognito.
Package storage persists the Cognito token sets obtained by github.com/wego/pkg/cognito.

Jump to

Keyboard shortcuts

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