mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-03-20 15:41:40 -04:00
445 lines
17 KiB
Plaintext
445 lines
17 KiB
Plaintext
@page "/credentials/create"
|
|
@page "/credentials/{id:guid}/edit"
|
|
@inherits MainBase
|
|
@inject CredentialService CredentialService
|
|
@using System.Globalization
|
|
@using System.Text.Json
|
|
@using System.Text.Json.Serialization
|
|
@using AliasVault.Generators.Identity
|
|
@using AliasVault.Generators.Identity.Implementations.Factories
|
|
@using AliasVault.Generators.Identity.Models
|
|
@inject IJSRuntime JSRuntime
|
|
@implements IAsyncDisposable
|
|
|
|
<PageHeader
|
|
BreadcrumbItems="@BreadcrumbItems"
|
|
Title="@(EditMode ? "Edit credentials" : "Add credentials")"
|
|
Description="@(EditMode ? "Edit the existing credentials entry below." : "Create a new credentials entry below.")">
|
|
<CustomActions>
|
|
<ConfirmButton OnClick="TriggerFormSubmit">Save Credentials</ConfirmButton>
|
|
<CancelButton OnClick="Cancel">Cancel</CancelButton>
|
|
</CustomActions>
|
|
</PageHeader>
|
|
|
|
@if (Loading)
|
|
{
|
|
<LoadingIndicator />
|
|
}
|
|
else
|
|
{
|
|
<EditForm @ref="EditFormRef" Model="Obj" OnValidSubmit="SaveAlias">
|
|
<DataAnnotationsValidator />
|
|
<div class="grid grid-cols-3 px-4 pt-6 lg:gap-4 dark:bg-gray-900">
|
|
<div class="col-1">
|
|
<div class="p-4 mb-4 bg-white border-2 border-primary-600 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
|
|
<h3 class="mb-4 text-xl font-semibold dark:text-white">Service</h3>
|
|
<div class="grid gap-6">
|
|
<div class="col-span-6 sm:col-span-3">
|
|
<EditFormRow Id="service-name" Label="Service Name" @bind-Value="Obj.ServiceName"></EditFormRow>
|
|
<ValidationMessage For="() => Obj.ServiceName"/>
|
|
</div>
|
|
<div class="col-span-6 sm:col-span-3">
|
|
<EditFormRow Id="service-url" OnFocus="OnFocusUrlInput" Label="Service URL" @bind-Value="Obj.ServiceUrl"></EditFormRow>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col">
|
|
<div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
|
|
<h3 class="mb-4 text-xl font-semibold dark:text-white">Notes</h3>
|
|
<div class="grid gap-6">
|
|
<div class="col-span-6 sm:col-span-3">
|
|
<EditFormRow Type="textarea" Id="notes" Label="Notes" @bind-Value="Obj.Notes"></EditFormRow>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col">
|
|
<div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
|
|
<h3 class="mb-4 text-xl font-semibold dark:text-white">Attachments</h3>
|
|
<div class="grid gap-6">
|
|
<div class="col-span-6 sm:col-span-3">
|
|
<AttachmentUploader
|
|
Attachments="@Obj.Attachments"
|
|
AttachmentsChanged="@HandleAttachmentsChanged" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col col-span-2">
|
|
<div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
|
|
<h3 class="mb-4 text-xl font-semibold dark:text-white">Login credentials</h3>
|
|
<div class="mb-4">
|
|
<button type="button" @onclick="GenerateRandomAlias" class="px-4 py-2 text-white bg-primary-600 rounded-lg hover:bg-primary-700 focus:ring-4 focus:ring-primary-300 dark:bg-primary-500 dark:hover:bg-primary-600 dark:focus:ring-primary-800">Generate Random Alias</button>
|
|
</div>
|
|
<div class="grid gap-6">
|
|
<div class="col-span-6 sm:col-span-3">
|
|
<EditEmailFormRow Id="email" Label="Email" @bind-Value="Obj.Alias.Email"></EditEmailFormRow>
|
|
</div>
|
|
<div class="col-span-6 sm:col-span-3">
|
|
<div class="relative">
|
|
<EditFormRow Id="username" Label="Username" @bind-Value="Obj.Username"></EditFormRow>
|
|
<button type="button" class="text-white absolute end-1 bottom-1 bg-gray-700 hover:bg-gray-800 focus:ring-4 focus:outline-none focus:ring-gray-300 font-medium rounded-lg text-sm px-4 py-2 dark:bg-gray-600 dark:hover:bg-gray-700 dark:focus:ring-gray-800" @onclick="GenerateRandomUsername">New Username</button>
|
|
</div>
|
|
</div>
|
|
<div class="col-span-6 sm:col-span-3">
|
|
<div class="relative">
|
|
<EditFormRow Id="password" Label="Password" @bind-Value="Obj.Password.Value"></EditFormRow>
|
|
<button type="button" class="text-white absolute end-1 bottom-1 bg-gray-700 hover:bg-gray-800 focus:ring-4 focus:outline-none focus:ring-gray-300 font-medium rounded-lg text-sm px-4 py-2 dark:bg-gray-600 dark:hover:bg-gray-700 dark:focus:ring-gray-800" @onclick="GenerateRandomPassword">New Password</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col">
|
|
<div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
|
|
<h3 class="mb-4 text-xl font-semibold dark:text-white">Alias</h3>
|
|
<div class="grid gap-6">
|
|
<div class="col-span-6 sm:col-span-3">
|
|
<EditFormRow Id="first-name" Label="First Name" @bind-Value="Obj.Alias.FirstName"></EditFormRow>
|
|
</div>
|
|
<div class="col-span-6 sm:col-span-3">
|
|
<EditFormRow Id="last-name" Label="Last Name" @bind-Value="Obj.Alias.LastName"></EditFormRow>
|
|
</div>
|
|
<div class="col-span-6 sm:col-span-3">
|
|
<EditFormRow Id="nickname" Label="Nick Name" @bind-Value="Obj.Alias.NickName"></EditFormRow>
|
|
</div>
|
|
<div class="col-span-6 sm:col-span-3">
|
|
<EditFormRow Id="gender" Label="Gender" @bind-Value="Obj.Alias.Gender"></EditFormRow>
|
|
</div>
|
|
<div class="col-span-6 sm:col-span-3">
|
|
<EditFormRow Id="birthdate" Label="Birth Date" @bind-Value="Obj.AliasBirthDate"></EditFormRow>
|
|
<ValidationMessage For="() => Obj.AliasBirthDate"/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<button type="submit" class="hidden">Save Credentials</button>
|
|
</EditForm>
|
|
}
|
|
|
|
@code {
|
|
/// <summary>
|
|
/// Gets or sets the Credentials ID.
|
|
/// </summary>
|
|
[Parameter]
|
|
public Guid? Id { get; set; }
|
|
|
|
private bool EditMode { get; set; }
|
|
private EditForm EditFormRef { get; set; } = null!;
|
|
private bool Loading { get; set; } = true;
|
|
private CredentialEdit Obj { get; set; } = new();
|
|
private IJSObjectReference? Module;
|
|
|
|
/// <inheritdoc />
|
|
async ValueTask IAsyncDisposable.DisposeAsync()
|
|
{
|
|
await KeyboardShortcutService.UnregisterShortcutAsync("gc");
|
|
if (Module is not null)
|
|
{
|
|
await Module.DisposeAsync();
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override void OnInitialized()
|
|
{
|
|
if (Id.HasValue)
|
|
{
|
|
// Edit mode
|
|
EditMode = true;
|
|
}
|
|
else
|
|
{
|
|
// Add mode
|
|
EditMode = false;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
await base.OnInitializedAsync();
|
|
|
|
if (EditMode)
|
|
{
|
|
BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = "View credentials entry", Url = $"/credentials/{Id}" });
|
|
BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = "Edit credential" });
|
|
}
|
|
else
|
|
{
|
|
BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = "Add new credential" });
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
{
|
|
await base.OnAfterRenderAsync(firstRender);
|
|
|
|
if (firstRender)
|
|
{
|
|
Module = await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./js/modules/newIdentityWidget.js");
|
|
if (EditMode)
|
|
{
|
|
if (Id is null)
|
|
{
|
|
// Error loading alias.
|
|
GlobalNotificationService.AddErrorMessage("This credential does not exist (anymore). Please try again.");
|
|
NavigationManager.NavigateTo("/", false, true);
|
|
return;
|
|
}
|
|
|
|
// Load existing Obj, retrieve from service
|
|
var alias = await CredentialService.LoadEntryAsync(Id.Value);
|
|
if (alias is null)
|
|
{
|
|
// Error loading alias.
|
|
GlobalNotificationService.AddErrorMessage("This credential does not exist (anymore). Please try again.");
|
|
NavigationManager.NavigateTo("/", false, true);
|
|
return;
|
|
}
|
|
|
|
Obj = CredentialToCredentialEdit(alias);
|
|
if (Obj.ServiceUrl is null)
|
|
{
|
|
Obj.ServiceUrl = CredentialService.DefaultServiceUrl;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Create new Obj
|
|
var alias = new Credential();
|
|
alias.Alias = new Alias();
|
|
alias.Alias.Email = "@" + CredentialService.GetDefaultEmailDomain();
|
|
alias.Service = new Service();
|
|
alias.Passwords = new List<Password> { new Password() };
|
|
|
|
Obj = CredentialToCredentialEdit(alias);
|
|
Obj.ServiceUrl = CredentialService.DefaultServiceUrl;
|
|
}
|
|
|
|
Loading = false;
|
|
StateHasChanged();
|
|
|
|
if (!EditMode)
|
|
{
|
|
// When creating a new identity: start with focus on the service name input.
|
|
await JsInteropService.FocusElementById("service-name");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// When the URL input is focused, place cursor at the end of the default URL to allow for easy typing.
|
|
/// </summary>
|
|
private void OnFocusUrlInput(FocusEventArgs e)
|
|
{
|
|
if (Obj.ServiceUrl != CredentialService.DefaultServiceUrl)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Use a small delay to ensure the focus is set after the browser's default behavior.
|
|
Task.Delay(1).ContinueWith(_ =>
|
|
{
|
|
JSRuntime.InvokeVoidAsync("eval", $"document.getElementById('service-url').setSelectionRange({CredentialService.DefaultServiceUrl.Length}, {CredentialService.DefaultServiceUrl.Length})");
|
|
});
|
|
}
|
|
|
|
private void HandleAttachmentsChanged(List<Attachment> updatedAttachments)
|
|
{
|
|
Obj.Attachments = updatedAttachments;
|
|
StateHasChanged();
|
|
}
|
|
|
|
private async Task GenerateRandomAlias()
|
|
{
|
|
GlobalLoadingSpinner.Show();
|
|
StateHasChanged();
|
|
|
|
Obj = CredentialToCredentialEdit(await CredentialService.GenerateRandomIdentity(CredentialEditToCredential(Obj)));
|
|
|
|
GlobalLoadingSpinner.Hide();
|
|
StateHasChanged();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generate a new random username based on existing identity, or if no identity is present,
|
|
/// generate a new random identity.
|
|
/// </summary>
|
|
private async Task GenerateRandomUsername()
|
|
{
|
|
// If current object is null, then we create a new random identity.
|
|
Identity identity;
|
|
if (Obj.Alias.FirstName is null && Obj.Alias.LastName is null && Obj.Alias.BirthDate == DateTime.MinValue)
|
|
{
|
|
identity = await IdentityGeneratorFactory.CreateIdentityGenerator(DbService.Settings.DefaultIdentityLanguage).GenerateRandomIdentityAsync();
|
|
}
|
|
else
|
|
{
|
|
// Assemble identity model with the current values
|
|
identity = new Identity
|
|
{
|
|
FirstName = Obj.Alias.FirstName ?? string.Empty,
|
|
LastName = Obj.Alias.LastName ?? string.Empty,
|
|
BirthDate = Obj.Alias.BirthDate,
|
|
Gender = Obj.Alias.Gender == Gender.Female.ToString() ? Gender.Female : Gender.Male,
|
|
NickName = Obj.Alias.NickName ?? string.Empty,
|
|
};
|
|
}
|
|
|
|
var generator = new UsernameEmailGenerator();
|
|
Obj.Username = generator.GenerateUsername(identity);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generate a new random password.
|
|
/// </summary>
|
|
private void GenerateRandomPassword()
|
|
{
|
|
Obj.Password.Value = CredentialService.GenerateRandomPassword();
|
|
}
|
|
|
|
private async Task SaveAlias()
|
|
{
|
|
GlobalLoadingSpinner.Show();
|
|
StateHasChanged();
|
|
|
|
if (EditMode)
|
|
{
|
|
if (Id is not null)
|
|
{
|
|
Id = await CredentialService.UpdateEntryAsync(CredentialEditToCredential(Obj));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Id = await CredentialService.InsertEntryAsync(CredentialEditToCredential(Obj));
|
|
}
|
|
|
|
GlobalLoadingSpinner.Hide();
|
|
StateHasChanged();
|
|
|
|
if (Id is null || Id == Guid.Empty)
|
|
{
|
|
// Error saving.
|
|
GlobalNotificationService.AddErrorMessage("Error saving credentials. Please try again.", true);
|
|
return;
|
|
}
|
|
|
|
// No error, add success message.
|
|
if (EditMode)
|
|
{
|
|
GlobalNotificationService.AddSuccessMessage("Credentials updated successfully.");
|
|
}
|
|
else
|
|
{
|
|
GlobalNotificationService.AddSuccessMessage("Credentials created successfully.");
|
|
}
|
|
|
|
NavigationManager.NavigateTo("/credentials/" + Id);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper method to convert a Credential object to a CredentialEdit object.
|
|
/// </summary>
|
|
private CredentialEdit CredentialToCredentialEdit(Credential alias)
|
|
{
|
|
// Create a deep copy of the alias object to prevent changes to the original object
|
|
// when editing the alias in the form. We only want to save the changes when the user
|
|
// clicks the save button.
|
|
var options = new JsonSerializerOptions
|
|
{
|
|
ReferenceHandler = ReferenceHandler.Preserve,
|
|
MaxDepth = 128 // Adjust this value as needed
|
|
};
|
|
|
|
// Create a deep copy of the credential object
|
|
var aliasJson = JsonSerializer.Serialize(alias, options);
|
|
var aliasCopy = JsonSerializer.Deserialize<Credential>(aliasJson, options)!;
|
|
|
|
return new CredentialEdit
|
|
{
|
|
Id = aliasCopy.Id,
|
|
Notes = aliasCopy.Notes ?? string.Empty,
|
|
Username = aliasCopy.Username ?? string.Empty,
|
|
ServiceName = aliasCopy.Service.Name ?? string.Empty,
|
|
ServiceUrl = aliasCopy.Service.Url,
|
|
ServiceLogo = aliasCopy.Service.Logo,
|
|
Password = aliasCopy.Passwords.FirstOrDefault() ?? new Password
|
|
{
|
|
Value = string.Empty,
|
|
CreatedAt = DateTime.UtcNow,
|
|
UpdatedAt = DateTime.UtcNow,
|
|
},
|
|
Alias = aliasCopy.Alias,
|
|
AliasBirthDate = aliasCopy.Alias.BirthDate.ToString("yyyy-MM-dd"),
|
|
Attachments = aliasCopy.Attachments.ToList(),
|
|
CreateDate = aliasCopy.CreatedAt,
|
|
LastUpdate = aliasCopy.UpdatedAt
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper method to convert a CredentialEdit object to a Credential object.
|
|
/// </summary>
|
|
private Credential CredentialEditToCredential(CredentialEdit alias)
|
|
{
|
|
var credential = new Credential()
|
|
{
|
|
Id = alias.Id,
|
|
Notes = alias.Notes,
|
|
Username = alias.Username,
|
|
Service = new Service
|
|
{
|
|
Name = alias.ServiceName,
|
|
Url = alias.ServiceUrl,
|
|
Logo = alias.ServiceLogo,
|
|
},
|
|
Passwords = new List<Password>
|
|
{
|
|
alias.Password,
|
|
},
|
|
Alias = alias.Alias,
|
|
Attachments = alias.Attachments,
|
|
};
|
|
|
|
if (string.IsNullOrWhiteSpace(alias.AliasBirthDate))
|
|
{
|
|
credential.Alias.BirthDate = DateTime.MinValue;
|
|
}
|
|
else
|
|
{
|
|
credential.Alias.BirthDate = DateTime.Parse(alias.AliasBirthDate, new CultureInfo("en-US"));
|
|
}
|
|
|
|
return credential;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cancel the edit operation and navigate back to the credentials view.
|
|
/// </summary>
|
|
private void Cancel()
|
|
{
|
|
NavigationManager.NavigateTo("/credentials/" + Id);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Trigger the form submit.
|
|
/// </summary>
|
|
private async Task TriggerFormSubmit()
|
|
{
|
|
if (EditFormRef.EditContext?.Validate() == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
await SaveAlias();
|
|
}
|
|
}
|