Complete browser extension gRPC flow
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user