Browse Source

Added local expiration date

Krzysztof Krysiński 4 months ago
parent
commit
23e28f2d6b

+ 55 - 38
src/PixiEditor.PixiAuth/PixiAuthClient.cs

@@ -17,7 +17,6 @@ public class PixiAuthClient
         httpClient = new HttpClient();
         httpClient.BaseAddress = new Uri(baseUrl);
         // TODO: Update expiration date locally
-        // TODO: Add error code handling
     }
 
     public async Task<Guid?> GenerateSession(string email)
@@ -51,7 +50,7 @@ public class PixiAuthClient
         return null;
     }
 
-    public async Task<string?> TryClaimSessionToken(string email, Guid session)
+    public async Task<(string? token, DateTime? expirationDate)> TryClaimSessionToken(string email, Guid session)
     {
         Dictionary<string, string> body = new() { { "email", email }, { "sessionId", session.ToString() } };
         var response = await httpClient.GetAsync($"/session/claimToken?userEmail={email}&sessionId={session}");
@@ -62,14 +61,19 @@ public class PixiAuthClient
             Dictionary<string, string>? resultDict =
                 System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, string>>(result);
             string? token = null;
+            DateTime? expirationDate = null;
             if (resultDict != null && resultDict.TryGetValue("token", out token))
             {
-                return token;
-            }
+                DateTime? expiration = null;
+                if (resultDict.TryGetValue("expirationDate", out string? expirationString))
+                {
+                    if (DateTime.TryParse(expirationString, out DateTime expirationDateValue))
+                    {
+                        expirationDate = expirationDateValue;
+                    }
+                }
 
-            if (!string.IsNullOrEmpty(token))
-            {
-                return token;
+                return (token, expirationDate);
             }
         }
         else if (response.StatusCode == HttpStatusCode.BadRequest)
@@ -81,7 +85,7 @@ public class PixiAuthClient
             throw new InternalServerErrorException("INTERNAL_SERVER_ERROR");
         }
 
-        return null;
+        return (null, null);
     }
 
     /// <summary>
@@ -91,11 +95,12 @@ public class PixiAuthClient
     /// <param name="userSessionToken">Authentication token.</param>
     /// <returns>Token if successful, null otherwise.</returns>
     /// <exception cref="UnauthorizedAccessException">Thrown if the session is not valid.</exception>
-    public async Task<string?> RefreshToken(Guid userSessionId, string userSessionToken)
+    public async Task<(string? token, DateTime? expirationDate)> RefreshToken(Guid userSessionId,
+        string userSessionToken)
     {
         if (string.IsNullOrEmpty(userSessionToken))
         {
-            return null;
+            return (null, null);
         }
 
         HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "/session/refreshToken");
@@ -112,12 +117,16 @@ public class PixiAuthClient
             string? token = null;
             if (resultDict != null && resultDict.TryGetValue("token", out token))
             {
-                return token;
-            }
+                DateTime? expirationDate = null;
+                if (resultDict.TryGetValue("expirationDate", out string? expirationString))
+                {
+                    if (DateTime.TryParse(expirationString, out DateTime expirationDateValue))
+                    {
+                        expirationDate = expirationDateValue;
+                    }
+                }
 
-            if (!string.IsNullOrEmpty(token))
-            {
-                return token;
+                return (token, expirationDate);
             }
         }
         else if (response.StatusCode == HttpStatusCode.Forbidden)
@@ -133,7 +142,7 @@ public class PixiAuthClient
             throw new InternalServerErrorException("INTERNAL_SERVER_ERROR");
         }
 
-        return null;
+        return (null, null);
     }
 
     public async Task Logout(Guid userSessionId, string userSessionToken)
@@ -161,42 +170,50 @@ public class PixiAuthClient
         var response = await httpClient.PostAsJsonAsync("/session/resendActivation",
             new ResendActivationModel(userEmail, userSessionId));
 
-        if (!response.IsSuccessStatusCode)
+        if (response.StatusCode == HttpStatusCode.BadRequest)
         {
-            Dictionary<string, object> responseData =
-                System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(
-                    await response.Content.ReadAsStringAsync());
-            if (responseData != null && responseData.TryGetValue("error", out object? error))
+            string responseString = await response.Content.ReadAsStringAsync();
+            try
             {
-                if (error is JsonElement errorElement)
+                Dictionary<string, object> responseData =
+                    System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(responseString);
+                if (responseData != null && responseData.TryGetValue("error", out object? error))
                 {
-                    error = errorElement.GetString();
-                }
+                    if (error is JsonElement errorElement)
+                    {
+                        error = errorElement.GetString();
+                    }
 
 
-                if (error is string errorString and "TOO_MANY_REQUESTS")
-                {
-                    if (responseData.TryGetValue("timeLeft", out object? timeLeft))
+                    if (error is string errorString and "TOO_MANY_REQUESTS")
                     {
-                        if (timeLeft is JsonElement timeLeftElement)
+                        if (responseData.TryGetValue("timeLeft", out object? timeLeft))
                         {
-                            timeLeft = timeLeftElement.GetDouble();
+                            if (timeLeft is JsonElement timeLeftElement)
+                            {
+                                timeLeft = timeLeftElement.GetDouble();
+                            }
+
+                            if (timeLeft is double timeLeftDouble)
+                            {
+                                double seconds = double.Round(timeLeftDouble / 1000);
+                                throw new TooManyRequestsException(errorString, seconds);
+                            }
                         }
 
-                        if (timeLeft is double timeLeftDouble)
-                        {
-                            double seconds = double.Round(timeLeftDouble / 1000);
-                            throw new TooManyRequestsException(errorString, seconds);
-                        }
+                        throw new BadRequestException(errorString);
                     }
-
-                    throw new BadRequestException(errorString);
                 }
             }
-            else if (response.StatusCode == HttpStatusCode.InternalServerError)
+            catch (JsonException)
             {
-                throw new InternalServerErrorException("INTERNAL_SERVER_ERROR");
+                // Handle JSON parsing error
+                throw new BadRequestException(responseString);
             }
         }
