database

package
v0.0.0-...-66da7ee Latest Latest
Warning

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

Go to latest
Published: May 12, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package database provides the data access layer for ghp, abstracting over PostgreSQL (production) and SQLite (development) backends. It defines the Store interface for all database operations, the data models (User, GitHubToken, ProxyToken), and the migration system. Both drivers are pure Go (no CGO) — SQLite via modernc.org/sqlite and PostgreSQL via pgx.

Index

Constants

View Source
const (
	OAuthStateKindLogin  = "login"
	OAuthStateKindBroker = "broker"
)

OAuthStateKind identifies which OAuth flow an oauth_states row belongs to.

View Source
const (
	DeviceAuthStatusPending  = "pending"
	DeviceAuthStatusApproved = "approved"
	DeviceAuthStatusDenied   = "denied"
)

DeviceAuthStatusPending, DeviceAuthStatusApproved, DeviceAuthStatusDenied are the lifecycle states of a CLI device-authorization request. The DB CHECK constraint enforces these values; keep them in sync.

View Source
const (
	VaultAuthMethodAppRole    = "approle"
	VaultAuthMethodKubernetes = "kubernetes"

	// DefaultK8sAuthMount is the conventional auth mount path for the
	// Vault kubernetes auth backend.
	DefaultK8sAuthMount = "kubernetes"
	// DefaultK8sTokenPath is the standard path to the projected service
	// account token inside a pod.
	DefaultK8sTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token"
)

Supported Vault auth methods for the ghp database backend.

View Source
const DefaultTokenType = "proxy"

DefaultTokenType is the default ProxyToken.TokenType used when none is specified at creation time. This mirrors token.TokenTypeProxy but is defined here to avoid a circular import (token → database).

Variables

View Source
var ErrNotFound = errors.New("not found")

ErrNotFound is returned by DeleteApp, UpdateApp, UpdateProxyTokenAppID, and UpdateProxyTokenScopes when the target record does not exist. Other mutating operations (RevokeProxyToken) and all read operations (Get*, List*) do not wrap ErrNotFound — reads return (nil, nil) for missing records. Callers can distinguish "not found" from other errors using errors.Is(err, ErrNotFound).

Functions

This section is empty.

Types

type App

type App struct {
	ID           string    `json:"id"`
	Name         string    `json:"name"`
	AppID        int64     `json:"app_id"`
	ClientID     string    `json:"client_id"`
	ClientSecret string    `json:"client_secret"`
	PrivateKey   string    `json:"private_key"`
	BaseURL      string    `json:"base_url"`
	IsDefault    bool      `json:"is_default"`
	CreatedAt    time.Time `json:"created_at"`
	UpdatedAt    time.Time `json:"updated_at"`
}

App represents a GitHub App configured in the proxy.

type CachedRepository

type CachedRepository struct {
	ID             string    `json:"id"`
	Owner          string    `json:"owner"`
	Name           string    `json:"name"`
	Enabled        bool      `json:"enabled"`
	TimeoutSeconds *int      `json:"timeout_seconds,omitempty"`
	MaxCacheSizeMB *int      `json:"max_cache_size_mb,omitempty"`
	CreatedAt      time.Time `json:"created_at"`
	UpdatedAt      time.Time `json:"updated_at"`
}

CachedRepository represents a repository whose git clone/fetch operations are cached locally by the proxy.

type DeviceAuth

type DeviceAuth struct {
	DeviceCode   string     `json:"device_code"`
	UserCode     string     `json:"user_code"`
	Status       string     `json:"status"`
	SessionToken string     `json:"session_token,omitempty"`
	Username     string     `json:"username,omitempty"`
	LastPolledAt *time.Time `json:"last_polled_at,omitempty"`
	ExpiresAt    time.Time  `json:"expires_at"`
	CreatedAt    time.Time  `json:"created_at"`
}

DeviceAuth represents an in-flight CLI device-authorization request. It is persisted across the four round-trips that make up the flow (start, poll, browser verify, browser decision) so that the request survives load balancing in HA deployments — every ghp instance reads the same row.

Lifecycle:

  • created with Status=pending by POST /cli/auth/device
  • transitions to approved or denied by POST /cli/auth/decision
  • deleted by the polling endpoint once the CLI receives the token, or by the cleanup goroutine after ExpiresAt passes

type GitHubToken

type GitHubToken struct {
	ID                    string    `json:"id"`
	UserID                string    `json:"user_id"`
	AppID                 *string   `json:"app_id,omitempty"`
	AccessToken           string    `json:"access_token"`
	RefreshToken          string    `json:"refresh_token"`
	AccessTokenExpiresAt  time.Time `json:"access_token_expires_at"`
	RefreshTokenExpiresAt time.Time `json:"refresh_token_expires_at"`
	Scopes                string    `json:"scopes"`
	CreatedAt             time.Time `json:"created_at"`
	UpdatedAt             time.Time `json:"updated_at"`
}

GitHubToken stores an encrypted GitHub OAuth token pair.

type MigrationExecutor

