Skip to content

Commit

Permalink
Add two-factor authentication (#13704)
Browse files Browse the repository at this point in the history
  • Loading branch information
MikeAlhayek committed May 18, 2023
1 parent b5efeb5 commit e295fae
Show file tree
Hide file tree
Showing 44 changed files with 2,947 additions and 72 deletions.
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

0 comments on commit e295fae

Please sign in to comment.