Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add two-factor authentication #13704

Merged
merged 14 commits into from
May 18, 2023
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;

namespace OrchardCore.Roles.ViewModels;

public class RoleLoginSettingsViewModel
{
public bool EnableTwoFactorAuthenticationForSpecificRoles { get; set; }

public RoleEntry[] Roles { get; set; } = Array.Empty<RoleEntry>();
}
6 changes: 6 additions & 0 deletions src/OrchardCore.Modules/OrchardCore.Users/Assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,11 @@
"Assets/js/password-generator.js"
],
"output": "wwwroot/Scripts/password-generator.js"
},
{
"inputs": [
"node_modules/qrcodejs/qrcode.js"
],
"output": "wwwroot/Scripts/qrcode.js"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using OrchardCore.Users.Models;
using OrchardCore.Workflows.Services;

namespace OrchardCore.Users.Controllers;

public class AccountBaseController : Controller
{
protected readonly UserManager<IUser> _userManager;

public AccountBaseController(UserManager<IUser> userManager)
{
_userManager = userManager;
}

protected async Task<IActionResult> LoggedInActionResult(IUser user, string returnUrl = null, ExternalLoginInfo info = null)
{
var workflowManager = HttpContext.RequestServices.GetService<IWorkflowManager>();
if (workflowManager != null && user is User u)
{
var input = new Dictionary<string, object>
{
["UserName"] = user.UserName,
["ExternalClaims"] = info?.Principal?.GetSerializableClaims() ?? Enumerable.Empty<SerializableClaim>(),
["Roles"] = u.RoleNames,
["Provider"] = info?.LoginProvider
};
await workflowManager.TriggerEventAsync(nameof(Workflows.Activities.UserLoggedInEvent),
input: input, correlationId: u.UserId);
}

return RedirectToLocal(returnUrl);
}

protected IActionResult RedirectToLocal(string returnUrl)
{
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl.ToUriComponents());
}

return Redirect("~/");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using OrchardCore.DisplayManagement.Notify;
Expand All @@ -23,19 +22,17 @@
using OrchardCore.Users.Models;
using OrchardCore.Users.Services;
using OrchardCore.Users.ViewModels;
using IWorkflowManager = OrchardCore.Workflows.Services.IWorkflowManager;
using SignInResult = Microsoft.AspNetCore.Identity.SignInResult;

namespace OrchardCore.Users.Controllers
{
[Authorize]
public class AccountController : Controller
public class AccountController : AccountBaseController
{
public const string DefaultExternalLoginProtector = "DefaultExternalLogin";

private readonly IUserService _userService;
private readonly SignInManager<IUser> _signInManager;
private readonly UserManager<IUser> _userManager;
private readonly ILogger _logger;
private readonly ISiteService _siteService;
private readonly IEnumerable<ILoginFormEvent> _accountEvents;
Expand All @@ -61,9 +58,9 @@ public AccountController(
IDistributedCache distributedCache,
IDataProtectionProvider dataProtectionProvider,
IEnumerable<IExternalLoginEventHandler> externalLoginHandlers)
: base(userManager)
{
_signInManager = signInManager;
_userManager = userManager;
_userService = userService;
_logger = logger;
_siteService = siteService;
Expand Down Expand Up @@ -219,6 +216,17 @@ public async Task<IActionResult> Login(LoginViewModel model, string returnUrl =
}
}

if (result.RequiresTwoFactor)
{
return RedirectToAction(nameof(TwoFactorAuthenticationController.LoginWith2FA),
typeof(TwoFactorAuthenticationController).ControllerName(),
new
{
returnUrl,
model.RememberMe
});
}

if (result.IsLockedOut)
{
ModelState.AddModelError(String.Empty, S["The account is locked out"]);
Expand Down Expand Up @@ -298,35 +306,6 @@ private void AddIdentityErrors(IdentityResult result)
}
}

private IActionResult RedirectToLocal(string returnUrl)
{
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl.ToUriComponents());
}

return Redirect("~/");
}

private async Task<IActionResult> LoggedInActionResult(IUser user, string returnUrl = null, ExternalLoginInfo info = null)
{
var workflowManager = HttpContext.RequestServices.GetService<IWorkflowManager>();
if (workflowManager != null && user is User u)
{
var input = new Dictionary<string, object>
{
["UserName"] = user.UserName,
["ExternalClaims"] = info?.Principal?.GetSerializableClaims() ?? Enumerable.Empty<SerializableClaim>(),
["Roles"] = u.RoleNames,
["Provider"] = info?.LoginProvider
};
await workflowManager.TriggerEventAsync(nameof(Workflows.Activities.UserLoggedInEvent),
input: input, correlationId: u.UserId);
}

return RedirectToLocal(returnUrl);
}

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
Expand Down
Loading