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

56
server/app/app.cue Normal file
View File

@@ -0,0 +1,56 @@
package app
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
"github.com/cortezaproject/corteza/server/app/options"
"github.com/cortezaproject/corteza/server/system"
"github.com/cortezaproject/corteza/server/compose"
"github.com/cortezaproject/corteza/server/automation"
"github.com/cortezaproject/corteza/server/federation"
)
corteza: schema.#platform & {
"ident": "corteza"
"options": [
options.DB,
options.HTTPClient,
options.HTTPServer,
options.RBAC,
options.SCIM,
options.SMTP,
options.actionLog,
options.apigw,
options.auth,
options.corredor,
options.environment,
options.eventbus,
options.federation,
options.limit,
options.locale,
options.log,
options.messagebus,
options.monitor,
options.objectStore,
options.provision,
options.sentry,
options.template,
options.upgrade,
options.waitFor,
options.websocket,
options.workflow,
options.discovery,
options.attachment,
options.webapp,
]
// platform resources
"resources": resources
"components": [
system.component,
compose.component,
automation.component,
federation.component,
]
}

92
server/app/app.go Normal file
View File

@@ -0,0 +1,92 @@
package app
import (
"context"
"net/http"
"github.com/cortezaproject/corteza/server/auth/settings"
"github.com/cortezaproject/corteza/server/pkg/logger"
"github.com/cortezaproject/corteza/server/pkg/options"
"github.com/cortezaproject/corteza/server/store"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/go-chi/chi/v5"
"github.com/go-oauth2/oauth2/v4"
"github.com/spf13/cobra"
"go.uber.org/zap"
"google.golang.org/grpc"
)
type (
httpApiServer interface {
Serve(ctx context.Context)
Activate(mm ...func(chi.Router))
Shutdown()
}
grpcServer interface {
RegisterServices(func(server *grpc.Server))
Serve(ctx context.Context)
}
wsServer interface {
MountRoutes(chi.Router)
Send(kind string, payload interface{}, userIDs ...uint64) error
}
authServicer interface {
MountHttpRoutes(string, chi.Router)
WellKnownOpenIDConfiguration() http.HandlerFunc
UpdateSettings(*settings.Settings)
Watch(ctx context.Context)
}
apigwServicer interface {
http.Handler
}
CortezaApp struct {
Opt *options.Options
lvl int
Log *zap.Logger
// Store interface
//
// Just a blank interface{} because we want to avoid generating
// whole store interface (as we do for other packages).
//
// Value will be type-casted when assigned to sys/msg/cmp services
// with warnings when incompatible
Store store.Storer
// CLI Commands
Command *cobra.Command
oa2m oauth2.Manager
DefaultAuthClient *types.AuthClient
// Servers
HttpServer httpApiServer
GrpcServer grpcServer
WsServer wsServer
AuthService authServicer
ApigwService apigwServicer
systemEntitiesInitialized bool
}
)
func New() *CortezaApp {
app := &CortezaApp{
lvl: bootLevelWaiting,
Log: logger.Default(),
}
app.InitCLI()
return app
}
func (app *CortezaApp) Options() *options.Options {
return app.Opt
}

159
server/app/boot_auth.go Normal file
View File

@@ -0,0 +1,159 @@
package app
import (
"context"
"fmt"
authHandlers "github.com/cortezaproject/corteza/server/auth/handlers"
"github.com/cortezaproject/corteza/server/auth/oauth2"
"github.com/cortezaproject/corteza/server/pkg/auth"
"github.com/cortezaproject/corteza/server/pkg/id"
"github.com/cortezaproject/corteza/server/pkg/options"
"github.com/cortezaproject/corteza/server/store"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/lestrrat-go/jwx/jwa"
"github.com/lestrrat-go/jwx/jwk"
"github.com/lestrrat-go/jwx/jwt"
"go.uber.org/zap"
"io/ioutil"
"strings"
)
func (app *CortezaApp) initAuth(ctx context.Context) (err error) {
log := app.Log.Named("auth")
if app.Opt.Auth.DefaultClient != "" {
// default client will help streamline authorization with default clients
app.DefaultAuthClient, err = store.LookupAuthClientByHandle(ctx, app.Store, app.Opt.Auth.DefaultClient)
if err != nil {
return fmt.Errorf("cannot load default client: %w", err)
}
}
app.oa2m = oauth2.NewManager(
app.Opt.Auth,
app.Log,
oauth2.NewClientStore(app.Store, app.DefaultAuthClient),
oauth2.NewTokenStore(app.Store),
)
// set base path for links&routes in auth server
authHandlers.BasePath = app.Opt.HTTPServer.BaseUrl
// set webapp base path in auth server
// @todo refactor, consider passing this into the functions that need it instead of setting it globally
authHandlers.WebappBasePath = app.Opt.HTTPServer.WebappBaseUrl
auth.DefaultSigner = auth.HmacSigner(app.Opt.Auth.Secret)
if auth.HttpTokenVerifier, err = auth.TokenVerifierMiddlewareWithSecretSigner(app.Opt.Auth.Secret); err != nil {
return fmt.Errorf("could not set token verifier")
}
// create token signature function from AUTH_ options
// and pass it on to the token issuer
alg, key, err := prepareSignatureFnParams(app.Opt.Auth)
if err != nil {
return fmt.Errorf("could not initialize token signer: %w", err)
}
log.Info(
"initializing JWT and authentication procedures",
zap.Stringer("algoritm", alg),
)
// construct token issuer with algorithm, secrets,
auth.TokenIssuer, err = auth.NewTokenIssuer(
auth.WithSigner(func(t jwt.Token) ([]byte, error) {
return jwt.Sign(t, alg, key)
}),
// @todo implement configurable issuer claim
//auth.WithDefaultIssuer(app.Opt.Auth.TokenClaimIssuer),
auth.WithDefaultExpiration(app.Opt.Auth.AccessTokenLifetime),
auth.WithDefaultClientID(app.DefaultAuthClient.ID),
auth.WithLookup(func(ctx context.Context, accessToken string) (err error) {
_, err = store.LookupAuthOa2tokenByAccess(ctx, app.Store, accessToken)
return err
}),
auth.WithStore(func(ctx context.Context, req auth.TokenRequest) error {
var (
eti = auth.GetExtraReqInfoFromContext(ctx)
createdAt = req.IssuedAt
oa2t = &types.AuthOa2token{
ID: id.Next(),
Access: req.AccessToken,
Refresh: req.RefreshToken,
CreatedAt: createdAt,
RemoteAddr: eti.RemoteAddr,
UserAgent: eti.UserAgent,
ClientID: req.ClientID,
UserID: req.UserID,
ExpiresAt: createdAt.Add(req.Expiration),
}
)
return store.CreateAuthOa2token(ctx, app.Store, oa2t)
}),
)
if err != nil {
return fmt.Errorf("could not initialize token issuer: %w", err)
}
return
}
// helper function that loads and/or parses private keys and initializes JWT signer function
// from the given arguments
func prepareSignatureFnParams(opt options.AuthOpt) (alg jwa.SignatureAlgorithm, key any, err error) {
alg = jwa.SignatureAlgorithm(opt.JwtAlgorithm)
switch alg {
case jwa.HS256, jwa.HS384, jwa.HS512:
// expecting secret to be set
if len(opt.Secret) == 0 {
return alg, nil, fmt.Errorf("token secret missing")
}
key = []byte(opt.Secret)
case
jwa.PS256, jwa.PS384, jwa.PS512,
jwa.RS256, jwa.RS384, jwa.RS512:
if len(opt.JwtKey) == 0 {
return alg, nil, fmt.Errorf("token key missing")
}
// if given key dos not begins with "-----BEGIN"
// assume it's path to a file and load contents of that file
if !strings.HasPrefix(opt.JwtKey, "-----BEGIN") {
var (
keyFile = opt.JwtKey
b []byte
)
if b, err = ioutil.ReadFile(keyFile); err != nil {
return alg, nil, fmt.Errorf("could not read key file: %w", err)
}
// overwrite th input and load
opt.JwtKey = string(b)
// recheck contents of the key
if !strings.HasPrefix(opt.JwtKey, "-----BEGIN") {
return alg, nil, fmt.Errorf("file %q does not contain a valid private key", keyFile)
}
}
// generates pem.Private from the kInput string
key, err = jwk.ParseKey([]byte(opt.JwtKey), jwk.WithPEM(true))
case "":
// should be caught by options init procedure and set to default,
// but you never know...
err = fmt.Errorf("token signature algorithm empty or missing")
default:
err = fmt.Errorf("token signature algorithm %q not supported", alg)
}
return
}

View File

