110 lines
2.8 KiB
Go
110 lines
2.8 KiB
Go
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)
|
|
}
|
|
}
|