Implemented disabling signup using Features.NativeSignup configuration key
Some checks failed
Build, Tag, Push Docker Image / build (push) Has been cancelled
Create Release / Generate Release (push) Has been cancelled

This commit is contained in:
Mia Rose Winter 2024-03-17 14:11:13 +01:00
parent cc5a61fc09
commit d2d66b6017
Signed by: miawinter
GPG key ID: 4B6F6A83178F595E
7 changed files with 121 additions and 106 deletions

View file

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

View file

@ -1,5 +1,5 @@
@using System.Globalization @using System.Globalization
<div class="alert @GetClass shadow" role="alert"> <div class="alert @GetClass shadow hyphens-auto" lang="@CultureInfo.CurrentCulture" role="alert">
<div> <div>
@* ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault *@ @* ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault *@
@switch (Type) { @switch (Type) {

View file

@ -1,7 +1,10 @@
@using System.Security.Claims @using System.Security.Claims
@using Microsoft.Extensions.Options
@using Wave.Data
@implements IDisposable @implements IDisposable
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IOptions<Features> Features
@inject IStringLocalizer<NavMenu> Localizer @inject IStringLocalizer<NavMenu> Localizer
<div class="h-12 hidden md:block" role="banner" aria-label="logo"> <div class="h-12 hidden md:block" role="banner" aria-label="logo">
@ -79,11 +82,13 @@
</svg> </svg>
</NavLink> </NavLink>
</li> </li>
<li> @if (Features.Value.NativeSignup) {
<NavLink href="Account/Register"> <li>
@Localizer["SignUp_Label"] <NavLink href="Account/Register">
</NavLink> @Localizer["SignUp_Label"]
</li> </NavLink>
</li>
}
</NotAuthorized> </NotAuthorized>
</AuthorizeView> </AuthorizeView>
</ul> </ul>

View file

@ -63,7 +63,7 @@ public class SitemapController(ApplicationDbContext context, IOptions<Features>
} }
root.Add(CreateUrlElement(nameSpace, new Uri(host, "/Account/Login"))); root.Add(CreateUrlElement(nameSpace, new Uri(host, "/Account/Login")));
root.Add(CreateUrlElement(nameSpace, new Uri(host, "/Account/Register"))); if (Features.NativeSignup) root.Add(CreateUrlElement(nameSpace, new Uri(host, "/Account/Register")));
if (Features.EmailSubscriptions) root.Add(CreateUrlElement(nameSpace, new Uri(host, "/Email/Subscribe"))); if (Features.EmailSubscriptions) root.Add(CreateUrlElement(nameSpace, new Uri(host, "/Email/Subscribe")));
document.Add(root); document.Add(root);

View file

@ -3,4 +3,5 @@
public class Features { public class Features {
public bool Rss { get; set; } = true; public bool Rss { get; set; } = true;
public bool EmailSubscriptions { get; set; } = false; public bool EmailSubscriptions { get; set; } = false;
public bool NativeSignup { get; set; } = true;
} }

View file

@ -119,4 +119,10 @@
<data name="ConfirmPassword_Placeholder" xml:space="preserve"> <data name="ConfirmPassword_Placeholder" xml:space="preserve">
<value>Passwort</value> <value>Passwort</value>
</data> </data>
<data name="Email_Placeholder" xml:space="preserve">
<value>name@example.de</value>
</data>
<data name="SignupDisabled_Message" xml:space="preserve">
<value>Registrationen wurden auf diesem Server deaktiviert.</value>
</data>
</root> </root>

View file

@ -122,4 +122,7 @@
<data name="Submit" xml:space="preserve"> <data name="Submit" xml:space="preserve">
<value>Sign Up Now</value> <value>Sign Up Now</value>
</data> </data>
<data name="SignupDisabled_Message" xml:space="preserve">
<value>Sign up has been disabled on this server. </value>
</data>
</root> </root>