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

clisignin

package
v0.17.2 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2026 License: AGPL-3.0 Imports: 17 Imported by: 0

Documentation

Overview

Package clisignin signs the CLI in through the browser (R-406, design 06 §3.5).

The shape is OAuth 2.0's authorization code grant for a native app (RFC 8252) with PKCE (RFC 7636), with Pando as its own authorization server and the console as its consent page:

  1. `pando login` makes a verifier and sends only its SHA-256, the challenge, to the console in the address it opens.
  2. The person, signed in to the console however their account signs in — password and second factor, or an identity provider — approves it. Approve stores the challenge against their account and answers with a one-time code, which the browser carries to the CLI's loopback address, or which the person pastes into the CLI.
  3. The CLI trades the code and the verifier for a delegated token (Exchange).

The code alone is worth nothing: a code that reaches the wrong hands — a link sent to someone, a page that read the loopback redirect — cannot be traded without the verifier, which never left the process that started the sign-in. That is this flow's version of R-043's rule that a sign-in finishes only where it started.

Index

Constants

View Source
const CodeLifetime = 5 * time.Minute

CodeLifetime is how long an approval waits for the CLI [P]: long enough to copy a code from one window and paste it into a terminal, short enough that one left on a screen is soon worth nothing.

Variables

This section is empty.

Functions

func Challenge

func Challenge(verifier string) string

Challenge is the S256 code challenge for a verifier (RFC 7636 §4.2).

func Digest

func Digest(code string) string

Digest is what is stored of a code: its SHA-256, hex.

Types

type AccountReader

type AccountReader interface {
	ByID(ctx context.Context, userID string) (state.User, bool, error)
}

AccountReader reads accounts (state.Users).

type Approved

type Approved struct {
	Code secret.Value

	// RedirectURL is the CLI's loopback address with the code on it, or
	// empty when the person pastes the code instead.
	RedirectURL string

	ExpiresAt time.Time
}

Approved is an approval's one-time code and where the browser takes it.

type Exchanged

type Exchanged struct {
	Issued state.Issued

	// UserID and Username are the account the token acts as (R-058).
	UserID   string
	Username string

	// ApprovedBy is the session that approved it.
	ApprovedBy string
	Name       string
}

Exchanged is a token made for an approved sign-in.

type Request

type Request struct {
	// CodeChallenge is base64url(SHA-256(verifier)), unpadded.
	CodeChallenge       string `json:"code_challenge"`
	CodeChallengeMethod string `json:"code_challenge_method"`

	// Name is the token's name: the machine's, "laptop CLI".
	Name string `json:"name"`

	// Port is the loopback port the CLI is listening on, or 0 when it waits
	// for the code to be pasted.
	Port int `json:"port"`

	// State is echoed to the CLI's loopback address, so it can tell its own
	// sign-in's answer from any other request that reaches the port.
	State string `json:"state"`
}

Request is what the CLI put in the address it opened, as the console sends it on.

type Service

type Service struct {
	Store  Store
	Users  AccountReader
	Tokens TokenMinter
	Policy interface {
		Load(ctx context.Context) (policy.Document, error)
	}
	Clock clock.Clock
}

Service approves CLI sign-ins and trades them for tokens. It decides nothing about who is asking: the caller has established that a signed-in person, not a token, approved, and audits what happened.

func (*Service) Approve

func (s *Service) Approve(ctx context.Context, userID, approvedBy string, req Request) (Approved, error)

Approve records that userID, signed in as approvedBy, lets the CLI that made req sign in as them.

func (*Service) Exchange

func (s *Service) Exchange(ctx context.Context, code, verifier string) (Exchanged, error)

Exchange trades a code and the verifier behind its challenge for a delegated token. The approval is gone whatever the outcome. When a code that named an approval is refused, the Exchanged returned with the error says whose it was (UserID, ApprovedBy, Name); for any other refusal it is empty.

type Store

type Store interface {
	Create(ctx context.Context, s state.CLISignIn) error
	Take(ctx context.Context, codeHash string) (state.CLISignIn, bool, error)
}

Store keeps approvals (state.CLISignIns).

type TokenMinter

type TokenMinter interface {
	Create(ctx context.Context, kind, name, ownerUserID, createdBy string, expiresAt *time.Time) (state.Issued, error)
}

TokenMinter mints tokens (state.Tokens).

Jump to

Keyboard shortcuts

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