Hmm. It looks like there's nothing at this location.
-- There are no external authentication services configured. See this article - for details on setting up this ASP.NET application to support logging in via external services. -
-- Deleting this data will permanently remove your account, and this cannot be recovered. -
-To use an authenticator app go through the following steps:
-- Download a two-factor authenticator app like Microsoft Authenticator for - Android and - iOS or - Google Authenticator for - Android and - iOS. -
-Scan the QR Code or enter this key @Model.SharedKey into your two factor authenticator app. Spaces and casing do not matter.
- - - -- Once you have scanned the QR code or input the key above, your two factor authentication app will provide you - with a unique code. Enter the code in the confirmation box below. -
-- There are no external authentication services configured. See this article - for details on setting up this ASP.NET application to support logging in via external services. -
-You do not have access to this resource.
++ Please check your email to reset your password. +
diff --git a/Server/Components/Account/Pages/InvalidPasswordReset.razor b/Server/Components/Account/Pages/InvalidPasswordReset.razor new file mode 100644 index 000000000..509578bbf --- /dev/null +++ b/Server/Components/Account/Pages/InvalidPasswordReset.razor @@ -0,0 +1,8 @@ +@page "/Account/InvalidPasswordReset" + ++ The password reset link is invalid. +
diff --git a/Server/Components/Account/Pages/InvalidUser.razor b/Server/Components/Account/Pages/InvalidUser.razor new file mode 100644 index 000000000..e61fe5def --- /dev/null +++ b/Server/Components/Account/Pages/InvalidUser.razor @@ -0,0 +1,7 @@ +@page "/Account/InvalidUser" + +This account has been locked out, please try again later.
+Your login is protected with an authenticator app. Enter your authenticator code below.
++ Don't have access to your authenticator device? You can + log in with a recovery code. +
+ +@code { + private string? message; + private RemotelyUser user = default!; + + [SupplyParameterFromForm] + private InputModel Input { get; set; } = new(); + + [SupplyParameterFromQuery] + private string? ReturnUrl { get; set; } + + [SupplyParameterFromQuery] + private bool RememberMe { get; set; } + + protected override async Task OnInitializedAsync() + { + // Ensure the user has gone through the username & password screen first + user = await SignInManager.GetTwoFactorAuthenticationUserAsync() ?? + throw new InvalidOperationException("Unable to load two-factor authentication user."); + } + + private async Task OnValidSubmitAsync() + { + var authenticatorCode = Input.TwoFactorCode!.Replace(" ", string.Empty).Replace("-", string.Empty); + var result = await SignInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, RememberMe, Input.RememberMachine); + var userId = await UserManager.GetUserIdAsync(user); + + if (result.Succeeded) + { + Logger.LogInformation("User with ID '{UserId}' logged in with 2fa.", userId); + RedirectManager.RedirectTo(ReturnUrl); + } + else if (result.IsLockedOut) + { + Logger.LogWarning("User with ID '{UserId}' account locked out.", userId); + RedirectManager.RedirectTo("Account/Lockout"); + } + else + { + Logger.LogWarning("Invalid authenticator code entered for user with ID '{UserId}'.", userId); + message = "Error: Invalid authenticator code."; + } + } + + private sealed class InputModel + { + [Required] + [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] + [DataType(DataType.Text)] + [Display(Name = "Authenticator code")] + public string? TwoFactorCode { get; set; } + + [Display(Name = "Remember this machine")] + public bool RememberMachine { get; set; } + } +} diff --git a/Server/Components/Account/Pages/LoginWithRecoveryCode.razor b/Server/Components/Account/Pages/LoginWithRecoveryCode.razor new file mode 100644 index 000000000..b127a349f --- /dev/null +++ b/Server/Components/Account/Pages/LoginWithRecoveryCode.razor @@ -0,0 +1,85 @@ +@page "/Account/LoginWithRecoveryCode" + +@using System.ComponentModel.DataAnnotations +@using Microsoft.AspNetCore.Identity +@using Remotely.Server.Data + +@inject SignInManager+ 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. +
++ Deleting this data will permanently remove your account, and this cannot be recovered. +
++ This action only disables 2FA. +
++ Disabling 2FA does not change the keys used in authenticator apps. If you wish to change the key + used in an authenticator app you should reset your authenticator keys. +
+To use an authenticator app go through the following steps:
++ Download a two-factor authenticator app like Microsoft Authenticator for + Android and + iOS or + Google Authenticator for + Android and + iOS. +
+Scan the QR Code or enter this key @sharedKey into your two factor authenticator app. Spaces and casing do not matter.
++ Once you have scanned the QR code or input the key above, your two factor authentication app will provide you + with a unique code. Enter the code in the confirmation box below. +
+@login.ProviderDisplayName | ++ @if (showRemoveButton) + { + + } + else + { + @: + } + | +
+ + Put these codes in a safe place. +
++ If you lose your device and don't have the recovery codes you will lose access to your account. +
++ Generating new recovery codes does not change the keys used in authenticator apps. If you wish to change the key + used in an authenticator app you should reset your authenticator keys. +
+Your account contains personal data that you have given us. This page allows you to download or delete that data.
++ Deleting this data will permanently remove your account, and this cannot be recovered. +
+ ++ Delete +
++ + If you reset your authenticator key your authenticator app will not work until you reconfigure it. +
++ This process disables 2FA until you verify your authenticator app. + If you do not complete your authenticator app configuration you may lose access to your account. +
++ You do not have a local username/password for this site. Add a local + account so you can log in without an external login. +
+You must generate a new set of recovery codes before you can log in with a recovery code.
+You can generate a new set of recovery codes.
+You should generate a new set of recovery codes.
+You must accept the policy before you can enable two factor authentication.
++ This app does not currently have a real email sender registered, see these docs for how to configure a real email sender. + Normally this would be emailed: Click here to confirm your account +
+} +else +{ +Please check your email to confirm your account.
+} + +@code { + private string? emailConfirmationLink; + private string? statusMessage; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + [SupplyParameterFromQuery] + private string? Email { get; set; } + + [SupplyParameterFromQuery] + private string? ReturnUrl { get; set; } + + protected override async Task OnInitializedAsync() + { + if (Email is null) + { + RedirectManager.RedirectTo(""); + } + + var user = await UserManager.FindByEmailAsync(Email); + if (user is null) + { + HttpContext.Response.StatusCode = StatusCodes.Status404NotFound; + statusMessage = "Error finding user for unspecified email"; + } + else if (EmailSender is IdentityNoOpEmailSender) + { + // Once you add a real email sender, you should remove this code that lets you confirm the account + var userId = await UserManager.GetUserIdAsync(user); + var code = await UserManager.GenerateEmailConfirmationTokenAsync(user); + code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); + emailConfirmationLink = NavigationManager.GetUriWithQueryParameters( + NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri, + new Dictionary+ Your password has been reset. Please click here to log in. +
diff --git a/Server/Components/Account/Pages/_Imports.razor b/Server/Components/Account/Pages/_Imports.razor new file mode 100644 index 000000000..b389ce16f --- /dev/null +++ b/Server/Components/Account/Pages/_Imports.razor @@ -0,0 +1,2 @@ +@using Remotely.Server.Components.Account.Shared +@layout AccountLayout diff --git a/Server/Components/Account/Shared/AccountLayout.razor b/Server/Components/Account/Shared/AccountLayout.razor new file mode 100644 index 000000000..9e74e3661 --- /dev/null +++ b/Server/Components/Account/Shared/AccountLayout.razor @@ -0,0 +1,28 @@ +@inherits LayoutComponentBase +@layout Remotely.Server.Components.Layout.MainLayout +@inject NavigationManager NavigationManager + +@if (HttpContext is null) +{ +Loading...
+} +else +{ + @Body +} + +@code { + [CascadingParameter] + private HttpContext? HttpContext { get; set; } + + protected override void OnParametersSet() + { + if (HttpContext is null) + { + // If this code runs, we're currently rendering in interactive mode, so there is no HttpContext. + // The identity pages need to set cookies, so they require an HttpContext. To achieve this we + // must transition back from interactive mode to a server-rendered page. + NavigationManager.Refresh(forceReload: true); + } + } +} diff --git a/Server/Components/Account/Shared/ExternalLoginPicker.razor b/Server/Components/Account/Shared/ExternalLoginPicker.razor new file mode 100644 index 000000000..548f8d967 --- /dev/null +++ b/Server/Components/Account/Shared/ExternalLoginPicker.razor @@ -0,0 +1,43 @@ +@using Microsoft.AspNetCore.Authentication +@using Microsoft.AspNetCore.Identity +@using Remotely.Server.Data + +@inject SignInManager+ There are no external authentication services configured. See this article + about setting up this ASP.NET application to support logging in via external services. +
++ Put these codes in a safe place. +
++ If you lose your device and don't have the recovery codes you will lose access to your account. +
+@recoveryCode
+