@@ -0,0 +1,143 @@
package app
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"github.com/cortezaproject/corteza/server/pkg/options"
"github.com/lestrrat-go/jwx/jwa"
"github.com/stretchr/testify/require"
"testing"
)
func TestPrepareSignatureFnParams(t *testing.T) {
type (
test struct {
name string
opt options.AuthOpt
err error
}
)
var (
privateKey = string(genKey())
)
tests := []test{
{
name: "empty algo",
opt: options.AuthOpt{JwtKey: "foobar"},
err: fmt.Errorf("token signature algorithm empty or missing"),
},
{
name: "unknown algo",
opt: options.AuthOpt{JwtAlgorithm: "foobar", JwtKey: "foobar"},
err: fmt.Errorf("token signature algorithm \"foobar\" not supported"),
},
{
name: "empty key",
opt: options.AuthOpt{JwtAlgorithm: jwa.HS256.String()},
err: fmt.Errorf("token secret missing"),
},
{
name: "empty key",
opt: options.AuthOpt{JwtAlgorithm: jwa.PS256.String()},
err: fmt.Errorf("token key missing"),
},
{
// "shared secret" string
name: "HS256",
opt: options.AuthOpt{JwtAlgorithm: jwa.HS256.String(), Secret: "test key"},
},
{
// "shared secret" string
name: "HS384",
opt: options.AuthOpt{JwtAlgorithm: jwa.HS384.String(), Secret: "test key"},
},
{
// "shared secret" string
name: "HS512",
opt: options.AuthOpt{JwtAlgorithm: jwa.HS512.String(), Secret: "test key"},
},
{
// requires private key
name: "PS256",
opt: options.AuthOpt{JwtAlgorithm: jwa.PS256.String(), JwtKey: privateKey},
},
{
// requires private key
name: "PS384",
opt: options.AuthOpt{JwtAlgorithm: jwa.PS384.String(), JwtKey: privateKey},
},
{
// requires private key
name: "PS512",
opt: options.AuthOpt{JwtAlgorithm: jwa.PS512.String(), JwtKey: privateKey},
},
{
// requires private key
name: "RS256",
opt: options.AuthOpt{JwtAlgorithm: jwa.RS256.String(), JwtKey: privateKey},
},
{
// requires private key
name: "RS384",
opt: options.AuthOpt{JwtAlgorithm: jwa.RS384.String(), JwtKey: privateKey},
},
{
// requires private key
name: "RS512",
opt: options.AuthOpt{JwtAlgorithm: jwa.RS512.String(), JwtKey: privateKey},
},
{
// requires private key
name: "RS512 from a file",
opt: options.AuthOpt{JwtAlgorithm: jwa.RS512.String(), JwtKey: "test_files/key.pem"},
},
{
// requires private key
name: "RS512 from a non-existing file",
opt: options.AuthOpt{JwtAlgorithm: jwa.RS512.String(), JwtKey: "test_files/not-here.pem"},
err: fmt.Errorf("could not read key file: open test_files/not-here.pem: no such file or directory"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var (
req = require.New(t)
)
alg, key, err := prepareSignatureFnParams(tt.opt)
if tt.err != nil {
req.EqualError(err, tt.err.Error())
return
}
req.NoError(err)
req.NotEmpty(alg)
req.NotEmpty(key)
})
}
}
func genKey() []byte {
bitSize := 4096
// Generate RSA key.
key, err := rsa.GenerateKey(rand.Reader, bitSize)
if err != nil {
panic(err)
}
// Encode private key to PKCS#1 ASN.1 PEM.
return pem.EncodeToMemory(
&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(key),
},
)
}

85
server/app/boot_dal.go Normal file
View File

@@ -0,0 +1,85 @@
package app
import (
"context"
"fmt"
"time"
"github.com/cortezaproject/corteza/server/pkg/dal"
"github.com/cortezaproject/corteza/server/pkg/id"
"github.com/cortezaproject/corteza/server/store"
"github.com/cortezaproject/corteza/server/system/service"
"github.com/cortezaproject/corteza/server/system/types"
"go.uber.org/zap"
)
func (app *CortezaApp) initDAL(ctx context.Context, log *zap.Logger) (err error) {
// no-op - if DAL is already initialized
if dal.Initialized() {
return
}
// Verify that primary store is connected
// or return error
if app.Store == nil {
return fmt.Errorf("primary store not connected")
}
var (
conn *types.DalConnection
cw *dal.ConnectionWrap
)
// load/create primary connection
if conn, err = provisionPrimaryDalConnection(ctx, app.Store); err != nil {
return fmt.Errorf("could not provision primary connection: %w", err)
}
// Convert connection to dal.ConnectionWrap
if cw, err = service.MakeDalConnection(conn, app.Store.ToDalConn()); err != nil {
return fmt.Errorf("could not convert connection: %w", err)
}
// Init DAL and prepare default connection
dal.SetGlobal(dal.New(log.Named("dal"), app.Opt.Environment.IsDevelopment()))
if err = dal.Service().ReplaceConnection(ctx, cw, true); err != nil {
return fmt.Errorf("could not set primary connection: %w", err)
}
return
}
// Creates entry for primary connection in the store
func provisionPrimaryDalConnection(ctx context.Context, s store.DalConnections) (conn *types.DalConnection, err error) {
conn, err = store.LookupDalConnectionByHandle(ctx, s, types.DalPrimaryConnectionHandle)
if err != nil && err != store.ErrNotFound {
return
}
// Already exists
if conn != nil {
return
}
conn = &types.DalConnection{
// Using id.Next since we dropped "special" ids a while ago.
// If needed, use the handle
ID: id.Next(),
Handle: types.DalPrimaryConnectionHandle,
Type: types.DalPrimaryConnectionResourceType,
Meta: types.ConnectionMeta{
Name: "Primary Database",
},
Config: types.ConnectionConfig{
DAL: &types.ConnectionConfigDAL{
ModelIdent: "compose_record",
},
},
CreatedAt: time.Now(),
}
return conn, store.CreateDalConnection(ctx, s, conn)
}

53
server/app/boot_envoy.go Normal file
View File

@@ -0,0 +1,53 @@
package app
import (
"context"
automationEnvoy "github.com/cortezaproject/corteza/server/automation/envoy"
composeEnvoy "github.com/cortezaproject/corteza/server/compose/envoy"
"github.com/cortezaproject/corteza/server/pkg/envoyx"
systemEnvoy "github.com/cortezaproject/corteza/server/system/envoy"
"go.uber.org/zap"
)
func (app *CortezaApp) initEnvoy(ctx context.Context, log *zap.Logger) (err error) {
// no-op - if envoy is already initialized
if envoyx.Initialized() {
return
}
// Init envoy
svc := envoyx.New()
envoyx.SetGlobal(svc)
// Register all component decoders
svc.AddDecoder(envoyx.DecodeTypeURI,
composeEnvoy.YamlDecoder{},
systemEnvoy.YamlDecoder{},
automationEnvoy.YamlDecoder{},
)
svc.AddDecoder(envoyx.DecodeTypeStore,
composeEnvoy.StoreDecoder{},
systemEnvoy.StoreDecoder{},
automationEnvoy.StoreDecoder{},
)
// Register all component encoders
svc.AddEncoder(envoyx.EncodeTypeIo,
composeEnvoy.YamlEncoder{},
systemEnvoy.YamlEncoder{},
automationEnvoy.YamlEncoder{},
)
svc.AddEncoder(envoyx.EncodeTypeStore,
composeEnvoy.StoreEncoder{},
systemEnvoy.StoreEncoder{},
automationEnvoy.StoreEncoder{},
)
// - datasource encoders
svc.AddEncoder(envoyx.EncodeTypeIo,
composeEnvoy.CsvEncoder{},
)
return
}

14
server/app/boot_expr.go Normal file
View File

@@ -0,0 +1,14 @@
package app
import (
"context"
"github.com/cortezaproject/corteza/server/pkg/expr"
"github.com/cortezaproject/corteza/server/pkg/rbac"
)
func (app *CortezaApp) InitExpr(ctx context.Context) (err error) {
expr.Init(rbac.AllFunctions, expr.AllFunctions)
return
}

