link

package
v1.76.10 Latest Latest
Warning

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

Go to latest
Published: Oct 15, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package link is what a person linking their GitHub account leaves behind, shared by the service that writes a link and the controller that keeps checking it.

A link is a GitHub account's id beside the work addresses GitHub verified for it, which the directory knew when the person linked. It is the only way a GitHub account is matched to a person on an organisation that GitHub does not disclose members' addresses to — every organisation not on its Enterprise Cloud plan.

It carries the person's token pair, so the controller can ask GitHub again, every pass, whether those addresses are still verified on the account. That is why a link lives in a Secret. The pair can read one thing — the account's own addresses — because it is a token for the link App, which asks for nothing else.

Index

Constants

View Source
const AppKey = "_link.json"

AppKey is where the link App's record and credential are kept, beside the organisations' in the same two objects. A leading underscore is never an organisation's login, so no organisation's key can be it.

View Source
const Version = 1

Version is the document version this build writes and reads.

Variables

View Source
var ErrVersion = errors.New("link: unsupported document version")

ErrVersion is a document of a version this build does not read.

Functions

func Encode

func Encode(l Link) ([]byte, error)

Encode writes a link.

func EncodeApp

func EncodeApp(a App) (string, error)

EncodeApp writes the App's record.

func EncodeAppCredential

func EncodeAppCredential(c AppCredential) ([]byte, error)

EncodeAppCredential writes the App's credential.

func IDOfKey

func IDOfKey(key string) (int64, bool)

IDOfKey reads an account id back out of a key.

func Key

func Key(id int64) string

Key is where one account's link is kept.

func Normalise

func Normalise(emails []string) []string

Normalise is [normalise] for callers building a link.

func SecretName

func SecretName(release string) string

SecretName is the object holding every link.

Types

type App

type App struct {
	Version int    `json:"version"`
	Owner   string `json:"owner"`
	AppID   int64  `json:"app_id"`
	AppSlug string `json:"app_slug"`
	// Purpose is always `link`: EncodeApp sets it. A record written before it
	// existed has none and is read as the link App's all the same.
	Purpose appid.Purpose `json:"purpose,omitempty"`
	// Labels are the App's own, for a reader that selects Apps by them.
	Labels map[string]string `json:"labels,omitempty"`
	// ClientID is public: it is in every authorize URL.
	ClientID    string    `json:"client_id"`
	HTMLURL     string    `json:"html_url,omitempty"`
	ConnectedAt time.Time `json:"connected_at"`
	ConnectedBy string    `json:"connected_by"`
}

App is the connected link App, as the console shows it.

func DecodeApp

func DecodeApp(raw string) (App, error)

DecodeApp reads the App's record.

type AppCredential

type AppCredential struct {
	Version      int    `json:"version"`
	AppID        int64  `json:"app_id"`
	ClientID     string `json:"client_id"`
	ClientSecret string `json:"client_secret"`
	// Record is a copy of the App's record, so that the Secret alone
	// restores the link App whose ConfigMap entry is gone.
	Record *App `json:"record,omitempty"`
}

AppCredential is what a person's authorization is redeemed and refreshed with.

func DecodeAppCredential

func DecodeAppCredential(raw []byte) (AppCredential, error)

DecodeAppCredential reads the App's credential.

type AppStore added in v1.76.0

type AppStore interface {
	PutLinkApp(ctx context.Context, record App, credential AppCredential) error
	LinkApp(ctx context.Context) (App, bool, error)
	LinkAppCredential(ctx context.Context) (AppCredential, bool, error)
	DeleteLinkApp(ctx context.Context) error
}

AppStore is where the link App is kept: its record, which the console shows, and its credential, which redeems a person's authorization.

