Documentation
¶
Index ¶
- Constants
- Variables
- func NewHandler(options HandlerOptions) http.Handler
- func NewInstallationStores(backend githubapp.DocumentStore) (InstallationStateStore, InstallationBindingStore, ...)
- func NewRepositoryEventStore(backend githubapp.DocumentStore) (RepositoryEventStore, RepositoryEventStatusStore)
- type AppInstallationVerifier
- type EnqueueResult
- type EnrolledIdentity
- type EnrolledMachine
- type GitHubAppInstallationVerifier
- type GitHubIdentityVerifier
- type GitHubOAuthVerifier
- type HandlerOptions
- type IdentityInstallationBinding
- type InstallationBindingStore
- type InstallationConnectRequest
- type InstallationConnectResponse
- type InstallationConnectionService
- func (service InstallationConnectionService) AuthorizeOpener(ctx context.Context, viewer Viewer, ...) error
- func (service InstallationConnectionService) Begin(ctx context.Context, viewer Viewer, request InstallationConnectRequest) (InstallationConnectResponse, error)
- func (service InstallationConnectionService) CompleteOAuth(ctx context.Context, state, code, browserContinuation, oauthCredential string) (InstallationRedirect, error)
- func (service InstallationConnectionService) CompleteSetup(ctx context.Context, state string, installationID int64, ...) (InstallationRedirect, error)
- func (service InstallationConnectionService) Continue(ctx context.Context, state, openerChallenge string) (InstallationContinuation, error)
- type InstallationContinuation
- type InstallationLifecycleAction
- type InstallationLifecycleEvent
- type InstallationLifecycleStore
- type InstallationOpenerAuthorizationRequest
- type InstallationRedirect
- type InstallationState
- type InstallationStateDigest
- type InstallationStateStore
- type Machine
- type MachineBearerResolver
- type MachineCredentialBinding
- type MachineCredentialResolver
- type MachineCredentialStore
- type MachineEnrollmentRequest
- type MachineEnrollmentResponse
- type MachineEnrollmentService
- type MachineScope
- type MachineSnapshotService
- type MachineSnapshotStore
- type MachineSnapshotStoreResult
- type MachineTokenDigest
- type PendingRefresh
- type ProjectionProcessor
- type RepositoryEntitlementResolver
- type RepositoryEventService
- func (service RepositoryEventService) Acknowledge(ctx context.Context, machine Machine, request repositoryevent.AckRequest) (repositoryevent.AckResponse, error)
- func (service RepositoryEventService) EnqueueWebhook(ctx context.Context, delivery WebhookDelivery) (EnqueueResult, error)
- func (service RepositoryEventService) Poll(ctx context.Context, machine Machine, cursor string, limit int, ...) (repositoryevent.PollResponse, error)
- type RepositoryEventStatusStore
- type RepositoryEventStore
- type StatusConnection
- type StatusDelivery
- type StatusDeliveryMarker
- type StatusError
- type StatusInstallation
- type StatusMachine
- type StatusResponse
- type StatusService
- type StoredMachineSnapshot
- type VerifiedGitHubIdentity
- type VerifiedInstallation
- type VerifiedRepository
- type Viewer
- type ViewerResolver
- type WebhookDelivery
Constants ¶
const ( WebhookPath = APIPrefix + "/github/webhook" // MinimumWebhookSecretBytes is the configuration-readiness floor for a // high-entropy GitHub webhook secret. MinimumWebhookSecretBytes = 32 )
const ( InstallationConnectPath = APIPrefix + "/github/installations/connect" InstallationContinuePath = APIPrefix + "/github/installations/continue" InstallationAuthorizePath = APIPrefix + "/github/installations/authorize" InstallationSetupPath = APIPrefix + "/github/installations/setup" InstallationCallbackPath = APIPrefix + "/github/installations/callback" )
const APIPrefix = "/v0/workbench"
const (
MachineEnrollmentPath = APIPrefix + "/machines/enroll"
)
const StatusPath = APIPrefix + "/github/status"
Variables ¶
var ( ErrInvalidInstallationState = errors.New("workbench github app installation state is invalid") )
Functions ¶
func NewHandler ¶
func NewHandler(options HandlerOptions) http.Handler
func NewInstallationStores ¶
func NewInstallationStores(backend githubapp.DocumentStore) (InstallationStateStore, InstallationBindingStore, RepositoryEntitlementResolver, InstallationLifecycleStore)
NewInstallationStores wires the provider-owned installation stores to a host's github.com/sneat-dev/wb/api/githubapp DocumentStore. The returned binding store also implements RepositoryEntitlementResolver and InstallationLifecycleStore, so the same value can be assigned to every matching field on InstallationConnectionService, RepositoryEventService, and StatusService.
func NewRepositoryEventStore ¶
func NewRepositoryEventStore(backend githubapp.DocumentStore) (RepositoryEventStore, RepositoryEventStatusStore)
NewRepositoryEventStore wires the provider-owned repository event store to a host's github.com/sneat-dev/wb/api/githubapp DocumentStore. The returned value implements both RepositoryEventStore and RepositoryEventStatusStore.
Types ¶
type AppInstallationVerifier ¶
type AppInstallationVerifier interface {
VerifyAppInstallation(context.Context, int64) (VerifiedInstallation, error)
}
type EnqueueResult ¶
type EnrolledIdentity ¶
type EnrolledMachine ¶
type GitHubAppInstallationVerifier ¶
type GitHubAppInstallationVerifier struct {
Client *http.Client
AppID int64
PrivateKeyPEM []byte
APIBaseURL string
Now func() time.Time
}
func (GitHubAppInstallationVerifier) VerifyAppInstallation ¶
func (verifier GitHubAppInstallationVerifier) VerifyAppInstallation(ctx context.Context, installationID int64) (VerifiedInstallation, error)
type GitHubIdentityVerifier ¶
type GitHubOAuthVerifier ¶
type GitHubOAuthVerifier struct {
Client *http.Client
ClientID string
ClientSecret string
CallbackURL string
OAuthBaseURL string
APIBaseURL string
}
func (GitHubOAuthVerifier) ExchangeGitHubOAuthCode ¶
func (GitHubOAuthVerifier) Validate ¶
func (verifier GitHubOAuthVerifier) Validate() error
func (GitHubOAuthVerifier) VerifyGitHubIdentity ¶
func (verifier GitHubOAuthVerifier) VerifyGitHubIdentity(ctx context.Context, token string) (VerifiedGitHubIdentity, error)
type HandlerOptions ¶
type HandlerOptions struct {
ViewerResolver ViewerResolver
MachineBearer MachineBearerResolver
Enrollment *MachineEnrollmentService
Snapshots *MachineSnapshotService
Installations *InstallationConnectionService
RepositoryEvents *RepositoryEventService
Status *StatusService
Projection ProjectionProcessor
WebhookSecret []byte
AllowedOrigin string
}
type InstallationBindingStore ¶
type InstallationBindingStore interface {
IdentityHasInstallation(context.Context, string, int64) (bool, error)
// CompleteIdentityInstallationBinding atomically verifies and consumes the
// pending OAuth state and upserts only the explicitly selected installation.
// It must reject a different GitHub user when the Workbench identity already
// has bindings. A replay or state mismatch returns ErrInvalidInstallationState.
CompleteIdentityInstallationBinding(context.Context, InstallationStateDigest, InstallationState, time.Time, IdentityInstallationBinding) error
ListIdentityInstallationBindings(context.Context, string) ([]IdentityInstallationBinding, error)
}
type InstallationConnectRequest ¶
type InstallationConnectRequest struct {
InstallationID int64 `json:"installation_id,omitempty"`
}
type InstallationConnectionService ¶
type InstallationConnectionService struct {
States InstallationStateStore
AppVerifier AppInstallationVerifier
OAuthVerifier GitHubIdentityVerifier
Bindings InstallationBindingStore
InstallURL string
OAuthAuthorizeURL string
OAuthClientID string
OAuthCallbackURL string
SuccessURL string
StateSecret []byte
StateLifetime time.Duration
Random io.Reader
Now func() time.Time
}
func (InstallationConnectionService) AuthorizeOpener ¶
func (service InstallationConnectionService) AuthorizeOpener(ctx context.Context, viewer Viewer, request InstallationOpenerAuthorizationRequest) error
func (InstallationConnectionService) Begin ¶
func (service InstallationConnectionService) Begin(ctx context.Context, viewer Viewer, request InstallationConnectRequest) (InstallationConnectResponse, error)
func (InstallationConnectionService) CompleteOAuth ¶
func (service InstallationConnectionService) CompleteOAuth(ctx context.Context, state, code, browserContinuation, oauthCredential string) (InstallationRedirect, error)
func (InstallationConnectionService) CompleteSetup ¶
func (service InstallationConnectionService) CompleteSetup(ctx context.Context, state string, installationID int64, browserContinuation string) (InstallationRedirect, error)
func (InstallationConnectionService) Continue ¶
func (service InstallationConnectionService) Continue(ctx context.Context, state, openerChallenge string) (InstallationContinuation, error)
type InstallationLifecycleAction ¶
type InstallationLifecycleAction string
const ( InstallationSuspended InstallationLifecycleAction = "suspended" InstallationRevoked InstallationLifecycleAction = "revoked" InstallationRepositoriesRemoved InstallationLifecycleAction = "repositories_removed" InstallationUserAccessRemoved InstallationLifecycleAction = "user_access_removed" GitHubUserAuthorizationRevoked InstallationLifecycleAction = "github_user_authorization_revoked" RepositoryUserAccessRemoved InstallationLifecycleAction = "repository_user_access_removed" )
type InstallationLifecycleEvent ¶
type InstallationLifecycleEvent struct {
DeliveryID string `firestore:"delivery_id"`
Action InstallationLifecycleAction `firestore:"action"`
InstallationID int64 `firestore:"installation_id"`
RepositoryIDs []int64 `firestore:"repository_ids,omitempty"`
RepositoryID int64 `firestore:"repository_id,omitempty"`
GitHubUserID int64 `firestore:"github_user_id,omitempty"`
}
type InstallationLifecycleStore ¶
type InstallationLifecycleStore interface {
// ApplyInstallationLifecycle is idempotent by DeliveryID and atomically
// removes or disables every affected entitlement before it returns success.
// A GitHubUserAuthorizationRevoked event applies to every binding for that
// GitHub user; RepositoryUserAccessRemoved applies only to its exact
// installation, repository, and GitHub user tuple.
ApplyInstallationLifecycle(context.Context, InstallationLifecycleEvent) error
}
type InstallationRedirect ¶
type InstallationState ¶
type InstallationState struct {
Kind installationStateKind `firestore:"kind"`
IdentityID string `firestore:"identity_id"`
InstallationID int64 `firestore:"installation_id,omitempty"`
BrowserContinuationDigest string `firestore:"browser_continuation_digest"`
OpenerChallengeDigest string `firestore:"opener_challenge_digest,omitempty"`
OpenerChallengeExpiresAt time.Time `firestore:"opener_challenge_expires_at,omitempty"`
OpenerAuthorizedAt time.Time `firestore:"opener_authorized_at,omitempty"`
OAuthAccessTokenCiphertext string `firestore:"oauth_access_token_ciphertext,omitempty"`
IssuedAt time.Time `firestore:"issued_at"`
ExpiresAt time.Time `firestore:"expires_at"`
}
type InstallationStateDigest ¶
type InstallationStateStore ¶
type InstallationStateStore interface {
// IssueInstallationState rejects an existing digest so a random-state
// collision cannot overwrite another pending authorization.
IssueInstallationState(context.Context, InstallationStateDigest, InstallationState) error
// ReadInstallationState returns a pending, unexpired state without consuming
// it. Store availability errors must remain distinguishable from
// ErrInvalidInstallationState so callers can safely retry transient failures.
ReadInstallationState(context.Context, InstallationStateDigest, time.Time) (InstallationState, error)
// TransitionInstallationState atomically verifies current against the stored
// pending state, consumes it, and issues next. A replay, expired state,
// mismatch, or next-digest collision returns ErrInvalidInstallationState.
TransitionInstallationState(context.Context, InstallationStateDigest, InstallationState, time.Time, InstallationStateDigest, InstallationState) error
// ReplaceInstallationState atomically verifies current against the stored
// pending state and replaces it under the same digest. It is used to retain
// an encrypted exchanged OAuth credential before any fallible post-exchange
// reads. A replay, expiry, or mismatch returns ErrInvalidInstallationState.
ReplaceInstallationState(context.Context, InstallationStateDigest, InstallationState, time.Time, InstallationState) error
}
type Machine ¶
type Machine struct {
ID string
Name string
IdentityID string
Scopes []MachineScope
}
type MachineBearerResolver ¶
func NewMachineBearerResolver ¶
func NewMachineBearerResolver(credentials MachineCredentialResolver, pepper []byte) MachineBearerResolver
type MachineCredentialBinding ¶
type MachineCredentialBinding struct {
MachineID string `firestore:"machine_id"`
MachineName string `firestore:"machine_name"`
IdentityID string `firestore:"identity_id"`
IdentityDisplayName string `firestore:"identity_display_name,omitempty"`
IssuedAt time.Time `firestore:"issued_at"`
Scopes []MachineScope `firestore:"scopes"`
}
type MachineCredentialResolver ¶
type MachineCredentialResolver interface {
ResolveMachineCredential(context.Context, MachineTokenDigest) (MachineCredentialBinding, error)
}
type MachineCredentialStore ¶
type MachineCredentialStore interface {
RotateMachineCredential(context.Context, MachineCredentialBinding, MachineTokenDigest) (MachineCredentialBinding, error)
}
type MachineEnrollmentRequest ¶
type MachineEnrollmentRequest struct {
Name string `json:"name"`
}
type MachineEnrollmentResponse ¶
type MachineEnrollmentResponse struct {
Machine EnrolledMachine `json:"machine"`
Identity EnrolledIdentity `json:"identity"`
Token string `json:"token"`
EnrolledAt time.Time `json:"enrolled_at"`
}
type MachineEnrollmentService ¶
type MachineEnrollmentService struct {
Store MachineCredentialStore
Pepper []byte
Random io.Reader
Now func() time.Time
}
func (MachineEnrollmentService) Enroll ¶
func (service MachineEnrollmentService) Enroll(ctx context.Context, viewer Viewer, request MachineEnrollmentRequest) (MachineEnrollmentResponse, error)
type MachineScope ¶
type MachineScope string
const ( ScopeSnapshotPublish MachineScope = "machine_snapshot:publish" ScopeSnapshotRead MachineScope = "machine_snapshot:read" ScopeEventsPoll MachineScope = "repository_events:poll" ScopeEventsAck MachineScope = "repository_events:ack" )
type MachineSnapshotService ¶
type MachineSnapshotService struct {
Store MachineSnapshotStore
Now func() time.Time
}
func (MachineSnapshotService) List ¶
func (service MachineSnapshotService) List(ctx context.Context, machine Machine) (machinesnapshot.ListResponse, error)
func (MachineSnapshotService) Publish ¶
func (service MachineSnapshotService) Publish(ctx context.Context, machine Machine, snapshot machinesnapshot.Snapshot) (machinesnapshot.Receipt, error)
type MachineSnapshotStore ¶
type MachineSnapshotStore interface {
StoreLatest(context.Context, StoredMachineSnapshot) (MachineSnapshotStoreResult, error)
ListLatest(context.Context) ([]StoredMachineSnapshot, error)
}
type MachineSnapshotStoreResult ¶
type MachineSnapshotStoreResult struct {
Current StoredMachineSnapshot
Updated bool
}
func ResolveLatestMachineSnapshot ¶
func ResolveLatestMachineSnapshot(current *StoredMachineSnapshot, candidate StoredMachineSnapshot) (MachineSnapshotStoreResult, error)
type MachineTokenDigest ¶
func DigestMachineToken ¶
func DigestMachineToken(token string, pepper []byte) (MachineTokenDigest, error)
type PendingRefresh ¶
type ProjectionProcessor ¶
type ProjectionProcessor interface {
ProcessProjection(context.Context, WebhookDelivery, string) error
}
ProjectionProcessor lets an existing dashboard projection subsystem consume the same already authenticated delivery while that subsystem is migrated.
type RepositoryEventService ¶
type RepositoryEventService struct {
Snapshots MachineSnapshotStore
Entitlements RepositoryEntitlementResolver
Lifecycle InstallationLifecycleStore
Store RepositoryEventStore
PollInterval time.Duration
Sleep func(context.Context, time.Duration) error
Now func() time.Time
}
func (RepositoryEventService) Acknowledge ¶
func (service RepositoryEventService) Acknowledge(ctx context.Context, machine Machine, request repositoryevent.AckRequest) (repositoryevent.AckResponse, error)
func (RepositoryEventService) EnqueueWebhook ¶
func (service RepositoryEventService) EnqueueWebhook(ctx context.Context, delivery WebhookDelivery) (EnqueueResult, error)
func (RepositoryEventService) Poll ¶
func (service RepositoryEventService) Poll(ctx context.Context, machine Machine, cursor string, limit int, wait time.Duration) (repositoryevent.PollResponse, error)
type RepositoryEventStatusStore ¶
type RepositoryEventStatusStore interface {
IdentityRepositoryEventStatus(context.Context, string) (*StatusDelivery, []PendingRefresh, []StatusError, error)
}
type RepositoryEventStore ¶
type RepositoryEventStore interface {
EnqueueForMachines(context.Context, repositoryevent.Event, []Machine) (EnqueueResult, error)
Poll(context.Context, Machine, string, int) (repositoryevent.PollResponse, error)
Acknowledge(context.Context, Machine, repositoryevent.AckRequest) (repositoryevent.AckResponse, error)
}
type StatusConnection ¶
type StatusDelivery ¶
type StatusDelivery struct {
LastReceived *StatusDeliveryMarker `json:"last_received,omitempty"`
LastAcknowledged *StatusDeliveryMarker `json:"last_acknowledged,omitempty"`
}
type StatusDeliveryMarker ¶
type StatusError ¶
type StatusInstallation ¶
type StatusInstallation struct {
ID string `json:"id"`
Account string `json:"account"`
AccountType string `json:"account_type,omitempty"`
State string `json:"state"`
RepositorySelection string `json:"repository_selection,omitempty"`
Repositories int `json:"repositories,omitempty"`
ManageURL string `json:"manage_url,omitempty"`
}
type StatusMachine ¶
type StatusResponse ¶
type StatusResponse struct {
GeneratedAt time.Time `json:"generated_at"`
Connection StatusConnection `json:"connection"`
Installations []StatusInstallation `json:"installations"`
Machines []StatusMachine `json:"machines"`
Delivery *StatusDelivery `json:"delivery,omitempty"`
PendingRefreshes []PendingRefresh `json:"pending_refreshes"`
Errors []StatusError `json:"errors"`
}
type StatusService ¶
type StatusService struct {
Bindings InstallationBindingStore
Snapshots MachineSnapshotStore
Events RepositoryEventStatusStore
AppName string
Now func() time.Time
}
func (StatusService) Read ¶
func (service StatusService) Read(ctx context.Context, viewer Viewer) (StatusResponse, error)
type StoredMachineSnapshot ¶
type VerifiedGitHubIdentity ¶
type VerifiedGitHubIdentity struct {
UserID int64 `json:"user_id"`
Login string `json:"login"`
Installations []VerifiedInstallation `json:"installations"`
}
func (VerifiedGitHubIdentity) Validate ¶
func (identity VerifiedGitHubIdentity) Validate() error
type VerifiedInstallation ¶
type VerifiedInstallation struct {
ID int64 `json:"id" firestore:"id"`
Account string `json:"account" firestore:"account"`
AccountType string `json:"account_type,omitempty" firestore:"account_type,omitempty"`
RepositorySelection string `json:"repository_selection" firestore:"repository_selection"`
Repositories []VerifiedRepository `json:"repositories" firestore:"repositories"`
State string `json:"state,omitempty" firestore:"state,omitempty"`
ManageURL string `json:"manage_url,omitempty" firestore:"manage_url,omitempty"`
}