type MigrationExecutor interface {
	EnsureMigrationsTable(ctx context.Context) error
	AppliedMigrations(ctx context.Context) ([]string, error)
	RunMigration(ctx context.Context, name, sql string) error
}

MigrationExecutor is implemented by stores that can run migrations.

type MigrationStatus

type MigrationStatus struct {
	Name    string
	Applied bool
}

MigrationStatus describes a migration's state.

type Migrator

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

Migrator runs database migrations.

func NewMigrator

func NewMigrator(db Store, driver string) *Migrator

NewMigrator creates a new Migrator.

func (*Migrator) Migrate

func (m *Migrator) Migrate(ctx context.Context) error

Migrate runs all pending up migrations.

func (*Migrator) PendingMigrations

func (m *Migrator) PendingMigrations(ctx context.Context) ([]string, error)

PendingMigrations returns the list of migrations not yet applied.

func (*Migrator) Status

func (m *Migrator) Status(ctx context.Context) ([]MigrationStatus, error)

Status returns the status of all known migrations.

type OAuthState

type OAuthState struct {
	State                 string    `json:"state"`
	Kind                  string    `json:"kind"`
	ReturnTo              string    `json:"return_to"`
	BrokerRedirectURI     string    `json:"broker_redirect_uri"`
	BrokerDownstreamState string    `json:"broker_downstream_state"`
	ExpiresAt             time.Time `json:"expires_at"`
	CreatedAt             time.Time `json:"created_at"`
}

OAuthState captures the per-row payload for an in-flight OAuth state. The Kind field selects which payload columns are meaningful: "login" uses ReturnTo; "broker" uses BrokerRedirectURI and BrokerDownstreamState.

type PostgresStore

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

PostgresStore implements Store using PostgreSQL.

func NewPostgresStore

func NewPostgresStore(dsn string) (*PostgresStore, error)

NewPostgresStore opens a PostgreSQL database with the given DSN.

func (*PostgresStore) AppliedMigrations

func (s *PostgresStore) AppliedMigrations(ctx context.Context) ([]string, error)

func (*PostgresStore) Close

func (s *PostgresStore) Close() error

func (*PostgresStore) ConsumeOAuthState

func (s *PostgresStore) ConsumeOAuthState(ctx context.Context, state, kind string) (*OAuthState, error)

func (*PostgresStore) CreateApp

func (s *PostgresStore) CreateApp(ctx context.Context, app *App) error

func (*PostgresStore) CreateCachedRepository

func (s *PostgresStore) CreateCachedRepository(ctx context.Context, repo *CachedRepository) error

func (*PostgresStore) CreateDeviceAuth

func (s *PostgresStore) CreateDeviceAuth(ctx context.Context, da *DeviceAuth) error

func (*PostgresStore) CreateOAuthState

func (s *PostgresStore) CreateOAuthState(ctx context.Context, st *OAuthState) error

func (*PostgresStore) CreateProxyToken

func (s *PostgresStore) CreateProxyToken(ctx context.Context, token *ProxyToken) error

func (*PostgresStore) CreateSession

func (s *PostgresStore) CreateSession(ctx context.Context, sess *Session) error

func (*PostgresStore) DeleteApp

func (s *PostgresStore) DeleteApp(ctx context.Context, id string) error

func (*PostgresStore) DeleteCachedRepository

func (s *PostgresStore) DeleteCachedRepository(ctx context.Context, id string) error

func (*PostgresStore) DeleteDeviceAuth

func (s *PostgresStore) DeleteDeviceAuth(ctx context.Context, deviceCode string) error

func (*PostgresStore) DeleteExpiredDeviceAuths

func (s *PostgresStore) DeleteExpiredDeviceAuths(ctx context.Context) (int64, error)

func (*PostgresStore) DeleteExpiredOAuthStates

func (s *PostgresStore) DeleteExpiredOAuthStates(ctx context.Context) (int64, error)

func (*PostgresStore) DeleteExpiredProxyTokens

func (s *PostgresStore) DeleteExpiredProxyTokens(ctx context.Context, olderThan time.Duration) (int64, error)

func (*PostgresStore) DeleteExpiredSessions

func (s *PostgresStore) DeleteExpiredSessions(ctx context.Context) (int64, error)

func (*PostgresStore) DeleteSession

func (s *PostgresStore) DeleteSession(ctx context.Context, tokenHash string) error

func (*PostgresStore) EnsureMigrationsTable

func (s *PostgresStore) EnsureMigrationsTable(ctx context.Context) error

func (*PostgresStore) GetAppByID

func (s *PostgresStore) GetAppByID(ctx context.Context, id string) (*App, error)

func (*PostgresStore) GetCachedRepositoryByID

func (s *PostgresStore) GetCachedRepositoryByID(ctx context.Context, id string) (*CachedRepository, error)

func (*PostgresStore) GetCachedRepositoryByOwnerName

func (s *PostgresStore) GetCachedRepositoryByOwnerName(ctx context.Context, owner, name string) (*CachedRepository, error)

func (*PostgresStore) GetDefaultApp

