auth

package
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Jun 6, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

Documentation

Overview

Package auth turns a raw HTTP credential into a normalized Actor and decides what that actor may do. M1 implements classic personal access tokens, the OAuth device flow, and the resolution path that backs GET /user; later milestones add fine-grained grants, GitHub Apps, and the repository authorizer.

Index

Constants

View Source
const (
	PrefixClassicPAT  = "ghp_"        // personal access token (classic)
	PrefixFineGrained = "github_pat_" // fine-grained PAT
	PrefixOAuth       = "gho_"        // OAuth app user access token
	PrefixUserToSrv   = "ghu_"        // GitHub App user-to-server token
	PrefixInstall     = "ghs_"        // GitHub App installation token
	PrefixRefresh     = "ghr_"        // GitHub App refresh token
)

Token class prefixes. The third letter classes the credential, matching GitHub's published token formats so a token minted here is detectable by the same secret-scanning rules.

Variables

View Source
var (
	ErrUnknownClient      = errors.New("auth: unknown client_id")
	ErrDeviceFlowDisabled = errors.New("auth: device flow not enabled for this app")
)

Errors returned by the device-flow request step. The REST layer renders them as OAuth error bodies.

View Source
var ErrBadCredentials = errors.New("auth: bad credentials")

ErrBadCredentials is returned when a credential is present but invalid, expired, or revoked. It maps to 401 at the HTTP layer.

Functions

func HashToken

func HashToken(token string) [32]byte

HashToken returns the sha256 the store indexes tokens by. Every caller hashes through this so they agree byte-for-byte.

func VerifyChecksum

func VerifyChecksum(token string) bool

VerifyChecksum validates a presented token's class prefix and CRC32 offline, so a malformed or mistyped token is rejected without a database hit.

func WithActor

func WithActor(ctx context.Context, a *Actor) context.Context

WithActor returns a context carrying a.

Types

type Actor

type Actor struct {
	Kind Kind

	UserID    int64 // resolved user pk; 0 for anonymous
	UserLogin string
	SiteAdmin bool
	TokenID   int64 // tokens.pk of the credential used; 0 for anonymous

	Scopes    Scopes
	ExpiresAt *time.Time

	// RateKey identifies the rate-limit bucket this actor is charged against.
	RateKey string
}

Actor is the normalized principal placed in the request context. A request that reached the auth middleware always carries at least the anonymous actor, so handlers never nil-check.

func ActorFrom

func ActorFrom(ctx context.Context) *Actor

ActorFrom never returns nil: a request without a stored actor is treated as anonymous.

func Anonymous

func Anonymous() *Actor

Anonymous returns the unauthenticated actor.

func (*Actor) IsAuthenticated

func (a *Actor) IsAuthenticated() bool

IsAuthenticated reports whether a real credential resolved.

func (*Actor) IsUser

func (a *Actor) IsUser() bool

IsUser reports whether the actor acts as a user.

type DeviceCodeResult

type DeviceCodeResult struct {
	DeviceCode      string
	UserCode        string
	VerificationURI string
	ExpiresIn       int
	Interval        int
}

DeviceCodeResult is the body of a successful POST /login/device/code.

type DeviceTokenOutcome

type DeviceTokenOutcome struct {
	Token            *IssuedToken
	Error            string // authorization_pending | slow_down | expired_token | access_denied | ...
	ErrorDescription string
	Interval         int // set with slow_down
}

DeviceTokenOutcome is the result of one poll of the device token endpoint. GitHub answers every poll with HTTP 200 and a JSON body that is either the token or an OAuth error, so the protocol-level conditions live here rather than in the returned error, which is reserved for genuine server failures.

type Generated

type Generated struct {
	Plaintext string   // shown once, never stored
	Prefix    string   // "ghp_" etc.
	Hash      [32]byte // sha256(Plaintext); store Hash[:] in tokens.token_hash
	Last8     string   // last eight chars, for the settings UI
}

Generated holds everything the store needs plus the one-time plaintext. The plaintext is shown to the user exactly once and never persisted.

func GenerateToken

func GenerateToken(prefix string) (Generated, error)

GenerateToken mints a token with the given class prefix. It never touches the database; the caller persists the hash.

type IssuedToken

type IssuedToken struct {
	AccessToken string
	TokenType   string
	Scope       string
}

IssuedToken is a minted user token returned by a completed device exchange.

type Kind

type Kind uint8

Kind classifies the credential behind an Actor.

const (
	KindAnonymous    Kind = iota // no or invalid credential, public-only access
	KindUser                     // classic PAT or OAuth user token
	KindUserToServer             // ghu_: a user bounded by an installation
	KindInstallation             // ghs_: an installation, no user
	KindAppJWT                   // app-level JWT
)

