diff --git a/core/cli/configure.go b/core/cli/configure.go index a533aba..e2219e6 100644 --- a/core/cli/configure.go +++ b/core/cli/configure.go @@ -124,6 +124,27 @@ func runE(cmd *cobra.Command, _ []string) error { } config.CliConfig.PermifyURL = url config.CliConfig.Tenant = tenantIds[tenant] + + // Request for Token + token, err := tui.StringPrompt("enter your auth token", "", config.CliConfig.Token) + if err != nil { + return err + } + config.CliConfig.Token = token + + // Request for Certificate Paths (Optional) + certPath, err := tui.StringPrompt("enter certificate path (optional)", "", config.CliConfig.CertPath) + if err != nil { + return err + } + config.CliConfig.CertPath = certPath + + certKeyPath, err := tui.StringPrompt("enter certificate key path (optional)", "", config.CliConfig.CertKeyPath) + if err != nil { + return err + } + config.CliConfig.CertKeyPath = certKeyPath + err = config.Write() if err != nil { logger.Log.Error(err) diff --git a/core/client/auth.go b/core/client/auth.go new file mode 100644 index 0000000..0c5af55 --- /dev/null +++ b/core/client/auth.go @@ -0,0 +1,20 @@ +package client + +import ( + "context" +) + +// tokenAuth implements grpc.PerRPCCredentials +type tokenAuth struct { + token string +} + +func (t tokenAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { + return map[string]string{ + "authorization": "Bearer " + t.token, + }, nil +} + +func (t tokenAuth) RequireTransportSecurity() bool { + return false // 允许在非 TLS 下传(视官方服务端需求而定,通常开发环境需要设为 false) +} diff --git a/core/client/grpc.go b/core/client/grpc.go index 11835df..f7f34bd 100644 --- a/core/client/grpc.go +++ b/core/client/grpc.go @@ -2,19 +2,68 @@ package client import ( + "crypto/tls" + "crypto/x509" + "os" + permify "github.com/Permify/permify-go/v1" + "github.com/Permify/permify-cli/core/config" "google.golang.org/grpc" + "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" ) // New initializes a new permify client func New(endpoint string) (*permify.Client, error) { + var opts []grpc.DialOption + + // Handle TLS Configuration + if config.CliConfig.CertPath != "" { + // Load client certificate if provided + var creds credentials.TransportCredentials + if config.CliConfig.CertKeyPath != "" { + cert, err := tls.LoadX509KeyPair(config.CliConfig.CertPath, config.CliConfig.CertKeyPath) + if err != nil { + return nil, err + } + creds = credentials.NewTLS(&tls.Config{ + Certificates: []tls.Certificate{cert}, + }) + } else { + // Load CA cert + caCert, err := os.ReadFile(config.CliConfig.CertPath) + if err != nil { + return nil, err + } + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + creds = credentials.NewTLS(&tls.Config{ + RootCAs: caCertPool, + }) + } + opts = append(opts, grpc.WithTransportCredentials(creds)) + } else { + opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) + } + + // Handle Token Authentication + // Check environment variable first for maximum flexibility + token := os.Getenv("PERMIFY_TOKEN") + if token == "" { + token = config.CliConfig.Token + } + + if token != "" { + opts = append(opts, grpc.WithPerRPCCredentials(tokenAuth{ + token: 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/config/config.go b/core/config/config.go index c9bdebb..35beb2a 100644 --- a/core/config/config.go +++ b/core/config/config.go @@ -25,8 +25,11 @@ type ProfileConfigs struct { // CoreConfig is the config struct type CoreConfig struct { - PermifyURL string `yaml:"permify_url"` - Tenant string `yaml:"tenant"` + PermifyURL string `yaml:"permify_url"` + Tenant string `yaml:"tenant"` + Token string `yaml:"token"` + CertPath string `yaml:"cert_path"` + CertKeyPath string `yaml:"cert_key_path"` SslEnabled bool `yaml:"-"` } diff --git a/core/config/config_test.go b/core/config/config_test.go new file mode 100644 index 0000000..39d7f83 --- /dev/null +++ b/core/config/config_test.go @@ -0,0 +1,41 @@ +package config + +import ( + "os" + "testing" + + "gopkg.in/yaml.v3" +) + +func TestCoreConfigPersistence(t *testing.T) { + tmpFile := "test_config.yaml" + defer os.Remove(tmpFile) + + expected := CoreConfig{ + PermifyURL: "https://localhost:8888", + Tenant: "test-tenant", + Token: "test-token-123", + CertPath: "/path/to/cert", + CertKeyPath: "/path/to/key", + } + + // Test Writing + configs := make(map[string]CoreConfig) + configs["default"] = expected + data, _ := yaml.Marshal(configs) + _ = os.WriteFile(tmpFile, data, 0644) + + // Test Reading + var actualConfigs map[string]CoreConfig + readData, _ := os.ReadFile(tmpFile) + yaml.Unmarshal(readData, &actualConfigs) + + actual := actualConfigs["default"] + + if actual.Token != expected.Token { + t.Errorf("Expected Token %s, got %s", expected.Token, actual.Token) + } + if actual.CertPath != expected.CertPath { + t.Errorf("Expected CertPath %s, got %s", expected.CertPath, actual.CertPath) + } +}