package api import ( "bytes" "code.gitea.io/sdk/gitea" "context" "crypto/hmac" "crypto/sha256" "encoding/hex" "git.lumeweb.com/LumeWeb/gitea-github-proxy/config" "github.com/gorilla/mux" "go.uber.org/zap" "io" "net/http" "strings" ) const AUTHED_CONTEXT_KEY = "authed" const REDIRECT_AFTER_AUTH = "redirect-after-auth" const AuthCookieName = "auth-token" func findAuthToken(r *http.Request) string { authHeader := parseAuthTokenHeader(r.Header) if authHeader != "" { return authHeader } cookie := getCookie(r, AuthCookieName) if cookie != "" { return cookie } return r.FormValue(AuthCookieName) } func giteaOauthVerifyMiddleware(cfg *config.Config) mux.MiddlewareFunc { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { token := findAuthToken(r) if token == "" { addAuthStatusToRequestServ(false, r, w, next) return } client, err := getClient(ClientParams{ Config: cfg, AuthToken: token, }) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } _, _, err = client.AdminListUsers(gitea.AdminListUsersOptions{}) if err != nil { addAuthStatusToRequestServ(false, r, w, next) return } addAuthStatusToRequestServ(true, r, w, next) }) } } func requireAuthMiddleware(cfg *config.Config) mux.MiddlewareFunc { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { status := getAuthedStatusFromRequest(r) if !status { setCookie(w, REDIRECT_AFTER_AUTH, cfg.Domain, r.Referer(), 0, http.SameSiteLaxMode) http.Redirect(w, r, "/setup", http.StatusFound) return } next.ServeHTTP(w, r) }) } } func loggingMiddleware(logger *zap.Logger) mux.MiddlewareFunc { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Do stuff here logger.Debug("Request", zap.String("method", r.Method), zap.String("url", r.RequestURI)) // Call the next handler, which can be another middleware in the chain, or the final handler. next.ServeHTTP(w, r) }) } } func giteaVerifyWebhookMiddleware(cfg *config.Config, logger *zap.Logger) mux.MiddlewareFunc { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { signature := r.Header.Get("X-Gitea-Signature") if signature == "" { http.Error(w, "No signature provided", http.StatusBadRequest) return } decodedSignature, err := hex.DecodeString(signature) if err != nil { http.Error(w, "Error decoding signature", http.StatusBadRequest) logger.Error("Error decoding signature", zap.Error(err)) return } payload, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "Error reading body", http.StatusBadRequest) logger.Error("Error reading body", zap.Error(err)) return } defer func(body io.ReadCloser) { err := body.Close() if err != nil { logger.Error("Error closing body", zap.Error(err)) } }(r.Body) r.Body = io.NopCloser(bytes.NewBuffer(payload)) mac := hmac.New(sha256.New, []byte(cfg.GiteaWebHookSecret)) mac.Write(payload) expectedMAC := mac.Sum(nil) if !hmac.Equal(decodedSignature, expectedMAC) { http.Error(w, "Invalid signature", http.StatusForbidden) return } next.ServeHTTP(w, r) }) } } func addAuthStatusToRequestServ(status bool, r *http.Request, w http.ResponseWriter, next http.Handler) { ctx := context.WithValue(r.Context(), AUTHED_CONTEXT_KEY, status) r = r.WithContext(ctx) next.ServeHTTP(w, r) } func parseAuthTokenHeader(headers http.Header) string { authHeader := headers.Get("Authorization") if authHeader == "" { return "" } authHeader = strings.TrimPrefix(authHeader, "Bearer ") return authHeader } func getAuthedStatusFromRequest(r *http.Request) bool { authed, ok := r.Context().Value(AUTHED_CONTEXT_KEY).(bool) if !ok { return false } return authed }