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:
- `pando login` makes a verifier and sends only its SHA-256, the challenge, to the console in the address it opens.
- 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.
- 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 ¶
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 ¶
Types ¶
type AccountReader ¶
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 ¶
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.