From 939d77d077134affde0a56b8f4b7fda404a6d1f9 Mon Sep 17 00:00:00 2001 From: Shaidyk Date: Fri, 12 Jun 2026 12:28:58 +0300 Subject: [PATCH] fix: resolve #2 - Endpoint, Token, Cert Path, and Cert Key Storage for CLI Too --- core/client/grpc.go | 49 +++++++++++++++++++++++++++++++++++++--- core/client/grpc_test.go | 42 ++++++++++++++++++++++++++++++++++ core/config/config.go | 9 +++++--- 3 files changed, 94 insertions(+), 6 deletions(-) create mode 100644 core/client/grpc_test.go diff --git a/core/client/grpc.go b/core/client/grpc.go index 11835df..dd6da82 100644 --- a/core/client/grpc.go +++ b/core/client/grpc.go @@ -2,19 +2,62 @@ package client import ( + "context" + "crypto/tls" + + "github.com/Permify/permify-cli/core/config" permify "github.com/Permify/permify-go/v1" "google.golang.org/grpc" + "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" ) -// New initializes a new permify client +// tokenAuth attaches a bearer token, retrieved from storage, to outgoing requests. +type tokenAuth struct { + token string +} + +// GetRequestMetadata returns the authorization metadata for each request. +func (t tokenAuth) GetRequestMetadata(_ context.Context, _ ...string) (map[string]string, error) { + return map[string]string{"authorization": "Bearer " + t.token}, nil +} + +// RequireTransportSecurity reports whether the token requires transport security. +func (tokenAuth) RequireTransportSecurity() bool { + return false +} + +// transportCredentials builds the gRPC transport credentials from the stored +// certificate path and key. An insecure connection is used when no certificate +// is configured. +func transportCredentials(certPath, certKey string) (credentials.TransportCredentials, error) { + if certPath == "" || certKey == "" { + return insecure.NewCredentials(), nil + } + cert, err := tls.LoadX509KeyPair(certPath, certKey) + if err != nil { + return nil, err + } + return credentials.NewTLS(&tls.Config{Certificates: []tls.Certificate{cert}}), nil +} + +// New initializes a new permify client using the credentials stored in config. func New(endpoint string) (*permify.Client, error) { + transportCreds, err := transportCredentials(config.CliConfig.CertPath, config.CliConfig.CertKey) + if err != nil { + return nil, err + } + + opts := []grpc.DialOption{grpc.WithTransportCredentials(transportCreds)} + if config.CliConfig.Token != "" { + opts = append(opts, grpc.WithPerRPCCredentials(tokenAuth{token: config.CliConfig.Token})) + } + client, err := permify.NewClient( permify.Config{ Endpoint: endpoint, }, - // Todo: Implement secure call with tls certificate - grpc.WithTransportCredentials(insecure.NewCredentials()), + opts..., ) return client, err } diff --git a/core/client/grpc_test.go b/core/client/grpc_test.go new file mode 100644 index 0000000..6f78144 --- /dev/null +++ b/core/client/grpc_test.go @@ -0,0 +1,42 @@ +package client + +import ( + "context" + "path/filepath" + "testing" +) + +func TestTransportCredentialsInsecureWhenNoCert(t *testing.T) { + creds, err := transportCredentials("", "") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if creds == nil { + t.Fatal("expected non-nil credentials") + } + if got := creds.Info().SecurityProtocol; got != "insecure" { + t.Fatalf("expected insecure security protocol, got %q", got) + } +} + +func TestTransportCredentialsErrorsOnMissingCert(t *testing.T) { + dir := t.TempDir() + _, err := transportCredentials(filepath.Join(dir, "missing.crt"), filepath.Join(dir, "missing.key")) + if err == nil { + t.Fatal("expected error for missing certificate files") + } +} + +func TestTokenAuthMetadata(t *testing.T) { + auth := tokenAuth{token: "secret"} + md, err := auth.GetRequestMetadata(context.Background()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if md["authorization"] != "Bearer secret" { + t.Fatalf("unexpected authorization header: %q", md["authorization"]) + } + if auth.RequireTransportSecurity() { + t.Fatal("expected RequireTransportSecurity to be false") + } +} diff --git a/core/config/config.go b/core/config/config.go index c9bdebb..9375bdc 100644 --- a/core/config/config.go +++ b/core/config/config.go @@ -25,9 +25,12 @@ type ProfileConfigs struct { // CoreConfig is the config struct type CoreConfig struct { - PermifyURL string `yaml:"permify_url"` - Tenant string `yaml:"tenant"` - SslEnabled bool `yaml:"-"` + PermifyURL string `yaml:"permify_url"` + Tenant string `yaml:"tenant"` + Token string `yaml:"token"` + CertPath string `yaml:"cert_path"` + CertKey string `yaml:"cert_key"` + SslEnabled bool `yaml:"-"` } // IsConfigured checks if permctl cli has been configured