1087
server/app/boot_levels.go Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,148 @@
package app
import (
"context"
"net/http"
"testing"
"github.com/cortezaproject/corteza/server/auth/external"
authSettings "github.com/cortezaproject/corteza/server/auth/settings"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/go-chi/chi/v5"
"github.com/stretchr/testify/require"
)
type (
authSettingsUpdaterMockedAuthService struct {
settings *authSettings.Settings
}
)
func (*authSettingsUpdaterMockedAuthService) MountHttpRoutes(string, chi.Router) {}
func (*authSettingsUpdaterMockedAuthService) WellKnownOpenIDConfiguration() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {}
}
func (*authSettingsUpdaterMockedAuthService) Watch(context.Context) {}
func (m *authSettingsUpdaterMockedAuthService) UpdateSettings(settings *authSettings.Settings) {
m.settings = settings
}
func Test_updateAuthSettings(t *testing.T) {
cc := []struct {
name string
in types.AppSettings
out authSettings.Settings
}{
{
"enabled internal auth",
func() (s types.AppSettings) {
s.Auth.Internal.Enabled = true
return
}(),
func() (s authSettings.Settings) {
s.LocalEnabled = true
return
}(),
},
{
"add external providers",
func() (s types.AppSettings) {
s.Auth.External.Providers = []*types.ExternalAuthProvider{
{ // skip
Enabled: false,
Handle: "disabled",
Key: "key",
Secret: "sec",
},
{ // skip
Enabled: true,
Handle: "malformed",
Key: "",
Secret: "",
},
{ // skip
Enabled: true,
Handle: external.OIDC_PROVIDER_PREFIX + "without-issuer",
Key: "key",
Secret: "sec",
},
{ // add
Enabled: true,
Handle: external.OIDC_PROVIDER_PREFIX + "with-issuer",
IssuerUrl: "issuer",
Key: "key",
Secret: "sec",
},
{ // add (w/o issuer)
Enabled: true,
Handle: "google",
Key: "key",
Secret: "sec",
},
{ // add
Enabled: true,
Handle: "github",
Key: "key",
Secret: "sec",
},
{ // skip
Enabled: false,
Handle: "linkedin",
Key: "key",
Secret: "sec",
},
{ // add (used only for API integration, not for identity)
Enabled: true,
Handle: "nylas",
Key: "key",
Secret: "sec",
Usage: []string{types.ExternalProviderUsageAPI},
},
}
return
}(),
func() (s authSettings.Settings) {
s.Providers = []authSettings.Provider{
{
Handle: external.OIDC_PROVIDER_PREFIX + "with-issuer",
IssuerUrl: "issuer",
Key: "key",
Secret: "sec",
Usage: []string{types.ExternalProviderUsageIdentity},
},
{
Handle: "google",
Key: "key",
Secret: "sec",
Usage: []string{types.ExternalProviderUsageIdentity},
},
{
Handle: "github",
Key: "key",
Secret: "sec",
Usage: []string{types.ExternalProviderUsageIdentity},
},
{
Handle: "nylas",
Key: "key",
Secret: "sec",
Usage: []string{types.ExternalProviderUsageAPI},
},
}
return
}(),
},
}
for _, c := range cc {
t.Run(c.name, func(t *testing.T) {
var (
req = require.New(t)
m = &authSettingsUpdaterMockedAuthService{}
)
updateAuthSettings(m, &c.in)
req.Equal(*m.settings, c.out)
})
}
}

158
server/app/cli.go Normal file
View File

@@ -0,0 +1,158 @@
package app
import (
"context"
"fmt"
"sync"
composeCommands "github.com/cortezaproject/corteza/server/compose/commands"
authCommands "github.com/cortezaproject/corteza/server/auth/commands"
federationCommands "github.com/cortezaproject/corteza/server/federation/commands"
"github.com/cortezaproject/corteza/server/pkg/actionlog"
"github.com/cortezaproject/corteza/server/pkg/api/server"
"github.com/cortezaproject/corteza/server/pkg/cli"
"github.com/cortezaproject/corteza/server/pkg/dal"
"github.com/cortezaproject/corteza/server/pkg/envoyx"
"github.com/cortezaproject/corteza/server/pkg/id"
"github.com/cortezaproject/corteza/server/pkg/options"
"github.com/cortezaproject/corteza/server/store"
systemCommands "github.com/cortezaproject/corteza/server/system/commands"
"go.uber.org/zap"
)
// InitCLI function initializes basic Corteza subsystems
// and sets-up the command line interface
func (app *CortezaApp) InitCLI() {
var (
ctx = cli.Context()
// path to all environmental files (or locations with .env file)
// filled from flag values
envs []string
)
id.Init(ctx)
app.Command = cli.RootCommand(func() error {
return nil
})
// Environmental variables (from the env, files, see cli.LoadEnv) MUST be
// loaded at this point!
if len(envs) == 0 {
envs = []string{"."}
}
// Loading env after the rootcommand is added, so as not to break
// the Execute() if there is an error
if err := cli.LoadEnv(envs...); err != nil {
app.Log.Error("failed to load environmental variables", zap.Error(err))
return
}
app.Opt = options.Init()
app.Command.Flags().StringSliceVar(&envs, "env-file", nil,
"Load environmental variables from files and directories containing .env file.\n"+
"Values from loaded files DO NOT override existing variables from the environment.\n"+
"This flag can be used multiple times, values are loaded from all provided locations.\n"+
"If no paths are provided, corteza loads .env file from the current directory (equivalent to --env-file .)")
serveCmd := cli.ServeCommand(func() (err error) {
wg := &sync.WaitGroup{}
{ // @todo refactor wait-for out of HTTP API server.
// test http server options first
if err = server.Test(app.Opt); err != nil {
return err
}
app.HttpServer = server.New(app.Log, app.Opt)
wg.Add(1)
go func() {
app.HttpServer.Serve(actionlog.RequestOriginToContext(ctx, actionlog.RequestOrigin_API_REST))
wg.Done()
}()
}
{
// @todo add other server/listeners here...
//wg.Add(1)
//go func(ctx context.Context) {
// grpcApi.Serve(actionlog.RequestOriginToContext(ctx, actionlog.RequestOrigin_API_GRPC))
// wg.Done()
//}(ctx)
}
if err = app.Activate(ctx); err != nil {
cli.HandleError(fmt.Errorf("could not start Corteza: %w", err))
return nil
}
app.HttpServer.Activate(app.mountHttpRoutes)
// Wait for all servers to be done
wg.Wait()
app.HttpServer.Shutdown()
return nil
})
upgradeCmd := cli.UpgradeCommand(func() (err error) {
if err = app.InitStore(ctx); err != nil {
return
}
return
})
provisionCmd := cli.ProvisionCommand(func() (err error) {
if err = app.Provision(ctx); err != nil {
return
}
return
})
storeInit := func(ctx context.Context) (store.Storer, error) {
err := app.InitStore(ctx)
return app.Store, err
}
dalInit := func(ctx context.Context) (dal.FullService, error) {
err := app.initDAL(ctx, app.Log)
return dal.Service(), err
}
envoyInit := func(ctx context.Context) (svc *envoyx.Service, err error) {
err = app.initEnvoy(ctx, app.Log)
return envoyx.Global(), err
}
app.Command.AddCommand(
systemCommands.Users(ctx, app),
systemCommands.Roles(ctx, app),
systemCommands.RBAC(ctx, storeInit),
systemCommands.Sink(ctx, app),
systemCommands.Settings(ctx, app),
systemCommands.Import(ctx, storeInit, dalInit, envoyInit),
systemCommands.Export(ctx, storeInit, dalInit, envoyInit),
serveCmd,
upgradeCmd,
provisionCmd,
authCommands.Command(ctx, app, storeInit),
federationCommands.Sync(ctx, app),
composeCommands.Base(ctx, app),
cli.EnvCommand(),
cli.VersionCommand(),
)
}
func (app *CortezaApp) Execute() error {
return app.Command.Execute()
}

View File

@@ -0,0 +1,43 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
RBAC: schema.#optionsGroup & {
handle: "rbac"
title: "RBAC options"
options: {
log: {
type: "bool"
description: "Log RBAC related events and actions"
}
service_user: {}
bypass_roles: {
defaultValue: "super-admin"
description: """
Space delimited list of role handles.
These roles causes short-circuiting access control check and allowing all operations.
System will refuse to start if check-bypassing roles are also listed as authenticated or anonymous auto-assigned roles.
"""
}
authenticated_roles: {
defaultValue: "authenticated"
description: """
Space delimited list of role handles.
These roles are automatically assigned to authenticated user.
Memberships can not be managed for these roles.
System will refuse to start if roles listed here are also listed under anonymous roles
"""
}
anonymous_roles: {
defaultValue: "anonymous"
description: """
Space delimited list of role handles.
These roles are automatically assigned to anonymous user.
Memberships can not be managed for these roles.
"""
}
}
}

View File

@@ -0,0 +1,35 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
SCIM: schema.#optionsGroup & {
handle: "scim"
title: "SCIM Server"
// @todo remove explicitly defined expIdent and adjust the code
expIdent: "SCIM"
options: {
enabled: {
type: "bool"
description: "Enable SCIM subsystem"
}
base_URL: {
defaultValue: "/scim"
description: "Prefix for SCIM API endpoints"
}
secret: {
description: "Secret to use to validate requests on SCIM API endpoints"
}
external_id_as_primary: {
type: "bool"
description: "Use external IDs in SCIM API endpoints"
}
external_id_validation: {
defaultValue: "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"
description: "Validates format of external IDs. Defaults to UUID"
}
}
}

View File

@@ -0,0 +1,45 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
SMTP: schema.#optionsGroup & {
handle: "smtp"
title: "Email sending"
// @todo remove explicitly defined expIdent and adjust the code
expIdent: "SMTP"
intro: """
Configure your local SMTP server or use one of the available providers.
These values are copied to settings when the server starts and can be managed from the administration console.
We recommend you remove these values after they are copied to settings.
If server detects difference between these options and settings, it shows a warning in the log on server start.
"""
options: {
host: {
description: "The SMTP server hostname."
}
port: {
type: "int"
description: "The SMTP post."
}
user: {
description: "The SMTP username."
}
pass: {
description: "The SMTP password."
}
from: {
description: "The SMTP `from` email parameter"
}
tls_insecure: {
type: "bool"
description: "Allow insecure (invalid, expired TLS certificates) connections."
}
tls_server_name: {}
}
}