+        else if (response.StatusCode == HttpStatusCode.InternalServerError)
+        {
+            throw new InternalServerErrorException("INTERNAL_SERVER_ERROR");
+        }
     }
 }

+ 1 - 0
src/PixiEditor.PixiAuth/User.cs

@@ -6,6 +6,7 @@ public class User
     public string Email { get; set; } = string.Empty;
     public Guid? SessionId { get; set; }
     public string? SessionToken { get; set; } = string.Empty;
+    public DateTime? SessionExpirationDate { get; set; }
 
     public User()
     {

+ 2 - 1
src/PixiEditor/Data/Localization/Languages/en.json

@@ -1030,5 +1030,6 @@
   "SESSION_NOT_VALID": "Session is not valid, please log in again",
   "SESSION_NOT_FOUND": "Session not found, try logging in again",
   "INTERNAL_SERVER_ERROR": "There was an internal server error. Please try again later.",
-  "TOO_MANY_REQUESTS": "Too many requests. Try again in {0} seconds."
+  "TOO_MANY_REQUESTS": "Too many requests. Try again in {0} seconds.",
+  "SESSION_EXPIRED": "Session expired. Please log in again."
 }

+ 23 - 5
src/PixiEditor/ViewModels/SubViewModels/UserViewModel.cs

@@ -79,6 +79,7 @@ internal class UserViewModel : SubViewModel<ViewModelMain>
         {
             await LoadUserData();
             await TryRefreshToken();
+            await LogoutIfTokenExpired();
         });
     }
 
@@ -166,11 +167,13 @@ internal class UserViewModel : SubViewModel<ViewModelMain>
 
         try
         {
-            string? token = await PixiAuthClient.RefreshToken(User.SessionId.Value, User.SessionToken);
+            (string? token, DateTime? expirationDate) =
+                await PixiAuthClient.RefreshToken(User.SessionId.Value, User.SessionToken);
 
             if (token != null)
             {
                 User.SessionToken = token;
+                User.SessionExpirationDate = expirationDate;
                 NotifyProperties();
                 SaveUserInfo();
                 return true;
@@ -202,11 +205,13 @@ internal class UserViewModel : SubViewModel<ViewModelMain>
 
         try
         {
-            string? token = await PixiAuthClient.TryClaimSessionToken(User.Email, User.SessionId.Value);
+            (string? token, DateTime? expirationDate) =
+                await PixiAuthClient.TryClaimSessionToken(User.Email, User.SessionId.Value);
             if (token != null)
             {
                 LastError = null;
                 User.SessionToken = token;
+                User.SessionExpirationDate = expirationDate;
                 NotifyProperties();
                 SaveUserInfo();
                 return true;
@@ -229,17 +234,21 @@ internal class UserViewModel : SubViewModel<ViewModelMain>
 
     public async Task Logout()
     {
-        if (!apiValid) return;
-
         if (!IsLoggedIn)
         {
             return;
         }
 
+        Guid? sessionId = User?.SessionId;
+        string? sessionToken = User?.SessionToken;
+
         User = null;
         NotifyProperties();
         SaveUserInfo();
-        await PixiAuthClient.Logout(User.SessionId.Value, User.SessionToken);
+
+        if (!apiValid) return;
+
+        await PixiAuthClient.Logout(sessionId.Value, sessionToken);
     }
 
     public async Task SaveUserInfo()
@@ -252,6 +261,15 @@ internal class UserViewModel : SubViewModel<ViewModelMain>
         User = await IOperatingSystem.Current.SecureStorage.GetValueAsync<User>("UserData", null);
     }
 
+    public async Task LogoutIfTokenExpired()
+    {
+        if (User?.SessionExpirationDate != null && User.SessionExpirationDate < DateTime.Now)
+        {
+            await Logout();
+            LastError = new LocalizedString("SESSION_EXPIRED");
+        }
+    }
+
     private void NotifyProperties()
     {
         OnPropertyChanged(nameof(User));

+ 1 - 1
src/PixiEditor/Views/Auth/LoginPopup.axaml

@@ -15,7 +15,7 @@
         <subViewModels:UserViewModel />
     </Design.DataContext>
     <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="5" Margin="10" MinWidth="300">
-        <auth:LoginForm IsVisible="{Binding NotLoggedIn}" RequestLoginCommand="{Binding Path=RequestLoginCommand}" />
+        <auth:LoginForm IsVisible="{Binding !IsLoggedIn}" RequestLoginCommand="{Binding Path=RequestLoginCommand}" />
         <TextBlock Text="Email sent! Check your inbox." IsVisible="{Binding WaitingForActivation}" />
         <TextBlock Text="Logged in as " IsVisible="{Binding IsLoggedIn}">
             <Run Text="{Binding User.Email}" />