added support for Forgot Username and Use Login Link
This commit is contained in:
@ -296,12 +296,24 @@ namespace Oqtane.Controllers
|
||||
|
||||
// POST api/<controller>/forgot
|
||||
[HttpPost("forgot")]
|
||||
public async Task Forgot([FromBody] User user)
|
||||
public async Task<User> Forgot([FromBody] User user)
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
await _userManager.ForgotPassword(user);
|
||||
return await _userManager.ForgotPassword(user);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// POST api/<controller>/forgotusername
|
||||
[HttpPost("forgotusername")]
|
||||
public async Task<User> ForgotUsername([FromBody] User user)
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
return await _userManager.ForgotUsername(user);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// POST api/<controller>/reset
|
||||
@ -559,5 +571,16 @@ namespace Oqtane.Controllers
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
}
|
||||
}
|
||||
|
||||
// POST api/<controller>/loginlink
|
||||
[HttpPost("loginlink")]
|
||||
public async Task<User> SendLoginLink([FromBody] User user)
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
return await _userManager.SendLoginLink(user);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,10 +4,8 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Security.Policy;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Oqtane.Enums;
|
||||
@ -30,7 +28,8 @@ namespace Oqtane.Managers
|
||||
Task<User> LoginUser(User user, bool setCookie, bool isPersistent);
|
||||
Task LogoutUserEverywhere(User user);
|
||||
Task<User> VerifyEmail(User user, string token);
|
||||
Task ForgotPassword(User user);
|
||||
Task<User> ForgotPassword(User user);
|
||||
Task<User> ForgotUsername(User user);
|
||||
Task<User> ResetPassword(User user, string token);
|
||||
User VerifyTwoFactor(User user, string token);
|
||||
Task<UserValidateResult> ValidateUser(string username, string email, string password);
|
||||
@ -42,6 +41,7 @@ namespace Oqtane.Managers
|
||||
Task<List<UserLogin>> GetLogins(int userId, int siteId);
|
||||
Task<User> AddLogin(User user, string token, string type, string key, string name);
|
||||
Task DeleteLogin(int userId, string provider, string key);
|
||||
Task<User> SendLoginLink(User user);
|
||||
}
|
||||
|
||||
public class UserManager : IUserManager
|
||||
@ -519,14 +519,16 @@ namespace Oqtane.Managers
|
||||
}
|
||||
return user;
|
||||
}
|
||||
public async Task ForgotPassword(User user)
|
||||
|
||||
public async Task<User> ForgotPassword(User user)
|
||||
{
|
||||
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username);
|
||||
if (identityuser != null)
|
||||
{
|
||||
var alias = _tenantManager.GetAlias();
|
||||
user = _users.GetUser(user.Username);
|
||||
string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser);
|
||||
|
||||
var alias = _tenantManager.GetAlias();
|
||||
user = GetUser(user.Username, alias.SiteId);
|
||||
string url = alias.Protocol + alias.Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
|
||||
string siteName = _sites.GetSite(alias.SiteId).Name;
|
||||
string subject = _localizer["ForgotPasswordEmailSubject"];
|
||||
@ -537,11 +539,51 @@ namespace Oqtane.Managers
|
||||
body = body.Replace("[SiteName]", siteName);
|
||||
var notification = new Notification(_tenantManager.GetAlias().SiteId, user, subject, body);
|
||||
_notifications.AddNotification(notification);
|
||||
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Security, "Password Reset Notification Sent For {Username}", user.Username);
|
||||
return new User { UserId = user.UserId, Username = user.Username, Email = user.Email }; // minimal object
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Password Reset Notification Failed For {Username}", user.Username);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<User> ForgotUsername(User user)
|
||||
{
|
||||
try
|
||||
{
|
||||
IdentityUser identityuser = await _identityUserManager.FindByEmailAsync(user.Email);
|
||||
if (identityuser != null)
|
||||
{
|
||||
var alias = _tenantManager.GetAlias();
|
||||
user = GetUser(identityuser.UserName, alias.SiteId);
|
||||
string url = alias.Protocol + alias.Name + "/login?name=" + user.Username;
|
||||
string siteName = _sites.GetSite(alias.SiteId).Name;
|
||||
string subject = _localizer["ForgotUsernameEmailSubject"];
|
||||
subject = subject.Replace("[SiteName]", siteName);
|
||||
string body = _localizer["ForgotUsernameEmailBody"].Value;
|
||||
body = body.Replace("[UserDisplayName]", user.DisplayName);
|
||||
body = body.Replace("[URL]", url);
|
||||
body = body.Replace("[SiteName]", siteName);
|
||||
var notification = new Notification(_tenantManager.GetAlias().SiteId, user, subject, body);
|
||||
_notifications.AddNotification(notification);
|
||||
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Security, "Forgot Username Notification Sent For {Email}", user.Email);
|
||||
return new User { UserId = user.UserId, Username = user.Username, Email = user.Email }; // minimal object
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Forgot Username Notification Failed For {Email}", user.Email);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// email may not be unique
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Forgot Username Notification Failed For {Email}", user.Email);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -588,6 +630,7 @@ namespace Oqtane.Managers
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
public async Task<UserValidateResult> ValidateUser(string username, string email, string password)
|
||||
{
|
||||
var validateResult = new UserValidateResult { Succeeded = true };
|
||||
@ -914,5 +957,49 @@ namespace Oqtane.Managers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<User> SendLoginLink(User user)
|
||||
{
|
||||
try
|
||||
{
|
||||
IdentityUser identityuser = await _identityUserManager.FindByEmailAsync(user.Email);
|
||||
if (identityuser != null)
|
||||
{
|
||||
var token = await _identityUserManager.GenerateTwoFactorTokenAsync(identityuser, "Email");
|
||||
|
||||
var alias = _tenantManager.GetAlias();
|
||||
user = GetUser(identityuser.UserName, alias.SiteId);
|
||||
user.TwoFactorCode = token;
|
||||
user.TwoFactorExpiry = DateTime.UtcNow.AddMinutes(10);
|
||||
_users.UpdateUser(user);
|
||||
|
||||
string url = alias.Protocol + alias.Name + "/pages/loginlink?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
|
||||
string siteName = _sites.GetSite(alias.SiteId).Name;
|
||||
string subject = _localizer["LoginLinkEmailSubject"];
|
||||
subject = subject.Replace("[SiteName]", siteName);
|
||||
string body = _localizer["LoginLinkEmailBody"].Value;
|
||||
body = body.Replace("[UserDisplayName]", user.DisplayName);
|
||||
body = body.Replace("[URL]", url);
|
||||
body = body.Replace("[SiteName]", siteName);
|
||||
var notification = new Notification(_tenantManager.GetAlias().SiteId, user, subject, body);
|
||||
_notifications.AddNotification(notification);
|
||||
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Security, "Login Link Notification Sent To {Email}", user.Email);
|
||||
return new User { UserId = user.UserId, Username = user.Username, Email = user.Email }; // minimal object
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Login Link Notification Failed For {Email}", user.Email);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// email may not be unique
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Login Link Notification Failed For {Email}", user.Email);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
3
Oqtane.Server/Pages/LoginLink.cshtml
Normal file
3
Oqtane.Server/Pages/LoginLink.cshtml
Normal file
@ -0,0 +1,3 @@
|
||||
@page "/pages/loginlink"
|
||||
@namespace Oqtane.Pages
|
||||
@model Oqtane.Pages.LoginLinkModel
|
||||
69
Oqtane.Server/Pages/LoginLink.cshtml.cs
Normal file
69
Oqtane.Server/Pages/LoginLink.cshtml.cs
Normal file
@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Oqtane.Enums;
|
||||
using Oqtane.Extensions;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Managers;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Pages
|
||||
{
|
||||
[AllowAnonymous]
|
||||
public class LoginLinkModel : PageModel
|
||||
{
|
||||
private readonly UserManager<IdentityUser> _identityUserManager;
|
||||
private readonly SignInManager<IdentityUser> _identitySignInManager;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly ILogManager _logger;
|
||||
|
||||
public LoginLinkModel(UserManager<IdentityUser> identityUserManager, SignInManager<IdentityUser> identitySignInManager, IUserManager userManager, ILogManager logger)
|
||||
{
|
||||
_identityUserManager = identityUserManager;
|
||||
_identitySignInManager = identitySignInManager;
|
||||
_userManager = userManager;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnGetAsync(string name, string token)
|
||||
{
|
||||
var returnurl = "/login";
|
||||
|
||||
if (bool.Parse(HttpContext.GetSiteSettings().GetValue("LoginOptions:LoginLink", "false")) &&
|
||||
!User.Identity.IsAuthenticated && !string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(token))
|
||||
{
|
||||
var validuser = false;
|
||||
|
||||
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(name);
|
||||
if (identityuser != null)
|
||||
{
|
||||
var user = _userManager.GetUser(identityuser.UserName, HttpContext.GetAlias().SiteId);
|
||||
if (user != null && user.TwoFactorCode == token && DateTime.UtcNow < user.TwoFactorExpiry)
|
||||
{
|
||||
await _identitySignInManager.SignInAsync(identityuser, false);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Security, "Login Link Successful For User {Username}", name);
|
||||
validuser = true;
|
||||
returnurl = "/";
|
||||
}
|
||||
}
|
||||
|
||||
if (!validuser)
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Login Link Failed For User {Username}", name);
|
||||
returnurl += $"?status={ExternalLoginStatus.LoginLinkFailed}";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Login Link Attempt For User {Username}", name);
|
||||
returnurl = "/";
|
||||
}
|
||||
|
||||
return LocalRedirect(Url.Content("~" + returnurl));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -121,7 +121,19 @@
|
||||
<value>Dear [UserDisplayName]<br><br>You recently requested to reset your password. Please use the link below to complete the process: <b><a href="[URL]"><br><br>Click here to Reset Password</a></b><br><br>Please note that the link is only valid for 24 hours so if you are unable to take action within that time period, you should initiate another password reset on the site.<br><br>If you did not request to reset your password you can safely ignore this message.<br><br>Thank You!<br>[SiteName] Team</value>
|
||||
</data>
|
||||
<data name="ForgotPasswordEmailSubject" xml:space="preserve">
|
||||
<value>Password Reset Notification Sent For [SiteName]</value>
|
||||
<value>Password Reset Notification For [SiteName]</value>
|
||||
</data>
|
||||
<data name="ForgotUsernameEmailBody" xml:space="preserve">
|
||||
<value>Dear [UserDisplayName]<br><br>You recently requested a username reminder. Please use the link below to complete the process: <b><a href="[URL]"><br><br>Click here to Login</a></b><br><br>If you did not request a username reminder you can safely ignore this message.<br><br>Thank You!<br>[SiteName] Team</value>
|
||||
</data>
|
||||
<data name="ForgotUsernameEmailSubject" xml:space="preserve">
|
||||
<value>Forgotten Username Reminder For [SiteName]</value>
|
||||
</data>
|
||||
<data name="LoginLinkEmailBody" xml:space="preserve">
|
||||
<value>Dear [UserDisplayName]<br><br>You recently requested a login link. Please use the link below to complete the process: <b><a href="[URL]"><br><br>Click here to Login</a></b><br><br>Please note that the link is only valid for 10 minutes so if you are unable to take action within that time period, you should initiate another login link request on the site.<br><br>If you did not request a login link you can safely ignore this message.<br><br>Thank You!<br>[SiteName] Team</value>
|
||||
</data>
|
||||
<data name="LoginLinkEmailSubject" xml:space="preserve">
|
||||
<value>Login Link Notification For [SiteName]</value>
|
||||
</data>
|
||||
<data name="NoVerificationEmailBody" xml:space="preserve">
|
||||
<value>Dear [UserDisplayName],<br><br>A user account has been successfully created for you with the username <b>[Username]</b>. Please <b><a href="[URL]">click here to login</a></b>. If you do not know your password, use the forgot password option on the login page to reset your account.<br><br>Thank You!<br>[SiteName] Team</value>
|
||||
|
||||
Reference in New Issue
Block a user