func (s *PostgresStore) GetDefaultApp(ctx context.Context) (*App, error)

func (*PostgresStore) GetDeviceAuthByDeviceCode

func (s *PostgresStore) GetDeviceAuthByDeviceCode(ctx context.Context, deviceCode string) (*DeviceAuth, error)

func (*PostgresStore) GetDeviceAuthByUserCode

func (s *PostgresStore) GetDeviceAuthByUserCode(ctx context.Context, userCode string) (*DeviceAuth, error)

func (*PostgresStore) GetGitHubToken

func (s *PostgresStore) GetGitHubToken(ctx context.Context, userID string) (*GitHubToken, error)

func (*PostgresStore) GetGitHubTokenByID

func (s *PostgresStore) GetGitHubTokenByID(ctx context.Context, id string) (*GitHubToken, error)

func (*PostgresStore) GetProxyTokenByHash

func (s *PostgresStore) GetProxyTokenByHash(ctx context.Context, hash string) (*ProxyToken, error)

func (*PostgresStore) GetProxyTokenByID

func (s *PostgresStore) GetProxyTokenByID(ctx context.Context, id string) (*ProxyToken, error)

func (*PostgresStore) GetSessionByTokenHash

func (s *PostgresStore) GetSessionByTokenHash(ctx context.Context, tokenHash string) (*Session, error)

func (*PostgresStore) GetUserByGitHubID

func (s *PostgresStore) GetUserByGitHubID(ctx context.Context, githubID int64) (*User, error)

func (*PostgresStore) GetUserByID

func (s *PostgresStore) GetUserByID(ctx context.Context, id string) (*User, error)

func (*PostgresStore) ListActiveProxyTokens

func (s *PostgresStore) ListActiveProxyTokens(ctx context.Context) ([]*ProxyToken, error)

func (*PostgresStore) ListAllProxyTokens

func (s *PostgresStore) ListAllProxyTokens(ctx context.Context) ([]*ProxyToken, error)

func (*PostgresStore) ListApps

func (s *PostgresStore) ListApps(ctx context.Context) ([]*App, error)

func (*PostgresStore) ListCachedRepositories

func (s *PostgresStore) ListCachedRepositories(ctx context.Context) ([]*CachedRepository, error)

func (*PostgresStore) ListProxyTokens

func (s *PostgresStore) ListProxyTokens(ctx context.Context, userID string) ([]*ProxyToken, error)

func (*PostgresStore) ListUsers

func (s *PostgresStore) ListUsers(ctx context.Context) ([]*User, error)

func (*PostgresStore) RevokeProxyToken

func (s *PostgresStore) RevokeProxyToken(ctx context.Context, id string) error

func (*PostgresStore) RunMigration

func (s *PostgresStore) RunMigration(ctx context.Context, name, sqlStr string) error

func (*PostgresStore) SetDefaultApp

func (s *PostgresStore) SetDefaultApp(ctx context.Context, appID string) error

func (*PostgresStore) SyncAdminRoles

func (s *PostgresStore) SyncAdminRoles(ctx context.Context, adminUsernames []string) error

func (*PostgresStore) UpdateApp

func (s *PostgresStore) UpdateApp(ctx context.Context, app *App) error

func (*PostgresStore) UpdateCachedRepository

func (s *PostgresStore) UpdateCachedRepository(ctx context.Context, repo *CachedRepository) error

func (*PostgresStore) UpdateDeviceAuth

func (s *PostgresStore) UpdateDeviceAuth(ctx context.Context, da *DeviceAuth) error

func (*PostgresStore) UpdateProxyTokenAppID

func (s *PostgresStore) UpdateProxyTokenAppID(ctx context.Context, id string, appID string) error

func (*PostgresStore) UpdateProxyTokenScopes

func (s *PostgresStore) UpdateProxyTokenScopes(ctx context.Context, id string, repositories json.RawMessage, scopes json.RawMessage) error

func (*PostgresStore) UpsertGitHubToken

func (s *PostgresStore) UpsertGitHubToken(ctx context.Context, token *GitHubToken) error

func (*PostgresStore) UpsertUser

func (s *PostgresStore) UpsertUser(ctx context.Context, user *User) error

type ProxyToken

type ProxyToken struct {
	ID             string          `json:"id"`
	TokenHash      string          `json:"-"`
	TokenPrefix    string          `json:"token_prefix"`
	TokenType      string          `json:"token_type"`
	AppID          *string         `json:"app_id,omitempty"`
	UserID         *string         `json:"user_id,omitempty"`
	GitHubTokenID  *string         `json:"github_token_id,omitempty"`
	InstallationID *int64          `json:"installation_id,omitempty"`
	Repositories   json.RawMessage `json:"repositories"`
	Scopes         json.RawMessage `json:"scopes"`
	SessionID      string          `json:"session_id"`
	ExpiresAt      *time.Time      `json:"expires_at"` // nil means no expiry.
	RevokedAt      *time.Time      `json:"revoked_at,omitempty"`
	CreatedAt      time.Time       `json:"created_at"`
}

