9b94f18d7a
Anonymous GitHub is capped at 60 requests/hour and cannot read private repositories, so a machine credential usable by a free (non-enterprise) account is needed to lift the request budget and reach private release assets. - Add internal/githubauth: a process-wide credential delivered via env/secret, applied by default to every outbound GitHub request. - Support two modes: a Personal Access Token sent as `Authorization: Bearer`, and a GitHub App that mints a short-lived RS256 JWT (stdlib crypto, no new dependency), exchanges it for a ~1h installation token, caches it, and single-flights a refresh a few minutes before expiry. - Inject the credential at the two GitHub call paths: the rpm github provider (releases scan + ranged asset-header GETs) and the generic byte proxy (private release-asset downloads for github.com hosts). - Honor precedence: a remote's own username/password overrides the server credential; no credential configured stays anonymous. - Fail closed at startup on partial App configuration; never persist the credential to the DB, return it from an API, or log it. - Read GITHUB_TOKEN / GITHUB_APP_ID / GITHUB_APP_INSTALLATION_ID / GITHUB_APP_PRIVATE_KEY[_PATH] via the existing getenv convention. - Document PAT vs App setup, the free-account fine-grained PAT scopes (Contents:read + Metadata:read), precedence, and the rate-limit implication.
78 lines
1.8 KiB
Go
78 lines
1.8 KiB
Go
package githubauth
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
)
|
|
|
|
func TestNew_NoConfigIsAnonymous(t *testing.T) {
|
|
c, err := New(Options{})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if c != nil {
|
|
t.Fatalf("expected nil credential when nothing configured, got %T", c)
|
|
}
|
|
}
|
|
|
|
func TestNew_TokenMode(t *testing.T) {
|
|
c, err := New(Options{Token: "ghp_example"})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
tok, err := c.Token(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("token: %v", err)
|
|
}
|
|
if tok != "ghp_example" {
|
|
t.Fatalf("token = %q, want ghp_example", tok)
|
|
}
|
|
}
|
|
|
|
func TestNew_TokenAndAppConflict(t *testing.T) {
|
|
_, err := New(Options{Token: "ghp_example", AppID: "123"})
|
|
if err == nil {
|
|
t.Fatal("expected error when both token and app fields are set")
|
|
}
|
|
}
|
|
|
|
func TestNew_PartialAppFailsClosed(t *testing.T) {
|
|
cases := map[string]Options{
|
|
"app id without key": {AppID: "123", InstallationID: "456"},
|
|
"key without app id": {InstallationID: "456", PrivateKeyPEM: testRSAKeyPEM(t)},
|
|
"app id without inst": {AppID: "123", PrivateKeyPEM: testRSAKeyPEM(t)},
|
|
}
|
|
for name, opts := range cases {
|
|
t.Run(name, func(t *testing.T) {
|
|
if _, err := New(opts); err == nil {
|
|
t.Fatalf("expected fail-closed error for %q", name)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNew_AppModeParsesKey(t *testing.T) {
|
|
c, err := New(Options{
|
|
AppID: "123",
|
|
InstallationID: "456",
|
|
PrivateKeyPEM: testRSAKeyPEM(t),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if _, ok := c.(*appCredential); !ok {
|
|
t.Fatalf("expected *appCredential, got %T", c)
|
|
}
|
|
}
|
|
|
|
func TestNew_AppModeRejectsBadKey(t *testing.T) {
|
|
_, err := New(Options{
|
|
AppID: "123",
|
|
InstallationID: "456",
|
|
PrivateKeyPEM: "-----BEGIN RSA PRIVATE KEY-----\nnope\n-----END RSA PRIVATE KEY-----",
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected error for malformed private key")
|
|
}
|
|
}
|