View File

@@ -0,0 +1,23 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
actionLog: schema.#optionsGroup & {
handle: "action-log"
env: "ACTIONLOG"
options: {
enabled: {
type: "bool"
defaultGoExpr: "true"
}
debug: {
type: "bool"
}
workflow_functions_enabled: {
type: "bool"
}
}
title: "Actionlog"
}

View File

@@ -0,0 +1,59 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
apigw: schema.#optionsGroup & {
handle: "apigw"
imports: [
"\"time\"",
]
options: {
enabled: {
type: "bool"
defaultGoExpr: "true"
description: "Enable API Gateway"
}
debug: {
type: "bool"
description: "Enable API Gateway debugging info"
}
log_enabled: {
type: "bool"
description: "Enable extra logging"
}
profiler_enabled: {
type: "bool"
defaultGoExpr: "true"
description: "Enable profiler"
}
profiler_global: {
type: "bool"
defaultGoExpr: "false"
description: "Profiler enabled for all routes"
}
log_request_body: {
type: "bool"
description: "Enable incoming request body output in logs"
}
proxy_enable_debug_log: {
type: "bool"
description: "Enable full debug log on requests / responses - warning, includes sensitive data"
}
proxy_follow_redirects: {
type: "bool"
defaultGoExpr: "true"
description: "Follow redirects on proxy requests"
}
proxy_outbound_timeout: {
type: "time.Duration"
description: "Outbound request timeout"
defaultGoExpr: "time.Second * 30"
defaultValue: "30s"
}
}
title: "API Gateway"
}

View File

@@ -0,0 +1,32 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
attachment: schema.#optionsGroup & {
handle: "attachment"
options: {
avatar_max_file_size: {
type: "int64"
defaultGoExpr: "1000000"
description: "Avatar image maximum upload size, default value is 1MB"
}
avatar_initials_font_path: {
defaultValue: "fonts/Poppins-Regular.ttf"
description: "Avatar initials font file path"
env: "AVATAR_INITIALS_FONT_PATH"
}
avatar_initials_background_color: {
defaultValue: "#F3F3F3"
description: "Avatar initials background color"
env: "AVATAR_INITIALS_BACKGROUND_COLOR"
}
avatar_initials_color: {
defaultValue: "#0B344E"
description: "Avatar initials text color"
env: "AVATAR_INITIALS_COLOR"
}
}
}

262
server/app/options/auth.cue Normal file
View File

@@ -0,0 +1,262 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
auth: schema.#optionsGroup & {
handle: "auth"
imports: [
"\"time\"",
]
options: {
log_enabled: {
type: "bool"
description: "Enable extra logging for authentication flows"
}
password_security: {
type: "bool"
defaultGoExpr: "true"
description: """
Password security allows you to disable constraints to which passwords must conform to.
[CAUTION]
====
Disabling password security can be useful for development environments as it removes the need for complex passwords.
Password security *should be enabled* on production environments to avoid security incidents
====
"""
}
jwt_algorithm: {
defaultGoExpr: "\"HS512\""
defaultValue: "HS512"
description: """
Algorithm to be use for JWT signature.
Supported values:
- HS256, HS384, HS512
- PS256, PS384, PS512,
- RS256, RS384, RS512
Provide shared secret string for HS256, HS384, HS512 and full private key or path to the file PS* and RS* algorithms.
"""
}
secret: {
defaultGoExpr: "getSecretFromEnv(\"jwt secret\")"
description: """
Secret used for signing JWT tokens.
Value is used only when HS256, HS384 or HS512 algorithm is used.
[IMPORTANT]
====
If secret is not set, system auto-generates one from DB_DSN and HOSTNAME environment variables.
Generated secret will change if you change any of these variables.
====
"""
env: "AUTH_JWT_SECRET"
}
jwt_key: {
description: """
Raw private key or absolute or relative path to the file containing one.
"""
}
access_token_lifetime: {
type: "time.Duration"
description: """
Lifetime of the access token. Should be shorter than lifetime of the refresh token.
"""
env: "AUTH_OAUTH2_ACCESS_TOKEN_LIFETIME"
defaultGoExpr: "time.Hour * 2"
defaultValue: "2h"
}
refresh_token_lifetime: {
type: "time.Duration"
description: """
Lifetime of the refresh token. Should be much longer than lifetime of the access token.
Refresh tokens are used to exchange expired access tokens with new ones.
"""
env: "AUTH_OAUTH2_REFRESH_TOKEN_LIFETIME"
defaultGoExpr: "time.Hour * 24 * 3"
defaultValue: "72h"
}
external_redirect_URL: {
description: """
Redirect URL to be sent with OAuth2 authentication request to provider
`provider` placeholder is replaced with the actual value when used.
"""
defaultGoExpr: "FullURL(\"/auth/external/{provider}/callback\")"
}
external_cookie_secret: {
description: """
Secret used for securing cookies
[IMPORTANT]
====
If secret is not set, system auto-generates one from DB_DSN and HOSTNAME environment variables.
Generated secret will change if you change any of these variables.
====
"""
defaultGoExpr: "getSecretFromEnv(\"external cookie secret\")"
}
base_URL: {
description: """
Frontend base URL. Must be an absolute URL, with the domain.
This is used for some redirects and links in auth emails.
"""
defaultGoExpr: "FullURL(\"/auth\")"
}
session_cookie_name: {
description: "Session cookie name"
defaultValue: "session"
}
session_cookie_path: {
description: "Session cookie path"
defaultGoExpr: "pathPrefix(\"/auth\")"
}
session_cookie_domain: {
defaultGoExpr: "GuessApiHostname()"
description: "Session cookie domain"
}
session_cookie_secure: {
type: "bool"
defaultGoExpr: "isSecure()"
description: "Defaults to true when HTTPS is used. Corteza will try to guess the this setting by"
}
session_lifetime: {
type: "time.Duration"
description: """
Maximum time user is allowed to stay idle when logged in without \"remember-me\" option and before session is expired.
Recommended value is between an hour and a day.
[IMPORTANT]
====
This affects only profile (/auth) pages. Using applications (admin, compose, ...) does not prolong the session.
====
"""
defaultGoExpr: "24 * time.Hour"
defaultValue: "24h"
}
session_perm_lifetime: {
type: "time.Duration"
description: """
Duration of the session in /auth lasts when user logs-in with \"remember-me\" option.
If set to 0, \"remember-me\" option is removed.
"""
defaultGoExpr: "360 * 24 * time.Hour"
defaultValue: "8640h"
}
garbage_collector_interval: {
type: "time.Duration"
description: "How often are expired sessions and tokens purged from the database"
defaultGoExpr: "15 * time.Minute"
defaultValue: "15min"
}
request_rate_limit: {
type: "int"
description: """
How many requests from a certain IP address are allowed in a time window.
Set to zero to disable
"""
defaultGoExpr: "60"
defaultValue: "60"
}
request_rate_window_length: {
type: "time.Duration"
defaultGoExpr: "time.Minute"
defaultValue: "1m"
description: "How many requests from a certain IP address are allowed in a time window"
}
csrf_secret: {
defaultGoExpr: "getSecretFromEnv(\"csrf secret\")"
description: """
Secret used for securing CSRF protection
[IMPORTANT]
====
If secret is not set, system auto-generates one from DB_DSN and HOSTNAME environment variables.
Generated secret will change if you change any of these variables.
====
"""
}
csrf_enabled: {
type: "bool"
defaultGoExpr: "true"
description: "Enable CSRF protection"
}
csrf_field_name: {
defaultValue: "same-site-authenticity-token"
description: "Form field name used for CSRF protection"
}
csrf_cookie_name: {
defaultValue: "same-site-authenticity-token"
description: "Cookie name used for CSRF protection"
}
default_client: {
defaultValue: "corteza-webapp"
description: """
Handle for OAuth2 client used for automatic redirect from /auth/oauth2/go endpoint.
This simplifies configuration for OAuth2 flow for Corteza Web applications as it removes
the need to supply redirection URL and client ID (oauth2/go endpoint does that internally)
"""
}
default_user_group: {
defaultValue: "default-root"
description: """
Default user group.
If default sub user group is not set, all users are assigned here.
Each user must belong to a user group if it wishes to use the system.
"""
}
default_sub_user_group: {
defaultValue: "default-sub-root"
description: """
Default sub-user group.
If default sub-user group is set, all users are assigned here.
Each user must belong to a user group if it wishes to use the system.
"""
}
assets_path: {
description: """
Path to js, css, images and template source files
When corteza starts, if path exists it tries to load template files from it.
When empty path is set (default value), embedded files are used.
"""
}
development_mode: {
type: "bool"
description: """
When enabled, corteza reloads template before every execution.
Enable this for debugging or when developing auth templates.
Should be disabled in production where templates do not change between server restarts.
"""
}
provision_super_user: {
description: """
When set, Corteza creates one or more users with the configured values using provided email as a password.
It skips existing (email, handle). All new users are assigned to all bypass roles.
When set in production, Corteza stops and reports an error
"""
}
}
title: "Authentication"
}