ProxyToken represents a ghx_ or gha_ token issued to agents.

type SQLiteStore

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

SQLiteStore implements Store using SQLite.

func NewSQLiteStore

func NewSQLiteStore(dsn string) (*SQLiteStore, error)

NewSQLiteStore opens a SQLite database at the given path.

func (*SQLiteStore) AppliedMigrations

func (s *SQLiteStore) AppliedMigrations(ctx context.Context) ([]string, error)

func (*SQLiteStore) Close

func (s *SQLiteStore) Close() error

func (*SQLiteStore) ConsumeOAuthState

func (s *SQLiteStore) ConsumeOAuthState(ctx context.Context, state, kind string) (*OAuthState, error)

func (*SQLiteStore) CreateApp

func (s *SQLiteStore) CreateApp(ctx context.Context, app *App) error

func (*SQLiteStore) CreateCachedRepository

func (s *SQLiteStore) CreateCachedRepository(ctx context.Context, repo *CachedRepository) error

func (*SQLiteStore) CreateDeviceAuth

func (s *SQLiteStore) CreateDeviceAuth(ctx context.Context, da *DeviceAuth) error

func (*SQLiteStore) CreateOAuthState

func (s *SQLiteStore) CreateOAuthState(ctx context.Context, st *OAuthState) error

func (*SQLiteStore) CreateProxyToken

func (s *SQLiteStore) CreateProxyToken(ctx context.Context, token *ProxyToken) error

func (*SQLiteStore) CreateSession

func (s *SQLiteStore) CreateSession(ctx context.Context, sess *Session) error

func (*SQLiteStore) DeleteApp

func (s *SQLiteStore) DeleteApp(ctx context.Context, id string) error

func (*SQLiteStore) DeleteCachedRepository

func (s *SQLiteStore) DeleteCachedRepository(ctx context.Context, id string) error

func (*SQLiteStore) DeleteDeviceAuth

func (s *SQLiteStore) DeleteDeviceAuth(ctx context.Context, deviceCode string) error

func (*SQLiteStore) DeleteExpiredDeviceAuths

func (s *SQLiteStore) DeleteExpiredDeviceAuths(ctx context.Context) (int64, error)

func (*SQLiteStore) DeleteExpiredOAuthStates

func (s *SQLiteStore) DeleteExpiredOAuthStates(ctx context.Context) (int64, error)

func (*SQLiteStore) DeleteExpiredProxyTokens

func (s *SQLiteStore) DeleteExpiredProxyTokens(ctx context.Context, olderThan time.Duration) (int64, error)

func (*SQLiteStore) DeleteExpiredSessions

func (s *SQLiteStore) DeleteExpiredSessions(ctx context.Context) (int64, error)

func (*SQLiteStore) DeleteSession

func (s *SQLiteStore) DeleteSession(ctx context.Context, tokenHash string) error

func (*SQLiteStore) EnsureMigrationsTable

func (s *SQLiteStore) EnsureMigrationsTable(ctx context.Context) error

func (*SQLiteStore) GetAppByID

func (s *SQLiteStore) GetAppByID(ctx context.Context, id string) (*App, error)

func (*SQLiteStore) GetCachedRepositoryByID

func (s *SQLiteStore) GetCachedRepositoryByID(ctx context.Context, id string) (*CachedRepository, error)

func (*SQLiteStore) GetCachedRepositoryByOwnerName

func (s *SQLiteStore) GetCachedRepositoryByOwnerName(ctx context.Context, owner, name string) (*CachedRepository, error)

func (*SQLiteStore) GetDefaultApp

func (s *SQLiteStore) GetDefaultApp(ctx context.Context) (*App, error)

func (*SQLiteStore) GetDeviceAuthByDeviceCode

func (s *SQLiteStore) GetDeviceAuthByDeviceCode(ctx context.Context, deviceCode string) (*DeviceAuth, error)

func (*SQLiteStore) GetDeviceAuthByUserCode

func (s *SQLiteStore) GetDeviceAuthByUserCode(ctx context.Context, userCode string) (*DeviceAuth, error)

func (*SQLiteStore) GetGitHubToken

func (s *SQLiteStore) GetGitHubToken(ctx context.Context, userID string) (*GitHubToken, error)

func (*SQLiteStore) GetGitHubTokenByID

func (s *SQLiteStore) GetGitHubTokenByID(ctx context.Context, id string) (*GitHubToken, error)

func (*SQLiteStore) GetProxyTokenByHash

func (s *SQLiteStore) GetProxyTokenByHash(ctx context.Context, hash string) (*ProxyToken, error)

func (*SQLiteStore) GetProxyTokenByID

func (s *SQLiteStore) GetProxyTokenByID(ctx context.Context, id string) (*ProxyToken, error)

func (*SQLiteStore) GetSessionByTokenHash

func (s *SQLiteStore) GetSessionByTokenHash(ctx context.Context, tokenHash string) (*Session, error)

func (*SQLiteStore) GetUserByGitHubID

func (s *SQLiteStore) GetUserByGitHubID(ctx context.Context, githubID int64) (*User, error)

func (*SQLiteStore) GetUserByID

func (s *SQLiteStore) GetUserByID(ctx context.Context, id string) (*User, error)

func (*SQLiteStore) ListActiveProxyTokens

func (s *SQLiteStore) ListActiveProxyTokens(ctx context.Context) ([]*ProxyToken, error)

func (*SQLiteStore) ListAllProxyTokens

func (s *SQLiteStore) ListAllProxyTokens(ctx context.Context) ([]*ProxyToken, error)

func (*SQLiteStore) ListApps

func (s *SQLiteStore) ListApps(ctx context.Context) ([]*App, error)

func (*SQLiteStore) ListCachedRepositories

func (s *SQLiteStore) ListCachedRepositories(ctx context.Context) ([]*CachedRepository, error)

func (*SQLiteStore) ListProxyTokens

func (s *SQLiteStore) ListProxyTokens(ctx context.Context, userID string) ([]*ProxyToken, error)

func (*SQLiteStore) ListUsers

func (s *SQLiteStore) ListUsers(ctx context.Context) ([]*User, error)

func (*SQLiteStore) RevokeProxyToken

func (s *SQLiteStore) RevokeProxyToken(ctx context.Context, id string) error

func (*SQLiteStore) RunMigration

func (s *SQLiteStore) RunMigration(ctx context.Context, name, sqlStr string) error

func (*SQLiteStore) SetDefaultApp

func (s *SQLiteStore) SetDefaultApp(ctx context.Context, appID string) error

func (*SQLiteStore) SyncAdminRoles

func (s *SQLiteStore) SyncAdminRoles(ctx context.Context, adminUsernames []string) error

func (*SQLiteStore) UpdateApp

func (s *SQLiteStore) UpdateApp(ctx context.Context, app *App) error

func (*SQLiteStore) UpdateCachedRepository

func (s *SQLiteStore) UpdateCachedRepository(ctx context.Context, repo *CachedRepository) error

func (*SQLiteStore) UpdateDeviceAuth

func (s *SQLiteStore) UpdateDeviceAuth(ctx context.Context, da *DeviceAuth) error

func (*SQLiteStore) UpdateProxyTokenAppID

func (s *SQLiteStore) UpdateProxyTokenAppID(ctx context.Context, id string, appID string) error

func (*SQLiteStore) UpdateProxyTokenScopes

func (s *SQLiteStore) UpdateProxyTokenScopes(ctx context.Context, id string, repositories json.RawMessage, scopes json.RawMessage) error

func (*SQLiteStore) UpsertGitHubToken

func (s *SQLiteStore) UpsertGitHubToken(ctx context.Context, token *GitHubToken) error

func (*SQLiteStore) UpsertUser

func (s *SQLiteStore) UpsertUser(ctx context.Context, user *User) error

type Scopes

type Scopes map[string]string

Scopes represents a map of permission to access level.

func ParseScopes

func ParseScopes(data json.RawMessage) (Scopes, error)

ParseScopes parses a JSON-encoded scopes value.

func (Scopes) HasPermission

func (s Scopes) HasPermission(permission, level string) bool

HasPermission checks if the scopes include the given permission at the required level. A "write" scope also grants "read" access.

type Session

type Session struct {
	TokenHash string    `json:"-"`
	UserID    string    `json:"user_id"`
	Username  string    `json:"username"`
	Role      string    `json:"role"`
	ExpiresAt time.Time `json:"expires_at"`
	CreatedAt time.Time `json:"created_at"`
}

Session represents a persisted browser/CLI session. The bearer token presented in the Authorization header (or the ghp_session cookie) is hashed with SHA-256 before being looked up here, so the database never holds raw bearer tokens.

type Store

