mirror of
https://github.com/lightninglabs/lightning-terminal.git
synced 2026-08-13 12:33:36 +02:00
accounts: add an IDFromCaveates helper
And use that from the existing accountFromMacaroon helper (which will then test the new helper by proxy). We add this helper so that we can use it later on from the sessions package where we want to extract an account ID from a caveat (we wont have a full macaroon available).
This commit is contained in:
parent
2717aa0bc2
commit
03712e6067
3 changed files with 144 additions and 17 deletions
|
|
@ -5,11 +5,14 @@ import (
|
|||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/macaroons"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"gopkg.in/macaroon-bakery.v2/bakery/checkers"
|
||||
"gopkg.in/macaroon.v2"
|
||||
)
|
||||
|
||||
|
|
@ -23,6 +26,15 @@ const (
|
|||
accountMiddlewareName = "lit-account"
|
||||
)
|
||||
|
||||
var (
|
||||
// caveatPrefix is the prefix that is used for custom caveats that are
|
||||
// used by the account system. This prefix is used to identify the
|
||||
// custom caveat and extract the condition (the AccountID) from it.
|
||||
caveatPrefix = []byte(fmt.Sprintf(
|
||||
"%s %s ", macaroons.CondLndCustom, CondAccount,
|
||||
))
|
||||
)
|
||||
|
||||
// Name returns the name of the interceptor.
|
||||
func (s *InterceptorService) Name() string {
|
||||
return accountMiddlewareName
|
||||
|
|
@ -199,22 +211,64 @@ func parseRPCMessage(msg *lnrpc.RPCMessage) (proto.Message, error) {
|
|||
// accountFromMacaroon attempts to extract an account ID from the custom account
|
||||
// caveat in the macaroon.
|
||||
func accountFromMacaroon(mac *macaroon.Macaroon) (*AccountID, error) {
|
||||
// Extract the account caveat from the macaroon.
|
||||
macaroonAccount := macaroons.GetCustomCaveatCondition(mac, CondAccount)
|
||||
if macaroonAccount == "" {
|
||||
// There is no condition that locks the macaroon to an account,
|
||||
// so there is nothing to check.
|
||||
if mac == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// The macaroon is indeed locked to an account. Fetch the account and
|
||||
// validate its balance.
|
||||
accountIDBytes, err := hex.DecodeString(macaroonAccount)
|
||||
// Extract the account caveat from the macaroon.
|
||||
accountID, err := IDFromCaveats(mac.Caveats())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var accountID AccountID
|
||||
copy(accountID[:], accountIDBytes)
|
||||
return &accountID, nil
|
||||
var id *AccountID
|
||||
accountID.WhenSome(func(aID AccountID) {
|
||||
id = &aID
|
||||
})
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// CaveatFromID creates a custom caveat that can be used to bind a macaroon to
|
||||
// a certain account.
|
||||
func CaveatFromID(id AccountID) macaroon.Caveat {
|
||||
condition := checkers.Condition(macaroons.CondLndCustom, fmt.Sprintf(
|
||||
"%s %x", CondAccount, id[:],
|
||||
))
|
||||
|
||||
return macaroon.Caveat{Id: []byte(condition)}
|
||||
}
|
||||
|
||||
// IDFromCaveats attempts to extract an AccountID from the given set of caveats
|
||||
// by looking for the custom caveat that binds a macaroon to a certain account.
|
||||
func IDFromCaveats(caveats []macaroon.Caveat) (fn.Option[AccountID], error) {
|
||||
var accountIDStr string
|
||||
for _, caveat := range caveats {
|
||||
// The caveat id has a format of
|
||||
// "lnd-custom [custom-caveat-name] [custom-caveat-condition]"
|
||||
// and we only want the condition part. If we match the prefix
|
||||
// part we return the condition that comes after the prefix.
|
||||
_, after, found := strings.Cut(
|
||||
string(caveat.Id), string(caveatPrefix),
|
||||
)
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
|
||||
accountIDStr = after
|
||||
}
|
||||
|
||||
if accountIDStr == "" {
|
||||
return fn.None[AccountID](), nil
|
||||
}
|
||||
|
||||
var accountID AccountID
|
||||
accountIDBytes, err := hex.DecodeString(accountIDStr)
|
||||
if err != nil {
|
||||
return fn.None[AccountID](), err
|
||||
}
|
||||
|
||||
copy(accountID[:], accountIDBytes)
|
||||
|
||||
return fn.Some(accountID), nil
|
||||
}
|
||||
|
|
|
|||
78
accounts/interceptor_test.go
Normal file
78
accounts/interceptor_test.go
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
package accounts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/macaroons"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/macaroon-bakery.v2/bakery/checkers"
|
||||
"gopkg.in/macaroon.v2"
|
||||
)
|
||||
|
||||
// TestAccountIDCaveatEmbedding tests that the account ID can be embedded in a
|
||||
// macaroon caveat and extracted from it.
|
||||
func TestAccountIDCaveatEmbedding(t *testing.T) {
|
||||
badCondition := checkers.Condition(macaroons.CondLndCustom, fmt.Sprintf(
|
||||
"%s %s", CondAccount, "invalid hex",
|
||||
))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
caveats []macaroon.Caveat
|
||||
expectedErr string
|
||||
expectedAcct fn.Option[AccountID]
|
||||
}{
|
||||
{
|
||||
name: "valid account ID, single caveat",
|
||||
caveats: []macaroon.Caveat{
|
||||
CaveatFromID(AccountID{1, 2, 3, 4, 5}),
|
||||
},
|
||||
expectedAcct: fn.Some(AccountID{1, 2, 3, 4, 5}),
|
||||
},
|
||||
{
|
||||
name: "valid account ID, single multiple caveats",
|
||||
caveats: []macaroon.Caveat{
|
||||
{Id: []byte("some other caveat")},
|
||||
CaveatFromID(AccountID{1, 2, 3, 4, 5}),
|
||||
{Id: []byte("another one")},
|
||||
},
|
||||
expectedAcct: fn.Some(AccountID{1, 2, 3, 4, 5}),
|
||||
},
|
||||
{
|
||||
name: "invalid account ID",
|
||||
caveats: []macaroon.Caveat{
|
||||
{Id: []byte(badCondition)},
|
||||
},
|
||||
expectedErr: "encoding/hex: invalid",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
acct, err := IDFromCaveats(test.caveats)
|
||||
if test.expectedErr != "" {
|
||||
require.ErrorContains(t, err, test.expectedErr)
|
||||
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
if test.expectedAcct.IsNone() {
|
||||
require.True(t, acct.IsNone())
|
||||
|
||||
return
|
||||
}
|
||||
require.True(t, acct.IsSome())
|
||||
|
||||
test.expectedAcct.WhenSome(func(id AccountID) {
|
||||
acct.WhenSome(func(acct AccountID) {
|
||||
require.Equal(t, id, acct)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -235,12 +235,7 @@ func (s *sessionRpcServer) AddSession(ctx context.Context,
|
|||
return nil, fmt.Errorf("invalid account ID: %v", err)
|
||||
}
|
||||
|
||||
cav := checkers.Condition(macaroons.CondLndCustom, fmt.Sprintf(
|
||||
"%s %x", accounts.CondAccount, id[:],
|
||||
))
|
||||
caveats = append(caveats, macaroon.Caveat{
|
||||
Id: []byte(cav),
|
||||
})
|
||||
caveats = append(caveats, accounts.CaveatFromID(*id))
|
||||
|
||||
// For the custom macaroon type, we use the custom permissions specified
|
||||
// in the request. For the time being, the caveats list will be empty
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue