Documentation
¶
Overview ¶
Package github is the GitHub implementation of forge.Admin: the outbound write-side client the orchestrator uses to list repos and create/update/delete the iterion webhook on them. Shared by the OAuth-App path (user token) and, later, the GitHub-App path (installation token) — they differ only in how the token is obtained, not in these REST calls.
Index ¶
- Variables
- func APIBaseFor(webBase string) string
- func AppManageURL(webBase, ownerLogin, ownerType, slug string) string
- func DeliveryInstallationPermissions() map[string]string
- func LastMintedPermissions(installationID int64) (map[string]string, bool)
- func MintInstallationToken(ctx context.Context, httpClient *http.Client, apiBase string, cfg AppConfig, ...) (string, time.Time, error)
- func MissingDeliveryPermissions(granted map[string]string) []string
- func RecordRuntimePermissions(installationID int64, perms map[string]string)
- func RepoAdminInstallationPermissions() map[string]string
- func RuntimeInstallationPermissions() map[string]string
- func RuntimePermissionsFor(granted map[string]string) map[string]string
- func VerifyInstallationOwnership(ctx context.Context, httpClient *http.Client, webBase string, cfg AppConfig, ...) error
- type AdminClient
- func (c *AdminClient) CollaboratorPermission(ctx context.Context, repo, user string) (string, error)
- func (c *AdminClient) CommentIssue(ctx context.Context, repo string, number int, body string) (forge.CommentRef, error)
- func (c *AdminClient) CreateHook(ctx context.Context, repo string, spec forge.HookSpec) (forge.HookHandle, error)
- func (c *AdminClient) CreateIssue(ctx context.Context, repo string, in forge.NewIssue) (forge.IssueRef, error)
- func (c *AdminClient) CreatePull(ctx context.Context, repo string, in forge.NewPull) (forge.PullRef, error)
- func (c *AdminClient) CreateRepo(ctx context.Context, spec forge.RepoCreateSpec) (forge.RepoSummary, error)
- func (c *AdminClient) DeleteHook(ctx context.Context, repo, hookID string) error
- func (c *AdminClient) GetCIStatus(ctx context.Context, repo, ref string) (forge.CIStatus, error)
- func (c *AdminClient) GetFile(ctx context.Context, repo, path, ref string) (forge.FileRef, error)
- func (c *AdminClient) GetHook(ctx context.Context, repo, deliveryURL string) (*forge.HookHandle, error)
- func (c *AdminClient) GetIssue(ctx context.Context, repo string, number int) (forge.IssueRef, error)
- func (c *AdminClient) GetPullRequest(ctx context.Context, repo string, number int) (forge.PullRef, error)
- func (c *AdminClient) ListCIHistory(ctx context.Context, repo, ref string, limit int) ([]forge.CIRun, error)
- func (c *AdminClient) ListHooks(ctx context.Context, repo string) ([]forge.HookHandle, error)
- func (c *AdminClient) ListIssues(ctx context.Context, repo string, opts forge.IssueListOptions) ([]forge.IssueRef, error)
- func (c *AdminClient) ListPullRequests(ctx context.Context, repo string, opts forge.PullListOptions) ([]forge.PullRef, error)
- func (c *AdminClient) ListRepos(ctx context.Context, q forge.RepoQuery) ([]forge.RepoSummary, error)
- func (c *AdminClient) MergePull(ctx context.Context, repo string, number int, opts forge.MergeOptions) (forge.PullRef, error)
- func (c *AdminClient) OrgMembershipRole(ctx context.Context, org string) (role string, active bool, err error)
- func (c *AdminClient) Provider() forge.Provider
- func (c *AdminClient) PutFile(ctx context.Context, repo string, in forge.PutFile) (forge.FileRef, error)
- func (c *AdminClient) UpdateHook(ctx context.Context, repo, hookID string, spec forge.HookSpec) (forge.HookHandle, error)
- func (c *AdminClient) UpdateIssue(ctx context.Context, repo string, number int, patch forge.IssuePatch) (forge.IssueRef, error)
- func (c *AdminClient) UpdatePull(ctx context.Context, repo string, number int, patch forge.PullPatch) (forge.PullRef, error)
- func (c *AdminClient) WhoAmI(ctx context.Context) (forge.Identity, error)
- type AppClient
- func (a *AppClient) CreateHook(ctx context.Context, repo string, spec forge.HookSpec) (forge.HookHandle, error)
- func (a *AppClient) CreateRepo(ctx context.Context, spec forge.RepoCreateSpec) (forge.RepoSummary, error)
- func (a *AppClient) DeleteHook(ctx context.Context, repo, hookID string) error
- func (a *AppClient) GetHook(ctx context.Context, repo, deliveryURL string) (*forge.HookHandle, error)
- func (a *AppClient) ListHooks(ctx context.Context, repo string) ([]forge.HookHandle, error)
- func (a *AppClient) ListRepos(ctx context.Context, q forge.RepoQuery) ([]forge.RepoSummary, error)
- func (a *AppClient) Provider() forge.Provider
- func (a *AppClient) UpdateHook(ctx context.Context, repo, hookID string, spec forge.HookSpec) (forge.HookHandle, error)
- func (a *AppClient) WhoAmI(context.Context) (forge.Identity, error)
- type AppConfig
- type AppManifest
- type AppManifestOptions
- type AppRefresher
- type Installation
- type InstallationTokenOptions
- type ManifestConversion
- type OAuthApp
Constants ¶
This section is empty.
Variables ¶
var DefaultScopes = []string{"repo"}
DefaultScopes is the OAuth-App scope set a GitHub connection requests. `repo` covers the whole job iterion's OAuth-App path needs: read the diff, post PR comments, and manage repo webhooks (it subsumes admin:repo_hook).
Least-privilege: `read:org` is deliberately NOT requested. ListRepos already surfaces org repositories via `/user/repos?affiliation=…, organization_member`, which `repo` alone authorizes; `read:org` only adds org/team-membership *enumeration* (the user's full org graph) that iterion does not use here. Dropping it keeps the connection token off the user's org-membership data. Operators wanting curated org-repo access should use the least-privilege GitHub-App connect flow (operator-selected repos).
var ErrInstallationNotOwned = fmt.Errorf("github: installation not accessible to the authorizing user")
ErrInstallationNotOwned is returned by VerifyInstallationOwnership when the authenticated user provably does NOT have access to the installation — the signal the install callback maps to a 403 (an attacker substituting another org's installation_id).
Functions ¶
func APIBaseFor ¶
APIBaseFor maps a GitHub WEB base URL to its REST API base. github.com → api.github.com; a GitHub Enterprise host → <host>/api/v3.
func AppManageURL ¶
AppManageURL is the GitHub settings page (Advanced tab, with the Delete button) for a created App, so the operator can remove it on the forge — GitHub exposes no API to delete an App. Empty when the slug is unknown.
func DeliveryInstallationPermissions ¶ added in v1.0.0
DeliveryInstallationPermissions are what a bot needs to SHIP an application rather than only change code: publish the CI definition that builds it, and publish the resulting image.
They are separated from the runtime baseline because `workflows: write` is a genuine escalation — an actor who can rewrite CI can run arbitrary code in it — so an App only gets them when the operator opts in at creation.
GitHub enforces this hard, not softly: pushing a commit that touches .github/workflows/** with a token lacking `workflows` is REJECTED outright ("refusing to allow a GitHub App to create or update workflow … without `workflows` permission"), which blocks the whole build-and-deploy chain at its first step.
func LastMintedPermissions ¶ added in v1.0.0
LastMintedPermissions returns the permissions carried by the most recent runtime token minted for an installation in this process, and whether one is known.
func MintInstallationToken ¶
func MintInstallationToken(ctx context.Context, httpClient *http.Client, apiBase string, cfg AppConfig, installationID int64, now time.Time, opts *InstallationTokenOptions) (string, time.Time, error)
MintInstallationToken trades the App JWT for a short-lived (≈1h) installation access token. apiBase is the REST API base (APIBaseFor). opts may be nil for an unconstrained (whole-installation) token, or narrow it to specific repositories + a permission subset (least-privilege).
func MissingDeliveryPermissions ¶ added in v1.0.0
MissingDeliveryPermissions lists the delivery grants an installation does NOT have, so the connection health view can name them BEFORE a run spends hours discovering them at push time. Empty when nothing is missing (or when the grant set is unknown — absence of data is not evidence of a gap).
func RecordRuntimePermissions ¶ added in v1.0.0
RecordRuntimePermissions notes what a runtime token carried. Called by the mint paths that produce the token a RUN uses.
func RepoAdminInstallationPermissions ¶ added in v0.50.0
RepoAdminInstallationPermissions is the permission subset minted ONLY for a CreateRepo call. administration:write is a broad grant (repo settings), so it never rides the cached runtime token (RuntimeInstallationPermissions) — each create mints its own short-lived token and drops it.
func RuntimeInstallationPermissions ¶
RuntimeInstallationPermissions is the least-privilege permission subset an installation token minted for iterion is pinned to — exactly what the forge layer needs (read/push code, open+comment PRs, comment the source issue for the MR back-link, manage the per-repo webhook, the mandatory metadata baseline) and nothing more. Mirrors the App manifest (BuildAppManifest); pinning it at mint time means a token stays minimal even if the installation is later granted broader permissions on the forge.
func RuntimePermissionsFor ¶ added in v1.0.0
RuntimePermissionsFor narrows the permissions iterion WANTS to those the installation actually granted.
Minting is not forgiving: asking for a permission the installation lacks fails the whole call (422), so a token cannot simply request the superset. Intersecting keeps one code path for both an App created before delivery permissions existed and one created with them.
granted == nil means "unknown" (a connection stored before grants were recorded); those keep exactly the historical baseline.
func VerifyInstallationOwnership ¶
func VerifyInstallationOwnership(ctx context.Context, httpClient *http.Client, webBase string, cfg AppConfig, code string, installationID int64) error
VerifyInstallationOwnership proves the user completing a GitHub-App install callback actually has access to the installation they claim, closing the IDOR where installation_id (an enumerable integer taken verbatim from the callback URL) is trusted without a check. It:
- exchanges the user-authorization `code` for a user-to-server token (the App must have "Request user authorization (OAuth) during installation" enabled, so GitHub appends `code` to the setup redirect);
- lists the installations that token can see (GET /user/installations);
- returns nil only when installationID is among them.
A user cannot mint a code for an installation they don't control, and /user/installations only returns installations the user can access, so a forged installation_id fails at step 3. Returns ErrInstallationNotOwned when the installation is absent, or a wrapped error on exchange/API failure (callers fail closed on any error).
Types ¶
type AdminClient ¶
type AdminClient struct {
HTTP *http.Client
APIBase string // e.g. "https://api.github.com" or "https://ghe.example.com/api/v3"
Token string
}
AdminClient talks to one GitHub instance (github.com or GHE) as one connection. Auth is a Bearer token (an OAuth user token, a PAT, or a GitHub-App installation token — GitHub accepts all three the same way).
func New ¶
func New(httpClient *http.Client, baseURL, token string) *AdminClient
New builds an AdminClient. baseURL is the forge's WEB base ("https://github.com" or a GHE host); it is mapped to the matching REST API base. A nil httpClient falls back to http.DefaultClient.
func (*AdminClient) CollaboratorPermission ¶
func (c *AdminClient) CollaboratorPermission(ctx context.Context, repo, user string) (string, error)
CollaboratorPermission returns user's permission on repo ("owner/repo") via GET /repos/{repo}/collaborators/{user}/permission — one of admin|maintain|write|triage|read|none (role_name when present, else the legacy permission). A 404 (not a collaborator) is "none", not an error. Used by the inbound-webhook command gate to authorize a commenter against a bot's MinReplierRole.
func (*AdminClient) CommentIssue ¶
func (c *AdminClient) CommentIssue(ctx context.Context, repo string, number int, body string) (forge.CommentRef, error)
CommentIssue posts a comment on an issue (or PR — GitHub shares the endpoint).
func (*AdminClient) CreateHook ¶
func (c *AdminClient) CreateHook(ctx context.Context, repo string, spec forge.HookSpec) (forge.HookHandle, error)
func (*AdminClient) CreateIssue ¶
func (c *AdminClient) CreateIssue(ctx context.Context, repo string, in forge.NewIssue) (forge.IssueRef, error)
CreateIssue opens a new issue (board→forge push).
func (*AdminClient) CreatePull ¶
func (c *AdminClient) CreatePull(ctx context.Context, repo string, in forge.NewPull) (forge.PullRef, error)
CreatePull opens a pull request. head/base are branch names; Draft maps to GitHub's `draft` create flag.
func (*AdminClient) CreateRepo ¶ added in v0.50.0
func (c *AdminClient) CreateRepo(ctx context.Context, spec forge.RepoCreateSpec) (forge.RepoSummary, error)
CreateRepo implements forge.RepoCreator. Owner "" targets the token's own user namespace (POST /user/repos); an org login targets POST /orgs/{owner}/repos. Spec.DefaultBranch is intentionally not sent: on an empty GitHub repo the FIRST PUSH decides the default branch (Appy's `git init -b main` flow), and with InitReadme GitHub applies the owner's configured default.
func (*AdminClient) DeleteHook ¶
func (c *AdminClient) DeleteHook(ctx context.Context, repo, hookID string) error
func (*AdminClient) GetCIStatus ¶
GetCIStatus returns the CURRENT aggregate CI state + runs for a ref, combining GitHub Actions/App check-runs with the legacy commit-status API.
func (*AdminClient) GetFile ¶ added in v0.50.0
GetFile reads a single file via GET /repos/{repo}/contents/{path}. A missing path returns forge.ErrFileNotFound; a directory (or symlink/submodule) is an error, not a silent empty read.
func (*AdminClient) GetHook ¶
func (c *AdminClient) GetHook(ctx context.Context, repo, deliveryURL string) (*forge.HookHandle, error)
func (*AdminClient) GetIssue ¶
func (c *AdminClient) GetIssue(ctx context.Context, repo string, number int) (forge.IssueRef, error)
GetIssue fetches one issue (or PR) by number.
func (*AdminClient) GetPullRequest ¶
func (c *AdminClient) GetPullRequest(ctx context.Context, repo string, number int) (forge.PullRef, error)
GetPullRequest fetches one PR by number.
func (*AdminClient) ListCIHistory ¶
func (c *AdminClient) ListCIHistory(ctx context.Context, repo, ref string, limit int) ([]forge.CIRun, error)
ListCIHistory lists recent check-runs for a ref/branch, newest first (by start time), capped at limit (0 → 30).
func (*AdminClient) ListHooks ¶
func (c *AdminClient) ListHooks(ctx context.Context, repo string) ([]forge.HookHandle, error)
ListHooks returns every webhook registered on repo (not just the iterion-owned one), for operator audit. Mirrors GetHook's listing but without the delivery-URL filter.
func (*AdminClient) ListIssues ¶
func (c *AdminClient) ListIssues(ctx context.Context, repo string, opts forge.IssueListOptions) ([]forge.IssueRef, error)
ListIssues lists issues for repo ("owner/repo"). PRs are returned by the same endpoint but flagged via IsPullRequest so callers can drop them.
func (*AdminClient) ListPullRequests ¶
func (c *AdminClient) ListPullRequests(ctx context.Context, repo string, opts forge.PullListOptions) ([]forge.PullRef, error)
ListPullRequests lists PRs for repo ("owner/repo"). GitHub's /pulls endpoint has no `since` filter, so opts.Since is ignored here.
func (*AdminClient) ListRepos ¶
func (c *AdminClient) ListRepos(ctx context.Context, q forge.RepoQuery) ([]forge.RepoSummary, error)
ListRepos returns repos the token can admin (Permissions.Admin) — the floor for managing repo webhooks.
func (*AdminClient) MergePull ¶
func (c *AdminClient) MergePull(ctx context.Context, repo string, number int, opts forge.MergeOptions) (forge.PullRef, error)
MergePull merges a PR via PUT /pulls/{n}/merge, then re-fetches it once so the returned ref reflects the merged state. When opts.DeleteBranch is set, the source branch (read off the re-fetched ref) is best-effort deleted afterwards — a failure there does not fail the merge.
func (*AdminClient) OrgMembershipRole ¶
func (c *AdminClient) OrgMembershipRole(ctx context.Context, org string) (role string, active bool, err error)
OrgMembershipRole reports the caller's role ("admin" | "member") in org and whether the membership is active, via GET /user/memberships/orgs/{org}. A 404/403 (not a member, or no visibility) returns ("", false, nil) — the caller treats that as "no proof of control", not an error. Used to verify an iterion team controls (admins) a GitHub org before its teams may be allow-listed for SSO.
func (*AdminClient) Provider ¶
func (c *AdminClient) Provider() forge.Provider
func (*AdminClient) PutFile ¶ added in v0.50.0
func (c *AdminClient) PutFile(ctx context.Context, repo string, in forge.PutFile) (forge.FileRef, error)
PutFile writes a single file via PUT /repos/{repo}/contents/{path}. Updating an existing file requires in.PrevSHA; a stale value yields HTTP 409, mapped to forge.ErrFileConflict so the caller re-reads instead of clobbering.
func (*AdminClient) UpdateHook ¶
func (c *AdminClient) UpdateHook(ctx context.Context, repo, hookID string, spec forge.HookSpec) (forge.HookHandle, error)
func (*AdminClient) UpdateIssue ¶
func (c *AdminClient) UpdateIssue(ctx context.Context, repo string, number int, patch forge.IssuePatch) (forge.IssueRef, error)
UpdateIssue applies a partial update; nil patch fields are left untouched.
func (*AdminClient) UpdatePull ¶
func (c *AdminClient) UpdatePull(ctx context.Context, repo string, number int, patch forge.PullPatch) (forge.PullRef, error)
UpdatePull applies a partial update. GitHub's REST PATCH covers title/body/ base/state; converting draft↔ready is GraphQL-only, so PullPatch carries no draft toggle.
type AppClient ¶
type AppClient struct {
HTTP *http.Client
WebBaseURL string
Cfg AppConfig
InstallationID int64
Now func() time.Time
// contains filtered or unexported fields
}
AppClient is a forge.Admin for one GitHub-App installation. It mints + caches the installation token (refreshing ≈60s before expiry) and delegates the actual REST calls to an AdminClient. Repo listing + identity differ from a user token (an installation token can't read /user), so those are overridden.
func (*AppClient) CreateHook ¶
func (*AppClient) CreateRepo ¶ added in v0.50.0
func (a *AppClient) CreateRepo(ctx context.Context, spec forge.RepoCreateSpec) (forge.RepoSummary, error)
CreateRepo (AppClient) mints a dedicated administration:write installation token for this single call — the cached runtime token keeps its minimal permission set. Requires the installation to have the Administration permission granted; an ungranted install surfaces forge.ErrPermissionsNotGranted with GitHub's actionable message (fix: approve the App's pending permission update on GitHub).
func (*AppClient) DeleteHook ¶
func (*AppClient) ListRepos ¶
ListRepos lists the installation's repositories (GET /installation/repositories) — an installation token's repo set, not the user's. The App was installed with webhook-write permission, so every listed repo is admin-capable (a missing permission surfaces as a 403 on CreateHook, mapped to insufficient_scope).
func (*AppClient) UpdateHook ¶
type AppConfig ¶
type AppConfig struct {
AppID int64
PrivateKeyPEM string
AppSlug string // for the install URL github.com/apps/<slug>/installations/new
// ClientID/ClientSecret are the App's user-authorization OAuth
// credentials. Optional: when both are set (and the App has "Request
// user authorization (OAuth) during installation" enabled on GitHub),
// the install callback verifies the completing user actually owns the
// installation before minting a token for it — see
// VerifyInstallationOwnership. Empty → verification is unavailable.
ClientID string
ClientSecret string
}
AppConfig is the global GitHub-App identity (registered once on GitHub), shared across every installation. The private key never leaves the process; it is loaded from deployment config, not from Mongo.
func (AppConfig) Configured ¶
func (AppConfig) UserAuthConfigured ¶
UserAuthConfigured reports whether the App carries the OAuth client credentials needed to verify installation ownership at connect time.
type AppManifest ¶
type AppManifest struct {
Name string `json:"name"`
URL string `json:"url"`
RedirectURL string `json:"redirect_url"`
// CallbackURLs are the user-authorization (OAuth) callback URLs baked into
// the created App. WITHOUT this the subsequent "connect via OAuth" step fails
// with GitHub's "This GitHub App must be configured with a callback URL" —
// RedirectURL above only covers the one-shot manifest-conversion redirect,
// not the recurring user-to-server OAuth the connect flow uses.
CallbackURLs []string `json:"callback_urls"`
// SetupURL is where GitHub redirects AFTER the user installs the App on
// repos — without it GitHub stays put and iterion never sees the
// installation, so the github_app connection is never created. It points at
// the install callback, which the install flow's state flows through to.
SetupURL string `json:"setup_url"`
SetupOnUpdate bool `json:"setup_on_update"`
Public bool `json:"public"`
DefaultEvents []string `json:"default_events"`
DefaultPermissions map[string]string `json:"default_permissions"`
HookAttributes map[string]any `json:"hook_attributes"`
}
AppManifest is the GitHub App manifest iterion POSTs to <web>/settings/apps/new — the only programmatic path to create a GitHub app (there is no create-OAuth-app REST endpoint). The created App's client_id/client_secret then drive the existing OAuth user-to-server connect flow (OAuthApp).
func BuildAppManifest ¶
func BuildAppManifest(name, homeURL, redirectURL string, opts ...AppManifestOptions) AppManifest
BuildAppManifest assembles the manifest for an iterion forge GitHub App. The baseline permissions are the LEAST-PRIVILEGE set iterion's forge layer actually needs: push/read code (contents), open + comment on PRs (pull_requests), the mandatory metadata baseline, and manage the per-repo inbound webhook (repository_hooks — the App-level webhook is disabled, iterion creates per-repo hooks itself). `administration` (repo settings / deletion / branch-protection) is never part of the baseline — webhooks require `repository_hooks:write`, not `administration` — and only joins the grant when opts.AllowRepoCreation asks for the create-repo capability.
type AppManifestOptions ¶ added in v0.50.0
type AppManifestOptions struct {
// AllowRepoCreation adds administration:write to the App's requested
// permissions so iterion can CREATE repositories in the installed
// org (the "new app → new repo" launch journey). Broad grant, hence
// opt-in at App-creation time and surfaced in the connect UI; at run
// time it is minted per CreateRepo call only
// (RepoAdminInstallationPermissions) — the cached runtime token stays
// on RuntimeInstallationPermissions.
AllowRepoCreation bool
// AllowAppDelivery adds workflows:write + packages:write so a bot can
// publish the CI that builds an app and the image it produces — the
// second half of the "new app → new repo → deployed" journey. Opt-in
// because `workflows: write` lets the holder rewrite CI, i.e. run
// arbitrary code in it.
AllowAppDelivery bool
}
AppManifestOptions tunes the App's requested grant set beyond the least-privilege runtime baseline.
type AppRefresher ¶
type AppRefresher struct {
HTTP *http.Client
Cfg AppConfig
Now func() time.Time
// Repos, when set, returns the short repo names (e.g. "api", not
// "org/api") this connection actually operates on, so the runtime
// forge_token is scoped to that repo set (least-privilege) instead of the
// whole installation. A nil slice with a nil error → whole-installation
// (still minimal permissions). A non-nil error means the set could not be
// determined; Refresh fails closed rather than minting a broader token.
// Injected by the server so the refresher stays free of a store dependency.
Repos func(ctx context.Context, conn forge.Connection) ([]string, error)
}
AppRefresher re-mints the installation token for the connection's managed forge_token secret (forge.TokenRefresher). The refreshToken arg is unused — a GitHub App re-mints from its private key, not a refresh token.
func (AppRefresher) Refresh ¶
func (r AppRefresher) Refresh(ctx context.Context, conn forge.Connection, _ string) (forge.RefreshedToken, error)
type Installation ¶ added in v1.0.0
type Installation struct {
Login string
HTMLURL string
// Permissions is the grant the ORG actually approved, which can be
// narrower than what the App requests (a permission added to an App after
// installation stays pending until an owner approves it). Carrying it lets
// the mint ask only for what exists, and lets the health probe name a
// missing grant BEFORE a run spends hours discovering it at push time.
Permissions map[string]string
}
Installation is what GET /app/installations/{id} tells us about a live installation.
func InstallationInfo ¶ added in v0.50.0
func InstallationInfo(ctx context.Context, httpClient *http.Client, apiBase string, cfg AppConfig, installationID int64, now time.Time) (info Installation, err error)
InstallationInfo returns the installation's account login, its GitHub settings page URL (html_url — the only place where the installation's repo scope and permission grants can be widened), and its approved permissions. App-JWT-authenticated: GET /app/installations/{id}.
type InstallationTokenOptions ¶
InstallationTokenOptions narrows a minted installation token below the installation's full grant (least-privilege). Both fields are optional; a nil field means "don't constrain that dimension" (GitHub returns the installation's full set).
- Repositories: short repo names (e.g. "api", NOT "org/api") the token may touch. Empty → all repositories in the installation.
- Permissions: the permission subset (a subset of the installation's own grants). Empty → the installation's full permission set.
type ManifestConversion ¶
type ManifestConversion struct {
ID int64 `json:"id"`
Slug string `json:"slug"`
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
// PEM is the App's private key — the credential the least-privilege
// github_app (installation-token) path needs. WebhookSecret is the App's
// generated webhook secret. Both are returned once by the conversion and
// must be captured here (GitHub won't re-issue the private key).
PEM string `json:"pem"`
WebhookSecret string `json:"webhook_secret"`
Owner struct {
Login string `json:"login"`
Type string `json:"type"` // "Organization" | "User"
} `json:"owner"`
}
ManifestConversion is the subset of GitHub's app-manifest conversion response iterion keeps: the App id + slug + owner (to deep-link its settings) and the OAuth client credentials.
func ConvertManifest ¶
func ConvertManifest(ctx context.Context, httpClient *http.Client, webBase, code string) (ManifestConversion, error)
ConvertManifest exchanges the temporary code GitHub returns after the operator confirms the manifest for the created App's credentials, via POST {apiBase}/app-manifests/{code}/conversions. The code is single-use and expires in ~1h; no auth header is needed (the code is the credential).
type OAuthApp ¶
type OAuthApp struct {
HTTP *http.Client
BaseURL string // WEB base ("https://github.com" or a GHE host)
ClientID string
ClientSecret string
}
OAuthApp drives the GitHub OAuth-App authorization-code flow for one GitHub instance (github.com or GHE). Classic OAuth Apps issue non-expiring tokens and don't support PKCE, so there is no refresh path (the connection carries no expiry → the refresh worker skips it).
func (*OAuthApp) AuthorizeURL ¶
AuthorizeURL builds the redirect URL. codeChallenge is ignored — classic GitHub OAuth Apps don't support PKCE.