Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion tp/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ import (
"github.com/superfly/macaroon/bundle"
)

// ErrMissingUserURLCallback is returned when a third party asks for the user
// to be sent to a URL in their browser, but the client has no
// WithUserURLCallback to send them with. Callers that can fall back to an
// interactive flow can test for it with errors.Is, including on the joined
// error FetchDischargeTokens returns for a whole set of tickets.
var ErrMissingUserURLCallback = errors.New("missing user-url callback")

type ClientOption func(*Client)

// WithHTTP specifies the HTTP client to use for requests to third parties.
Expand Down Expand Up @@ -321,7 +328,7 @@ func (c *Client) doUserInteractive(ctx context.Context, ui *jsonUserInteractive)
return "", errors.New("bad discharge response")
}
if c.userURLCallback == nil {
return "", errors.New("missing user-url callback")
return "", ErrMissingUserURLCallback
}

if err := c.openUserInteractiveURL(ctx, ui.UserURL); err != nil {
Expand Down
18 changes: 18 additions & 0 deletions tp/tp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package tp

import (
"context"
"errors"
"net/http"
"net/http/httptest"
"strings"
Expand Down Expand Up @@ -202,6 +203,23 @@ func TestTP(t *testing.T) {
assert.Equal(t, []string{"fp-cav", "dis-cav"}, cavs)
})

t.Run("user interactive response without a callback", func(t *testing.T) {
handleInit = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := CaveatsFromRequest(r)
assert.NoError(t, err)

tp.RespondUserInteractive(w, r)
})

hdr := genFP(t, tp, myCaveat("fp-cav"))

// the error comes back joined with whatever the other tickets did, so
// this is the check a caller has to be able to make to decide whether
// retrying with a callback is worth it.
_, err = NewClient(firstPartyLocation).FetchDischargeTokens(context.Background(), hdr)
assert.True(t, errors.Is(err, ErrMissingUserURLCallback), "got %v", err)
})

t.Run("user interactive response", func(t *testing.T) {
userSecret := ""

Expand Down
Loading