mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-04-30 19:36:19 -04:00
Add form validation to login/register pages (#23)
This commit is contained in:
@@ -5,6 +5,8 @@
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
using AliasVault.Shared.Models.WebApi;
|
||||
|
||||
namespace AliasVault.Api.Controllers;
|
||||
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
@@ -43,7 +45,7 @@ public class AuthController(AliasDbContext context, UserManager<IdentityUser> us
|
||||
return Ok(tokenModel);
|
||||
}
|
||||
|
||||
return Unauthorized();
|
||||
return BadRequest(ServerValidationErrorResponse.Create("Invalid username or password. Please try again.", 400));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -151,10 +153,9 @@ public class AuthController(AliasDbContext context, UserManager<IdentityUser> us
|
||||
var tokenModel = await GenerateNewTokenForUser(user);
|
||||
return Ok(tokenModel);
|
||||
}
|
||||
else
|
||||
{
|
||||
return BadRequest(result.Errors);
|
||||
}
|
||||
|
||||
var errors = result.Errors.Select(e => e.Description).ToArray();
|
||||
return BadRequest(ServerValidationErrorResponse.Create(errors, 400));
|
||||
}
|
||||
|
||||
private string GenerateJwtToken(IdentityUser user)
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace AliasVault.Shared.Models;
|
||||
|
||||
/// <summary>
|
||||
@@ -15,10 +17,13 @@ public class LoginModel
|
||||
/// <summary>
|
||||
/// Gets or sets the email.
|
||||
/// </summary>
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the password.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string Password { get; set; } = null!;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using AliasVault.Shared.Models.Validation;
|
||||
|
||||
namespace AliasVault.Shared.Models;
|
||||
|
||||
/// <summary>
|
||||
@@ -15,20 +18,27 @@ public class RegisterModel
|
||||
/// <summary>
|
||||
/// Gets or sets the email.
|
||||
/// </summary>
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the password.
|
||||
/// </summary>
|
||||
[Required]
|
||||
[MinLength(8, ErrorMessage = "Password must be at least 8 characters long.")]
|
||||
public string Password { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the password confirmation.
|
||||
/// </summary>
|
||||
[Required]
|
||||
[Compare("Password", ErrorMessage = "Passwords do not match.")]
|
||||
public string PasswordConfirm { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the terms and conditions are accepted or not.
|
||||
/// </summary>
|
||||
[MustBeTrue(ErrorMessage = "You must accept the terms and conditions.")]
|
||||
public bool AcceptTerms { get; set; } = false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="MustBeTrueAttribute.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.Shared.Models.Validation;
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
/// <summary>
|
||||
/// Validation attribute to ensure that a boolean property is true.
|
||||
/// </summary>
|
||||
public class MustBeTrueAttribute : ValidationAttribute
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool IsValid(object? value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case null:
|
||||
return false;
|
||||
case bool b:
|
||||
return b;
|
||||
default:
|
||||
throw new InvalidOperationException("Can only be used on boolean properties.");
|
||||
}
|
||||
}
|
||||
}
|
||||
47
src/AliasVault.Shared/Models/WebApi/AliasEdit.cs
Normal file
47
src/AliasVault.Shared/Models/WebApi/AliasEdit.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="AliasEdit.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.Shared.Models.WebApi;
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
/// <summary>
|
||||
/// Alias model.
|
||||
/// </summary>
|
||||
public class AliasEdit
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the service.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string ServiceName { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the URL of the service.
|
||||
/// </summary>
|
||||
public string? ServiceUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Alias Identity object.
|
||||
/// </summary>
|
||||
public Identity Identity { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Alias Password object.
|
||||
/// </summary>
|
||||
public Password Password { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Alias CreateDate.
|
||||
/// </summary>
|
||||
public DateTime CreateDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Alias LastUpdate.
|
||||
/// </summary>
|
||||
public DateTime LastUpdate { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="ServerValidationErrorResponse.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.Shared.Models.WebApi;
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the structure of a validation error response from the API.
|
||||
/// </summary>
|
||||
public class ServerValidationErrorResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the error.
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the title of the error.
|
||||
/// </summary>
|
||||
[JsonPropertyName("title")]
|
||||
public string Title { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the HTTP status code of the response.
|
||||
/// </summary>
|
||||
[JsonPropertyName("status")]
|
||||
public int Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the validation errors. The key is the name of the field that has the error, and the value is an array of error messages for that field.
|
||||
/// </summary>
|
||||
[JsonPropertyName("errors")]
|
||||
public Dictionary<string, string[]> Errors { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the trace ID of the error.
|
||||
/// </summary>
|
||||
[JsonPropertyName("traceId")]
|
||||
public string TraceId { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="ServerValidationErrorResponse"/>.
|
||||
/// </summary>
|
||||
/// <param name="title">Title of the error.</param>
|
||||
/// <param name="status">Status code.</param>
|
||||
/// <returns>ServerValidationErrorResponse object.</returns>
|
||||
public static ServerValidationErrorResponse Create(string title, int status)
|
||||
{
|
||||
var errors = new Dictionary<string, string[]>
|
||||
{
|
||||
{ title, [title] },
|
||||
};
|
||||
|
||||
return new ServerValidationErrorResponse
|
||||
{
|
||||
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1",
|
||||
Title = title,
|
||||
Errors = errors,
|
||||
Status = status,
|
||||
TraceId = Guid.NewGuid().ToString(),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="ServerValidationErrorResponse"/>.
|
||||
/// </summary>
|
||||
/// <param name="errorArray">Array with errors.</param>
|
||||
/// <param name="status">Status code.</param>
|
||||
/// <returns>ServerValidationErrorResponse object.</returns>
|
||||
public static ServerValidationErrorResponse Create(string[] errorArray, int status)
|
||||
{
|
||||
var errors = new Dictionary<string, string[]>();
|
||||
foreach (var t in errorArray)
|
||||
{
|
||||
errors.Add(t, new[] { t });
|
||||
}
|
||||
|
||||
return new ServerValidationErrorResponse
|
||||
{
|
||||
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1",
|
||||
Title = errorArray.First(),
|
||||
Errors = errors,
|
||||
Status = status,
|
||||
TraceId = Guid.NewGuid().ToString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
namespace AliasVault.Shared.Models.WebApi;
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
/// <summary>
|
||||
/// Service model.
|
||||
/// </summary>
|
||||
@@ -15,6 +17,7 @@ public class Service
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the service.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
@inject AuthService AuthService
|
||||
@using System.Text.Json
|
||||
@using AliasVault.Shared.Models
|
||||
@using AliasVault.Shared.Models.WebApi
|
||||
@using AliasVault.WebApp.Auth.Components
|
||||
@using AliasVault.WebApp.Auth.Services
|
||||
|
||||
@@ -15,14 +16,20 @@
|
||||
Sign in to AliasVault
|
||||
</h2>
|
||||
|
||||
<EditForm Model="_user" OnSubmit="HandleLogin" class="mt-8 space-y-6">
|
||||
<FullScreenLoadingIndicator @ref="_loadingIndicator" />
|
||||
<ServerValidationErrors @ref="_serverValidationErrors" />
|
||||
|
||||
<EditForm Model="_loginModel" OnValidSubmit="HandleLogin" class="mt-8 space-y-6">
|
||||
<DataAnnotationsValidator/>
|
||||
<div>
|
||||
<label asp-for="Input.Email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your email</label>
|
||||
<InputTextField id="email" @bind-Value="_user.Email" placeholder="name@company.com" />
|
||||
<InputTextField id="email" @bind-Value="_loginModel.Email" placeholder="name@company.com" />
|
||||
<ValidationMessage For="() => _loginModel.Email"/>
|
||||
</div>
|
||||
<div>
|
||||
<label asp-for="Input.Password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your password</label>
|
||||
<InputTextField id="password" @bind-Value="_user.Password" type="password" placeholder="••••••••" />
|
||||
<InputTextField id="password" @bind-Value="_loginModel.Password" type="password" placeholder="••••••••" />
|
||||
<ValidationMessage For="() => _loginModel.Password"/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-start">
|
||||
@@ -41,12 +48,10 @@
|
||||
</div>
|
||||
</EditForm>
|
||||
|
||||
|
||||
<FullScreenLoadingIndicator @ref="_loadingIndicator" />
|
||||
|
||||
@code {
|
||||
private LoginModel _user = new LoginModel();
|
||||
private readonly LoginModel _loginModel = new();
|
||||
private FullScreenLoadingIndicator _loadingIndicator = new();
|
||||
private ServerValidationErrors _serverValidationErrors = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnInitializedAsync()
|
||||
@@ -62,11 +67,20 @@
|
||||
private async Task HandleLogin()
|
||||
{
|
||||
_loadingIndicator.Show();
|
||||
_serverValidationErrors.Clear();
|
||||
|
||||
try
|
||||
{
|
||||
var result = await Http.PostAsJsonAsync("api/Auth/login", _user);
|
||||
var result = await Http.PostAsJsonAsync("api/Auth/login", _loginModel);
|
||||
var responseContent = await result.Content.ReadAsStringAsync();
|
||||
|
||||
if (!result.IsSuccessStatusCode)
|
||||
{
|
||||
_serverValidationErrors.ParseResponse(responseContent);
|
||||
StateHasChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
var tokenObject = JsonSerializer.Deserialize<TokenModel>(responseContent);
|
||||
|
||||
if (tokenObject != null)
|
||||
@@ -80,6 +94,7 @@
|
||||
{
|
||||
// Handle the case where the token is not present in the response
|
||||
Console.WriteLine("Token not found in the response.");
|
||||
return;
|
||||
}
|
||||
|
||||
await AuthStateProvider.GetAuthenticationStateAsync();
|
||||
|
||||
@@ -14,26 +14,34 @@
|
||||
Create a Free Account
|
||||
</h2>
|
||||
|
||||
<EditForm Model="user" OnSubmit="HandleRegister" class="mt-8 space-y-6">
|
||||
<FullScreenLoadingIndicator @ref="_loadingIndicator" />
|
||||
<ServerValidationErrors @ref="_serverValidationErrors" />
|
||||
|
||||
<EditForm Model="_registerModel" OnValidSubmit="HandleRegister" class="mt-8 space-y-6">
|
||||
<DataAnnotationsValidator/>
|
||||
<div>
|
||||
<label asp-for="Input.Email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your email</label>
|
||||
<InputTextField id="email" @bind-Value="user.Email" placeholder="name@company.com" />
|
||||
<InputTextField id="email" @bind-Value="_registerModel.Email" placeholder="name@company.com" />
|
||||
<ValidationMessage For="() => _registerModel.Email"/>
|
||||
</div>
|
||||
<div>
|
||||
<label asp-for="Input.Password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your password</label>
|
||||
<InputTextField id="password" @bind-Value="user.Password" type="password" placeholder="••••••••" />
|
||||
<InputTextField id="password" @bind-Value="_registerModel.Password" type="password" placeholder="••••••••" />
|
||||
<ValidationMessage For="() => _registerModel.Password"/>
|
||||
</div>
|
||||
<div>
|
||||
<label asp-for="Input.Password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Confirm password</label>
|
||||
<InputTextField id="password2" @bind-Value="user.PasswordConfirm" type="password" placeholder="••••••••" />
|
||||
<InputTextField id="password2" @bind-Value="_registerModel.PasswordConfirm" type="password" placeholder="••••••••" />
|
||||
<ValidationMessage For="() => _registerModel.PasswordConfirm"/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-start">
|
||||
<div class="flex items-center h-5">
|
||||
<input id="terms" aria-describedby="terms" name="terms" type="checkbox" class="w-4 h-4 border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-primary-300 dark:focus:ring-primary-600 dark:ring-offset-gray-800 dark:bg-gray-700 dark:border-gray-600" required>
|
||||
<InputCheckbox id="terms" @bind-Value="_registerModel.AcceptTerms" class="w-4 h-4 border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-primary-300 dark:focus:ring-primary-600 dark:ring-offset-gray-800 dark:bg-gray-700 dark:border-gray-600" />
|
||||
</div>
|
||||
<div class="ml-3 text-sm">
|
||||
<label for="terms" class="font-medium text-gray-900 dark:text-white">I accept the <a href="#" class="text-primary-700 hover:underline dark:text-primary-500">Terms and Conditions</a></label>
|
||||
<ValidationMessage For="() => _registerModel.AcceptTerms"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -43,79 +51,48 @@
|
||||
</div>
|
||||
</EditForm>
|
||||
|
||||
@if (validationErrors.Any())
|
||||
{
|
||||
<div class="alert alert-danger">
|
||||
<ul>
|
||||
@foreach (var error in validationErrors)
|
||||
{
|
||||
<li>@error</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
|
||||
<FullScreenLoadingIndicator @ref="loadingIndicator" />
|
||||
|
||||
@code {
|
||||
RegisterModel user = new();
|
||||
FullScreenLoadingIndicator loadingIndicator = new();
|
||||
List<string> validationErrors = [];
|
||||
private readonly RegisterModel _registerModel = new();
|
||||
private FullScreenLoadingIndicator _loadingIndicator = new();
|
||||
private ServerValidationErrors _serverValidationErrors = new();
|
||||
|
||||
async Task HandleRegister()
|
||||
{
|
||||
loadingIndicator.Show();
|
||||
validationErrors.Clear();
|
||||
_loadingIndicator.Show();
|
||||
_serverValidationErrors.Clear();
|
||||
|
||||
try
|
||||
{
|
||||
var result = await Http.PostAsJsonAsync("api/Auth/register", user);
|
||||
var result = await Http.PostAsJsonAsync("api/Auth/register", _registerModel);
|
||||
var responseContent = await result.Content.ReadAsStringAsync();
|
||||
|
||||
if (result.IsSuccessStatusCode)
|
||||
if (!result.IsSuccessStatusCode)
|
||||
{
|
||||
var responseContent = await result.Content.ReadAsStringAsync();
|
||||
var tokenObject = JsonSerializer.Deserialize<TokenModel>(responseContent);
|
||||
_serverValidationErrors.ParseResponse(responseContent);
|
||||
StateHasChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
if (tokenObject != null)
|
||||
{
|
||||
// Store the token as a plain string in local storage
|
||||
await AuthService.StoreAccessTokenAsync(tokenObject.Token);
|
||||
await AuthService.StoreRefreshTokenAsync(tokenObject.RefreshToken);
|
||||
await AuthStateProvider.GetAuthenticationStateAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Handle the case where the token is not present in the response
|
||||
Console.WriteLine("Token not found in the response.");
|
||||
}
|
||||
var tokenObject = JsonSerializer.Deserialize<TokenModel>(responseContent);
|
||||
|
||||
NavigationManager.NavigateTo("/");
|
||||
if (tokenObject != null)
|
||||
{
|
||||
// Store the token as a plain string in local storage
|
||||
await AuthService.StoreAccessTokenAsync(tokenObject.Token);
|
||||
await AuthService.StoreRefreshTokenAsync(tokenObject.RefreshToken);
|
||||
await AuthStateProvider.GetAuthenticationStateAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
var responseContent = await result.Content.ReadAsStringAsync();
|
||||
var errorResponse = System.Text.Json.JsonSerializer.Deserialize<ValidationErrorResponse>(responseContent);
|
||||
if (errorResponse != null && errorResponse.Errors != null)
|
||||
{
|
||||
foreach (var error in errorResponse.Errors.Values)
|
||||
{
|
||||
validationErrors.AddRange(error);
|
||||
}
|
||||
}
|
||||
// Handle the case where the token is not present in the response
|
||||
Console.WriteLine("Token not found in the response.");
|
||||
}
|
||||
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
finally
|
||||
{
|
||||
loadingIndicator.Hide();
|
||||
_loadingIndicator.Hide();
|
||||
}
|
||||
}
|
||||
|
||||
public class ValidationErrorResponse
|
||||
{
|
||||
public string Type { get; set; } = null!;
|
||||
public string Title { get; set; } = null!;
|
||||
public int Status { get; set; }
|
||||
public Dictionary<string, string[]> Errors { get; set; } = new();
|
||||
public string TraceId { get; set; } = null!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
</div>
|
||||
|
||||
@code {
|
||||
/// <summary>
|
||||
/// The message to show.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string Message { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
</div>
|
||||
|
||||
@code {
|
||||
/// <summary>
|
||||
/// The message to show.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string Message { get; set; } = string.Empty;
|
||||
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
/// <summary>
|
||||
/// Refreshes the messages by adding any new messages from the PortalMessageService.
|
||||
/// </summary>
|
||||
public void RefreshAddMessages()
|
||||
private void RefreshAddMessages()
|
||||
{
|
||||
// We retrieve any additional messages from the GlobalNotificationService that we do not yet have.
|
||||
var newMessages = GlobalNotificationService.GetMessagesForDisplay();
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
@using AliasVault.Shared.Models.WebApi
|
||||
|
||||
@if (_errors.Any())
|
||||
{
|
||||
@foreach (var error in _errors)
|
||||
{
|
||||
<AlertMessageError Message="@error" />
|
||||
}
|
||||
}
|
||||
|
||||
@code {
|
||||
private bool IsVisible { get; set; }
|
||||
private List<string> _errors = [];
|
||||
|
||||
/// <summary>
|
||||
/// Parses the response content and displays the server validation errors.
|
||||
/// </summary>
|
||||
public void ParseResponse(string responseContent)
|
||||
{
|
||||
_errors.Clear();
|
||||
var errorResponse = System.Text.Json.JsonSerializer.Deserialize<ServerValidationErrorResponse>(responseContent);
|
||||
if (errorResponse is not null)
|
||||
{
|
||||
foreach (var error in errorResponse.Errors)
|
||||
{
|
||||
_errors.AddRange(error.Value);
|
||||
}
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the server validation errors.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
_errors.Clear();
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@
|
||||
{
|
||||
<div class="z-50 my-4 w-56 text-base list-none bg-white rounded divide-y divide-gray-100 shadow dark:bg-gray-700 dark:divide-gray-600" id="userMenuDropdown" data-popper-placement="bottom" style="position: absolute; inset: 0px auto auto 0px; margin: 0px; margin-left: -200px; transform: translate3d(1537.5px, 58px, 0px);">
|
||||
<div class="py-3 px-4">
|
||||
<span class="block text-sm font-semibold text-gray-900 dark:text-white">@Username</span>
|
||||
<span class="block text-sm font-semibold text-gray-900 dark:text-white">@_username</span>
|
||||
</div>
|
||||
<ul class="py-1 font-light text-gray-500 dark:text-gray-400" aria-labelledby="userMenuDropdownButton">
|
||||
<li>
|
||||
@@ -101,14 +101,16 @@
|
||||
|
||||
@code {
|
||||
private bool isMenuOpen = false;
|
||||
public string Username { get; set; } = "";
|
||||
private string _username { get; set; } = "";
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
Username = await GetUsernameAsync();
|
||||
_username = await GetUsernameAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
|
||||
@@ -43,103 +43,107 @@ else {
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="grid px-4 pt-6 lg:gap-4 dark:bg-gray-900">
|
||||
<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">Service</h3>
|
||||
<div class="grid gap-6">
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="service-name" Label="Service Name" Value="@(Obj.Service.Name)" ValueChanged="@(val => Obj.Service.Name = val)"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="service-url" Label="Service URL" Value="@(Obj.Service.Url)" ValueChanged="@(val => Obj.Service.Url = val)"></EditFormRow>
|
||||
<EditForm Model="Obj" OnValidSubmit="SaveAlias">
|
||||
<DataAnnotationsValidator />
|
||||
<div class="grid px-4 pt-6 lg:gap-4 dark:bg-gray-900">
|
||||
<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">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" Label="Service URL" @bind-Value="Obj.ServiceUrl"></EditFormRow>
|
||||
</div>
|
||||
</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">Login credentials</h3>
|
||||
<div class="mb-4">
|
||||
<button class="px-4 py-2 text-white bg-blue-600 rounded-lg hover:bg-blue-700 focus:ring-4 focus:ring-blue-300 dark:bg-blue-500 dark:hover:bg-blue-600 dark:focus:ring-blue-800" @onclick="GenerateRandomIdentity">Generate Random Identity</button>
|
||||
<button class="px-4 py-2 text-white bg-green-600 rounded-lg hover:bg-green-700 focus:ring-4 focus:ring-green-300 dark:bg-green-500 dark:hover:bg-green-600 dark:focus:ring-green-800" @onclick="SaveAlias">Save Alias</button>
|
||||
@if (IsIdentityLoading)
|
||||
{
|
||||
<p>Loading...</p>
|
||||
}
|
||||
</div>
|
||||
<div class="grid gap-6">
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="email" Label="Email" Value="@(Obj.Identity.EmailPrefix)" ValueChanged="@(val => Obj.Identity.EmailPrefix = val)"></EditFormRow>
|
||||
<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">Login credentials</h3>
|
||||
<div class="mb-4">
|
||||
<button class="px-4 py-2 text-white bg-blue-600 rounded-lg hover:bg-blue-700 focus:ring-4 focus:ring-blue-300 dark:bg-blue-500 dark:hover:bg-blue-600 dark:focus:ring-blue-800" @onclick="GenerateRandomIdentity">Generate Random Identity</button>
|
||||
<button type="submit" class="px-4 py-2 text-white bg-green-600 rounded-lg hover:bg-green-700 focus:ring-4 focus:ring-green-300 dark:bg-green-500 dark:hover:bg-green-600 dark:focus:ring-green-800">Save Alias</button>
|
||||
@if (IsIdentityLoading)
|
||||
{
|
||||
<p>Loading...</p>
|
||||
}
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="username" Label="Username" Value="@(Obj.Identity.NickName)" ValueChanged="@(val => Obj.Identity.NickName = val)"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<div class="relative">
|
||||
<EditFormRow Id="password" Label="Password" Value="@(Obj.Password.Value)" ValueChanged="@(val => Obj.Password.Value = val)"></EditFormRow>
|
||||
<button type="submit" 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">(Re)generate Random Password</button>
|
||||
<div class="grid gap-6">
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="email" Label="Email" @bind-Value="Obj.Identity.EmailPrefix"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="username" Label="Username" @bind-Value="Obj.Identity.NickName"></EditFormRow>
|
||||
</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="submit" 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">(Re)generate Random Password</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid px-4 pt-6 lg:gap-4 dark:bg-gray-900">
|
||||
<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">Identity</h3>
|
||||
<div class="grid gap-6">
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="first-name" Label="First Name" Value="@(Obj.Identity.FirstName)" ValueChanged="@(val => Obj.Identity.FirstName = val)"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="last-name" Label="Last Name" Value="@(Obj.Identity.LastName)" ValueChanged="@(val => Obj.Identity.LastName = val)"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="gender" Label="Gender" Value="@(Obj.Identity.Gender)" ValueChanged="@(val => Obj.Identity.Gender = val)"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="nickname" Label="Nick Name" Value="@(Obj.Identity.NickName)" ValueChanged="@(val => Obj.Identity.NickName = val)"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="birthdate" Label="Birth Date" Value="@(Obj.Identity.BirthDate)" ValueChanged="@(val => Obj.Identity.BirthDate = val)"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="street" Label="Address Street" Value="@(Obj.Identity.AddressStreet)" ValueChanged="@(val => Obj.Identity.AddressStreet = val)"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="city" Label="Address City" Value="@(Obj.Identity.AddressCity)" ValueChanged="@(val => Obj.Identity.AddressCity = val)"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="state" Label="Address State" Value="@(Obj.Identity.AddressState)" ValueChanged="@(val => Obj.Identity.AddressState = val)"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="zipcode" Label="Address Zip Code" Value="@(Obj.Identity.AddressZipCode)" ValueChanged="@(val => Obj.Identity.AddressZipCode = val)"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="country" Label="Address Country" Value="@(Obj.Identity.AddressCountry)" ValueChanged="@(val => Obj.Identity.AddressCountry = val)"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="hobbies" Label="Hobbies" Value="@(Obj.Identity.Hobbies)" ValueChanged="@(val => Obj.Identity.Hobbies = val)"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="phone-mobile" Label="Phone Mobile" Value="@(Obj.Identity.PhoneMobile)" ValueChanged="@(val => Obj.Identity.PhoneMobile = val)"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="iban" Label="Bank Account IBAN" Value="@(Obj.Identity.BankAccountIBAN)" ValueChanged="@(val => Obj.Identity.BankAccountIBAN = val)"></EditFormRow>
|
||||
<div class="grid px-4 pt-6 lg:gap-4 dark:bg-gray-900">
|
||||
<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">Identity</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.Identity.FirstName"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="last-name" Label="Last Name" @bind-Value="Obj.Identity.LastName"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="gender" Label="Gender" @bind-Value="Obj.Identity.Gender"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="nickname" Label="Nick Name" @bind-Value="Obj.Identity.NickName"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="birthdate" Label="Birth Date" @bind-Value="Obj.Identity.BirthDate"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="street" Label="Address Street" @bind-Value="Obj.Identity.AddressStreet"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="city" Label="Address City" @bind-Value="Obj.Identity.AddressCity"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="state" Label="Address State" @bind-Value="Obj.Identity.AddressState"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="zipcode" Label="Address Zip Code" @bind-Value="Obj.Identity.AddressZipCode"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="country" Label="Address Country" @bind-Value="Obj.Identity.AddressCountry"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="hobbies" Label="Hobbies" @bind-Value="Obj.Identity.Hobbies"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="phone-mobile" Label="Phone Mobile" @bind-Value="Obj.Identity.PhoneMobile"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="iban" Label="Bank Account IBAN" @bind-Value="Obj.Identity.BankAccountIBAN"></EditFormRow>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="px-4 py-2 text-white bg-green-600 rounded-lg hover:bg-green-700 focus:ring-4 focus:ring-green-300 dark:bg-green-500 dark:hover:bg-green-600 dark:focus:ring-green-800">Save Alias</button>
|
||||
</div>
|
||||
</div>
|
||||
<button class="px-4 py-2 text-white bg-green-600 rounded-lg hover:bg-green-700 focus:ring-4 focus:ring-green-300 dark:bg-green-500 dark:hover:bg-green-600 dark:focus:ring-green-800" @onclick="SaveAlias">Save Alias</button>
|
||||
</div>
|
||||
</EditForm>
|
||||
|
||||
@if (IsSaving)
|
||||
{
|
||||
<p>Saving...</p>
|
||||
}
|
||||
@if (IsSaving)
|
||||
{
|
||||
<p>Saving...</p>
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -152,7 +156,7 @@ else
|
||||
|
||||
private bool EditMode { get; set; }
|
||||
private bool Loading { get; set; } = true;
|
||||
private Alias Obj { get; set; } = new();
|
||||
private AliasEdit Obj { get; set; } = new();
|
||||
private bool IsIdentityLoading { get; set; }
|
||||
private bool IsSaving { get; set; }
|
||||
|
||||
@@ -211,15 +215,17 @@ else
|
||||
return;
|
||||
}
|
||||
|
||||
Obj = alias;
|
||||
Obj = AliasToAliasEdit(alias);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create new Obj
|
||||
Obj = new Alias();
|
||||
Obj.Identity = new Shared.Models.WebApi.Identity();
|
||||
Obj.Service = new Shared.Models.WebApi.Service();
|
||||
Obj.Password = new Shared.Models.WebApi.Password();
|
||||
var alias = new Alias();
|
||||
alias.Identity = new Shared.Models.WebApi.Identity();
|
||||
alias.Service = new Shared.Models.WebApi.Service();
|
||||
alias.Password = new Shared.Models.WebApi.Password();
|
||||
|
||||
Obj = AliasToAliasEdit(alias);
|
||||
}
|
||||
|
||||
// Hide loading spinner
|
||||
@@ -297,12 +303,12 @@ else
|
||||
{
|
||||
if (Id is not null)
|
||||
{
|
||||
Id = await AliasService.UpdateAliasAsync(Obj, Id.Value);
|
||||
Id = await AliasService.UpdateAliasAsync(AliasEditToAlias(Obj), Id.Value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Id = await AliasService.InsertAliasAsync(Obj);
|
||||
Id = await AliasService.InsertAliasAsync(AliasEditToAlias(Obj));
|
||||
}
|
||||
|
||||
IsSaving = false;
|
||||
@@ -327,4 +333,33 @@ else
|
||||
|
||||
Navigation.NavigateTo("/alias/" + Id);
|
||||
}
|
||||
|
||||
private AliasEdit AliasToAliasEdit(Alias alias)
|
||||
{
|
||||
return new AliasEdit
|
||||
{
|
||||
ServiceName = alias.Service.Name,
|
||||
ServiceUrl = alias.Service.Url,
|
||||
Password = alias.Password,
|
||||
Identity = alias.Identity,
|
||||
CreateDate = alias.CreateDate,
|
||||
LastUpdate = alias.LastUpdate
|
||||
};
|
||||
}
|
||||
|
||||
private Alias AliasEditToAlias(AliasEdit alias)
|
||||
{
|
||||
return new Alias
|
||||
{
|
||||
Service = new Service
|
||||
{
|
||||
Name = alias.ServiceName,
|
||||
Url = alias.ServiceUrl
|
||||
},
|
||||
Password = alias.Password,
|
||||
Identity = alias.Identity,
|
||||
CreateDate = alias.CreateDate,
|
||||
LastUpdate = alias.LastUpdate
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
private bool IsLoading { get; set; } = true;
|
||||
private List<Shared.Models.WebApi.AliasListEntry> Aliases { get; set; } = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
|
||||
Reference in New Issue
Block a user