Skip to content

Instantly share code, notes, and snippets.

@lukehinds
Last active January 11, 2024 19:58
Show Gist options
  • Save lukehinds/5076f315be05cb1dc4085e988edbb759 to your computer and use it in GitHub Desktop.
Save lukehinds/5076f315be05cb1dc4085e988edbb759 to your computer and use it in GitHub Desktop.
package main
import (
"context"
"crypto/rsa"
"encoding/json"
"fmt"
"net/http"
"os"
"strconv"
"time"
"github.com/golang-jwt/jwt/v4"
"github.com/google/go-github/v58/github"
"golang.org/x/oauth2"
)
const (
githubAppID = "792289"
privateKeyPath = "./private.pem"
webhookSecret = "secret"
)
func createJWT(appID string, privateKey *rsa.PrivateKey) (string, error) {
appIDInt, err := strconv.ParseInt(appID, 10, 64)
if err != nil {
return "", fmt.Errorf("unable to parse appID to integer: %v", err)
}
// Create the Claims
claims := jwt.MapClaims{
"iat": time.Now().Unix(),
"exp": time.Now().Add(time.Minute * 10).Unix(),
"iss": appIDInt,
}
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
jwtToken, err := token.SignedString(privateKey)
if err != nil {
return "", fmt.Errorf("unable to sign JWT token: %v", err)
}
return jwtToken, nil
}
func handleInstallationEvent(installationID int64, privateKey *rsa.PrivateKey) {
jwtToken, err := createJWT(githubAppID, privateKey)
if err != nil {
fmt.Println("Error creating JWT:", err)
return
}
// Use JWT to authenticate with GitHub API for installation access token
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: jwtToken})
tc := oauth2.NewClient(context.Background(), ts)
appClient := github.NewClient(tc)
// Grab the installation access token
token, _, err := appClient.Apps.CreateInstallationToken(context.Background(), installationID, nil)
if err != nil {
fmt.Println("Error creating installation token:", err)
return
}
fmt.Println("Installation token created")
fmt.Println("Token:", *token.Token)
fmt.Println("Expires at:", token.ExpiresAt)
fmt.Println("Permissions:")
fmt.Println(" Contents:", *token.Permissions.Contents)
fmt.Println(" Metadata:", *token.Permissions.Metadata)
// Use installation token to interact with GitHub API
installationTS := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: *token.Token})
installationClient := github.NewClient(oauth2.NewClient(context.Background(), installationTS))
// List some repos
repos, _, err := installationClient.Apps.ListRepos(context.Background(), nil)
if err != nil {
fmt.Println("Error listing repos:", err)
return
}
fmt.Println("Repos:")
for _, repo := range repos.Repositories {
fmt.Println(*repo.FullName)
}
}
func handleWebhook(w http.ResponseWriter, r *http.Request) {
var payload struct {
Action string `json:"action"`
Installation struct {
ID int64 `json:"id"`
} `json:"installation"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
http.Error(w, "Error decoding JSON", http.StatusBadRequest)
return
}
if payload.Action == "created" {
privateKeyBytes, err := os.ReadFile(privateKeyPath)
if err != nil {
fmt.Fprintf(w, "Error reading private key: %v", err)
return
}
privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(privateKeyBytes)
if err != nil {
fmt.Fprintf(w, "Error parsing private key: %v", err)
return
}
handleInstallationEvent(payload.Installation.ID, privateKey)
}
fmt.Fprintf(w, "Webhook received")
}
func handleHealthCheck(w http.ResponseWriter, r *http.Request) {
fmt.Println("Health check")
fmt.Fprintf(w, "OK")
}
func main() {
http.HandleFunc("/api/v1/github/hook", handleWebhook)
http.HandleFunc("/healthz", handleHealthCheck)
http.ListenAndServe(":8080", nil)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment