@Localizer["Forgot your password?"]
@* A forgotten password (PrivaPub's /clientapi/user/recover): the login's name, or its verified address, asks for a link,
and PrivaPub answers everyone with the same sentence, so the page never tells whether a login or an address exists.
The link comes back here with its code (?rc=), which leaves the address bar and the history at once and stays in the
page; a new password set with it ends every session the login had, and the reader signs in with it. *@
@if (CascadingState.IsSignedIn)
{
@Localizer["You are signed in"]
@Localizer["Home"]
}
else if (recoveryCode is null && sentence is not null)
{
@Localizer["Check your inbox"]
@sentence
@Localizer["Login"]
}
else if (recoveryCode is null)
{
@Localizer["Forgot your password?"]
@Localizer["Give your login's username or its email address: if the login has a verified address, a link to choose a new password is sent to it."]
@Localizer["or"]
@if (isWorking)
{
}
@Localizer["Send the recovery link"]@Localizer["I remember it: sign in"]
}
else if (IsLoading)
{
}
else if (isCodeValid is false)
{
@Localizer["This recovery link doesn't work"]
@Localizer["A link works for one hour, and only once. Ask for a new one."]
@Localizer["Ask for a new link"]
}
else
{
@* also while the code couldn't be checked (offline, or the check failed): the answer to the new password says *@
@Localizer["Choose a new password"]
@Localizer["Every device signed in to this login is signed out."]
@if (isWorking)
{
}
@Localizer["Change the password"]
}
@code {
[Inject] AuthService AuthService { get; set; }
readonly PasswordRecoveryForm request = new();
NewPasswordForm newPassword = new();
/// The code of the link the page was opened from, kept here once it left the address bar.
string recoveryCode;
/// Whether the code works: null until PrivaPub said.
bool? isCodeValid;
/// PrivaPub's answer to a request, the same for everyone.
string sentence;
string error;
bool isWorking;
[SupplyParameterFromQuery(Name = "rc")] public string LinkCode { get; set; }
protected override async Task OnInitializedAsync()
{
if (LinkCode is { Length: > 0 } code)
{
recoveryCode = code.Trim();
newPassword.RecoveryCode = recoveryCode;
// the code is as good as the password for an hour: out of the address bar and the history at once
Navigation.NavigateTo("password-recovery", replace: true);
}
if (CascadingState.IsOffline)
{
WhenOnline(LoadData);
StartOnlinePolling();
IsLoading = false;
return;
}
await LoadData();
IsLoading = false;
}
async Task LoadData()
{
if (recoveryCode is null)
return;
var result = await AuthService.CheckRecoveryCode(recoveryCode);
if (result.IsInvalid)
{
error = result.ErrorMessage;
return;
}
isCodeValid = result.Data is true;
}
static string NullIfBlank(string value) => string.IsNullOrWhiteSpace(value) ? null : value;
async Task Request()
{
error = default;
isWorking = true;
request.UserName = request.UserName?.Trim();
request.Email = request.Email?.Trim().ToLowerInvariant();
var result = await AuthService.RequestRecovery(request);
isWorking = false;
if (result.IsInvalid)
{
error = result.ErrorMessage;
return;
}
sentence = result.Data is string { Length: > 0 } said
? said
: Localizer["If that account has an email address, a recovery link is on its way. It works for one hour."];
}
void AskAgain()
{
recoveryCode = null;
isCodeValid = null;
newPassword = new();
error = default;
}
async Task Recover()
{
error = default;
isWorking = true;
var result = await AuthService.Recover(newPassword);
isWorking = false;
if (result.StatusCode is (int)System.Net.HttpStatusCode.NotFound)
{
// used or expired meanwhile
isCodeValid = false;
return;
}
if (result.IsInvalid)
{
error = result.ErrorMessage;
return;
}
Toast.AddResponseSuccess(Localizer["Password changed: sign in with the new one."]);
Navigation.NavigateTo("login");
}
}