snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s

This commit is contained in:
upstream-mirror
2026-09-18 01:22:23 +05:00
commit 7e385fc126
8391 changed files with 1766236 additions and 0 deletions

View File

@@ -0,0 +1,205 @@
package api
import (
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"github.com/cortezaproject/corteza/extra/server-discovery/pkg/options"
)
type (
credentials struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
expiresAt time.Time
authBaseUri string
key string
secret string
}
client struct {
baseUri string
discoveryBaseUrl string
credentials *credentials
}
ClientService interface {
HttpClient() *http.Client
Mappings() (*http.Request, error)
Feed(url.Values) (*http.Request, error)
Resources(string, url.Values) (*http.Request, error)
Namespaces() (*http.Request, error)
Modules(uint64) (*http.Request, error)
Request(string) (*http.Request, error)
Authenticate() error
}
)
func Client(opt options.CortezaOpt, key, secret string) (c *client, err error) {
c = &client{baseUri: opt.BaseUrl, discoveryBaseUrl: opt.DiscoveryUrl}
c.credentials = &credentials{
authBaseUri: opt.AuthUrl,
key: key,
secret: secret,
}
return c, err
}
func (*client) HttpClient() *http.Client {
return http.DefaultClient
}
func (c *client) Mappings() (*http.Request, error) {
return c.Request(fmt.Sprintf("%s/mappings/", c.discoveryBaseUrl))
}
func (c *client) Feed(qs url.Values) (*http.Request, error) {
query := ""
if len(qs.Encode()) > 0 {
query = fmt.Sprintf("from=%s", qs.Get("from"))
}
return c.Request(fmt.Sprintf("%s/feed/?%s", c.discoveryBaseUrl, query))
}
func (c *client) Resources(endpoint string, qs url.Values) (*http.Request, error) {
return c.Request(fmt.Sprintf("%s/resources/%s?%s", c.discoveryBaseUrl, strings.TrimLeft(endpoint, "/"), qs.Encode()))
}
func (c *client) Namespaces() (*http.Request, error) {
return c.Request(fmt.Sprintf("%s/api/compose/namespace/", c.baseUri))
}
func (c *client) Modules(namespaceID uint64) (*http.Request, error) {
return c.Request(fmt.Sprintf("%s/api/compose/namespace/%d/module/?sort=name+ASC", c.baseUri, namespaceID))
}
func (c *client) Request(endpoint string) (req *http.Request, err error) {
if err = c.Authenticate(); err != nil {
return
}
if req, err = http.NewRequest(http.MethodGet, endpoint, nil); err != nil {
return
}
req.Header.Set("User-Agent", "corteza-server-discovery/0.1")
req.Header.Set("Accept", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.credentials.AccessToken))
return
}
func (c *client) Authenticate() (err error) {
if c.credentials == nil {
return fmt.Errorf("missing credentials")
}
tokenNeedsRefresh := c.credentials.expiresAt.IsZero() || time.Now().After(c.credentials.expiresAt.Add(-60*time.Second))
if tokenNeedsRefresh {
newCredentials, err := c.authToken()
if err != nil {
return fmt.Errorf("failed to refresh token: %w", err)
}
c.credentials = newCredentials
}
return nil
}
func (c *client) authToken() (crd *credentials, err error) {
var (
req *http.Request
rsp *http.Response
form = url.Values{}
authBaseUri = c.credentials.authBaseUri
key = c.credentials.key
secret = c.credentials.secret
)
form.Set("grant_type", "client_credentials")
form.Set("scope", "profile api discovery")
req, err = http.NewRequest(http.MethodPost, authBaseUri+"/oauth2/token", strings.NewReader(form.Encode()))
if err != nil {
return
}
req.SetBasicAuth(key, secret)
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
//d, _ := httputil.DumpRequest(req, true)
//println(string(d))
rsp, err = c.HttpClient().Do(req)
if err != nil {
return
}
defer rsp.Body.Close()
crd = &credentials{
authBaseUri: authBaseUri,
key: key,
secret: secret,
}
if rsp.StatusCode != http.StatusOK {
aux := struct{ Error string }{}
if err = json.NewDecoder(rsp.Body).Decode(&aux); err != nil {
return
} else if aux.Error != "" {
return nil, fmt.Errorf(aux.Error)
} else {
return nil, fmt.Errorf("can not authenticate, unexpected error")
}
}
//d, _ := httputil.DumpResponse(rsp, true)
//println(string(d))
err = json.NewDecoder(rsp.Body).Decode(crd)
if err != nil {
return
}
crd.expiresAt, err = parseJWTExpiry(crd.AccessToken)
if err != nil {
crd.expiresAt = time.Now().Add(time.Second * time.Duration(crd.ExpiresIn))
}
return
}
func parseJWTExpiry(tokenString string) (time.Time, error) {
parts := strings.Split(tokenString, ".")
if len(parts) != 3 {
return time.Time{}, fmt.Errorf("invalid JWT format: expected 3 parts, got %d", len(parts))
}
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
return time.Time{}, fmt.Errorf("failed to decode JWT payload: %w", err)
}
// Parse exp claim
var claims struct {
Exp int64 `json:"exp"`
}
if err := json.Unmarshal(payload, &claims); err != nil {
return time.Time{}, fmt.Errorf("failed to unmarshal JWT claims: %w", err)
}
if claims.Exp == 0 {
return time.Time{}, fmt.Errorf("JWT missing exp claim")
}
return time.Unix(claims.Exp, 0), nil
}

