mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-07 23:05:40 -04:00
Refactor (#181)
This commit is contained in:
committed by
Leendert de Borst
parent
fe4b11cf4d
commit
90156dd1f8
@@ -1,5 +1,4 @@
|
||||
@inherits ComponentBase
|
||||
@inject TotpCodeService TotpCodeService
|
||||
@inject GlobalNotificationService GlobalNotificationService
|
||||
@inject ConfirmModalService ConfirmModalService
|
||||
@using AliasVault.RazorComponents.Services
|
||||
@@ -10,7 +9,7 @@
|
||||
<div>
|
||||
<h3 class="mb-4 text-xl font-semibold dark:text-white">Two-factor authentication</h3>
|
||||
</div>
|
||||
@if (TotpCodeList.Where(t => !t.IsDeleted).Any() && !IsAddFormVisible)
|
||||
@if (TotpCodeList.Any(t => !t.IsDeleted) && !IsAddFormVisible)
|
||||
{
|
||||
<div>
|
||||
<button id="add-totp-code" @onclick="ShowAddForm" type="button" class="text-blue-700 hover:text-white border border-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-3 py-2 text-center dark:border-blue-500 dark:text-blue-500 dark:hover:text-white dark:hover:bg-blue-600 dark:focus:ring-blue-800">
|
||||
@@ -100,11 +99,6 @@
|
||||
[Parameter]
|
||||
public EventCallback<List<TotpCode>> TotpCodesChanged { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The dictionary of current cached TOTP codes.
|
||||
/// </summary>
|
||||
private Dictionary<string, string> _currentCodes = new();
|
||||
|
||||
private bool IsAddFormVisible { get; set; } = false;
|
||||
private TotpCodeEdit NewTotpCode { get; set; } = new();
|
||||
private List<Guid> OriginalTotpCodeIds { get; set; } = [];
|
||||
@@ -150,7 +144,6 @@
|
||||
|
||||
// Sanitize the secret key (remove whitespace and hyphens)
|
||||
secretKey = secretKey.Replace(" ", string.Empty).Replace("-", string.Empty);
|
||||
|
||||
string? name = NewTotpCode.Name;
|
||||
|
||||
// Check if the input is a TOTP URI
|
||||
@@ -162,7 +155,7 @@
|
||||
var queryParams = System.Web.HttpUtility.ParseQueryString(uri.Query);
|
||||
|
||||
// Extract the secret from query parameters
|
||||
secretKey = queryParams["secret"] ?? throw new Exception("Secret not found in URI");
|
||||
secretKey = queryParams["secret"] ?? throw new ArgumentException("Secret not found in URI");
|
||||
|
||||
// If no name was provided, try to get it from the URI
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
@@ -211,9 +204,6 @@
|
||||
await TotpCodesChanged.InvokeAsync(TotpCodeList);
|
||||
|
||||
HideAddForm();
|
||||
|
||||
// Refresh the codes
|
||||
_currentCodes[newTotpCode.SecretKey] = TotpGenerator.GenerateTotpCode(newTotpCode.SecretKey);
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
@inherits ComponentBase
|
||||
@inject TotpCodeService TotpCodeService
|
||||
@inject ClipboardCopyService ClipboardCopyService
|
||||
@inject JsInteropService JsInteropService
|
||||
@implements IDisposable
|
||||
@@ -69,7 +68,7 @@
|
||||
/// <summary>
|
||||
/// The dictionary of current cached TOTP codes.
|
||||
/// </summary>
|
||||
private Dictionary<string, string> _currentCodes = new();
|
||||
private readonly Dictionary<string, string> _currentCodes = new();
|
||||
|
||||
private bool IsLoading { get; set; } = true;
|
||||
private Timer? _refreshTimer;
|
||||
@@ -86,9 +85,9 @@
|
||||
await base.OnInitializedAsync();
|
||||
|
||||
// Generate initial codes
|
||||
foreach (var code in TotpCodeList)
|
||||
foreach (var code in TotpCodeList.Select(t => t.SecretKey))
|
||||
{
|
||||
_currentCodes[code.SecretKey] = TotpGenerator.GenerateTotpCode(code.SecretKey);
|
||||
_currentCodes[code] = TotpGenerator.GenerateTotpCode(code);
|
||||
}
|
||||
|
||||
// Start a timer to refresh the TOTP codes every second
|
||||
@@ -97,17 +96,27 @@
|
||||
IsLoading = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the remaining seconds for the TOTP code.
|
||||
/// </summary>
|
||||
/// <returns>The remaining seconds.</returns>
|
||||
private static int GetRemainingSeconds(int step = 30)
|
||||
{
|
||||
var unixTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
return step - (int)(unixTimestamp % step);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the TOTP codes by generating new codes based on the secret keys.
|
||||
/// </summary>
|
||||
private async Task RefreshCodesAsync()
|
||||
{
|
||||
foreach (var code in TotpCodeList)
|
||||
foreach (var code in TotpCodeList.Select(t => t.SecretKey))
|
||||
{
|
||||
var newCode = TotpGenerator.GenerateTotpCode(code.SecretKey);
|
||||
if (!_currentCodes.ContainsKey(code.SecretKey) || _currentCodes[code.SecretKey] != newCode)
|
||||
var newCode = TotpGenerator.GenerateTotpCode(code);
|
||||
if (!_currentCodes.ContainsKey(code) || _currentCodes[code] != newCode)
|
||||
{
|
||||
_currentCodes[code.SecretKey] = newCode;
|
||||
_currentCodes[code] = newCode;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,15 +141,6 @@
|
||||
return newCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the remaining seconds for the TOTP code.
|
||||
/// </summary>
|
||||
/// <returns>The remaining seconds.</returns>
|
||||
private int GetRemainingSeconds()
|
||||
{
|
||||
return TotpCodeService.GetRemainingSeconds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the remaining percentage for the TOTP code.
|
||||
/// </summary>
|
||||
|
||||
@@ -74,7 +74,6 @@ builder.Services.AddScoped<GlobalLoadingService>();
|
||||
builder.Services.AddScoped<KeyboardShortcutService>();
|
||||
builder.Services.AddScoped<JsInteropService>();
|
||||
builder.Services.AddScoped<EmailService>();
|
||||
builder.Services.AddScoped<TotpCodeService>();
|
||||
builder.Services.AddSingleton<ClipboardCopyService>();
|
||||
builder.Services.AddScoped<ConfirmModalService>();
|
||||
|
||||
|
||||
@@ -222,8 +222,6 @@ public sealed class CredentialService(HttpClient httpClient, DbService dbService
|
||||
throw new InvalidOperationException("Login object not found.");
|
||||
}
|
||||
|
||||
// If the email starts with an @ it is most likely still the placeholder which hasn't been filled.
|
||||
// So we remove it.
|
||||
if (loginObject.Alias.Email is not null && loginObject.Alias.Email.StartsWith('@'))
|
||||
{
|
||||
loginObject.Alias.Email = null;
|
||||
@@ -235,87 +233,13 @@ public sealed class CredentialService(HttpClient httpClient, DbService dbService
|
||||
loginObject.Service.Url = null;
|
||||
}
|
||||
|
||||
login.UpdatedAt = DateTime.UtcNow;
|
||||
login.Notes = loginObject.Notes;
|
||||
login.Username = loginObject.Username;
|
||||
// Update all fields and collections.
|
||||
UpdateBasicCredentialInfo(login, loginObject);
|
||||
UpdateAttachments(context, login, loginObject);
|
||||
UpdateTotpCodes(context, login, loginObject);
|
||||
|
||||
login.Alias.NickName = loginObject.Alias.NickName;
|
||||
login.Alias.FirstName = loginObject.Alias.FirstName;
|
||||
login.Alias.LastName = loginObject.Alias.LastName;
|
||||
login.Alias.BirthDate = loginObject.Alias.BirthDate;
|
||||
login.Alias.Gender = loginObject.Alias.Gender;
|
||||
login.Alias.Email = loginObject.Alias.Email;
|
||||
login.Alias.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
login.Passwords = loginObject.Passwords;
|
||||
if (login.Passwords.Count > 0)
|
||||
{
|
||||
login.Passwords.First().UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
login.Service.Name = loginObject.Service.Name;
|
||||
login.Service.Url = loginObject.Service.Url;
|
||||
login.Service.Logo = loginObject.Service.Logo;
|
||||
login.Service.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
// Remove attachments that are no longer in the list
|
||||
var attachmentsToRemove = login.Attachments.Where(existingAttachment => !loginObject.Attachments.Any(a => a.Id == existingAttachment.Id)).ToList();
|
||||
foreach (var attachmentToRemove in attachmentsToRemove)
|
||||
{
|
||||
login.Attachments.Remove(attachmentToRemove);
|
||||
context.Entry(attachmentToRemove).State = EntityState.Deleted;
|
||||
}
|
||||
|
||||
// Update existing attachments and add new ones
|
||||
foreach (var attachment in loginObject.Attachments)
|
||||
{
|
||||
if (attachment.Id != Guid.Empty)
|
||||
{
|
||||
var existingAttachment = login.Attachments.FirstOrDefault(a => a.Id == attachment.Id);
|
||||
if (existingAttachment != null)
|
||||
{
|
||||
// Update existing attachment
|
||||
context.Entry(existingAttachment).CurrentValues.SetValues(attachment);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add new attachment
|
||||
login.Attachments.Add(attachment);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove TOTP codes that are no longer in the list
|
||||
var totpCodesToRemove = login.TotpCodes.Where(existingTotp => !loginObject.TotpCodes.Any(t => t.Id == existingTotp.Id)).ToList();
|
||||
foreach (var totpToRemove in totpCodesToRemove)
|
||||
{
|
||||
login.TotpCodes.Remove(totpToRemove);
|
||||
context.Entry(totpToRemove).State = EntityState.Deleted;
|
||||
}
|
||||
|
||||
// Update existing TOTP codes and add new ones
|
||||
foreach (var totpCode in loginObject.TotpCodes)
|
||||
{
|
||||
if (totpCode.Id != Guid.Empty)
|
||||
{
|
||||
var existingTotpCode = login.TotpCodes.FirstOrDefault(t => t.Id == totpCode.Id);
|
||||
if (existingTotpCode != null)
|
||||
{
|
||||
// Update existing TOTP code
|
||||
context.Entry(existingTotpCode).CurrentValues.SetValues(totpCode);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add new TOTP code
|
||||
login.TotpCodes.Add(totpCode);
|
||||
}
|
||||
}
|
||||
|
||||
// Save the database to the server.
|
||||
if (!await dbService.SaveDatabaseAsync())
|
||||
{
|
||||
// If saving database failed, return empty guid to indicate error.
|
||||
return Guid.Empty;
|
||||
}
|
||||
|
||||
@@ -434,6 +358,107 @@ public sealed class CredentialService(HttpClient httpClient, DbService dbService
|
||||
return await dbService.SaveDatabaseAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the basic credential information.
|
||||
/// </summary>
|
||||
/// <param name="login">The login object to update.</param>
|
||||
/// <param name="loginObject">The login object to update from.</param>
|
||||
private static void UpdateBasicCredentialInfo(Credential login, Credential loginObject)
|
||||
{
|
||||
login.UpdatedAt = DateTime.UtcNow;
|
||||
login.Notes = loginObject.Notes;
|
||||
login.Username = loginObject.Username;
|
||||
|
||||
login.Alias.NickName = loginObject.Alias.NickName;
|
||||
login.Alias.FirstName = loginObject.Alias.FirstName;
|
||||
login.Alias.LastName = loginObject.Alias.LastName;
|
||||
login.Alias.BirthDate = loginObject.Alias.BirthDate;
|
||||
login.Alias.Gender = loginObject.Alias.Gender;
|
||||
login.Alias.Email = loginObject.Alias.Email;
|
||||
login.Alias.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
login.Passwords = loginObject.Passwords;
|
||||
if (login.Passwords.Count > 0)
|
||||
{
|
||||
login.Passwords.First().UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
login.Service.Name = loginObject.Service.Name;
|
||||
login.Service.Url = loginObject.Service.Url;
|
||||
login.Service.Logo = loginObject.Service.Logo;
|
||||
login.Service.UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the attachments.
|
||||
/// </summary>
|
||||
/// <param name="context">The database context.</param>
|
||||
/// <param name="login">The login object to update.</param>
|
||||
/// <param name="loginObject">The login object to update from.</param>
|
||||
private static void UpdateAttachments(DbContext context, Credential login, Credential loginObject)
|
||||
{
|
||||
var attachmentsToRemove = login.Attachments
|
||||
.Where(existingAttachment => !loginObject.Attachments.Any(a => a.Id == existingAttachment.Id))
|
||||
.ToList();
|
||||
|
||||
foreach (var attachmentToRemove in attachmentsToRemove)
|
||||
{
|
||||
login.Attachments.Remove(attachmentToRemove);
|
||||
context.Entry(attachmentToRemove).State = EntityState.Deleted;
|
||||
}
|
||||
|
||||
foreach (var attachment in loginObject.Attachments)
|
||||
{
|
||||
if (attachment.Id != Guid.Empty)
|
||||
{
|
||||
var existingAttachment = login.Attachments.FirstOrDefault(a => a.Id == attachment.Id);
|
||||
if (existingAttachment != null)
|
||||
{
|
||||
context.Entry(existingAttachment).CurrentValues.SetValues(attachment);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
login.Attachments.Add(attachment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the TOTP codes.
|
||||
/// </summary>
|
||||
/// <param name="context">The database context.</param>
|
||||
/// <param name="login">The login object to update.</param>
|
||||
/// <param name="loginObject">The login object to update from.</param>
|
||||
private static void UpdateTotpCodes(DbContext context, Credential login, Credential loginObject)
|
||||
{
|
||||
var totpCodesToRemove = login.TotpCodes
|
||||
.Where(existingTotp => !loginObject.TotpCodes.Any(t => t.Id == existingTotp.Id))
|
||||
.ToList();
|
||||
|
||||
foreach (var totpToRemove in totpCodesToRemove)
|
||||
{
|
||||
login.TotpCodes.Remove(totpToRemove);
|
||||
context.Entry(totpToRemove).State = EntityState.Deleted;
|
||||
}
|
||||
|
||||
foreach (var totpCode in loginObject.TotpCodes)
|
||||
{
|
||||
if (totpCode.Id != Guid.Empty)
|
||||
{
|
||||
var existingTotpCode = login.TotpCodes.FirstOrDefault(t => t.Id == totpCode.Id);
|
||||
if (existingTotpCode != null)
|
||||
{
|
||||
context.Entry(existingTotpCode).CurrentValues.SetValues(totpCode);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
login.TotpCodes.Add(totpCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract favicon from service URL if available in object. If successful the passed object itself will be updated with the bytes.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="TotpCodeService.cs" company="lanedirt">
|
||||
// Copyright (c) lanedirt. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.Client.Services;
|
||||
|
||||
using AliasClientDb;
|
||||
using AliasVault.Client.Services.Database;
|
||||
using AliasVault.TotpGenerator;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
/// <summary>
|
||||
/// Service for managing TOTP codes.
|
||||
/// </summary>
|
||||
public class TotpCodeService
|
||||
{
|
||||
private readonly DbService _dbService;
|
||||
private readonly ILogger<TotpCodeService> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TotpCodeService"/> class.
|
||||
/// </summary>
|
||||
/// <param name="dbService">The database service.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
public TotpCodeService(DbService dbService, ILogger<TotpCodeService> logger)
|
||||
{
|
||||
_dbService = dbService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the remaining seconds until the TOTP code expires.
|
||||
/// </summary>
|
||||
/// <param name="step">The time step in seconds. Default is 30.</param>
|
||||
/// <returns>The remaining seconds.</returns>
|
||||
public int GetRemainingSeconds(int step = 30)
|
||||
{
|
||||
var unixTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
return step - (int)(unixTimestamp % step);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user