lightning-terminal/session/sql_store.go
Elle Mouton 7cb22cead9
session: embed db.BaseDB in SQLStore
So that we can extract the same BaseDB in other packages and use it for
initialising other SQL stores during tests.
2025-04-15 11:24:49 +02:00

844 lines
23 KiB
Go

package session
import (
"context"
"database/sql"
"errors"
"fmt"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightninglabs/lightning-node-connect/mailbox"
"github.com/lightninglabs/lightning-terminal/accounts"
"github.com/lightninglabs/lightning-terminal/db"
"github.com/lightninglabs/lightning-terminal/db/sqlc"
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/fn"
"gopkg.in/macaroon-bakery.v2/bakery"
"gopkg.in/macaroon.v2"
)
// SQLQueries is a subset of the sqlc.Queries interface that can be used to
// interact with session related tables.
type SQLQueries interface {
GetAliasBySessionID(ctx context.Context, id int64) ([]byte, error)
GetSessionByID(ctx context.Context, id int64) (sqlc.Session, error)
GetSessionsInGroup(ctx context.Context, groupID sql.NullInt64) ([]sqlc.Session, error)
GetSessionAliasesInGroup(ctx context.Context, groupID sql.NullInt64) ([][]byte, error)
GetSessionByAlias(ctx context.Context, legacyID []byte) (sqlc.Session, error)
GetSessionByLocalPublicKey(ctx context.Context, localPublicKey []byte) (sqlc.Session, error)
GetSessionFeatureConfigs(ctx context.Context, sessionID int64) ([]sqlc.SessionFeatureConfig, error)
GetSessionMacaroonCaveats(ctx context.Context, sessionID int64) ([]sqlc.SessionMacaroonCaveat, error)
GetSessionIDByAlias(ctx context.Context, legacyID []byte) (int64, error)
GetSessionMacaroonPermissions(ctx context.Context, sessionID int64) ([]sqlc.SessionMacaroonPermission, error)
GetSessionPrivacyFlags(ctx context.Context, sessionID int64) ([]sqlc.SessionPrivacyFlag, error)
InsertSessionFeatureConfig(ctx context.Context, arg sqlc.InsertSessionFeatureConfigParams) error
SetSessionRevokedAt(ctx context.Context, arg sqlc.SetSessionRevokedAtParams) error
InsertSessionMacaroonCaveat(ctx context.Context, arg sqlc.InsertSessionMacaroonCaveatParams) error
InsertSessionMacaroonPermission(ctx context.Context, arg sqlc.InsertSessionMacaroonPermissionParams) error
InsertSessionPrivacyFlag(ctx context.Context, arg sqlc.InsertSessionPrivacyFlagParams) error
InsertSession(ctx context.Context, arg sqlc.InsertSessionParams) (int64, error)
ListSessions(ctx context.Context) ([]sqlc.Session, error)
ListSessionsByType(ctx context.Context, sessionType int16) ([]sqlc.Session, error)
ListSessionsByState(ctx context.Context, state int16) ([]sqlc.Session, error)
SetSessionRemotePublicKey(ctx context.Context, arg sqlc.SetSessionRemotePublicKeyParams) error
SetSessionGroupID(ctx context.Context, arg sqlc.SetSessionGroupIDParams) error
UpdateSessionState(ctx context.Context, arg sqlc.UpdateSessionStateParams) error
DeleteSessionsWithState(ctx context.Context, state int16) error
GetAccountIDByAlias(ctx context.Context, alias int64) (int64, error)
GetAccount(ctx context.Context, id int64) (sqlc.Account, error)
}
var _ Store = (*SQLStore)(nil)
// BatchedSQLQueries is a version of the SQLQueries that's capable of batched
// database operations.
type BatchedSQLQueries interface {
SQLQueries
db.BatchedTx[SQLQueries]
}
// SQLStore represents a storage backend.
type SQLStore struct {
// db is all the higher level queries that the SQLStore has access to
// in order to implement all its CRUD logic.
db BatchedSQLQueries
// BaseDB represents the underlying database connection.
*db.BaseDB
clock clock.Clock
}
// NewSQLStore creates a new SQLStore instance given an open BatchedSQLQueries
// storage backend.
func NewSQLStore(sqlDB *db.BaseDB, clock clock.Clock) *SQLStore {
executor := db.NewTransactionExecutor(
sqlDB, func(tx *sql.Tx) SQLQueries {
return sqlDB.WithTx(tx)
},
)
return &SQLStore{
db: executor,
BaseDB: sqlDB,
clock: clock,
}
}
// NewSession creates and persists a new session with the given user-defined
// parameters. The initial state of the session will be Reserved until
// ShiftState is called with StateCreated.
//
// NOTE: this is part of the Store interface.
func (s *SQLStore) NewSession(ctx context.Context, label string, typ Type,
expiry time.Time, serverAddr string, opts ...Option) (*Session, error) {
var (
writeTxOpts db.QueriesTxOptions
sess *Session
)
err := s.db.ExecTx(ctx, &writeTxOpts, func(db SQLQueries) error {
id, localPrivKey, err := getSqlUnusedAliasAndKeyPair(ctx, db)
if err != nil {
return err
}
sess, err = buildSession(
id, localPrivKey, label, typ, s.clock.Now().UTC(),
expiry, serverAddr, opts...,
)
if err != nil {
return err
}
var acctIDInt64 sql.NullInt64
sess.AccountID.WhenSome(func(alias accounts.AccountID) {
// Do a manual check to ensure the account exists so
// that we can throw a predicable error.
var acctAlias int64
acctAlias, err = alias.ToInt64()
if err != nil {
return
}
var acctDBID int64
acctDBID, err = db.GetAccountIDByAlias(ctx, acctAlias)
if errors.Is(err, sql.ErrNoRows) {
err = accounts.ErrAccNotFound
return
} else if err != nil {
return
}
acctIDInt64 = sql.NullInt64{
Int64: acctDBID,
Valid: true,
}
})
if err != nil {
return fmt.Errorf("unable to convert account ID: %w", err)
}
localKey := sess.LocalPublicKey.SerializeCompressed()
dbID, err := db.InsertSession(ctx, sqlc.InsertSessionParams{
Alias: sess.ID[:],
Label: sess.Label,
State: int16(sess.State),
Type: int16(sess.Type),
Expiry: sess.Expiry.UTC(),
CreatedAt: sess.CreatedAt.UTC(),
ServerAddress: sess.ServerAddr,
DevServer: sess.DevServer,
MacaroonRootKey: int64(sess.MacaroonRootKey),
PairingSecret: sess.PairingSecret[:],
LocalPrivateKey: sess.LocalPrivateKey.Serialize(),
LocalPublicKey: localKey,
Privacy: sess.WithPrivacyMapper,
AccountID: acctIDInt64,
})
if err != nil {
return fmt.Errorf("unable to insert session: %w", err)
}
// Check that the linked session is known.
groupID, err := db.GetSessionIDByAlias(ctx, sess.GroupID[:])
if errors.Is(err, sql.ErrNoRows) {
return ErrUnknownGroup
} else if err != nil {
return fmt.Errorf("unable to fetch group(%x): %w",
sess.GroupID[:], err)
}
// Ensure that all other sessions in this group are no longer
// active.
linkedSessions, err := db.GetSessionsInGroup(ctx, sql.NullInt64{
Int64: groupID,
Valid: true,
})
if err != nil {
return fmt.Errorf("unable to fetch group(%x): %w",
sess.GroupID[:], err)
}
// Make sure that all linked sessions (sessions in the same
// group) are no longer active.
for _, linkedSession := range linkedSessions {
// Skip the new session that we are adding.
if linkedSession.ID == dbID {
continue
}
// Any other session should not be active.
if !State(linkedSession.State).Terminal() {
return fmt.Errorf("linked session(%x) is "+
"still active: %w",
linkedSession.Alias[:],
ErrSessionsInGroupStillActive)
}
}
err = db.SetSessionGroupID(ctx, sqlc.SetSessionGroupIDParams{
ID: dbID,
GroupID: sql.NullInt64{
Int64: groupID,
Valid: true,
},
})
if err != nil {
return fmt.Errorf("unable to set group Alias: %w", err)
}
// Write mac perms and caveats.
if sess.MacaroonRecipe != nil {
for _, perm := range sess.MacaroonRecipe.Permissions {
err := db.InsertSessionMacaroonPermission(
ctx, sqlc.InsertSessionMacaroonPermissionParams{
SessionID: dbID,
Entity: perm.Entity,
Action: perm.Action,
},
)
if err != nil {
return fmt.Errorf("unable to insert "+
"mac perm: %w", err)
}
}
for _, caveat := range sess.MacaroonRecipe.Caveats {
err := db.InsertSessionMacaroonCaveat(
ctx, sqlc.InsertSessionMacaroonCaveatParams{
SessionID: dbID,
CaveatID: caveat.Id,
VerificationID: caveat.
VerificationId,
Location: sql.NullString{
String: caveat.Location,
Valid: caveat.
Location != "",
},
},
)
if err != nil {
return fmt.Errorf("unable to insert "+
"mac caveat: %v", err)
}
}
}
// Write feature configs.
if sess.FeatureConfig != nil {
for featureName, config := range *sess.FeatureConfig {
err := db.InsertSessionFeatureConfig(
ctx, sqlc.InsertSessionFeatureConfigParams{
SessionID: dbID,
FeatureName: featureName,
Config: config,
},
)
if err != nil {
return fmt.Errorf("unable to insert "+
"feature config: %w", err)
}
}
}
// Write privacy flags.
for _, flag := range sess.PrivacyFlags {
err := db.InsertSessionPrivacyFlag(
ctx, sqlc.InsertSessionPrivacyFlagParams{
SessionID: dbID,
Flag: int32(flag),
},
)
if err != nil {
return fmt.Errorf("unable to insert privacy "+
"flag: %w", err)
}
}
return nil
})
if err != nil {
mappedSQLErr := db.MapSQLError(err)
var uniqueConstraintErr *db.ErrSqlUniqueConstraintViolation
if errors.As(mappedSQLErr, &uniqueConstraintErr) {
// Add context to unique constraint errors.
return nil, fmt.Errorf("session violates unique "+
"constraint: %w", uniqueConstraintErr)
}
return nil, fmt.Errorf("unable to add session: %w", err)
}
return sess, nil
}
// ListSessionsByType returns all sessions currently known to the store that
// have the given type.
//
// NOTE: this is part of the Store interface.
func (s *SQLStore) ListSessionsByType(ctx context.Context, t Type) ([]*Session,
error) {
var (
readTxOpts = db.NewQueryReadTx()
sessions []*Session
)
err := s.db.ExecTx(ctx, &readTxOpts, func(db SQLQueries) error {
dbSessions, err := db.ListSessionsByType(ctx, int16(t))
if err != nil {
return fmt.Errorf("could not list sessions: %w", err)
}
for _, dbSess := range dbSessions {
sess, err := unmarshalSession(ctx, db, dbSess)
if err != nil {
return fmt.Errorf("could not unmarshal "+
"session: %w", err)
}
sessions = append(sessions, sess)
}
return nil
})
return sessions, err
}
// ListSessionsByState returns all sessions currently known to the store that
// are in the given state.
//
// NOTE: this is part of the Store interface.
func (s *SQLStore) ListSessionsByState(ctx context.Context, state State) (
[]*Session, error) {
var (
readTxOpts = db.NewQueryReadTx()
sessions []*Session
)
err := s.db.ExecTx(ctx, &readTxOpts, func(db SQLQueries) error {
dbSessions, err := db.ListSessionsByState(ctx, int16(state))
if err != nil {
return fmt.Errorf("could not list sessions: %w", err)
}
for _, dbSess := range dbSessions {
sess, err := unmarshalSession(ctx, db, dbSess)
if err != nil {
return fmt.Errorf("could not unmarshal "+
"session: %w", err)
}
sessions = append(sessions, sess)
}
return nil
})
return sessions, err
}
// ShiftState updates the state of the session with the given ID to the "dest"
// state.
//
// NOTE: this is part of the Store interface.
func (s *SQLStore) ShiftState(ctx context.Context, alias ID, dest State) error {
var writeTxOpts db.QueriesTxOptions
return s.db.ExecTx(ctx, &writeTxOpts, func(db SQLQueries) error {
dbSession, err := db.GetSessionByAlias(ctx, alias[:])
if errors.Is(err, sql.ErrNoRows) {
return fmt.Errorf("%w: unable to get session: %w",
ErrSessionNotFound, err)
} else if err != nil {
return fmt.Errorf("unable to get session: %w", err)
}
dbState := State(dbSession.State)
// If the session is already in the desired state, we return
// with no error to maintain idempotency.
if dbState == dest {
return nil
}
// Ensure that the wanted state change is allowed.
allowedDestinations, ok := legalStateShifts[dbState]
if !ok || !allowedDestinations[dest] {
return fmt.Errorf("illegal session state transition "+
"from %d to %d", dbState, dest)
}
// If the session is terminal, we set the revoked at time to the
// current time.
if dest.Terminal() {
err = db.SetSessionRevokedAt(
ctx, sqlc.SetSessionRevokedAtParams{
RevokedAt: sql.NullTime{
Valid: true,
Time: s.clock.Now().UTC(),
},
ID: dbSession.ID,
},
)
if err != nil {
return fmt.Errorf("unable to set revoked at "+
"time: %w", err)
}
}
return db.UpdateSessionState(
ctx, sqlc.UpdateSessionStateParams{
ID: dbSession.ID,
State: int16(dest),
},
)
})
}
// DeleteReservedSessions deletes all sessions that are in the StateReserved
// state.
//
// NOTE: this is part of the Store interface.
func (s *SQLStore) DeleteReservedSessions(ctx context.Context) error {
var writeTxOpts db.QueriesTxOptions
return s.db.ExecTx(ctx, &writeTxOpts, func(db SQLQueries) error {
return db.DeleteSessionsWithState(ctx, int16(StateReserved))
})
}
// GetSessionByLocalPub fetches the session with the given local pub key.
//
// NOTE: This is part of the Store interface.
func (s *SQLStore) GetSessionByLocalPub(ctx context.Context,
key *btcec.PublicKey) (*Session, error) {
var (
readTxOpts = db.NewQueryReadTx()
sess *Session
)
err := s.db.ExecTx(ctx, &readTxOpts, func(db SQLQueries) error {
dbSess, err := db.GetSessionByLocalPublicKey(
ctx, key.SerializeCompressed(),
)
if errors.Is(err, sql.ErrNoRows) {
return fmt.Errorf("%w: %w", ErrSessionNotFound, err)
} else if err != nil {
return fmt.Errorf("unable to get session: %w", err)
}
sess, err = unmarshalSession(ctx, s.db, dbSess)
if err != nil {
return fmt.Errorf("unable to unmarshal session: %w",
err)
}
return nil
})
if err != nil {
return nil, err
}
return sess, nil
}
// ListAllSessions returns all sessions currently known to the store.
//
// NOTE: This is part of the Store interface.
func (s *SQLStore) ListAllSessions(ctx context.Context) ([]*Session, error) {
var (
readTxOpts = db.NewQueryReadTx()
sessions []*Session
)
err := s.db.ExecTx(ctx, &readTxOpts, func(db SQLQueries) error {
dbSessions, err := db.ListSessions(ctx)
if err != nil {
return fmt.Errorf("could not list sessions: %w", err)
}
for _, dbSess := range dbSessions {
sess, err := unmarshalSession(ctx, db, dbSess)
if err != nil {
return fmt.Errorf("could not unmarshal "+
"session: %w", err)
}
sessions = append(sessions, sess)
}
return nil
})
return sessions, err
}
// UpdateSessionRemotePubKey can be used to add the given remote pub key to the
// session with the given legacy ID.
//
// NOTE: This is part of the Store interface.
func (s *SQLStore) UpdateSessionRemotePubKey(ctx context.Context, alias ID,
remotePubKey *btcec.PublicKey) error {
var (
writeTxOpts db.QueriesTxOptions
remoteKey = remotePubKey.SerializeCompressed()
)
return s.db.ExecTx(ctx, &writeTxOpts, func(db SQLQueries) error {
id, err := db.GetSessionIDByAlias(ctx, alias[:])
if errors.Is(err, sql.ErrNoRows) {
return fmt.Errorf("%w: %w", ErrSessionNotFound, err)
} else if err != nil {
return fmt.Errorf("unable to get session: %w", err)
}
return db.SetSessionRemotePublicKey(
ctx, sqlc.SetSessionRemotePublicKeyParams{
ID: id,
RemotePublicKey: remoteKey,
},
)
})
}
// getSqlUnusedAliasAndKeyPair can be used to generate a new, unused, local
// private key and session Alias pair. Care must be taken to ensure that no
// other thread calls this before the returned Alias and key pair from this
// method are either used or discarded.
func getSqlUnusedAliasAndKeyPair(ctx context.Context, db SQLQueries) (ID,
*btcec.PrivateKey, error) {
// Spin until we find a key with an Alias that does not collide
// with any of our existing IDs.
for {
// Generate a new private key and Alias pair.
privKey, alias, err := NewSessionPrivKeyAndID()
if err != nil {
return ID{}, nil, err
}
// Check that no such legacy Alias exits.
_, err = db.GetSessionByAlias(ctx, alias[:])
if errors.Is(err, sql.ErrNoRows) {
return alias, privKey, nil
} else if err != nil {
return ID{}, nil, fmt.Errorf("unable to get "+
"session: %w", err)
}
continue
}
}
// GetSession returns the session with the given legacy Alias.
//
// NOTE: This is part of the Store interface.
func (s *SQLStore) GetSession(ctx context.Context, alias ID) (*Session, error) {
var (
readTxOpts = db.NewQueryReadTx()
sess *Session
)
err := s.db.ExecTx(ctx, &readTxOpts, func(db SQLQueries) error {
dbSess, err := db.GetSessionByAlias(ctx, alias[:])
if errors.Is(err, sql.ErrNoRows) {
return ErrSessionNotFound
} else if err != nil {
return fmt.Errorf("unable to get session: %w", err)
}
sess, err = unmarshalSession(ctx, s.db, dbSess)
if err != nil {
return fmt.Errorf("unable to unmarshal session: %w",
err)
}
return nil
})
return sess, err
}
// GetGroupID will return the legacy group Alias for the given legacy session Alias.
//
// NOTE: This is part of the AliasToGroupIndex interface.
func (s *SQLStore) GetGroupID(ctx context.Context, sessionID ID) (ID, error) {
var (
readTxOpts = db.NewQueryReadTx()
legacyGroupID ID
)
err := s.db.ExecTx(ctx, &readTxOpts, func(db SQLQueries) error {
// Get the session using the legacy Alias.
sess, err := db.GetSessionByAlias(ctx, sessionID[:])
if errors.Is(err, sql.ErrNoRows) {
return ErrSessionNotFound
} else if err != nil {
return err
}
if !sess.GroupID.Valid {
return fmt.Errorf("session does not have a group Alias")
}
// Get the legacy group Alias using the session group Alias.
legacyGroupIDB, err := db.GetAliasBySessionID(
ctx, sess.GroupID.Int64,
)
if errors.Is(err, sql.ErrNoRows) {
return fmt.Errorf("%w: session not found for group "+
"ID: %w", ErrSessionNotFound, err)
}
if err != nil {
return err
}
legacyGroupID, err = IDFromBytes(legacyGroupIDB)
return err
})
if err != nil {
return ID{}, err
}
return legacyGroupID, nil
}
// GetSessionIDs will return the set of legacy session IDs that are in the
// group with the given legacy Alias.
//
// NOTE: This is part of the AliasToGroupIndex interface.
func (s *SQLStore) GetSessionIDs(ctx context.Context, legacyGroupID ID) ([]ID,
error) {
var (
readTxOpts = db.NewQueryReadTx()
sessionIDs []ID
)
err := s.db.ExecTx(ctx, &readTxOpts, func(db SQLQueries) error {
groupID, err := db.GetSessionIDByAlias(ctx, legacyGroupID[:])
if errors.Is(err, sql.ErrNoRows) {
return ErrUnknownGroup
} else if err != nil {
return fmt.Errorf("unable to get session Alias: %v",
err)
}
sessIDs, err := db.GetSessionAliasesInGroup(
ctx, sql.NullInt64{
Int64: groupID,
Valid: true,
},
)
if err != nil {
return fmt.Errorf("unable to get session IDs: %v", err)
}
sessionIDs = make([]ID, len(sessIDs))
for i, sessID := range sessIDs {
id, err := IDFromBytes(sessID)
if err != nil {
return err
}
sessionIDs[i] = id
}
return nil
})
if err != nil {
return nil, err
}
return sessionIDs, nil
}
func unmarshalSession(ctx context.Context, db SQLQueries,
dbSess sqlc.Session) (*Session, error) {
var legacyGroupID ID
if dbSess.GroupID.Valid {
groupID, err := db.GetAliasBySessionID(
ctx, dbSess.GroupID.Int64,
)
if err != nil {
return nil, fmt.Errorf("unable to get legacy group "+
"Alias: %v", err)
}
legacyGroupID, err = IDFromBytes(groupID)
if err != nil {
return nil, fmt.Errorf("unable to get legacy Alias: %v",
err)
}
}
var acctAlias fn.Option[accounts.AccountID]
if dbSess.AccountID.Valid {
account, err := db.GetAccount(ctx, dbSess.AccountID.Int64)
if err != nil {
return nil, fmt.Errorf("unable to get account: %v", err)
}
accountAlias, err := accounts.AccountIDFromInt64(account.Alias)
if err != nil {
return nil, fmt.Errorf("unable to get account ID: %v", err)
}
acctAlias = fn.Some(accountAlias)
}
legacyID, err := IDFromBytes(dbSess.Alias)
if err != nil {
return nil, fmt.Errorf("unable to get legacy Alias: %v", err)
}
var revokedAt time.Time
if dbSess.RevokedAt.Valid {
revokedAt = dbSess.RevokedAt.Time
}
localPriv, localPub := btcec.PrivKeyFromBytes(dbSess.LocalPrivateKey)
var remotePub *btcec.PublicKey
if len(dbSess.RemotePublicKey) != 0 {
remotePub, err = btcec.ParsePubKey(dbSess.RemotePublicKey)
if err != nil {
return nil, fmt.Errorf("unable to parse remote "+
"public key: %v", err)
}
}
// Get the macaroon permissions if they exist.
perms, err := db.GetSessionMacaroonPermissions(ctx, dbSess.ID)
if err != nil {
return nil, fmt.Errorf("unable to get macaroon "+
"permissions: %v", err)
}
// Get the macaroon caveats if they exist.
caveats, err := db.GetSessionMacaroonCaveats(ctx, dbSess.ID)
if err != nil {
return nil, fmt.Errorf("unable to get macaroon "+
"caveats: %v", err)
}
var macRecipe *MacaroonRecipe
if perms != nil || caveats != nil {
macRecipe = &MacaroonRecipe{
Permissions: unmarshalMacPerms(perms),
Caveats: unmarshalMacCaveats(caveats),
}
}
// Get the feature configs if they exist.
featureConfigs, err := db.GetSessionFeatureConfigs(ctx, dbSess.ID)
if err != nil {
return nil, fmt.Errorf("unable to get feature configs: %v", err)
}
var featureCfgs *FeaturesConfig
if featureConfigs != nil {
featureCfgs = unmarshalFeatureConfigs(featureConfigs)
}
// Get the privacy flags if they exist.
privacyFlags, err := db.GetSessionPrivacyFlags(ctx, dbSess.ID)
if err != nil {
return nil, fmt.Errorf("unable to get privacy flags: %v", err)
}
var privFlags PrivacyFlags
if privacyFlags != nil {
privFlags = unmarshalPrivacyFlags(privacyFlags)
}
var pairingSecret [mailbox.NumPassphraseEntropyBytes]byte
copy(pairingSecret[:], dbSess.PairingSecret)
return &Session{
ID: legacyID,
Label: dbSess.Label,
State: State(dbSess.State),
Type: Type(dbSess.Type),
Expiry: dbSess.Expiry,
CreatedAt: dbSess.CreatedAt,
RevokedAt: revokedAt,
ServerAddr: dbSess.ServerAddress,
DevServer: dbSess.DevServer,
MacaroonRootKey: uint64(dbSess.MacaroonRootKey),
PairingSecret: pairingSecret,
LocalPrivateKey: localPriv,
LocalPublicKey: localPub,
RemotePublicKey: remotePub,
WithPrivacyMapper: dbSess.Privacy,
GroupID: legacyGroupID,
PrivacyFlags: privFlags,
MacaroonRecipe: macRecipe,
FeatureConfig: featureCfgs,
AccountID: acctAlias,
}, nil
}
func unmarshalMacPerms(dbPerms []sqlc.SessionMacaroonPermission) []bakery.Op {
ops := make([]bakery.Op, len(dbPerms))
for i, dbPerm := range dbPerms {
ops[i] = bakery.Op{
Entity: dbPerm.Entity,
Action: dbPerm.Action,
}
}
return ops
}
func unmarshalMacCaveats(dbCaveats []sqlc.SessionMacaroonCaveat) []macaroon.Caveat {
caveats := make([]macaroon.Caveat, len(dbCaveats))
for i, dbCaveat := range dbCaveats {
caveats[i] = macaroon.Caveat{
Id: dbCaveat.CaveatID,
VerificationId: dbCaveat.VerificationID,
Location: dbCaveat.Location.String,
}
}
return caveats
}
func unmarshalFeatureConfigs(dbConfigs []sqlc.SessionFeatureConfig) *FeaturesConfig {
configs := make(FeaturesConfig, len(dbConfigs))
for _, dbConfig := range dbConfigs {
configs[dbConfig.FeatureName] = dbConfig.Config
}
return &configs
}
func unmarshalPrivacyFlags(dbFlags []sqlc.SessionPrivacyFlag) PrivacyFlags {
flags := make(PrivacyFlags, len(dbFlags))
for i, dbFlag := range dbFlags {
flags[i] = PrivacyFlag(dbFlag.Flag)
}
return flags
}