added support for Forgot Username and Use Login Link

This commit is contained in:
sbwalker
2025-12-14 15:13:53 -05:00
parent 6b883b3f94
commit ec2afd5f03
11 changed files with 500 additions and 124 deletions

View File

@@ -14,93 +14,133 @@
}
else
{
@if (!twofactor)
{
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="Oqtane-Modules-Admin-Login" @onkeypress="@(e => KeyPressed(e))">
@if (_allowexternallogin)
{
<button type="button" class="btn btn-primary col-12" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
<hr class="app-rule mt-3 mb-2" />
}
@if (_allowsitelogin)
{
<div class="form-group text-center">
<Label Class="control-label" For="username" HelpText="Please enter your Username" ResourceKey="Username">Username:</Label>
<input id="username" type="text" @ref="username" class="form-control" placeholder="@Localizer["Username.Placeholder"]" @bind="@_username" @bind:event="oninput" required />
</div>
<div class="form-group text-center mt-2">
<Label Class="control-label" For="password" HelpText="Please enter your Password" ResourceKey="Password">Password:</Label>
<div class="input-group">
<input id="password" type="@_passwordtype" name="Password" class="form-control" placeholder="@Localizer["Password.Placeholder"]" @bind="@_password" @bind:event="oninput" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
@if (!_alwaysremember)
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="Oqtane-Modules-Admin-Login" @onkeypress="@(e => KeyPressed(e))">
@switch (_action)
{
case "Login":
@if (_allowexternallogin)
{
<button type="button" class="btn btn-primary col-12" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
<hr class="app-rule mt-3 mb-2" />
}
@if (_allowsitelogin)
{
<div class="form-group text-center">
<Label Class="control-label" For="username" HelpText="Please enter your Username" ResourceKey="Username">Username:</Label>
<input id="username" type="text" @ref="username" class="form-control" placeholder="@Localizer["Username.Placeholder"]" @bind="@_username" @bind:event="oninput" required />
</div>
<div class="form-group text-center mt-2">
<div>
<input id="remember" type="checkbox" class="form-check-input" @bind="@_remember" />
<Label Class="control-label" For="remember" HelpText="Specify if you would like to be signed back in automatically the next time you visit this site" ResourceKey="Remember">Remember Me?</Label>
<Label Class="control-label" For="password" HelpText="Please enter your Password" ResourceKey="Password">Password:</Label>
<div class="input-group">
<input id="password" type="@_passwordtype" @ref="password" name="Password" class="form-control" placeholder="@Localizer["Password.Placeholder"]" @bind="@_password" @bind:event="oninput" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
@if (!_alwaysremember)
{
<div class="form-group text-center mt-2">
<div>
<input id="remember" type="checkbox" class="form-check-input" @bind="@_remember" />
<Label Class="control-label" For="remember" HelpText="Specify if you would like to be signed back in automatically the next time you visit this site" ResourceKey="Remember">Stay Signed In?</Label>
</div>
</div>
}
<div class="btn-group mt-2 col-12" role="group">
<button type="button" class="btn btn-primary col-6" @onclick="Login">@SharedLocalizer["Login"]</button>
<button type="button" class="btn btn-secondary col-6" @onclick="CancelLogin">@SharedLocalizer["Cancel"]</button>
</div>
<button type="button" class="btn btn-secondary col-12 mt-4" @onclick="@(() => SetAction("ForgotPassword"))">@Localizer["ForgotPassword"]</button>
}
<div class="btn-group mt-2 col-12" role="group">
<button type="button" class="btn btn-primary col-6" @onclick="Login">@SharedLocalizer["Login"]</button>
<button type="button" class="btn btn-secondary col-6" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</div>
<button type="button" class="btn btn-secondary col-12 mt-4" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
@if (_allowloginlink)
{
<hr class="app-rule mt-3" />
<button type="button" class="btn btn-primary col-12 mt-2" @onclick="@(() => SetAction("LoginLink"))">@Localizer["UseLoginLink"]</button>
}
@if (_allowpasskeys)
{
<hr class="app-rule mt-3" />
<button type="button" class="btn btn-primary col-12 mt-2" @onclick="Passkey">@Localizer["Passkey"]</button>
<button type="button" class="btn btn-primary col-12 mt-2" @onclick="PasskeyLogin">@Localizer["Passkey"]</button>
}
@if (PageState.Site.AllowRegistration)
{
{
<hr class="app-rule mt-3" />
<div class="text-center mt-2">
<NavLink href="@_registerurl">@Localizer["Register"]</NavLink>
</div>
}
}
</div>
</form>
}
else
{
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container Oqtane-Modules-Admin-Login">
<div class="form-group">
<Label Class="control-label" For="code" HelpText="Please enter the secure verification code which was sent to you by email" ResourceKey="Code">Verification Code:</Label>
<input id="code" class="form-control" @bind="@_code" placeholder="@Localizer["Code.Placeholder"]" maxlength="6" required />
</div>
<br />
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Cancel"]</button>
</div>
</form>
}
break;
case "ForgotPassword":
<div class="form-group text-center">
<Label Class="control-label" For="username" HelpText="Please enter your Username" ResourceKey="Username">Username:</Label>
<input id="username" type="text" class="form-control" placeholder="@Localizer["Username.Placeholder"]" @bind="@_username" @bind:event="oninput" required />
</div>
<div class="btn-group mt-4 col-12" role="group">
<button type="button" class="btn btn-primary col-6" @onclick="ForgotPassword">@SharedLocalizer["Send"]</button>
<button type="button" class="btn btn-secondary col-6" @onclick="@(() => SetAction("Login"))">@SharedLocalizer["Cancel"]</button>
</div>
<button type="button" class="btn btn-secondary col-12 mt-4" @onclick="@(() => SetAction("ForgotUsername"))">@Localizer["ForgotUsername"]</button>
break;
case "ForgotUsername":
<div class="form-group text-center">
<Label Class="control-label" For="email" HelpText="Please enter your Email" ResourceKey="Email">Email:</Label>
<input id="email" type="text" class="form-control" placeholder="@Localizer["Email.Placeholder"]" @bind="@_email" required />
</div>
<div class="btn-group mt-4 col-12" role="group">
<button type="button" class="btn btn-primary col-6" @onclick="ForgotUsername">@SharedLocalizer["Send"]</button>
<button type="button" class="btn btn-secondary col-6" @onclick="@(() => SetAction("Login"))">@SharedLocalizer["Cancel"]</button>
</div>
break;
case "LoginLink":
<div class="form-group text-center">
<Label Class="control-label" For="email" HelpText="Please enter your Email" ResourceKey="Email">Email:</Label>
<input id="email" type="text" class="form-control" placeholder="@Localizer["Email.Placeholder"]" @bind="@_email" required />
</div>
<div class="btn-group mt-4 col-12" role="group">
<button type="button" class="btn btn-primary col-6" @onclick="LoginLink">@SharedLocalizer["Send"]</button>
<button type="button" class="btn btn-secondary col-6" @onclick="@(() => SetAction("Login"))">@SharedLocalizer["Cancel"]</button>
</div>
break;
case "TwoFactor":
<div class="form-group">
<Label Class="control-label" For="code" HelpText="Please enter the secure verification code which was sent to you by email" ResourceKey="Code">Verification Code:</Label>
<input id="code" class="form-control" @bind="@_code" placeholder="@Localizer["Code.Placeholder"]" maxlength="6" required />
</div>
<div class="btn-group mt-4 col-12" role="group">
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
<button type="button" class="btn btn-secondary" @onclick="@(() => SetAction("Login"))">@SharedLocalizer["Cancel"]</button>
</div>
break;
}
</div>
</form>
}
@code {
private bool _allowsitelogin = true;
private string _action = "Login";
private bool _allowexternallogin = false;
private bool _allowsitelogin = true;
private bool _allowloginlink = false;
private bool _allowpasskeys = false;
private ElementReference login;
private bool validated = false;
private bool twofactor = false;
private string _username = string.Empty;
private ElementReference username;
private string _password = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private ElementReference password;
private bool _remember = false;
private bool _alwaysremember = false;
private string _code = string.Empty;
private string _registerurl = string.Empty;
private string _email = string.Empty;
private string _code = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
public override bool? Prerender => true;
@@ -116,6 +156,7 @@ else
{
_allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false;
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
_allowloginlink = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:LoginLink", "false"));
_allowpasskeys = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:Passkeys", "false"));
_alwaysremember = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AlwaysRemember", "false"));
@@ -186,6 +227,45 @@ else
}
}
private async Task KeyPressed(KeyboardEventArgs e)
{
if (e.Code == "Enter" || e.Code == "NumpadEnter")
{
switch (_action)
{
case "Login":
await Login();
break;
}
}
}
private void SetAction(string action)
{
_action = action;
ClearModuleMessage();
StateHasChanged();
}
private void ExternalLogin()
{
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + WebUtility.UrlEncode(PageState.ReturnUrl)), true);
}
private void TogglePassword()
{
if (_passwordtype == "password")
{
_passwordtype = "text";
_togglepassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordtype = "password";
_togglepassword = SharedLocalizer["ShowPassword"];
}
}
private async Task Login()
{
try
@@ -197,7 +277,7 @@ else
var hybrid = (PageState.Runtime == Shared.Runtime.Hybrid);
var user = new User { SiteId = PageState.Site.SiteId, Username = _username, Password = _password, LastIPAddress = SiteState.RemoteIPAddress};
if (!twofactor)
if (_action == "Login")
{
_remember = _alwaysremember || _remember;
user = await UserService.LoginUserAsync(user, hybrid, _remember);
@@ -233,13 +313,13 @@ else
{
if (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "required" || (user != null && user.TwoFactorRequired))
{
twofactor = true;
_action = "TwoFactor";
validated = false;
AddModuleMessage(Localizer["Message.TwoFactor"], MessageType.Info);
}
else
{
if (!twofactor)
if (_action != "TwoFactor")
{
await logger.LogInformation(LogFunction.Security, "Login Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error);
@@ -264,23 +344,32 @@ else
}
}
private void Cancel()
private void CancelLogin()
{
NavigationManager.NavigateTo(PageState.ReturnUrl);
}
private async Task Forgot()
private async Task PasskeyLogin()
{
// post back to the Passkey page so that the cookies are set correctly
var interop = new Interop(JSRuntime);
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, operation = "request", returnurl = NavigateUrl() };
string url = Utilities.TenantUrl(PageState.Alias, "/pages/passkey/");
await interop.SubmitForm(url, fields);
}
private async Task ForgotPassword()
{
try
{
if (_username != string.Empty)
if (!string.IsNullOrEmpty(_username))
{
var user = await UserService.GetUserAsync(_username, PageState.Site.SiteId);
var user = new User { Username = _username };
user = await UserService.ForgotPasswordAsync(user);
if (user != null)
{
await UserService.ForgotPasswordAsync(user);
await logger.LogInformation(LogFunction.Security, "Password Reset Notification Sent For Username {Username}", _username);
AddModuleMessage(Localizer["Message.ForgotUser"], MessageType.Info);
AddModuleMessage(Localizer["Message.ForgotPassword"], MessageType.Info);
}
else
{
@@ -289,10 +378,8 @@ else
}
else
{
AddModuleMessage(Localizer["Message.ForgotPassword"], MessageType.Info);
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
}
StateHasChanged();
}
catch (Exception ex)
{
@@ -301,51 +388,66 @@ else
}
}
private void Reset()
private async Task ForgotUsername()
{
twofactor = false;
_username = "";
_password = "";
ClearModuleMessage();
StateHasChanged();
}
private async Task KeyPressed(KeyboardEventArgs e)
{
if (e.Code == "Enter" || e.Code == "NumpadEnter")
try
{
await Login();
if (!string.IsNullOrEmpty(_email))
{
var user = new User { Email = _email };
user = await UserService.ForgotUsernameAsync(user);
if (user != null)
{
AddModuleMessage(Localizer["Message.ForgotUsername"], MessageType.Info);
await logger.LogInformation(LogFunction.Security, "Username Reminder Notification Sent For Email {Email}", _email);
}
else
{
AddModuleMessage(Localizer["Message.UserDoesNotExist"], MessageType.Warning);
}
}
else
{
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Sending Username Reminder {Error}", ex.Message);
AddModuleMessage(Localizer["Error.ForgotUsername"], MessageType.Error);
}
}
private void TogglePassword()
private async Task LoginLink()
{
if (_passwordtype == "password")
try
{
_passwordtype = "text";
_togglepassword = SharedLocalizer["HidePassword"];
if (!string.IsNullOrEmpty(_email))
{
var user = new User { Email = _email };
user = await UserService.SendLoginLinkAsync(user);
if (user != null)
{
AddModuleMessage(Localizer["Message.SendLoginLink"], MessageType.Info);
await logger.LogInformation(LogFunction.Security, "Login Link Sent To Email {Email}", _email);
}
else
{
AddModuleMessage(Localizer["Message.UserDoesNotExist"], MessageType.Warning);
}
}
else
{
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
}
}
else
catch (Exception ex)
{
_passwordtype = "password";
_togglepassword = SharedLocalizer["ShowPassword"];
await logger.LogError(ex, "Error Sending Login Link {Error}", ex.Message);
AddModuleMessage(Localizer["Error.SendLoginLink"], MessageType.Error);
}
}
private void ExternalLogin()
{
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + WebUtility.UrlEncode(PageState.ReturnUrl)), true);
}
private async Task Passkey()
{
// post back to the Passkey page so that the cookies are set correctly
var interop = new Interop(JSRuntime);
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, operation = "request", returnurl = NavigateUrl() };
string url = Utilities.TenantUrl(PageState.Alias, "/pages/passkey/");
await interop.SubmitForm(url, fields);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && PageState.QueryString.ContainsKey("options"))
@@ -377,11 +479,21 @@ else
return;
}
if (firstRender && PageState.User == null && _allowsitelogin)
if (firstRender && PageState.User == null && _allowsitelogin && _action == "Login")
{
if (!string.IsNullOrEmpty(username.Id)) // ensure username is visible in UI
if (string.IsNullOrEmpty(_username))
{
await username.FocusAsync();
if (!string.IsNullOrEmpty(username.Id)) // ensure username is visible in UI
{
await username.FocusAsync();
}
}
else
{
if (!string.IsNullOrEmpty(password.Id)) // ensure password is visible in UI
{
await password.FocusAsync();
}
}
}

View File

@@ -98,6 +98,15 @@ else
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="loginlink" HelpText="Do you want to allow users to login using a time sensitive link sent by email" ResourceKey="LoginLink">Allow Login Link?</Label>
<div class="col-sm-9">
<select id="loginlink" class="form-select" @bind="@_loginlink">
<option value="true">@SharedLocalizer["Yes"]</option>
<option value="false">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="passkeys" HelpText="Do you want to allow users to login using passkeys (ie. passwordless authentication using WebAuthn/FIDO2)" ResourceKey="Passkeys">Allow Passkeys?</Label>
<div class="col-sm-9">
@@ -547,6 +556,7 @@ else
private string _registerurl;
private string _profileurl;
private string _requireconfirmedemail;
private string _loginlink;
private string _passkeys;
private string _twofactor;
private string _cookiename;
@@ -625,6 +635,7 @@ else
_registerurl = SettingService.GetSetting(settings, "LoginOptions:RegisterUrl", "");
_profileurl = SettingService.GetSetting(settings, "LoginOptions:ProfileUrl", "");
_requireconfirmedemail = SettingService.GetSetting(settings, "LoginOptions:RequireConfirmedEmail", "true");
_loginlink = SettingService.GetSetting(settings, "LoginOptions:LoginLink", "false");
_passkeys = SettingService.GetSetting(settings, "LoginOptions:Passkeys", "false");
_twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false");
_cookiename = SettingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application");
@@ -764,6 +775,7 @@ else
settings = SettingService.SetSetting(settings, "LoginOptions:RegisterUrl", _registerurl, false);
settings = SettingService.SetSetting(settings, "LoginOptions:ProfileUrl", _profileurl, false);
settings = SettingService.SetSetting(settings, "LoginOptions:RequireConfirmedEmail", _requireconfirmedemail, false);
settings = SettingService.SetSetting(settings, "LoginOptions:LoginLink", _loginlink, false);
settings = SettingService.SetSetting(settings, "LoginOptions:Passkeys", _passkeys, false);
settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true);

View File

@@ -120,6 +120,12 @@
<data name="ForgotPassword" xml:space="preserve">
<value>Forgot Password?</value>
</data>
<data name="ForgotUsername" xml:space="preserve">
<value>Forgot Username?</value>
</data>
<data name="UseLoginLink" xml:space="preserve">
<value>Use Login Link</value>
</data>
<data name="Success.Account.Verified" xml:space="preserve">
<value>User Account Email Address Verified Successfully. You Can Now Login With Your Username And Password.</value>
</data>
@@ -142,16 +148,19 @@
<value>You Are Already Signed In</value>
</data>
<data name="Message.ForgotPassword" xml:space="preserve">
<value>Please Enter The Username Related To Your Account And Then Select The Forgot Password Option Again</value>
</data>
<data name="Message.ForgotUser" xml:space="preserve">
<value>Please Check The Email Address Associated To Your User Account For A Password Reset Notification</value>
</data>
<data name="Message.ForgotUsername" xml:space="preserve">
<value>Please Check Your Email For A Username Reminder Notification</value>
</data>
<data name="Message.SendLoginLink" xml:space="preserve">
<value>A Login Link Has Been Sent To Your Email Address. The Link Is Only Valid For A Limited Amount Of Time.</value>
</data>
<data name="Message.UserDoesNotExist" xml:space="preserve">
<value>User Does Not Exist</value>
<value>User Does Not Exist For Criteria Specified</value>
</data>
<data name="Code.HelpText" xml:space="preserve">
<value>Please Enter The Secure Verification Code Which Was Sent To You By Email.</value>
<value>Please enter the secure verification code which was sent to you by email</value>
</data>
<data name="Code.Placeholder" xml:space="preserve">
<value>Verification Code</value>
@@ -166,7 +175,7 @@
<value>A Secure Verification Code Has Been Sent To Your Email Address. Please Enter The Code That You Received. If You Do Not Receive The Code Or You Have Lost Access To Your Email, Please Contact Your Administrator.</value>
</data>
<data name="Password.HelpText" xml:space="preserve">
<value>Please Enter The Password Related To Your Account. Remember That Passwords Are Case Sensitive. If You Attempt Unsuccessfully To Log In To Your Account Multiple Times, You Will Be Locked Out For A Period Of Time.</value>
<value>Please enter the password related to your account. Remember that passwords are sase sensitive. If you attempt to login to your account multiple times unsuccessfully, you will be locked out for a period of time.</value>
</data>
<data name="Password.Placeholder" xml:space="preserve">
<value>Password</value>
@@ -175,13 +184,13 @@
<value>Password:</value>
</data>
<data name="Remember.HelpText" xml:space="preserve">
<value>Specify If You Would Like To Be Signed Back In Automatically The Next Time You Visit This Site</value>
<value>Specify if you would like to be signed back in automatically the next time you visit this site</value>
</data>
<data name="Remember.Text" xml:space="preserve">
<value>Remember Me?</value>
<value>Stay Signed In?</value>
</data>
<data name="Username.HelpText" xml:space="preserve">
<value>Please Enter The Username Related To Your Account</value>
<value>Please enter the username related to your account</value>
</data>
<data name="Username.Placeholder" xml:space="preserve">
<value>Username</value>
@@ -201,7 +210,13 @@
<data name="Error.ResetPassword" xml:space="preserve">
<value>Error Resetting Password</value>
</data>
<data name="ExternalLoginStatus.DuplicateEmail" xml:space="preserve">
<data name="Error.ForgotUsername" xml:space="preserve">
<value>Error Sending Username Reminder</value>
</data>
<data name="Error.SendLoginLink" xml:space="preserve">
<value>Error Sending Login Link</value>
</data>
<data name="ExternalLoginStatus.DuplicateEmail" xml:space="preserve">
<value>Multiple User Accounts Already Exist With The Email Address Of Your External Login. Please Contact Your Administrator For Further Instructions.</value>
</data>
<data name="ExternalLoginStatus.MissingClaims" xml:space="preserve">
@@ -228,6 +243,9 @@
<data name="ExternalLoginStatus.ReviewClaims" xml:space="preserve">
<value>The Review Claims Option Was Enabled In External Login Settings. Please Visit The Event Log To View The Claims Returned By The Provider.</value>
</data>
<data name="ExternalLoginStatus.LoginLinkFailed" xml:space="preserve">
<value>Login Links Are Time Sensitive. Please Request Another Login Link To Complete The Login Process.</value>
</data>
<data name="Register" xml:space="preserve">
<value>Register as new user?</value>
</data>
@@ -237,4 +255,13 @@
<data name="Error.Passkey.Fail" xml:space="preserve">
<value>Passkey Login Was Not Successful</value>
</data>
<data name="Email.HelpText" xml:space="preserve">
<value>Please enter the email address related to your account</value>
</data>
<data name="Email.Placeholder" xml:space="preserve">
<value>Email Address</value>
</data>
<data name="Email.Text" xml:space="preserve">
<value>Email:</value>
</data>
</root>

View File

@@ -567,4 +567,10 @@
<data name="Passkeys.HelpText" xml:space="preserve">
<value>Do you want to allow users to login using passkeys (ie. passwordless authentication using WebAuthn/FIDO2)?</value>
</data>
<data name="LoginLink.Text" xml:space="preserve">
<value>Allow Login Link?</value>
</data>
<data name="LoginLink.HelpText" xml:space="preserve">
<value>Do you want to allow users to login using a time sensitive link sent by email?</value>
</data>
</root>

View File

@@ -101,7 +101,14 @@ namespace Oqtane.Services
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
Task ForgotPasswordAsync(User user);
Task<User> ForgotPasswordAsync(User user);
/// <summary>
/// Trigger a forgot-username e-mail for this <see cref="User"/>.
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
Task<User> ForgotUsernameAsync(User user);
/// <summary>
/// Reset the password of this <see cref="User"/>
@@ -211,6 +218,13 @@ namespace Oqtane.Services
/// <param name="key"></param>
/// <returns></returns>
Task DeleteLoginAsync(int userId, string provider, string key);
/// <summary>
/// Send a login link
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
Task<User> SendLoginLinkAsync(User user);
}
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
@@ -275,9 +289,14 @@ namespace Oqtane.Services
return await PostJsonAsync<User>($"{Apiurl}/verify?token={token}", user);
}
public async Task ForgotPasswordAsync(User user)
public async Task<User> ForgotPasswordAsync(User user)
{
await PostJsonAsync($"{Apiurl}/forgot", user);
return await PostJsonAsync<User>($"{Apiurl}/forgot", user);
}
public async Task<User> ForgotUsernameAsync(User user)
{
return await PostJsonAsync<User>($"{Apiurl}/forgotusername", user);
}
public async Task<User> ResetPasswordAsync(User user, string token)
@@ -366,5 +385,10 @@ namespace Oqtane.Services
{
await DeleteAsync($"{Apiurl}/login?id={userId}&provider={provider}&key={key}");
}
public async Task<User> SendLoginLinkAsync(User user)
{
return await PostJsonAsync<User>($"{Apiurl}/loginlink", user);
}
}
}