View File

@@ -0,0 +1,76 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
corredor: schema.#optionsGroup & {
handle: "corredor"
imports: [
"\"time\"",
]
options: {
enabled: {
type: "bool"
description: "Enable/disable Corredor integration"
}
addr: {
defaultValue: "localhost:50051"
description: "Hostname and port of the Corredor gRPC server."
}
max_backoff_delay: {
type: "time.Duration"
description: "Max delay for backoff on connection."
defaultGoExpr: "time.Minute"
defaultValue: "1m"
}
max_receive_message_size: {
type: "int"
defaultGoExpr: "2 << 23"
description: "Max message size that can be received."
}
default_exec_timeout: {
type: "time.Duration"
defaultGoExpr: "time.Minute"
}
list_timeout: {
type: "time.Duration"
defaultGoExpr: "time.Second * 2"
defaultValue: "2s"
}
list_refresh: {
type: "time.Duration"
defaultGoExpr: "time.Second * 5"
}
run_as_enabled: {
type: "bool"
defaultGoExpr: "true"
}
tls_cert_enabled: {
type: "bool"
env: "CORREDOR_CLIENT_CERTIFICATES_ENABLED"
}
tls_cert_path: {
defaultValue: "/certs/corredor/client"
env: "CORREDOR_CLIENT_CERTIFICATES_PATH"
}
tls_cert_cA: {
defaultValue: "ca.crt"
env: "CORREDOR_CLIENT_CERTIFICATES_CA"
}
tls_cert_private: {
defaultValue: "private.key"
env: "CORREDOR_CLIENT_CERTIFICATES_PRIVATE"
}
tls_cert_public: {
defaultValue: "public.crt"
env: "CORREDOR_CLIENT_CERTIFICATES_PUBLIC"
}
tls_server_name: {
env: "CORREDOR_CLIENT_CERTIFICATES_SERVER_NAME"
}
}
title: "Connection to Corredor"
}

17
server/app/options/db.cue Normal file
View File

@@ -0,0 +1,17 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
DB: schema.#optionsGroup & {
handle: "DB"
options: {
DSN: {
defaultValue: "sqlite3://file::memory:?cache=shared&mode=memory"
description: "Database connection string."
}
}
title: "Connection to data store backend"
}

View File

@@ -0,0 +1,51 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
discovery: schema.#optionsGroup & {
handle: "discovery"
options: {
enabled: {
type: "bool"
defaultGoExpr: "false"
description: "Enable discovery endpoints"
}
debug: {
type: "bool"
defaultGoExpr: "false"
description: "Enable discovery related activity info"
}
corteza_domain: {
type: "string"
description: "Indicates host of corteza compose webapp"
}
base_url: {
type: "string"
description: "Indicates host of corteza discovery server"
}
embeddings_enabled: {
type: "bool"
defaultGoExpr: "false"
description: "Enable discovery embeddings generation"
}
embeddings_dimension: {
type: "int"
description: "Embeddings dimension"
defaultGoExpr: "384"
}
hnsw_ef_construction: {
type: "int"
description: "HNSW ef construction parameter"
defaultGoExpr: "128"
}
hnsw_m: {
type: "int"
description: "HNSW m parameter"
defaultGoExpr: "16"
}
}
title: "Discovery"
}

View File

@@ -0,0 +1,16 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
environment: schema.#optionsGroup & {
handle: "environment"
options: {
environment: {
defaultValue: "production"
env: "ENVIRONMENT"
}
}
title: "Environment"
}

View File

@@ -0,0 +1,29 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
eventbus: schema.#optionsGroup & {
handle: "eventbus"
imports: [
"\"time\"",
]
options: {
scheduler_enabled: {
type: "bool"
defaultGoExpr: "true"
description: "Enable eventbus scheduler."
}
scheduler_interval: {
type: "time.Duration"
description: "Set time interval for `eventbus` scheduler."
defaultGoExpr: "time.Minute"
defaultValue: "60s"
}
}
title: "Events and scheduler"
}

View File

@@ -0,0 +1,58 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
federation: schema.#optionsGroup & {
handle: "federation"
imports: [
"\"time\"",
]
options: {
enabled: {
type: "bool"
description: "Federation enabled on system, it toggles rest API endpoints, possibility to map modules in Compose and sync itself"
}
label: {
type: "string"
defaultValue: "federated"
description: "Federation label"
}
host: {
type: "string"
defaultValue: "local.cortezaproject.org"
description: "Host that is used during node pairing, also included in invitation"
}
structure_monitor_interval: {
type: "time.Duration"
defaultGoExpr: "time.Minute * 2"
defaultValue: "2m"
description: "Delay in seconds for structure sync"
env: "FEDERATION_SYNC_STRUCTURE_MONITOR_INTERVAL"
}
structure_page_size: {
type: "int"
defaultGoExpr: "1"
defaultValue: "1"
description: "Bulk size in fetching for structure sync"
env: "FEDERATION_SYNC_STRUCTURE_PAGE_SIZE"
}
data_monitor_interval: {
type: "time.Duration"
defaultGoExpr: "time.Minute "
defaultValue: "1m"
description: "Delay in seconds for data sync"
env: "FEDERATION_SYNC_DATA_MONITOR_INTERVAL"
}
data_page_size: {
type: "int"
defaultGoExpr: "100"
defaultValue: "100"
description: "Bulk size in fetching for data sync"
env: "FEDERATION_SYNC_DATA_PAGE_SIZE"
}
}
}

View File

@@ -0,0 +1,39 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
HTTPClient: schema.#optionsGroup & {
title: "HTTP Client"
// Explicitly define all variants to be 100% compaltible with old name
handle: "http-client"
// @todo remove explcitly defined expIdent and adjust the code
expIdent: "HTTPClient"
imports: [
"\"time\"",
]
options: {
tls_insecure: {
type: "bool"
description: """
Allow insecure (invalid, expired TLS/SSL certificates) connections.
[IMPORTANT]
====
We strongly recommend keeping this value set to false except for local development or demos.
====
"""
}
timeout: {
type: "time.Duration"
description: "Default timeout for clients."
defaultGoExpr: "30 * time.Second"
defaultValue: "30s"
}
}
}

View File

@@ -0,0 +1,158 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
HTTPServer: schema.#optionsGroup & {
handle: "http-server"
title: "HTTP Server"
imports: [
"\"github.com/cortezaproject/corteza/server/pkg/rand\"",
]
options: {
domain: {
defaultValue: "localhost"
description: "Domain for the HTTP server."
env: "DOMAIN"
}
domainWebapp: {
defaultValue: "localhost"
description: "Domain for the HTTP webapp."
env: "DOMAIN_WEBAPP"
}
addr: {
defaultValue: ":80"
description: "IP and port for the HTTP server."
env: "HTTP_ADDR"
}
logRequest: {
type: "bool"
description: "Log HTTP requests."
env: "HTTP_LOG_REQUEST"
}
logResponse: {
type: "bool"
description: "Log HTTP responses."
env: "HTTP_LOG_RESPONSE"
}
tracing: {
type: "bool"
env: "HTTP_ERROR_TRACING"
}
enableHealthcheckRoute: {
type: "bool"
defaultGoExpr: "true"
env: "HTTP_ENABLE_HEALTHCHECK_ROUTE"
}
enableVersionRoute: {
type: "bool"
defaultGoExpr: "true"
description: "Enable `/version` route."
env: "HTTP_ENABLE_VERSION_ROUTE"
}
enableDebugRoute: {
type: "bool"
description: "Enable `/debug` route."
env: "HTTP_ENABLE_DEBUG_ROUTE"
}
enableMetrics: {
type: "bool"
description: "Enable (prometheus) metrics."
env: "HTTP_METRICS"
}
metricsServiceLabel: {
defaultValue: "corteza"
description: "Name for metrics endpoint."
env: "HTTP_METRICS_NAME"
}
metricsUsername: {
defaultValue: "metrics"
description: "Username for the metrics endpoint."
env: "HTTP_METRICS_USERNAME"
}
metricsPassword: {
defaultGoExpr: "string(rand.Bytes(5))"
description: "Password for the metrics endpoint."
env: "HTTP_METRICS_PASSWORD"
}
enablePanicReporting: {
type: "bool"
defaultGoExpr: "true"
description: "Report HTTP panic to Sentry."
env: "HTTP_REPORT_PANIC"
}
baseUrl: {
defaultValue: "/"
description: "Base URL (prefix) for all routes (<baseUrl>/auth, <baseUrl>/api, ...)"
env: "HTTP_BASE_URL"
}
apiEnabled: {
type: "bool"
defaultGoExpr: "true"
env: "HTTP_API_ENABLED"
}
apiBaseUrl: {
defaultValue: "/"
description: """
When webapps are enabled (HTTP_WEBAPP_ENABLED) this is moved to '/api' if not explicitly set otherwise.
API base URL is internally prefixed with baseUrl
"""
env: "HTTP_API_BASE_URL"
}
webappEnabled: {
type: "bool"
env: "HTTP_WEBAPP_ENABLED"
}
webappBaseUrl: {
defaultValue: "/"
description: "Webapp base URL is internally prefixed with baseUrl"
env: "HTTP_WEBAPP_BASE_URL"
}
webappBaseDir: {
defaultValue: "./webapp/public"
env: "HTTP_WEBAPP_BASE_DIR"
}
webappList: {
defaultValue: "admin,compose,workflow,reporter,privacy"
env: "HTTP_WEBAPP_LIST"
}
sslTerminated: {
type: "bool"
defaultGoExpr: "isSecure()"
description: """
Is SSL termination enabled in ingres, proxy or load balancer that is in front of Corteza?
By default, Corteza checks for presence of LETSENCRYPT_HOST environmental variable.
This DOES NOT enable SSL termination in Corteza!
"""
env: "HTTP_SSL_TERMINATED"
}
assets_path: {
description: """
Corteza will directly serve these assets (static files).
When empty path is set (default value), embedded files are used.
"""
}
web_console_enabled: {
type: "bool"
defaultGoExpr: "false"
description: "Enable web console. When running in dev environment, web console is enabled by default."
}
web_console_username: {
defaultValue: "admin"
description: "Username for the web console endpoint."
}
web_console_password: {
defaultGoExpr: "string(rand.Bytes(32))"
description: """
Password for the web console endpoint. When running in dev environment, password is not required.
Corteza intentionally sets default password to random chars to prevent security incidents.
"""
}
}
}

