diff --git a/go.mod b/go.mod index 85df59daa4cd..3e6b35309e97 100644 --- a/go.mod +++ b/go.mod @@ -87,7 +87,6 @@ require ( cloud.google.com/go/auth v0.16.1 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.7.0 // indirect - filippo.io/edwards25519 v1.1.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect @@ -135,7 +134,6 @@ require ( github.com/go-openapi/spec v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/go-openapi/validate v0.24.0 // indirect - github.com/go-sql-driver/mysql v1.9.3 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect @@ -185,7 +183,6 @@ require ( github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect github.com/magefile/mage v1.15.0 // indirect github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mdlayher/socket v0.4.1 // indirect github.com/mdlayher/vsock v1.2.1 // indirect @@ -199,7 +196,6 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect github.com/natefinch/wrap v0.2.0 // indirect - github.com/ncruces/go-strftime v0.1.9 // indirect github.com/oklog/run v1.1.0 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/oklog/ulid/v2 v2.1.1 // indirect @@ -221,7 +217,6 @@ require ( github.com/prometheus/procfs v0.16.1 // indirect github.com/prometheus/sigv4 v0.1.2 // indirect github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect - github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect @@ -330,9 +325,5 @@ require ( k8s.io/client-go v0.33.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e // indirect - modernc.org/libc v1.66.3 // indirect - modernc.org/mathutil v1.7.1 // indirect - modernc.org/memory v1.11.0 // indirect - modernc.org/sqlite v1.38.2 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/go.sum b/go.sum index 34654f0cca5d..643e5a4a3e09 100644 --- a/go.sum +++ b/go.sum @@ -443,8 +443,6 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= -github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= @@ -1466,7 +1464,6 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1793,32 +1790,14 @@ k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUy k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro= k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM= -modernc.org/cc/v4 v4.26.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= -modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU= -modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE= -modernc.org/fileutil v1.3.8 h1:qtzNm7ED75pd1C7WgAGcK4edm4fvhtBsEiI/0NQ54YM= -modernc.org/fileutil v1.3.8/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= -modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= -modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= -modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= -modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ= modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= -modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= -modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= -modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= -modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek= modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E= -modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= -modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= -modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= -modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/pkg/authz/authz.go b/pkg/authz/authz.go index 2381cdf05a17..52eb59392def 100644 --- a/pkg/authz/authz.go +++ b/pkg/authz/authz.go @@ -1,7 +1,15 @@ package authz -import "github.com/SigNoz/signoz/pkg/factory" +import ( + "context" + + "github.com/SigNoz/signoz/pkg/factory" + openfgav1 "github.com/openfga/api/proto/openfga/v1" +) type AuthZ interface { factory.Service + + // Check returns error when the upstream authorization server is unavailable or the subject (s) doesn't have relation (r) on object (o). + Check(context.Context, *openfgav1.CheckRequestTupleKey) error } diff --git a/pkg/authz/openfgaauthz/provider.go b/pkg/authz/openfgaauthz/provider.go index 85987a9ab68c..bc66d5a41105 100644 --- a/pkg/authz/openfgaauthz/provider.go +++ b/pkg/authz/openfgaauthz/provider.go @@ -2,8 +2,12 @@ package openfgaauthz import ( "context" + "sync" authz "github.com/SigNoz/signoz/pkg/authz" + "github.com/SigNoz/signoz/pkg/errors" + "github.com/SigNoz/signoz/pkg/types/authtypes" + "github.com/SigNoz/signoz/pkg/valuer" "github.com/SigNoz/signoz/pkg/factory" "github.com/SigNoz/signoz/pkg/sqlstore" @@ -13,25 +17,31 @@ import ( "google.golang.org/protobuf/encoding/protojson" ) +var ( + openfgaDefaultStore = valuer.NewString("signoz") +) + type provider struct { config authz.Config settings factory.ScopedProviderSettings openfgaSchema []openfgapkgtransformer.ModuleFile openfgaServer *openfgapkgserver.Server + storeID string + modelID string + mtx sync.RWMutex stopChan chan struct{} } -func NewProviderFactory(sqlstoreConfig sqlstore.Config, openfgaSchema []openfgapkgtransformer.ModuleFile) factory.ProviderFactory[authz.AuthZ, authz.Config] { +func NewProviderFactory(sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile) factory.ProviderFactory[authz.AuthZ, authz.Config] { return factory.NewProviderFactory(factory.MustNewName("openfga"), func(ctx context.Context, ps factory.ProviderSettings, config authz.Config) (authz.AuthZ, error) { - return newOpenfgaProvider(ctx, ps, config, sqlstoreConfig, openfgaSchema) + return newOpenfgaProvider(ctx, ps, config, sqlstore, openfgaSchema) }) } -func newOpenfgaProvider(ctx context.Context, settings factory.ProviderSettings, config authz.Config, sqlstoreConfig sqlstore.Config, openfgaSchema []openfgapkgtransformer.ModuleFile) (authz.AuthZ, error) { +func newOpenfgaProvider(ctx context.Context, settings factory.ProviderSettings, config authz.Config, sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile) (authz.AuthZ, error) { scopedProviderSettings := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/pkg/authz/openfgaauthz") - // setup connections and run the migrations - sqlstore, err := NewSQLStore(storeConfig{sqlstoreConfig: sqlstoreConfig}) + store, err := NewSQLStore(sqlstore) if err != nil { scopedProviderSettings.Logger().DebugContext(ctx, "failed to initialize sqlstore for authz") return nil, err @@ -39,7 +49,7 @@ func newOpenfgaProvider(ctx context.Context, settings factory.ProviderSettings, // setup the openfga server opts := []openfgapkgserver.OpenFGAServiceV1Option{ - openfgapkgserver.WithDatastore(sqlstore), + openfgapkgserver.WithDatastore(store), openfgapkgserver.WithLogger(NewLogger(scopedProviderSettings.Logger())), } openfgaServer, err := openfgapkgserver.NewServerWithOpts(opts...) @@ -53,21 +63,27 @@ func newOpenfgaProvider(ctx context.Context, settings factory.ProviderSettings, settings: scopedProviderSettings, openfgaServer: openfgaServer, openfgaSchema: openfgaSchema, + mtx: sync.RWMutex{}, stopChan: make(chan struct{}), }, nil } func (provider *provider) Start(ctx context.Context) error { - storeId, err := provider.getOrCreateStore(ctx, "signoz") + storeId, err := provider.getOrCreateStore(ctx, openfgaDefaultStore.StringValue()) if err != nil { return err } - _, err = provider.getOrCreateModel(ctx, storeId) + modelID, err := provider.getOrCreateModel(ctx, storeId) if err != nil { return err } + provider.mtx.Lock() + provider.modelID = modelID + provider.storeID = storeId + provider.mtx.Unlock() + <-provider.stopChan return nil } @@ -157,4 +173,24 @@ func (provider *provider) isModelEqual(expected *openfgav1.AuthorizationModel, a } return string(expectedAuthModelBytes) == string(actualAuthModelBytes), nil + +} + +func (provider *provider) Check(ctx context.Context, tupleReq *openfgav1.CheckRequestTupleKey) error { + checkResponse, err := provider.openfgaServer.Check( + ctx, + &openfgav1.CheckRequest{ + StoreId: provider.storeID, + AuthorizationModelId: provider.modelID, + TupleKey: tupleReq, + }) + if err != nil { + return errors.Newf(errors.TypeInternal, authtypes.ErrCodeAuthZUnavailable, "authorization server is unavailable").WithAdditional(err.Error()) + } + + if !checkResponse.Allowed { + return errors.Newf(errors.TypeForbidden, authtypes.ErrCodeAuthZForbidden, "subject %s cannot %s object %s", tupleReq.User, tupleReq.Relation, tupleReq.Object) + } + + return nil } diff --git a/pkg/authz/openfgaauthz/provider_test.go b/pkg/authz/openfgaauthz/provider_test.go new file mode 100644 index 000000000000..47345d1bbaa8 --- /dev/null +++ b/pkg/authz/openfgaauthz/provider_test.go @@ -0,0 +1,47 @@ +package openfgaauthz + +import ( + "context" + "testing" + "time" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/SigNoz/signoz/pkg/authz" + "github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest" + "github.com/SigNoz/signoz/pkg/sqlstore" + "github.com/SigNoz/signoz/pkg/sqlstore/sqlstoretest" + "github.com/openfga/language/pkg/go/transformer" + "github.com/stretchr/testify/require" +) + +func TestProviderStartStop(t *testing.T) { + providerSettings := instrumentationtest.New().ToProviderSettings() + sqlstore := sqlstoretest.New(sqlstore.Config{Provider: "postgres"}, sqlmock.QueryMatcherRegexp) + + expectedModel := `module base + type user` + provider, err := newOpenfgaProvider(context.Background(), providerSettings, authz.Config{}, sqlstore, []transformer.ModuleFile{{Name: "test.fga", Contents: expectedModel}}) + require.NoError(t, err) + + storeRows := sqlstore.Mock().NewRows([]string{"id", "name", "created_at", "updated_at"}).AddRow("01K3V0NTN47MPTMEV1PD5ST6ZC", "signoz", time.Now(), time.Now()) + sqlstore.Mock().ExpectQuery("SELECT (.+) FROM store WHERE (.+)").WillReturnRows(storeRows) + + authModelCollectionRows := sqlstore.Mock().NewRows([]string{"authorization_model_id"}).AddRow("01K44QQKXR6F729W160NFCJT58") + sqlstore.Mock().ExpectQuery("SELECT DISTINCT (.+) FROM authorization_model WHERE store (.+) ORDER BY (.+)").WillReturnRows(authModelCollectionRows) + + modelRows := sqlstore.Mock().NewRows([]string{"authorization_model_id", "schema_version", "type", "type_definition", "serialized_protobuf"}). + AddRow("01K44QQKXR6F729W160NFCJT58", "1.1", "", "", "") + sqlstore.Mock().ExpectQuery("SELECT authorization_model_id, schema_version, type, type_definition, serialized_protobuf FROM authorization_model WHERE authorization_model_id = (.+) AND store = (.+)").WithArgs("01K44QQKXR6F729W160NFCJT58", "01K3V0NTN47MPTMEV1PD5ST6ZC").WillReturnRows(modelRows) + + sqlstore.Mock().ExpectExec("INSERT INTO authorization_model (.+) VALUES (.+)").WillReturnResult(sqlmock.NewResult(1, 1)) + go func() { + err := provider.Start(context.Background()) + require.NoError(t, err) + }() + + // wait for the service to start + time.Sleep(time.Second * 2) + + err = provider.Stop(context.Background()) + require.NoError(t, err) +} diff --git a/pkg/authz/openfgaauthz/sqlstore.go b/pkg/authz/openfgaauthz/sqlstore.go index 6232a5cccd27..69f1b8386ec5 100644 --- a/pkg/authz/openfgaauthz/sqlstore.go +++ b/pkg/authz/openfgaauthz/sqlstore.go @@ -4,39 +4,19 @@ import ( "github.com/SigNoz/signoz/pkg/errors" "github.com/SigNoz/signoz/pkg/sqlstore" "github.com/openfga/openfga/pkg/storage" - "github.com/openfga/openfga/pkg/storage/migrate" "github.com/openfga/openfga/pkg/storage/postgres" "github.com/openfga/openfga/pkg/storage/sqlcommon" - "github.com/openfga/openfga/pkg/storage/sqlite" ) -type storeConfig struct { - sqlstoreConfig sqlstore.Config -} - -func NewSQLStore(cfg storeConfig) (storage.OpenFGADatastore, error) { - switch cfg.sqlstoreConfig.Provider { +func NewSQLStore(sqlstore sqlstore.SQLStore) (storage.OpenFGADatastore, error) { + switch sqlstore.BunDB().Dialect().Name().String() { + // use the NewWithDB for sqlite once https://github.com/openfga/openfga/pull/2679 is merged and released else will figure out something else. case "sqlite": - err := migrate.RunMigrations(migrate.MigrationConfig{Engine: cfg.sqlstoreConfig.Provider, URI: "file:" + cfg.sqlstoreConfig.Sqlite.Path + "?_foreign_keys=true"}) - if err != nil { - return nil, err - } - - return sqlite.New("file:"+cfg.sqlstoreConfig.Sqlite.Path+"?_foreign_keys=true", &sqlcommon.Config{ + case "pg": + return postgres.NewWithDB(sqlstore.SQLDB(), nil, &sqlcommon.Config{ MaxTuplesPerWriteField: 100, MaxTypesPerModelField: 100, }) - case "postgres": - err := migrate.RunMigrations(migrate.MigrationConfig{Engine: cfg.sqlstoreConfig.Provider, URI: cfg.sqlstoreConfig.Postgres.DSN}) - if err != nil { - return nil, err - } - - return postgres.New(cfg.sqlstoreConfig.Postgres.DSN, &sqlcommon.Config{ - MaxTuplesPerWriteField: 100, - MaxTypesPerModelField: 100, - }) - default: - return nil, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid store type: %s", cfg.sqlstoreConfig.Provider) } + return nil, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid store type: %s", sqlstore.BunDB().Dialect().Name().String()) } diff --git a/pkg/http/middleware/authz.go b/pkg/http/middleware/authz.go index 60ef94aabccb..63dd785ce983 100644 --- a/pkg/http/middleware/authz.go +++ b/pkg/http/middleware/authz.go @@ -4,6 +4,7 @@ import ( "log/slog" "net/http" + "github.com/SigNoz/signoz/pkg/authz" "github.com/SigNoz/signoz/pkg/http/render" "github.com/SigNoz/signoz/pkg/types/authtypes" "github.com/gorilla/mux" @@ -14,7 +15,8 @@ const ( ) type AuthZ struct { - logger *slog.Logger + logger *slog.Logger + authzService authz.AuthZ } func NewAuthZ(logger *slog.Logger) *AuthZ { @@ -103,3 +105,17 @@ func (middleware *AuthZ) OpenAccess(next http.HandlerFunc) http.HandlerFunc { next(rw, req) }) } + +// each individual APIs should be responsible for defining the relation and the object being accessed, subject will be derived from the request +func (middleware *AuthZ) Check(next http.HandlerFunc, relation string) http.HandlerFunc { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + checkRequestTupleKey := authtypes.NewTuple("", "", "") + err := middleware.authzService.Check(req.Context(), checkRequestTupleKey) + if err != nil { + render.Error(rw, err) + return + } + + next(rw, req) + }) +} diff --git a/pkg/types/authtypes/tuple.go b/pkg/types/authtypes/tuple.go new file mode 100644 index 000000000000..7911954b45d7 --- /dev/null +++ b/pkg/types/authtypes/tuple.go @@ -0,0 +1,15 @@ +package authtypes + +import ( + "github.com/SigNoz/signoz/pkg/errors" + openfgav1 "github.com/openfga/api/proto/openfga/v1" +) + +var ( + ErrCodeAuthZUnavailable = errors.MustNewCode("authz_unavailable") + ErrCodeAuthZForbidden = errors.MustNewCode("authz_forbidden") +) + +func NewTuple(subject string, relation string, object string) *openfgav1.CheckRequestTupleKey { + return &openfgav1.CheckRequestTupleKey{User: subject, Relation: relation, Object: object} +}