type Link struct {
	Version int    `json:"version"`
	ID      int64  `json:"id"`
	Login   string `json:"login"`
	// Source is how the link came to be; empty is a link written before
	// sources existed, which were all self-links.
	Source Source `json:"source,omitempty"`
	// Note says where a link that is not a self-link came from.
	Note string `json:"note,omitempty"`
	// AppID is the link App the tokens were issued to. A token is only
	// ever checked with that App's credentials: checked with another's,
	// GitHub would call a perfectly good token unknown.
	AppID int64 `json:"app_id"`
	// Emails are the addresses the link proves, lowercased and sorted.
	Emails []string `json:"emails"`
	State  State    `json:"state"`
	// Reason says why a link is lost or unverifiable.
	Reason    string    `json:"reason,omitempty"`
	LinkedAt  time.Time `json:"linked_at"`
	CheckedAt time.Time `json:"checked_at,omitzero"`
	ChangedAt time.Time `json:"changed_at,omitzero"`
	// Revision counts writes. A writer that read a link at one revision
	// replaces it only if it is still at that revision, so a person
	// linking again is never overwritten by a check that started before.
	Revision int64 `json:"revision"`

	AccessToken   string    `json:"access_token,omitempty"`
	AccessExpires time.Time `json:"access_expires,omitzero"`
	RefreshToken  string    `json:"refresh_token,omitempty"`
	// RefreshExpires is when the refresh token stops working unused.
	RefreshExpires time.Time `json:"refresh_expires,omitzero"`
	// RefreshingSince is written BEFORE the pair is refreshed and cleared
	// after the new pair is kept. GitHub kills the old pair the moment it
	// issues a new one, so a crash in between leaves a link whose tokens
	// are all dead — which, without this, would read exactly like a
	// revoked authorization and remove the person.
	RefreshingSince time.Time `json:"refreshing_since,omitzero"`
}

Link is one GitHub account's link.

func Adopt

func Adopt(existing, candidates []Link) (adopted []Link, skipped map[int64]string)

Adopt adds links that were not made by the person — matched from a profile, or imported — without ever displacing one that was. A candidate is skipped when its account already has a link that counts, and loses any address another account's link already proves. It returns the links to write and, by account id, why each skipped candidate was.

func Claim

func Claim(existing []Link, claimed Link, now time.Time) []Link

Claim is a person linking an account: the new link, and every other link that proved one of its addresses, narrowed to what it still proves.

An address proves one account. When a person links a second account with the same verified address, the address moves to the one they just linked: they are the only person who can have verified it on both, and the account they linked last is the one they mean. An account left with nothing to prove is lost, and leaves the organisation.

func Decode

func Decode(raw []byte) (Link, error)

Decode reads a link.

func Invalidate

func Invalidate(existing []Link, reason string, now time.Time) []Link

Invalidate makes every self-linked account unverifiable, forgetting its tokens: what disconnecting the link App does, because nothing can check those tokens any more.

func (Link) Active

func (l Link) Active() bool

Active reports whether a link counts: it proves its addresses right now.

func (Link) Checked

func (l Link) Checked() bool

Checked reports whether the controller re-checks the link on GitHub: a self-link, which holds the person's tokens. The other sources hold none.

func (*Link) Forget

func (l *Link) Forget()

Forget drops the tokens of a link that is no longer checked.

func (Link) Public

func (l Link) Public() Link

Public is the link without its tokens: what the console shows.

type Links interface {
	List(ctx context.Context) ([]Link, error)
	Claim(ctx context.Context, claimed Link, now time.Time) ([]Link, error)
	Adopt(ctx context.Context, candidates []Link) ([]Link, map[int64]string, error)
	Invalidate(ctx context.Context, reason string, now time.Time) (int, error)
}

Links is where people's links are kept.

type Source

type Source string

Source is how a link came to be.

const (
	// SourceSelf: the person authorized the link App. GitHub verified the
	// addresses, and they are checked again every pass.
	SourceSelf Source = "self"
	// SourceProfile: the account publishes the work address on its profile,
	// which GitHub allows only for a verified address. Proven when matched;
	// hiding the address later removes nobody.
	SourceProfile Source = "profile"
	// SourceImported: an approved pairing from the records github-roster
	// 0.x kept. Declared, not re-checked on GitHub.
	SourceImported Source = "imported"
)

The sources, strongest first.

type State

type State string

State is where a link stands.

const (
	// StateLinked: GitHub still verifies at least one of the addresses.
	StateLinked State = "linked"
	// StateLost: GitHub said, in so many words, that the proof is gone —
	// every linked address is gone or unverified, or the person revoked the
	// authorization. The account leaves the organisation.
	StateLost State = "lost"
	// StateUnverifiable: the link cannot be checked any more, and GitHub
	// did not say it is gone — the token pair was lost in a refresh, or the
	// link App it was made with was disconnected. Nothing is removed on
	// its account and nothing is added; the person links again.
	StateUnverifiable State = "unverifiable"
)

The states.

Jump to

Keyboard shortcuts

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