Correctly handle account with invalid access token

Show a popup saying the session was signed out and cleanup
the models data, instead of spamming the users with
never-ending errors.
This commit is contained in:
miruka 2020-09-04 11:21:36 -04:00
parent 3c9895b0b2
commit 7b6478f514
9 changed files with 92 additions and 9 deletions

View File

@ -2,7 +2,6 @@
- Fix right margin of own `<image url>\n<image url>` messages - Fix right margin of own `<image url>\n<image url>` messages
- handle invalid access token
- If an account is gone from the user's config, discard UI state last page - If an account is gone from the user's config, discard UI state last page
- filter > enter > room list is always scrolled to top - filter > enter > room list is always scrolled to top
- ctrl+j/k when scrolled up - ctrl+j/k when scrolled up

View File

@ -18,7 +18,7 @@ from appdirs import AppDirs
import nio import nio
from . import __app_name__ from . import __app_name__
from .errors import MatrixError from .errors import MatrixError, MatrixInvalidAccessToken
from .matrix_client import MatrixClient from .matrix_client import MatrixClient
from .media_cache import MediaCache from .media_cache import MediaCache
from .models import SyncId from .models import SyncId
@ -305,6 +305,11 @@ class Backend:
client = self.clients.pop(user_id, None) client = self.clients.pop(user_id, None)
if client: if client:
try:
await client.logout()
except MatrixInvalidAccessToken:
pass
self.models["accounts"].pop(user_id, None) self.models["accounts"].pop(user_id, None)
self.models["matching_accounts"].pop(user_id, None) self.models["matching_accounts"].pop(user_id, None)
self.models[user_id, "uploads"].clear() self.models[user_id, "uploads"].clear()
@ -317,8 +322,6 @@ class Backend:
self.models[user_id, "rooms"].clear() self.models[user_id, "rooms"].clear()
await client.logout()
await self.saved_accounts.delete(user_id) await self.saved_accounts.delete(user_id)

View File

@ -33,6 +33,12 @@ class MatrixError(Exception):
return cls(response.transport_response.status, response.status_code) return cls(response.transport_response.status, response.status_code)
@dataclass
class MatrixInvalidAccessToken(MatrixError):
http_code: int = 401
m_code: str = "M_UNKNOWN_TOKEN"
@dataclass @dataclass
class MatrixUnauthorized(MatrixError): class MatrixUnauthorized(MatrixError):
http_code: int = 401 http_code: int = 401

View File

