Migrated to .NET Core 2.

This commit is contained in:
JudahGabriel
2017-08-29 15:42:24 -05:00
parent 2d94f69369
commit 9364d2d394
92 changed files with 1472 additions and 1204 deletions
+6 -11
View File
@@ -10,11 +10,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{DCA799
.nuget\NuGet.targets = .nuget\NuGet.targets
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{B730B0AD-FB9B-4768-BC78-B099DD77A876}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RavenDB.Identity", "RavenDB.Identity\RavenDB.Identity.csproj", "{C7824FBF-9E9B-40B5-9CF5-4884F8378BA6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample", "Sample\Sample.csproj", "{BAF1679A-0D3F-4373-B3E4-7C1C78EFF3E2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RavenDB.Identity", "RavenDB.Identity\RavenDB.Identity.csproj", "{C7824FBF-9E9B-40B5-9CF5-4884F8378BA6}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample", "Sample\Sample.csproj", "{35105AF1-C737-4F5B-BF31-1889100BE511}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -22,21 +20,18 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{BAF1679A-0D3F-4373-B3E4-7C1C78EFF3E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BAF1679A-0D3F-4373-B3E4-7C1C78EFF3E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BAF1679A-0D3F-4373-B3E4-7C1C78EFF3E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BAF1679A-0D3F-4373-B3E4-7C1C78EFF3E2}.Release|Any CPU.Build.0 = Release|Any CPU
{C7824FBF-9E9B-40B5-9CF5-4884F8378BA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C7824FBF-9E9B-40B5-9CF5-4884F8378BA6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C7824FBF-9E9B-40B5-9CF5-4884F8378BA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C7824FBF-9E9B-40B5-9CF5-4884F8378BA6}.Release|Any CPU.Build.0 = Release|Any CPU
{35105AF1-C737-4F5B-BF31-1889100BE511}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{35105AF1-C737-4F5B-BF31-1889100BE511}.Debug|Any CPU.Build.0 = Debug|Any CPU
{35105AF1-C737-4F5B-BF31-1889100BE511}.Release|Any CPU.ActiveCfg = Release|Any CPU
{35105AF1-C737-4F5B-BF31-1889100BE511}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{BAF1679A-0D3F-4373-B3E4-7C1C78EFF3E2} = {B730B0AD-FB9B-4768-BC78-B099DD77A876}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {10F0E164-7506-4DA6-92BE-EA5E531FC11D}
EndGlobalSection
+7 -7
View File
@@ -3,11 +3,11 @@
<PropertyGroup>
<Description>The simple and easy RavenDB Identity provider for ASP.NET Core. Use Raven to store your users and logins. As simple as services.AddRavenDbIdentity&lt;AppUser&gt;();</Description>
<Authors>Judah Gabriel Himango</Authors>
<TargetFrameworks>netstandard1.6</TargetFrameworks>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AssemblyName>RavenDb.Identity</AssemblyName>
<AssemblyName>RavenDB.Identity</AssemblyName>
<Title>RavenDB Identity for ASP.NET Core</Title>
<PackageId>RavenDb.Identity</PackageId>
<PackageId>RavenDB.Identity</PackageId>
<PackageTags>ravendb;identity;aspnetcore;membership;authentication;nosql;authorization</PackageTags>
<PackageProjectUrl>https://github.com/JudahGabriel/RavenDB.Identity</PackageProjectUrl>
<RepositoryType>git</RepositoryType>
@@ -18,14 +18,14 @@
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<Version>1.0.3</Version>
<Version>2.0.2</Version>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.1" />
<PackageReference Include="RavenDB.Client" Version="3.5.4" />
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
<PackageReference Include="RavenDB.Client" Version="3.5.5-patch-35218" />
</ItemGroup>
</Project>
+15 -1
View File
@@ -13,10 +13,24 @@ using Microsoft.Extensions.Logging;
namespace RavenDB.Identity
{
/// <summary>
///
/// </summary>
/// <typeparam name="TRole"></typeparam>
public class RoleMan<TRole> : RoleManager<TRole>
where TRole : class
{
public RoleMan(IRoleStore<TRole> store, IEnumerable<IRoleValidator<TRole>> roleValidators, ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors, ILogger<RoleManager<TRole>> logger, IHttpContextAccessor contextAccessor) : base(store, roleValidators, keyNormalizer, errors, logger, contextAccessor)
/// <summary>
///
/// </summary>
/// <param name="store"></param>
/// <param name="roleValidators"></param>
/// <param name="keyNormalizer"></param>
/// <param name="errors"></param>
/// <param name="logger"></param>
/// <param name="contextAccessor"></param>
public RoleMan(IRoleStore<TRole> store, IEnumerable<IRoleValidator<TRole>> roleValidators, ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors, ILogger<RoleManager<TRole>> logger, IHttpContextAccessor contextAccessor) :
base(store, roleValidators, keyNormalizer, errors, logger)
{
}
}
@@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Raven.Client;
using Raven.Client.Document;
using System;
@@ -84,10 +85,13 @@ namespace RavenDB.Identity
public static IServiceCollection AddRavenDbIdentity<TUser>(this IServiceCollection services)
where TUser : IdentityUser
{
services.AddIdentity<TUser, IdentityRole>(); // Adds the AspNet identity system to work with our RavenDB identity objects.
// Add the AspNet identity system to work with our RavenDB identity objects.
services.AddIdentity<TUser, IdentityRole>()
.AddDefaultTokenProviders(); // options => options.Tokens.ProviderMap.Add("Default", new TokenProviderDescriptor(typeof(UserStore<TUser>)))
services.AddScoped<Microsoft.AspNetCore.Identity.IUserStore<TUser>, UserStore<TUser>>();
services.AddScoped<Microsoft.AspNetCore.Identity.IRoleStore<IdentityRole>, RoleStore<IdentityRole>>();
return services;
}
}
+172 -190
View File
@@ -3,62 +3,57 @@ using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Sample.Web.NetCore.Models;
using Sample.Web.NetCore.Models.AccountViewModels;
using Sample.Web.NetCore.Services;
using Sample.Models;
using Sample.Models.AccountViewModels;
using Sample.Services;
using Raven.Client;
namespace Sample.Web.NetCore.Controllers
namespace Sample.Controllers
{
[Authorize]
[Route("[controller]/[action]")]
public class AccountController : RavenController
{
private readonly UserManager<AppUser> _userManager;
private readonly SignInManager<AppUser> _signInManager;
private readonly IEmailSender _emailSender;
private readonly ISmsSender _smsSender;
private readonly ILogger _logger;
private readonly string _externalCookieScheme;
public AccountController(
UserManager<AppUser> userManager,
SignInManager<AppUser> signInManager,
IOptions<IdentityCookieOptions> identityCookieOptions,
IEmailSender emailSender,
ISmsSender smsSender,
ILoggerFactory loggerFactory,
ILogger<AccountController> logger,
IAsyncDocumentSession dbSession) // Grab the RavenDB document session so that we can pass it to our base RavenController class.
: base(dbSession) // Pass the RavenDB doc session to the base controller. Now we can access Raven via .DbSession.
: base(dbSession) // Pass the RavenDB doc session to the base controller. We can access the Raven session via this.DbSession
{
_userManager = userManager;
_signInManager = signInManager;
_externalCookieScheme = identityCookieOptions.Value.ExternalCookieAuthenticationScheme;
_emailSender = emailSender;
_smsSender = smsSender;
_logger = loggerFactory.CreateLogger<AccountController>();
_logger = logger;
}
//
// GET: /Account/Login
[TempData]
public string ErrorMessage { get; set; }
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> Login(string returnUrl = null)
{
// Clear the existing external cookie to ensure a clean login process
await HttpContext.Authentication.SignOutAsync(_externalCookieScheme);
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
ViewData["ReturnUrl"] = returnUrl;
return View();
}
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
@@ -72,17 +67,17 @@ namespace Sample.Web.NetCore.Controllers
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation(1, "User logged in.");
_logger.LogInformation("User logged in.");
return RedirectToLocal(returnUrl);
}
if (result.RequiresTwoFactor)
{
return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
return RedirectToAction(nameof(LoginWith2fa), new { returnUrl, model.RememberMe });
}
if (result.IsLockedOut)
{
_logger.LogWarning(2, "User account locked out.");
return View("Lockout");
_logger.LogWarning("User account locked out.");
return RedirectToAction(nameof(Lockout));
}
else
{
@@ -95,8 +90,123 @@ namespace Sample.Web.NetCore.Controllers
return View(model);
}
//
// GET: /Account/Register
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> LoginWith2fa(bool rememberMe, string returnUrl = null)
{
// Ensure the user has gone through the username & password screen first
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
{
throw new ApplicationException($"Unable to load two-factor authentication user.");
}
var model = new LoginWith2faViewModel { RememberMe = rememberMe };
ViewData["ReturnUrl"] = returnUrl;
return View(model);
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> LoginWith2fa(LoginWith2faViewModel model, bool rememberMe, string returnUrl = null)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var authenticatorCode = model.TwoFactorCode.Replace(" ", string.Empty).Replace("-", string.Empty);
var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, rememberMe, model.RememberMachine);
if (result.Succeeded)
{
_logger.LogInformation("User with ID {UserId} logged in with 2fa.", user.Id);
return RedirectToLocal(returnUrl);
}
else if (result.IsLockedOut)
{
_logger.LogWarning("User with ID {UserId} account locked out.", user.Id);
return RedirectToAction(nameof(Lockout));
}
else
{
_logger.LogWarning("Invalid authenticator code entered for user with ID {UserId}.", user.Id);
ModelState.AddModelError(string.Empty, "Invalid authenticator code.");
return View();
}
}
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> LoginWithRecoveryCode(string returnUrl = null)
{
// Ensure the user has gone through the username & password screen first
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
{
throw new ApplicationException($"Unable to load two-factor authentication user.");
}
ViewData["ReturnUrl"] = returnUrl;
return View();
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> LoginWithRecoveryCode(LoginWithRecoveryCodeViewModel model, string returnUrl = null)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
{
throw new ApplicationException($"Unable to load two-factor authentication user.");
}
var recoveryCode = model.RecoveryCode.Replace(" ", string.Empty);
var result = await _signInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode);
if (result.Succeeded)
{
_logger.LogInformation("User with ID {UserId} logged in with a recovery code.", user.Id);
return RedirectToLocal(returnUrl);
}
if (result.IsLockedOut)
{
_logger.LogWarning("User with ID {UserId} account locked out.", user.Id);
return RedirectToAction(nameof(Lockout));
}
else
{
_logger.LogWarning("Invalid recovery code entered for user with ID {UserId}", user.Id);
ModelState.AddModelError(string.Empty, "Invalid recovery code entered.");
return View();
}
}
[HttpGet]
[AllowAnonymous]
public IActionResult Lockout()
{
return View();
}
[HttpGet]
[AllowAnonymous]
public IActionResult Register(string returnUrl = null)
@@ -105,8 +215,6 @@ namespace Sample.Web.NetCore.Controllers
return View();
}
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
@@ -115,22 +223,18 @@ namespace Sample.Web.NetCore.Controllers
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var user = new AppUser
{
UserName = model.Email,
Email = model.Email
};
var user = new AppUser { UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
// For more information on how to enable account confirmation and password reset please visit https://go.microsoft.com/fwlink/?LinkID=532713
// Send an email with this link
//var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
//var callbackUrl = Url.Action(nameof(ConfirmEmail), "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
//await _emailSender.SendEmailAsync(model.Email, "Confirm your account",
// $"Please confirm your account by clicking this link: <a href='{callbackUrl}'>link</a>");
_logger.LogInformation("User created a new account with password.");
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
await _emailSender.SendEmailConfirmationAsync(model.Email, callbackUrl);
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation(3, "User created a new account with password.");
_logger.LogInformation("User created a new account with password.");
return RedirectToLocal(returnUrl);
}
AddErrors(result);
@@ -140,40 +244,34 @@ namespace Sample.Web.NetCore.Controllers
return View(model);
}
//
// POST: /Account/Logout
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Logout()
{
await _signInManager.SignOutAsync();
_logger.LogInformation(4, "User logged out.");
_logger.LogInformation("User logged out.");
return RedirectToAction(nameof(HomeController.Index), "Home");
}
//
// POST: /Account/ExternalLogin
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public IActionResult ExternalLogin(string provider, string returnUrl = null)
{
// Request a redirect to the external login provider.
var redirectUrl = Url.Action(nameof(ExternalLoginCallback), "Account", new { ReturnUrl = returnUrl });
var redirectUrl = Url.Action(nameof(ExternalLoginCallback), "Account", new { returnUrl });
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return Challenge(properties, provider);
}
//
// GET: /Account/ExternalLoginCallback
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
{
if (remoteError != null)
{
ModelState.AddModelError(string.Empty, $"Error from external provider: {remoteError}");
return View(nameof(Login));
ErrorMessage = $"Error from external provider: {remoteError}";
return RedirectToAction(nameof(Login));
}
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
@@ -182,19 +280,15 @@ namespace Sample.Web.NetCore.Controllers
}
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false);
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
if (result.Succeeded)
{
_logger.LogInformation(5, "User logged in with {Name} provider.", info.LoginProvider);
_logger.LogInformation("User logged in with {Name} provider.", info.LoginProvider);
return RedirectToLocal(returnUrl);
}
if (result.RequiresTwoFactor)
{
return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl });
}
if (result.IsLockedOut)
{
return View("Lockout");
return RedirectToAction(nameof(Lockout));
}
else
{
@@ -202,16 +296,14 @@ namespace Sample.Web.NetCore.Controllers
ViewData["ReturnUrl"] = returnUrl;
ViewData["LoginProvider"] = info.LoginProvider;
var email = info.Principal.FindFirstValue(ClaimTypes.Email);
return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = email });
return View("ExternalLogin", new ExternalLoginViewModel { Email = email });
}
}
//
// POST: /Account/ExternalLoginConfirmation
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl = null)
public async Task<IActionResult> ExternalLoginConfirmation(ExternalLoginViewModel model, string returnUrl = null)
{
if (ModelState.IsValid)
{
@@ -219,7 +311,7 @@ namespace Sample.Web.NetCore.Controllers
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
return View("ExternalLoginFailure");
throw new ApplicationException("Error loading external login information during confirmation.");
}
var user = new AppUser { UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(user);
@@ -229,7 +321,7 @@ namespace Sample.Web.NetCore.Controllers
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation(6, "User created an account using {Name} provider.", info.LoginProvider);
_logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
return RedirectToLocal(returnUrl);
}
}
@@ -237,29 +329,26 @@ namespace Sample.Web.NetCore.Controllers
}
ViewData["ReturnUrl"] = returnUrl;
return View(model);
return View(nameof(ExternalLogin), model);
}
// GET: /Account/ConfirmEmail
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> ConfirmEmail(string userId, string code)
{
if (userId == null || code == null)
{
return View("Error");
return RedirectToAction(nameof(HomeController.Index), "Home");
}
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
return View("Error");
throw new ApplicationException($"Unable to load user with ID '{userId}'.");
}
var result = await _userManager.ConfirmEmailAsync(user, code);
return View(result.Succeeded ? "ConfirmEmail" : "Error");
}
//
// GET: /Account/ForgotPassword
[HttpGet]
[AllowAnonymous]
public IActionResult ForgotPassword()
@@ -267,8 +356,6 @@ namespace Sample.Web.NetCore.Controllers
return View();
}
//
// POST: /Account/ForgotPassword
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
@@ -280,24 +367,22 @@ namespace Sample.Web.NetCore.Controllers
if (user == null || !(await _userManager.IsEmailConfirmedAsync(user)))
{
// Don't reveal that the user does not exist or is not confirmed
return View("ForgotPasswordConfirmation");
return RedirectToAction(nameof(ForgotPasswordConfirmation));
}
// For more information on how to enable account confirmation and password reset please visit https://go.microsoft.com/fwlink/?LinkID=532713
// Send an email with this link
//var code = await _userManager.GeneratePasswordResetTokenAsync(user);
//var callbackUrl = Url.Action(nameof(ResetPassword), "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
//await _emailSender.SendEmailAsync(model.Email, "Reset Password",
// $"Please reset your password by clicking here: <a href='{callbackUrl}'>link</a>");
//return View("ForgotPasswordConfirmation");
// For more information on how to enable account confirmation and password reset please
// visit https://go.microsoft.com/fwlink/?LinkID=532713
var code = await _userManager.GeneratePasswordResetTokenAsync(user);
var callbackUrl = Url.ResetPasswordCallbackLink(user.Id, code, Request.Scheme);
await _emailSender.SendEmailAsync(model.Email, "Reset Password",
$"Please reset your password by clicking here: <a href='{callbackUrl}'>link</a>");
return RedirectToAction(nameof(ForgotPasswordConfirmation));
}
// If we got this far, something failed, redisplay form
return View(model);
}
//
// GET: /Account/ForgotPasswordConfirmation
[HttpGet]
[AllowAnonymous]
public IActionResult ForgotPasswordConfirmation()
@@ -305,17 +390,18 @@ namespace Sample.Web.NetCore.Controllers
return View();
}
//
// GET: /Account/ResetPassword
[HttpGet]
[AllowAnonymous]
public IActionResult ResetPassword(string code = null)
{
return code == null ? View("Error") : View();
if (code == null)
{
throw new ApplicationException("A code must be supplied for password reset.");
}
var model = new ResetPasswordViewModel { Code = code };
return View(model);
}
//
// POST: /Account/ResetPassword
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
@@ -329,19 +415,17 @@ namespace Sample.Web.NetCore.Controllers
if (user == null)
{
// Don't reveal that the user does not exist
return RedirectToAction(nameof(AccountController.ResetPasswordConfirmation), "Account");
return RedirectToAction(nameof(ResetPasswordConfirmation));
}
var result = await _userManager.ResetPasswordAsync(user, model.Code, model.Password);
if (result.Succeeded)
{
return RedirectToAction(nameof(AccountController.ResetPasswordConfirmation), "Account");
return RedirectToAction(nameof(ResetPasswordConfirmation));
}
AddErrors(result);
return View();
}
//
// GET: /Account/ResetPasswordConfirmation
[HttpGet]
[AllowAnonymous]
public IActionResult ResetPasswordConfirmation()
@@ -349,109 +433,7 @@ namespace Sample.Web.NetCore.Controllers
return View();
}
//
// GET: /Account/SendCode
[HttpGet]
[AllowAnonymous]
public async Task<ActionResult> SendCode(string returnUrl = null, bool rememberMe = false)
{
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
{
return View("Error");
}
var userFactors = await _userManager.GetValidTwoFactorProvidersAsync(user);
var factorOptions = userFactors.Select(purpose => new SelectListItem { Text = purpose, Value = purpose }).ToList();
return View(new SendCodeViewModel { Providers = factorOptions, ReturnUrl = returnUrl, RememberMe = rememberMe });
}
//
// POST: /Account/SendCode
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SendCode(SendCodeViewModel model)
{
if (!ModelState.IsValid)
{
return View();
}
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
{
return View("Error");
}
// Generate the token and send it
var code = await _userManager.GenerateTwoFactorTokenAsync(user, model.SelectedProvider);
if (string.IsNullOrWhiteSpace(code))
{
return View("Error");
}
var message = "Your security code is: " + code;
if (model.SelectedProvider == "Email")
{
await _emailSender.SendEmailAsync(await _userManager.GetEmailAsync(user), "Security Code", message);
}
else if (model.SelectedProvider == "Phone")
{
await _smsSender.SendSmsAsync(await _userManager.GetPhoneNumberAsync(user), message);
}
return RedirectToAction(nameof(VerifyCode), new { Provider = model.SelectedProvider, ReturnUrl = model.ReturnUrl, RememberMe = model.RememberMe });
}
//
// GET: /Account/VerifyCode
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> VerifyCode(string provider, bool rememberMe, string returnUrl = null)
{
// Require that the user has already logged in via username/password or external login
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
{
return View("Error");
}
return View(new VerifyCodeViewModel { Provider = provider, ReturnUrl = returnUrl, RememberMe = rememberMe });
}
//
// POST: /Account/VerifyCode
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> VerifyCode(VerifyCodeViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
// The following code protects for brute force attacks against the two factor codes.
// If a user enters incorrect codes for a specified amount of time then the user account
// will be locked out for a specified amount of time.
var result = await _signInManager.TwoFactorSignInAsync(model.Provider, model.Code, model.RememberMe, model.RememberBrowser);
if (result.Succeeded)
{
return RedirectToLocal(model.ReturnUrl);
}
if (result.IsLockedOut)
{
_logger.LogWarning(7, "User account locked out.");
return View("Lockout");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid code.");
return View(model);
}
}
//
// GET: /Account/AccessDenied
[HttpGet]
public IActionResult AccessDenied()
{
+3 -2
View File
@@ -1,12 +1,13 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Sample.Models;
using Raven.Client;
using Sample.Web.NetCore.Models;
namespace Sample.Web.NetCore.Controllers
namespace Sample.Controllers
{
public class HomeController : RavenController
{
+368 -236
View File
@@ -1,223 +1,153 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Sample.Web.NetCore.Models;
using Sample.Web.NetCore.Models.ManageViewModels;
using Sample.Web.NetCore.Services;
using Sample.Models;
using Sample.Models.ManageViewModels;
using Sample.Services;
namespace Sample.Web.NetCore.Controllers
namespace Sample.Controllers
{
[Authorize]
[Route("[controller]/[action]")]
public class ManageController : Controller
{
private readonly UserManager<AppUser> _userManager;
private readonly SignInManager<AppUser> _signInManager;
private readonly string _externalCookieScheme;
private readonly IEmailSender _emailSender;
private readonly ISmsSender _smsSender;
private readonly ILogger _logger;
private readonly UrlEncoder _urlEncoder;
private const string AuthenicatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6";
public ManageController(
UserManager<AppUser> userManager,
SignInManager<AppUser> signInManager,
IOptions<IdentityCookieOptions> identityCookieOptions,
IEmailSender emailSender,
ISmsSender smsSender,
ILoggerFactory loggerFactory)
ILogger<ManageController> logger,
UrlEncoder urlEncoder)
{
_userManager = userManager;
_signInManager = signInManager;
_externalCookieScheme = identityCookieOptions.Value.ExternalCookieAuthenticationScheme;
_emailSender = emailSender;
_smsSender = smsSender;
_logger = loggerFactory.CreateLogger<ManageController>();
_logger = logger;
_urlEncoder = urlEncoder;
}
//
// GET: /Manage/Index
[HttpGet]
public async Task<IActionResult> Index(ManageMessageId? message = null)
{
ViewData["StatusMessage"] =
message == ManageMessageId.ChangePasswordSuccess ? "Your password has been changed."
: message == ManageMessageId.SetPasswordSuccess ? "Your password has been set."
: message == ManageMessageId.SetTwoFactorSuccess ? "Your two-factor authentication provider has been set."
: message == ManageMessageId.Error ? "An error has occurred."
: message == ManageMessageId.AddPhoneSuccess ? "Your phone number was added."
: message == ManageMessageId.RemovePhoneSuccess ? "Your phone number was removed."
: "";
[TempData]
public string StatusMessage { get; set; }
var user = await GetCurrentUserAsync();
[HttpGet]
public async Task<IActionResult> Index()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return View("Error");
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var model = new IndexViewModel
{
HasPassword = await _userManager.HasPasswordAsync(user),
PhoneNumber = await _userManager.GetPhoneNumberAsync(user),
TwoFactor = await _userManager.GetTwoFactorEnabledAsync(user),
Logins = await _userManager.GetLoginsAsync(user),
BrowserRemembered = await _signInManager.IsTwoFactorClientRememberedAsync(user)
Username = user.UserName,
Email = user.Email,
PhoneNumber = user.PhoneNumber,
IsEmailConfirmed = user.IsEmailConfirmed,
StatusMessage = StatusMessage
};
return View(model);
}
//
// POST: /Manage/RemoveLogin
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RemoveLogin(RemoveLoginViewModel account)
{
ManageMessageId? message = ManageMessageId.Error;
var user = await GetCurrentUserAsync();
if (user != null)
{
var result = await _userManager.RemoveLoginAsync(user, account.LoginProvider, account.ProviderKey);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
message = ManageMessageId.RemoveLoginSuccess;
}
}
return RedirectToAction(nameof(ManageLogins), new { Message = message });
}
//
// GET: /Manage/AddPhoneNumber
public IActionResult AddPhoneNumber()
{
return View();
}
//
// POST: /Manage/AddPhoneNumber
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddPhoneNumber(AddPhoneNumberViewModel model)
public async Task<IActionResult> Index(IndexViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
// Generate the token and send it
var user = await GetCurrentUserAsync();
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return View("Error");
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var code = await _userManager.GenerateChangePhoneNumberTokenAsync(user, model.PhoneNumber);
await _smsSender.SendSmsAsync(model.PhoneNumber, "Your security code is: " + code);
return RedirectToAction(nameof(VerifyPhoneNumber), new { PhoneNumber = model.PhoneNumber });
var email = user.Email;
if (model.Email != email)
{
var setEmailResult = await _userManager.SetEmailAsync(user, model.Email);
if (!setEmailResult.Succeeded)
{
throw new ApplicationException($"Unexpected error occurred setting email for user with ID '{user.Id}'.");
}
}
var phoneNumber = user.PhoneNumber;
if (model.PhoneNumber != phoneNumber)
{
var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, model.PhoneNumber);
if (!setPhoneResult.Succeeded)
{
throw new ApplicationException($"Unexpected error occurred setting phone number for user with ID '{user.Id}'.");
}
}
StatusMessage = "Your profile has been updated";
return RedirectToAction(nameof(Index));
}
//
// POST: /Manage/EnableTwoFactorAuthentication
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EnableTwoFactorAuthentication()
{
var user = await GetCurrentUserAsync();
if (user != null)
{
await _userManager.SetTwoFactorEnabledAsync(user, true);
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation(1, "User enabled two-factor authentication.");
}
return RedirectToAction(nameof(Index), "Manage");
}
//
// POST: /Manage/DisableTwoFactorAuthentication
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DisableTwoFactorAuthentication()
{
var user = await GetCurrentUserAsync();
if (user != null)
{
await _userManager.SetTwoFactorEnabledAsync(user, false);
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation(2, "User disabled two-factor authentication.");
}
return RedirectToAction(nameof(Index), "Manage");
}
//
// GET: /Manage/VerifyPhoneNumber
[HttpGet]
public async Task<IActionResult> VerifyPhoneNumber(string phoneNumber)
{
var user = await GetCurrentUserAsync();
if (user == null)
{
return View("Error");
}
var code = await _userManager.GenerateChangePhoneNumberTokenAsync(user, phoneNumber);
// Send an SMS to verify the phone number
return phoneNumber == null ? View("Error") : View(new VerifyPhoneNumberViewModel { PhoneNumber = phoneNumber });
}
//
// POST: /Manage/VerifyPhoneNumber
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> VerifyPhoneNumber(VerifyPhoneNumberViewModel model)
public async Task<IActionResult> SendVerificationEmail(IndexViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await GetCurrentUserAsync();
if (user != null)
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
var result = await _userManager.ChangePhoneNumberAsync(user, model.PhoneNumber, model.Code);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
return RedirectToAction(nameof(Index), new { Message = ManageMessageId.AddPhoneSuccess });
}
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
// If we got this far, something failed, redisplay the form
ModelState.AddModelError(string.Empty, "Failed to verify phone number");
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
var email = user.Email;
await _emailSender.SendEmailConfirmationAsync(email, callbackUrl);
StatusMessage = "Verification email sent. Please check your email.";
return RedirectToAction(nameof(Index));
}
[HttpGet]
public async Task<IActionResult> ChangePassword()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var hasPassword = await _userManager.HasPasswordAsync(user);
if (!hasPassword)
{
return RedirectToAction(nameof(SetPassword));
}
var model = new ChangePasswordViewModel { StatusMessage = StatusMessage };
return View(model);
}
//
// POST: /Manage/RemovePhoneNumber
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RemovePhoneNumber()
{
var user = await GetCurrentUserAsync();
if (user != null)
{
var result = await _userManager.SetPhoneNumberAsync(user, null);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
return RedirectToAction(nameof(Index), new { Message = ManageMessageId.RemovePhoneSuccess });
}
}
return RedirectToAction(nameof(Index), new { Message = ManageMessageId.Error });
}
//
// GET: /Manage/ChangePassword
[HttpGet]
public IActionResult ChangePassword()
{
return View();
}
//
// POST: /Manage/ChangePassword
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ChangePassword(ChangePasswordViewModel model)
@@ -226,32 +156,47 @@ namespace Sample.Web.NetCore.Controllers
{
return View(model);
}
var user = await GetCurrentUserAsync();
if (user != null)
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
var result = await _userManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation(3, "User changed their password successfully.");
return RedirectToAction(nameof(Index), new { Message = ManageMessageId.ChangePasswordSuccess });
}
AddErrors(result);
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var changePasswordResult = await _userManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword);
if (!changePasswordResult.Succeeded)
{
AddErrors(changePasswordResult);
return View(model);
}
return RedirectToAction(nameof(Index), new { Message = ManageMessageId.Error });
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation("User changed their password successfully.");
StatusMessage = "Your password has been changed.";
return RedirectToAction(nameof(ChangePassword));
}
//
// GET: /Manage/SetPassword
[HttpGet]
public IActionResult SetPassword()
public async Task<IActionResult> SetPassword()
{
return View();
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var hasPassword = await _userManager.HasPasswordAsync(user);
if (hasPassword)
{
return RedirectToAction(nameof(ChangePassword));
}
var model = new SetPasswordViewModel { StatusMessage = StatusMessage };
return View(model);
}
//
// POST: /Manage/SetPassword
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SetPassword(SetPasswordViewModel model)
@@ -261,84 +206,262 @@ namespace Sample.Web.NetCore.Controllers
return View(model);
}
var user = await GetCurrentUserAsync();
if (user != null)
{
var result = await _userManager.AddPasswordAsync(user, model.NewPassword);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
return RedirectToAction(nameof(Index), new { Message = ManageMessageId.SetPasswordSuccess });
}
AddErrors(result);
return View(model);
}
return RedirectToAction(nameof(Index), new { Message = ManageMessageId.Error });
}
//GET: /Manage/ManageLogins
[HttpGet]
public async Task<IActionResult> ManageLogins(ManageMessageId? message = null)
{
ViewData["StatusMessage"] =
message == ManageMessageId.RemoveLoginSuccess ? "The external login was removed."
: message == ManageMessageId.AddLoginSuccess ? "The external login was added."
: message == ManageMessageId.Error ? "An error has occurred."
: "";
var user = await GetCurrentUserAsync();
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return View("Error");
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var userLogins = await _userManager.GetLoginsAsync(user);
var otherLogins = _signInManager.GetExternalAuthenticationSchemes().Where(auth => userLogins.All(ul => auth.AuthenticationScheme != ul.LoginProvider)).ToList();
ViewData["ShowRemoveButton"] = user.PasswordHash != null || userLogins.Count > 1;
return View(new ManageLoginsViewModel
var addPasswordResult = await _userManager.AddPasswordAsync(user, model.NewPassword);
if (!addPasswordResult.Succeeded)
{
CurrentLogins = userLogins,
OtherLogins = otherLogins
});
AddErrors(addPasswordResult);
return View(model);
}
await _signInManager.SignInAsync(user, isPersistent: false);
StatusMessage = "Your password has been set.";
return RedirectToAction(nameof(SetPassword));
}
[HttpGet]
public async Task<IActionResult> ExternalLogins()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var model = new ExternalLoginsViewModel { CurrentLogins = await _userManager.GetLoginsAsync(user) };
model.OtherLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync())
.Where(auth => model.CurrentLogins.All(ul => auth.Name != ul.LoginProvider))
.ToList();
model.ShowRemoveButton = await _userManager.HasPasswordAsync(user) || model.CurrentLogins.Count > 1;
model.StatusMessage = StatusMessage;
return View(model);
}
//
// POST: /Manage/LinkLogin
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> LinkLogin(string provider)
{
// Clear the existing external cookie to ensure a clean login process
await HttpContext.Authentication.SignOutAsync(_externalCookieScheme);
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
// Request a redirect to the external login provider to link a login for the current user
var redirectUrl = Url.Action(nameof(LinkLoginCallback), "Manage");
var redirectUrl = Url.Action(nameof(LinkLoginCallback));
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, _userManager.GetUserId(User));
return Challenge(properties, provider);
return new ChallengeResult(provider, properties);
}
//
// GET: /Manage/LinkLoginCallback
[HttpGet]
public async Task<ActionResult> LinkLoginCallback()
public async Task<IActionResult> LinkLoginCallback()
{
var user = await GetCurrentUserAsync();
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return View("Error");
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var info = await _signInManager.GetExternalLoginInfoAsync(await _userManager.GetUserIdAsync(user));
var info = await _signInManager.GetExternalLoginInfoAsync(user.Id);
if (info == null)
{
return RedirectToAction(nameof(ManageLogins), new { Message = ManageMessageId.Error });
throw new ApplicationException($"Unexpected error occurred loading external login info for user with ID '{user.Id}'.");
}
var result = await _userManager.AddLoginAsync(user, info);
var message = ManageMessageId.Error;
if (result.Succeeded)
if (!result.Succeeded)
{
message = ManageMessageId.AddLoginSuccess;
// Clear the existing external cookie to ensure a clean login process
await HttpContext.Authentication.SignOutAsync(_externalCookieScheme);
throw new ApplicationException($"Unexpected error occurred adding external login for user with ID '{user.Id}'.");
}
return RedirectToAction(nameof(ManageLogins), new { Message = message });
// Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
StatusMessage = "The external login was added.";
return RedirectToAction(nameof(ExternalLogins));
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RemoveLogin(RemoveLoginViewModel model)
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var result = await _userManager.RemoveLoginAsync(user, model.LoginProvider, model.ProviderKey);
if (!result.Succeeded)
{
throw new ApplicationException($"Unexpected error occurred removing external login for user with ID '{user.Id}'.");
}
await _signInManager.SignInAsync(user, isPersistent: false);
StatusMessage = "The external login was removed.";
return RedirectToAction(nameof(ExternalLogins));
}
[HttpGet]
public async Task<IActionResult> TwoFactorAuthentication()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var model = new TwoFactorAuthenticationViewModel
{
HasAuthenticator = await _userManager.GetAuthenticatorKeyAsync(user) != null,
Is2faEnabled = user.TwoFactorAuthEnabled,
RecoveryCodesLeft = await _userManager.CountRecoveryCodesAsync(user),
};
return View(model);
}
[HttpGet]
public async Task<IActionResult> Disable2faWarning()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (!user.TwoFactorAuthEnabled)
{
throw new ApplicationException($"Unexpected error occured disabling 2FA for user with ID '{user.Id}'.");
}
return View(nameof(Disable2fa));
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Disable2fa()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var disable2faResult = await _userManager.SetTwoFactorEnabledAsync(user, false);
if (!disable2faResult.Succeeded)
{
throw new ApplicationException($"Unexpected error occured disabling 2FA for user with ID '{user.Id}'.");
}
_logger.LogInformation("User with ID {UserId} has disabled 2fa.", user.Id);
return RedirectToAction(nameof(TwoFactorAuthentication));
}
[HttpGet]
public async Task<IActionResult> EnableAuthenticator()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
if (string.IsNullOrEmpty(unformattedKey))
{
await _userManager.ResetAuthenticatorKeyAsync(user);
unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
}
var model = new EnableAuthenticatorViewModel
{
SharedKey = FormatKey(unformattedKey),
AuthenticatorUri = GenerateQrCodeUri(user.Email, unformattedKey)
};
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EnableAuthenticator(EnableAuthenticatorViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
// Strip spaces and hypens
var verificationCode = model.Code.Replace(" ", string.Empty).Replace("-", string.Empty);
var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync(
user, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode);
if (!is2faTokenValid)
{
ModelState.AddModelError("model.Code", "Verification code is invalid.");
return View(model);
}
await _userManager.SetTwoFactorEnabledAsync(user, true);
_logger.LogInformation("User with ID {UserId} has enabled 2FA with an authenticator app.", user.Id);
return RedirectToAction(nameof(GenerateRecoveryCodes));
}
[HttpGet]
public IActionResult ResetAuthenticatorWarning()
{
return View(nameof(ResetAuthenticator));
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ResetAuthenticator()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
await _userManager.SetTwoFactorEnabledAsync(user, false);
await _userManager.ResetAuthenticatorKeyAsync(user);
_logger.LogInformation("User with id '{UserId}' has reset their authentication app key.", user.Id);
return RedirectToAction(nameof(EnableAuthenticator));
}
[HttpGet]
public async Task<IActionResult> GenerateRecoveryCodes()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (!user.TwoFactorAuthEnabled)
{
throw new ApplicationException($"Cannot generate recovery codes for user with ID '{user.Id}' as they do not have 2FA enabled.");
}
var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
var model = new GenerateRecoveryCodesViewModel { RecoveryCodes = recoveryCodes.ToArray() };
_logger.LogInformation("User with ID {UserId} has generated new 2FA recovery codes.", user.Id);
return View(model);
}
#region Helpers
@@ -351,21 +474,30 @@ namespace Sample.Web.NetCore.Controllers
}
}
public enum ManageMessageId
private string FormatKey(string unformattedKey)
{
AddPhoneSuccess,
AddLoginSuccess,
ChangePasswordSuccess,
SetTwoFactorSuccess,
SetPasswordSuccess,
RemoveLoginSuccess,
RemovePhoneSuccess,
Error
var result = new StringBuilder();
int currentPosition = 0;
while (currentPosition + 4 < unformattedKey.Length)
{
result.Append(unformattedKey.Substring(currentPosition, 4)).Append(" ");
currentPosition += 4;
}
if (currentPosition < unformattedKey.Length)
{
result.Append(unformattedKey.Substring(currentPosition));
}
return result.ToString().ToLowerInvariant();
}
private Task<AppUser> GetCurrentUserAsync()
private string GenerateQrCodeUri(string email, string unformattedKey)
{
return _userManager.GetUserAsync(HttpContext.User);
return string.Format(
AuthenicatorUriFormat,
_urlEncoder.Encode("Sample"),
_urlEncoder.Encode(email),
unformattedKey);
}
#endregion
+2 -2
View File
@@ -6,7 +6,7 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Sample.Web.NetCore.Controllers
namespace Sample.Controllers
{
/// <summary>
/// Controller that saves changes on the RavenDB document session.
@@ -18,7 +18,7 @@ namespace Sample.Web.NetCore.Controllers
DbSession = dbSession ?? throw new ArgumentNullException(nameof(dbSession));
// RavenDB best practice: during save, wait for the indexes to update.
// This way, Post-Redirect-Get scenarios are covered, not affected by stale indexes.
// This way, Post-Redirect-Get scenarios won't be affected by stale indexes.
// For more info, see https://ravendb.net/docs/article-page/3.5/Csharp/client-api/session/saving-changes
DbSession.Advanced.WaitForIndexesAfterSaveChanges(timeout: TimeSpan.FromSeconds(30), throwOnTimeout: false);
}
@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Sample.Services;
namespace Sample.Services
{
public static class EmailSenderExtensions
{
public static Task SendEmailConfirmationAsync(this IEmailSender emailSender, string email, string link)
{
return emailSender.SendEmailAsync(email, "Confirm your email",
$"Please confirm your account by clicking this link: <a href='{HtmlEncoder.Default.Encode(link)}'>link</a>");
}
}
}
+29
View File
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Sample.Controllers;
namespace Microsoft.AspNetCore.Mvc
{
public static class UrlHelperExtensions
{
public static string EmailConfirmationLink(this IUrlHelper urlHelper, string userId, string code, string scheme)
{
return urlHelper.Action(
action: nameof(AccountController.ConfirmEmail),
controller: "Account",
values: new { userId, code },
protocol: scheme);
}
public static string ResetPasswordCallbackLink(this IUrlHelper urlHelper, string userId, string code, string scheme)
{
return urlHelper.Action(
action: nameof(AccountController.ResetPassword),
controller: "Account",
values: new { userId, code },
protocol: scheme);
}
}
}
@@ -4,9 +4,9 @@ using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace Sample.Web.NetCore.Models.AccountViewModels
namespace Sample.Models.AccountViewModels
{
public class ExternalLoginConfirmationViewModel
public class ExternalLoginViewModel
{
[Required]
[EmailAddress]
@@ -4,7 +4,7 @@ using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace Sample.Web.NetCore.Models.AccountViewModels
namespace Sample.Models.AccountViewModels
{
public class ForgotPasswordViewModel
{
@@ -4,7 +4,7 @@ using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace Sample.Web.NetCore.Models.AccountViewModels
namespace Sample.Models.AccountViewModels
{
public class LoginViewModel
{
@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace Sample.Models.AccountViewModels
{
public class LoginWith2faViewModel
{
[Required]
[StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Text)]
[Display(Name = "Authenticator code")]
public string TwoFactorCode { get; set; }
[Display(Name = "Remember this machine")]
public bool RememberMachine { get; set; }
public bool RememberMe { get; set; }
}
}
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace Sample.Models.AccountViewModels
{
public class LoginWithRecoveryCodeViewModel
{
[Required]
[DataType(DataType.Text)]
[Display(Name = "Recovery Code")]
public string RecoveryCode { get; set; }
}
}
@@ -4,7 +4,7 @@ using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace Sample.Web.NetCore.Models.AccountViewModels
namespace Sample.Models.AccountViewModels
{
public class RegisterViewModel
{
@@ -4,7 +4,7 @@ using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace Sample.Web.NetCore.Models.AccountViewModels
namespace Sample.Models.AccountViewModels
{
public class ResetPasswordViewModel
{
@@ -1,19 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace Sample.Web.NetCore.Models.AccountViewModels
{
public class SendCodeViewModel
{
public string SelectedProvider { get; set; }
public ICollection<SelectListItem> Providers { get; set; }
public string ReturnUrl { get; set; }
public bool RememberMe { get; set; }
}
}
@@ -1,25 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace Sample.Web.NetCore.Models.AccountViewModels
{
public class VerifyCodeViewModel
{
[Required]
public string Provider { get; set; }
[Required]
public string Code { get; set; }
public string ReturnUrl { get; set; }
[Display(Name = "Remember this browser?")]
public bool RememberBrowser { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
}
+1 -1
View File
@@ -4,7 +4,7 @@ using System.Linq;
using System.Threading.Tasks;
using RavenDB.Identity;
namespace Sample.Web.NetCore.Models
namespace Sample.Models
{
// Add profile data for application users by adding properties to the AppUser class
public class AppUser : IdentityUser
+11
View File
@@ -0,0 +1,11 @@
using System;
namespace Sample.Models
{
public class ErrorViewModel
{
public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
}
}
@@ -1,16 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace Sample.Web.NetCore.Models.ManageViewModels
{
public class AddPhoneNumberViewModel
{
[Required]
[Phone]
[Display(Name = "Phone number")]
public string PhoneNumber { get; set; }
}
}
@@ -4,7 +4,7 @@ using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace Sample.Web.NetCore.Models.ManageViewModels
namespace Sample.Models.ManageViewModels
{
public class ChangePasswordViewModel
{
@@ -23,5 +23,7 @@ namespace Sample.Web.NetCore.Models.ManageViewModels
[Display(Name = "Confirm new password")]
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
public string StatusMessage { get; set; }
}
}
@@ -1,15 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace Sample.Web.NetCore.Models.ManageViewModels
{
public class ConfigureTwoFactorViewModel
{
public string SelectedProvider { get; set; }
public ICollection<SelectListItem> Providers { get; set; }
}
}
@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace Sample.Models.ManageViewModels
{
public class EnableAuthenticatorViewModel
{
[Required]
[StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Text)]
[Display(Name = "Verification Code")]
public string Code { get; set; }
[ReadOnly(true)]
public string SharedKey { get; set; }
public string AuthenticatorUri { get; set; }
}
}
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
namespace Sample.Models.ManageViewModels
{
public class ExternalLoginsViewModel
{
public IList<UserLoginInfo> CurrentLogins { get; set; }
public IList<AuthenticationScheme> OtherLogins { get; set; }
public bool ShowRemoveButton { get; set; }
public string StatusMessage { get; set; }
}
}
@@ -1,12 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Sample.Web.NetCore.Models.ManageViewModels
{
public class FactorViewModel
{
public string Purpose { get; set; }
}
}
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace Sample.Models.ManageViewModels
{
public class GenerateRecoveryCodesViewModel
{
public string[] RecoveryCodes { get; set; }
}
}
@@ -1,21 +1,25 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
namespace Sample.Web.NetCore.Models.ManageViewModels
namespace Sample.Models.ManageViewModels
{
public class IndexViewModel
{
public bool HasPassword { get; set; }
public string Username { get; set; }
public IList<UserLoginInfo> Logins { get; set; }
public bool IsEmailConfirmed { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
[Phone]
[Display(Name = "Phone number")]
public string PhoneNumber { get; set; }
public bool TwoFactor { get; set; }
public bool BrowserRemembered { get; set; }
public string StatusMessage { get; set; }
}
}
@@ -1,16 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.Identity;
namespace Sample.Web.NetCore.Models.ManageViewModels
{
public class ManageLoginsViewModel
{
public IList<UserLoginInfo> CurrentLogins { get; set; }
public IList<AuthenticationDescription> OtherLogins { get; set; }
}
}
@@ -4,7 +4,7 @@ using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace Sample.Web.NetCore.Models.ManageViewModels
namespace Sample.Models.ManageViewModels
{
public class RemoveLoginViewModel
{
@@ -4,7 +4,7 @@ using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace Sample.Web.NetCore.Models.ManageViewModels
namespace Sample.Models.ManageViewModels
{
public class SetPasswordViewModel
{
@@ -18,5 +18,7 @@ namespace Sample.Web.NetCore.Models.ManageViewModels
[Display(Name = "Confirm new password")]
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
public string StatusMessage { get; set; }
}
}
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace Sample.Models.ManageViewModels
{
public class TwoFactorAuthenticationViewModel
{
public bool HasAuthenticator { get; set; }
public int RecoveryCodesLeft { get; set; }
public bool Is2faEnabled { get; set; }
}
}
@@ -1,19 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace Sample.Web.NetCore.Models.ManageViewModels
{
public class VerifyPhoneNumberViewModel
{
[Required]
public string Code { get; set; }
[Required]
[Phone]
[Display(Name = "Phone number")]
public string PhoneNumber { get; set; }
}
}
+10 -10
View File
@@ -3,23 +3,23 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace Sample.Web.NetCore
namespace Sample
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.UseApplicationInsights()
.Build();
host.Run();
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
}
+8 -23
View File
@@ -1,36 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
<PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81;</PackageTargetFallback>
<UserSecretsId>aspnet-Sample.Web.NetCore-56FAE12E-CFF5-44D1-AE0F-D5E908F78EF0</UserSecretsId>
<TargetFramework>netcoreapp2.0</TargetFramework>
<UserSecretsId>aspnet-Sample-ABF406ED-6407-4847-8914-417141BB844E</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.3" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.2" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="1.1.1" PrivateAssets="All" />
<PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.0" PrivateAssets="All" />
<PackageReference Include="RavenDB.Identity" Version="2.0.2" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.1" />
<DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="1.0.1" />
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="1.0.1" />
</ItemGroup>
<ItemGroup>
<Folder Include="Data\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RavenDB.Identity\RavenDB.Identity.csproj" />
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
<DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="2.0.0" />
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />
</ItemGroup>
</Project>
+17
View File
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Sample.Services
{
// This class is used by the application to send email for account confirmation and password reset.
// For more details see https://go.microsoft.com/fwlink/?LinkID=532713
public class EmailSender : IEmailSender
{
public Task SendEmailAsync(string email, string subject, string message)
{
return Task.CompletedTask;
}
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Sample.Web.NetCore.Services
namespace Sample.Services
{
public interface IEmailSender
{
-12
View File
@@ -1,12 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Sample.Web.NetCore.Services
{
public interface ISmsSender
{
Task SendSmsAsync(string number, string message);
}
}
-25
View File
@@ -1,25 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Sample.Web.NetCore.Services
{
// This class is used by the application to send Email and SMS
// when you turn on two-factor authentication in ASP.NET Identity.
// For more details see this link https://go.microsoft.com/fwlink/?LinkID=532713
public class AuthMessageSender : IEmailSender, ISmsSender
{
public Task SendEmailAsync(string email, string subject, string message)
{
// Plug in your email service here to send an email.
return Task.FromResult(0);
}
public Task SendSmsAsync(string number, string message)
{
// Plug in your SMS service here to send a text message.
return Task.FromResult(0);
}
}
}
+15 -31
View File
@@ -3,37 +3,25 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Sample.Web.NetCore.Models;
using Sample.Web.NetCore.Services;
using Sample.Models;
using Sample.Services;
using RavenDB.Identity;
namespace Sample.Web.NetCore
namespace Sample
{
public class Startup
{
public Startup(IHostingEnvironment env)
public Startup(IConfiguration configuration)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
if (env.IsDevelopment())
{
// For more details on using the user secret store see https://go.microsoft.com/fwlink/?LinkID=532709
builder.AddUserSecrets<Startup>();
}
builder.AddEnvironmentVariables();
Configuration = builder.Build();
Configuration = configuration;
}
public IConfigurationRoot Configuration { get; }
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
@@ -41,26 +29,24 @@ namespace Sample.Web.NetCore
// Add RavenDB and identity.
services
.AddRavenDb(Configuration.GetConnectionString("RavenDbConnection")) // Create a RavenDB DocumentStore singleton.
.AddRavenDbAsyncSession() // Create a RavenDB IASyncDocumentSession for each request.
.AddRavenDbAsyncSession() // Create a RavenDB IAsyncDocumentSession for each request.
.AddRavenDbIdentity<AppUser>(); // Use Raven for users and roles.
services.AddMvc();
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
services.AddTransient<IEmailSender, EmailSender>();
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
app.UseDatabaseErrorPage();
}
else
{
@@ -69,9 +55,7 @@ namespace Sample.Web.NetCore
app.UseStaticFiles();
app.UseIdentity();
// Add external authentication middleware below. To configure them please see https://go.microsoft.com/fwlink/?LinkID=532715
app.UseAuthentication();
app.UseMvc(routes =>
{
+2 -2
View File
@@ -1,8 +1,8 @@
@{
ViewData["Title"] = "Access Denied";
ViewData["Title"] = "Access denied";
}
<header>
<h1 class="text-danger">Access Denied.</h1>
<h2 class="text-danger">ViewData["Title"]</h2>
<p class="text-danger">You do not have access to this resource.</p>
</header>
+3 -3
View File
@@ -1,10 +1,10 @@
@{
ViewData["Title"] = "Confirm Email";
ViewData["Title"] = "Confirm email";
}
<h2>@ViewData["Title"].</h2>
<h2>@ViewData["Title"]</h2>
<div>
<p>
Thank you for confirming your email. Please <a asp-controller="Account" asp-action="Login">Click here to Log in</a>.
Thank you for confirming your email.
</p>
</div>
+32
View File
@@ -0,0 +1,32 @@
@model ExternalLoginViewModel
@{
ViewData["Title"] = "Register";
}
<h2>@ViewData["Title"]</h2>
<h4>Associate your @ViewData["LoginProvider"] account.</h4>
<hr />
<p class="text-info">
You've successfully authenticated with <strong>@ViewData["LoginProvider"]</strong>.
Please enter an email address for this site below and click the Register button to finish
logging in.
</p>
<div class="row">
<div class="col-md-4">
<form asp-action="ExternalLoginConfirmation" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Email"></label>
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-default">Register</button>
</form>
</div>
</div>
@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
}
@@ -1,35 +0,0 @@
@model ExternalLoginConfirmationViewModel
@{
ViewData["Title"] = "Register";
}
<h2>@ViewData["Title"].</h2>
<h3>Associate your @ViewData["LoginProvider"] account.</h3>
<form asp-controller="Account" asp-action="ExternalLoginConfirmation" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal">
<h4>Association Form</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<p class="text-info">
You've successfully authenticated with <strong>@ViewData["LoginProvider"]</strong>.
Please enter an email address for this site below and click the Register button to finish
logging in.
</p>
<div class="form-group">
<label asp-for="Email" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-default">Register</button>
</div>
</div>
</form>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}
@@ -1,8 +0,0 @@
@{
ViewData["Title"] = "Login Failure";
}
<header>
<h2>@ViewData["Title"].</h2>
<p class="text-danger">Unsuccessful login with service.</p>
</header>
+14 -20
View File
@@ -4,28 +4,22 @@
}
<h2>@ViewData["Title"]</h2>
<p>
For more information on how to enable reset password please see this <a href="https://go.microsoft.com/fwlink/?LinkID=532713">article</a>.
</p>
@*<form asp-controller="Account" asp-action="ForgotPassword" method="post" class="form-horizontal">
<h4>Enter your email.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Email" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<h4>Enter your email.</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="ForgotPassword" method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Email"></label>
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-default">Submit</button>
</div>
</form>
</div>
</form>*@
</div>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
@await Html.PartialAsync("_ValidationScriptsPartial")
}
@@ -1,8 +1,8 @@
@{
ViewData["Title"] = "Forgot Password Confirmation";
ViewData["Title"] = "Forgot password confirmation";
}
<h2>@ViewData["Title"].</h2>
<h2>@ViewData["Title"]</h2>
<p>
Please check your email to reset your password.
</p>
+1 -1
View File
@@ -3,6 +3,6 @@
}
<header>
<h1 class="text-danger">Locked out.</h1>
<h2 class="text-danger">@ViewData["Title"]</h2>
<p class="text-danger">This account has been locked out, please try again later.</p>
</header>
+31 -36
View File
@@ -1,4 +1,5 @@
@using System.Collections.Generic
@using System.Linq
@using Microsoft.AspNetCore.Http
@using Microsoft.AspNetCore.Http.Authentication
@model LoginViewModel
@@ -8,58 +9,52 @@
ViewData["Title"] = "Log in";
}
<h2>@ViewData["Title"].</h2>
<h2>@ViewData["Title"]</h2>
<div class="row">
<div class="col-md-8">
<div class="col-md-4">
<section>
<form asp-controller="Account" asp-action="Login" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal">
<form asp-route-returnurl="@ViewData["ReturnUrl"]" method="post">
<h4>Use a local account to log in.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Email" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
<label asp-for="Email"></label>
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Password"></label>
<input asp-for="Password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
<div class="form-group">
<div class="checkbox">
<label asp-for="RememberMe">
<input asp-for="RememberMe" />
@Html.DisplayNameFor(m => m.RememberMe)
</label>
</div>
</div>
<div class="form-group">
<label asp-for="Password" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-default">Log in</button>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<div class="checkbox">
<label asp-for="RememberMe">
<input asp-for="RememberMe" />
@Html.DisplayNameFor(m => m.RememberMe)
</label>
</div>
</div>
<p>
<a asp-action="ForgotPassword">Forgot your password?</a>
</p>
<p>
<a asp-action="Register" asp-route-returnurl="@ViewData["ReturnUrl"]">Register as a new user?</a>
</p>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-default">Log in</button>
</div>
</div>
<p>
<a asp-action="Register" asp-route-returnurl="@ViewData["ReturnUrl"]">Register as a new user?</a>
</p>
<p>
<a asp-action="ForgotPassword">Forgot your password?</a>
</p>
</form>
</section>
</div>
<div class="col-md-4">
<div class="col-md-6 col-md-offset-2">
<section>
<h4>Use another service to log in.</h4>
<hr />
@{
var loginProviders = SignInManager.GetExternalAuthenticationSchemes().ToList();
var loginProviders = (await SignInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (loginProviders.Count == 0)
{
<div>
@@ -71,12 +66,12 @@
}
else
{
<form asp-controller="Account" asp-action="ExternalLogin" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal">
<form asp-action="ExternalLogin" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal">
<div>
<p>
@foreach (var provider in loginProviders)
{
<button type="submit" class="btn btn-default" name="provider" value="@provider.AuthenticationScheme" title="Log in using your @provider.DisplayName account">@provider.AuthenticationScheme</button>
<button type="submit" class="btn btn-default" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.Name</button>
}
</p>
</div>
@@ -88,5 +83,5 @@
</div>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
@await Html.PartialAsync("_ValidationScriptsPartial")
}
+40
View File
@@ -0,0 +1,40 @@
@model LoginWith2faViewModel
@{
ViewData["Title"] = "Two-factor authentication";
}
<h2>@ViewData["Title"]</h2>
<hr />
<p>Your login is protected with an authenticator app. Enter your authenticator code below.</p>
<div class="row">
<div class="col-md-4">
<form method="post" asp-route-returnUrl="@ViewData["ReturnUrl"]">
<input asp-for="RememberMe" type="hidden" />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="TwoFactorCode"></label>
<input asp-for="TwoFactorCode" class="form-control" autocomplete="off" />
<span asp-validation-for="TwoFactorCode" class="text-danger"></span>
</div>
<div class="form-group">
<div class="checkbox">
<label asp-for="RememberMachine">
<input asp-for="RememberMachine" />
@Html.DisplayNameFor(m => m.RememberMachine)
</label>
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-default">Log in</button>
</div>
</form>
</div>
</div>
<p>
Don't have access to your authenticator device? You can
<a asp-action="LoginWithRecoveryCode" asp-route-returnUrl="@ViewData["ReturnUrl"]">log in with a recovery code</a>.
</p>
@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
}
@@ -0,0 +1,28 @@
@model LoginWithRecoveryCodeViewModel
@{
ViewData["Title"] = "Recovery code verification";
}
<h2>@ViewData["Title"]</h2>
<hr />
<p>
You have requested to login with a recovery code. This login will not be remembered until you provide
an authenticator app code at login or disable 2FA and login again.
</p>
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="RecoveryCode"></label>
<input asp-for="RecoveryCode" class="form-control" autocomplete="off" />
<span asp-validation-for="RecoveryCode" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-default">Log in</button>
</form>
</div>
</div>
@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
}
+25 -31
View File
@@ -3,40 +3,34 @@
ViewData["Title"] = "Register";
}
<h2>@ViewData["Title"].</h2>
<h2>@ViewData["Title"]</h2>
<form asp-controller="Account" asp-action="Register" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal">
<h4>Create a new account.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Email" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="Password" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="ConfirmPassword" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="ConfirmPassword" class="form-control" />
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<div class="row">
<div class="col-md-4">
<form asp-route-returnUrl="@ViewData["ReturnUrl"]" method="post">
<h4>Create a new account.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Email"></label>
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Password"></label>
<input asp-for="Password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ConfirmPassword"></label>
<input asp-for="ConfirmPassword" class="form-control" />
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-default">Register</button>
</div>
</form>
</div>
</form>
</div>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
@await Html.PartialAsync("_ValidationScriptsPartial")
}
+26 -33
View File
@@ -3,41 +3,34 @@
ViewData["Title"] = "Reset password";
}
<h2>@ViewData["Title"].</h2>
<form asp-controller="Account" asp-action="ResetPassword" method="post" class="form-horizontal">
<h4>Reset your password.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<input asp-for="Code" type="hidden" />
<div class="form-group">
<label asp-for="Email" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="Password" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="ConfirmPassword" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="ConfirmPassword" class="form-control" />
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<h2>@ViewData["Title"]</h2>
<h4>Reset your password.</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<input asp-for="Code" type="hidden" />
<div class="form-group">
<label asp-for="Email"></label>
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Password"></label>
<input asp-for="Password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ConfirmPassword"></label>
<input asp-for="ConfirmPassword" class="form-control" />
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-default">Reset</button>
</div>
</form>
</div>
</form>
</div>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
@await Html.PartialAsync("_ValidationScriptsPartial")
}
@@ -2,7 +2,7 @@
ViewData["Title"] = "Reset password confirmation";
}
<h1>@ViewData["Title"].</h1>
<h2>@ViewData["Title"]</h2>
<p>
Your password has been reset. Please <a asp-controller="Account" asp-action="Login">Click here to log in</a>.
Your password has been reset. Please <a asp-action="Login">click here to log in</a>.
</p>
-21
View File
@@ -1,21 +0,0 @@
@model SendCodeViewModel
@{
ViewData["Title"] = "Send Verification Code";
}
<h2>@ViewData["Title"].</h2>
<form asp-controller="Account" asp-action="SendCode" asp-route-returnurl="@Model.ReturnUrl" method="post" class="form-horizontal">
<input asp-for="RememberMe" type="hidden" />
<div class="row">
<div class="col-md-8">
Select Two-Factor Authentication Provider:
<select asp-for="SelectedProvider" asp-items="Model.Providers"></select>
<button type="submit" class="btn btn-default">Submit</button>
</div>
</div>
</form>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}
+8
View File
@@ -0,0 +1,8 @@
@{
ViewData["Title"] = "Signed out";
}
<h2>@ViewData["Title"]</h2>
<p>
You have successfully signed out.
</p>
-38
View File
@@ -1,38 +0,0 @@
@model VerifyCodeViewModel
@{
ViewData["Title"] = "Verify";
}
<h2>@ViewData["Title"].</h2>
<form asp-controller="Account" asp-action="VerifyCode" asp-route-returnurl="@Model.ReturnUrl" method="post" class="form-horizontal">
<div asp-validation-summary="All" class="text-danger"></div>
<input asp-for="Provider" type="hidden" />
<input asp-for="RememberMe" type="hidden" />
<h4>@ViewData["Status"]</h4>
<hr />
<div class="form-group">
<label asp-for="Code" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Code" class="form-control" />
<span asp-validation-for="Code" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<div class="checkbox">
<input asp-for="RememberBrowser" />
<label asp-for="RememberBrowser"></label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-default">Submit</button>
</div>
</div>
</form>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}
+1 -1
View File
@@ -1,7 +1,7 @@
@{
ViewData["Title"] = "About";
}
<h2>@ViewData["Title"].</h2>
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>
<p>Use this area to provide additional information.</p>
+1 -1
View File
@@ -1,7 +1,7 @@
@{
ViewData["Title"] = "Contact";
}
<h2>@ViewData["Title"].</h2>
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>
<address>
+2 -1
View File
@@ -54,9 +54,10 @@
</ul>
</div>
<div class="col-md-3">
<h2>Run & Deploy</h2>
<h2>Run &amp; Deploy</h2>
<ul>
<li><a href="https://go.microsoft.com/fwlink/?LinkID=517851">Run your app</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkID=517853">Run tools such as EF migrations and more</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkID=398609">Publish to Microsoft Azure Web Apps</a></li>
</ul>
</div>
-27
View File
@@ -1,27 +0,0 @@
@model AddPhoneNumberViewModel
@{
ViewData["Title"] = "Add Phone Number";
}
<h2>@ViewData["Title"].</h2>
<form asp-controller="Manage" asp-action="AddPhoneNumber" method="post" class="form-horizontal">
<h4>Add a phone number.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="PhoneNumber" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="PhoneNumber" class="form-control" />
<span asp-validation-for="PhoneNumber" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-default">Send verification code</button>
</div>
</div>
</form>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}
+27 -34
View File
@@ -1,42 +1,35 @@
@model ChangePasswordViewModel
@{
ViewData["Title"] = "Change Password";
ViewData["Title"] = "Change password";
ViewData.AddActivePage(ManageNavPages.ChangePassword);
}
<h2>@ViewData["Title"].</h2>
<form asp-controller="Manage" asp-action="ChangePassword" method="post" class="form-horizontal">
<h4>Change Password Form</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="OldPassword" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="OldPassword" class="form-control" />
<span asp-validation-for="OldPassword" class="text-danger"></span>
</div>
<h4>@ViewData["Title"]</h4>
@Html.Partial("_StatusMessage", Model.StatusMessage)
<div class="row">
<div class="col-md-6">
<form method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="OldPassword"></label>
<input asp-for="OldPassword" class="form-control" />
<span asp-validation-for="OldPassword" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="NewPassword"></label>
<input asp-for="NewPassword" class="form-control" />
<span asp-validation-for="NewPassword" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ConfirmPassword"></label>
<input asp-for="ConfirmPassword" class="form-control" />
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-default">Update password</button>
</form>
</div>
<div class="form-group">
<label asp-for="NewPassword" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="NewPassword" class="form-control" />
<span asp-validation-for="NewPassword" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="ConfirmPassword" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="ConfirmPassword" class="form-control" />
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-default">Change password</button>
</div>
</div>
</form>
</div>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
@await Html.PartialAsync("_ValidationScriptsPartial")
}
+24
View File
@@ -0,0 +1,24 @@
@{
ViewData["Title"] = "Disable two-factor authentication (2FA)";
ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication);
}
<h2>@ViewData["Title"]</h2>
<div class="alert alert-warning" role="alert">
<p>
<span class="glyphicon glyphicon-warning-sign"></span>
<strong>This action only disables 2FA.</strong>
</p>
<p>
Disabling 2FA does not change the keys used in authenticator apps. If you wish to change the key
used in an authenticator app you should <a asp-action="ResetAuthenticatorWarning">reset your
authenticator keys.</a>
</p>
</div>
<div>
<form asp-action="Disable2fa" method="post" class="form-group">
<button class="btn btn-danger" type="submit">Disable 2FA</button>
</form>
</div>
@@ -0,0 +1,52 @@
@model EnableAuthenticatorViewModel
@{
ViewData["Title"] = "Enable authenticator";
ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication);
}
<h4>@ViewData["Title"]</h4>
<div>
<p>To use an authenticator app go through the following steps:</p>
<ol class="list">
<li>
<p>
Download a two-factor authenticator app like Microsoft Authenticator for
<a href="https://go.microsoft.com/fwlink/?Linkid=825071">Windows Phone</a>,
<a href="https://go.microsoft.com/fwlink/?Linkid=825072">Android</a> and
<a href="https://go.microsoft.com/fwlink/?Linkid=825073">iOS</a> or
Google Authenticator for
<a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&amp;hl=en">Android</a> and
<a href="https://itunes.apple.com/us/app/google-authenticator/id388497605?mt=8">iOS</a>.
</p>
</li>
<li>
<p>Scan the QR Code or enter this key <kbd>@Model.SharedKey</kbd> into your two factor authenticator app. Spaces and casing do not matter.</p>
<div class="alert alert-info">To enable QR code generation please read our <a href="https://go.microsoft.com/fwlink/?Linkid=852423">documentation</a>.</div>
<div id="qrCode"></div>
<div id="qrCodeData" data-url="@Html.Raw(Model.AuthenticatorUri)"></div>
</li>
<li>
<p>
Once you have scanned the QR code or input the key above, your two factor authentication app will provide you
with a unique code. Enter the code in the confirmation box below.
</p>
<div class="row">
<div class="col-md-6">
<form method="post">
<div class="form-group">
<label asp-for="Code" class="control-label">Verification Code</label>
<input asp-for="Code" class="form-control" autocomplete="off" />
<span asp-validation-for="Code" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-default">Verify</button>
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
</form>
</div>
</div>
</li>
</ol>
</div>
@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
}
+52
View File
@@ -0,0 +1,52 @@
@model ExternalLoginsViewModel
@{
ViewData["Title"] = "Manage your external logins";
ViewData.AddActivePage(ManageNavPages.ExternalLogins);
}
@Html.Partial("_StatusMessage", Model.StatusMessage)
@if (Model.CurrentLogins?.Count > 0)
{
<h4>Registered Logins</h4>
<table class="table">
<tbody>
@foreach (var login in Model.CurrentLogins)
{
<tr>
<td>@login.LoginProvider</td>
<td>
@if (Model.ShowRemoveButton)
{
<form asp-action="RemoveLogin" method="post">
<div>
<input asp-for="@login.LoginProvider" name="LoginProvider" type="hidden" />
<input asp-for="@login.ProviderKey" name="ProviderKey" type="hidden" />
<button type="submit" class="btn btn-default" title="Remove this @login.LoginProvider login from your account">Remove</button>
</div>
</form>
}
else
{
@: &nbsp;
}
</td>
</tr>
}
</tbody>
</table>
}
@if (Model.OtherLogins?.Count > 0)
{
<h4>Add another service to log in.</h4>
<hr />
<form asp-action="LinkLogin" method="post" class="form-horizontal">
<div id="socialLoginList">
<p>
@foreach (var provider in Model.OtherLogins)
{
<button type="submit" class="btn btn-default" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
}
</p>
</div>
</form>
}
@@ -0,0 +1,24 @@
@model GenerateRecoveryCodesViewModel
@{
ViewData["Title"] = "Recovery codes";
ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication);
}
<h4>@ViewData["Title"]</h4>
<div class="alert alert-warning" role="alert">
<p>
<span class="glyphicon glyphicon-warning-sign"></span>
<strong>Put these codes in a safe place.</strong>
</p>
<p>
If you lose your device and don't have the recovery codes you will lose access to your account.
</p>
</div>
<div class="row">
<div class="col-md-12">
@for (var row = 0; row < Model.RecoveryCodes.Count(); row += 2)
{
<code>@Model.RecoveryCodes[row]</code><text>&nbsp;</text><code>@Model.RecoveryCodes[row + 1]</code><br />
}
</div>
</div>
+35 -61
View File
@@ -1,71 +1,45 @@
@model IndexViewModel
@{
ViewData["Title"] = "Manage your account";
ViewData["Title"] = "Profile";
ViewData.AddActivePage(ManageNavPages.Index);
}
<h2>@ViewData["Title"].</h2>
<p class="text-success">@ViewData["StatusMessage"]</p>
<div>
<h4>Change your account settings</h4>
<hr />
<dl class="dl-horizontal">
<dt>Password:</dt>
<dd>
@if (Model.HasPassword)
{
<a asp-controller="Manage" asp-action="ChangePassword" class="btn-bracketed">Change</a>
}
else
{
<a asp-controller="Manage" asp-action="SetPassword" class="btn-bracketed">Create</a>
}
</dd>
<dt>External Logins:</dt>
<dd>
@Model.Logins.Count <a asp-controller="Manage" asp-action="ManageLogins" class="btn-bracketed">Manage</a>
</dd>
<dt>Phone Number:</dt>
<dd>
<p>
Phone Numbers can be used as a second factor of verification in two-factor authentication.
See <a href="https://go.microsoft.com/fwlink/?LinkID=532713">this article</a>
for details on setting up this ASP.NET application to support two-factor authentication using SMS.
</p>
@*@(Model.PhoneNumber ?? "None")
@if (Model.PhoneNumber != null)
<h4>@ViewData["Title"]</h4>
@Html.Partial("_StatusMessage", Model.StatusMessage)
<div class="row">
<div class="col-md-6">
<form method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Username"></label>
<input asp-for="Username" class="form-control" disabled />
</div>
<div class="form-group">
<label asp-for="Email"></label>
@if (Model.IsEmailConfirmed)
{
<br />
<a asp-controller="Manage" asp-action="AddPhoneNumber" class="btn-bracketed">Change</a>
<form asp-controller="Manage" asp-action="RemovePhoneNumber" method="post">
[<button type="submit" class="btn-link">Remove</button>]
</form>
<div class="input-group">
<input asp-for="Email" class="form-control" />
<span class="input-group-addon" aria-hidden="true"><span class="glyphicon glyphicon-ok text-success"></span></span>
</div>
}
else
{
<a asp-controller="Manage" asp-action="AddPhoneNumber" class="btn-bracketed">Add</a>
}*@
</dd>
<dt>Two-Factor Authentication:</dt>
<dd>
<p>
There are no two-factor authentication providers configured. See <a href="https://go.microsoft.com/fwlink/?LinkID=532713">this article</a>
for setting up this application to support two-factor authentication.
</p>
@*@if (Model.TwoFactor)
{
<form asp-controller="Manage" asp-action="DisableTwoFactorAuthentication" method="post" class="form-horizontal">
Enabled <button type="submit" class="btn-link btn-bracketed">Disable</button>
</form>
<input asp-for="Email" class="form-control" />
<button asp-action="SendVerificationEmail" class="btn btn-link">Send verification email</button>
}
else
{
<form asp-controller="Manage" asp-action="EnableTwoFactorAuthentication" method="post" class="form-horizontal">
<button type="submit" class="btn-link btn-bracketed">Enable</button> Disabled
</form>
}*@
</dd>
</dl>
<span asp-validation-for="Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="PhoneNumber"></label>
<input asp-for="PhoneNumber" class="form-control" />
<span asp-validation-for="PhoneNumber" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-default">Save</button>
</form>
</div>
</div>
@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
}
-54
View File
@@ -1,54 +0,0 @@
@model ManageLoginsViewModel
@using Microsoft.AspNetCore.Http.Authentication
@{
ViewData["Title"] = "Manage your external logins";
}
<h2>@ViewData["Title"].</h2>
<p class="text-success">@ViewData["StatusMessage"]</p>
@if (Model.CurrentLogins.Count > 0)
{
<h4>Registered Logins</h4>
<table class="table">
<tbody>
@for (var index = 0; index < Model.CurrentLogins.Count; index++)
{
<tr>
<td>@Model.CurrentLogins[index].LoginProvider</td>
<td>
@if ((bool)ViewData["ShowRemoveButton"])
{
<form asp-controller="Manage" asp-action="RemoveLogin" method="post" class="form-horizontal">
<div>
<input asp-for="@Model.CurrentLogins[index].LoginProvider" name="LoginProvider" type="hidden" />
<input asp-for="@Model.CurrentLogins[index].ProviderKey" name="ProviderKey" type="hidden" />
<input type="submit" class="btn btn-default" value="Remove" title="Remove this @Model.CurrentLogins[index].LoginProvider login from your account" />
</div>
</form>
}
else
{
@: &nbsp;
}
</td>
</tr>
}
</tbody>
</table>
}
@if (Model.OtherLogins.Count > 0)
{
<h4>Add another service to log in.</h4>
<hr />
<form asp-controller="Manage" asp-action="LinkLogin" method="post" class="form-horizontal">
<div id="socialLoginList">
<p>
@foreach (var provider in Model.OtherLogins)
{
<button type="submit" class="btn btn-default" name="provider" value="@provider.AuthenticationScheme" title="Log in using your @provider.DisplayName account">@provider.AuthenticationScheme</button>
}
</p>
</div>
</form>
}
+38
View File
@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
namespace Sample.Views.Manage
{
public static class ManageNavPages
{
public static string ActivePageKey => "ActivePage";
public static string Index => "Index";
public static string ChangePassword => "ChangePassword";
public static string ExternalLogins => "ExternalLogins";
public static string TwoFactorAuthentication => "TwoFactorAuthentication";
public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index);
public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword);
public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins);
public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication);
public static string PageNavClass(ViewContext viewContext, string page)
{
var activePage = viewContext.ViewData["ActivePage"] as string;
return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null;
}
public static void AddActivePage(this ViewDataDictionary viewData, string activePage) => viewData[ActivePageKey] = activePage;
}
}
@@ -0,0 +1,21 @@
@{
ViewData["Title"] = "Reset authenticator key";
ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication);
}
<h4>@ViewData["Title"]</h4>
<div class="alert alert-warning" role="alert">
<p>
<span class="glyphicon glyphicon-warning-sign"></span>
<strong>If you reset your authenticator key your authenticator app will not work until you reconfigure it.</strong>
</p>
<p>
This process disables 2FA until you verify your authenticator app and will also reset your 2FA recovery codes.
If you do not complete your authenticator app configuration you may lose access to your account.
</p>
</div>
<div>
<form asp-action="ResetAuthenticator" method="post" class="form-group">
<button class="btn btn-danger" type="submit">Reset authenticator key</button>
</form>
</div>
+21 -25
View File
@@ -1,38 +1,34 @@
@model SetPasswordViewModel
@{
ViewData["Title"] = "Set Password";
ViewData["Title"] = "Set password";
ViewData.AddActivePage(ManageNavPages.ChangePassword);
}
<h4>Set your password</h4>
@Html.Partial("_StatusMessage", Model.StatusMessage)
<p class="text-info">
You do not have a local username/password for this site. Add a local
account so you can log in without an external login.
</p>
<form asp-controller="Manage" asp-action="SetPassword" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal">
<h4>Set your password</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="NewPassword" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="NewPassword" class="form-control" />
<span asp-validation-for="NewPassword" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="ConfirmPassword" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="ConfirmPassword" class="form-control" />
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<div class="row">
<div class="col-md-6">
<form method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="NewPassword"></label>
<input asp-for="NewPassword" class="form-control" />
<span asp-validation-for="NewPassword" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ConfirmPassword"></label>
<input asp-for="ConfirmPassword" class="form-control" />
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-default">Set password</button>
</div>
</form>
</div>
</form>
</div>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
@await Html.PartialAsync("_ValidationScriptsPartial")
}
@@ -0,0 +1,49 @@
@model TwoFactorAuthenticationViewModel
@{
ViewData["Title"] = "Two-factor authentication";
ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication);
}
<h4>@ViewData["Title"]</h4>
@if (Model.Is2faEnabled)
{
if (Model.RecoveryCodesLeft == 0)
{
<div class="alert alert-danger">
<strong>You have no recovery codes left.</strong>
<p>You must <a asp-action="GenerateRecoveryCodes">generate a new set of recovery codes</a> before you can log in with a recovery code.</p>
</div>
}
else if (Model.RecoveryCodesLeft == 1)
{
<div class="alert alert-danger">
<strong>You have 1 recovery code left.</strong>
<p>You can <a asp-action="GenerateRecoveryCodes">generate a new set of recovery codes</a>.</p>
</div>
}
else if (Model.RecoveryCodesLeft <= 3)
{
<div class="alert alert-warning">
<strong>You have @Model.RecoveryCodesLeft recovery codes left.</strong>
<p>You should <a asp-action="GenerateRecoveryCodes">generate a new set of recovery codes</a>.</p>
</div>
}
<a asp-action="Disable2faWarning" class="btn btn-default">Disable 2FA</a>
<a asp-action="GenerateRecoveryCodes" class="btn btn-default">Reset recovery codes</a>
}
<h5>Authenticator app</h5>
@if (!Model.HasAuthenticator)
{
<a asp-action="EnableAuthenticator" class="btn btn-default">Add authenticator app</a>
}
else
{
<a asp-action="EnableAuthenticator" class="btn btn-default">Configure authenticator app</a>
<a asp-action="ResetAuthenticatorWarning" class="btn btn-default">Reset authenticator key</a>
}
@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
}
@@ -1,30 +0,0 @@
@model VerifyPhoneNumberViewModel
@{
ViewData["Title"] = "Verify Phone Number";
}
<h2>@ViewData["Title"].</h2>
<form asp-controller="Manage" asp-action="VerifyPhoneNumber" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal">
<input asp-for="PhoneNumber" type="hidden" />
<h4>Add a phone number.</h4>
<h5>@ViewData["Status"]</h5>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Code" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Code" class="form-control" />
<span asp-validation-for="Code" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-default">Submit</button>
</div>
</div>
</form>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}
+23
View File
@@ -0,0 +1,23 @@
@{
Layout = "/Views/Shared/_Layout.cshtml";
}
<h2>Manage your account</h2>
<div>
<h4>Change your account settings</h4>
<hr />
<div class="row">
<div class="col-md-3">
@await Html.PartialAsync("_ManageNav")
</div>
<div class="col-md-9">
@RenderBody()
</div>
</div>
</div>
@section Scripts {
@RenderSection("Scripts", required: false)
}
+16
View File
@@ -0,0 +1,16 @@
@using Sample.Views.Manage
@inject SignInManager<AppUser> SignInManager
@{
var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any();
}
<ul class="nav nav-pills nav-stacked">
<li class="@ManageNavPages.IndexNavClass(ViewContext)"><a asp-action="Index">Profile</a></li>
<li class="@ManageNavPages.ChangePasswordNavClass(ViewContext)"><a asp-action="ChangePassword">Password</a></li>
@if (hasExternalLogins)
{
<li class="@ManageNavPages.ExternalLoginsNavClass(ViewContext)"><a asp-action="ExternalLogins">External logins</a></li>
}
<li class="@ManageNavPages.TwoFactorAuthenticationNavClass(ViewContext)"><a asp-action="TwoFactorAuthentication">Two-factor authentication</a></li>
</ul>
+10
View File
@@ -0,0 +1,10 @@
@model string
@if (!String.IsNullOrEmpty(Model))
{
var statusMessageClass = Model.StartsWith("Error") ? "danger" : "success";
<div class="alert alert-@statusMessageClass alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
@Model
</div>
}
+1
View File
@@ -0,0 +1 @@
@using Sample.Views.Manage
+9 -1
View File
@@ -1,10 +1,18 @@
@{
@model ErrorViewModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
+8 -10
View File
@@ -1,22 +1,20 @@
@inject Microsoft.ApplicationInsights.AspNetCore.JavaScriptSnippet JavaScriptSnippet
<!DOCTYPE html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Sample Asp.NET Core app with RavenDB Identity</title>
<title>@ViewData["Title"] - Sample</title>
<environment names="Development">
<environment include="Development">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" />
</environment>
<environment names="Staging,Production">
<environment exclude="Development">
<link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
</environment>
@Html.Raw(JavaScriptSnippet.FullScript)
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
@@ -28,7 +26,7 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a asp-area="" asp-controller="Home" asp-action="Index" class="navbar-brand">Sample.Web.NetCore</a>
<a asp-area="" asp-controller="Home" asp-action="Index" class="navbar-brand">Sample</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
@@ -44,16 +42,16 @@
@RenderBody()
<hr />
<footer>
<p>&copy; 2017 - Sample.Web.NetCore</p>
<p>&copy; 2017 - Sample</p>
</footer>
</div>
<environment names="Development">
<environment include="Development">
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
</environment>
<environment names="Staging,Production">
<environment exclude="Development">
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js"
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
asp-fallback-test="window.jQuery"
+1 -1
View File
@@ -1,5 +1,5 @@
@using Microsoft.AspNetCore.Identity
@using Sample.Web.NetCore.Models
@using Sample.Models
@inject SignInManager<AppUser> SignInManager
@inject UserManager<AppUser> UserManager
@@ -1,8 +1,8 @@
<environment names="Development">
<environment include="Development">
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
</environment>
<environment names="Staging,Production">
<environment exclude="Development">
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/jquery.validate.min.js"
asp-fallback-src="~/lib/jquery-validation/dist/jquery.validate.min.js"
asp-fallback-test="window.jQuery && window.jQuery.validator"
+4 -4
View File
@@ -1,6 +1,6 @@
@using Sample.Web.NetCore
@using Sample.Web.NetCore.Models
@using Sample.Web.NetCore.Models.AccountViewModels
@using Sample.Web.NetCore.Models.ManageViewModels
@using Microsoft.AspNetCore.Identity
@using Sample
@using Sample.Models
@using Sample.Models.AccountViewModels
@using Sample.Models.ManageViewModels
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
+1 -1
View File
@@ -1,6 +1,6 @@
{
"ConnectionStrings": {
"RavenDbConnection": "Url = http://localhost:8080; Database = TestDb;"
"RavenDbConnection": "Url = http://live-test.ravendb.net; Database = RavenDB-Identity-Tester;"
},
"Logging": {
"IncludeScopes": false,
+5 -20
View File
@@ -10,37 +10,22 @@
padding-right: 15px;
}
/* Set widths on the form inputs since otherwise they're 100% wide */
input,
select,
textarea {
max-width: 280px;
}
/* Carousel */
.carousel-caption p {
font-size: 20px;
line-height: 1.4;
}
/* buttons and links extension to use brackets: [ click me ] */
.btn-bracketed::before {
display: inline-block;
content: "[";
padding-right: 0.5em;
}
.btn-bracketed::after {
display: inline-block;
content: "]";
padding-left: 0.5em;
}
/* Make .svg files in the carousel display properly in older browsers */
.carousel-inner .item img[src$=".svg"] {
width: 100%;
}
/* QR code generator */
#qrCode {
margin: 15px;
}
/* Hide/rearrange for smaller screens */
@media screen and (max-width: 767px) {
/* Hide captions */
+1 -1
View File
@@ -1 +1 @@
body{padding-top:50px;padding-bottom:20px}.body-content{padding-left:15px;padding-right:15px}input,select,textarea{max-width:280px}.carousel-caption p{font-size:20px;line-height:1.4}.btn-bracketed::before{display:inline-block;content:"[";padding-right:.5em}.btn-bracketed::after{display:inline-block;content:"]";padding-left:.5em}.carousel-inner .item img[src$=".svg"]{width:100%}@media screen and (max-width:767px){.carousel-caption{display:none}}
body{padding-top:50px;padding-bottom:20px}.body-content{padding-left:15px;padding-right:15px}.carousel-caption p{font-size:20px;line-height:1.4}.carousel-inner .item img[src$=".svg"]{width:100%}#qrCode{margin:15px}@media screen and (max-width:767px){.carousel-caption{display:none}}
+1 -1
View File
@@ -1 +1 @@
// Write your Javascript code.
// Write your JavaScript code.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -334,7 +334,7 @@ if (typeof jQuery === 'undefined') {
Carousel.TRANSITION_DURATION = 600
Carousel.DEFAULTS = {
interval: 61507,
interval: 5000,
pause: 'hover',
wrap: true,
keyboard: true
@@ -220,7 +220,7 @@ $.validator.addMethod("cpfBR", function(value) {
value === "22222222222" ||
value === "33333333333" ||
value === "44444444444" ||
value === "61506615065" ||
value === "55555555555" ||
value === "66666666666" ||
value === "77777777777" ||
value === "88888888888" ||
File diff suppressed because one or more lines are too long