type Store interface {
	// Apps
	CreateApp(ctx context.Context, app *App) error
	GetAppByID(ctx context.Context, id string) (*App, error)
	GetDefaultApp(ctx context.Context) (*App, error)
	ListApps(ctx context.Context) ([]*App, error)
	UpdateApp(ctx context.Context, app *App) error
	DeleteApp(ctx context.Context, id string) error
	// SetDefaultApp atomically marks appID as the default and clears the
	// default flag on all other apps. For SQL backends this runs inside a
	// transaction; for Vault the new default is set before clearing the old
	// one so that a partial failure never leaves the system with no default.
	// Returns ErrNotFound if appID does not exist.
	SetDefaultApp(ctx context.Context, appID string) error

	// Users
	UpsertUser(ctx context.Context, user *User) error
	GetUserByGitHubID(ctx context.Context, githubID int64) (*User, error)
	GetUserByID(ctx context.Context, id string) (*User, error)
	ListUsers(ctx context.Context) ([]*User, error)
	SyncAdminRoles(ctx context.Context, adminUsernames []string) error

	// GitHub tokens
	UpsertGitHubToken(ctx context.Context, token *GitHubToken) error
	GetGitHubToken(ctx context.Context, userID string) (*GitHubToken, error)
	GetGitHubTokenByID(ctx context.Context, id string) (*GitHubToken, error)

	// Proxy tokens
	CreateProxyToken(ctx context.Context, token *ProxyToken) error
	GetProxyTokenByHash(ctx context.Context, hash string) (*ProxyToken, error)
	GetProxyTokenByID(ctx context.Context, id string) (*ProxyToken, error)
	ListProxyTokens(ctx context.Context, userID string) ([]*ProxyToken, error)
	ListAllProxyTokens(ctx context.Context) ([]*ProxyToken, error)
	ListActiveProxyTokens(ctx context.Context) ([]*ProxyToken, error)
	RevokeProxyToken(ctx context.Context, id string) error
	UpdateProxyTokenAppID(ctx context.Context, id string, appID string) error
	// UpdateProxyTokenScopes updates the repositories and scopes of an existing
	// proxy token. Returns ErrNotFound if the token does not exist.
	UpdateProxyTokenScopes(ctx context.Context, id string, repositories json.RawMessage, scopes json.RawMessage) error
	// DeleteExpiredProxyTokens hard-deletes proxy tokens that have been
	// expired or revoked for longer than olderThan.  It returns the number
	// of rows deleted.  Tokens with no expiry (expires_at IS NULL) are only
	// deleted when they have been revoked.  olderThan must be > 0; a zero
	// or negative value is rejected to prevent accidentally deleting active
	// tokens with a future cutoff.
	DeleteExpiredProxyTokens(ctx context.Context, olderThan time.Duration) (int64, error)

	// Cached repositories
	CreateCachedRepository(ctx context.Context, repo *CachedRepository) error
	GetCachedRepositoryByID(ctx context.Context, id string) (*CachedRepository, error)
	GetCachedRepositoryByOwnerName(ctx context.Context, owner, name string) (*CachedRepository, error)
	ListCachedRepositories(ctx context.Context) ([]*CachedRepository, error)
	UpdateCachedRepository(ctx context.Context, repo *CachedRepository) error
	DeleteCachedRepository(ctx context.Context, id string) error

	// Sessions (browser cookie / CLI bearer).
	//
	// CreateSession persists a new session keyed by the SHA-256 hash of the
	// raw bearer token. Callers retain the raw token and present it on
	// subsequent requests; the DB never holds the unhashed value.
	CreateSession(ctx context.Context, s *Session) error
	// GetSessionByTokenHash returns the session for the given hash, or
	// wrapped ErrNotFound if the hash is unknown or its ExpiresAt has
	// passed.
	GetSessionByTokenHash(ctx context.Context, tokenHash string) (*Session, error)
	// DeleteSession removes the session for the given token hash. No-op
	// (no error) if the row does not exist.
	DeleteSession(ctx context.Context, tokenHash string) error
	// DeleteExpiredSessions removes sessions past their ExpiresAt. Returns
	// the number of rows deleted.
	DeleteExpiredSessions(ctx context.Context) (int64, error)

	// In-flight OAuth state tokens (one-time use, short-lived).
	//
	// CreateOAuthState persists a new state row.
	CreateOAuthState(ctx context.Context, s *OAuthState) error
	// ConsumeOAuthState atomically reads and deletes the state row matching
	// state and kind. Returns wrapped ErrNotFound if the state is unknown,
	// expired, or of a different kind. The atomic consume prevents a state
	// from being replayed if the same callback fires twice.
	ConsumeOAuthState(ctx context.Context, state, kind string) (*OAuthState, error)
	// DeleteExpiredOAuthStates removes oauth_states rows past their
	// ExpiresAt. Returns the number of rows deleted.
	DeleteExpiredOAuthStates(ctx context.Context) (int64, error)

	// CLI device-authorization requests.
	//
	// CreateDeviceAuth inserts a new pending request. It must reject duplicate
	// device_code or user_code values at the database level.
	CreateDeviceAuth(ctx context.Context, da *DeviceAuth) error
	// GetDeviceAuthByDeviceCode looks up a request by the long polling
	// secret. Returns wrapped ErrNotFound if absent. Implementations also
	// return ErrNotFound when the row exists but is past its ExpiresAt — the
	// poll endpoint and cleanup goroutine should treat those identically.
	GetDeviceAuthByDeviceCode(ctx context.Context, deviceCode string) (*DeviceAuth, error)
	// GetDeviceAuthByUserCode is the verification page's lookup. Same
	// not-found / expiry semantics as GetDeviceAuthByDeviceCode.
	GetDeviceAuthByUserCode(ctx context.Context, userCode string) (*DeviceAuth, error)
	// UpdateDeviceAuth persists the in-memory mutation back to storage.
	// Returns ErrNotFound if the device_code does not exist.
	UpdateDeviceAuth(ctx context.Context, da *DeviceAuth) error
	// DeleteDeviceAuth removes a row by device_code. It is a no-op (no error)
	// if the row does not exist; the polling endpoint deletes after handing
	// the token to the CLI and races with the cleanup goroutine.
	DeleteDeviceAuth(ctx context.Context, deviceCode string) error
	// DeleteExpiredDeviceAuths removes all rows whose ExpiresAt is in the
	// past. Returns the number of rows deleted.
	DeleteExpiredDeviceAuths(ctx context.Context) (int64, error)

	// Lifecycle
	Close() error
}