The actor kinds. M1 produces Anonymous and User; the App-related kinds are reserved for later milestones.

type Scope

type Scope string

Scope is a classic OAuth/PAT scope string, e.g. "repo" or "read:org".

type Scopes

type Scopes []Scope

Scopes is a set of classic scopes, kept sorted and deduplicated after NormalizeScopes so the X-OAuth-Scopes header round-trips byte-for-byte with GitHub for the common cases.

func NormalizeScopes

func NormalizeScopes(in Scopes) Scopes

NormalizeScopes keeps only known scopes, drops any implied by a held parent, then dedupes and sorts.

func ParseScopeParam

func ParseScopeParam(s string) Scopes

ParseScopeParam splits a space- or comma-delimited scope parameter (the OAuth "scope" field accepts both) into a Scopes set.

func (Scopes) Has

func (ss Scopes) Has(sc Scope) bool

Has reports whether the set effectively carries sc, expanding parents.

func (Scopes) Header

func (ss Scopes) Header() string

Header renders the set as GitHub's comma-space separated list, e.g. "gist, repo".

func (Scopes) Strings

func (ss Scopes) Strings() []string

Strings returns the scopes as a plain string slice, preserving order.

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service resolves credentials into Actors and serves the OAuth device flow. It is framework-agnostic: it speaks contexts, strings, and typed results, never http.ResponseWriter, so the REST layer owns all request and response wiring. The dependency direction is auth -> store only.

func NewService

func NewService(st Store, baseURL string) *Service

NewService wires a Service over the store. baseURL is the site root used in device-flow responses (for example https://git.example.com).

func (*Service) ApproveDeviceCode

func (s *Service) ApproveDeviceCode(ctx context.Context, userCode string, userPK int64) error

ApproveDeviceCode marks the session behind userCode approved by userPK. It returns store.ErrNotFound when the code is unknown or already expired.

func (*Service) Authenticate

func (s *Service) Authenticate(ctx context.Context, authorization string) (*Actor, error)

Authenticate turns an Authorization header value into an Actor. An empty header is the anonymous actor with a nil error: public reads still work, and handlers decide whether to demand authentication. A present-but-invalid credential returns ErrBadCredentials, which the REST layer maps to 401.

func (*Service) Close

func (s *Service) Close()

Close releases the background last-used flusher.

func (*Service) DenyDeviceCode

func (s *Service) DenyDeviceCode(ctx context.Context, userCode string) error

DenyDeviceCode marks the session behind userCode denied.

func (*Service) PollDeviceToken

func (s *Service) PollDeviceToken(ctx context.Context, clientID, deviceCode string) (*DeviceTokenOutcome, error)

PollDeviceToken advances the device-flow state machine for one poll. The returned error is non-nil only on a genuine failure (a bad client, an unknown device code, or a store error); every protocol condition is reported in the outcome.

func (*Service) RequestDeviceCode

func (s *Service) RequestDeviceCode(ctx context.Context, clientID, scopeParam string) (*DeviceCodeResult, error)

RequestDeviceCode opens a device-flow session for the given client and scopes.

type Store

type Store interface {
	// Credential resolution.
	TokenByHash(ctx context.Context, hash []byte) (*store.TokenRow, error)
	UserByPK(ctx context.Context, pk int64) (*store.UserRow, error)
	BumpTokenLastUsed(ctx context.Context, at map[int64]time.Time) error

	// OAuth device flow.
	OAuthAppByClientID(ctx context.Context, clientID string) (*store.OAuthAppRow, error)
	InsertToken(ctx context.Context, t *store.TokenRow) error
	InsertDeviceCode(ctx context.Context, d *store.DeviceCodeRow) error
	DeviceCodeByHash(ctx context.Context, hash []byte) (*store.DeviceCodeRow, error)
	DeviceCodeByUserCode(ctx context.Context, userCode string) (*store.DeviceCodeRow, error)
	SetDeviceState(ctx context.Context, pk int64, state string, userPK int64) error
	SetDeviceInterval(ctx context.Context, pk int64, interval int) error
	SetDevicePolled(ctx context.Context, pk int64, at time.Time) error
	DeleteDeviceCode(ctx context.Context, pk int64) error
}

Store is the narrow slice of the metadata store the auth package depends on. *store.Store satisfies it. Keeping the dependency to an interface lets the auth tests drive the service with an in-memory fake and documents exactly which store methods auth reaches for.

Jump to

Keyboard shortcuts

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