View File

@@ -0,0 +1,33 @@
package server
import (
"net/http"
"github.com/go-chi/cors"
)
// Sets up default CORS rules to use as a middleware
func handleCORS(next http.Handler) http.Handler {
return cors.New(cors.Options{
AllowedOrigins: []string{
"http://*",
"https://*",
},
AllowedMethods: []string{
http.MethodHead,
http.MethodGet,
http.MethodPost,
http.MethodPut,
http.MethodPatch,
http.MethodDelete,
},
AllowedHeaders: []string{
"Accept",
"Authorization",
"Content-Type",
"X-CSRF-ID",
},
AllowCredentials: true,
MaxAge: 300, // Maximum value not ignored by any of major browsers
}).Handler(next)
}

View File

@@ -0,0 +1,53 @@
package server
import (
"fmt"
"net/http"
"github.com/go-chi/chi/v5"
"go.uber.org/atomic"
)
type (
// demux (demultiplexer) routes request to one of the underlying routers
// according to current state
demux struct {
state *atomic.Uint32
routers map[uint32]chi.Router
}
)
var _ http.Handler = &demux{}
func Demux(state uint32, r chi.Router) *demux {
return &demux{
state: atomic.NewUint32(state),
routers: map[uint32]chi.Router{state: r},
}
}
func (d *demux) State(s uint32) {
d.state.Store(s)
}
func (d *demux) Router(s uint32, r chi.Router) {
d.routers[s] = r
}
func (d *demux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer panicRecovery(r.Context(), w)
var (
state = d.state.Load()
router, exists = d.routers[state]
)
if !exists {
_, _ = fmt.Fprintf(w, "unconfigured request demultiplexor state (%d)", state)
w.WriteHeader(http.StatusInternalServerError)
return
}
router.ServeHTTP(w, r)
}

View File

