package main import ( "context" "flag" "fmt" "net" "os" "strings" "time" keepassgov1 "git.julianfamily.org/keepassgo/proto/keepassgo/v1" "google.golang.org/grpc" ) type validationServer struct { keepassgov1.UnimplementedVaultServiceServer statePath string pageURL string } func readState(path string) string { data, err := os.ReadFile(path) if err != nil { return "idle" } return strings.TrimSpace(string(data)) } func writeState(path, value string) { _ = os.WriteFile(path, []byte(value), 0o644) } func (s *validationServer) GetSessionStatus(context.Context, *keepassgov1.GetSessionStatusRequest) (*keepassgov1.GetSessionStatusResponse, error) { pending := uint32(0) if readState(s.statePath) == "pending" { pending = 1 } return &keepassgov1.GetSessionStatusResponse{ Locked: false, EntryCount: 1, PendingApprovalCount: pending, TokenPendingApprovalCount: pending, }, nil } func (s *validationServer) FindBrowserLogins(context.Context, *keepassgov1.FindBrowserLoginsRequest) (*keepassgov1.FindBrowserLoginsResponse, error) { return &keepassgov1.FindBrowserLoginsResponse{ Matches: []*keepassgov1.BrowserLoginMatch{ { Id: "vault-console", Title: "Vault Console", Username: "dannyocean", Url: s.pageURL, Path: []string{"Root", "Crew"}, Quality: "exact-host", }, }, }, nil } func (s *validationServer) GetBrowserCredential(ctx context.Context, req *keepassgov1.GetBrowserCredentialRequest) (*keepassgov1.GetBrowserCredentialResponse, error) { writeState(s.statePath, "pending") ticker := time.NewTicker(200 * time.Millisecond) defer ticker.Stop() timeout := time.After(20 * time.Second) for { select { case <-ctx.Done(): return nil, ctx.Err() case <-timeout: return nil, fmt.Errorf("timed out waiting for browser-approval state") case <-ticker.C: if readState(s.statePath) == "approved" { writeState(s.statePath, "done") return &keepassgov1.GetBrowserCredentialResponse{ Id: req.GetId(), Username: "dannyocean", Password: "token-1", Url: s.pageURL, }, nil } } } } func main() { listenAddr := flag.String("listen", "127.0.0.1:47779", "listen address") statePath := flag.String("state", "", "path to mutable validation state file") pageURL := flag.String("page-url", "http://127.0.0.1:18080/login.html", "login page URL returned by the stub") flag.Parse() if strings.TrimSpace(*statePath) == "" { panic("validation state file is required") } listener, err := net.Listen("tcp", strings.TrimSpace(*listenAddr)) if err != nil { panic(err) } server := grpc.NewServer() keepassgov1.RegisterVaultServiceServer(server, &validationServer{ statePath: strings.TrimSpace(*statePath), pageURL: strings.TrimSpace(*pageURL), }) if err := server.Serve(listener); err != nil { panic(err) } }