From 1beca653b772cf10586c417b2c25df03a67df8a2 Mon Sep 17 00:00:00 2001 From: TheArcaneBrony Date: Mon, 17 Jul 2023 00:21:24 +0200 Subject: Handle external logouts --- .../Extensions/HttpClientExtensions.cs | 3 +- MatrixRoomUtils.Core/MatrixException.cs | 4 +- MatrixRoomUtils.Web/Classes/MRUStorageWrapper.cs | 40 +++++++-- MatrixRoomUtils.Web/Pages/About.razor | 4 +- MatrixRoomUtils.Web/Pages/Index.razor | 18 +++- MatrixRoomUtils.Web/Pages/InvalidSession.razor | 97 ++++++++++++++++++++++ MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor | 10 ++- MatrixRoomUtils.Web/Shared/InlineUserItem.razor | 9 +- MatrixRoomUtils.Web/Shared/ModalWindow.razor | 22 +++-- MatrixRoomUtils.Web/Shared/ModalWindow.razor.css | 4 +- MatrixRoomUtils.Web/Shared/RoomList.razor | 15 ++-- .../Shared/SimpleComponents/LinkButton.razor | 14 ++-- MatrixRoomUtils.Web/_Imports.razor | 3 +- MatrixRoomUtils.Web/wwwroot/css/app.css | 2 +- MatrixRoomUtils.Web/wwwroot/index.html | 13 ++- 15 files changed, 212 insertions(+), 46 deletions(-) create mode 100644 MatrixRoomUtils.Web/Pages/InvalidSession.razor diff --git a/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs b/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs index 060867d..695e8e3 100644 --- a/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs +++ b/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs @@ -40,6 +40,7 @@ public class MatrixHttpClient : HttpClient { var content = await a.Content.ReadAsStringAsync(cancellationToken); if (content.StartsWith('{')) { var ex = JsonSerializer.Deserialize(content); + ex.RawContent = content; Console.WriteLine($"Failed to send request: {ex}"); if (ex?.RetryAfterMs is not null) { await Task.Delay(ex.RetryAfterMs.Value, cancellationToken); @@ -61,4 +62,4 @@ public class MatrixHttpClient : HttpClient { await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken); return await JsonSerializer.DeserializeAsync(responseStream, cancellationToken: cancellationToken); } -} \ No newline at end of file +} diff --git a/MatrixRoomUtils.Core/MatrixException.cs b/MatrixRoomUtils.Core/MatrixException.cs index 4795d6d..a469a62 100644 --- a/MatrixRoomUtils.Core/MatrixException.cs +++ b/MatrixRoomUtils.Core/MatrixException.cs @@ -16,6 +16,8 @@ public class MatrixException : Exception { [JsonPropertyName("retry_after_ms")] public int? RetryAfterMs { get; set; } + public string RawContent { get; set; } + public override string Message => $"{ErrorCode}: {ErrorCode switch { // common @@ -54,4 +56,4 @@ public class MatrixException : Exception { "M_CANNOT_LEAVE_SERVER_NOTICE_ROOM" => $"Cannot leave server notice room: {Error}", _ => $"Unknown error: {new { ErrorCode, Error, SoftLogout, RetryAfterMs }.ToJson(ignoreNull: true)}" }}"; -} \ No newline at end of file +} diff --git a/MatrixRoomUtils.Web/Classes/MRUStorageWrapper.cs b/MatrixRoomUtils.Web/Classes/MRUStorageWrapper.cs index 41f604d..d4b256b 100644 --- a/MatrixRoomUtils.Web/Classes/MRUStorageWrapper.cs +++ b/MatrixRoomUtils.Web/Classes/MRUStorageWrapper.cs @@ -21,8 +21,10 @@ public class MRUStorageWrapper { } public async Task?> GetAllTokens() { - return await _storageService.DataStorageProvider.LoadObjectAsync>("mru.tokens") ?? new List(); + return await _storageService.DataStorageProvider.LoadObjectAsync>("mru.tokens") ?? + new List(); } + public async Task GetCurrentToken() { var currentToken = await _storageService.DataStorageProvider.LoadObjectAsync("token"); var allTokens = await GetAllTokens(); @@ -30,12 +32,15 @@ public class MRUStorageWrapper { await SetCurrentToken(null); return null; } + if (currentToken is null) { await SetCurrentToken(currentToken = allTokens[0]); } - if(!allTokens.Any(x=>x.AccessToken == currentToken.AccessToken)) { + + if (!allTokens.Any(x => x.AccessToken == currentToken.AccessToken)) { await SetCurrentToken(currentToken = allTokens[0]); } + return currentToken; } @@ -49,22 +54,39 @@ public class MRUStorageWrapper { await _storageService.DataStorageProvider.SaveObjectAsync("mru.tokens", tokens); } - public async Task GetCurrentSession() { + private async Task GetCurrentSession() { var token = await GetCurrentToken(); if (token == null) { return null; } - - return await _homeserverProviderService.GetAuthenticatedWithToken(token.Homeserver, token.AccessToken); + + return await _homeserverProviderService.GetAuthenticatedWithToken(token.Homeserver, token.AccessToken); } public async Task GetCurrentSessionOrNavigate() { - var session = await GetCurrentSession(); - if (session == null) { + AuthenticatedHomeServer? session = null; + + try { + //catch if the token is invalid + session = await GetCurrentSession(); + } + catch (MatrixException e) { + if (e.ErrorCode == "M_UNKNOWN_TOKEN") { + var token = await GetCurrentToken(); + _navigationManager.NavigateTo("/InvalidSession?ctx=" + token.AccessToken); + return null; + } + + throw; + } + + if (session is null) { _navigationManager.NavigateTo("/Login"); } + return session; } + public class Settings { public DeveloperSettings DeveloperSettings { get; set; } = new(); } @@ -81,11 +103,11 @@ public class MRUStorageWrapper { return; } - tokens.RemoveAll(x=>x.AccessToken == auth.AccessToken); + tokens.RemoveAll(x => x.AccessToken == auth.AccessToken); await _storageService.DataStorageProvider.SaveObjectAsync("mru.tokens", tokens); } public async Task SetCurrentToken(LoginResponse? auth) { _storageService.DataStorageProvider.SaveObjectAsync("token", auth); } -} \ No newline at end of file +} diff --git a/MatrixRoomUtils.Web/Pages/About.razor b/MatrixRoomUtils.Web/Pages/About.razor index b8d9c4a..971bd9b 100644 --- a/MatrixRoomUtils.Web/Pages/About.razor +++ b/MatrixRoomUtils.Web/Pages/About.razor @@ -1,4 +1,4 @@ -@page "/About" +@page "/About" @using System.Net @using System.Net.Sockets @inject NavigationManager NavigationManager @@ -56,7 +56,7 @@ var message = "Hello, World!\nThis is a terminal emulator!\n\nYou can type stuff here, and it will be sent to the server!\n\nThis is a test of the emergency broadcast system.\n\nThis is only a t"; _terminal.Options.RendererType = RendererType.Dom; _terminal.Options.ScreenReaderMode = true; - TcpClient. +// TcpClient. for (var i = 0; i < message.Length; i++) { await _terminal.Write(message[i].ToString()); diff --git a/MatrixRoomUtils.Web/Pages/Index.razor b/MatrixRoomUtils.Web/Pages/Index.razor index 16a6cee..01e2be4 100644 --- a/MatrixRoomUtils.Web/Pages/Index.razor +++ b/MatrixRoomUtils.Web/Pages/Index.razor @@ -21,10 +21,10 @@ Small collection of tools to do not-so-everyday things. @_user.DisplayName on @_auth.Homeserver Remove - +

Member of @_user.RoomCount rooms

- + } @@ -39,7 +39,17 @@ Small collection of tools to do not-so-everyday things. var tokens = await MRUStorage.GetAllTokens(); var profileTasks = tokens.Select(async token => { UserInfo userInfo = new(); - var hs = await HomeserverProvider.GetAuthenticatedWithToken(token.Homeserver, token.AccessToken); + AuthenticatedHomeServer hs; + try { + hs = await HomeserverProvider.GetAuthenticatedWithToken(token.Homeserver, token.AccessToken); + } + catch (MatrixException e) { + if (e.ErrorCode == "M_UNKNOWN_TOKEN") { + NavigationManager.NavigateTo("/InvalidSession?ctx="+token.AccessToken); + return; + } + throw; + } var roomCountTask = hs.GetJoinedRooms(); var profile = await hs.GetProfile(hs.WhoAmI.UserId); userInfo.DisplayName = profile.DisplayName ?? hs.WhoAmI.UserId; @@ -75,4 +85,4 @@ Small collection of tools to do not-so-everyday things. await MRUStorage.SetCurrentToken(auth); await OnInitializedAsync(); } -} \ No newline at end of file +} diff --git a/MatrixRoomUtils.Web/Pages/InvalidSession.razor b/MatrixRoomUtils.Web/Pages/InvalidSession.razor new file mode 100644 index 0000000..3bcd797 --- /dev/null +++ b/MatrixRoomUtils.Web/Pages/InvalidSession.razor @@ -0,0 +1,97 @@ +@page "/InvalidSession" +@using MatrixRoomUtils.Core.Helpers +@using MatrixRoomUtils.Core.Responses +@using MatrixRoomUtils.Web.Shared.SimpleComponents + +Invalid session + +

Rory&::MatrixUtils - Invalid session encountered

+

A session was encountered that is no longer valid. This can happen if you have logged out of the account on another device, or if the access token has expired.

+ +@if (_login is not null) { +

It appears that the affected user is @_login.UserId (@_login.DeviceId) on @_login.Homeserver!

+ Refresh token + Remove + + @if (_showRefreshDialog) { + +
+ Log in + @if (_loginException is not null) { +
@_loginException.RawContent
+ } +
+ } +} + +@code +{ + [Parameter] + [SupplyParameterFromQuery(Name = "ctx")] + public string Context { get; set; } + + private LoginResponse _login { get; set; } + + private bool _showRefreshDialog { get; set; } = false; + + private string _password { get; set; } = ""; + + private MatrixException _loginException { get; set; } + + protected override async Task OnInitializedAsync() { + var tokens = await MRUStorage.GetAllTokens(); + if (tokens is null || tokens.Count == 0) { + NavigationManager.NavigateTo("/Login"); + return; + } + + _login = tokens.FirstOrDefault(x => x.AccessToken == Context); + + if (_login is null) { + Console.WriteLine($"Could not find {_login} in stored tokens!"); + } + + await base.OnInitializedAsync(); + } + + private async Task RemoveUser() { + await MRUStorage.RemoveToken(_login); + if ((await MRUStorage.GetCurrentToken()).AccessToken == _login.AccessToken) + MRUStorage.SetCurrentToken((await MRUStorage.GetAllTokens()).FirstOrDefault()); + await OnInitializedAsync(); + } + + private async Task OpenRefreshDialog() { + _showRefreshDialog = true; + StateHasChanged(); + } + + private async Task SwitchSession(LoginResponse auth) { + Console.WriteLine($"Switching to {auth.Homeserver} {auth.AccessToken} {auth.UserId}"); + await MRUStorage.SetCurrentToken(auth); + await OnInitializedAsync(); + } + + private async Task TryLogin() { + try { + var result = await HomeserverProvider.Login(_login.Homeserver, _login.UserId, _password); + if (result is null) { + Console.WriteLine($"Failed to login to {_login.Homeserver} as {_login.UserId}!"); + return; + } + Console.WriteLine($"Obtained access token for {result.UserId}!"); + + await RemoveUser(); + await MRUStorage.AddToken(result); + if (result.UserId == (await MRUStorage.GetCurrentToken())?.UserId) + await MRUStorage.SetCurrentToken(result); + NavigationManager.NavigateTo("/"); + } + catch (MatrixException e) { + Console.WriteLine($"Failed to login to {_login.Homeserver} as {_login.UserId}!"); + Console.WriteLine(e); + _loginException = e; + StateHasChanged(); + } + } +} diff --git a/MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor b/MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor index 4cb16b8..cd4788b 100644 --- a/MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor +++ b/MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor @@ -207,7 +207,9 @@ else { } private async Task LoadStatesAsync() { - var hs = await MRUStorage.GetCurrentSession(); + var hs = await MRUStorage.GetCurrentSessionOrNavigate(); + if (hs is null) return; + var room = await hs.GetRoom(RoomId); var states = room.GetFullStateAsync(); @@ -215,8 +217,8 @@ else { if (!state.Type.StartsWith("m.policy.rule")) continue; PolicyEvents.Add(state); } - - + + // var stateEventsQuery = await room.GetStateAsync(""); // var stateEvents = stateEventsQuery.Value.Deserialize>(); // PolicyEvents = stateEvents.Where(x => x.Type.StartsWith("m.policy.rule")) @@ -247,4 +249,4 @@ else { StateHasChanged(); } -} \ No newline at end of file +} diff --git a/MatrixRoomUtils.Web/Shared/InlineUserItem.razor b/MatrixRoomUtils.Web/Shared/InlineUserItem.razor index ffccc25..f9cef91 100644 --- a/MatrixRoomUtils.Web/Shared/InlineUserItem.razor +++ b/MatrixRoomUtils.Web/Shared/InlineUserItem.razor @@ -35,14 +35,15 @@ protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); - var hs = await MRUStorage.GetCurrentSession(); - + var hs = await MRUStorage.GetCurrentSessionOrNavigate(); + if(hs is null) return; + await _semaphoreSlim.WaitAsync(); if (User == null && UserId == null) throw new ArgumentNullException(nameof(UserId)); User ??= await hs.GetProfile(UserId); - + ProfileAvatar ??= MediaResolver.ResolveMediaUri(hs.FullHomeServerDomain, User.AvatarUrl); ProfileName ??= User.DisplayName; @@ -50,4 +51,4 @@ _semaphoreSlim.Release(); } -} \ No newline at end of file +} diff --git a/MatrixRoomUtils.Web/Shared/ModalWindow.razor b/MatrixRoomUtils.Web/Shared/ModalWindow.razor index 216f1f3..b40d246 100644 --- a/MatrixRoomUtils.Web/Shared/ModalWindow.razor +++ b/MatrixRoomUtils.Web/Shared/ModalWindow.razor @@ -1,12 +1,12 @@
- @Title + @Title
-
- @ChildContent -
+
+ @ChildContent +
@code { @@ -23,18 +23,30 @@ [Parameter] public double Y { get; set; } = 60; + [Parameter] + public double MinWidth { get; set; } = 100; + [Parameter] public Action OnCloseClicked { get; set; } [Parameter] public bool Collapsed { get; set; } = false; + private ElementReference _titleRef; + private double _x = 60; private double _y = 60; - protected override void OnInitialized() { + protected override async Task OnInitializedAsync() { _x = X; _y = Y; + await base.OnInitializedAsync(); + } + + protected override async Task OnAfterRenderAsync(bool firstRender) { + //set minwidth to title width + MinWidth = await JSRuntime.InvokeAsync("getWidth", _titleRef) + 75; + await base.OnAfterRenderAsync(firstRender); } private void WindowDrag(DragEventArgs obj) { diff --git a/MatrixRoomUtils.Web/Shared/ModalWindow.razor.css b/MatrixRoomUtils.Web/Shared/ModalWindow.razor.css index b25ab0e..6d08114 100644 --- a/MatrixRoomUtils.Web/Shared/ModalWindow.razor.css +++ b/MatrixRoomUtils.Web/Shared/ModalWindow.razor.css @@ -24,6 +24,7 @@ top: 0; left: 0; width: fit-content; + text-wrap: nowrap; height: 100%; line-height: 25px; padding-left: 10px; @@ -55,14 +56,13 @@ cursor: pointer; } -.r-modal > .content { +.r-modal > .r-modal-content { position: relative; top: 25px; left: 0; width: fit-content; height: fit-content; min-width: 150px; - min-height: 5px; max-width: 75vw; max-height: 75vh; overflow: auto; diff --git a/MatrixRoomUtils.Web/Shared/RoomList.razor b/MatrixRoomUtils.Web/Shared/RoomList.razor index fadec1c..e3894a6 100644 --- a/MatrixRoomUtils.Web/Shared/RoomList.razor +++ b/MatrixRoomUtils.Web/Shared/RoomList.razor @@ -22,9 +22,12 @@ else { public ProfileResponseEventData? GlobalProfile { get; set; } Dictionary> RoomsWithTypes = new(); - + protected override async Task OnInitializedAsync() { - GlobalProfile ??= await (await MRUStorage.GetCurrentSession())!.GetProfile((await MRUStorage.GetCurrentSession())!.WhoAmI.UserId); + var hs = await MRUStorage.GetCurrentSessionOrNavigate(); + if (hs is null) return; + + GlobalProfile ??= await hs.GetProfile(hs.WhoAmI.UserId); if (RoomsWithTypes.Any()) return; var tasks = Rooms.Select(ProcessRoom); @@ -41,7 +44,7 @@ else { _ => roomType }; - + private static SemaphoreSlim _semaphoreSlim = new(8, 8); private async Task ProcessRoom(RoomInfo room) { await _semaphoreSlim.WaitAsync(); @@ -59,14 +62,14 @@ else { catch (MatrixException e) { roomType = $"Error: {e.ErrorCode}"; } - + if (!RoomsWithTypes.ContainsKey(roomType)) { RoomsWithTypes.Add(roomType, new List()); } RoomsWithTypes[roomType].Add(room); - + StateHasChanged(); _semaphoreSlim.Release(); } -} \ No newline at end of file +} diff --git a/MatrixRoomUtils.Web/Shared/SimpleComponents/LinkButton.razor b/MatrixRoomUtils.Web/Shared/SimpleComponents/LinkButton.razor index 09b5c32..b800989 100644 --- a/MatrixRoomUtils.Web/Shared/SimpleComponents/LinkButton.razor +++ b/MatrixRoomUtils.Web/Shared/SimpleComponents/LinkButton.razor @@ -1,16 +1,20 @@ - + @ChildContent @code { - + [Parameter] - public string href { get; set; } - + public string? href { get; set; } + [Parameter] public RenderFragment ChildContent { get; set; } [Parameter] public string? Color { get; set; } -} \ No newline at end of file + [Parameter] + public Func? OnClick { get; set; } + +} diff --git a/MatrixRoomUtils.Web/_Imports.razor b/MatrixRoomUtils.Web/_Imports.razor index 85c015e..b1d9212 100644 --- a/MatrixRoomUtils.Web/_Imports.razor +++ b/MatrixRoomUtils.Web/_Imports.razor @@ -21,4 +21,5 @@ @inject MRUStorageWrapper MRUStorage @inject HomeserverProviderService HomeserverProvider @inject TieredStorageService TieredStorage -@inject HomeserverResolverService HomeserverResolver \ No newline at end of file +@inject HomeserverResolverService HomeserverResolver +@inject IJSRuntime JSRuntime diff --git a/MatrixRoomUtils.Web/wwwroot/css/app.css b/MatrixRoomUtils.Web/wwwroot/css/app.css index b3a8cf3..bcd8f5d 100644 --- a/MatrixRoomUtils.Web/wwwroot/css/app.css +++ b/MatrixRoomUtils.Web/wwwroot/css/app.css @@ -118,4 +118,4 @@ a, .btn-link { pre { font-family: JetBrainsMono, var(--bs-font-monospace); -} \ No newline at end of file +} diff --git a/MatrixRoomUtils.Web/wwwroot/index.html b/MatrixRoomUtils.Web/wwwroot/index.html index 0598c4d..9a85530 100644 --- a/MatrixRoomUtils.Web/wwwroot/index.html +++ b/MatrixRoomUtils.Web/wwwroot/index.html @@ -34,10 +34,21 @@ if (element instanceof HTMLElement) { console.log(element); element.focus(); - } else if (element.__internalId) { + } else if (element.hasOwnProperty("__internalId")) { console.log("Element is not an HTMLElement", element); } } + function getWidth(element) { + console.log("getWidth", element); + if (element == null) return 0; + if (element instanceof HTMLElement) { + return element.offsetWidth + } else if (element.hasOwnProperty("__internalId")) { + console.log("Element is not an HTMLElement", element); + return 0; + } + return 0; + } function getWindowDimensions() { return { width: window.innerWidth, -- cgit 1.4.1