View File

@@ -0,0 +1,20 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
limit: schema.#optionsGroup & {
handle: "limit"
options: {
system_users: {
type: "int"
description: "Maximum number of valid (not deleted, not suspended) users"
}
record_count_per_module: {
type: "int"
description: "Maximum number of records per module"
}
}
title: "Limits"
}

View File

@@ -0,0 +1,59 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
locale: schema.#optionsGroup & {
handle: "locale"
options: {
languages: {
defaultValue: "en"
description: """
List of comma delimited languages (language tags) to enable.
In case when an enabled language can not be loaded, error is logged.
When loading language configurations (config.xml) from the configured path(s).
"""
}
path: {
description: """
One or more paths to locale config and translation files, separated by colon
When with LOCALE_DEVELOPMENT_MODE=true, default value for path is ../../locale
"""
}
query_string_param: {
defaultValue: "lng"
description: """
Name of the query string parameter used to pass the language tag (it overrides Accept-Language header).
Set it to empty string to disable detection from the query string.
This parameter is ignored if only one language is enabled
"""
}
resource_translations_enabled: {
type: "bool"
description: """
When enabled, an editor for resource translations is enabled in UI
"""
}
log: {
type: "bool"
description: "Log locale related events and actions"
}
development_mode: {
type: "bool"
description: """
When enabled, Corteza reloads language files on every request
Enable this for debugging or developing.
"""
}
}
}

View File

@@ -0,0 +1,67 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
log: schema.#optionsGroup & {
handle: "log"
options: {
debug: {
type: "bool"
description: """
Disables json format for logging and enables more human-readable output with colors.
Disable for production.
"""
}
level: {
defaultValue: "warn"
description: """
Minimum logging level. If set to "warn",
Levels warn, error, dpanic panic and fatal will be logged.
Recommended value for production: warn
Possible values: debug, info, warn, error, dpanic, panic, fatal
"""
}
filter: {
description: """
Log filtering rules by level and name (log-level:log-namespace).
Please note that level (LOG_LEVEL) is applied before filter and it affects the final output!
Leave unset for production.
Example:
`warn+:* *:auth,workflow.*`
Log warnings, errors, panic, fatals. Everything from auth and workflow is logged.
See more examples and documentation here: https://github.com/moul/zapfilter
"""
}
include_caller: {
type: "bool"
description: """
Set to true to see where the logging was called from.
Disable for production.
"""
}
stacktrace_level: {
defaultValue: "dpanic"
description: """
Include stack-trace when logging at a specified level or below.
Disable for production.
Possible values: debug, info, warn, error, dpanic, panic, fatal
"""
}
}
}

View File

@@ -0,0 +1,21 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
messagebus: schema.#optionsGroup & {
handle: "messagebus"
options: {
Enabled: {
type: "bool"
defaultGoExpr: "true"
description: "Enable messagebus"
}
log_enabled: {
type: "bool"
description: "Enable extra logging for messagebus watchers"
}
}
title: "Messaging queue"
}

View File

@@ -0,0 +1,23 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
monitor: schema.#optionsGroup & {
handle: "monitor"
imports: [
"\"time\"",
]
options: {
interval: {
type: "time.Duration"
defaultGoExpr: "5 * time.Minute"
defaultValue: "5m"
description: "Output (log) interval for monitoring."
}
}
title: "Monitoring"
}

View File

@@ -0,0 +1,49 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
objectStore: schema.#optionsGroup & {
handle: "object-store"
title: "Object (file) storage"
intro: "The MinIO integration allows you to replace local storage with cloud storage. When configured, `STORAGE_PATH` is not needed."
options: {
path: {
defaultValue: "var/store"
description: "Location where uploaded files are stored."
env: "STORAGE_PATH"
}
minioEndpoint: {
env: "MINIO_ENDPOINT"
}
minioSecure: {
type: "bool"
defaultGoExpr: "true"
env: "MINIO_SECURE"
}
minioAccessKey: {
env: "MINIO_ACCESS_KEY"
}
minioSecretKey: {
env: "MINIO_SECRET_KEY"
}
minioSSECKey: {
env: "MINIO_SSEC_KEY"
}
minioBucket: {
defaultValue: "{component}"
description: "`component` placeholder is replaced with service name (e.g system)."
env: "MINIO_BUCKET"
}
minioPathPrefix: {
description: "`component` placeholder is replaced with service name (e.g system)."
env: "MINIO_PATH_PREFIX"
}
minioStrict: {
type: "bool"
env: "MINIO_STRICT"
}
}
}

View File

@@ -0,0 +1,32 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
provision: schema.#optionsGroup & {
handle: "provision"
options: {
always: {
type: "bool"
defaultGoExpr: "true"
description: "Controls if provision should run when the server starts."
}
path: {
defaultValue: "provision/*"
description: "Colon seperated paths to config files for provisioning."
}
}
title: "Provisioning"
intro: """
Provisioning allows you to configure a {PRODUCT_NAME} instance when deployed.
It occurs automatically after the {PRODUCT_NAME} server starts.
[IMPORTANT]
====
We recommend you to keep provisioning enabled as it simplifies version updates by updating the database and updating settings.
If you're doing local development or some debugging, you can disable this.
====
"""
}

View File

@@ -0,0 +1,65 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
sentry: schema.#optionsGroup & {
handle: "sentry"
imports: [
"\"github.com/cortezaproject/corteza/server/pkg/version\"",
]
title: "Sentry monitoring"
intro: """
[NOTE]
====
These parameters help in the development and testing process.
When you are deploying to production, these should be disabled to improve performance and reduce storage usage.
You should configure external services such as Sentry or ELK to keep track of logs and error reports.
====
"""
options: {
DSN: {
description: "Set to enable Sentry client."
}
debug: {
type: "bool"
description: "Print out debugging information."
}
attach_stacktrace: {
type: "bool"
defaultGoExpr: "true"
description: "Attach stacktraces"
}
sample_rate: {
type: "float64"
description: "Sample rate for event submission (0.0 - 1.0. defaults to 1.0)"
}
max_breadcrumbs: {
type: "int"
defaultGoExpr: "0"
description: "Maximum number of breadcrumbs."
}
server_name: {
description: "Set reported Server name."
env: "SENTRY_SERVERNAME"
}
release: {
defaultGoExpr: "version.Version"
description: "Set reported Release."
}
dist: {
description: "Set reported distribution."
}
environment: {
description: "Set reported environment."
}
webapp_DSN: {
description: "Set to enable Sentry client for webapp."
}
}
}

View File

@@ -0,0 +1,22 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
template: schema.#optionsGroup & {
handle: "template"
title: "Rendering engine"
options: {
renderer_gotenberg_address: {
defaultGoExpr: ""
description: "Gotenberg rendering container address."
}
renderer_gotenberg_enabled: {
type: "bool"
description: "Is Gotenberg rendering container enabled."
}
}
}

View File

