diff --git a/github/credentials.go b/github/credentials.go new file mode 100644 index 00000000000..ff6e6f6913a --- /dev/null +++ b/github/credentials.go @@ -0,0 +1,37 @@ +// Copyright 2025 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" +) + +// CredentialsService handles credentials related methods of the GitHub API. +type CredentialsService service + +// revokeCredentialsRequest represents the request body for revoking credentials. +type revokeCredentialsRequest struct { + // The list of credential strings (tokens) to revoke. + Credentials []string `json:"credentials"` +} + +// Revoke revokes a list of credentials. +// +// GitHub API docs: https://docs.github.com/rest/credentials/revoke#revoke-a-list-of-credentials +// +//meta:operation POST /credentials/revoke +func (s *CredentialsService) Revoke(ctx context.Context, credentials []string) (*Response, error) { + u := "credentials/revoke" + + reqBody := &revokeCredentialsRequest{Credentials: credentials} + + req, err := s.client.NewRequest("POST", u, reqBody) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} diff --git a/github/credentials_test.go b/github/credentials_test.go new file mode 100644 index 00000000000..997c33fc9ec --- /dev/null +++ b/github/credentials_test.go @@ -0,0 +1,48 @@ +// Copyright 2025 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "errors" + "net/http" + "testing" +) + +func TestCredentialsService_Revoke(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + creds := []string{ + "ghp_1234567890abcdef1234567890abcdef12345678", + "ghp_abcdef1234567890abcdef1234567890abcdef12", + } + expectedBodyBytes, _ := json.Marshal(map[string][]string{"credentials": creds}) + expectedBody := string(expectedBodyBytes) + "\n" + + mux.HandleFunc("/credentials/revoke", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testBody(t, r, expectedBody) + w.WriteHeader(http.StatusAccepted) + }) + + ctx := t.Context() + resp, err := client.Credentials.Revoke(ctx, creds) + if !errors.As(err, new(*AcceptedError)) { + t.Errorf("Credentials.Revoke returned error: %v (want AcceptedError)", err) + } + if resp == nil { + t.Fatal("Credentials.Revoke returned nil response") + } + if resp.StatusCode != http.StatusAccepted { + t.Errorf("Credentials.Revoke returned status %v, want %v", resp.StatusCode, http.StatusAccepted) + } + + const methodName = "Revoke" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + return client.Credentials.Revoke(ctx, []string{"a"}) + }) +} diff --git a/github/github.go b/github/github.go index eba55176108..b9efa3b4e89 100644 --- a/github/github.go +++ b/github/github.go @@ -203,6 +203,7 @@ type Client struct { CodesOfConduct *CodesOfConductService Codespaces *CodespacesService Copilot *CopilotService + Credentials *CredentialsService Dependabot *DependabotService DependencyGraph *DependencyGraphService Emojis *EmojisService @@ -445,6 +446,7 @@ func (c *Client) initialize() { c.Codespaces = (*CodespacesService)(&c.common) c.CodesOfConduct = (*CodesOfConductService)(&c.common) c.Copilot = (*CopilotService)(&c.common) + c.Credentials = (*CredentialsService)(&c.common) c.Dependabot = (*DependabotService)(&c.common) c.DependencyGraph = (*DependencyGraphService)(&c.common) c.Emojis = (*EmojisService)(&c.common)