Files
f.weber ae30ba1240 feat(vault-tui): implement KV client and service for managing secrets
- Added internal/vault/client.go for creating a Vault client with configuration settings.
- Introduced internal/vault/errors.go to classify Vault API errors for better UI handling.
- Created internal/vault/kv.go to manage KV secrets, including listing, reading, writing, and deleting operations.
- Implemented internal/vault/mounts.go to list and describe secret engine mounts.
- Developed internal/vault/service.go to provide a unified entry point for Vault operations.
- Added internal/vault/kv_test.go for comprehensive testing of KV operations.
- Introduced internal/ui/toast.go for transient notifications in the UI.
- Added renovate.json for dependency management and updates.
2026-08-14 11:09:03 +02:00

108 lines
3.4 KiB
Go

package oidc
import (
"fmt"
"os"
"os/exec"
"runtime"
"strings"
)
// openBrowser tries, in order: an explicit command override, $BROWSER, then
// platform-appropriate auto-detection. It exists instead of
// github.com/pkg/browser because that package shells out to xdg-open, which
// is absent on a bare WSL2 install (verified: no xdg-open, no
// xclip/wl-copy, no wslview on this project's own dev machine) — there,
// the working option is handing the URL to the Windows side via
// powershell.exe or cmd.exe.
//
// override, when non-empty, is treated as a command template: "%s" is
// replaced with the URL if present, otherwise the URL is appended as the
// final argument. It is run through the shell so users can supply
// something like `firefox --new-tab %s`.
func openBrowser(override, url string) error {
if override != "" {
return runShell(substituteOrAppend(override, url))
}
if b := os.Getenv("BROWSER"); b != "" {
return runShell(substituteOrAppend(b, url))
}
for _, cand := range candidates(url) {
cmd := exec.Command(cand[0], cand[1:]...)
if err := cmd.Start(); err == nil {
// Don't Wait(): a real browser backgrounds itself, and waiting on
// e.g. `cmd.exe /c start` (which returns immediately anyway) is
// harmless, but waiting on something that stays foregrounded
// would block the login flow.
return nil
}
}
return fmt.Errorf("no working browser launcher found for %s (tried: %s)", runtime.GOOS, candidateNames(url))
}
// candidates returns launcher argv lists to try in order, most-specific
// (and most likely to actually work on this host) first.
func candidates(url string) [][]string {
var out [][]string
if isWSL() {
out = append(out,
[]string{"wslview", url},
[]string{"powershell.exe", "-NoProfile", "-NonInteractive", "-Command", "Start-Process", quoteForPowerShell(url)},
[]string{"cmd.exe", "/c", "start", "", url},
)
}
switch runtime.GOOS {
case "darwin":
out = append(out, []string{"open", url})
case "windows":
out = append(out, []string{"cmd", "/c", "start", "", url},
[]string{"rundll32", "url.dll,FileProtocolHandler", url})
default:
out = append(out, []string{"xdg-open", url}, []string{"x-www-browser", url})
}
return out
}
func candidateNames(url string) string {
var names []string
for _, c := range candidates(url) {
names = append(names, c[0])
}
return strings.Join(names, ", ")
}
// isWSL detects WSL1/2 by checking /proc/version for the "microsoft"
// marker Microsoft's kernel build injects there — the standard, widely
// used detection technique since there's no dedicated syscall for it.
func isWSL() bool {
if runtime.GOOS != "linux" {
return false
}
b, err := os.ReadFile("/proc/version")
if err != nil {
return false
}
v := strings.ToLower(string(b))
return strings.Contains(v, "microsoft") || strings.Contains(v, "wsl")
}
func substituteOrAppend(template, url string) string {
if strings.Contains(template, "%s") {
return fmt.Sprintf(template, url)
}
return template + " " + url
}
func runShell(command string) error {
cmd := exec.Command("sh", "-c", command)
return cmd.Start()
}
// quoteForPowerShell wraps url in single quotes for use inside a
// -Command argument; OIDC auth URLs are server-generated and may contain
// characters PowerShell would otherwise interpret.
func quoteForPowerShell(url string) string {
return "'" + strings.ReplaceAll(url, "'", "''") + "'"
}