diff --git a/cmd/keepassgo-browser-bridge/main.go b/cmd/keepassgo-browser-bridge/main.go index 2934a83..96ab4d5 100644 --- a/cmd/keepassgo-browser-bridge/main.go +++ b/cmd/keepassgo-browser-bridge/main.go @@ -14,7 +14,14 @@ import ( "google.golang.org/grpc" ) +type bridgeConfig struct { + grpcAddr string +} + func main() { + cfg := bridgeConfig{ + grpcAddr: resolveGlobalGRPCAddr(os.Args[1:]), + } if len(os.Args) > 1 { switch strings.TrimSpace(os.Args[1]) { case "install-native-host": @@ -23,13 +30,13 @@ func main() { } return case "status": - if err := runStatus(os.Args[2:]); err != nil { + if err := runStatus(cfg, stripGlobalGRPCAddrFlags(os.Args[2:])); err != nil { fail(err) } return } } - if err := runNativeMessage(); err != nil { + if err := runNativeMessage(cfg); err != nil { _ = browserbridge.WriteResponse(os.Stdout, browserbridge.Response{Success: false, Error: err.Error()}) os.Exit(1) } @@ -80,50 +87,79 @@ func runInstallNativeHost(args []string) error { return nil } -func runStatus(args []string) error { +func runStatus(cfg bridgeConfig, args []string) error { fs := flag.NewFlagSet("status", flag.ContinueOnError) - grpcAddr := fs.String("grpc-addr", grpcaddr.Default(runtime.GOOS), "KeePassGO local gRPC address") token := fs.String("token", "", "KeePassGO API bearer token") if err := fs.Parse(args); err != nil { return err } req := browserbridge.Request{ Action: "status", - GRPCAddress: strings.TrimSpace(*grpcAddr), BearerToken: strings.TrimSpace(*token), } - conn, client, ctx, err := dialBridge(context.Background(), req) + conn, client, ctx, err := dialBridge(context.Background(), cfg, req) if err != nil { return err } defer func() { _ = conn.Close() }() - resp := browserbridge.HandleRequest(ctx, req, client) + resp := browserbridge.HandleRequest(ctx, req, cfg.grpcAddr, client) enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") return enc.Encode(resp) } -func runNativeMessage() error { +func runNativeMessage(cfg bridgeConfig) error { req, err := browserbridge.ReadRequest(os.Stdin) if err != nil { return err } - conn, client, ctx, err := dialBridge(context.Background(), req) + conn, client, ctx, err := dialBridge(context.Background(), cfg, req) if err != nil { return browserbridge.WriteResponse(os.Stdout, browserbridge.Response{Success: false, Error: err.Error()}) } defer func() { _ = conn.Close() }() - return browserbridge.WriteResponse(os.Stdout, browserbridge.HandleRequest(ctx, req, client)) + return browserbridge.WriteResponse(os.Stdout, browserbridge.HandleRequest(ctx, req, cfg.grpcAddr, client)) } -func dialBridge(ctx context.Context, req browserbridge.Request) (*grpc.ClientConn, *browserbridge.GRPCClient, context.Context, error) { - return browserbridge.DialRequest(ctx, req) +func dialBridge(ctx context.Context, cfg bridgeConfig, req browserbridge.Request) (*grpc.ClientConn, *browserbridge.GRPCClient, context.Context, error) { + return browserbridge.DialRequest(ctx, req, cfg.grpcAddr) } func defaultBinaryPath() (string, error) { return browserbridge.ResolveBridgeBinaryPath("") } +func resolveGlobalGRPCAddr(args []string) string { + addr := grpcaddr.Default(runtime.GOOS) + for i := 0; i < len(args); i++ { + arg := strings.TrimSpace(args[i]) + switch { + case arg == "--grpc-addr" && i+1 < len(args): + return strings.TrimSpace(args[i+1]) + case strings.HasPrefix(arg, "--grpc-addr="): + return strings.TrimSpace(strings.TrimPrefix(arg, "--grpc-addr=")) + } + } + return addr +} + +func stripGlobalGRPCAddrFlags(args []string) []string { + out := make([]string, 0, len(args)) + for i := 0; i < len(args); i++ { + arg := strings.TrimSpace(args[i]) + switch { + case arg == "--grpc-addr" && i+1 < len(args): + i++ + continue + case strings.HasPrefix(arg, "--grpc-addr="): + continue + default: + out = append(out, args[i]) + } + } + return out +} + func fail(err error) { fmt.Fprintln(os.Stderr, err) os.Exit(1) diff --git a/internal/browserbridge/bridge.go b/internal/browserbridge/bridge.go index 4f18fcc..987134c 100644 --- a/internal/browserbridge/bridge.go +++ b/internal/browserbridge/bridge.go @@ -29,7 +29,6 @@ const ( type Request struct { Action string `json:"action"` - GRPCAddress string `json:"grpcAddress,omitempty"` BearerToken string `json:"bearerToken,omitempty"` URL string `json:"url,omitempty"` EntryID string `json:"entryId,omitempty"` @@ -139,9 +138,9 @@ func WriteResponse(w io.Writer, resp Response) error { return err } -func (r Request) Connection() (Connection, error) { +func (r Request) Connection(grpcAddr string) (Connection, error) { return normalizeConnection(Connection{ - GRPCAddress: strings.TrimSpace(r.GRPCAddress), + GRPCAddress: strings.TrimSpace(grpcAddr), BearerToken: strings.TrimSpace(r.BearerToken), }) } @@ -158,8 +157,8 @@ func normalizeConnection(conn Connection) (Connection, error) { return conn, nil } -func HandleRequest(ctx context.Context, req Request, client Client) Response { - conn, err := req.Connection() +func HandleRequest(ctx context.Context, req Request, grpcAddr string, client Client) Response { + conn, err := req.Connection(grpcAddr) if err != nil { return Response{Success: false, Error: err.Error()} } diff --git a/internal/browserbridge/bridge_test.go b/internal/browserbridge/bridge_test.go index ca05759..59fd806 100644 --- a/internal/browserbridge/bridge_test.go +++ b/internal/browserbridge/bridge_test.go @@ -23,7 +23,6 @@ func TestReadRequestAndWriteResponse(t *testing.T) { var input bytes.Buffer body, err := json.Marshal(Request{ Action: "find-logins", - GRPCAddress: "127.0.0.1:47777", BearerToken: "secret", URL: "https://example.invalid/login", }) @@ -44,8 +43,8 @@ func TestReadRequestAndWriteResponse(t *testing.T) { if req.Action != "find-logins" || req.BearerToken != "secret" { t.Fatalf("ReadRequest() = %#v, want action and token preserved", req) } - if conn, err := req.Connection(); err != nil || conn.GRPCAddress != "127.0.0.1:47777" { - t.Fatalf("req.Connection() = (%#v, %v), want explicit tcp address preserved", conn, err) + if conn, err := req.Connection("127.0.0.1:47777"); err != nil || conn.GRPCAddress != "127.0.0.1:47777" { + t.Fatalf("req.Connection(127.0.0.1:47777) = (%#v, %v), want explicit tcp address preserved", conn, err) } var output bytes.Buffer @@ -81,7 +80,7 @@ func TestHandleRequestFindLogins(t *testing.T) { Action: "find-logins", BearerToken: "secret", URL: "https://vault.example.invalid/login", - }, client) + }, "", client) if !resp.Success { t.Fatalf("HandleRequest() success = false, error = %q", resp.Error) } @@ -107,7 +106,7 @@ func TestHandleRequestStatusIncludesPendingApprovalCounts(t *testing.T) { resp := HandleRequest(context.Background(), Request{ Action: "status", BearerToken: "secret", - }, client) + }, "", client) if !resp.Success { t.Fatalf("HandleRequest(status) success = false, error = %q", resp.Error) } @@ -138,7 +137,7 @@ func TestHandleRequestGetLogin(t *testing.T) { BearerToken: "secret", EntryID: "vault-console", URL: "https://vault.example.invalid/login", - }, client) + }, "", client) if !resp.Success { t.Fatalf("HandleRequest() success = false, error = %q", resp.Error) } @@ -158,7 +157,7 @@ func TestHandleRequestFindLoginsInfersLockedStatusFromRPC(t *testing.T) { Action: "find-logins", BearerToken: "secret", URL: "https://vault.example.invalid/login", - }, client) + }, "", client) if !resp.Success { t.Fatalf("HandleRequest(find-logins locked) success = false, error = %q", resp.Error) } @@ -173,7 +172,7 @@ func TestHandleRequestFindLoginsInfersLockedStatusFromRPC(t *testing.T) { func TestHandleRequestRequiresBearerToken(t *testing.T) { t.Parallel() - resp := HandleRequest(context.Background(), Request{Action: "status"}, &fakeClient{}) + resp := HandleRequest(context.Background(), Request{Action: "status"}, "", &fakeClient{}) if resp.Success { t.Fatal("HandleRequest().Success = true, want false without token") } @@ -183,9 +182,9 @@ func TestRequestConnectionDefaultsAddress(t *testing.T) { t.Parallel() req := Request{Action: "status", BearerToken: "secret"} - conn, err := req.Connection() + conn, err := req.Connection("") if err != nil { - t.Fatalf("Connection() error = %v", err) + t.Fatalf("Connection(\"\") error = %v", err) } if conn.GRPCAddress == "" { t.Fatal("Connection().GRPCAddress = empty, want default address") diff --git a/internal/browserbridge/client.go b/internal/browserbridge/client.go index e5ef206..8d0f01f 100644 --- a/internal/browserbridge/client.go +++ b/internal/browserbridge/client.go @@ -17,8 +17,8 @@ type GRPCClient struct { client keepassgov1.VaultServiceClient } -func DialRequest(ctx context.Context, req Request) (*grpc.ClientConn, *GRPCClient, context.Context, error) { - conn, err := req.Connection() +func DialRequest(ctx context.Context, req Request, grpcAddr string) (*grpc.ClientConn, *GRPCClient, context.Context, error) { + conn, err := req.Connection(grpcAddr) if err != nil { return nil, nil, nil, err }