lightning-terminal/session/interface.go

338 lines
10 KiB
Go
Raw Normal View History

2021-08-10 19:06:49 +02:00
package session
import (
2025-02-25 13:14:28 +02:00
"context"
2021-08-10 19:06:49 +02:00
"fmt"
"time"
"github.com/btcsuite/btcd/btcec/v2"
2021-08-10 19:06:49 +02:00
"github.com/lightninglabs/lightning-node-connect/mailbox"
"github.com/lightninglabs/lightning-terminal/accounts"
"github.com/lightninglabs/lightning-terminal/macaroons"
"github.com/lightningnetwork/lnd/fn"
"gopkg.in/macaroon-bakery.v2/bakery"
2021-08-10 19:06:49 +02:00
"gopkg.in/macaroon.v2"
)
// EmptyID is an empty session ID.
var EmptyID ID
2021-08-10 19:06:49 +02:00
// Type represents the type of session.
type Type uint8
const (
TypeMacaroonReadonly Type = 0
TypeMacaroonAdmin Type = 1
TypeMacaroonCustom Type = 2
TypeUIPassword Type = 3
TypeAutopilot Type = 4
TypeMacaroonAccount Type = 5
2021-08-10 19:06:49 +02:00
)
// State represents the state of a session.
type State uint8
/*
/---> StateExpired (terminal)
StateReserved ---> StateCreated ---
\---> StateRevoked (terminal)
*/
2021-08-10 19:06:49 +02:00
const (
// StateCreated is the state of a session once it has been fully
// committed to the BoltStore and is ready to be used. This is the
// first state after StateReserved.
2021-08-10 19:06:49 +02:00
StateCreated State = 0
// StateInUse is the state of a session that is currently being used.
//
// NOTE: this state is not currently used, but we keep it around for now
// since old sessions might still have this state persisted.
StateInUse State = 1
// StateRevoked is the state of a session that has been revoked before
// its expiry date.
2021-08-10 19:06:49 +02:00
StateRevoked State = 2
// StateExpired is the state of a session that has passed its expiry
// date.
2021-08-10 19:06:49 +02:00
StateExpired State = 3
// StateReserved is a temporary initial state of a session. This is used
// to reserve a unique ID and private key pair for a session before it
// is fully created. On start-up, any sessions in this state should be
// cleaned up.
StateReserved State = 4
2021-08-10 19:06:49 +02:00
)
// Terminal returns true if the state is a terminal state.
func (s State) Terminal() bool {
return s == StateExpired || s == StateRevoked
}
// legalStateShifts is a map that defines the legal State transitions that a
// Session can be put through.
var legalStateShifts = map[State]map[State]bool{
StateReserved: {
StateCreated: true,
},
StateCreated: {
StateExpired: true,
StateRevoked: true,
},
StateInUse: {
StateRevoked: true,
StateExpired: true,
},
}
// MacaroonRecipe defines the permissions and caveats that should be used
// to bake a macaroon.
type MacaroonRecipe struct {
Permissions []bakery.Op
Caveats []macaroon.Caveat
}
// FeaturesConfig is a map from feature name to a raw byte array which stores
// any config feature config options.
type FeaturesConfig map[string][]byte
2021-08-10 19:06:49 +02:00
// Session is a struct representing a long-term Terminal Connect session.
type Session struct {
ID ID
Label string
State State
Type Type
Expiry time.Time
CreatedAt time.Time
RevokedAt time.Time
ServerAddr string
DevServer bool
MacaroonRootKey uint64
MacaroonRecipe *MacaroonRecipe
PairingSecret [mailbox.NumPassphraseEntropyBytes]byte
LocalPrivateKey *btcec.PrivateKey
LocalPublicKey *btcec.PublicKey
RemotePublicKey *btcec.PublicKey
FeatureConfig *FeaturesConfig
WithPrivacyMapper bool
PrivacyFlags PrivacyFlags
// GroupID is the Session ID of the very first Session in the linked
// group of sessions. If this is the very first session in the group
// then this will be the same as ID.
GroupID ID
// AccountID is an optional account that the session has been linked to.
AccountID fn.Option[accounts.AccountID]
2021-08-10 19:06:49 +02:00
}
// buildSession creates a new session with the given user-defined parameters.
func buildSession(id ID, localPrivKey *btcec.PrivateKey, label string, typ Type,
created, expiry time.Time, serverAddr string,
options ...Option) (*Session, error) {
opts := defaultSessionOptions()
for _, o := range options {
o(opts)
}
2021-08-10 19:06:49 +02:00
_, pairingSecret, err := mailbox.NewPassphraseEntropy()
2021-08-10 19:06:49 +02:00
if err != nil {
return nil, fmt.Errorf("error deriving pairing secret: %v", err)
}
macRootKey := macaroons.NewSuperMacaroonRootKeyID(id)
2021-08-10 19:06:49 +02:00
// The group ID will by default be the same as the Session ID
// unless this session links to a previous session.
groupID := id
if opts.linkedGroupID != nil {
// If this session is linked to a previous session, then the
// group ID is the same as the linked session's group ID.
groupID = *opts.linkedGroupID
}
sess := &Session{
ID: id,
Label: label,
State: StateReserved,
Type: typ,
Expiry: expiry.UTC(),
CreatedAt: created.UTC(),
ServerAddr: serverAddr,
DevServer: opts.devServer,
MacaroonRootKey: macRootKey,
PairingSecret: pairingSecret,
LocalPrivateKey: localPrivKey,
LocalPublicKey: localPrivKey.PubKey(),
RemotePublicKey: nil,
WithPrivacyMapper: opts.privacy,
PrivacyFlags: opts.privacyFlags,
GroupID: groupID,
MacaroonRecipe: opts.macaroonRecipe,
AccountID: opts.accountID,
}
if len(opts.featureConfig) != 0 {
sess.FeatureConfig = &opts.featureConfig
}
return sess, nil
}
// sessionOptions defines various options that can be tweaked via functional
// parameters for session creation.
type sessionOptions struct {
// privacy indicates if a privacy map should be used with this session.
privacy bool
// privacyFlags to use in combination with the session's privacy mapper.
privacyFlags PrivacyFlags
// featureConfig holds any feature configuration bytes to use for this
// session.
featureConfig FeaturesConfig
// linkedGroupID is the ID of the group that this session is linked
// to. By default, a session is not linked to another group.
linkedGroupID *ID
// devServer is true if TLS should be skipped when connecting to the
// mailbox server.
devServer bool
// macaroonRecipe holds the permissions and caveats that should be used
// to bake the macaroon to be used with this session.
macaroonRecipe *MacaroonRecipe
// accountID is an optional account that the session has been linked to.
accountID fn.Option[accounts.AccountID]
}
// defaultSessionOptions returns a new sessionOptions struct with default
// values set.
func defaultSessionOptions() *sessionOptions {
return &sessionOptions{
privacy: false,
privacyFlags: PrivacyFlags{},
featureConfig: FeaturesConfig{},
linkedGroupID: nil,
devServer: false,
}
}
// Option defines the signature of a functional option that can be used to
// tweak various session creation options.
type Option func(*sessionOptions)
// WithPrivacy can be used to enable the privacy mapper for this session.
// An optional set of privacy flags can be provided to further customize the
// privacy mapper.
func WithPrivacy(flags PrivacyFlags) Option {
return func(o *sessionOptions) {
o.privacy = true
o.privacyFlags = flags
}
}
// WithFeatureConfig can be used to set the feature configuration bytes for
// this session.
func WithFeatureConfig(config FeaturesConfig) Option {
return func(o *sessionOptions) {
o.featureConfig = config
}
}
// WithLinkedGroupID can be used to link this session to a previous session.
func WithLinkedGroupID(groupID *ID) Option {
return func(o *sessionOptions) {
o.linkedGroupID = groupID
}
}
// WithDevServer can be used to set if TLS verification should be skipped when
// connecting to the mailbox server.
func WithDevServer() Option {
return func(o *sessionOptions) {
o.devServer = true
}
}
// WithMacaroonRecipe can be used to set the permissions and caveats that
// should be used to bake the macaroon for a session.
func WithMacaroonRecipe(caveats []macaroon.Caveat, perms []bakery.Op) Option {
return func(o *sessionOptions) {
o.macaroonRecipe = &MacaroonRecipe{
Permissions: perms,
Caveats: caveats,
}
}
2021-08-10 19:06:49 +02:00
}
// WithAccount can be used to link the session to an account.
func WithAccount(id accounts.AccountID) Option {
return func(o *sessionOptions) {
o.accountID = fn.Some(id)
}
}
// IDToGroupIndex defines an interface for the session ID to group ID index.
type IDToGroupIndex interface {
// GetGroupID will return the group ID for the given session ID.
GetGroupID(ctx context.Context, sessionID ID) (ID, error)
// GetSessionIDs will return the set of session IDs that are in the
// group with the given ID.
GetSessionIDs(ctx context.Context, groupID ID) ([]ID, error)
}
2021-08-10 19:06:49 +02:00
// Store is the interface a persistent storage must implement for storing and
// retrieving Terminal Connect sessions.
type Store interface {
// NewSession creates a new session with the given user-defined
// parameters. The session will remain in the StateReserved state until
// ShiftState is called to update the state.
2025-02-25 13:14:28 +02:00
NewSession(ctx context.Context, label string, typ Type,
expiry time.Time, serverAddr string, opts ...Option) (*Session,
error)
// GetSessionByLocalPub fetches the session with the given local pub
// key.
GetSessionByLocalPub(ctx context.Context,
key *btcec.PublicKey) (*Session, error)
// ListAllSessions returns all sessions currently known to the store.
ListAllSessions(ctx context.Context) ([]*Session, error)
2021-08-10 19:06:49 +02:00
// ListSessionsByType returns all sessions of the given type.
ListSessionsByType(ctx context.Context, t Type) ([]*Session, error)
// ListSessionsByState returns all sessions currently known to the store
// that are in the given state.
ListSessionsByState(ctx context.Context, state State) ([]*Session,
error)
// UpdateSessionRemotePubKey can be used to add the given remote pub key
// to the session with the given ID.
UpdateSessionRemotePubKey(ctx context.Context, id ID,
remotePubKey *btcec.PublicKey) error
// GetSession fetches the session with the given ID.
GetSession(ctx context.Context, id ID) (*Session, error)
// DeleteReservedSessions deletes all sessions that are in the
// StateReserved state.
DeleteReservedSessions(ctx context.Context) error
// DeleteReservedSession deletes the session with the given ID if it is
// in the StateReserved state.
DeleteReservedSession(ctx context.Context, id ID) error
// ShiftState updates the state of the session with the given ID to the
// "dest" state.
2025-02-25 13:42:18 +02:00
ShiftState(ctx context.Context, id ID, dest State) error
IDToGroupIndex
2021-08-10 19:06:49 +02:00
}