mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-18 13:28:12 -04:00
Refactor web app MobileLoginUtility flow, add helper model (#1347)
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="MobileLoginResult.cs" company="aliasvault">
|
||||
// Copyright (c) aliasvault. All rights reserved.
|
||||
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.Client.Auth.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Result of a successful mobile login containing decrypted authentication data.
|
||||
/// </summary>
|
||||
public sealed class MobileLoginResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the username.
|
||||
/// </summary>
|
||||
public required string Username { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the JWT access token.
|
||||
/// </summary>
|
||||
public required string Token { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the refresh token.
|
||||
/// </summary>
|
||||
public required string RefreshToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the vault decryption key (base64 encoded).
|
||||
/// </summary>
|
||||
public required string DecryptionKey { get; set; }
|
||||
}
|
||||
@@ -4,9 +4,11 @@
|
||||
@attribute [AllowAnonymous]
|
||||
@using System.Text.Json
|
||||
@using AliasVault.Client.Auth.Components
|
||||
@using AliasVault.Client.Auth.Models
|
||||
@using AliasVault.Client.Auth.Services
|
||||
@using AliasVault.Client.Utilities
|
||||
@using AliasVault.Cryptography.Client
|
||||
@using AliasVault.Shared.Models.WebApi.Auth
|
||||
@using Microsoft.Extensions.Localization
|
||||
@implements IDisposable
|
||||
|
||||
@@ -140,9 +142,10 @@
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle successful authentication.
|
||||
/// Handle successful authentication from mobile login.
|
||||
/// </summary>
|
||||
private async Task HandleSuccessfulAuthAsync(string username, string token, string refreshToken, string decryptionKey, string salt, string encryptionType, string encryptionSettings)
|
||||
/// <param name="result">The decrypted mobile login result containing authentication data.</param>
|
||||
private async Task HandleSuccessfulAuthAsync(MobileLoginResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -150,12 +153,33 @@
|
||||
_qrCodeUrl = null;
|
||||
StateHasChanged();
|
||||
|
||||
// Call /login endpoint to retrieve salt and encryption settings
|
||||
var loginInitiateRequest = new { username = result.Username };
|
||||
var loginResponse = await Http.PostAsJsonAsync("v1/Auth/login", loginInitiateRequest);
|
||||
|
||||
if (!loginResponse.IsSuccessStatusCode)
|
||||
{
|
||||
_isLoading = false;
|
||||
_errorMessage = SharedLocalizer["ErrorUnknown"];
|
||||
StateHasChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
var loginData = await loginResponse.Content.ReadFromJsonAsync<LoginInitiateResponse>();
|
||||
if (loginData == null)
|
||||
{
|
||||
_isLoading = false;
|
||||
_errorMessage = SharedLocalizer["ErrorUnknown"];
|
||||
StateHasChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the tokens in local storage
|
||||
await AuthService.StoreAccessTokenAsync(token);
|
||||
await AuthService.StoreRefreshTokenAsync(refreshToken);
|
||||
await AuthService.StoreAccessTokenAsync(result.Token);
|
||||
await AuthService.StoreRefreshTokenAsync(result.RefreshToken);
|
||||
|
||||
// Convert decryption key from base64 string to byte array
|
||||
var decryptionKeyBytes = Convert.FromBase64String(decryptionKey);
|
||||
var decryptionKeyBytes = Convert.FromBase64String(result.DecryptionKey);
|
||||
|
||||
// Store the encryption key in memory
|
||||
await AuthService.StoreEncryptionKeyAsync(decryptionKeyBytes);
|
||||
|
||||
@@ -11,6 +11,7 @@ using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AliasVault.Client.Auth.Models;
|
||||
using AliasVault.Client.Services.JsInterop;
|
||||
using AliasVault.Shared.Models.WebApi.Auth;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -75,10 +76,10 @@ public sealed class MobileLoginUtility : IDisposable
|
||||
/// <summary>
|
||||
/// Starts polling the server for mobile login response.
|
||||
/// </summary>
|
||||
/// <param name="onSuccess">Callback for successful authentication.</param>
|
||||
/// <param name="onSuccess">Callback for successful authentication with decrypted login result.</param>
|
||||
/// <param name="onError">Callback for errors.</param>
|
||||
/// <returns>Task.</returns>
|
||||
public Task StartPollingAsync(Func<string, string, string, string, string, string, string, Task> onSuccess, Action<string> onError)
|
||||
public Task StartPollingAsync(Func<MobileLoginResult, Task> onSuccess, Action<string> onError)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_requestId) || string.IsNullOrEmpty(_privateKey))
|
||||
{
|
||||
@@ -134,7 +135,7 @@ public sealed class MobileLoginUtility : IDisposable
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
private async Task PollServerAsync(Func<string, string, string, string, string, string, string, Task> onSuccess, Action<string> onError)
|
||||
private async Task PollServerAsync(Func<MobileLoginResult, Task> onSuccess, Action<string> onError)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_requestId) || _cancellationTokenSource?.IsCancellationRequested == true)
|
||||
{
|
||||
@@ -161,20 +162,34 @@ public sealed class MobileLoginUtility : IDisposable
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<MobileLoginPollResponse>();
|
||||
|
||||
if (result?.Fulfilled == true && !string.IsNullOrEmpty(result.EncryptedDecryptionKey) && !string.IsNullOrEmpty(result.Username) && result.Token != null && !string.IsNullOrEmpty(result.Salt) && !string.IsNullOrEmpty(result.EncryptionType) && !string.IsNullOrEmpty(result.EncryptionSettings))
|
||||
if (result?.Fulfilled == true && !string.IsNullOrEmpty(result.EncryptedSymmetricKey))
|
||||
{
|
||||
// Stop polling
|
||||
StopPolling();
|
||||
|
||||
// Decrypt the decryption key using private key
|
||||
var decryptionKey = await _jsInteropService.DecryptWithPrivateKey(result.EncryptedDecryptionKey, _privateKey!);
|
||||
// Decrypt the vault decryption key directly with RSA private key
|
||||
var decryptionKey = await _jsInteropService.DecryptWithPrivateKey(result.EncryptedDecryptionKey!, _privateKey!);
|
||||
|
||||
// Decrypt the symmetric key with RSA private key
|
||||
var symmetricKeyBase64 = await _jsInteropService.DecryptWithPrivateKey(result.EncryptedSymmetricKey, _privateKey!);
|
||||
|
||||
// Decrypt all remaining fields using the symmetric key
|
||||
var token = await _jsInteropService.SymmetricDecrypt(result.EncryptedToken!, symmetricKeyBase64);
|
||||
var refreshToken = await _jsInteropService.SymmetricDecrypt(result.EncryptedRefreshToken!, symmetricKeyBase64);
|
||||
var username = await _jsInteropService.SymmetricDecrypt(result.EncryptedUsername!, symmetricKeyBase64);
|
||||
|
||||
// Clear sensitive data
|
||||
_privateKey = null;
|
||||
_requestId = null;
|
||||
|
||||
// Call success callback
|
||||
await onSuccess(result.Username, result.Token.Token, result.Token.RefreshToken, decryptionKey, result.Salt, result.EncryptionType, result.EncryptionSettings);
|
||||
// Call success callback with decrypted data
|
||||
await onSuccess(new MobileLoginResult
|
||||
{
|
||||
Username = username,
|
||||
Token = token,
|
||||
RefreshToken = refreshToken,
|
||||
DecryptionKey = decryptionKey,
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
Reference in New Issue
Block a user