Skip to content

Commit 8a34189

Browse files
committed
session: assert account exists before linking a session to it
In this commit, we more tightly & explicitly link a session to an account. At a persitance layer, we have always only linked a session to an account by encoding the AccountID within the macaroon caveat that we store with the session. We still keep this persistence the same but now we first ensure that the account exists and we also add an AccountID field to the Session struct.
1 parent 03712e6 commit 8a34189

File tree

5 files changed

+126
-5
lines changed

5 files changed

+126
-5
lines changed

session/interface.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import (
77

88
"github.com/btcsuite/btcd/btcec/v2"
99
"github.com/lightninglabs/lightning-node-connect/mailbox"
10+
"github.com/lightninglabs/lightning-terminal/accounts"
1011
"github.com/lightninglabs/lightning-terminal/macaroons"
12+
"github.com/lightningnetwork/lnd/fn"
1113
"gopkg.in/macaroon-bakery.v2/bakery"
1214
"gopkg.in/macaroon.v2"
1315
)
@@ -117,6 +119,9 @@ type Session struct {
117119
// group of sessions. If this is the very first session in the group
118120
// then this will be the same as ID.
119121
GroupID ID
122+
123+
// AccountID is an optional account that the session has been linked to.
124+
AccountID fn.Option[accounts.AccountID]
120125
}
121126

122127
// buildSession creates a new session with the given user-defined parameters.
@@ -163,6 +168,7 @@ func buildSession(id ID, localPrivKey *btcec.PrivateKey, label string, typ Type,
163168
PrivacyFlags: opts.privacyFlags,
164169
GroupID: groupID,
165170
MacaroonRecipe: opts.macaroonRecipe,
171+
AccountID: opts.accountID,
166172
}
167173

168174
if len(opts.featureConfig) != 0 {
@@ -196,6 +202,9 @@ type sessionOptions struct {
196202
// macaroonRecipe holds the permissions and caveats that should be used
197203
// to bake the macaroon to be used with this session.
198204
macaroonRecipe *MacaroonRecipe
205+
206+
// accountID is an optional account that the session has been linked to.
207+
accountID fn.Option[accounts.AccountID]
199208
}
200209

201210
// defaultSessionOptions returns a new sessionOptions struct with default
@@ -258,6 +267,13 @@ func WithMacaroonRecipe(caveats []macaroon.Caveat, perms []bakery.Op) Option {
258267
}
259268
}
260269

