GODT-2003: introduces 3 phases user state (SignedOut/Locked/Connected)

WIP: introduced UserState enum in GUI and implemented logic.
This commit is contained in:
Xavier Michelon
2022-11-15 17:24:54 +01:00
parent 49b3c18903
commit e087a7972e
27 changed files with 1516 additions and 1276 deletions

View File

@ -529,14 +529,14 @@ func must[T any](val T, err error) T {
return val
}
func getConnectedUserIDs(t *testing.T, bridge *bridge.Bridge) []string {
func getConnectedUserIDs(t *testing.T, b *bridge.Bridge) []string {
t.Helper()
return xslices.Filter(bridge.GetUserIDs(), func(userID string) bool {
info, err := bridge.GetUserInfo(userID)
return xslices.Filter(b.GetUserIDs(), func(userID string) bool {
info, err := b.GetUserInfo(userID)
require.NoError(t, err)
return info.Connected
return info.State == bridge.Connected
})
}

View File

@ -69,14 +69,14 @@ func TestBridge_Sync(t *testing.T) {
})
// If we then connect an IMAP client, it should see all the messages.
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
mocks.Reporter.EXPECT().ReportMessageWithContext(gomock.Any(), gomock.Any()).AnyTimes()
info, err := bridge.GetUserInfo(userID)
info, err := b.GetUserInfo(userID)
require.NoError(t, err)
require.True(t, info.Connected)
require.True(t, info.State == bridge.Connected)
client, err := client.Dial(fmt.Sprintf("%v:%v", constants.Host, bridge.GetIMAPPort()))
client, err := client.Dial(fmt.Sprintf("%v:%v", constants.Host, b.GetIMAPPort()))
require.NoError(t, err)
require.NoError(t, client.Login("imap@pm.me", string(info.BridgePass)))
defer func() { _ = client.Logout() }()
@ -95,23 +95,23 @@ func TestBridge_Sync(t *testing.T) {
netCtl.SetReadLimit(2 * total / 3)
// Login the user; its sync should fail.
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
mocks.Reporter.EXPECT().ReportMessageWithContext(gomock.Any(), gomock.Any()).AnyTimes()
{
syncCh, done := chToType[events.Event, events.SyncFailed](bridge.GetEvents(events.SyncFailed{}))
syncCh, done := chToType[events.Event, events.SyncFailed](b.GetEvents(events.SyncFailed{}))
defer done()
userID, err := bridge.LoginFull(ctx, "imap", password, nil, nil)
userID, err := b.LoginFull(ctx, "imap", password, nil, nil)
require.NoError(t, err)
require.Equal(t, userID, (<-syncCh).UserID)
info, err := bridge.GetUserInfo(userID)
info, err := b.GetUserInfo(userID)
require.NoError(t, err)
require.True(t, info.Connected)
require.True(t, info.State == bridge.Connected)
client, err := client.Dial(fmt.Sprintf("%v:%v", constants.Host, bridge.GetIMAPPort()))
client, err := client.Dial(fmt.Sprintf("%v:%v", constants.Host, b.GetIMAPPort()))
require.NoError(t, err)
require.NoError(t, client.Login("imap@pm.me", string(info.BridgePass)))
defer func() { _ = client.Logout() }()
@ -125,16 +125,16 @@ func TestBridge_Sync(t *testing.T) {
netCtl.SetReadLimit(0)
{
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
syncCh, done := chToType[events.Event, events.SyncFinished](b.GetEvents(events.SyncFinished{}))
defer done()
require.Equal(t, userID, (<-syncCh).UserID)
info, err := bridge.GetUserInfo(userID)
info, err := b.GetUserInfo(userID)
require.NoError(t, err)
require.True(t, info.Connected)
require.True(t, info.State == bridge.Connected)
client, err := client.Dial(fmt.Sprintf("%v:%v", constants.Host, bridge.GetIMAPPort()))
client, err := client.Dial(fmt.Sprintf("%v:%v", constants.Host, b.GetIMAPPort()))
require.NoError(t, err)
require.NoError(t, client.Login("imap@pm.me", string(info.BridgePass)))
defer func() { _ = client.Logout() }()

View File

@ -34,6 +34,14 @@ import (
"gitlab.protontech.ch/go/liteapi"
)
type UserState int
const (
SignedOut UserState = iota
Locked
Connected
)
type UserInfo struct {
// UserID is the user's API ID.
UserID string
@ -41,8 +49,8 @@ type UserInfo struct {
// Username is the user's API username.
Username string
// Connected is true if the user is logged in (has API auth).
Connected bool
// Signed Out is true if the user is signed out (no AuthUID, user will need to provide credentials to log in again)
State UserState
// Addresses holds the user's email addresses. The first address is the primary address.
Addresses []string
@ -75,7 +83,11 @@ func (bridge *Bridge) GetUserInfo(userID string) (UserInfo, error) {
var info UserInfo
if err := bridge.vault.GetUser(userID, func(user *vault.User) {
info = getUserInfo(user.UserID(), user.Username(), user.AddressMode())
state := Locked
if len(user.AuthUID()) == 0 {
state = SignedOut
}
info = getUserInfo(user.UserID(), user.Username(), state, user.AddressMode())
}); err != nil {
return UserInfo{}, fmt.Errorf("failed to get user info: %w", err)
}
@ -515,8 +527,9 @@ func (bridge *Bridge) logoutUser(ctx context.Context, user *user.User, withAPI,
}
// getUserInfo returns information about a disconnected user.
func getUserInfo(userID, username string, addressMode vault.AddressMode) UserInfo {
func getUserInfo(userID, username string, state UserState, addressMode vault.AddressMode) UserInfo {
return UserInfo{
State: state,
UserID: userID,
Username: username,
AddressMode: addressMode,
@ -526,7 +539,7 @@ func getUserInfo(userID, username string, addressMode vault.AddressMode) UserInf
// getConnUserInfo returns information about a connected user.
func getConnUserInfo(user *user.User) UserInfo {
return UserInfo{
Connected: true,
State: Connected,
UserID: user.ID(),
Username: user.Name(),
Addresses: user.Emails(),