lockbox

password manager
Log | Files | Refs | README | LICENSE

commit 5596d2a8798af49d3a09478cf8535b105fd267bb
parent 47651e781b0d093678e5fcbb415d6dd00b9933c0
Author: Sean Enck <sean@ttypty.com>
Date:   Sat, 14 Jun 2025 09:12:21 -0400

introduce feature flags

Diffstat:
Mcmd/lb/main_test.go | 10++++++++++
Mcmd/lb/tests/expected.log | 3+++
Minternal/app/completions/core.go | 11+++++++++--
Minternal/app/help/core.go | 23++++++++++++++---------
Minternal/app/totp.go | 4++++
Ainternal/config/features/clip.go | 7+++++++
Ainternal/config/features/flags.go | 24++++++++++++++++++++++++
Ainternal/config/features/flags_test.go | 26++++++++++++++++++++++++++
Ainternal/config/features/totp.go | 7+++++++
Minternal/platform/clip/core.go | 6+++++-
Mjustfile | 9+++++++--
11 files changed, 116 insertions(+), 14 deletions(-)

diff --git a/cmd/lb/main_test.go b/cmd/lb/main_test.go @@ -105,6 +105,12 @@ func (r runner) raw(pipeIn, command, stdout, stderr string) error { return cmd.Run() } +func (r runner) feature(command, useBinary string) error { + text := fmt.Sprintf("%s %s >> %s 2>&1", fmt.Sprintf("%s-%s", binary, useBinary), command, r.log) + cmd := exec.Command("/bin/sh", "-c", text) + return cmd.Run() +} + func (r runner) logAppend(command string) error { return exec.Command("/bin/sh", "-c", fmt.Sprintf("%s >> %s", command, r.log)).Run() } @@ -349,6 +355,10 @@ func test(profile string) error { r.section("env") r.run("", fmt.Sprintf("vars | sed 's#/%s#/datadir#g' | grep -v CREDENTIALS | sort", profile)) + r.section("features") + r.feature("clip abc", "noclip") + r.feature("totp ls", "nototp") + // cleanup and diff results tmpFile := fmt.Sprintf("%s.tmp", r.log) for _, item := range []string{"'s/\"modtime\": \"[0-9].*$/\"modtime\": \"XXXX-XX-XX\",/g'", "'s/^[0-9][0-9][0-9][0-9][0-9][0-9]$/XXXXXX/g'"} { diff --git a/cmd/lb/tests/expected.log b/cmd/lb/tests/expected.log @@ -330,3 +330,6 @@ LOCKBOX_JSON_HASH_LENGTH=3 LOCKBOX_JSON_MODE=hash LOCKBOX_STORE=testdata/datadir/pass.kdbx LOCKBOX_TOTP_TIMEOUT=1 +features +unable to get clipboard: clip feature is disabled +totp feature is disabled diff --git a/internal/app/completions/core.go b/internal/app/completions/core.go @@ -11,6 +11,7 @@ import ( "text/template" "git.sr.ht/~enckse/lockbox/internal/app/commands" + "git.sr.ht/~enckse/lockbox/internal/config/features" ) type ( @@ -67,8 +68,14 @@ func Generate(completionType, exe string) ([]string, error) { ExportCommand: fmt.Sprintf("%s %s %s", exe, commands.Env, commands.Completions), } - c.Options = []string{commands.Help, commands.List, commands.Show, commands.Version, commands.JSON, commands.Groups, commands.Clip, commands.TOTP, commands.Move, commands.Remove, commands.Insert, commands.Unset} - c.TOTPSubCommands = []string{commands.TOTPMinimal, commands.TOTPOnce, commands.TOTPShow, commands.TOTPURL, commands.TOTPSeed, commands.TOTPClip} + c.Options = []string{commands.Help, commands.List, commands.Show, commands.Version, commands.JSON, commands.Groups, commands.Move, commands.Remove, commands.Insert, commands.Unset} + if features.CanClip() { + c.Options = append(c.Options, commands.Clip) + } + if features.CanTOTP() { + c.Options = append(c.Options, commands.TOTP) + c.TOTPSubCommands = []string{commands.TOTPMinimal, commands.TOTPOnce, commands.TOTPShow, commands.TOTPURL, commands.TOTPSeed, commands.TOTPClip} + } sort.Strings(c.Options) sort.Strings(c.TOTPSubCommands) diff --git a/internal/app/help/core.go b/internal/app/help/core.go @@ -12,6 +12,7 @@ import ( "git.sr.ht/~enckse/lockbox/internal/app/commands" "git.sr.ht/~enckse/lockbox/internal/config" + "git.sr.ht/~enckse/lockbox/internal/config/features" "git.sr.ht/~enckse/lockbox/internal/kdbx" "git.sr.ht/~enckse/lockbox/internal/output" ) @@ -75,7 +76,9 @@ func Usage(verbose bool, exe string) ([]string, error) { isGroup = "group" ) var results []string - results = append(results, command(commands.Clip, isEntry, "copy the entry's value into the clipboard")) + if features.CanClip() { + results = append(results, command(commands.Clip, isEntry, "copy the entry's value into the clipboard")) + } results = append(results, command(commands.Completions, "<shell>", "generate completions via auto-detection")) for _, c := range commands.CompletionTypes { results = append(results, subCommand(commands.Completions, c, "", fmt.Sprintf("generate %s completions", c))) @@ -93,14 +96,16 @@ func Usage(verbose bool, exe string) ([]string, error) { results = append(results, command(commands.ReKey, "", "rekey/reinitialize the database credentials")) results = append(results, command(commands.Remove, isGroup, "remove an entry from the store")) results = append(results, command(commands.Show, isEntry, "show the entry's value")) - results = append(results, command(commands.TOTP, isEntry, "display an updating totp generated code")) - results = append(results, subCommand(commands.TOTP, commands.TOTPClip, isEntry, "copy totp code to clipboard")) - results = append(results, subCommand(commands.TOTP, commands.TOTPList, isFilter, "list entries with totp settings")) - results = append(results, subCommand(commands.TOTP, commands.TOTPOnce, isEntry, "display the first generated code")) - results = append(results, subCommand(commands.TOTP, commands.TOTPMinimal, isEntry, "display one generated code (no details)")) - results = append(results, subCommand(commands.TOTP, commands.TOTPURL, isEntry, "display TOTP url information")) - results = append(results, subCommand(commands.TOTP, commands.TOTPSeed, isEntry, "show the TOTP seed (only)")) - results = append(results, subCommand(commands.TOTP, commands.TOTPShow, isEntry, "show the totp entry")) + if features.CanTOTP() { + results = append(results, command(commands.TOTP, isEntry, "display an updating totp generated code")) + results = append(results, subCommand(commands.TOTP, commands.TOTPClip, isEntry, "copy totp code to clipboard")) + results = append(results, subCommand(commands.TOTP, commands.TOTPList, isFilter, "list entries with totp settings")) + results = append(results, subCommand(commands.TOTP, commands.TOTPOnce, isEntry, "display the first generated code")) + results = append(results, subCommand(commands.TOTP, commands.TOTPMinimal, isEntry, "display one generated code (no details)")) + results = append(results, subCommand(commands.TOTP, commands.TOTPURL, isEntry, "display TOTP url information")) + results = append(results, subCommand(commands.TOTP, commands.TOTPSeed, isEntry, "show the TOTP seed (only)")) + results = append(results, subCommand(commands.TOTP, commands.TOTPShow, isEntry, "show the totp entry")) + } results = append(results, command(commands.Version, "", "display version information")) sort.Strings(results) usage := []string{fmt.Sprintf("%s usage:", exe)} diff --git a/internal/app/totp.go b/internal/app/totp.go @@ -14,6 +14,7 @@ import ( "git.sr.ht/~enckse/lockbox/internal/app/commands" "git.sr.ht/~enckse/lockbox/internal/config" + "git.sr.ht/~enckse/lockbox/internal/config/features" "git.sr.ht/~enckse/lockbox/internal/kdbx" "git.sr.ht/~enckse/lockbox/internal/platform/clip" ) @@ -208,6 +209,9 @@ func (args *TOTPArguments) Do(opts TOTPOptions) error { // NewTOTPArguments will parse the input arguments func NewTOTPArguments(args []string) (*TOTPArguments, error) { + if !features.CanTOTP() { + return nil, features.NewError("totp") + } if len(args) == 0 { return nil, errors.New("not enough arguments for totp") } diff --git a/internal/config/features/clip.go b/internal/config/features/clip.go @@ -0,0 +1,7 @@ +//go:build noclip + +package features + +func init() { + clipFeature = false +} diff --git a/internal/config/features/flags.go b/internal/config/features/flags.go @@ -0,0 +1,24 @@ +// Package features controls build flag features +package features + +import "fmt" + +var ( + clipFeature = true + totpFeature = true +) + +// CanTOTP indicates if TOTP is enabled +func CanTOTP() bool { + return totpFeature +} + +// CanClip indicates if clip(board) is enabled +func CanClip() bool { + return clipFeature +} + +// NewError creates an error if a feature is not enabled +func NewError(name string) error { + return fmt.Errorf("%s feature is disabled", name) +} diff --git a/internal/config/features/flags_test.go b/internal/config/features/flags_test.go @@ -0,0 +1,26 @@ +package features_test + +import ( + "testing" + + "git.sr.ht/~enckse/lockbox/internal/config/features" +) + +func TestCanTOTP(t *testing.T) { + if !features.CanTOTP() { + t.Error("flag should be on by default") + } +} + +func TestCanClip(t *testing.T) { + if !features.CanClip() { + t.Error("flag should be on by default") + } +} + +func TestNewError(t *testing.T) { + err := features.NewError("abc") + if err == nil || err.Error() != "abc feature is disabled" { + t.Errorf("invalid error: %v", err) + } +} diff --git a/internal/config/features/totp.go b/internal/config/features/totp.go @@ -0,0 +1,7 @@ +//go:build nototp + +package features + +func init() { + totpFeature = false +} diff --git a/internal/platform/clip/core.go b/internal/platform/clip/core.go @@ -8,6 +8,7 @@ import ( "git.sr.ht/~enckse/lockbox/internal/app/commands" "git.sr.ht/~enckse/lockbox/internal/config" + "git.sr.ht/~enckse/lockbox/internal/config/features" "git.sr.ht/~enckse/lockbox/internal/platform" osc "github.com/aymanbagabas/go-osc52" ) @@ -30,8 +31,11 @@ func newBoard(copying, pasting []string) (Board, error) { return Board{copying: copying, pasting: pasting, MaxTime: maximum, isOSC52: false}, nil } -// New will retrieve the commands to use for clipboard operations. +// New creates a new clipboard func New() (Board, error) { + if !features.CanClip() { + return Board{}, features.NewError("clip") + } overridePaste := config.EnvClipPaste.Get() overrideCopy := config.EnvClipCopy.Get() setPaste := len(overridePaste) > 0 diff --git a/justfile b/justfile @@ -6,21 +6,26 @@ ldflags := env_var_or_default("LDFLAGS", "") gotest := "LOCKBOX_CONFIG_TOML=fake go test" files := `find . -type f -name "*.go" | tr '\n' ' '` cmd := "cmd/lb" +tags := "" default: build build: mkdir -p "{{target}}" - go build {{goflags}} -ldflags "{{ldflags}} -X main.version={{version}}" -o "{{object}}" {{cmd}}/main.go + go build {{goflags}} -tags={{tags}} -ldflags "{{ldflags}} -X main.version={{version}}" -o "{{object}}" {{cmd}}/main.go unittest: {{gotest}} ./... check: unittest tests -tests: build +tests: features {{gotest}} {{cmd}}/main_test.go +features: build + just tags=noclip object={{target}}/lb-noclip + just tags=nototp object={{target}}/lb-nototp + clean: rm -f "{{object}}" find internal/ {{cmd}} -type f -wholename "*testdata*" -delete