120 lines
3.1 KiB
Go
120 lines
3.1 KiB
Go
|
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)["app_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.SigningMethodHS256, 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("/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")
|
||
|
}
|