Store defines the database operations for ghp.

func Open

func Open(driver, dsn string) (Store, error)

Open creates a Store for the given driver and DSN. For SQL backends, the DSN is the connection string. For Vault, use OpenVault instead.

func OpenVault

func OpenVault(ctx context.Context, cfg VaultConfig) (Store, error)

OpenVault creates a VaultStore with the given configuration.

type User

type User struct {
	ID             string    `json:"id"`
	GitHubID       int64     `json:"github_id"`
	GitHubUsername string    `json:"github_username"`
	GitHubEmail    string    `json:"github_email"`
	Role           string    `json:"role"`
	CreatedAt      time.Time `json:"created_at"`
	UpdatedAt      time.Time `json:"updated_at"`
}

User represents a ghp user authenticated via GitHub OAuth.

type VaultConfig

type VaultConfig struct {
	Addr      string // Vault server address
	MountPath string // KV v2 mount path (default: "secret")
	BasePath  string // key prefix (default: "ghp")

	// AuthMethod selects how ghp authenticates to Vault. Valid values:
	// "approle" (default) and "kubernetes". An empty string is treated as
	// "approle" for backwards compatibility.
	AuthMethod string

	// AppRole credentials. Required when AuthMethod is "approle".
	RoleID   string
	SecretID string

	// Kubernetes auth fields. Required when AuthMethod is "kubernetes".
	// K8sRole is the Vault role to authenticate as.
	// K8sMount is the auth mount path (default: "kubernetes").
	// K8sTokenPath is the path to the projected service account JWT
	// (default: /var/run/secrets/kubernetes.io/serviceaccount/token).
	K8sRole      string
	K8sMount     string
	K8sTokenPath string
}

VaultConfig holds configuration for Vault connectivity.

type VaultStore

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

VaultStore implements Store using HashiCorp Vault KV v2 secrets engine.

func NewVaultStore

func NewVaultStore(ctx context.Context, cfg VaultConfig) (*VaultStore, error)

NewVaultStore creates a VaultStore authenticated to Vault using the configured auth method (AppRole by default, or kubernetes auth when requested).

func NewVaultStoreFromClient

func NewVaultStoreFromClient(client *vault.Client, mountPath, basePath string) *VaultStore

NewVaultStoreFromClient creates a VaultStore from an existing authenticated client. Primarily used for testing.

func (*VaultStore) Close

func (s *VaultStore) Close() error

func (*VaultStore) ConsumeOAuthState

func (s *VaultStore) ConsumeOAuthState(ctx context.Context, state, kind string) (*OAuthState, error)

ConsumeOAuthState reads then deletes. Vault KV does not support atomic read-and-delete, so a duplicate concurrent callback could in principle receive the same state twice. The window is bounded by the time between the kvRead and kvDelete here (typically single-digit ms), and the state payload itself is non-secret — at worst a duplicate browser session would race to consume the same row. Documented in CLAUDE.md as the Vault read-modify-write trade-off.

func (*VaultStore) CreateApp

func (s *VaultStore) CreateApp(ctx context.Context, app *App) error

func (*VaultStore) CreateCachedRepository

func (s *VaultStore) CreateCachedRepository(ctx context.Context, repo *CachedRepository) error

func (*VaultStore) CreateDeviceAuth

func (s *VaultStore) CreateDeviceAuth(ctx context.Context, da *DeviceAuth) error

func (*VaultStore) CreateOAuthState

func (s *VaultStore) CreateOAuthState(ctx context.Context, st *OAuthState) error

func (*VaultStore) CreateProxyToken

func (s *VaultStore) CreateProxyToken(ctx context.Context, token *ProxyToken) error

func (*VaultStore) CreateSession

func (s *VaultStore) CreateSession(ctx context.Context, sess *Session) error

func (*VaultStore) DeleteApp

func (s *VaultStore) DeleteApp(ctx context.Context, id string) error

func (*VaultStore) DeleteCachedRepository

func (s *VaultStore) DeleteCachedRepository(ctx context.Context, id string) error

func (*VaultStore) DeleteDeviceAuth

func (s *VaultStore) DeleteDeviceAuth(ctx context.Context, deviceCode string) error

func (*VaultStore) DeleteExpiredDeviceAuths

func (s *VaultStore) DeleteExpiredDeviceAuths(ctx context.Context) (int64, error)

DeleteExpiredDeviceAuths iterates device-auth/ and deletes expired records along with their user_code indexes. by-user-code/ entries that point at missing primary records are also pruned.

func (*VaultStore) DeleteExpiredOAuthStates

func (s *VaultStore) DeleteExpiredOAuthStates(ctx context.Context) (int64, error)

func (*VaultStore) DeleteExpiredProxyTokens

func (s *VaultStore) DeleteExpiredProxyTokens(ctx context.Context, olderThan time.Duration) (int64, error)

