Expose API approval prompts to app state and UI

This commit is contained in:
Joe Julian
2026-03-29 23:14:39 -07:00
parent f77a185e46
commit 74dfe3f3d0
4 changed files with 298 additions and 0 deletions
+23
View File
@@ -6,6 +6,8 @@ import (
"slices"
"strings"
"git.julianfamily.org/keepassgo/apiapproval"
"git.julianfamily.org/keepassgo/apitokens"
"git.julianfamily.org/keepassgo/vault"
"git.julianfamily.org/keepassgo/webdav"
)
@@ -81,8 +83,14 @@ type RemoteOpenableSession interface {
OpenRemote(webdav.Client, string, vault.MasterKey) error
}
type ApprovalManager interface {
Pending() []apiapproval.Request
Resolve(string, apiapproval.Outcome) (apiapproval.Request, *apitokens.PolicyRule, error)
}
type State struct {
Session CurrentSession
Approvals ApprovalManager
Section Section
CurrentPath []string
SearchQuery string
@@ -92,6 +100,21 @@ type State struct {
ErrorMessage string
}
func (s *State) PendingApprovals() []apiapproval.Request {
if s.Approvals == nil {
return nil
}
return s.Approvals.Pending()
}
func (s *State) ResolveApproval(id string, outcome apiapproval.Outcome) error {
if s.Approvals == nil {
return fmt.Errorf("approval manager is not configured")
}
_, _, err := s.Approvals.Resolve(id, outcome)
return err
}
func (s *State) ShowSection(section Section) {
s.Section = section
s.CurrentPath = nil
+48
View File
@@ -5,6 +5,8 @@ import (
"slices"
"testing"
"git.julianfamily.org/keepassgo/apiapproval"
"git.julianfamily.org/keepassgo/apitokens"
"git.julianfamily.org/keepassgo/session"
"git.julianfamily.org/keepassgo/vault"
"git.julianfamily.org/keepassgo/webdav"
@@ -41,6 +43,36 @@ func TestVisibleEntriesFollowsCurrentPathWithoutSearch(t *testing.T) {
}
}
func TestPendingApprovalsReturnsManagerRequests(t *testing.T) {
t.Parallel()
state := State{
Approvals: &stubApprovalManager{
pending: []apiapproval.Request{
{ID: "approval-1", TokenName: "CLI", Operation: apitokens.OperationListEntries},
},
},
}
got := state.PendingApprovals()
if len(got) != 1 || got[0].ID != "approval-1" {
t.Fatalf("PendingApprovals() = %#v, want approval-1", got)
}
}
func TestResolveApprovalDelegatesToManager(t *testing.T) {
t.Parallel()
manager := &stubApprovalManager{}
state := State{Approvals: manager}
if err := state.ResolveApproval("approval-1", apiapproval.OutcomeAllowPermanent); err != nil {
t.Fatalf("ResolveApproval() error = %v", err)
}
if manager.lastID != "approval-1" || manager.lastOutcome != apiapproval.OutcomeAllowPermanent {
t.Fatalf("ResolveApproval() delegated (%q, %q), want (approval-1, allow-permanent)", manager.lastID, manager.lastOutcome)
}
}
func TestVisibleEntriesUsesGlobalSearchWhenQueryPresent(t *testing.T) {
t.Parallel()
@@ -1378,3 +1410,19 @@ func (s *lifecycleStubSession) ChangeMasterKey(key vault.MasterKey) error {
s.changedKey = key
return nil
}
type stubApprovalManager struct {
pending []apiapproval.Request
lastID string
lastOutcome apiapproval.Outcome
}
func (s stubApprovalManager) Pending() []apiapproval.Request {
return append([]apiapproval.Request(nil), s.pending...)
}
func (s *stubApprovalManager) Resolve(id string, outcome apiapproval.Outcome) (apiapproval.Request, *apitokens.PolicyRule, error) {
s.lastID = id
s.lastOutcome = outcome
return apiapproval.Request{ID: id}, nil, nil
}