@ -36,8 +36,9 @@ from nio.crypto import async_generator_from_data
from . import __app_name__, __display_name__, utils from . import __app_name__, __display_name__, utils
from .errors import ( from .errors import (
BadMimeType, InvalidUserId, InvalidUserInContext, MatrixBadGateway, BadMimeType, InvalidUserId, InvalidUserInContext, MatrixBadGateway,
MatrixError, MatrixForbidden, MatrixNotFound, MatrixTooLarge, MatrixError, MatrixForbidden, MatrixInvalidAccessToken, MatrixNotFound,
MatrixUnauthorized, UneededThumbnail, UserFromOtherServerDisallowed, MatrixTooLarge, MatrixUnauthorized, UneededThumbnail,
UserFromOtherServerDisallowed,
) )
from .html_markdown import HTML_PROCESSOR as HTML from .html_markdown import HTML_PROCESSOR as HTML
from .media_cache import Media, Thumbnail from .media_cache import Media, Thumbnail
@ -47,7 +48,9 @@ from .models.items import (
from .models.model_store import ModelStore from .models.model_store import ModelStore
from .nio_callbacks import NioCallbacks from .nio_callbacks import NioCallbacks
from .presence import Presence from .presence import Presence
from .pyotherside_events import AlertRequested, LoopException from .pyotherside_events import (
AlertRequested, InvalidAccessToken, LoopException,
)
if TYPE_CHECKING: if TYPE_CHECKING:
from .backend import Backend from .backend import Backend
@ -201,6 +204,8 @@ class MatrixClient(nio.AsyncClient):
# {room_id: event} # {room_id: event}
self.power_level_events: Dict[str, nio.PowerLevelsEvent] = {} self.power_level_events: Dict[str, nio.PowerLevelsEvent] = {}
self.invalid_disconnecting: bool = False
self.nio_callbacks = NioCallbacks(self) self.nio_callbacks = NioCallbacks(self)
@ -236,7 +241,15 @@ class MatrixClient(nio.AsyncClient):
response = await super()._send(*args, **kwargs) response = await super()._send(*args, **kwargs)
if isinstance(response, nio.ErrorResponse): if isinstance(response, nio.ErrorResponse):
raise MatrixError.from_nio(response) try:
raise MatrixError.from_nio(response)
except MatrixInvalidAccessToken:
if not self.invalid_disconnecting:
self.invalid_disconnecting = True
InvalidAccessToken(self.user_id)
await self.backend.logout_client(self.user_id)
raise
return response return response

View File

@ -98,3 +98,10 @@ class DevicesUpdated(PyOtherSideEvent):
"""Indicate changes in devices for us or users we share a room with.""" """Indicate changes in devices for us or users we share a room with."""
our_user_id: str = field() our_user_id: str = field()
@dataclass
class InvalidAccessToken(PyOtherSideEvent):
"""Indicate one of our account's access token is invalid or revoked."""
user_id: str = field()

View File

@ -0,0 +1,46 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
import QtQuick 2.12
import "../Base"
import "../Base/Buttons"
HFlickableColumnPopup {
id: popup
property string userId
signal signBackInRequest()
page.footer: AutoDirectionLayout {
ApplyButton {
id: signBackButton
text: qsTr("Sign back in")
icon.name: "sign-back-in"
onClicked: {
const page = "Pages/AddAccount/AddAccount.qml"
window.mainUI.pageLoader.show(page)
popup.close()
}
}
CancelButton {
text: qsTr("Close")
onClicked: popup.close()
}
}
SummaryLabel {
text: qsTr("Signed out from %1").arg(coloredNameHtml("", userId))
textFormat: SummaryLabel.StyledText
}
DetailsLabel {
text: qsTr(
"You have been disconnected from another session, " +
"by the server for security reasons, or the access token in " +
"your configuration file is invalid."
)
}
onOpened: signBackButton.forceActiveFocus()
}

View File

@ -94,4 +94,8 @@ QtObject {
function onDevicesUpdated(forAccount) { function onDevicesUpdated(forAccount) {
deviceUpdateSignal(forAccount) deviceUpdateSignal(forAccount)
} }
function onInvalidAccessToken(userId) {
window.makePopup("Popups/InvalidAccessTokenPopup.qml", {userId})
}
} }

View File

@ -5,5 +5,7 @@ import QtQuick 2.12
QtObject { QtObject {
readonly property var pendingCoroutines: ({}) readonly property var pendingCoroutines: ({})
readonly property var hideErrorTypes: new Set(["gaierror", "SSLError"]) readonly property var hideErrorTypes: new Set([
"gaierror", "SSLError", "MatrixInvalidAccessToken",
])
} }

View File

@ -0,0 +1,3 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="m16 2c3.309 0 6 2.691 6 6s-2.691 6-6 6-6-2.691-6-6 2.691-6 6-6zm0-2c-4.418 0-8 3.582-8 8s3.582 8 8 8 8-3.582 8-8-3.582-8-8-8zm-5.405 16.4-1.472 1.6h-3.123v2h-2v2h-2v-2.179l5.903-5.976c-.404-.559-.754-1.158-1.038-1.795l-6.865 6.95v5h6v-2h2v-2h2l2.451-2.663c-.655-.249-1.276-.562-1.856-.937zm7.405-11.4c.551 0 1 .449 1 1s-.449 1-1 1-1-.449-1-1 .449-1 1-1zm0-1c-1.104 0-2 .896-2 2s.896 2 2 2 2-.896 2-2-.896-2-2-2z"/>
</svg>

After

Width:  |  Height:  |  Size: 519 B