@@ -0,0 +1,25 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
upgrade: schema.#optionsGroup & {
handle: "upgrade"
title: "Data store (database) upgrade"
options: {
debug: {
type: "bool"
description: """
Enable/disable debug logging.
To enable debug logging set `UPGRADE_DEBUG=true`.
"""
}
always: {
type: "bool"
defaultGoExpr: "true"
description: "Controls if the upgradable systems should be upgraded when the server starts."
}
}
}

View File

@@ -0,0 +1,76 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
waitFor: schema.#optionsGroup & {
handle: "wait-for"
imports: [
"\"time\"",
]
title: "Delay system startup"
intro: """
You can configure these options to defer API execution until another external (HTTP) service is up and running.
[ TIP ]
====
Delaying API execution can come in handy in complex setups where execution order is important.
====
"""
options: {
delay: {
type: "time.Duration"
description: """
Delays API startup for the amount of time specified (10s, 2m...).
This delay happens before service (`WAIT_FOR_SERVICES`) probing.
"""
env: "WAIT_FOR"
}
status_page: {
type: "bool"
defaultGoExpr: "true"
description: "Show temporary status web page."
env: "WAIT_FOR_STATUS_PAGE"
}
services: {
description: """
Space delimited list of hosts and/or URLs to probe.
Host format: `host` or `host:443` (port will default to 80).
[NOTE]
====
Services are probed in parallel.
====
"""
env: "WAIT_FOR_SERVICES"
}
services_timeout: {
type: "time.Duration"
defaultGoExpr: "time.Minute"
defaultValue: "1m"
description: "Max time for each service probe."
env: "WAIT_FOR_SERVICES_TIMEOUT"
}
services_probe_timeout: {
type: "time.Duration"
defaultGoExpr: "time.Second * 30"
defaultValue: "30s"
description: "Timeout for each service probe."
env: "WAIT_FOR_SERVICES_PROBE_TIMEOUT"
}
services_probe_interval: {
type: "time.Duration"
defaultGoExpr: "time.Second * 5"
defaultValue: "5s"
description: "Interval between service probes."
env: "WAIT_FOR_SERVICES_PROBE_INTERVAL"
}
}
}

View File

@@ -0,0 +1,15 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
webapp: schema.#optionsGroup & {
handle: "webapp"
options: {
scss_dir_path: {
description: "Path to custom SCSS source files directory"
}
}
}

View File

@@ -0,0 +1,39 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
websocket: schema.#optionsGroup & {
handle: "websocket"
imports: [
"\"time\"",
]
title: "Websocket server"
options: {
log_enabled: {
type: "bool"
description: "Enable extra logging for authentication flows"
}
timeout: {
type: "time.Duration"
description: "Time before `WsServer` gets timed out."
defaultGoExpr: "15 * time.Second"
defaultValue: "15s"
}
ping_timeout: {
type: "time.Duration"
defaultGoExpr: "120 * time.Second"
defaultValue: "120s"
}
ping_period: {
type: "time.Duration"
defaultGoExpr: "((120 * time.Second) * 9) / 10"
defaultValue: "119s"
}
}
}

View File

@@ -0,0 +1,36 @@
package options
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
workflow: schema.#optionsGroup & {
handle: "workflow"
options: {
register: {
type: "bool"
defaultGoExpr: "true"
description: "Registers enabled and valid workflows and executes them when triggered"
}
exec_debug: {
type: "bool"
description: "Enables verbose logging for workflow execution"
}
call_stack_size: {
type: "int"
defaultGoExpr: "16"
description: "Defines the maximum call stack size between workflows"
}
stack_trace_enabled: {
type: "bool"
defaultGoExpr: "true"
description: "Enables execution stack trace construction"
}
stack_trace_full: {
type: "bool"
defaultGoExpr: "false"
description: "Forces the stack trace to record all steps; intended for testing, memory use grows with every executed step"
}
}
title: "Workflow"
}

17
server/app/rand.go Normal file
View File

@@ -0,0 +1,17 @@
package app
import (
cr "crypto/rand"
"encoding/binary"
"math/rand"
)
func init() {
// seed some randomness
var b [8]byte
if _, err := cr.Read(b[:]); err != nil {
panic(err)
}
rand.Seed(int64(binary.LittleEndian.Uint64(b[:])))
}

389
server/app/resources.cue Normal file
View File

@@ -0,0 +1,389 @@
package app
import (
"github.com/cortezaproject/corteza/server/codegen/schema"
)
_allFeaturesDisabled: {
labels: false
paging: false
sorting: false
checkFn: false
}
resources: { [key=_]: {"handle": key, "component": "system", "platform": "corteza" } & schema.#PkgResource } & {
"rbac-rule": {
package: {
ident: "rbac"
import: "github.com/cortezaproject/corteza/server/pkg/rbac"
}
ident: "rule"
identPlural: "rules"
expIdent: "Rule"
features: _allFeaturesDisabled
model: {
ident: "rbac_rules"
attributes: {
role_id: {
goType: "uint64",
ident: "roleID",
storeIdent: "rel_role"
dal: { type: "Ref", refModelResType: "corteza::system:role" }
}
resource: {
dal: { length: 512 }
}
operation: {
dal: { length: 50 }
}
access: {
goType: "types.Access"
dal: { type: "Number", meta: { "rdbms:type": "integer" } }
}
}
indexes: {
"primary": { attributes: [ "role_id", "resource", "operation" ] }
}
}
store: {
ident: "rbacRule"
api: {
functions: [
{
expIdent: "TransferRbacRules"
args: [
{ident: "src", goType: "uint64"},
{ident: "dst", goType: "uint64"},
]
return: []
},
]
}
}
}
"label": {
package: {
ident: "labels"
import: "github.com/cortezaproject/corteza/server/pkg/label/types"
}
ident: "label"
identPlural: "labels"
expIdent: "Label"
features: _allFeaturesDisabled
model: {
attributes: {
kind: {
dal: { length: 64 }
}
resource_id: {
goType: "uint64",
ident: "resourceID",
storeIdent: "rel_resource"
dal: { type: "ID" }
}
name: {
ignoreCase: true
dal: { length: 512 }
}
value: {
goType: "types.LabelValue"
dal: { type: "JSON"}
omitSetter: true
omitGetter: true
}
}
indexes: {
"primary": { attributes: ["kind", "resource_id", "name"] }
"unique_kind_res_name": {
fields: [
{ attribute: "kind" },
{ attribute: "resource_id" },
{ attribute: "name", modifiers: [ "LOWERCASE" ] },
]
}
}
}
filter: {
expIdent: "LabelFilter"
struct: {
kind: {}
rel_resource: { goType: "[]uint64", ident: "resourceID" }
limit: { goType: "uint" }
}
byValue: ["kind" , "rel_resource", ]
}
store: {
api: {
lookups: [
{
fields: ["kind", "resource_id", "name"]
description: """
searches for label by kind, resource ID and name
"""
},
]
functions: [
{
expIdent: "DeleteExtraLabels"
args: [
{ident: "kind", goType: "string"},
{ident: "resourceId", goType: "uint64"},
{ident: "name", goType: "string", spread: true},
]
return: []
},
]
}
}
}
"flag": {
package: {
ident: "flag"
import: "github.com/cortezaproject/corteza/server/pkg/flag/types"
}
ident: "flag"
identPlural: "flags"
expIdent: "Flag"
features: _allFeaturesDisabled
model: {
attributes: {
kind: {
dal: { length: 64 }
}
resource_id: {
goType: "uint64",
ident: "resourceID",
storeIdent: "rel_resource"
dal: { type: "ID" }
}
owned_by: schema.AttributeUserRef
name: {
ignoreCase: true
dal: { length: 512 }
}
active: {
goType: "bool"
dal: { type: "Boolean" }
}
}
indexes: {
"primary": { attributes: ["kind", "resource_id", "owned_by", "name"] }
"unique_kind_res_owner_name": {
fields: [
{ attribute: "kind" },
{ attribute: "resource_id" },
{ attribute: "owned_by" },
{ attribute: "name", modifiers: [ "LOWERCASE" ] },
]
}
}
}
filter: {
expIdent: "FlagFilter"
struct: {
kind: {}
resource_id: { goType: "[]uint64",
ident: "resourceID", storeIdent: "rel_resource" }
owned_by: { goType: "[]uint64", ident: "ownedBy" }
name: { goType: "[]string", ident: "name" }
}
byValue: ["kind", "resource_id", "owned_by", "name", ]
}
store: {
api: {
lookups: [
{
fields: ["kind", "resource_id", "owned_by", "name"]
description: """
searches for flag by kind, resource ID, owner and name
"""
},
]
}
}
}
"actionlog": {
package: {
ident: "actionlog"
import: "github.com/cortezaproject/corteza/server/pkg/actionlog"
}
ident: "action"
identPlural: "action"
expIdent: "Action"
features: {
labels: false
paging: false
checkFn: false
}
model: {
ident: "actionlog"
attributes: {
id: schema.IdField
timestamp: schema.SortableTimestampField & { storeIdent: "ts" }
actor_ip_addr: {
ident: "actorIPAddr"
dal: { type: "Text", length: 64 }
}
actor_id: {
goType: "uint64",
ident: "actorID"
dal: { type: "Ref", refModelResType: "corteza::system:user" }
}
request_origin: {
dal: { type: "Text", length: 32 }
}
request_id: {
ident: "requestID"
dal: { type: "Text", length: 256 }
}
resource: {
dal: { type: "Text", length: 512 }
}
action: {
dal: { type: "Text", length: 64 }
}
error: {
dal: {}
}
severity: {
goType: "types.Severity"
dal: { type: "Number", default: 0, meta: { "rdbms:type": "integer" } }
}
description: {
dal: {}
}
meta: {
goType: "types.Meta"
dal: { type: "JSON", defaultEmptyObject: true }
}
}
indexes: {
"primary": { attribute: "id" }
"action": { attribute: "action"}
"actor_id": { attribute: "actor_id"}
"rel_resource": { attribute: "resource"}
"ts": { attribute: "timestamp"}
}
}
filter: {
expIdent: "Filter"
struct: {
from_timestamp: { goType: "*time.Time" }
to_timestamp: { goType: "*time.Time" }
before_action_id: { goType: "uint64", ident: "beforeActionID" }
actor_id: { goType: "[]uint64", ident: "actorID" }
origin: {}
resource: {}
action: {}
limit: { goType: "uint" }
}
byValue: ["action", "resource", "origin", "actor_id" ]
}
store: {
ident: "actionlog"
api: {
lookups: [
{
fields: ["id"]
description: """
searches for action log by ID
"""
},
]
}
}
}
"resource-activity": {
package: {
ident: "discovery"
import: "github.com/cortezaproject/corteza/server/discovery/types"
}
ident: "resourceActivity"
identPlural: "resourceActivities"
expIdent: "ResourceActivity"
expIdentPlural: "ResourceActivities"
features: _allFeaturesDisabled
model: {
ident: "resource_activity_log"
attributes: {
id: schema.IdField
timestamp: schema.SortableTimestampField & { storeIdent: "ts" }
resource_type: {
dal: {}
}
resource_action: {
dal: {}
}
resource_id: {
goType: "uint64",
ident: "resourceID",
storeIdent: "rel_resource"
dal: { type: "ID" }
}
meta: {
goType: "rawJson"
dal: { type: "JSON", defaultEmptyObject: true }
}
}
indexes: {
"primary": { attribute: "id" }
"resource": { attribute: "resource_id" }
}
}
filter: {
expIdent: "ResourceActivityFilter"
struct: {
from_timestamp: { goType: "*time.Time" }
to_timestamp: { goType: "*time.Time" }
}
}
store: {
ident: "resourceActivity"
api: {
lookups: []
}
}
}
}

