package api import ( "crypto/x509" "encoding/json" "encoding/pem" "git.lumeweb.com/LumeWeb/gitea-github-proxy/config" "git.lumeweb.com/LumeWeb/gitea-github-proxy/db/model" "github.com/golang-jwt/jwt" "github.com/gorilla/mux" "go.uber.org/zap" "gorm.io/gorm" "net/http" "strconv" "time" ) type appInstallApi struct { config *config.Config logger *zap.Logger db *gorm.DB } func newAppInstallApi(cfg *config.Config, db *gorm.DB, logger *zap.Logger) *appInstallApi { return &appInstallApi{config: cfg, logger: logger, db: db} } func (a *appInstallApi) handlerAppGetAccessToken(w http.ResponseWriter, r *http.Request) { now := time.Now().Add(-30 * time.Second) expirationTime := now.Add(10 * time.Minute) appId := mux.Vars(r)["installation_id"] appIdInt, err := strconv.ParseInt(appId, 10, 64) if err != nil { http.Error(w, "Failed to parse app id", http.StatusBadRequest) a.logger.Error("Failed to parse app id", zap.Error(err)) return } appRecord := &model.Apps{} appRecord.ID = uint(appIdInt) if err := a.db.First(appRecord).Error; err != nil { http.Error(w, "Failed to find app", http.StatusNotFound) a.logger.Error("Failed to find app", zap.Error(err)) return } block, _ := pem.Decode([]byte(appRecord.PrivateKey)) if block == nil { http.Error(w, "Failed to parse PEM block containing the key", http.StatusInternalServerError) a.logger.Error("Failed to parse PEM block containing the key") return } privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { http.Error(w, "Failed to parse DER encoded private key", http.StatusInternalServerError) a.logger.Error("Failed to parse DER encoded private key", zap.Error(err)) } claims := jwt.MapClaims{ "iss": appId, "iat": now.Unix(), "exp": expirationTime, } token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) signedToken, err := token.SignedString(privateKey) if err != nil { http.Error(w, "Failed to sign token", http.StatusInternalServerError) a.logger.Error("Failed to sign token", zap.Error(err)) return } out := struct { Token string `json:"token"` ExpiresAt time.Time `json:"expires_at"` }{ Token: signedToken, ExpiresAt: expirationTime, } a.respond(w, http.StatusCreated, out) } func (r appInstallApi) respond(w http.ResponseWriter, status int, data interface{}) { jsonData, err := json.Marshal(data) if err != nil { http.Error(w, "Failed to marshal response", http.StatusInternalServerError) r.logger.Error("Failed to marshal response", zap.Error(err)) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) if data != nil { _, _ = w.Write(jsonData) } } func setupAppInstallRoutes(params RouteParams) { logger := params.Logger cfg := params.Config db := params.Db r := params.R appInstallApi := newAppInstallApi(cfg, db, logger) appRouter := r.PathPrefix("/api/v3/app").Subrouter() appRouter.Use(githubRestVerifyMiddleware(params.Db)) appRouter.Use(githubRestRequireAuthMiddleware(params.Config)) appRouter.HandleFunc("/installations/{installation_id}/access_tokens", appInstallApi.handlerAppGetAccessToken).Methods("POST").Name("app-install-get-access-token") }