@@ -0,0 +1,106 @@
package server
import (
"github.com/cortezaproject/corteza/extra/server-discovery/pkg/auth"
"github.com/cortezaproject/corteza/extra/server-discovery/pkg/healthcheck"
"github.com/cortezaproject/corteza/extra/server-discovery/pkg/options"
"github.com/cortezaproject/corteza/server/pkg/api"
"github.com/cortezaproject/corteza/server/pkg/version"
"github.com/go-chi/chi/v5"
"go.uber.org/zap"
"net/http"
"path"
"strings"
)
// routes used when server is in waiting mode
func waitingRoutes(log *zap.Logger, httpOpt options.HttpServerOpt) (r chi.Router) {
r = chi.NewRouter()
r.Use(handleCORS)
mountServiceHandlers(r, log, httpOpt, waiting)
return
}
// routes used when server in shutdown mode
func shutdownRoutes() (r chi.Router) {
r = chi.NewRouter()
return
}
// routes used when in active mode
func activeRoutes(log *zap.Logger, mountable []func(r chi.Router), envOpt options.EnvironmentOpt, httpOpt options.HttpServerOpt, searcherOpt options.SearcherOpt) (r chi.Router) {
r = chi.NewRouter()
r.Use(handleCORS)
r.Route("/"+strings.TrimPrefix(httpOpt.BaseUrl, "/"), func(r chi.Router) {
// Base middleware, CORS, RealIP, RequestID, context-logger
r.Use(BaseMiddleware(envOpt.IsProduction(), log)...)
// Verifies JWT in headers, cookies, ...
r.Use(auth.HttpTokenVerifier)
for _, mount := range mountable {
mount(r)
}
})
if httpOpt.BaseUrl != "/" {
r.Handle("/", http.RedirectHandler(httpOpt.BaseUrl, http.StatusTemporaryRedirect))
}
mountServiceHandlers(r, log, httpOpt, active)
return
}
func mountServiceHandlers(r chi.Router, log *zap.Logger, opt options.HttpServerOpt, state uint32) {
if opt.EnableVersionRoute {
mountVersionHandler(r, log, opt.BaseUrl)
}
if opt.EnableHealthcheckRoute {
mountHealthCheckHandler(r, log, opt.BaseUrl)
}
}
func mountVersionHandler(r chi.Router, log *zap.Logger, basePath string) {
var (
dPath = "/version"
sPath = path.Join(basePath, dPath)
handler = func(w http.ResponseWriter, r *http.Request) {
api.Send(w, r, struct {
BuildTime string `json:"buildTime"`
Version string `json:"version"`
}{version.BuildTime, version.Version})
}
)
r.Get(dPath, handler)
log.Debug("version route enabled: " + dPath)
if dPath != sPath {
r.Get(sPath, handler)
log.Debug("version route enabled: " + sPath)
}
}
func mountHealthCheckHandler(r chi.Router, log *zap.Logger, basePath string) {
// default & sub path for health-check endpoint
var (
dPath = "/healthcheck"
sPath = path.Join(basePath, dPath)
)
r.Get(dPath, healthcheck.HttpHandler())
log.Debug("health check route enabled: " + sPath)
if dPath != sPath {
r.Get(sPath, healthcheck.HttpHandler())
log.Debug("health check route enabled: " + sPath)
}
}

View File

@@ -0,0 +1,76 @@
package server
import (
"github.com/cortezaproject/corteza/server/pkg/logger"
"github.com/go-chi/chi/v5/middleware"
"go.uber.org/zap"
"net/http"
"strings"
"time"
)
// contextLogger middleware binds logger to request's context.
//
// This allows us to use logger from context (with requestID)
// inside our (generated) handlers and controllers
func contextLogger(log *zap.Logger) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
var requestID = middleware.GetReqID(req.Context())
w.Header().Add("X-Request-Id", requestID)
req = req.WithContext(logger.ContextWithValue(
req.Context(),
log.With(zap.String("requestID", requestID)).Named("rest"),
))
next.ServeHTTP(w, req)
})
}
}
// LogRequest middleware logs request details
//
// It uses logger from context, see contextLogger()
func LogRequest(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
var remote = req.RemoteAddr
if l := strings.LastIndex(remote, ":"); l > -1 {
remote = remote[:l]
}
logger.ContextValue(req.Context()).Info(
"HTTP request "+req.Method+" "+req.URL.Path,
zap.String("method", req.Method),
zap.String("path", req.URL.Path),
zap.Int64("size", req.ContentLength),
zap.String("remote", remote),
)
next.ServeHTTP(w, req)
})
}
// LogResponse middleware logs response details
//
// It uses logger from context, see contextLogger()
func LogResponse(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
wrapped := middleware.NewWrapResponseWriter(w, req.ProtoMajor)
t := time.Now()
defer func() {
logger.ContextValue(req.Context()).Info(
"HTTP response "+req.Method+" "+req.URL.Path,
zap.String("method", req.Method),
zap.String("path", req.URL.Path),
zap.Int("status", wrapped.Status()),
zap.Int("size", wrapped.BytesWritten()),
zap.Float64("duration", time.Since(t).Seconds()),
)
}()
next.ServeHTTP(wrapped, req)
})
}

View File

