|
|
|
@ -5,8 +5,10 @@
|
|
|
|
|
@using System.Text.Encodings.Web
|
|
|
|
|
@using Microsoft.AspNetCore.Identity
|
|
|
|
|
@using Microsoft.AspNetCore.WebUtilities
|
|
|
|
|
@using Microsoft.Extensions.Options
|
|
|
|
|
@using Wave.Data
|
|
|
|
|
|
|
|
|
|
@inject IOptions<Features> Features
|
|
|
|
|
@inject UserManager<ApplicationUser> UserManager
|
|
|
|
|
@inject IUserStore<ApplicationUser> UserStore
|
|
|
|
|
@inject SignInManager<ApplicationUser> SignInManager
|
|
|
|
@ -25,125 +27,123 @@
|
|
|
|
|
<PageTitle>@Localizer["Title"]</PageTitle>
|
|
|
|
|
|
|
|
|
|
<StatusMessage Message="@Message" />
|
|
|
|
|
<div class="flex gap-y-4 gap-x-8 flex-wrap h-full place-content-center">
|
|
|
|
|
<section class="w-80 max-w-xs">
|
|
|
|
|
<h2 class="text-2xl lg:text-4xl mb-3">@Localizer["Title"]</h2>
|
|
|
|
|
<EditForm Model="Input" asp-route-returnUrl="@ReturnUrl" method="post" OnValidSubmit="RegisterUser"
|
|
|
|
|
FormName="register" class="w-full">
|
|
|
|
|
<DataAnnotationsValidator />
|
|
|
|
|
|
|
|
|
|
<InputLabelComponent LabelText="@Localizer["Email_Label"]" For="() => Input.Email">
|
|
|
|
|
<InputText @bind-Value="Input.Email" class="input input-bordered w-full" autocomplete="username"
|
|
|
|
|
required aria-required="true" placeholder="@Localizer["Email_Placeholder"]" />
|
|
|
|
|
</InputLabelComponent>
|
|
|
|
|
<InputLabelComponent LabelText="@Localizer["Password_Label"]" For="() => Input.Password">
|
|
|
|
|
<InputText @bind-Value="Input.Password" class="input input-bordered w-full"
|
|
|
|
|
autocomplete="new-password" type="password"
|
|
|
|
|
required aria-required="true" placeholder="@Localizer["Password_Placeholder"]" />
|
|
|
|
|
</InputLabelComponent>
|
|
|
|
|
<InputLabelComponent LabelText="@Localizer["ConfirmPassword_Label"]" For="() => Input.ConfirmPassword">
|
|
|
|
|
<InputText @bind-Value="Input.ConfirmPassword" class="input input-bordered w-full"
|
|
|
|
|
autocomplete="new-password" type="password"
|
|
|
|
|
required aria-required="true" placeholder="@Localizer["ConfirmPassword_Placeholder"]"/>
|
|
|
|
|
</InputLabelComponent>
|
|
|
|
|
<BoardComponent CenterContent="true">
|
|
|
|
|
<BoardCardComponent Heading="@Localizer["Title"]">
|
|
|
|
|
@if (Features.Value.NativeSignup) {
|
|
|
|
|
<EditForm Model="Input" asp-route-returnUrl="@ReturnUrl" method="post" OnValidSubmit="RegisterUser"
|
|
|
|
|
FormName="register" class="w-full">
|
|
|
|
|
<DataAnnotationsValidator />
|
|
|
|
|
|
|
|
|
|
<button type="submit" class="btn btn-primary w-full">
|
|
|
|
|
@Localizer["Submit"]
|
|
|
|
|
</button>
|
|
|
|
|
</EditForm>
|
|
|
|
|
</section>
|
|
|
|
|
<!--
|
|
|
|
|
<div class="col-md-6 col-md-offset-2">
|
|
|
|
|
<section>
|
|
|
|
|
<h3>Use another service to register.</h3>
|
|
|
|
|
<hr />
|
|
|
|
|
<ExternalLoginPicker />
|
|
|
|
|
</section>
|
|
|
|
|
</div>
|
|
|
|
|
-->
|
|
|
|
|
</div>
|
|
|
|
|
<InputLabelComponent LabelText="@Localizer["Email_Label"]" For="() => Input.Email">
|
|
|
|
|
<InputText @bind-Value="Input.Email" class="input input-bordered w-full" autocomplete="username"
|
|
|
|
|
required aria-required="true" placeholder="@Localizer["Email_Placeholder"]" />
|
|
|
|
|
</InputLabelComponent>
|
|
|
|
|
<InputLabelComponent LabelText="@Localizer["Password_Label"]" For="() => Input.Password">
|
|
|
|
|
<InputText @bind-Value="Input.Password" class="input input-bordered w-full"
|
|
|
|
|
autocomplete="new-password" type="password"
|
|
|
|
|
required aria-required="true" placeholder="@Localizer["Password_Placeholder"]" />
|
|
|
|
|
</InputLabelComponent>
|
|
|
|
|
<InputLabelComponent LabelText="@Localizer["ConfirmPassword_Label"]" For="() => Input.ConfirmPassword">
|
|
|
|
|
<InputText @bind-Value="Input.ConfirmPassword" class="input input-bordered w-full"
|
|
|
|
|
autocomplete="new-password" type="password"
|
|
|
|
|
required aria-required="true" placeholder="@Localizer["ConfirmPassword_Placeholder"]"/>
|
|
|
|
|
</InputLabelComponent>
|
|
|
|
|
|
|
|
|
|
<button type="submit" class="btn btn-primary w-full">
|
|
|
|
|
@Localizer["Submit"]
|
|
|
|
|
</button>
|
|
|
|
|
</EditForm>
|
|
|
|
|
} else {
|
|
|
|
|
<Alert CanRemove="false" Type="Alert.MessageType.Warning">
|
|
|
|
|
<p><strong>@Localizer["SignupDisabled_Message"]</strong></p>
|
|
|
|
|
</Alert>
|
|
|
|
|
}
|
|
|
|
|
</BoardCardComponent>
|
|
|
|
|
</BoardComponent>
|
|
|
|
|
|
|
|
|
|
@code {
|
|
|
|
|
private IEnumerable<IdentityError>? _identityErrors;
|
|
|
|
|
private IEnumerable<IdentityError>? _identityErrors;
|
|
|
|
|
|
|
|
|
|
[SupplyParameterFromForm]
|
|
|
|
|
private InputModel Input { get; set; } = new();
|
|
|
|
|
[SupplyParameterFromForm]
|
|
|
|
|
private InputModel Input { get; set; } = new();
|
|
|
|
|
|
|
|
|
|
[SupplyParameterFromQuery]
|
|
|
|
|
private string? ReturnUrl { get; set; }
|
|
|
|
|
[SupplyParameterFromQuery]
|
|
|
|
|
private string? ReturnUrl { get; set; }
|
|
|
|
|
|
|
|
|
|
private string? Message =>
|
|
|
|
|
_identityErrors is null ? null :
|
|
|
|
|
$"Error: {string.Join(", ", _identityErrors.Select(error => error.Description))}";
|
|
|
|
|
private string? Message =>
|
|
|
|
|
_identityErrors is null ? null :
|
|
|
|
|
$"Error: {string.Join(", ", _identityErrors.Select(error => error.Description))}";
|
|
|
|
|
|
|
|
|
|
public async Task RegisterUser(EditContext editContext) {
|
|
|
|
|
var user = CreateUser();
|
|
|
|
|
public async Task RegisterUser(EditContext editContext) {
|
|
|
|
|
if (Features.Value.NativeSignup is false) return;
|
|
|
|
|
var user = CreateUser();
|
|
|
|
|
|
|
|
|
|
await UserStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
|
|
|
|
|
var emailStore = GetEmailStore();
|
|
|
|
|
await emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);
|
|
|
|
|
var result = await UserManager.CreateAsync(user, Input.Password);
|
|
|
|
|
await UserStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
|
|
|
|
|
var emailStore = GetEmailStore();
|
|
|
|
|
await emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);
|
|
|
|
|
var result = await UserManager.CreateAsync(user, Input.Password);
|
|
|
|
|
|
|
|
|
|
if (!result.Succeeded) {
|
|
|
|
|
_identityErrors = result.Errors;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!result.Succeeded) {
|
|
|
|
|
_identityErrors = result.Errors;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Logger.LogInformation("User created a new account with password.");
|
|
|
|
|
Logger.LogInformation("User created a new account with password.");
|
|
|
|
|
|
|
|
|
|
var userId = await UserManager.GetUserIdAsync(user);
|
|
|
|
|
var code = await UserManager.GenerateEmailConfirmationTokenAsync(user);
|
|
|
|
|
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
|
|
|
|
|
var callbackUrl = NavigationManager.GetUriWithQueryParameters(
|
|
|
|
|
NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri,
|
|
|
|
|
new Dictionary<string, object?> { ["userId"] = userId, ["code"] = code, ["returnUrl"] = ReturnUrl });
|
|
|
|
|
var userId = await UserManager.GetUserIdAsync(user);
|
|
|
|
|
var code = await UserManager.GenerateEmailConfirmationTokenAsync(user);
|
|
|
|
|
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
|
|
|
|
|
var callbackUrl = NavigationManager.GetUriWithQueryParameters(
|
|
|
|
|
NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri,
|
|
|
|
|
new Dictionary<string, object?> { ["userId"] = userId, ["code"] = code, ["returnUrl"] = ReturnUrl });
|
|
|
|
|
|
|
|
|
|
await EmailSender.SendConfirmationLinkAsync(user, Input.Email, HtmlEncoder.Default.Encode(callbackUrl));
|
|
|
|
|
await EmailSender.SendConfirmationLinkAsync(user, Input.Email, HtmlEncoder.Default.Encode(callbackUrl));
|
|
|
|
|
|
|
|
|
|
if (UserManager.Options.SignIn.RequireConfirmedAccount) {
|
|
|
|
|
RedirectManager.RedirectTo(
|
|
|
|
|
"Account/RegisterConfirmation",
|
|
|
|
|
new() { ["email"] = Input.Email, ["returnUrl"] = ReturnUrl });
|
|
|
|
|
}
|
|
|
|
|
if (UserManager.Options.SignIn.RequireConfirmedAccount) {
|
|
|
|
|
RedirectManager.RedirectTo(
|
|
|
|
|
"Account/RegisterConfirmation",
|
|
|
|
|
new() { ["email"] = Input.Email, ["returnUrl"] = ReturnUrl });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await SignInManager.SignInAsync(user, isPersistent: false);
|
|
|
|
|
RedirectManager.RedirectTo(ReturnUrl);
|
|
|
|
|
}
|
|
|
|
|
await SignInManager.SignInAsync(user, isPersistent: false);
|
|
|
|
|
RedirectManager.RedirectTo(ReturnUrl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private ApplicationUser CreateUser() {
|
|
|
|
|
try {
|
|
|
|
|
return Activator.CreateInstance<ApplicationUser>();
|
|
|
|
|
} catch {
|
|
|
|
|
throw new InvalidOperationException(
|
|
|
|
|
$"Can't create an instance of '{nameof(ApplicationUser)}'. " +
|
|
|
|
|
$"Ensure that '{nameof(ApplicationUser)}' is not an abstract class and has a parameterless constructor.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
private ApplicationUser CreateUser() {
|
|
|
|
|
try {
|
|
|
|
|
return Activator.CreateInstance<ApplicationUser>();
|
|
|
|
|
} catch {
|
|
|
|
|
throw new InvalidOperationException(
|
|
|
|
|
$"Can't create an instance of '{nameof(ApplicationUser)}'. " +
|
|
|
|
|
$"Ensure that '{nameof(ApplicationUser)}' is not an abstract class and has a parameterless constructor.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private IUserEmailStore<ApplicationUser> GetEmailStore() {
|
|
|
|
|
if (!UserManager.SupportsUserEmail) {
|
|
|
|
|
throw new NotSupportedException("The default UI requires a user store with email support.");
|
|
|
|
|
}
|
|
|
|
|
private IUserEmailStore<ApplicationUser> GetEmailStore() {
|
|
|
|
|
if (!UserManager.SupportsUserEmail) {
|
|
|
|
|
throw new NotSupportedException("The default UI requires a user store with email support.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (IUserEmailStore<ApplicationUser>)UserStore;
|
|
|
|
|
}
|
|
|
|
|
return (IUserEmailStore<ApplicationUser>)UserStore;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private sealed class InputModel {
|
|
|
|
|
[Required]
|
|
|
|
|
[EmailAddress]
|
|
|
|
|
[Display(Name = "Email")]
|
|
|
|
|
public string Email { get; set; } = "";
|
|
|
|
|
private sealed class InputModel {
|
|
|
|
|
[Required]
|
|
|
|
|
[EmailAddress]
|
|
|
|
|
[Display(Name = "Email")]
|
|
|
|
|
public string Email { get; set; } = "";
|
|
|
|
|
|
|
|
|
|
[Required]
|
|
|
|
|
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
|
|
|
|
|
[DataType(DataType.Password)]
|
|
|
|
|
[Display(Name = "Password")]
|
|
|
|
|
public string Password { get; set; } = "";
|
|
|
|
|
[Required]
|
|
|
|
|
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
|
|
|
|
|
[DataType(DataType.Password)]
|
|
|
|
|
[Display(Name = "Password")]
|
|
|
|
|
public string Password { get; set; } = "";
|
|
|
|
|
|
|
|
|
|
[DataType(DataType.Password)]
|
|
|
|
|
[Display(Name = "Confirm password")]
|
|
|
|
|
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
|
|
|
|
|
public string ConfirmPassword { get; set; } = "";
|
|
|
|
|
}
|
|
|
|
|
[DataType(DataType.Password)]
|
|
|
|
|
[Display(Name = "Confirm password")]
|
|
|
|
|
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
|
|
|
|
|
public string ConfirmPassword { get; set; } = "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|