Improved LoginWithRecoveryCode

This commit is contained in:
Mia Rose Winter 2024-01-21 19:42:57 +01:00
parent 2097b1d919
commit eb0d54bbc4
Signed by: miawinter
GPG key ID: 4B6F6A83178F595E
4 changed files with 366 additions and 44 deletions

View file

@ -1,6 +1,7 @@
@page "/Account/LoginWithRecoveryCode" @page "/Account/LoginWithRecoveryCode"
@using System.ComponentModel.DataAnnotations @using System.ComponentModel.DataAnnotations
@using System.Globalization
@using Microsoft.AspNetCore.Identity @using Microsoft.AspNetCore.Identity
@using Wave.Data @using Wave.Data
@ -8,75 +9,60 @@
@inject UserManager<ApplicationUser> UserManager @inject UserManager<ApplicationUser> UserManager
@inject IdentityRedirectManager RedirectManager @inject IdentityRedirectManager RedirectManager
@inject ILogger<LoginWithRecoveryCode> Logger @inject ILogger<LoginWithRecoveryCode> Logger
@inject IStringLocalizer<LoginWithRecoveryCode> Localizer
<PageTitle>Recovery code verification</PageTitle> <PageTitle>@Localizer["Title"]</PageTitle>
<h1>Recovery code verification</h1> <StatusMessage Message="@Message" />
<hr /> <BoardComponent CenterContent="true">
<StatusMessage Message="@message" /> <BoardCardComponent Heading="@Localizer["Title"]">
<p> <p class="text-justify hyphens-auto" lang="@CultureInfo.CurrentCulture">@Localizer["Message"]</p>
You have requested to log in with a recovery code. This login will not be remembered until you provide <EditForm Model="Input" FormName="login-with-recovery-code" OnValidSubmit="OnValidSubmitAsync" method="post" class="w-full">
an authenticator app code at log in or disable 2FA and log in again.
</p>
<div class="row">
<div class="col-md-4">
<EditForm Model="Input" FormName="login-with-recovery-code" OnValidSubmit="OnValidSubmitAsync" method="post">
<DataAnnotationsValidator /> <DataAnnotationsValidator />
<ValidationSummary class="text-danger" role="alert" />
<div class="form-floating mb-3"> <InputLabelComponent LabelText="@Localizer["RecoveryCode_Label"]" For="() => Input.RecoveryCode">
<InputText @bind-Value="Input.RecoveryCode" class="form-control" autocomplete="off" placeholder="RecoveryCode" /> <InputText @bind-Value="Input.RecoveryCode" class="input input-bordered w-full"
<label for="recovery-code" class="form-label">Recovery Code</label> autocomplete="off" placeholder="@Localizer["RecoveryCode_Placeholder"]" />
<ValidationMessage For="() => Input.RecoveryCode" class="text-danger" /> </InputLabelComponent>
</div>
<button type="submit" class="w-100 btn btn-lg btn-primary">Log in</button> <button type="submit" class="btn btn-primary w-full">@Localizer["RecoveryCode_Submit"]</button>
</EditForm> </EditForm>
</div> </BoardCardComponent>
</div> </BoardComponent>
@code { @code {
private string? message;
private ApplicationUser user = default!;
[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; }
protected override async Task OnInitializedAsync() private string? Message { get; set; }
{ private ApplicationUser User { get; set; } = default!;
protected override async Task OnInitializedAsync() {
// Ensure the user has gone through the username & password screen first // Ensure the user has gone through the username & password screen first
user = await SignInManager.GetTwoFactorAuthenticationUserAsync() ?? User = await SignInManager.GetTwoFactorAuthenticationUserAsync() ??
throw new InvalidOperationException("Unable to load two-factor authentication user."); throw new InvalidOperationException("Unable to load two-factor authentication user.");
} }
private async Task OnValidSubmitAsync() private async Task OnValidSubmitAsync() {
{ string recoveryCode = Input.RecoveryCode.Replace(" ", string.Empty);
var recoveryCode = Input.RecoveryCode.Replace(" ", string.Empty);
var result = await SignInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode); var result = await SignInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode);
var userId = await UserManager.GetUserIdAsync(user); string userId = await UserManager.GetUserIdAsync(User);
if (result.Succeeded) {
if (result.Succeeded)
{
Logger.LogInformation("User with ID '{UserId}' logged in with a recovery code.", userId); Logger.LogInformation("User with ID '{UserId}' logged in with a recovery code.", userId);
RedirectManager.RedirectTo(ReturnUrl); RedirectManager.RedirectTo(ReturnUrl);
} } else if (result.IsLockedOut) {
else if (result.IsLockedOut)
{
Logger.LogWarning("User account locked out."); Logger.LogWarning("User account locked out.");
RedirectManager.RedirectTo("Account/Lockout"); RedirectManager.RedirectTo("Account/Lockout");
} } else {
else
{
Logger.LogWarning("Invalid recovery code entered for user with ID '{UserId}' ", userId); Logger.LogWarning("Invalid recovery code entered for user with ID '{UserId}' ", userId);
message = "Error: Invalid recovery code entered."; Message = Localizer["RecoveryCode_ErrorInvalidCode"];
} }
} }
private sealed class InputModel private sealed class InputModel {
{
[Required] [Required]
[DataType(DataType.Text)] [DataType(DataType.Text)]
[Display(Name = "Recovery Code")] [Display(Name = "Recovery Code")]

View file

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 1.3
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">1.3</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1">this is my long string</data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
[base64 mime encoded serialized .NET Framework object]
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
[base64 mime encoded string representing a byte array form of the .NET Framework object]
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="RecoveryCode_Submit" xml:space="preserve">
<value>Anmelden</value>
</data>
<data name="RecoveryCode_Label" xml:space="preserve">
<value>Wiederherstellungsschlüssel</value>
</data>
<data name="Title" xml:space="preserve">
<value>Wiederherstellungscode Verifizierung </value>
</data>
<data name="RecoveryCode_ErrorInvalidCode" xml:space="preserve">
<value>Fehler: Der eingegebene Schlüssel ist nit korrekt.</value>
</data>
<data name="Message" xml:space="preserve">
<value>Sie haben angefordert sich mit einem Wiederherstellungsschlüssel anzumelden. Dieser Computer wird sich nicht gemerkt bis Sie sich mit ihrer Authentifizierungsapp anmelden oder 2fa deaktivieren und sich erneut anmelden.</value>
</data>
</root>

View file

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 1.3
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">1.3</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1">this is my long string</data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
[base64 mime encoded serialized .NET Framework object]
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
[base64 mime encoded string representing a byte array form of the .NET Framework object]
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View file

@ -0,0 +1,119 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 1.3
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">1.3</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1">this is my long string</data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
[base64 mime encoded serialized .NET Framework object]
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
[base64 mime encoded string representing a byte array form of the .NET Framework object]
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Title" xml:space="preserve">
<value>Recovery Code Verification</value>
</data>
<data name="Message" xml:space="preserve">
<value>You have requested to log in with a recovery code. This login will not be remembered until you provide an authenticator app code at log in or disable 2FA and log in again.</value>
</data>
<data name="RecoveryCode_Label" xml:space="preserve">
<value>Recovery Code</value>
</data>
<data name="RecoveryCode_Placeholder" xml:space="preserve">
<value>xxxx</value>
</data>
<data name="RecoveryCode_Submit" xml:space="preserve">
<value>Log in</value>
</data>
<data name="RecoveryCode_ErrorInvalidCode" xml:space="preserve">
<value>Error: Invalid recovery code entered.</value>
</data>
</root>