Skip to content

Commit 1b2f18c

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 2ce4381 commit 1b2f18c

File tree

5 files changed

+129
-5
lines changed

5 files changed

+129
-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: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/btcsuite/btcd/btcec/v2"
1515
"github.com/lightninglabs/lightning-terminal/accounts"
1616
"github.com/lightningnetwork/lnd/clock"
17+
"github.com/lightningnetwork/lnd/fn"
1718
"go.etcd.io/bbolt"
1819
)
1920

@@ -217,6 +218,16 @@ func (db *BoltStore) NewSession(ctx context.Context, label string, typ Type,
217218

218219
sessionKey := getSessionKey(session)
219220

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

session/store_test.go

Lines changed: 79 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)
@@ -335,9 +341,49 @@ func TestStateShift(t *testing.T) {
335341
require.ErrorContains(t, err, "illegal session state transition")
336342
}
337343

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

343389
func defaultTestSessOpts() *testSessionOpts {
@@ -363,6 +409,12 @@ func withType(t Type) testSessionModifier {
363409
}
364410
}
365411

412+
func withAccount(alias accounts.AccountID) testSessionModifier {
413+
return func(s *testSessionOpts) {
414+
s.account = fn.Some(alias)
415+
}
416+
}
417+
366418
func reserveSession(db Store, label string,
367419
mods ...testSessionModifier) (*Session, error) {
368420

@@ -371,13 +423,36 @@ func reserveSession(db Store, label string,
371423
mod(opts)
372424
}
373425

374-
return db.NewSession(
375-
context.Background(), label, opts.sessType,
376-
time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC),
377-
"foo.bar.baz:1234",
426+
options := []Option{
378427
WithDevServer(),
379428
WithPrivacy(PrivacyFlags{ClearPubkeys}),
380429
WithLinkedGroupID(opts.groupID),
430+
}
431+
432+
var caveats []macaroon.Caveat
433+
opts.account.WhenSome(func(id accounts.AccountID) {
434+
// For now, we manually add the account caveat for bbolt
435+
// compatibility.
436+
accountCaveat := checkers.Condition(
437+
macaroons.CondLndCustom,
438+
fmt.Sprintf("%s %x", accounts.CondAccount, id[:]),
439+
)
440+
441+
caveats = append(caveats, macaroon.Caveat{
442+
Id: []byte(accountCaveat),
443+
})
444+
445+
options = append(
446+
options,
447+
WithAccount(id),
448+
WithMacaroonRecipe(caveats, nil),
449+
)
450+
})
451+
452+
return db.NewSession(
453+
context.Background(), label, opts.sessType,
454+
time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC),
455+
"foo.bar.baz:1234", options...,
381456
)
382457
}
383458

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"
@@ -211,7 +212,10 @@ func (s *sessionRpcServer) AddSession(ctx context.Context,
211212
permissions[entity][action] = struct{}{}
212213
}
213214

214-
var caveats []macaroon.Caveat
215+
var (
216+
caveats []macaroon.Caveat
217+
accountID fn.Option[accounts.AccountID]
218+
)
215219
switch typ {
216220
// For the default session types we use empty caveats and permissions,
217221
// the macaroons are baked correctly when creating the session.
@@ -226,6 +230,7 @@ func (s *sessionRpcServer) AddSession(ctx context.Context,
226230
}
227231

228232
caveats = append(caveats, accounts.CaveatFromID(*id))
233+
accountID = fn.Some(*id)
229234

230235
// For the custom macaroon type, we use the custom permissions specified
231236
// in the request. For the time being, the caveats list will be empty
@@ -310,6 +315,10 @@ func (s *sessionRpcServer) AddSession(ctx context.Context,
310315
sessOpts = append(sessOpts, session.WithDevServer())
311316
}
312317

318+
accountID.WhenSome(func(id accounts.AccountID) {
319+
sessOpts = append(sessOpts, session.WithAccount(id))
320+
})
321+
313322
sess, err := s.cfg.db.NewSession(
314323
ctx, req.Label, typ, expiry, req.MailboxServerAddr,
315324
sessOpts...,

0 commit comments

Comments
 (0)