163
server/app/servers.go Normal file
View File

@@ -0,0 +1,163 @@
package app
import (
"net/http"
"path"
"regexp"
"strings"
"github.com/cortezaproject/corteza/server/assets"
automationRest "github.com/cortezaproject/corteza/server/automation/rest"
composeRest "github.com/cortezaproject/corteza/server/compose/rest"
discoveryRest "github.com/cortezaproject/corteza/server/discovery/rest"
"github.com/cortezaproject/corteza/server/docs"
federationRest "github.com/cortezaproject/corteza/server/federation/rest"
"github.com/cortezaproject/corteza/server/pkg/logger"
"github.com/cortezaproject/corteza/server/pkg/options"
"github.com/cortezaproject/corteza/server/pkg/webapp"
systemRest "github.com/cortezaproject/corteza/server/system/rest"
"github.com/cortezaproject/corteza/server/system/scim"
"github.com/go-chi/chi/v5"
"go.uber.org/zap"
)
func (app *CortezaApp) mountHttpRoutes(r chi.Router) {
var (
ho = app.Opt.HTTPServer
)
func() {
// asset serving has some overlap with auth assets, web-console and webapp serving
// and might be joined with one or more of them in the later version
var (
url = options.CleanBase(ho.BaseUrl, "assets")
aPath = ho.AssetsPath
files = assets.Files(app.Log, aPath)
)
r.Handle(url+"/*", http.StripPrefix(url+"/", http.FileServer(http.FS(files))))
if aPath != "" {
app.Log.Info("custom web assets mounted", zap.String("url", url), zap.String("path", aPath))
} else {
app.Log.Info("embedded web assets mounted", zap.String("url", url))
}
}()
func() {
if ho.WebappEnabled && ho.ApiEnabled && ho.ApiBaseUrl == ho.WebappBaseUrl {
app.Log.
Warn("client web applications and api can not use the same base URL: '" + ho.WebappBaseUrl + "'")
ho.WebappEnabled = false
}
if !ho.WebappEnabled {
app.Log.Info("client web applications disabled")
return
}
r.Route(options.CleanBase(ho.WebappBaseUrl), webapp.MakeWebappServer(app.Log, ho, app.Opt.Auth, app.Opt.Discovery, app.Opt.Sentry))
app.Log.Info(
"client web applications enabled",
zap.String("baseUrl", options.CleanBase(ho.BaseUrl, ho.WebappBaseUrl)),
zap.String("baseDir", ho.WebappBaseDir),
zap.Strings("apps", strings.Split(ho.WebappList, ",")),
)
}()
// Auth server
app.AuthService.MountHttpRoutes(ho.BaseUrl, r)
func() {
if !ho.ApiEnabled {
app.Log.Info("JSON REST API disabled")
return
}
r.Route(options.CleanBase(ho.ApiBaseUrl), func(r chi.Router) {
var fullpathAPI = "/" + strings.TrimPrefix(options.CleanBase(ho.BaseUrl, ho.ApiBaseUrl), "/")
app.Log.Info(
"JSON REST API enabled",
zap.String("baseUrl", fullpathAPI),
)
r.Route("/system", systemRest.MountRoutes())
r.Route("/automation", automationRest.MountRoutes())
r.Route("/compose", composeRest.MountRoutes())
r.Route("/websocket", app.WsServer.MountRoutes)
if app.Opt.Discovery.Enabled {
r.Route("/discovery", discoveryRest.MountRoutes(app.Opt.Discovery))
}
if app.Opt.Federation.Enabled {
r.Route("/federation", federationRest.MountRoutes(app.Opt.Limit))
}
var fullpathDocs = options.CleanBase(ho.BaseUrl, ho.ApiBaseUrl, "docs")
app.Log.Info(
"API docs enabled",
zap.String("baseUrl", fullpathDocs),
)
r.Handle("/docs", http.RedirectHandler(fullpathDocs+"/", http.StatusPermanentRedirect))
r.Handle("/docs*", http.StripPrefix(fullpathDocs, http.FileServer(docs.GetFS())))
var fullpathGateway = options.CleanBase(ho.BaseUrl, ho.ApiBaseUrl, "gateway")
r.Handle("/gateway*", http.StripPrefix(fullpathGateway, app.ApigwService))
})
}()
func() {
if !app.Opt.SCIM.Enabled {
return
}
if app.Opt.SCIM.Secret == "" {
app.Log.
Error("SCIM secret empty")
}
var (
baseUrl = app.Opt.SCIM.BaseURL
extIdValidation *regexp.Regexp
err error
)
if len(app.Opt.SCIM.ExternalIdValidation) > 0 {
extIdValidation, err = regexp.Compile(app.Opt.SCIM.ExternalIdValidation)
}
if err != nil {
app.Log.Error("failed to compile SCIM external ID validation", zap.Error(err))
return
}
app.Log.Debug(
"SCIM enabled",
zap.String("baseUrl", path.Join(app.Opt.HTTPServer.BaseUrl, baseUrl)),
logger.Mask("secret", app.Opt.SCIM.Secret),
)
r.Route(baseUrl, func(r chi.Router) {
if !app.Opt.Environment.IsDevelopment() {
r.Use(scim.Guard(app.Opt.SCIM))
}
scim.Routes(r, scim.Config{
ExternalIdAsPrimary: app.Opt.SCIM.ExternalIdAsPrimary,
ExternalIdValidator: extIdValidation,
})
})
}()
func() {
r.Handle("/.well-known/openid-configuration", app.AuthService.WellKnownOpenIDConfiguration())
// clients discover the document under the issuer URL (AUTH_BASE_URL) that ends with /auth
r.Handle("/auth/.well-known/openid-configuration", app.AuthService.WellKnownOpenIDConfiguration())
}()
}

9
server/app/store.go Normal file
View File

@@ -0,0 +1,9 @@
package app
// Registers all supported store backends
import (
_ "github.com/cortezaproject/corteza/server/store/adapters/rdbms/drivers/mssql"
_ "github.com/cortezaproject/corteza/server/store/adapters/rdbms/drivers/mysql"
_ "github.com/cortezaproject/corteza/server/store/adapters/rdbms/drivers/postgres"
_ "github.com/cortezaproject/corteza/server/store/adapters/rdbms/drivers/sqlite"
)

View File

@@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----