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
- Variables
- func HashToken(token string) [32]byte
- func VerifyChecksum(token string) bool
- func WithActor(ctx context.Context, a *Actor) context.Context
- type Actor
- type DeviceCodeResult
- type DeviceTokenOutcome
- type Generated
- type IssuedToken
- type Kind
- type Scope
- type Scopes
- type Service
- func (s *Service) ApproveDeviceCode(ctx context.Context, userCode string, userPK int64) error
- func (s *Service) Authenticate(ctx context.Context, authorization string) (*Actor, error)
- func (s *Service) Close()
- func (s *Service) DenyDeviceCode(ctx context.Context, userCode string) error
- func (s *Service) PollDeviceToken(ctx context.Context, clientID, deviceCode string) (*DeviceTokenOutcome, error)
- func (s *Service) RequestDeviceCode(ctx context.Context, clientID, scopeParam string) (*DeviceCodeResult, error)
- type Store
Constants ¶
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 ¶
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.
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 ¶
HashToken returns the sha256 the store indexes tokens by. Every caller hashes through this so they agree byte-for-byte.
func VerifyChecksum ¶
VerifyChecksum validates a presented token's class prefix and CRC32 offline, so a malformed or mistyped token is rejected without a database hit.
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 ¶
ActorFrom never returns nil: a request without a stored actor is treated as anonymous.
func (*Actor) IsAuthenticated ¶
IsAuthenticated reports whether a real credential resolved.
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 ¶
GenerateToken mints a token with the given class prefix. It never touches the database; the caller persists the hash.
type IssuedToken ¶
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 ¶
NormalizeScopes keeps only known scopes, drops any implied by a held parent, then dedupes and sorts.
func ParseScopeParam ¶
ParseScopeParam splits a space- or comma-delimited scope parameter (the OAuth "scope" field accepts both) into a Scopes set.
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 ¶
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 ¶
ApproveDeviceCode marks the session behind userCode approved by userPK. It returns store.ErrNotFound when the code is unknown or already expired.
func (*Service) Authenticate ¶
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) DenyDeviceCode ¶
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.