diff --git a/app/src/types/generated/lit-sessions_pb.d.ts b/app/src/types/generated/lit-sessions_pb.d.ts index dab9793e..4c7e6fd3 100644 --- a/app/src/types/generated/lit-sessions_pb.d.ts +++ b/app/src/types/generated/lit-sessions_pb.d.ts @@ -688,6 +688,7 @@ export interface SessionStateMap { STATE_IN_USE: 1; STATE_REVOKED: 2; STATE_EXPIRED: 3; + STATE_RESERVED: 4; } export const SessionState: SessionStateMap; diff --git a/app/src/types/generated/lit-sessions_pb.js b/app/src/types/generated/lit-sessions_pb.js index 677c9b16..a356c54f 100644 --- a/app/src/types/generated/lit-sessions_pb.js +++ b/app/src/types/generated/lit-sessions_pb.js @@ -5276,7 +5276,8 @@ proto.litrpc.SessionState = { STATE_CREATED: 0, STATE_IN_USE: 1, STATE_REVOKED: 2, - STATE_EXPIRED: 3 + STATE_EXPIRED: 3, + STATE_RESERVED: 4 }; goog.object.extend(exports, proto.litrpc); diff --git a/litrpc/lit-autopilot.swagger.json b/litrpc/lit-autopilot.swagger.json index e1342634..6e360281 100644 --- a/litrpc/lit-autopilot.swagger.json +++ b/litrpc/lit-autopilot.swagger.json @@ -662,7 +662,8 @@ "STATE_CREATED", "STATE_IN_USE", "STATE_REVOKED", - "STATE_EXPIRED" + "STATE_EXPIRED", + "STATE_RESERVED" ], "default": "STATE_CREATED" }, diff --git a/litrpc/lit-sessions.pb.go b/litrpc/lit-sessions.pb.go index f7739356..61a1df18 100644 --- a/litrpc/lit-sessions.pb.go +++ b/litrpc/lit-sessions.pb.go @@ -81,10 +81,11 @@ func (SessionType) EnumDescriptor() ([]byte, []int) { type SessionState int32 const ( - SessionState_STATE_CREATED SessionState = 0 - SessionState_STATE_IN_USE SessionState = 1 - SessionState_STATE_REVOKED SessionState = 2 - SessionState_STATE_EXPIRED SessionState = 3 + SessionState_STATE_CREATED SessionState = 0 + SessionState_STATE_IN_USE SessionState = 1 + SessionState_STATE_REVOKED SessionState = 2 + SessionState_STATE_EXPIRED SessionState = 3 + SessionState_STATE_RESERVED SessionState = 4 ) // Enum value maps for SessionState. @@ -94,12 +95,14 @@ var ( 1: "STATE_IN_USE", 2: "STATE_REVOKED", 3: "STATE_EXPIRED", + 4: "STATE_RESERVED", } SessionState_value = map[string]int32{ - "STATE_CREATED": 0, - "STATE_IN_USE": 1, - "STATE_REVOKED": 2, - "STATE_EXPIRED": 3, + "STATE_CREATED": 0, + "STATE_IN_USE": 1, + "STATE_REVOKED": 2, + "STATE_EXPIRED": 3, + "STATE_RESERVED": 4, } ) @@ -1897,31 +1900,32 @@ var file_lit_sessions_proto_rawDesc = []byte{ 0x12, 0x12, 0x0a, 0x0e, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x55, 0x54, 0x4f, 0x50, 0x49, 0x4c, 0x4f, 0x54, 0x10, 0x04, 0x12, 0x19, 0x0a, 0x15, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x41, 0x43, 0x41, 0x52, 0x4f, 0x4f, 0x4e, 0x5f, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x10, 0x05, 0x2a, - 0x59, 0x0a, 0x0c, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, + 0x6d, 0x0a, 0x0c, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x49, 0x4e, 0x5f, 0x55, 0x53, 0x45, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x44, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, - 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x45, 0x44, 0x10, 0x03, 0x32, 0xe8, 0x01, 0x0a, 0x08, 0x53, - 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x43, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x53, 0x65, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, - 0x64, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1a, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x53, 0x65, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0c, - 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1b, 0x2e, 0x6c, - 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x69, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0d, 0x52, 0x65, 0x76, 0x6f, 0x6b, - 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, - 0x73, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2d, 0x74, 0x65, 0x72, 0x6d, - 0x69, 0x6e, 0x61, 0x6c, 0x2f, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x45, 0x44, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, + 0x41, 0x54, 0x45, 0x5f, 0x52, 0x45, 0x53, 0x45, 0x52, 0x56, 0x45, 0x44, 0x10, 0x04, 0x32, 0xe8, + 0x01, 0x0a, 0x08, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x43, 0x0a, 0x0a, 0x41, + 0x64, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x2e, 0x6c, 0x69, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, + 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x49, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, + 0x12, 0x1b, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, + 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0d, 0x52, + 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x2e, 0x6c, + 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x69, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, + 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2d, + 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x2f, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/litrpc/lit-sessions.proto b/litrpc/lit-sessions.proto index a32499f2..1c38808e 100644 --- a/litrpc/lit-sessions.proto +++ b/litrpc/lit-sessions.proto @@ -97,6 +97,7 @@ enum SessionState { STATE_IN_USE = 1; STATE_REVOKED = 2; STATE_EXPIRED = 3; + STATE_RESERVED = 4; } message AddSessionResponse { diff --git a/litrpc/lit-sessions.swagger.json b/litrpc/lit-sessions.swagger.json index 2300f69c..4d0557d0 100644 --- a/litrpc/lit-sessions.swagger.json +++ b/litrpc/lit-sessions.swagger.json @@ -521,7 +521,8 @@ "STATE_CREATED", "STATE_IN_USE", "STATE_REVOKED", - "STATE_EXPIRED" + "STATE_EXPIRED", + "STATE_RESERVED" ], "default": "STATE_CREATED" }, diff --git a/proto/lit-sessions.proto b/proto/lit-sessions.proto index 9068ce65..fbd56f63 100644 --- a/proto/lit-sessions.proto +++ b/proto/lit-sessions.proto @@ -97,6 +97,7 @@ enum SessionState { STATE_IN_USE = 1; STATE_REVOKED = 2; STATE_EXPIRED = 3; + STATE_RESERVED = 4; } message AddSessionResponse { diff --git a/session/interface.go b/session/interface.go index 1aa4c2db..204b3d16 100644 --- a/session/interface.go +++ b/session/interface.go @@ -51,6 +51,12 @@ const ( // StateExpired is the state of a session that has passed its expiry // date. StateExpired State = 3 + + // StateReserved is a temporary initial state of a session. On start-up, + // any sessions in this state should be cleaned up. + // + // NOTE: this isn't used yet. + StateReserved State = 4 ) // MacaroonRecipe defines the permissions and caveats that should be used @@ -215,5 +221,9 @@ type Store interface { CheckSessionGroupPredicate(groupID ID, fn func(s *Session) bool) (bool, error) + // DeleteReservedSessions deletes all sessions that are in the + // StateReserved state. + DeleteReservedSessions() error + IDToGroupIndex } diff --git a/session/kvdb_store.go b/session/kvdb_store.go index 2f5f252d..5dbab9ba 100644 --- a/session/kvdb_store.go +++ b/session/kvdb_store.go @@ -444,6 +444,99 @@ func (db *BoltStore) listSessions(filterFn func(s *Session) bool) ([]*Session, return sessions, nil } +// DeleteReservedSessions deletes all sessions that are in the StateReserved +// state. +// +// NOTE: this is part of the Store interface. +func (db *BoltStore) DeleteReservedSessions() error { + return db.Update(func(tx *bbolt.Tx) error { + sessionBucket, err := getBucket(tx, sessionBucketKey) + if err != nil { + return err + } + + return sessionBucket.ForEach(func(k, v []byte) error { + // We'll also get buckets here, skip those (identified + // by nil value). + if v == nil { + return nil + } + + session, err := DeserializeSession(bytes.NewReader(v)) + if err != nil { + return err + } + + if session.State != StateReserved { + return nil + } + + err = sessionBucket.Delete(k) + if err != nil { + return err + } + + idIndexBkt := sessionBucket.Bucket(idIndexKey) + if idIndexBkt == nil { + return ErrDBInitErr + } + + // Delete the entire session ID bucket. + err = idIndexBkt.DeleteBucket(session.ID[:]) + if err != nil { + return err + } + + groupIdIndexBkt := sessionBucket.Bucket(groupIDIndexKey) + if groupIdIndexBkt == nil { + return ErrDBInitErr + } + + groupBkt := groupIdIndexBkt.Bucket(session.GroupID[:]) + if groupBkt == nil { + return ErrDBInitErr + } + + sessionIDsBkt := groupBkt.Bucket(sessionIDKey) + if sessionIDsBkt == nil { + return ErrDBInitErr + } + + var ( + seqKey []byte + numSessions int + ) + err = sessionIDsBkt.ForEach(func(k, v []byte) error { + numSessions++ + + if !bytes.Equal(v, session.ID[:]) { + return nil + } + + seqKey = k + + return nil + }) + if err != nil { + return err + } + + if numSessions == 0 { + return fmt.Errorf("no sessions found for "+ + "group ID %x", session.GroupID) + } + + if numSessions == 1 { + // Delete the whole group bucket. + return groupBkt.DeleteBucket(sessionIDKey) + } + + // Else, delete just the session ID entry. + return sessionIDsBkt.Delete(seqKey) + }) + }) +} + // RevokeSession updates the state of the session with the given local // public key to be revoked. // diff --git a/session/store_test.go b/session/store_test.go index e5530fdd..aa3cacf7 100644 --- a/session/store_test.go +++ b/session/store_test.go @@ -142,9 +142,58 @@ func TestBasicSessionStore(t *testing.T) { require.NoError(t, err) require.Empty(t, sessions) - sessions, err = db.ListSessionsByState(StateInUse) + sessions, err = db.ListSessionsByState(StateReserved) require.NoError(t, err) require.Empty(t, sessions) + + // Demonstrate deletion of a reserved session. + // + // Calling DeleteReservedSessions should have no effect yet since none + // of the sessions are reserved. + require.NoError(t, db.DeleteReservedSessions()) + + sessions, err = db.ListSessionsByState(StateReserved) + require.NoError(t, err) + require.Empty(t, sessions) + + // Add a session and put it in the StateReserved state. We'll also + // link it to session 1. + s5 := newSession( + t, db, clock, "session 5", withState(StateReserved), + withLinkedGroupID(&session1.GroupID), + ) + require.NoError(t, db.CreateSession(s5)) + + sessions, err = db.ListSessionsByState(StateReserved) + require.NoError(t, err) + require.Equal(t, 1, len(sessions)) + assertEqualSessions(t, s5, sessions[0]) + + // Show that the group ID/session ID index has also been populated with + // this session. + groupID, err := db.GetGroupID(s5.ID) + require.NoError(t, err) + require.Equal(t, s1.ID, groupID) + + sessIDs, err := db.GetSessionIDs(s5.GroupID) + require.NoError(t, err) + require.ElementsMatch(t, []ID{s5.ID, s1.ID}, sessIDs) + + // Now delete the reserved session and show that it is no longer in the + // database and no longer in the group ID/session ID index. + require.NoError(t, db.DeleteReservedSessions()) + + sessions, err = db.ListSessionsByState(StateReserved) + require.NoError(t, err) + require.Empty(t, sessions) + + _, err = db.GetGroupID(s5.ID) + require.ErrorContains(t, err, "no index entry") + + // Only session 1 should remain in this group. + sessIDs, err = db.GetSessionIDs(s5.GroupID) + require.NoError(t, err) + require.ElementsMatch(t, []ID{s1.ID}, sessIDs) } // TestLinkingSessions tests that session linking works as expected. @@ -359,6 +408,12 @@ func withType(t Type) testSessionModifier { } } +func withState(state State) testSessionModifier { + return func(s *Session) { + s.State = state + } +} + func newSession(t *testing.T, db Store, clock clock.Clock, label string, mods ...testSessionModifier) *Session { diff --git a/session_rpcserver.go b/session_rpcserver.go index e85d3578..4e452068 100644 --- a/session_rpcserver.go +++ b/session_rpcserver.go @@ -100,6 +100,12 @@ func newSessionRPCServer(cfg *sessionRpcServerConfig) (*sessionRpcServer, // start all the components necessary for the sessionRpcServer to start serving // requests. This includes resuming all non-revoked sessions. func (s *sessionRpcServer) start(ctx context.Context) error { + // Delete all sessions in the Reserved state. + err := s.cfg.db.DeleteReservedSessions() + if err != nil { + return fmt.Errorf("error deleting reserved sessions: %v", err) + } + // Start up all previously created sessions. sessions, err := s.cfg.db.ListSessionsByState( session.StateCreated, @@ -1518,6 +1524,9 @@ func marshalRPCMacaroonRecipe( // marshalRPCState converts a session state to its RPC counterpart. func marshalRPCState(state session.State) (litrpc.SessionState, error) { switch state { + case session.StateReserved: + return litrpc.SessionState_STATE_RESERVED, nil + case session.StateCreated: return litrpc.SessionState_STATE_CREATED, nil