270+
// WithAccount can be used to link the session to an account.
271+
func WithAccount(id accounts.AccountID) Option {
272+
return func(o *sessionOptions) {
273+
o.accountID = fn.Some(id)
274+
}
275+
}
276+
261277
// IDToGroupIndex defines an interface for the session ID to group ID index.
262278
type IDToGroupIndex interface {
263279
// GetGroupID will return the group ID for the given session ID.

session/kvdb_store.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,15 @@ func (db *BoltStore) NewSession(ctx context.Context, label string, typ Type,
217217

218218
sessionKey := getSessionKey(session)
219219

220+
// If an account is being linked, we first need to check that
221+
// it exists.
222+
session.AccountID.WhenSome(func(account accounts.AccountID) {
223+
_, err = db.accounts.Account(ctx, account)
224+
})
225+
if err != nil {
226+
return err
227+
}
228+
220229
if len(sessionBucket.Get(sessionKey)) != 0 {
221230
return fmt.Errorf("session with local public key(%x) "+
222231
"already exists",

session/store_test.go

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,18 @@ package session
22

33
import (
44
"context"
5+
"fmt"
56
"testing"
67
"time"
78

89
"github.com/btcsuite/btcd/btcec/v2"
10+
"github.com/lightninglabs/lightning-terminal/accounts"
911
"github.com/lightningnetwork/lnd/clock"
12+
"github.com/lightningnetwork/lnd/fn"
13+
"github.com/lightningnetwork/lnd/macaroons"
1014
"github.com/stretchr/testify/require"
15+
"gopkg.in/macaroon-bakery.v2/bakery/checkers"
16+
"gopkg.in/macaroon.v2"
1117
)
1218

1319
var testTime = time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
@@ -324,9 +330,49 @@ func TestStateShift(t *testing.T) {
324330
require.ErrorContains(t, err, "illegal session state transition")
325331
}
326332

333+
// TestLinkedAccount tests that linking a session to an account works as
334+
// expected.
335+
func TestLinkedAccount(t *testing.T) {
336+
t.Parallel()
337+
ctx := context.Background()
338+
clock := clock.NewTestClock(testTime)
339+
340+
accts := accounts.NewTestDB(t, clock)
341+
db := NewTestDBWithAccounts(t, clock, accts)
342+
343+
// Reserve a session. Link it to an account that does not yet exist.
344+
// This should fail.
345+
acctID := accounts.AccountID{1, 2, 3, 4}
346+
_, err := reserveSession(db, "session 1", withAccount(acctID))
347+
require.ErrorIs(t, err, accounts.ErrAccNotFound)
348+
349+
// Now, add a new account
350+
acct, err := accts.NewAccount(ctx, 1234, clock.Now().Add(time.Hour), "")
351+
require.NoError(t, err)
352+
353+
// Reserve a session. Link it to the account that was just created.
354+
// This should succeed.
355+
356+
s1, err := reserveSession(db, "session 1", withAccount(acct.ID))
357+
require.NoError(t, err)
358+
require.True(t, s1.AccountID.IsSome())
359+
s1.AccountID.WhenSome(func(id accounts.AccountID) {
360+
require.Equal(t, acct.ID, id)
361+
})
362+
363+
// Make sure that a fetched session includes the account ID.
364+
s1, err = db.GetSessionByID(ctx, s1.ID)
365+
require.NoError(t, err)
366+
require.True(t, s1.AccountID.IsSome())
367+
s1.AccountID.WhenSome(func(id accounts.AccountID) {
368+
require.Equal(t, acct.ID, id)
369+
})
370+
}
371+
327372
type testSessionOpts struct {
328373
groupID *ID
329374
sessType Type
375+
account fn.Option[accounts.AccountID]
330376
}
331377

332378
func defaultTestSessOpts() *testSessionOpts {
@@ -352,6 +398,12 @@ func withType(t Type) testSessionModifier {
352398
}
353399
}
354400

401+
func withAccount(alias accounts.AccountID) testSessionModifier {
402+
return func(s *testSessionOpts) {
403+
s.account = fn.Some(alias)
404+
}
405+
}
406+
355407
func reserveSession(db Store, label string,
356408
mods ...testSessionModifier) (*Session, error) {
357409

@@ -360,13 +412,35 @@ func reserveSession(db Store, label string,
360412
mod(opts)
361413
}
362414

363-
return db.NewSession(
364-
context.Background(), label, opts.sessType,
365-
time.Date(9999, 1, 1, 0, 0, 0, 0, time.UTC),
366-
"foo.bar.baz:1234",
415+
options := []Option{
367416
WithDevServer(),
368417
WithPrivacy(PrivacyFlags{ClearPubkeys}),
369418
WithLinkedGroupID(opts.groupID),
419+
}
420+
421+
opts.account.WhenSome(func(id accounts.AccountID) {
422+
// For now, we manually add the account caveat for bbolt
423+
// compatibility.
424+
accountCaveat := checkers.Condition(
425+
macaroons.CondLndCustom,
426+
fmt.Sprintf("%s %x", accounts.CondAccount, id[:]),
427+
)
428+
429+
caveats := append(caveats, macaroon.Caveat{
430+
Id: []byte(accountCaveat),
431+
})
432+
433+
options = append(
434+
options,
435+
WithAccount(id),
436+
WithMacaroonRecipe(caveats, nil),
437+
)
438+
})
439+
440+
return db.NewSession(
441+
context.Background(), label, opts.sessType,
442+
time.Date(9999, 1, 1, 0, 0, 0, 0, time.UTC),
443+
"foo.bar.baz:1234", options...,
370444
)
371445
}
372446

session/tlv.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"time"
88

99
"github.com/btcsuite/btcd/btcec/v2"
10+
"github.com/lightninglabs/lightning-terminal/accounts"
1011
"github.com/lightningnetwork/lnd/tlv"
1112
"gopkg.in/macaroon-bakery.v2/bakery"
1213
"gopkg.in/macaroon.v2"
@@ -278,6 +279,18 @@ func DeserializeSession(r io.Reader) (*Session, error) {
278279
session.GroupID = session.ID
279280
}
280281

282+
// For any sessions stored in the BBolt store, a coupled account (if
283+
// any) is linked implicitly via the macaroon recipe caveat. So we
284+
// need to extract it from there.
285+
if session.MacaroonRecipe != nil {
286+
session.AccountID, err = accounts.IDFromCaveats(
287+
session.MacaroonRecipe.Caveats,
288+
)
289+
if err != nil {
290+
return nil, err
291+
}
292+
}
293+
281294
return session, nil
282295
}
283296

session_rpcserver.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/lightninglabs/lightning-terminal/perms"
2323
"github.com/lightninglabs/lightning-terminal/rules"
2424
"github.com/lightninglabs/lightning-terminal/session"
25+
"github.com/lightningnetwork/lnd/fn"
2526
"github.com/lightningnetwork/lnd/macaroons"
2627
"google.golang.org/grpc"
2728
"gopkg.in/macaroon-bakery.v2/bakery"
@@ -221,7 +222,10 @@ func (s *sessionRpcServer) AddSession(ctx context.Context,
221222
permissions[entity][action] = struct{}{}
222223
}
223224

224-
var caveats []macaroon.Caveat
225+
var (
226+
caveats []macaroon.Caveat
227+
accountID fn.Option[accounts.AccountID]
228+
)
225229
switch typ {
226230
// For the default session types we use empty caveats and permissions,
227231
// the macaroons are baked correctly when creating the session.
@@ -236,6 +240,7 @@ func (s *sessionRpcServer) AddSession(ctx context.Context,
236240
}
237241

238242
caveats = append(caveats, accounts.CaveatFromID(*id))
243+
accountID = fn.Some(*id)
239244

240245
// For the custom macaroon type, we use the custom permissions specified
241246
// in the request. For the time being, the caveats list will be empty
@@ -320,6 +325,10 @@ func (s *sessionRpcServer) AddSession(ctx context.Context,
320325
sessOpts = append(sessOpts, session.WithDevServer())
321326
}
322327

328+
accountID.WhenSome(func(id accounts.AccountID) {
329+
sessOpts = append(sessOpts, session.WithAccount(id))
330+
})
331+
323332
sess, err := s.cfg.db.NewSession(
324333
ctx, req.Label, typ, expiry, req.MailboxServerAddr,
325334
sessOpts...,

0 commit comments

Comments
 (0)