oauth

package
v0.17.0 Latest Latest
Warning

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

Go to latest
Published: Sep 7, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Overview

Package oauth is keryx's provider-neutral OAuth *capture* layer for the CLI: it builds nothing protocol-specific itself, but given an already-built authorize URL and a resolved redirect URI it runs the local callback server (+ stdin paste fallback) and returns the authorization code. The protocol (token exchange/refresh) is the caller's job — standard providers use golang.org/x/oauth2, bespoke ones (Instagram's long-lived upgrade) keep their own exchange. This is the shared seam behind `keryx auth <platform>`.

Two redirect styles are supported, picked by the caller via the resolver:

  • https self-signed loopback (Meta rejects http://localhost) — FreeHTTPSLoopback
  • plain-http loopback (RFC 8252 providers like Google) — FreeHTTPLoopback

The callback server binds all interfaces so the browser reaches it via localhost, an SSH tunnel, or the host's LAN IP; paste is always available as a fallback (first to arrive wins).

Index

Constants

View Source
const (
	// DefaultTimeout bounds how long Capture waits for the code.
	DefaultTimeout = 5 * time.Minute
)

Variables

This section is empty.

Functions

func FreeHTTPLoopback

func FreeHTTPLoopback(ctx context.Context) (string, error)

FreeHTTPLoopback picks a plain-http loopback redirect on a free ephemeral port for RFC 8252 providers that accept any loopback port (e.g. Google). Returns `http://127.0.0.1:<port>/`.

func FreeHTTPLoopbackPath

func FreeHTTPLoopbackPath(ctx context.Context, path string) (string, error)

FreeHTTPLoopbackPath is FreeHTTPLoopback with an explicit callback path — some providers register (and exact-match) a specific path, e.g. TikTok Desktop's `/callback/`. The path is normalised to a leading slash.

func FreeHTTPSLoopback

func FreeHTTPSLoopback(ctx context.Context, flag, saved string, base, count int) (string, error)

FreeHTTPSLoopback picks an https loopback redirect for providers that exact- match a pre-registered set of URIs (e.g. Meta, which rejects http://localhost): the explicit flag wins; else the saved redirect if its port is free; else the first free port in [base, base+count). Every candidate must be registered with the provider. Returns the chosen `https://localhost:<port>/`.

func SanitizeCode

func SanitizeCode(s string) string

SanitizeCode accepts a bare code, a "code=…" fragment, or the whole redirected URL, and returns just the code (some providers append "#_").

Types

type Capturer

type Capturer struct {
	// Platform is the display label used in the printed prompt and the branded
	// callback page (e.g. "Instagram", "YouTube").
	Platform string
	// Redirect is the resolved redirect URI the authorize URL was built with; its
	// scheme decides TLS, its port decides the bind. Use a FreeHTTP(S)Loopback
	// resolver to pick one.
	Redirect string
	// Open opens a URL in a browser; a nil or erroring Open just means the user
	// follows the printed URL manually (the headless path).
	Open func(ctx context.Context, rawURL string) error
	// Timeout overrides DefaultTimeout when non-zero.
	Timeout time.Duration
	// CertSource provisions a browser-trusted loopback certificate for an https
	// redirect — the same seam the studio uses (spec 0027 R-TLS-2). A trusted
	// local-CA leaf removes the "untrusted certificate" warning once the root is
	// installed; a nil source (or one that errors) falls back to a self-signed cert.
	CertSource CertProvider
	// contains filtered or unexported fields
}

Capturer captures an OAuth authorization code for one platform via a local callback server and/or a code pasted on stdin.

func (Capturer) Capture

func (c Capturer) Capture(ctx context.Context, authURL string, out io.Writer, in io.Reader) (string, error)

Capture prints authURL (and opens a browser if it can), then returns the authorization code from whichever arrives first: the loopback callback (same machine / tunnel / LAN) or a code pasted on stdin. The result is sanitised.

type CertProvider added in v0.9.0

type CertProvider interface {
	ServerCert(ctx context.Context, host string, ips []net.IP) (*tls.Certificate, error)
}

CertProvider yields a loopback server certificate for the https callback. It is satisfied structurally by the studio's cert source, so the same trusted-CA source serves both surfaces (spec 0027 R-TLS-2).

type GitLabWriteBack

type GitLabWriteBack struct {
	BaseURL string       // GitLab API v4 base, e.g. https://gitlab.com/api/v4
	Project string       // numeric id or path (phpboyscout/blog) — URL-encoded per request
	Token   string       // api-scoped access token
	HTTP    *http.Client // nil → a default client with a sane timeout
}

GitLabWriteBack persists a rotated/refreshed secret as a GitLab CI/CD variable via the API, so the next scheduled pipeline reads the fresh value. It solves the "an ephemeral CI job can't write back to the variables it read" wrinkle (spec 0010 §5): the scheduled `auth refresh` job updates the same masked, protected variables the publishers read (TIKTOK_REFRESH_TOKEN, …), keyed by the token's Store.EnvVar.

The variable must already exist (created masked + protected in the project's CI/CD settings) — the backend updates its value and never creates a new, unmasked secret. It authenticates with an api-scoped access token; a project or group access token, since CI_JOB_TOKEN cannot write CI variables.

func (GitLabWriteBack) Save

func (g GitLabWriteBack) Save(ctx context.Context, store Store, value string) error

Save updates the CI/CD variable named by the Store's EnvVar with value.

type LocalWriteBack

type LocalWriteBack struct {
	Store *config.Store
}

LocalWriteBack persists via the OS keychain, else the config file — the CLI default (the same path Store.Save already takes).

func (LocalWriteBack) Save

func (l LocalWriteBack) Save(ctx context.Context, store Store, value string) error

Save writes the secret to the keychain/config destination the Store describes.

type Store

type Store struct {
	EnvVar          string // checked first; empty disables the env lookup
	KeychainService string
	KeychainAccount string
	ConfigKey       string // viper key, used when no keychain is available
}

Store persists and reads one OAuth secret (access or refresh token) across the precedence keryx uses: env var → OS keychain → config file. The env var is the CI/manual override; the keychain is the desktop secret service; the config file is the headless/CI fallback (plaintext — keep it out of version control).

func (Store) Resolve

func (s Store) Resolve(ctx context.Context, readers ...config.Reader) string

Resolve returns the secret from the first source that has it (env → keychain → each reader in order), or "" if none do.

Readers are tried in the order given, which is how the accounts-file migration stays invisible: callers pass the accounts file first and the general config second, so a token still living in an older file keeps working while every new write goes to the accounts file (spec 0042 §3.4). A credential migrates itself the next time it rotates.

func (Store) Save

func (s Store) Save(ctx context.Context, store *config.Store, secret string) (string, error)

Save persists the secret where it can be read back: the OS keychain when a desktop secret service is available, else the config file (via the Store's structure-preserving Apply). Returns a human label for where it landed.

type WriteBack

type WriteBack interface {
	Save(ctx context.Context, store Store, value string) error
}

WriteBack persists a refreshed/rotated secret to its destination. The Store describes *where* (keychain account, config key, env / CI-variable name); the WriteBack chooses the *destination strategy* — local keychain/config for the CLI, the GitLab CI-variable API for the scheduled job, an external secret manager, etc. It is a config-selected, additive backend (spec 0010 §5), the same pluggable pattern as keryx's generation providers.

Jump to

Keyboard shortcuts

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