@@ -0,0 +1,49 @@
package server
import (
"context"
"net/http"
"os"
"runtime/debug"
"github.com/cortezaproject/corteza/server/pkg/api"
"github.com/cortezaproject/corteza/server/pkg/locale"
"github.com/cortezaproject/corteza/server/pkg/logger"
"github.com/go-chi/chi/v5/middleware"
"go.uber.org/zap"
)
func BaseMiddleware(isProduction bool, log *zap.Logger) []func(http.Handler) http.Handler {
return []func(http.Handler) http.Handler{
handleCORS,
locale.DetectLanguage(locale.Global()),
middleware.RealIP,
api.RemoteAddrToContext,
middleware.RequestID,
api.DebugToContext(isProduction),
contextLogger(log),
}
}
func panicRecovery(ctx context.Context, w http.ResponseWriter) {
if err := recover(); err != nil {
log := logger.ContextValue(ctx)
if err, ok := err.(error); ok {
log = log.With(zap.Error(err))
} else {
log = log.With(zap.Any("recover-value", err))
}
log.Debug("crashed on http request", zap.ByteString("stack", debug.Stack()))
w.WriteHeader(500)
if _, has := os.LookupEnv("DEBUG_DUMP_STACK_IN_RESPONSE"); has {
// Provide nice call stack on endpoint when
// we crash
_, _ = w.Write(debug.Stack())
}
return
}
}

View File

@@ -0,0 +1,107 @@
package server
import (
"context"
"net"
"net/http"
"github.com/cortezaproject/corteza/extra/server-discovery/pkg/options"
"github.com/go-chi/chi/v5"
"go.uber.org/zap"
)
type (
server struct {
log *zap.Logger
httpOpt options.HttpServerOpt
waitForOpt options.WaitForOpt
environmentOpt options.EnvironmentOpt
searcherOpt options.SearcherOpt
endpoints []func(r chi.Router)
demux *demux
}
)
const (
waiting uint32 = iota
active
shutdown
)
// New initializes new HTTP server with special powers
// Server is started as early as possible and with a special request handler
// that demultiplexes request to one of the configured routers according to the server state.
//
// Waiting state
// This is initial state that with some simple route handlers:
// - /version
// - /healthcheck
// - /healthcheck
func New(log *zap.Logger, envOpt options.EnvironmentOpt, httpOpt options.HttpServerOpt, waitForOpt options.WaitForOpt, searcherOpt options.SearcherOpt) *server {
s := &server{
endpoints: make([]func(r chi.Router), 0),
log: log.Named("http"),
environmentOpt: envOpt,
httpOpt: httpOpt,
searcherOpt: searcherOpt,
waitForOpt: waitForOpt,
}
s.demux = Demux(waiting, waitingRoutes(s.log.Named("waiting"), s.httpOpt))
s.demux.Router(shutdown, shutdownRoutes())
return s
}
// Activate reconfigures server to use active routes
func (s *server) Activate(mm ...func(chi.Router)) {
s.demux.Router(active, activeRoutes(s.log, mm, s.environmentOpt, s.httpOpt, s.searcherOpt))
s.log.Debug("entering active state")
s.demux.State(active)
}
// Shutdown reconfigures server to use shutdown routes
func (s *server) Shutdown() {
s.log.Debug("entering shutdown state")
s.demux.State(shutdown)
}
func (s server) Serve(ctx context.Context) {
s.log.Info(
"starting HTTP server",
zap.String("path-prefix", s.httpOpt.BaseUrl),
zap.String("address", s.httpOpt.Addr),
)
listener, err := net.Listen("tcp", s.httpOpt.Addr)
if err != nil {
s.log.Error("cannot start server", zap.Error(err))
return
}
go func() {
srv := http.Server{
Handler: s.demux,
// use root context as server's base context and as a basis for
// context for all requests
// this enables us to send cancellation down to every request
BaseContext: func(listener net.Listener) context.Context { return ctx },
}
err = srv.Serve(listener)
}()
<-ctx.Done()
if err == nil {
err = ctx.Err()
if err == context.Canceled {
err = nil
}
}
s.log.Info("HTTP server stopped", zap.Error(err))
}

View File

@@ -0,0 +1,34 @@
package server
import (
"context"
"github.com/go-chi/chi/v5"
"go.uber.org/zap"
"net"
"net/http"
"os"
)
// WaitFor sets up a simple status page, delays execution and probes services
func (s server) WaitFor(ctx context.Context) {
var (
err error
)
// Set up a simple HTTP server that will inform the impatient users
listener, err := net.Listen("tcp", s.httpOpt.Addr)
if err != nil {
s.log.Error("cannot start server", zap.Error(err))
os.Exit(1)
}
defer listener.Close()
go func() {
router := chi.NewRouter()
router.Get("/*", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusPreconditionFailed)
w.Write([]byte("waiting for services..."))
})
_ = http.Serve(listener, router)
}()
<-ctx.Done()
}