func (*VaultStore) DeleteExpiredSessions

func (s *VaultStore) DeleteExpiredSessions(ctx context.Context) (int64, error)

DeleteExpiredSessions iterates the sessions/ prefix and deletes anything past its expiry. Vault has no list-with-filter, so this fans out one Read per row. Acceptable because cleanup runs infrequently — the auth handler invokes it on a fixed cadence (see authStateCleanupInterval in the auth package, currently 5 minutes).

func (*VaultStore) DeleteSession

func (s *VaultStore) DeleteSession(ctx context.Context, tokenHash string) error

func (*VaultStore) GetAppByID

func (s *VaultStore) GetAppByID(ctx context.Context, id string) (*App, error)

func (*VaultStore) GetCachedRepositoryByID

func (s *VaultStore) GetCachedRepositoryByID(ctx context.Context, id string) (*CachedRepository, error)

func (*VaultStore) GetCachedRepositoryByOwnerName

func (s *VaultStore) GetCachedRepositoryByOwnerName(ctx context.Context, owner, name string) (*CachedRepository, error)

func (*VaultStore) GetDefaultApp

func (s *VaultStore) GetDefaultApp(ctx context.Context) (*App, error)

func (*VaultStore) GetDeviceAuthByDeviceCode

func (s *VaultStore) GetDeviceAuthByDeviceCode(ctx context.Context, deviceCode string) (*DeviceAuth, error)

func (*VaultStore) GetDeviceAuthByUserCode

func (s *VaultStore) GetDeviceAuthByUserCode(ctx context.Context, userCode string) (*DeviceAuth, error)

func (*VaultStore) GetGitHubToken

func (s *VaultStore) GetGitHubToken(ctx context.Context, userID string) (*GitHubToken, error)

func (*VaultStore) GetGitHubTokenByID

func (s *VaultStore) GetGitHubTokenByID(ctx context.Context, id string) (*GitHubToken, error)

func (*VaultStore) GetProxyTokenByHash

func (s *VaultStore) GetProxyTokenByHash(ctx context.Context, hash string) (*ProxyToken, error)

func (*VaultStore) GetProxyTokenByID

func (s *VaultStore) GetProxyTokenByID(ctx context.Context, id string) (*ProxyToken, error)

func (*VaultStore) GetSessionByTokenHash

func (s *VaultStore) GetSessionByTokenHash(ctx context.Context, tokenHash string) (*Session, error)

func (*VaultStore) GetUserByGitHubID

func (s *VaultStore) GetUserByGitHubID(ctx context.Context, githubID int64) (*User, error)

func (*VaultStore) GetUserByID

func (s *VaultStore) GetUserByID(ctx context.Context, id string) (*User, error)

func (*VaultStore) ListActiveProxyTokens

func (s *VaultStore) ListActiveProxyTokens(ctx context.Context) ([]*ProxyToken, error)

func (*VaultStore) ListAllProxyTokens

func (s *VaultStore) ListAllProxyTokens(ctx context.Context) ([]*ProxyToken, error)

func (*VaultStore) ListApps

func (s *VaultStore) ListApps(ctx context.Context) ([]*App, error)

func (*VaultStore) ListCachedRepositories

func (s *VaultStore) ListCachedRepositories(ctx context.Context) ([]*CachedRepository, error)

func (*VaultStore) ListProxyTokens

func (s *VaultStore) ListProxyTokens(ctx context.Context, userID string) ([]*ProxyToken, error)

func (*VaultStore) ListUsers

func (s *VaultStore) ListUsers(ctx context.Context) ([]*User, error)

func (*VaultStore) RevokeProxyToken

func (s *VaultStore) RevokeProxyToken(ctx context.Context, id string) error

func (*VaultStore) SetDefaultApp

func (s *VaultStore) SetDefaultApp(ctx context.Context, appID string) error

func (*VaultStore) SyncAdminRoles

func (s *VaultStore) SyncAdminRoles(ctx context.Context, adminUsernames []string) error

func (*VaultStore) UpdateApp

func (s *VaultStore) UpdateApp(ctx context.Context, app *App) error

func (*VaultStore) UpdateCachedRepository

func (s *VaultStore) UpdateCachedRepository(ctx context.Context, repo *CachedRepository) error

func (*VaultStore) UpdateDeviceAuth

func (s *VaultStore) UpdateDeviceAuth(ctx context.Context, da *DeviceAuth) error

func (*VaultStore) UpdateProxyTokenAppID

func (s *VaultStore) UpdateProxyTokenAppID(ctx context.Context, id string, appID string) error

func (*VaultStore) UpdateProxyTokenScopes

func (s *VaultStore) UpdateProxyTokenScopes(ctx context.Context, id string, repositories json.RawMessage, scopes json.RawMessage) error

func (*VaultStore) UpsertGitHubToken

func (s *VaultStore) UpsertGitHubToken(ctx context.Context, token *GitHubToken) error

func (*VaultStore) UpsertUser

func (s *VaultStore) UpsertUser(ctx context.Context, user *User) error

Jump to

Keyboard shortcuts

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