├── .bowerrc ├── .gitignore ├── ASP.NET-core-role-based-authentication.csproj ├── Controllers ├── AccountController.cs ├── AdminController.cs ├── HomeController.cs └── ManageController.cs ├── Data ├── ApplicationDbContext.cs ├── Migrations │ ├── 00000000000000_CreateIdentitySchema.Designer.cs │ ├── 00000000000000_CreateIdentitySchema.cs │ └── ApplicationDbContextModelSnapshot.cs └── Seed.cs ├── Extensions ├── EmailSenderExtensions.cs └── UrlHelperExtensions.cs ├── LICENSE ├── Models ├── AccountViewModels │ ├── ExternalLoginViewModel.cs │ ├── ForgotPasswordViewModel.cs │ ├── LoginViewModel.cs │ ├── LoginWith2faViewModel.cs │ ├── LoginWithRecoveryCodeViewModel.cs │ ├── RegisterViewModel.cs │ └── ResetPasswordViewModel.cs ├── ApplicationUser.cs ├── ErrorViewModel.cs └── ManageViewModels │ ├── ChangePasswordViewModel.cs │ ├── EnableAuthenticatorViewModel.cs │ ├── ExternalLoginsViewModel.cs │ ├── GenerateRecoveryCodesViewModel.cs │ ├── IndexViewModel.cs │ ├── RemoveLoginViewModel.cs │ ├── SetPasswordViewModel.cs │ └── TwoFactorAuthenticationViewModel.cs ├── Program.cs ├── README.md ├── Services ├── EmailSender.cs └── IEmailSender.cs ├── Startup.cs ├── Views ├── Account │ ├── AccessDenied.cshtml │ ├── ConfirmEmail.cshtml │ ├── ExternalLogin.cshtml │ ├── ForgotPassword.cshtml │ ├── ForgotPasswordConfirmation.cshtml │ ├── Lockout.cshtml │ ├── Login.cshtml │ ├── LoginWith2fa.cshtml │ ├── LoginWithRecoveryCode.cshtml │ ├── Register.cshtml │ ├── ResetPassword.cshtml │ ├── ResetPasswordConfirmation.cshtml │ └── SignedOut.cshtml ├── Admin │ └── Index.cshtml ├── Home │ ├── About.cshtml │ ├── Contact.cshtml │ └── Index.cshtml ├── Manage │ ├── ChangePassword.cshtml │ ├── Disable2fa.cshtml │ ├── EnableAuthenticator.cshtml │ ├── ExternalLogins.cshtml │ ├── GenerateRecoveryCodes.cshtml │ ├── Index.cshtml │ ├── ManageNavPages.cs │ ├── ResetAuthenticator.cshtml │ ├── SetPassword.cshtml │ ├── TwoFactorAuthentication.cshtml │ ├── _Layout.cshtml │ ├── _ManageNav.cshtml │ ├── _StatusMessage.cshtml │ └── _ViewImports.cshtml ├── Shared │ ├── Error.cshtml │ ├── _Layout.cshtml │ ├── _LoginPartial.cshtml │ └── _ValidationScriptsPartial.cshtml ├── _ViewImports.cshtml └── _ViewStart.cshtml ├── appsettings.Development.json ├── appsettings.json ├── bower.json ├── bundleconfig.json └── wwwroot ├── css ├── site.css └── site.min.css ├── favicon.ico ├── images ├── banner1.svg ├── banner2.svg ├── banner3.svg └── banner4.svg ├── js ├── site.js └── site.min.js └── lib ├── bootstrap ├── .bower.json ├── LICENSE └── dist │ ├── css │ ├── bootstrap-theme.css │ ├── bootstrap-theme.css.map │ ├── bootstrap-theme.min.css │ ├── bootstrap-theme.min.css.map │ ├── bootstrap.css │ ├── bootstrap.css.map │ ├── bootstrap.min.css │ └── bootstrap.min.css.map │ ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 │ └── js │ ├── bootstrap.js │ ├── bootstrap.min.js │ └── npm.js ├── jquery-validation-unobtrusive ├── .bower.json ├── jquery.validate.unobtrusive.js └── jquery.validate.unobtrusive.min.js ├── jquery-validation ├── .bower.json ├── LICENSE.md └── dist │ ├── additional-methods.js │ ├── additional-methods.min.js │ ├── jquery.validate.js │ └── jquery.validate.min.js └── jquery ├── .bower.json ├── LICENSE.txt └── dist ├── jquery.js ├── jquery.min.js └── jquery.min.map /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "wwwroot/lib" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | .vscode/ 25 | 26 | # Visual Studio 2015 cache/options directory 27 | .vs/ 28 | # Uncomment if you have tasks that create the project's static files in wwwroot 29 | #wwwroot/ 30 | 31 | # MSTest test Results 32 | [Tt]est[Rr]esult*/ 33 | [Bb]uild[Ll]og.* 34 | 35 | # NUNIT 36 | *.VisualState.xml 37 | TestResult.xml 38 | 39 | # Build Results of an ATL Project 40 | [Dd]ebugPS/ 41 | [Rr]eleasePS/ 42 | dlldata.c 43 | 44 | # DNX 45 | project.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | # TODO: Comment the next line if you want to checkin your web deploy settings 143 | # but database connection strings (with potential passwords) will be unencrypted 144 | *.pubxml 145 | *.publishproj 146 | 147 | # NuGet Packages 148 | *.nupkg 149 | # The packages folder can be ignored because of Package Restore 150 | **/packages/* 151 | # except build/, which is used as an MSBuild target. 152 | !**/packages/build/ 153 | # Uncomment if necessary however generally it will be regenerated when needed 154 | #!**/packages/repositories.config 155 | 156 | # Microsoft Azure Build Output 157 | csx/ 158 | *.build.csdef 159 | 160 | # Microsoft Azure Emulator 161 | ecf/ 162 | rcf/ 163 | 164 | # Microsoft Azure ApplicationInsights config file 165 | ApplicationInsights.config 166 | 167 | # Windows Store app package directory 168 | AppPackages/ 169 | BundleArtifacts/ 170 | 171 | # Visual Studio cache files 172 | # files ending in .cache can be ignored 173 | *.[Cc]ache 174 | # but keep track of directories ending in .cache 175 | !*.[Cc]ache/ 176 | 177 | # Others 178 | ClientBin/ 179 | ~$* 180 | *~ 181 | *.dbmdl 182 | *.dbproj.schemaview 183 | *.pfx 184 | *.publishsettings 185 | node_modules/ 186 | orleans.codegen.cs 187 | 188 | # RIA/Silverlight projects 189 | Generated_Code/ 190 | 191 | # Backup & report files from converting an old project file 192 | # to a newer Visual Studio version. Backup files are not needed, 193 | # because we have git ;-) 194 | _UpgradeReport_Files/ 195 | Backup*/ 196 | UpgradeLog*.XML 197 | UpgradeLog*.htm 198 | 199 | # SQL Server files 200 | *.mdf 201 | *.ldf 202 | 203 | # Business Intelligence projects 204 | *.rdl.data 205 | *.bim.layout 206 | *.bim_*.settings 207 | 208 | # Microsoft Fakes 209 | FakesAssemblies/ 210 | 211 | # GhostDoc plugin setting file 212 | *.GhostDoc.xml 213 | 214 | # Node.js Tools for Visual Studio 215 | .ntvs_analysis.dat 216 | 217 | # Visual Studio 6 build log 218 | *.plg 219 | 220 | # Visual Studio 6 workspace options file 221 | *.opt 222 | 223 | # Visual Studio LightSwitch build output 224 | **/*.HTMLClient/GeneratedArtifacts 225 | **/*.DesktopClient/GeneratedArtifacts 226 | **/*.DesktopClient/ModelManifest.xml 227 | **/*.Server/GeneratedArtifacts 228 | **/*.Server/ModelManifest.xml 229 | _Pvt_Extensions 230 | 231 | # Paket dependency manager 232 | .paket/paket.exe 233 | 234 | # FAKE - F# Make 235 | .fake/ 236 | -------------------------------------------------------------------------------- /ASP.NET-core-role-based-authentication.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | aspnet-ASP.NET_core_role_based_authentication-018C7A66-6420-4E86-94C4-CC57B267B3F6 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Controllers/AccountController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Security.Claims; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Authentication; 7 | using Microsoft.AspNetCore.Authorization; 8 | using Microsoft.AspNetCore.Identity; 9 | using Microsoft.AspNetCore.Mvc; 10 | using Microsoft.AspNetCore.Mvc.Rendering; 11 | using Microsoft.Extensions.Logging; 12 | using Microsoft.Extensions.Options; 13 | using ASP.NET_core_role_based_authentication.Models; 14 | using ASP.NET_core_role_based_authentication.Models.AccountViewModels; 15 | using ASP.NET_core_role_based_authentication.Services; 16 | 17 | namespace ASP.NET_core_role_based_authentication.Controllers 18 | { 19 | [Authorize] 20 | [Route("[controller]/[action]")] 21 | public class AccountController : Controller 22 | { 23 | private readonly UserManager _userManager; 24 | private readonly SignInManager _signInManager; 25 | private readonly IEmailSender _emailSender; 26 | private readonly ILogger _logger; 27 | 28 | public AccountController( 29 | UserManager userManager, 30 | SignInManager signInManager, 31 | IEmailSender emailSender, 32 | ILogger logger) 33 | { 34 | _userManager = userManager; 35 | _signInManager = signInManager; 36 | _emailSender = emailSender; 37 | _logger = logger; 38 | } 39 | 40 | [TempData] 41 | public string ErrorMessage { get; set; } 42 | 43 | [HttpGet] 44 | [AllowAnonymous] 45 | public async Task Login(string returnUrl = null) 46 | { 47 | // Clear the existing external cookie to ensure a clean login process 48 | await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); 49 | 50 | ViewData["ReturnUrl"] = returnUrl; 51 | return View(); 52 | } 53 | 54 | [HttpPost] 55 | [AllowAnonymous] 56 | [ValidateAntiForgeryToken] 57 | public async Task Login(LoginViewModel model, string returnUrl = null) 58 | { 59 | ViewData["ReturnUrl"] = returnUrl; 60 | if (ModelState.IsValid) 61 | { 62 | // This doesn't count login failures towards account lockout 63 | // To enable password failures to trigger account lockout, set lockoutOnFailure: true 64 | var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false); 65 | if (result.Succeeded) 66 | { 67 | _logger.LogInformation("User logged in."); 68 | return RedirectToLocal(returnUrl); 69 | } 70 | if (result.RequiresTwoFactor) 71 | { 72 | return RedirectToAction(nameof(LoginWith2fa), new { returnUrl, model.RememberMe }); 73 | } 74 | if (result.IsLockedOut) 75 | { 76 | _logger.LogWarning("User account locked out."); 77 | return RedirectToAction(nameof(Lockout)); 78 | } 79 | else 80 | { 81 | ModelState.AddModelError(string.Empty, "Invalid login attempt."); 82 | return View(model); 83 | } 84 | } 85 | 86 | // If we got this far, something failed, redisplay form 87 | return View(model); 88 | } 89 | 90 | [HttpGet] 91 | [AllowAnonymous] 92 | public async Task LoginWith2fa(bool rememberMe, string returnUrl = null) 93 | { 94 | // Ensure the user has gone through the username & password screen first 95 | var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); 96 | 97 | if (user == null) 98 | { 99 | throw new ApplicationException($"Unable to load two-factor authentication user."); 100 | } 101 | 102 | var model = new LoginWith2faViewModel { RememberMe = rememberMe }; 103 | ViewData["ReturnUrl"] = returnUrl; 104 | 105 | return View(model); 106 | } 107 | 108 | [HttpPost] 109 | [AllowAnonymous] 110 | [ValidateAntiForgeryToken] 111 | public async Task LoginWith2fa(LoginWith2faViewModel model, bool rememberMe, string returnUrl = null) 112 | { 113 | if (!ModelState.IsValid) 114 | { 115 | return View(model); 116 | } 117 | 118 | var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); 119 | if (user == null) 120 | { 121 | throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 122 | } 123 | 124 | var authenticatorCode = model.TwoFactorCode.Replace(" ", string.Empty).Replace("-", string.Empty); 125 | 126 | var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, rememberMe, model.RememberMachine); 127 | 128 | if (result.Succeeded) 129 | { 130 | _logger.LogInformation("User with ID {UserId} logged in with 2fa.", user.Id); 131 | return RedirectToLocal(returnUrl); 132 | } 133 | else if (result.IsLockedOut) 134 | { 135 | _logger.LogWarning("User with ID {UserId} account locked out.", user.Id); 136 | return RedirectToAction(nameof(Lockout)); 137 | } 138 | else 139 | { 140 | _logger.LogWarning("Invalid authenticator code entered for user with ID {UserId}.", user.Id); 141 | ModelState.AddModelError(string.Empty, "Invalid authenticator code."); 142 | return View(); 143 | } 144 | } 145 | 146 | [HttpGet] 147 | [AllowAnonymous] 148 | public async Task LoginWithRecoveryCode(string returnUrl = null) 149 | { 150 | // Ensure the user has gone through the username & password screen first 151 | var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); 152 | if (user == null) 153 | { 154 | throw new ApplicationException($"Unable to load two-factor authentication user."); 155 | } 156 | 157 | ViewData["ReturnUrl"] = returnUrl; 158 | 159 | return View(); 160 | } 161 | 162 | [HttpPost] 163 | [AllowAnonymous] 164 | [ValidateAntiForgeryToken] 165 | public async Task LoginWithRecoveryCode(LoginWithRecoveryCodeViewModel model, string returnUrl = null) 166 | { 167 | if (!ModelState.IsValid) 168 | { 169 | return View(model); 170 | } 171 | 172 | var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); 173 | if (user == null) 174 | { 175 | throw new ApplicationException($"Unable to load two-factor authentication user."); 176 | } 177 | 178 | var recoveryCode = model.RecoveryCode.Replace(" ", string.Empty); 179 | 180 | var result = await _signInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode); 181 | 182 | if (result.Succeeded) 183 | { 184 | _logger.LogInformation("User with ID {UserId} logged in with a recovery code.", user.Id); 185 | return RedirectToLocal(returnUrl); 186 | } 187 | if (result.IsLockedOut) 188 | { 189 | _logger.LogWarning("User with ID {UserId} account locked out.", user.Id); 190 | return RedirectToAction(nameof(Lockout)); 191 | } 192 | else 193 | { 194 | _logger.LogWarning("Invalid recovery code entered for user with ID {UserId}", user.Id); 195 | ModelState.AddModelError(string.Empty, "Invalid recovery code entered."); 196 | return View(); 197 | } 198 | } 199 | 200 | [HttpGet] 201 | [AllowAnonymous] 202 | public IActionResult Lockout() 203 | { 204 | return View(); 205 | } 206 | 207 | [HttpGet] 208 | [AllowAnonymous] 209 | public IActionResult Register(string returnUrl = null) 210 | { 211 | ViewData["ReturnUrl"] = returnUrl; 212 | return View(); 213 | } 214 | 215 | [HttpPost] 216 | [AllowAnonymous] 217 | [ValidateAntiForgeryToken] 218 | public async Task Register(RegisterViewModel model, string returnUrl = null) 219 | { 220 | ViewData["ReturnUrl"] = returnUrl; 221 | if (ModelState.IsValid) 222 | { 223 | var user = new ApplicationUser { UserName = model.Email, Email = model.Email }; 224 | var result = await _userManager.CreateAsync(user, model.Password); 225 | if (result.Succeeded) 226 | { 227 | _logger.LogInformation("User created a new account with password."); 228 | 229 | // Add a user to the default role, or any role you prefer here 230 | await _userManager.AddToRoleAsync(user, "Member"); 231 | 232 | var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); 233 | var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme); 234 | await _emailSender.SendEmailConfirmationAsync(model.Email, callbackUrl); 235 | 236 | await _signInManager.SignInAsync(user, isPersistent: false); 237 | _logger.LogInformation("User created a new account with password."); 238 | return RedirectToLocal(returnUrl); 239 | } 240 | AddErrors(result); 241 | } 242 | 243 | // If we got this far, something failed, redisplay form 244 | return View(model); 245 | } 246 | 247 | [HttpPost] 248 | [ValidateAntiForgeryToken] 249 | public async Task Logout() 250 | { 251 | await _signInManager.SignOutAsync(); 252 | _logger.LogInformation("User logged out."); 253 | return RedirectToAction(nameof(HomeController.Index), "Home"); 254 | } 255 | 256 | [HttpPost] 257 | [AllowAnonymous] 258 | [ValidateAntiForgeryToken] 259 | public IActionResult ExternalLogin(string provider, string returnUrl = null) 260 | { 261 | // Request a redirect to the external login provider. 262 | var redirectUrl = Url.Action(nameof(ExternalLoginCallback), "Account", new { returnUrl }); 263 | var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); 264 | return Challenge(properties, provider); 265 | } 266 | 267 | [HttpGet] 268 | [AllowAnonymous] 269 | public async Task ExternalLoginCallback(string returnUrl = null, string remoteError = null) 270 | { 271 | if (remoteError != null) 272 | { 273 | ErrorMessage = $"Error from external provider: {remoteError}"; 274 | return RedirectToAction(nameof(Login)); 275 | } 276 | var info = await _signInManager.GetExternalLoginInfoAsync(); 277 | if (info == null) 278 | { 279 | return RedirectToAction(nameof(Login)); 280 | } 281 | 282 | // Sign in the user with this external login provider if the user already has a login. 283 | var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true); 284 | if (result.Succeeded) 285 | { 286 | _logger.LogInformation("User logged in with {Name} provider.", info.LoginProvider); 287 | return RedirectToLocal(returnUrl); 288 | } 289 | if (result.IsLockedOut) 290 | { 291 | return RedirectToAction(nameof(Lockout)); 292 | } 293 | else 294 | { 295 | // If the user does not have an account, then ask the user to create an account. 296 | ViewData["ReturnUrl"] = returnUrl; 297 | ViewData["LoginProvider"] = info.LoginProvider; 298 | var email = info.Principal.FindFirstValue(ClaimTypes.Email); 299 | return View("ExternalLogin", new ExternalLoginViewModel { Email = email }); 300 | } 301 | } 302 | 303 | [HttpPost] 304 | [AllowAnonymous] 305 | [ValidateAntiForgeryToken] 306 | public async Task ExternalLoginConfirmation(ExternalLoginViewModel model, string returnUrl = null) 307 | { 308 | if (ModelState.IsValid) 309 | { 310 | // Get the information about the user from the external login provider 311 | var info = await _signInManager.GetExternalLoginInfoAsync(); 312 | if (info == null) 313 | { 314 | throw new ApplicationException("Error loading external login information during confirmation."); 315 | } 316 | var user = new ApplicationUser { UserName = model.Email, Email = model.Email }; 317 | var result = await _userManager.CreateAsync(user); 318 | if (result.Succeeded) 319 | { 320 | result = await _userManager.AddLoginAsync(user, info); 321 | if (result.Succeeded) 322 | { 323 | await _signInManager.SignInAsync(user, isPersistent: false); 324 | _logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider); 325 | return RedirectToLocal(returnUrl); 326 | } 327 | } 328 | AddErrors(result); 329 | } 330 | 331 | ViewData["ReturnUrl"] = returnUrl; 332 | return View(nameof(ExternalLogin), model); 333 | } 334 | 335 | [HttpGet] 336 | [AllowAnonymous] 337 | public async Task ConfirmEmail(string userId, string code) 338 | { 339 | if (userId == null || code == null) 340 | { 341 | return RedirectToAction(nameof(HomeController.Index), "Home"); 342 | } 343 | var user = await _userManager.FindByIdAsync(userId); 344 | if (user == null) 345 | { 346 | throw new ApplicationException($"Unable to load user with ID '{userId}'."); 347 | } 348 | var result = await _userManager.ConfirmEmailAsync(user, code); 349 | return View(result.Succeeded ? "ConfirmEmail" : "Error"); 350 | } 351 | 352 | [HttpGet] 353 | [AllowAnonymous] 354 | public IActionResult ForgotPassword() 355 | { 356 | return View(); 357 | } 358 | 359 | [HttpPost] 360 | [AllowAnonymous] 361 | [ValidateAntiForgeryToken] 362 | public async Task ForgotPassword(ForgotPasswordViewModel model) 363 | { 364 | if (ModelState.IsValid) 365 | { 366 | var user = await _userManager.FindByEmailAsync(model.Email); 367 | if (user == null || !(await _userManager.IsEmailConfirmedAsync(user))) 368 | { 369 | // Don't reveal that the user does not exist or is not confirmed 370 | return RedirectToAction(nameof(ForgotPasswordConfirmation)); 371 | } 372 | 373 | // For more information on how to enable account confirmation and password reset please 374 | // visit https://go.microsoft.com/fwlink/?LinkID=532713 375 | var code = await _userManager.GeneratePasswordResetTokenAsync(user); 376 | var callbackUrl = Url.ResetPasswordCallbackLink(user.Id, code, Request.Scheme); 377 | await _emailSender.SendEmailAsync(model.Email, "Reset Password", 378 | $"Please reset your password by clicking here: link"); 379 | return RedirectToAction(nameof(ForgotPasswordConfirmation)); 380 | } 381 | 382 | // If we got this far, something failed, redisplay form 383 | return View(model); 384 | } 385 | 386 | [HttpGet] 387 | [AllowAnonymous] 388 | public IActionResult ForgotPasswordConfirmation() 389 | { 390 | return View(); 391 | } 392 | 393 | [HttpGet] 394 | [AllowAnonymous] 395 | public IActionResult ResetPassword(string code = null) 396 | { 397 | if (code == null) 398 | { 399 | throw new ApplicationException("A code must be supplied for password reset."); 400 | } 401 | var model = new ResetPasswordViewModel { Code = code }; 402 | return View(model); 403 | } 404 | 405 | [HttpPost] 406 | [AllowAnonymous] 407 | [ValidateAntiForgeryToken] 408 | public async Task ResetPassword(ResetPasswordViewModel model) 409 | { 410 | if (!ModelState.IsValid) 411 | { 412 | return View(model); 413 | } 414 | var user = await _userManager.FindByEmailAsync(model.Email); 415 | if (user == null) 416 | { 417 | // Don't reveal that the user does not exist 418 | return RedirectToAction(nameof(ResetPasswordConfirmation)); 419 | } 420 | var result = await _userManager.ResetPasswordAsync(user, model.Code, model.Password); 421 | if (result.Succeeded) 422 | { 423 | return RedirectToAction(nameof(ResetPasswordConfirmation)); 424 | } 425 | AddErrors(result); 426 | return View(); 427 | } 428 | 429 | [HttpGet] 430 | [AllowAnonymous] 431 | public IActionResult ResetPasswordConfirmation() 432 | { 433 | return View(); 434 | } 435 | 436 | 437 | [HttpGet] 438 | public IActionResult AccessDenied() 439 | { 440 | return View(); 441 | } 442 | 443 | #region Helpers 444 | 445 | private void AddErrors(IdentityResult result) 446 | { 447 | foreach (var error in result.Errors) 448 | { 449 | ModelState.AddModelError(string.Empty, error.Description); 450 | } 451 | } 452 | 453 | private IActionResult RedirectToLocal(string returnUrl) 454 | { 455 | if (Url.IsLocalUrl(returnUrl)) 456 | { 457 | return Redirect(returnUrl); 458 | } 459 | else 460 | { 461 | return RedirectToAction(nameof(HomeController.Index), "Home"); 462 | } 463 | } 464 | 465 | #endregion 466 | } 467 | } 468 | -------------------------------------------------------------------------------- /Controllers/AdminController.cs: -------------------------------------------------------------------------------- 1 | 2 | using Microsoft.AspNetCore.Authorization; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace ASP.NET_core_role_based_authentication.Controllers 6 | { 7 | // [Authorize(Roles="Admin")] 8 | [Authorize(Policy = "RequireAdminRole")] 9 | public class AdminController : Controller 10 | { 11 | public IActionResult Index() 12 | { 13 | return View(); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Mvc; 7 | using ASP.NET_core_role_based_authentication.Models; 8 | 9 | namespace ASP.NET_core_role_based_authentication.Controllers 10 | { 11 | public class HomeController : Controller 12 | { 13 | public IActionResult Index() 14 | { 15 | return View(); 16 | } 17 | 18 | public IActionResult About() 19 | { 20 | ViewData["Message"] = "Your application description page."; 21 | 22 | return View(); 23 | } 24 | 25 | public IActionResult Contact() 26 | { 27 | ViewData["Message"] = "Your contact page."; 28 | 29 | return View(); 30 | } 31 | 32 | public IActionResult Error() 33 | { 34 | return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Data/ApplicationDbContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 6 | using Microsoft.EntityFrameworkCore; 7 | using ASP.NET_core_role_based_authentication.Models; 8 | 9 | namespace ASP.NET_core_role_based_authentication.Data 10 | { 11 | public class ApplicationDbContext : IdentityDbContext 12 | { 13 | public ApplicationDbContext(DbContextOptions options) 14 | : base(options) 15 | { 16 | } 17 | 18 | protected override void OnModelCreating(ModelBuilder builder) 19 | { 20 | base.OnModelCreating(builder); 21 | // Customize the ASP.NET Identity model and override the defaults if needed. 22 | // For example, you can rename the ASP.NET Identity table names and more. 23 | // Add your customizations after calling base.OnModelCreating(builder); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Microsoft.EntityFrameworkCore.Metadata; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | 7 | namespace ASP.NET_core_role_based_authentication.Data.Migrations 8 | { 9 | [DbContext(typeof(ApplicationDbContext))] 10 | [Migration("00000000000000_CreateIdentitySchema")] 11 | partial class CreateIdentitySchema 12 | { 13 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 14 | { 15 | modelBuilder 16 | .HasAnnotation("ProductVersion", "1.0.2"); 17 | 18 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => 19 | { 20 | b.Property("Id"); 21 | 22 | b.Property("ConcurrencyStamp") 23 | .IsConcurrencyToken(); 24 | 25 | b.Property("Name") 26 | .HasMaxLength(256); 27 | 28 | b.Property("NormalizedName") 29 | .HasMaxLength(256); 30 | 31 | b.HasKey("Id"); 32 | 33 | b.HasIndex("NormalizedName") 34 | .HasName("RoleNameIndex"); 35 | 36 | b.ToTable("AspNetRoles"); 37 | }); 38 | 39 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 40 | { 41 | b.Property("Id") 42 | .ValueGeneratedOnAdd(); 43 | 44 | b.Property("ClaimType"); 45 | 46 | b.Property("ClaimValue"); 47 | 48 | b.Property("RoleId") 49 | .IsRequired(); 50 | 51 | b.HasKey("Id"); 52 | 53 | b.HasIndex("RoleId"); 54 | 55 | b.ToTable("AspNetRoleClaims"); 56 | }); 57 | 58 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 59 | { 60 | b.Property("Id") 61 | .ValueGeneratedOnAdd(); 62 | 63 | b.Property("ClaimType"); 64 | 65 | b.Property("ClaimValue"); 66 | 67 | b.Property("UserId") 68 | .IsRequired(); 69 | 70 | b.HasKey("Id"); 71 | 72 | b.HasIndex("UserId"); 73 | 74 | b.ToTable("AspNetUserClaims"); 75 | }); 76 | 77 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 78 | { 79 | b.Property("LoginProvider"); 80 | 81 | b.Property("ProviderKey"); 82 | 83 | b.Property("ProviderDisplayName"); 84 | 85 | b.Property("UserId") 86 | .IsRequired(); 87 | 88 | b.HasKey("LoginProvider", "ProviderKey"); 89 | 90 | b.HasIndex("UserId"); 91 | 92 | b.ToTable("AspNetUserLogins"); 93 | }); 94 | 95 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 96 | { 97 | b.Property("UserId"); 98 | 99 | b.Property("RoleId"); 100 | 101 | b.HasKey("UserId", "RoleId"); 102 | 103 | b.HasIndex("RoleId"); 104 | 105 | b.HasIndex("UserId"); 106 | 107 | b.ToTable("AspNetUserRoles"); 108 | }); 109 | 110 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 111 | { 112 | b.Property("UserId"); 113 | 114 | b.Property("LoginProvider"); 115 | 116 | b.Property("Name"); 117 | 118 | b.Property("Value"); 119 | 120 | b.HasKey("UserId", "LoginProvider", "Name"); 121 | 122 | b.ToTable("AspNetUserTokens"); 123 | }); 124 | 125 | modelBuilder.Entity("ASP.NET_core_role_based_authentication.Models.ApplicationUser", b => 126 | { 127 | b.Property("Id"); 128 | 129 | b.Property("AccessFailedCount"); 130 | 131 | b.Property("ConcurrencyStamp") 132 | .IsConcurrencyToken(); 133 | 134 | b.Property("Email") 135 | .HasMaxLength(256); 136 | 137 | b.Property("EmailConfirmed"); 138 | 139 | b.Property("LockoutEnabled"); 140 | 141 | b.Property("LockoutEnd"); 142 | 143 | b.Property("NormalizedEmail") 144 | .HasMaxLength(256); 145 | 146 | b.Property("NormalizedUserName") 147 | .HasMaxLength(256); 148 | 149 | b.Property("PasswordHash"); 150 | 151 | b.Property("PhoneNumber"); 152 | 153 | b.Property("PhoneNumberConfirmed"); 154 | 155 | b.Property("SecurityStamp"); 156 | 157 | b.Property("TwoFactorEnabled"); 158 | 159 | b.Property("UserName") 160 | .HasMaxLength(256); 161 | 162 | b.HasKey("Id"); 163 | 164 | b.HasIndex("NormalizedEmail") 165 | .HasName("EmailIndex"); 166 | 167 | b.HasIndex("NormalizedUserName") 168 | .IsUnique() 169 | .HasName("UserNameIndex"); 170 | 171 | b.ToTable("AspNetUsers"); 172 | }); 173 | 174 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 175 | { 176 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") 177 | .WithMany("Claims") 178 | .HasForeignKey("RoleId") 179 | .OnDelete(DeleteBehavior.Cascade); 180 | }); 181 | 182 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 183 | { 184 | b.HasOne("ASP.NET_core_role_based_authentication.Models.ApplicationUser") 185 | .WithMany("Claims") 186 | .HasForeignKey("UserId") 187 | .OnDelete(DeleteBehavior.Cascade); 188 | }); 189 | 190 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 191 | { 192 | b.HasOne("ASP.NET_core_role_based_authentication.Models.ApplicationUser") 193 | .WithMany("Logins") 194 | .HasForeignKey("UserId") 195 | .OnDelete(DeleteBehavior.Cascade); 196 | }); 197 | 198 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 199 | { 200 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") 201 | .WithMany("Users") 202 | .HasForeignKey("RoleId") 203 | .OnDelete(DeleteBehavior.Cascade); 204 | 205 | b.HasOne("ASP.NET_core_role_based_authentication.Models.ApplicationUser") 206 | .WithMany("Roles") 207 | .HasForeignKey("UserId") 208 | .OnDelete(DeleteBehavior.Cascade); 209 | }); 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /Data/Migrations/00000000000000_CreateIdentitySchema.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.EntityFrameworkCore.Migrations; 4 | 5 | namespace ASP.NET_core_role_based_authentication.Data.Migrations 6 | { 7 | public partial class CreateIdentitySchema : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.CreateTable( 12 | name: "AspNetRoles", 13 | columns: table => new 14 | { 15 | Id = table.Column(nullable: false), 16 | ConcurrencyStamp = table.Column(nullable: true), 17 | Name = table.Column(maxLength: 256, nullable: true), 18 | NormalizedName = table.Column(maxLength: 256, nullable: true) 19 | }, 20 | constraints: table => 21 | { 22 | table.PrimaryKey("PK_AspNetRoles", x => x.Id); 23 | }); 24 | 25 | migrationBuilder.CreateTable( 26 | name: "AspNetUserTokens", 27 | columns: table => new 28 | { 29 | UserId = table.Column(nullable: false), 30 | LoginProvider = table.Column(nullable: false), 31 | Name = table.Column(nullable: false), 32 | Value = table.Column(nullable: true) 33 | }, 34 | constraints: table => 35 | { 36 | table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); 37 | }); 38 | 39 | migrationBuilder.CreateTable( 40 | name: "AspNetUsers", 41 | columns: table => new 42 | { 43 | Id = table.Column(nullable: false), 44 | AccessFailedCount = table.Column(nullable: false), 45 | ConcurrencyStamp = table.Column(nullable: true), 46 | Email = table.Column(maxLength: 256, nullable: true), 47 | EmailConfirmed = table.Column(nullable: false), 48 | LockoutEnabled = table.Column(nullable: false), 49 | LockoutEnd = table.Column(nullable: true), 50 | NormalizedEmail = table.Column(maxLength: 256, nullable: true), 51 | NormalizedUserName = table.Column(maxLength: 256, nullable: true), 52 | PasswordHash = table.Column(nullable: true), 53 | PhoneNumber = table.Column(nullable: true), 54 | PhoneNumberConfirmed = table.Column(nullable: false), 55 | SecurityStamp = table.Column(nullable: true), 56 | TwoFactorEnabled = table.Column(nullable: false), 57 | UserName = table.Column(maxLength: 256, nullable: true) 58 | }, 59 | constraints: table => 60 | { 61 | table.PrimaryKey("PK_AspNetUsers", x => x.Id); 62 | }); 63 | 64 | migrationBuilder.CreateTable( 65 | name: "AspNetRoleClaims", 66 | columns: table => new 67 | { 68 | Id = table.Column(nullable: false) 69 | .Annotation("Autoincrement", true), 70 | ClaimType = table.Column(nullable: true), 71 | ClaimValue = table.Column(nullable: true), 72 | RoleId = table.Column(nullable: false) 73 | }, 74 | constraints: table => 75 | { 76 | table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); 77 | table.ForeignKey( 78 | name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", 79 | column: x => x.RoleId, 80 | principalTable: "AspNetRoles", 81 | principalColumn: "Id", 82 | onDelete: ReferentialAction.Cascade); 83 | }); 84 | 85 | migrationBuilder.CreateTable( 86 | name: "AspNetUserClaims", 87 | columns: table => new 88 | { 89 | Id = table.Column(nullable: false) 90 | .Annotation("Autoincrement", true), 91 | ClaimType = table.Column(nullable: true), 92 | ClaimValue = table.Column(nullable: true), 93 | UserId = table.Column(nullable: false) 94 | }, 95 | constraints: table => 96 | { 97 | table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); 98 | table.ForeignKey( 99 | name: "FK_AspNetUserClaims_AspNetUsers_UserId", 100 | column: x => x.UserId, 101 | principalTable: "AspNetUsers", 102 | principalColumn: "Id", 103 | onDelete: ReferentialAction.Cascade); 104 | }); 105 | 106 | migrationBuilder.CreateTable( 107 | name: "AspNetUserLogins", 108 | columns: table => new 109 | { 110 | LoginProvider = table.Column(nullable: false), 111 | ProviderKey = table.Column(nullable: false), 112 | ProviderDisplayName = table.Column(nullable: true), 113 | UserId = table.Column(nullable: false) 114 | }, 115 | constraints: table => 116 | { 117 | table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); 118 | table.ForeignKey( 119 | name: "FK_AspNetUserLogins_AspNetUsers_UserId", 120 | column: x => x.UserId, 121 | principalTable: "AspNetUsers", 122 | principalColumn: "Id", 123 | onDelete: ReferentialAction.Cascade); 124 | }); 125 | 126 | migrationBuilder.CreateTable( 127 | name: "AspNetUserRoles", 128 | columns: table => new 129 | { 130 | UserId = table.Column(nullable: false), 131 | RoleId = table.Column(nullable: false) 132 | }, 133 | constraints: table => 134 | { 135 | table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); 136 | table.ForeignKey( 137 | name: "FK_AspNetUserRoles_AspNetRoles_RoleId", 138 | column: x => x.RoleId, 139 | principalTable: "AspNetRoles", 140 | principalColumn: "Id", 141 | onDelete: ReferentialAction.Cascade); 142 | table.ForeignKey( 143 | name: "FK_AspNetUserRoles_AspNetUsers_UserId", 144 | column: x => x.UserId, 145 | principalTable: "AspNetUsers", 146 | principalColumn: "Id", 147 | onDelete: ReferentialAction.Cascade); 148 | }); 149 | 150 | migrationBuilder.CreateIndex( 151 | name: "RoleNameIndex", 152 | table: "AspNetRoles", 153 | column: "NormalizedName"); 154 | 155 | migrationBuilder.CreateIndex( 156 | name: "IX_AspNetRoleClaims_RoleId", 157 | table: "AspNetRoleClaims", 158 | column: "RoleId"); 159 | 160 | migrationBuilder.CreateIndex( 161 | name: "IX_AspNetUserClaims_UserId", 162 | table: "AspNetUserClaims", 163 | column: "UserId"); 164 | 165 | migrationBuilder.CreateIndex( 166 | name: "IX_AspNetUserLogins_UserId", 167 | table: "AspNetUserLogins", 168 | column: "UserId"); 169 | 170 | migrationBuilder.CreateIndex( 171 | name: "IX_AspNetUserRoles_RoleId", 172 | table: "AspNetUserRoles", 173 | column: "RoleId"); 174 | 175 | migrationBuilder.CreateIndex( 176 | name: "IX_AspNetUserRoles_UserId", 177 | table: "AspNetUserRoles", 178 | column: "UserId"); 179 | 180 | migrationBuilder.CreateIndex( 181 | name: "EmailIndex", 182 | table: "AspNetUsers", 183 | column: "NormalizedEmail"); 184 | 185 | migrationBuilder.CreateIndex( 186 | name: "UserNameIndex", 187 | table: "AspNetUsers", 188 | column: "NormalizedUserName", 189 | unique: true); 190 | } 191 | 192 | protected override void Down(MigrationBuilder migrationBuilder) 193 | { 194 | migrationBuilder.DropTable( 195 | name: "AspNetRoleClaims"); 196 | 197 | migrationBuilder.DropTable( 198 | name: "AspNetUserClaims"); 199 | 200 | migrationBuilder.DropTable( 201 | name: "AspNetUserLogins"); 202 | 203 | migrationBuilder.DropTable( 204 | name: "AspNetUserRoles"); 205 | 206 | migrationBuilder.DropTable( 207 | name: "AspNetUserTokens"); 208 | 209 | migrationBuilder.DropTable( 210 | name: "AspNetRoles"); 211 | 212 | migrationBuilder.DropTable( 213 | name: "AspNetUsers"); 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /Data/Migrations/ApplicationDbContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Microsoft.EntityFrameworkCore.Metadata; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | 7 | namespace ASP.NET_core_role_based_authentication.Data.Migrations 8 | { 9 | [DbContext(typeof(ApplicationDbContext))] 10 | partial class ApplicationDbContextModelSnapshot : ModelSnapshot 11 | { 12 | protected override void BuildModel(ModelBuilder modelBuilder) 13 | { 14 | modelBuilder 15 | .HasAnnotation("ProductVersion", "1.0.2"); 16 | 17 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole", b => 18 | { 19 | b.Property("Id"); 20 | 21 | b.Property("ConcurrencyStamp") 22 | .IsConcurrencyToken(); 23 | 24 | b.Property("Name") 25 | .HasMaxLength(256); 26 | 27 | b.Property("NormalizedName") 28 | .HasMaxLength(256); 29 | 30 | b.HasKey("Id"); 31 | 32 | b.HasIndex("NormalizedName") 33 | .HasName("RoleNameIndex"); 34 | 35 | b.ToTable("AspNetRoles"); 36 | }); 37 | 38 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRoleClaim", b => 39 | { 40 | b.Property("Id") 41 | .ValueGeneratedOnAdd(); 42 | 43 | b.Property("ClaimType"); 44 | 45 | b.Property("ClaimValue"); 46 | 47 | b.Property("RoleId") 48 | .IsRequired(); 49 | 50 | b.HasKey("Id"); 51 | 52 | b.HasIndex("RoleId"); 53 | 54 | b.ToTable("AspNetRoleClaims"); 55 | }); 56 | 57 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserClaim", b => 58 | { 59 | b.Property("Id") 60 | .ValueGeneratedOnAdd(); 61 | 62 | b.Property("ClaimType"); 63 | 64 | b.Property("ClaimValue"); 65 | 66 | b.Property("UserId") 67 | .IsRequired(); 68 | 69 | b.HasKey("Id"); 70 | 71 | b.HasIndex("UserId"); 72 | 73 | b.ToTable("AspNetUserClaims"); 74 | }); 75 | 76 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserLogin", b => 77 | { 78 | b.Property("LoginProvider"); 79 | 80 | b.Property("ProviderKey"); 81 | 82 | b.Property("ProviderDisplayName"); 83 | 84 | b.Property("UserId") 85 | .IsRequired(); 86 | 87 | b.HasKey("LoginProvider", "ProviderKey"); 88 | 89 | b.HasIndex("UserId"); 90 | 91 | b.ToTable("AspNetUserLogins"); 92 | }); 93 | 94 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserRole", b => 95 | { 96 | b.Property("UserId"); 97 | 98 | b.Property("RoleId"); 99 | 100 | b.HasKey("UserId", "RoleId"); 101 | 102 | b.HasIndex("RoleId"); 103 | 104 | b.HasIndex("UserId"); 105 | 106 | b.ToTable("AspNetUserRoles"); 107 | }); 108 | 109 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserToken", b => 110 | { 111 | b.Property("UserId"); 112 | 113 | b.Property("LoginProvider"); 114 | 115 | b.Property("Name"); 116 | 117 | b.Property("Value"); 118 | 119 | b.HasKey("UserId", "LoginProvider", "Name"); 120 | 121 | b.ToTable("AspNetUserTokens"); 122 | }); 123 | 124 | modelBuilder.Entity("ASP.NET_core_role_based_authentication.Models.ApplicationUser", b => 125 | { 126 | b.Property("Id"); 127 | 128 | b.Property("AccessFailedCount"); 129 | 130 | b.Property("ConcurrencyStamp") 131 | .IsConcurrencyToken(); 132 | 133 | b.Property("Email") 134 | .HasMaxLength(256); 135 | 136 | b.Property("EmailConfirmed"); 137 | 138 | b.Property("LockoutEnabled"); 139 | 140 | b.Property("LockoutEnd"); 141 | 142 | b.Property("NormalizedEmail") 143 | .HasMaxLength(256); 144 | 145 | b.Property("NormalizedUserName") 146 | .HasMaxLength(256); 147 | 148 | b.Property("PasswordHash"); 149 | 150 | b.Property("PhoneNumber"); 151 | 152 | b.Property("PhoneNumberConfirmed"); 153 | 154 | b.Property("SecurityStamp"); 155 | 156 | b.Property("TwoFactorEnabled"); 157 | 158 | b.Property("UserName") 159 | .HasMaxLength(256); 160 | 161 | b.HasKey("Id"); 162 | 163 | b.HasIndex("NormalizedEmail") 164 | .HasName("EmailIndex"); 165 | 166 | b.HasIndex("NormalizedUserName") 167 | .IsUnique() 168 | .HasName("UserNameIndex"); 169 | 170 | b.ToTable("AspNetUsers"); 171 | }); 172 | 173 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRoleClaim", b => 174 | { 175 | b.HasOne("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole") 176 | .WithMany("Claims") 177 | .HasForeignKey("RoleId") 178 | .OnDelete(DeleteBehavior.Cascade); 179 | }); 180 | 181 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserClaim", b => 182 | { 183 | b.HasOne("ASP.NET_core_role_based_authentication.Models.ApplicationUser") 184 | .WithMany("Claims") 185 | .HasForeignKey("UserId") 186 | .OnDelete(DeleteBehavior.Cascade); 187 | }); 188 | 189 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserLogin", b => 190 | { 191 | b.HasOne("ASP.NET_core_role_based_authentication.Models.ApplicationUser") 192 | .WithMany("Logins") 193 | .HasForeignKey("UserId") 194 | .OnDelete(DeleteBehavior.Cascade); 195 | }); 196 | 197 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserRole", b => 198 | { 199 | b.HasOne("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole") 200 | .WithMany("Users") 201 | .HasForeignKey("RoleId") 202 | .OnDelete(DeleteBehavior.Cascade); 203 | 204 | b.HasOne("ASP.NET_core_role_based_authentication.Models.ApplicationUser") 205 | .WithMany("Roles") 206 | .HasForeignKey("UserId") 207 | .OnDelete(DeleteBehavior.Cascade); 208 | }); 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /Data/Seed.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using ASP.NET_core_role_based_authentication.Models; 4 | using Microsoft.AspNetCore.Identity; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | 8 | namespace NET_core_role_based_authentication.Data 9 | { 10 | public static class Seed 11 | { 12 | public static async Task CreateRoles(IServiceProvider serviceProvider, IConfiguration Configuration) 13 | { 14 | //adding customs roles 15 | var RoleManager = serviceProvider.GetRequiredService>(); 16 | var UserManager = serviceProvider.GetRequiredService>(); 17 | string[] roleNames = { "Admin", "Manager", "Member" }; 18 | IdentityResult roleResult; 19 | 20 | foreach (var roleName in roleNames) 21 | { 22 | //creating the roles and seeding them to the database 23 | var roleExist = await RoleManager.RoleExistsAsync(roleName); 24 | if (!roleExist) 25 | { 26 | roleResult = await RoleManager.CreateAsync(new IdentityRole(roleName)); 27 | } 28 | } 29 | 30 | //creating a super user who could maintain the web app 31 | var poweruser = new ApplicationUser 32 | { 33 | UserName = Configuration.GetSection("AppSettings")["UserEmail"], 34 | Email = Configuration.GetSection("AppSettings")["UserEmail"] 35 | }; 36 | 37 | string userPassword = Configuration.GetSection("AppSettings")["UserPassword"]; 38 | var user = await UserManager.FindByEmailAsync(Configuration.GetSection("AppSettings")["UserEmail"]); 39 | 40 | if(user == null) 41 | { 42 | var createPowerUser = await UserManager.CreateAsync(poweruser, userPassword); 43 | if (createPowerUser.Succeeded) 44 | { 45 | //here we tie the new user to the "Admin" role 46 | await UserManager.AddToRoleAsync(poweruser, "Admin"); 47 | 48 | } 49 | } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /Extensions/EmailSenderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.Encodings.Web; 5 | using System.Threading.Tasks; 6 | using ASP.NET_core_role_based_authentication.Services; 7 | 8 | namespace ASP.NET_core_role_based_authentication.Services 9 | { 10 | public static class EmailSenderExtensions 11 | { 12 | public static Task SendEmailConfirmationAsync(this IEmailSender emailSender, string email, string link) 13 | { 14 | return emailSender.SendEmailAsync(email, "Confirm your email", 15 | $"Please confirm your account by clicking this link: link"); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Extensions/UrlHelperExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using ASP.NET_core_role_based_authentication.Controllers; 6 | 7 | namespace Microsoft.AspNetCore.Mvc 8 | { 9 | public static class UrlHelperExtensions 10 | { 11 | public static string EmailConfirmationLink(this IUrlHelper urlHelper, string userId, string code, string scheme) 12 | { 13 | return urlHelper.Action( 14 | action: nameof(AccountController.ConfirmEmail), 15 | controller: "Account", 16 | values: new { userId, code }, 17 | protocol: scheme); 18 | } 19 | 20 | public static string ResetPasswordCallbackLink(this IUrlHelper urlHelper, string userId, string code, string scheme) 21 | { 22 | return urlHelper.Action( 23 | action: nameof(AccountController.ResetPassword), 24 | controller: "Account", 25 | values: new { userId, code }, 26 | protocol: scheme); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Temi Lajumoke 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Models/AccountViewModels/ExternalLoginViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace ASP.NET_core_role_based_authentication.Models.AccountViewModels 8 | { 9 | public class ExternalLoginViewModel 10 | { 11 | [Required] 12 | [EmailAddress] 13 | public string Email { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Models/AccountViewModels/ForgotPasswordViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace ASP.NET_core_role_based_authentication.Models.AccountViewModels 8 | { 9 | public class ForgotPasswordViewModel 10 | { 11 | [Required] 12 | [EmailAddress] 13 | public string Email { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Models/AccountViewModels/LoginViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace ASP.NET_core_role_based_authentication.Models.AccountViewModels 8 | { 9 | public class LoginViewModel 10 | { 11 | [Required] 12 | [EmailAddress] 13 | public string Email { get; set; } 14 | 15 | [Required] 16 | [DataType(DataType.Password)] 17 | public string Password { get; set; } 18 | 19 | [Display(Name = "Remember me?")] 20 | public bool RememberMe { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Models/AccountViewModels/LoginWith2faViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace ASP.NET_core_role_based_authentication.Models.AccountViewModels 8 | { 9 | public class LoginWith2faViewModel 10 | { 11 | [Required] 12 | [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 13 | [DataType(DataType.Text)] 14 | [Display(Name = "Authenticator code")] 15 | public string TwoFactorCode { get; set; } 16 | 17 | [Display(Name = "Remember this machine")] 18 | public bool RememberMachine { get; set; } 19 | 20 | public bool RememberMe { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Models/AccountViewModels/LoginWithRecoveryCodeViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace ASP.NET_core_role_based_authentication.Models.AccountViewModels 8 | { 9 | public class LoginWithRecoveryCodeViewModel 10 | { 11 | [Required] 12 | [DataType(DataType.Text)] 13 | [Display(Name = "Recovery Code")] 14 | public string RecoveryCode { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Models/AccountViewModels/RegisterViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace ASP.NET_core_role_based_authentication.Models.AccountViewModels 8 | { 9 | public class RegisterViewModel 10 | { 11 | [Required] 12 | [EmailAddress] 13 | [Display(Name = "Email")] 14 | public string Email { get; set; } 15 | 16 | [Required] 17 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 18 | [DataType(DataType.Password)] 19 | [Display(Name = "Password")] 20 | public string Password { get; set; } 21 | 22 | [DataType(DataType.Password)] 23 | [Display(Name = "Confirm password")] 24 | [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] 25 | public string ConfirmPassword { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Models/AccountViewModels/ResetPasswordViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace ASP.NET_core_role_based_authentication.Models.AccountViewModels 8 | { 9 | public class ResetPasswordViewModel 10 | { 11 | [Required] 12 | [EmailAddress] 13 | public string Email { get; set; } 14 | 15 | [Required] 16 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 17 | [DataType(DataType.Password)] 18 | public string Password { get; set; } 19 | 20 | [DataType(DataType.Password)] 21 | [Display(Name = "Confirm password")] 22 | [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] 23 | public string ConfirmPassword { get; set; } 24 | 25 | public string Code { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Models/ApplicationUser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Identity; 6 | 7 | namespace ASP.NET_core_role_based_authentication.Models 8 | { 9 | // Add profile data for application users by adding properties to the ApplicationUser class 10 | public class ApplicationUser : IdentityUser 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Models/ErrorViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ASP.NET_core_role_based_authentication.Models 4 | { 5 | public class ErrorViewModel 6 | { 7 | public string RequestId { get; set; } 8 | 9 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 10 | } 11 | } -------------------------------------------------------------------------------- /Models/ManageViewModels/ChangePasswordViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace ASP.NET_core_role_based_authentication.Models.ManageViewModels 8 | { 9 | public class ChangePasswordViewModel 10 | { 11 | [Required] 12 | [DataType(DataType.Password)] 13 | [Display(Name = "Current password")] 14 | public string OldPassword { get; set; } 15 | 16 | [Required] 17 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 18 | [DataType(DataType.Password)] 19 | [Display(Name = "New password")] 20 | public string NewPassword { get; set; } 21 | 22 | [DataType(DataType.Password)] 23 | [Display(Name = "Confirm new password")] 24 | [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] 25 | public string ConfirmPassword { get; set; } 26 | 27 | public string StatusMessage { get; set; } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Models/ManageViewModels/EnableAuthenticatorViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.ComponentModel.DataAnnotations; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace ASP.NET_core_role_based_authentication.Models.ManageViewModels 9 | { 10 | public class EnableAuthenticatorViewModel 11 | { 12 | [Required] 13 | [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 14 | [DataType(DataType.Text)] 15 | [Display(Name = "Verification Code")] 16 | public string Code { get; set; } 17 | 18 | [ReadOnly(true)] 19 | public string SharedKey { get; set; } 20 | 21 | public string AuthenticatorUri { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Models/ManageViewModels/ExternalLoginsViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authentication; 6 | using Microsoft.AspNetCore.Identity; 7 | 8 | namespace ASP.NET_core_role_based_authentication.Models.ManageViewModels 9 | { 10 | public class ExternalLoginsViewModel 11 | { 12 | public IList CurrentLogins { get; set; } 13 | 14 | public IList OtherLogins { get; set; } 15 | 16 | public bool ShowRemoveButton { get; set; } 17 | 18 | public string StatusMessage { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Models/ManageViewModels/GenerateRecoveryCodesViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace ASP.NET_core_role_based_authentication.Models.ManageViewModels 8 | { 9 | public class GenerateRecoveryCodesViewModel 10 | { 11 | public string[] RecoveryCodes { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Models/ManageViewModels/IndexViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace ASP.NET_core_role_based_authentication.Models.ManageViewModels 8 | { 9 | public class IndexViewModel 10 | { 11 | public string Username { get; set; } 12 | 13 | public bool IsEmailConfirmed { get; set; } 14 | 15 | [Required] 16 | [EmailAddress] 17 | public string Email { get; set; } 18 | 19 | [Phone] 20 | [Display(Name = "Phone number")] 21 | public string PhoneNumber { get; set; } 22 | 23 | public string StatusMessage { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Models/ManageViewModels/RemoveLoginViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace ASP.NET_core_role_based_authentication.Models.ManageViewModels 8 | { 9 | public class RemoveLoginViewModel 10 | { 11 | public string LoginProvider { get; set; } 12 | public string ProviderKey { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Models/ManageViewModels/SetPasswordViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace ASP.NET_core_role_based_authentication.Models.ManageViewModels 8 | { 9 | public class SetPasswordViewModel 10 | { 11 | [Required] 12 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 13 | [DataType(DataType.Password)] 14 | [Display(Name = "New password")] 15 | public string NewPassword { get; set; } 16 | 17 | [DataType(DataType.Password)] 18 | [Display(Name = "Confirm new password")] 19 | [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] 20 | public string ConfirmPassword { get; set; } 21 | 22 | public string StatusMessage { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Models/ManageViewModels/TwoFactorAuthenticationViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace ASP.NET_core_role_based_authentication.Models.ManageViewModels 8 | { 9 | public class TwoFactorAuthenticationViewModel 10 | { 11 | public bool HasAuthenticator { get; set; } 12 | 13 | public int RecoveryCodesLeft { get; set; } 14 | 15 | public bool Is2faEnabled { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Microsoft.Extensions.Logging; 11 | using NET_core_role_based_authentication.Data; 12 | 13 | namespace ASP.NET_core_role_based_authentication 14 | { 15 | public class Program 16 | { 17 | public static void Main(string[] args) 18 | { 19 | var host = BuildWebHost(args); 20 | 21 | using (var scope = host.Services.CreateScope()) 22 | { 23 | var services = scope.ServiceProvider; 24 | try 25 | { 26 | var serviceProvider = services.GetRequiredService(); 27 | var configuration = services.GetRequiredService(); 28 | Seed.CreateRoles(serviceProvider, configuration).Wait(); 29 | 30 | } 31 | catch (Exception exception) 32 | { 33 | var logger = services.GetRequiredService>(); 34 | logger.LogError(exception, "An error occurred while creating roles"); 35 | } 36 | } 37 | 38 | host.Run(); 39 | } 40 | 41 | public static IWebHost BuildWebHost(string[] args) => 42 | WebHost.CreateDefaultBuilder(args) 43 | .UseStartup() 44 | .Build(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ASP.NET Core role based authentication and custom role creation 2 | Code sample detailing how to create custom roles in ASP.NET core on startup and role-based authentication using role checks and policy based checks. 3 | 4 | ## This application consists of: 5 | 6 | * Sample pages using ASP.NET Core MVC 7 | * [Bower](https://go.microsoft.com/fwlink/?LinkId=518004) for managing client-side libraries 8 | * Theming using [Bootstrap](https://go.microsoft.com/fwlink/?LinkID=398939) 9 | 10 | ## Installing 11 | clone or download this repo into your local computer, check-in to the root directory of the app and run the app 12 | 13 | ### [ASP.NET core 1.x](https://github.com/temilaj/ASP.NET-core-role-based-authentication/tree/version/dotnet-core-1-1) 14 | ### [ASP.NET core 2.x](https://github.com/temilaj/ASP.NET-core-role-based-authentication/tree/version/dotnet-core-2-0) 15 | 16 | ## Run & Deploy 17 | 18 | restore the .NET core packages described in the `package.json`, install the npm and bower dependencies,and then update database with the migrations and run the project: 19 | 20 | ```bash 21 | dotnet run 22 | ``` 23 | 24 | the `dotnet run` command simultaneously re-compiles and runs the `kestrel-server`. 25 | 26 | I would love to hear your [feedback](https://temilajumoke.com) 27 | -------------------------------------------------------------------------------- /Services/EmailSender.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace ASP.NET_core_role_based_authentication.Services 7 | { 8 | // This class is used by the application to send email for account confirmation and password reset. 9 | // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 10 | public class EmailSender : IEmailSender 11 | { 12 | public Task SendEmailAsync(string email, string subject, string message) 13 | { 14 | return Task.CompletedTask; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Services/IEmailSender.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace ASP.NET_core_role_based_authentication.Services 7 | { 8 | public interface IEmailSender 9 | { 10 | Task SendEmailAsync(string email, string subject, string message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Identity; 7 | using Microsoft.EntityFrameworkCore; 8 | using Microsoft.AspNetCore.Hosting; 9 | using Microsoft.Extensions.Configuration; 10 | using Microsoft.Extensions.DependencyInjection; 11 | using ASP.NET_core_role_based_authentication.Data; 12 | using ASP.NET_core_role_based_authentication.Models; 13 | using ASP.NET_core_role_based_authentication.Services; 14 | 15 | namespace ASP.NET_core_role_based_authentication 16 | { 17 | public class Startup 18 | { 19 | public Startup(IConfiguration configuration) 20 | { 21 | Configuration = configuration; 22 | } 23 | 24 | public IConfiguration Configuration { get; } 25 | 26 | // This method gets called by the runtime. Use this method to add services to the container. 27 | public void ConfigureServices(IServiceCollection services) 28 | { 29 | services.AddDbContext(options => 30 | options.UseInMemoryDatabase("CustomDB")); 31 | 32 | services.AddIdentity() 33 | .AddEntityFrameworkStores() 34 | .AddDefaultTokenProviders(); 35 | 36 | // Add application services. 37 | services.AddTransient(); 38 | 39 | services.AddMvc(); 40 | services.AddAuthorization(options => 41 | { 42 | options.AddPolicy("RequireAdminRole", policy => policy.RequireRole("Admin")); 43 | }); 44 | 45 | } 46 | 47 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 48 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 49 | { 50 | if (env.IsDevelopment()) 51 | { 52 | app.UseDeveloperExceptionPage(); 53 | app.UseDatabaseErrorPage(); 54 | } 55 | else 56 | { 57 | app.UseExceptionHandler("/Home/Error"); 58 | } 59 | 60 | app.UseStaticFiles(); 61 | 62 | app.UseAuthentication(); 63 | 64 | app.UseMvc(routes => 65 | { 66 | routes.MapRoute( 67 | name: "default", 68 | template: "{controller=Home}/{action=Index}/{id?}"); 69 | }); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Views/Account/AccessDenied.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Access denied"; 3 | } 4 | 5 |
6 |

@ViewData["Title"]

7 |

You do not have access to this resource.

8 |
9 | -------------------------------------------------------------------------------- /Views/Account/ConfirmEmail.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Confirm email"; 3 | } 4 | 5 |

@ViewData["Title"]

6 |
7 |

8 | Thank you for confirming your email. 9 |

10 |
11 | -------------------------------------------------------------------------------- /Views/Account/ExternalLogin.cshtml: -------------------------------------------------------------------------------- 1 | @model ExternalLoginViewModel 2 | @{ 3 | ViewData["Title"] = "Register"; 4 | } 5 | 6 |

@ViewData["Title"]

7 |

Associate your @ViewData["LoginProvider"] account.

8 |
9 | 10 |

11 | You've successfully authenticated with @ViewData["LoginProvider"]. 12 | Please enter an email address for this site below and click the Register button to finish 13 | logging in. 14 |

15 | 16 |
17 |
18 |
19 |
20 |
21 | 22 | 23 | 24 |
25 | 26 |
27 |
28 |
29 | 30 | @section Scripts { 31 | @await Html.PartialAsync("_ValidationScriptsPartial") 32 | } 33 | -------------------------------------------------------------------------------- /Views/Account/ForgotPassword.cshtml: -------------------------------------------------------------------------------- 1 | @model ForgotPasswordViewModel 2 | @{ 3 | ViewData["Title"] = "Forgot your password?"; 4 | } 5 | 6 |

@ViewData["Title"]

7 |

Enter your email.

8 |
9 |
10 |
11 |
12 |
13 |
14 | 15 | 16 | 17 |
18 | 19 |
20 |
21 |
22 | 23 | @section Scripts { 24 | @await Html.PartialAsync("_ValidationScriptsPartial") 25 | } 26 | -------------------------------------------------------------------------------- /Views/Account/ForgotPasswordConfirmation.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Forgot password confirmation"; 3 | } 4 | 5 |

@ViewData["Title"]

6 |

7 | Please check your email to reset your password. 8 |

9 | -------------------------------------------------------------------------------- /Views/Account/Lockout.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Locked out"; 3 | } 4 | 5 |
6 |

@ViewData["Title"]

7 |

This account has been locked out, please try again later.

8 |
9 | -------------------------------------------------------------------------------- /Views/Account/Login.cshtml: -------------------------------------------------------------------------------- 1 | @using System.Collections.Generic 2 | @using System.Linq 3 | @using Microsoft.AspNetCore.Http 4 | @using Microsoft.AspNetCore.Http.Authentication 5 | @model LoginViewModel 6 | @inject SignInManager SignInManager 7 | 8 | @{ 9 | ViewData["Title"] = "Log in"; 10 | } 11 | 12 |

@ViewData["Title"]

13 |
14 |
15 |
16 |
17 |

Use a local account to log in.

18 |
19 |
20 |
21 | 22 | 23 | 24 |
25 |
26 | 27 | 28 | 29 |
30 |
31 |
32 | 36 |
37 |
38 |
39 | 40 |
41 |
42 |

43 | Forgot your password? 44 |

45 |

46 | Register as a new user? 47 |

48 |
49 |
50 |
51 |
52 |
53 |
54 |

Use another service to log in.

55 |
56 | @{ 57 | var loginProviders = (await SignInManager.GetExternalAuthenticationSchemesAsync()).ToList(); 58 | if (loginProviders.Count == 0) 59 | { 60 |
61 |

62 | There are no external authentication services configured. See this article 63 | for details on setting up this ASP.NET application to support logging in via external services. 64 |

65 |
66 | } 67 | else 68 | { 69 |
70 |
71 |

72 | @foreach (var provider in loginProviders) 73 | { 74 | 75 | } 76 |

77 |
78 |
79 | } 80 | } 81 |
82 |
83 |
84 | 85 | @section Scripts { 86 | @await Html.PartialAsync("_ValidationScriptsPartial") 87 | } 88 | -------------------------------------------------------------------------------- /Views/Account/LoginWith2fa.cshtml: -------------------------------------------------------------------------------- 1 | @model LoginWith2faViewModel 2 | @{ 3 | ViewData["Title"] = "Two-factor authentication"; 4 | } 5 | 6 |

@ViewData["Title"]

7 |
8 |

Your login is protected with an authenticator app. Enter your authenticator code below.

9 |
10 |
11 |
12 | 13 |
14 |
15 | 16 | 17 | 18 |
19 |
20 |
21 | 25 |
26 |
27 |
28 | 29 |
30 |
31 |
32 |
33 |

34 | Don't have access to your authenticator device? You can 35 | log in with a recovery code. 36 |

37 | 38 | @section Scripts { 39 | @await Html.PartialAsync("_ValidationScriptsPartial") 40 | } -------------------------------------------------------------------------------- /Views/Account/LoginWithRecoveryCode.cshtml: -------------------------------------------------------------------------------- 1 | @model LoginWithRecoveryCodeViewModel 2 | @{ 3 | ViewData["Title"] = "Recovery code verification"; 4 | } 5 | 6 |

@ViewData["Title"]

7 |
8 |

9 | You have requested to login with a recovery code. This login will not be remembered until you provide 10 | an authenticator app code at login or disable 2FA and login again. 11 |

12 |
13 |
14 |
15 |
16 |
17 | 18 | 19 | 20 |
21 | 22 |
23 |
24 |
25 | 26 | @section Scripts { 27 | @await Html.PartialAsync("_ValidationScriptsPartial") 28 | } -------------------------------------------------------------------------------- /Views/Account/Register.cshtml: -------------------------------------------------------------------------------- 1 | @model RegisterViewModel 2 | @{ 3 | ViewData["Title"] = "Register"; 4 | } 5 | 6 |

@ViewData["Title"]

7 | 8 |
9 |
10 |
11 |

Create a new account.

12 |
13 |
14 |
15 | 16 | 17 | 18 |
19 |
20 | 21 | 22 | 23 |
24 |
25 | 26 | 27 | 28 |
29 | 30 |
31 |
32 |
33 | 34 | @section Scripts { 35 | @await Html.PartialAsync("_ValidationScriptsPartial") 36 | } 37 | -------------------------------------------------------------------------------- /Views/Account/ResetPassword.cshtml: -------------------------------------------------------------------------------- 1 | @model ResetPasswordViewModel 2 | @{ 3 | ViewData["Title"] = "Reset password"; 4 | } 5 | 6 |

@ViewData["Title"]

7 |

Reset your password.

8 |
9 |
10 |
11 |
12 |
13 | 14 |
15 | 16 | 17 | 18 |
19 |
20 | 21 | 22 | 23 |
24 |
25 | 26 | 27 | 28 |
29 | 30 |
31 |
32 |
33 | 34 | @section Scripts { 35 | @await Html.PartialAsync("_ValidationScriptsPartial") 36 | } 37 | -------------------------------------------------------------------------------- /Views/Account/ResetPasswordConfirmation.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Reset password confirmation"; 3 | } 4 | 5 |

@ViewData["Title"]

6 |

7 | Your password has been reset. Please click here to log in. 8 |

9 | -------------------------------------------------------------------------------- /Views/Account/SignedOut.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Signed out"; 3 | } 4 | 5 |

@ViewData["Title"]

6 |

7 | You have successfully signed out. 8 |

9 | -------------------------------------------------------------------------------- /Views/Admin/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Admin Page"; 3 | } 4 |

@ViewData["Title"]

5 |

@ViewData["Message"]

6 | 7 |

8 | This page should be visible to only admins 9 |

10 | 11 | 12 |
13 | One Microsoft Way
14 | Redmond, WA 98052-6399
15 | P: 16 | 425.555.0100 17 |
18 | 19 |
20 | Support: Support@example.com
21 | Marketing: Marketing@example.com 22 |
23 | -------------------------------------------------------------------------------- /Views/Home/About.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "About"; 3 | } 4 |

@ViewData["Title"]

5 |

@ViewData["Message"]

6 | 7 |

Use this area to provide additional information.

8 | -------------------------------------------------------------------------------- /Views/Home/Contact.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Contact"; 3 | } 4 |

@ViewData["Title"]

5 |

@ViewData["Message"]

6 | 7 |
8 | One Microsoft Way
9 | Redmond, WA 98052-6399
10 | P: 11 | 425.555.0100 12 |
13 | 14 |
15 | Support: Support@example.com
16 | Marketing: Marketing@example.com 17 |
18 | -------------------------------------------------------------------------------- /Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Home Page"; 3 | } 4 | 5 | 67 | 68 |
69 |
70 |

Application uses

71 |
    72 |
  • Sample pages using ASP.NET Core MVC
  • 73 |
  • Bower for managing client-side libraries
  • 74 |
  • Theming using Bootstrap
  • 75 |
76 |
77 | 88 | 100 |
101 |

Run & Deploy

102 | 107 |
108 |
109 | -------------------------------------------------------------------------------- /Views/Manage/ChangePassword.cshtml: -------------------------------------------------------------------------------- 1 | @model ChangePasswordViewModel 2 | @{ 3 | ViewData["Title"] = "Change password"; 4 | ViewData.AddActivePage(ManageNavPages.ChangePassword); 5 | } 6 | 7 |

@ViewData["Title"]

8 | @Html.Partial("_StatusMessage", Model.StatusMessage) 9 |
10 |
11 |
12 |
13 |
14 | 15 | 16 | 17 |
18 |
19 | 20 | 21 | 22 |
23 |
24 | 25 | 26 | 27 |
28 | 29 |
30 |
31 |
32 | 33 | @section Scripts { 34 | @await Html.PartialAsync("_ValidationScriptsPartial") 35 | } 36 | -------------------------------------------------------------------------------- /Views/Manage/Disable2fa.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Disable two-factor authentication (2FA)"; 3 | ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication); 4 | } 5 | 6 |

@ViewData["Title"]

7 | 8 | 19 | 20 |
21 |
22 | 23 |
24 |
25 | -------------------------------------------------------------------------------- /Views/Manage/EnableAuthenticator.cshtml: -------------------------------------------------------------------------------- 1 | @model EnableAuthenticatorViewModel 2 | @{ 3 | ViewData["Title"] = "Enable authenticator"; 4 | ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication); 5 | } 6 | 7 |

@ViewData["Title"]

8 |
9 |

To use an authenticator app go through the following steps:

10 |
    11 |
  1. 12 |

    13 | Download a two-factor authenticator app like Microsoft Authenticator for 14 | Windows Phone, 15 | Android and 16 | iOS or 17 | Google Authenticator for 18 | Android and 19 | iOS. 20 |

    21 |
  2. 22 |
  3. 23 |

    Scan the QR Code or enter this key @Model.SharedKey into your two factor authenticator app. Spaces and casing do not matter.

    24 |
    To enable QR code generation please read our documentation.
    25 |
    26 |
    27 |
  4. 28 |
  5. 29 |

    30 | Once you have scanned the QR code or input the key above, your two factor authentication app will provide you 31 | with a unique code. Enter the code in the confirmation box below. 32 |

    33 |
    34 |
    35 |
    36 |
    37 | 38 | 39 | 40 |
    41 | 42 |
    43 |
    44 |
    45 |
    46 |
  6. 47 |
48 |
49 | 50 | @section Scripts { 51 | @await Html.PartialAsync("_ValidationScriptsPartial") 52 | } 53 | -------------------------------------------------------------------------------- /Views/Manage/ExternalLogins.cshtml: -------------------------------------------------------------------------------- 1 | @model ExternalLoginsViewModel 2 | @{ 3 | ViewData["Title"] = "Manage your external logins"; 4 | ViewData.AddActivePage(ManageNavPages.ExternalLogins); 5 | } 6 | 7 | @Html.Partial("_StatusMessage", Model.StatusMessage) 8 | @if (Model.CurrentLogins?.Count > 0) 9 | { 10 |

Registered Logins

11 | 12 | 13 | @foreach (var login in Model.CurrentLogins) 14 | { 15 | 16 | 17 | 33 | 34 | } 35 | 36 |
@login.LoginProvider 18 | @if (Model.ShowRemoveButton) 19 | { 20 |
21 |
22 | 23 | 24 | 25 |
26 |
27 | } 28 | else 29 | { 30 | @:   31 | } 32 |
37 | } 38 | @if (Model.OtherLogins?.Count > 0) 39 | { 40 |

Add another service to log in.

41 |
42 |
43 |
44 |

45 | @foreach (var provider in Model.OtherLogins) 46 | { 47 | 48 | } 49 |

50 |
51 |
52 | } 53 | -------------------------------------------------------------------------------- /Views/Manage/GenerateRecoveryCodes.cshtml: -------------------------------------------------------------------------------- 1 | @model GenerateRecoveryCodesViewModel 2 | @{ 3 | ViewData["Title"] = "Recovery codes"; 4 | ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication); 5 | } 6 | 7 |

@ViewData["Title"]

8 | 17 |
18 |
19 | @for (var row = 0; row < Model.RecoveryCodes.Count(); row += 2) 20 | { 21 | @Model.RecoveryCodes[row] @Model.RecoveryCodes[row + 1]
22 | } 23 |
24 |
-------------------------------------------------------------------------------- /Views/Manage/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model IndexViewModel 2 | @{ 3 | ViewData["Title"] = "Profile"; 4 | ViewData.AddActivePage(ManageNavPages.Index); 5 | } 6 | 7 |

@ViewData["Title"]

8 | @Html.Partial("_StatusMessage", Model.StatusMessage) 9 |
10 |
11 |
12 |
13 |
14 | 15 | 16 |
17 |
18 | 19 | @if (Model.IsEmailConfirmed) 20 | { 21 |
22 | 23 | 24 |
25 | } 26 | else 27 | { 28 | 29 | 30 | } 31 | 32 |
33 |
34 | 35 | 36 | 37 |
38 | 39 |
40 |
41 |
42 | 43 | @section Scripts { 44 | @await Html.PartialAsync("_ValidationScriptsPartial") 45 | } 46 | -------------------------------------------------------------------------------- /Views/Manage/ManageNavPages.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc.Rendering; 6 | using Microsoft.AspNetCore.Mvc.ViewFeatures; 7 | 8 | namespace ASP.NET_core_role_based_authentication.Views.Manage 9 | { 10 | public static class ManageNavPages 11 | { 12 | public static string ActivePageKey => "ActivePage"; 13 | 14 | public static string Index => "Index"; 15 | 16 | public static string ChangePassword => "ChangePassword"; 17 | 18 | public static string ExternalLogins => "ExternalLogins"; 19 | 20 | public static string TwoFactorAuthentication => "TwoFactorAuthentication"; 21 | 22 | public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index); 23 | 24 | public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword); 25 | 26 | public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins); 27 | 28 | public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication); 29 | 30 | public static string PageNavClass(ViewContext viewContext, string page) 31 | { 32 | var activePage = viewContext.ViewData["ActivePage"] as string; 33 | return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null; 34 | } 35 | 36 | public static void AddActivePage(this ViewDataDictionary viewData, string activePage) => viewData[ActivePageKey] = activePage; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Views/Manage/ResetAuthenticator.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Reset authenticator key"; 3 | ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication); 4 | } 5 | 6 |

@ViewData["Title"]

7 | 17 |
18 |
19 | 20 |
21 |
-------------------------------------------------------------------------------- /Views/Manage/SetPassword.cshtml: -------------------------------------------------------------------------------- 1 | @model SetPasswordViewModel 2 | @{ 3 | ViewData["Title"] = "Set password"; 4 | ViewData.AddActivePage(ManageNavPages.ChangePassword); 5 | } 6 | 7 |

Set your password

8 | @Html.Partial("_StatusMessage", Model.StatusMessage) 9 |

10 | You do not have a local username/password for this site. Add a local 11 | account so you can log in without an external login. 12 |

13 |
14 |
15 |
16 |
17 |
18 | 19 | 20 | 21 |
22 |
23 | 24 | 25 | 26 |
27 | 28 |
29 |
30 |
31 | 32 | @section Scripts { 33 | @await Html.PartialAsync("_ValidationScriptsPartial") 34 | } 35 | -------------------------------------------------------------------------------- /Views/Manage/TwoFactorAuthentication.cshtml: -------------------------------------------------------------------------------- 1 | @model TwoFactorAuthenticationViewModel 2 | @{ 3 | ViewData["Title"] = "Two-factor authentication"; 4 | ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication); 5 | } 6 | 7 |

@ViewData["Title"]

8 | @if (Model.Is2faEnabled) 9 | { 10 | if (Model.RecoveryCodesLeft == 0) 11 | { 12 |
13 | You have no recovery codes left. 14 |

You must generate a new set of recovery codes before you can log in with a recovery code.

15 |
16 | } 17 | else if (Model.RecoveryCodesLeft == 1) 18 | { 19 |
20 | You have 1 recovery code left. 21 |

You can generate a new set of recovery codes.

22 |
23 | } 24 | else if (Model.RecoveryCodesLeft <= 3) 25 | { 26 |
27 | You have @Model.RecoveryCodesLeft recovery codes left. 28 |

You should generate a new set of recovery codes.

29 |
30 | } 31 | 32 | Disable 2FA 33 | Reset recovery codes 34 | } 35 | 36 |
Authenticator app
37 | @if (!Model.HasAuthenticator) 38 | { 39 | Add authenticator app 40 | } 41 | else 42 | { 43 | Configure authenticator app 44 | Reset authenticator key 45 | } 46 | 47 | @section Scripts { 48 | @await Html.PartialAsync("_ValidationScriptsPartial") 49 | } 50 | -------------------------------------------------------------------------------- /Views/Manage/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "/Views/Shared/_Layout.cshtml"; 3 | } 4 | 5 |

Manage your account

6 | 7 |
8 |

Change your account settings

9 |
10 |
11 |
12 | @await Html.PartialAsync("_ManageNav") 13 |
14 |
15 | @RenderBody() 16 |
17 |
18 |
19 | 20 | @section Scripts { 21 | @RenderSection("Scripts", required: false) 22 | } 23 | 24 | -------------------------------------------------------------------------------- /Views/Manage/_ManageNav.cshtml: -------------------------------------------------------------------------------- 1 | @using ASP.NET_core_role_based_authentication.Views.Manage 2 | @inject SignInManager SignInManager 3 | @{ 4 | var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any(); 5 | } 6 | 7 | 16 | 17 | -------------------------------------------------------------------------------- /Views/Manage/_StatusMessage.cshtml: -------------------------------------------------------------------------------- 1 | @model string 2 | 3 | @if (!String.IsNullOrEmpty(Model)) 4 | { 5 | var statusMessageClass = Model.StartsWith("Error") ? "danger" : "success"; 6 | 10 | } 11 | -------------------------------------------------------------------------------- /Views/Manage/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using ASP.NET_core_role_based_authentication.Views.Manage -------------------------------------------------------------------------------- /Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @model ErrorViewModel 2 | @{ 3 | ViewData["Title"] = "Error"; 4 | } 5 | 6 |

Error.

7 |

An error occurred while processing your request.

8 | 9 | @if (Model.ShowRequestId) 10 | { 11 |

12 | Request ID: @Model.RequestId 13 |

14 | } 15 | 16 |

Development Mode

17 |

18 | Swapping to Development environment will display more detailed information about the error that occurred. 19 |

20 |

21 | Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. 22 |

23 | -------------------------------------------------------------------------------- /Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewData["Title"] - ASP.NET_core_role_based_authentication 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 45 |
46 | @RenderBody() 47 |
48 |
49 |

© 2018 - ASP.NET_core_role_based_authentication

50 |
51 |
52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 65 | 71 | 72 | 73 | 74 | @RenderSection("Scripts", required: false) 75 | 76 | 77 | -------------------------------------------------------------------------------- /Views/Shared/_LoginPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @using ASP.NET_core_role_based_authentication.Models 3 | 4 | @inject SignInManager SignInManager 5 | @inject UserManager UserManager 6 | 7 | @if (SignInManager.IsSignedIn(User)) 8 | { 9 | 19 | } 20 | else 21 | { 22 | 26 | } 27 | -------------------------------------------------------------------------------- /Views/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @using ASP.NET_core_role_based_authentication 3 | @using ASP.NET_core_role_based_authentication.Models 4 | @using ASP.NET_core_role_based_authentication.Models.AccountViewModels 5 | @using ASP.NET_core_role_based_authentication.Models.ManageViewModels 6 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 7 | -------------------------------------------------------------------------------- /Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "AppSettings":{ 3 | "UserEmail": "johndoe@email.com", 4 | "UserPassword": "P@ssw0rd" 5 | }, 6 | "Logging": { 7 | "IncludeScopes": false, 8 | "LogLevel": { 9 | "Default": "Debug", 10 | "System": "Information", 11 | "Microsoft": "Information" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "AppSettings":{ 3 | "UserEmail": "johndoe@email.com", 4 | "UserPassword": "P@ssw0rd" 5 | }, 6 | "Logging": { 7 | "IncludeScopes": false, 8 | "LogLevel": { 9 | "Default": "Debug", 10 | "System": "Information", 11 | "Microsoft": "Information" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asp.net", 3 | "private": true, 4 | "dependencies": { 5 | "bootstrap": "3.3.7", 6 | "jquery": "2.2.0", 7 | "jquery-validation": "1.14.0", 8 | "jquery-validation-unobtrusive": "3.2.6" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /bundleconfig.json: -------------------------------------------------------------------------------- 1 | // Configure bundling and minification for the project. 2 | // More info at https://go.microsoft.com/fwlink/?LinkId=808241 3 | [ 4 | { 5 | "outputFileName": "wwwroot/css/site.min.css", 6 | // An array of relative input file paths. Globbing patterns supported 7 | "inputFiles": [ 8 | "wwwroot/css/site.css" 9 | ] 10 | }, 11 | { 12 | "outputFileName": "wwwroot/js/site.min.js", 13 | "inputFiles": [ 14 | "wwwroot/js/site.js" 15 | ], 16 | // Optionally specify minification options 17 | "minify": { 18 | "enabled": true, 19 | "renameLocals": true 20 | }, 21 | // Optionally generate .map file 22 | "sourceMap": false 23 | } 24 | ] 25 | -------------------------------------------------------------------------------- /wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | padding-bottom: 20px; 4 | } 5 | 6 | /* Wrapping element */ 7 | /* Set some basic padding to keep content from hitting the edges */ 8 | .body-content { 9 | padding-left: 15px; 10 | padding-right: 15px; 11 | } 12 | 13 | /* Carousel */ 14 | .carousel-caption p { 15 | font-size: 20px; 16 | line-height: 1.4; 17 | } 18 | 19 | /* Make .svg files in the carousel display properly in older browsers */ 20 | .carousel-inner .item img[src$=".svg"] { 21 | width: 100%; 22 | } 23 | 24 | /* QR code generator */ 25 | #qrCode { 26 | margin: 15px; 27 | } 28 | 29 | /* Hide/rearrange for smaller screens */ 30 | @media screen and (max-width: 767px) { 31 | /* Hide captions */ 32 | .carousel-caption { 33 | display: none; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /wwwroot/css/site.min.css: -------------------------------------------------------------------------------- 1 | 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}} -------------------------------------------------------------------------------- /wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/temilaj/ASP.NET-core-role-based-authentication/f66cd68242bb027740206855c9538872148ea8d4/wwwroot/favicon.ico -------------------------------------------------------------------------------- /wwwroot/images/banner1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wwwroot/images/banner2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wwwroot/images/banner3.svg: -------------------------------------------------------------------------------- 1 | banner3b -------------------------------------------------------------------------------- /wwwroot/images/banner4.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Write your JavaScript code. 2 | -------------------------------------------------------------------------------- /wwwroot/js/site.min.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/temilaj/ASP.NET-core-role-based-authentication/f66cd68242bb027740206855c9538872148ea8d4/wwwroot/js/site.min.js -------------------------------------------------------------------------------- /wwwroot/lib/bootstrap/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bootstrap", 3 | "description": "The most popular front-end framework for developing responsive, mobile first projects on the web.", 4 | "keywords": [ 5 | "css", 6 | "js", 7 | "less", 8 | "mobile-first", 9 | "responsive", 10 | "front-end", 11 | "framework", 12 | "web" 13 | ], 14 | "homepage": "http://getbootstrap.com", 15 | "license": "MIT", 16 | "moduleType": "globals", 17 | "main": [ 18 | "less/bootstrap.less", 19 | "dist/js/bootstrap.js" 20 | ], 21 | "ignore": [ 22 | "/.*", 23 | "_config.yml", 24 | "CNAME", 25 | "composer.json", 26 | "CONTRIBUTING.md", 27 | "docs", 28 | "js/tests", 29 | "test-infra" 30 | ], 31 | "dependencies": { 32 | "jquery": "1.9.1 - 3" 33 | }, 34 | "version": "3.3.7", 35 | "_release": "3.3.7", 36 | "_resolution": { 37 | "type": "version", 38 | "tag": "v3.3.7", 39 | "commit": "0b9c4a4007c44201dce9a6cc1a38407005c26c86" 40 | }, 41 | "_source": "https://github.com/twbs/bootstrap.git", 42 | "_target": "v3.3.7", 43 | "_originalSource": "bootstrap", 44 | "_direct": true 45 | } -------------------------------------------------------------------------------- /wwwroot/lib/bootstrap/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2016 Twitter, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/temilaj/ASP.NET-core-role-based-authentication/f66cd68242bb027740206855c9538872148ea8d4/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/temilaj/ASP.NET-core-role-based-authentication/f66cd68242bb027740206855c9538872148ea8d4/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/temilaj/ASP.NET-core-role-based-authentication/f66cd68242bb027740206855c9538872148ea8d4/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/temilaj/ASP.NET-core-role-based-authentication/f66cd68242bb027740206855c9538872148ea8d4/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /wwwroot/lib/bootstrap/dist/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /wwwroot/lib/jquery-validation-unobtrusive/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-validation-unobtrusive", 3 | "version": "3.2.6", 4 | "homepage": "https://github.com/aspnet/jquery-validation-unobtrusive", 5 | "description": "Add-on to jQuery Validation to enable unobtrusive validation options in data-* attributes.", 6 | "main": [ 7 | "jquery.validate.unobtrusive.js" 8 | ], 9 | "ignore": [ 10 | "**/.*", 11 | "*.json", 12 | "*.md", 13 | "*.txt", 14 | "gulpfile.js" 15 | ], 16 | "keywords": [ 17 | "jquery", 18 | "asp.net", 19 | "mvc", 20 | "validation", 21 | "unobtrusive" 22 | ], 23 | "authors": [ 24 | "Microsoft" 25 | ], 26 | "license": "http://www.microsoft.com/web/webpi/eula/net_library_eula_enu.htm", 27 | "repository": { 28 | "type": "git", 29 | "url": "git://github.com/aspnet/jquery-validation-unobtrusive.git" 30 | }, 31 | "dependencies": { 32 | "jquery-validation": ">=1.8", 33 | "jquery": ">=1.8" 34 | }, 35 | "_release": "3.2.6", 36 | "_resolution": { 37 | "type": "version", 38 | "tag": "v3.2.6", 39 | "commit": "13386cd1b5947d8a5d23a12b531ce3960be1eba7" 40 | }, 41 | "_source": "git://github.com/aspnet/jquery-validation-unobtrusive.git", 42 | "_target": "3.2.6", 43 | "_originalSource": "jquery-validation-unobtrusive" 44 | } -------------------------------------------------------------------------------- /wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | ** Unobtrusive validation support library for jQuery and jQuery Validate 3 | ** Copyright (C) Microsoft Corporation. All rights reserved. 4 | */ 5 | !function(a){function e(a,e,n){a.rules[e]=n,a.message&&(a.messages[e]=a.message)}function n(a){return a.replace(/^\s+|\s+$/g,"").split(/\s*,\s*/g)}function t(a){return a.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g,"\\$1")}function r(a){return a.substr(0,a.lastIndexOf(".")+1)}function i(a,e){return 0===a.indexOf("*.")&&(a=a.replace("*.",e)),a}function o(e,n){var r=a(this).find("[data-valmsg-for='"+t(n[0].name)+"']"),i=r.attr("data-valmsg-replace"),o=i?a.parseJSON(i)!==!1:null;r.removeClass("field-validation-valid").addClass("field-validation-error"),e.data("unobtrusiveContainer",r),o?(r.empty(),e.removeClass("input-validation-error").appendTo(r)):e.hide()}function d(e,n){var t=a(this).find("[data-valmsg-summary=true]"),r=t.find("ul");r&&r.length&&n.errorList.length&&(r.empty(),t.addClass("validation-summary-errors").removeClass("validation-summary-valid"),a.each(n.errorList,function(){a("
  • ").html(this.message).appendTo(r)}))}function s(e){var n=e.data("unobtrusiveContainer");if(n){var t=n.attr("data-valmsg-replace"),r=t?a.parseJSON(t):null;n.addClass("field-validation-valid").removeClass("field-validation-error"),e.removeData("unobtrusiveContainer"),r&&n.empty()}}function l(e){var n=a(this),t="__jquery_unobtrusive_validation_form_reset";if(!n.data(t)){n.data(t,!0);try{n.data("validator").resetForm()}finally{n.removeData(t)}n.find(".validation-summary-errors").addClass("validation-summary-valid").removeClass("validation-summary-errors"),n.find(".field-validation-error").addClass("field-validation-valid").removeClass("field-validation-error").removeData("unobtrusiveContainer").find(">*").removeData("unobtrusiveContainer")}}function m(e){var n=a(e),t=n.data(v),r=a.proxy(l,e),i=p.unobtrusive.options||{},m=function(n,t){var r=i[n];r&&a.isFunction(r)&&r.apply(e,t)};return t||(t={options:{errorClass:i.errorClass||"input-validation-error",errorElement:i.errorElement||"span",errorPlacement:function(){o.apply(e,arguments),m("errorPlacement",arguments)},invalidHandler:function(){d.apply(e,arguments),m("invalidHandler",arguments)},messages:{},rules:{},success:function(){s.apply(e,arguments),m("success",arguments)}},attachValidation:function(){n.off("reset."+v,r).on("reset."+v,r).validate(this.options)},validate:function(){return n.validate(),n.valid()}},n.data(v,t)),t}var u,p=a.validator,v="unobtrusiveValidation";p.unobtrusive={adapters:[],parseElement:function(e,n){var t,r,i,o=a(e),d=o.parents("form")[0];d&&(t=m(d),t.options.rules[e.name]=r={},t.options.messages[e.name]=i={},a.each(this.adapters,function(){var n="data-val-"+this.name,t=o.attr(n),s={};void 0!==t&&(n+="-",a.each(this.params,function(){s[this]=o.attr(n+this)}),this.adapt({element:e,form:d,message:t,params:s,rules:r,messages:i}))}),a.extend(r,{__dummy__:!0}),n||t.attachValidation())},parse:function(e){var n=a(e),t=n.parents().addBack().filter("form").add(n.find("form")).has("[data-val=true]");n.find("[data-val=true]").each(function(){p.unobtrusive.parseElement(this,!0)}),t.each(function(){var a=m(this);a&&a.attachValidation()})}},u=p.unobtrusive.adapters,u.add=function(a,e,n){return n||(n=e,e=[]),this.push({name:a,params:e,adapt:n}),this},u.addBool=function(a,n){return this.add(a,function(t){e(t,n||a,!0)})},u.addMinMax=function(a,n,t,r,i,o){return this.add(a,[i||"min",o||"max"],function(a){var i=a.params.min,o=a.params.max;i&&o?e(a,r,[i,o]):i?e(a,n,i):o&&e(a,t,o)})},u.addSingleVal=function(a,n,t){return this.add(a,[n||"val"],function(r){e(r,t||a,r.params[n])})},p.addMethod("__dummy__",function(a,e,n){return!0}),p.addMethod("regex",function(a,e,n){var t;return this.optional(e)?!0:(t=new RegExp(n).exec(a),t&&0===t.index&&t[0].length===a.length)}),p.addMethod("nonalphamin",function(a,e,n){var t;return n&&(t=a.match(/\W/g),t=t&&t.length>=n),t}),p.methods.extension?(u.addSingleVal("accept","mimtype"),u.addSingleVal("extension","extension")):u.addSingleVal("extension","extension","accept"),u.addSingleVal("regex","pattern"),u.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url"),u.addMinMax("length","minlength","maxlength","rangelength").addMinMax("range","min","max","range"),u.addMinMax("minlength","minlength").addMinMax("maxlength","minlength","maxlength"),u.add("equalto",["other"],function(n){var o=r(n.element.name),d=n.params.other,s=i(d,o),l=a(n.form).find(":input").filter("[name='"+t(s)+"']")[0];e(n,"equalTo",l)}),u.add("required",function(a){("INPUT"!==a.element.tagName.toUpperCase()||"CHECKBOX"!==a.element.type.toUpperCase())&&e(a,"required",!0)}),u.add("remote",["url","type","additionalfields"],function(o){var d={url:o.params.url,type:o.params.type||"GET",data:{}},s=r(o.element.name);a.each(n(o.params.additionalfields||o.element.name),function(e,n){var r=i(n,s);d.data[r]=function(){var e=a(o.form).find(":input").filter("[name='"+t(r)+"']");return e.is(":checkbox")?e.filter(":checked").val()||e.filter(":hidden").val()||"":e.is(":radio")?e.filter(":checked").val()||"":e.val()}}),e(o,"remote",d)}),u.add("password",["min","nonalphamin","regex"],function(a){a.params.min&&e(a,"minlength",a.params.min),a.params.nonalphamin&&e(a,"nonalphamin",a.params.nonalphamin),a.params.regex&&e(a,"regex",a.params.regex)}),a(function(){p.unobtrusive.parse(document)})}(jQuery); -------------------------------------------------------------------------------- /wwwroot/lib/jquery-validation/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-validation", 3 | "homepage": "http://jqueryvalidation.org/", 4 | "repository": { 5 | "type": "git", 6 | "url": "git://github.com/jzaefferer/jquery-validation.git" 7 | }, 8 | "authors": [ 9 | "Jörn Zaefferer " 10 | ], 11 | "description": "Form validation made easy", 12 | "main": "dist/jquery.validate.js", 13 | "keywords": [ 14 | "forms", 15 | "validation", 16 | "validate" 17 | ], 18 | "license": "MIT", 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components", 23 | "test", 24 | "demo", 25 | "lib" 26 | ], 27 | "dependencies": { 28 | "jquery": ">= 1.7.2" 29 | }, 30 | "version": "1.14.0", 31 | "_release": "1.14.0", 32 | "_resolution": { 33 | "type": "version", 34 | "tag": "1.14.0", 35 | "commit": "c1343fb9823392aa9acbe1c3ffd337b8c92fed48" 36 | }, 37 | "_source": "git://github.com/jzaefferer/jquery-validation.git", 38 | "_target": ">=1.8", 39 | "_originalSource": "jquery-validation" 40 | } -------------------------------------------------------------------------------- /wwwroot/lib/jquery-validation/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright Jörn Zaefferer 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /wwwroot/lib/jquery-validation/dist/additional-methods.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery Validation Plugin - v1.14.0 - 6/30/2015 2 | * http://jqueryvalidation.org/ 3 | * Copyright (c) 2015 Jörn Zaefferer; Licensed MIT */ 4 | !function(a){"function"==typeof define&&define.amd?define(["jquery","./jquery.validate.min"],a):a(jQuery)}(function(a){!function(){function b(a){return a.replace(/<.[^<>]*?>/g," ").replace(/ | /gi," ").replace(/[.(),;:!?%#$'\"_+=\/\-“”’]*/g,"")}a.validator.addMethod("maxWords",function(a,c,d){return this.optional(c)||b(a).match(/\b\w+\b/g).length<=d},a.validator.format("Please enter {0} words or less.")),a.validator.addMethod("minWords",function(a,c,d){return this.optional(c)||b(a).match(/\b\w+\b/g).length>=d},a.validator.format("Please enter at least {0} words.")),a.validator.addMethod("rangeWords",function(a,c,d){var e=b(a),f=/\b\w+\b/g;return this.optional(c)||e.match(f).length>=d[0]&&e.match(f).length<=d[1]},a.validator.format("Please enter between {0} and {1} words."))}(),a.validator.addMethod("accept",function(b,c,d){var e,f,g="string"==typeof d?d.replace(/\s/g,"").replace(/,/g,"|"):"image/*",h=this.optional(c);if(h)return h;if("file"===a(c).attr("type")&&(g=g.replace(/\*/g,".*"),c.files&&c.files.length))for(e=0;ec;c++)d=h-c,e=f.substring(c,c+1),g+=d*e;return g%11===0},"Please specify a valid bank account number"),a.validator.addMethod("bankorgiroaccountNL",function(b,c){return this.optional(c)||a.validator.methods.bankaccountNL.call(this,b,c)||a.validator.methods.giroaccountNL.call(this,b,c)},"Please specify a valid bank or giro account number"),a.validator.addMethod("bic",function(a,b){return this.optional(b)||/^([A-Z]{6}[A-Z2-9][A-NP-Z1-2])(X{3}|[A-WY-Z0-9][A-Z0-9]{2})?$/.test(a)},"Please specify a valid BIC code"),a.validator.addMethod("cifES",function(a){"use strict";var b,c,d,e,f,g,h=[];if(a=a.toUpperCase(),!a.match("((^[A-Z]{1}[0-9]{7}[A-Z0-9]{1}$|^[T]{1}[A-Z0-9]{8}$)|^[0-9]{8}[A-Z]{1}$)"))return!1;for(d=0;9>d;d++)h[d]=parseInt(a.charAt(d),10);for(c=h[2]+h[4]+h[6],e=1;8>e;e+=2)f=(2*h[e]).toString(),g=f.charAt(1),c+=parseInt(f.charAt(0),10)+(""===g?0:parseInt(g,10));return/^[ABCDEFGHJNPQRSUVW]{1}/.test(a)?(c+="",b=10-parseInt(c.charAt(c.length-1),10),a+=b,h[8].toString()===String.fromCharCode(64+b)||h[8].toString()===a.charAt(a.length-1)):!1},"Please specify a valid CIF number."),a.validator.addMethod("cpfBR",function(a){if(a=a.replace(/([~!@#$%^&*()_+=`{}\[\]\-|\\:;'<>,.\/? ])+/g,""),11!==a.length)return!1;var b,c,d,e,f=0;if(b=parseInt(a.substring(9,10),10),c=parseInt(a.substring(10,11),10),d=function(a,b){var c=10*a%11;return(10===c||11===c)&&(c=0),c===b},""===a||"00000000000"===a||"11111111111"===a||"22222222222"===a||"33333333333"===a||"44444444444"===a||"55555555555"===a||"66666666666"===a||"77777777777"===a||"88888888888"===a||"99999999999"===a)return!1;for(e=1;9>=e;e++)f+=parseInt(a.substring(e-1,e),10)*(11-e);if(d(f,b)){for(f=0,e=1;10>=e;e++)f+=parseInt(a.substring(e-1,e),10)*(12-e);return d(f,c)}return!1},"Please specify a valid CPF number"),a.validator.addMethod("creditcardtypes",function(a,b,c){if(/[^0-9\-]+/.test(a))return!1;a=a.replace(/\D/g,"");var d=0;return c.mastercard&&(d|=1),c.visa&&(d|=2),c.amex&&(d|=4),c.dinersclub&&(d|=8),c.enroute&&(d|=16),c.discover&&(d|=32),c.jcb&&(d|=64),c.unknown&&(d|=128),c.all&&(d=255),1&d&&/^(5[12345])/.test(a)?16===a.length:2&d&&/^(4)/.test(a)?16===a.length:4&d&&/^(3[47])/.test(a)?15===a.length:8&d&&/^(3(0[012345]|[68]))/.test(a)?14===a.length:16&d&&/^(2(014|149))/.test(a)?15===a.length:32&d&&/^(6011)/.test(a)?16===a.length:64&d&&/^(3)/.test(a)?16===a.length:64&d&&/^(2131|1800)/.test(a)?15===a.length:128&d?!0:!1},"Please enter a valid credit card number."),a.validator.addMethod("currency",function(a,b,c){var d,e="string"==typeof c,f=e?c:c[0],g=e?!0:c[1];return f=f.replace(/,/g,""),f=g?f+"]":f+"]?",d="^["+f+"([1-9]{1}[0-9]{0,2}(\\,[0-9]{3})*(\\.[0-9]{0,2})?|[1-9]{1}[0-9]{0,}(\\.[0-9]{0,2})?|0(\\.[0-9]{0,2})?|(\\.[0-9]{1,2})?)$",d=new RegExp(d),this.optional(b)||d.test(a)},"Please specify a valid currency"),a.validator.addMethod("dateFA",function(a,b){return this.optional(b)||/^[1-4]\d{3}\/((0?[1-6]\/((3[0-1])|([1-2][0-9])|(0?[1-9])))|((1[0-2]|(0?[7-9]))\/(30|([1-2][0-9])|(0?[1-9]))))$/.test(a)},a.validator.messages.date),a.validator.addMethod("dateITA",function(a,b){var c,d,e,f,g,h=!1,i=/^\d{1,2}\/\d{1,2}\/\d{4}$/;return i.test(a)?(c=a.split("/"),d=parseInt(c[0],10),e=parseInt(c[1],10),f=parseInt(c[2],10),g=new Date(Date.UTC(f,e-1,d,12,0,0,0)),h=g.getUTCFullYear()===f&&g.getUTCMonth()===e-1&&g.getUTCDate()===d?!0:!1):h=!1,this.optional(b)||h},a.validator.messages.date),a.validator.addMethod("dateNL",function(a,b){return this.optional(b)||/^(0?[1-9]|[12]\d|3[01])[\.\/\-](0?[1-9]|1[012])[\.\/\-]([12]\d)?(\d\d)$/.test(a)},a.validator.messages.date),a.validator.addMethod("extension",function(a,b,c){return c="string"==typeof c?c.replace(/,/g,"|"):"png|jpe?g|gif",this.optional(b)||a.match(new RegExp("\\.("+c+")$","i"))},a.validator.format("Please enter a value with a valid extension.")),a.validator.addMethod("giroaccountNL",function(a,b){return this.optional(b)||/^[0-9]{1,7}$/.test(a)},"Please specify a valid giro account number"),a.validator.addMethod("iban",function(a,b){if(this.optional(b))return!0;var c,d,e,f,g,h,i,j,k,l=a.replace(/ /g,"").toUpperCase(),m="",n=!0,o="",p="";if(c=l.substring(0,2),h={AL:"\\d{8}[\\dA-Z]{16}",AD:"\\d{8}[\\dA-Z]{12}",AT:"\\d{16}",AZ:"[\\dA-Z]{4}\\d{20}",BE:"\\d{12}",BH:"[A-Z]{4}[\\dA-Z]{14}",BA:"\\d{16}",BR:"\\d{23}[A-Z][\\dA-Z]",BG:"[A-Z]{4}\\d{6}[\\dA-Z]{8}",CR:"\\d{17}",HR:"\\d{17}",CY:"\\d{8}[\\dA-Z]{16}",CZ:"\\d{20}",DK:"\\d{14}",DO:"[A-Z]{4}\\d{20}",EE:"\\d{16}",FO:"\\d{14}",FI:"\\d{14}",FR:"\\d{10}[\\dA-Z]{11}\\d{2}",GE:"[\\dA-Z]{2}\\d{16}",DE:"\\d{18}",GI:"[A-Z]{4}[\\dA-Z]{15}",GR:"\\d{7}[\\dA-Z]{16}",GL:"\\d{14}",GT:"[\\dA-Z]{4}[\\dA-Z]{20}",HU:"\\d{24}",IS:"\\d{22}",IE:"[\\dA-Z]{4}\\d{14}",IL:"\\d{19}",IT:"[A-Z]\\d{10}[\\dA-Z]{12}",KZ:"\\d{3}[\\dA-Z]{13}",KW:"[A-Z]{4}[\\dA-Z]{22}",LV:"[A-Z]{4}[\\dA-Z]{13}",LB:"\\d{4}[\\dA-Z]{20}",LI:"\\d{5}[\\dA-Z]{12}",LT:"\\d{16}",LU:"\\d{3}[\\dA-Z]{13}",MK:"\\d{3}[\\dA-Z]{10}\\d{2}",MT:"[A-Z]{4}\\d{5}[\\dA-Z]{18}",MR:"\\d{23}",MU:"[A-Z]{4}\\d{19}[A-Z]{3}",MC:"\\d{10}[\\dA-Z]{11}\\d{2}",MD:"[\\dA-Z]{2}\\d{18}",ME:"\\d{18}",NL:"[A-Z]{4}\\d{10}",NO:"\\d{11}",PK:"[\\dA-Z]{4}\\d{16}",PS:"[\\dA-Z]{4}\\d{21}",PL:"\\d{24}",PT:"\\d{21}",RO:"[A-Z]{4}[\\dA-Z]{16}",SM:"[A-Z]\\d{10}[\\dA-Z]{12}",SA:"\\d{2}[\\dA-Z]{18}",RS:"\\d{18}",SK:"\\d{20}",SI:"\\d{15}",ES:"\\d{20}",SE:"\\d{20}",CH:"\\d{5}[\\dA-Z]{12}",TN:"\\d{20}",TR:"\\d{5}[\\dA-Z]{17}",AE:"\\d{3}\\d{16}",GB:"[A-Z]{4}\\d{14}",VG:"[\\dA-Z]{4}\\d{16}"},g=h[c],"undefined"!=typeof g&&(i=new RegExp("^[A-Z]{2}\\d{2}"+g+"$",""),!i.test(l)))return!1;for(d=l.substring(4,l.length)+l.substring(0,4),j=0;j9&&a.match(/^(?:(?:(?:00\s?|\+)44\s?|0)7(?:[1345789]\d{2}|624)\s?\d{3}\s?\d{3})$/)},"Please specify a valid mobile number"),a.validator.addMethod("nieES",function(a){"use strict";return a=a.toUpperCase(),a.match("((^[A-Z]{1}[0-9]{7}[A-Z0-9]{1}$|^[T]{1}[A-Z0-9]{8}$)|^[0-9]{8}[A-Z]{1}$)")?/^[T]{1}/.test(a)?a[8]===/^[T]{1}[A-Z0-9]{8}$/.test(a):/^[XYZ]{1}/.test(a)?a[8]==="TRWAGMYFPDXBNJZSQVHLCKE".charAt(a.replace("X","0").replace("Y","1").replace("Z","2").substring(0,8)%23):!1:!1},"Please specify a valid NIE number."),a.validator.addMethod("nifES",function(a){"use strict";return a=a.toUpperCase(),a.match("((^[A-Z]{1}[0-9]{7}[A-Z0-9]{1}$|^[T]{1}[A-Z0-9]{8}$)|^[0-9]{8}[A-Z]{1}$)")?/^[0-9]{8}[A-Z]{1}$/.test(a)?"TRWAGMYFPDXBNJZSQVHLCKE".charAt(a.substring(8,0)%23)===a.charAt(8):/^[KLM]{1}/.test(a)?a[8]===String.fromCharCode(64):!1:!1},"Please specify a valid NIF number."),jQuery.validator.addMethod("notEqualTo",function(b,c,d){return this.optional(c)||!a.validator.methods.equalTo.call(this,b,c,d)},"Please enter a different value, values must not be the same."),a.validator.addMethod("nowhitespace",function(a,b){return this.optional(b)||/^\S+$/i.test(a)},"No white space please"),a.validator.addMethod("pattern",function(a,b,c){return this.optional(b)?!0:("string"==typeof c&&(c=new RegExp("^(?:"+c+")$")),c.test(a))},"Invalid format."),a.validator.addMethod("phoneNL",function(a,b){return this.optional(b)||/^((\+|00(\s|\s?\-\s?)?)31(\s|\s?\-\s?)?(\(0\)[\-\s]?)?|0)[1-9]((\s|\s?\-\s?)?[0-9]){8}$/.test(a)},"Please specify a valid phone number."),a.validator.addMethod("phoneUK",function(a,b){return a=a.replace(/\(|\)|\s+|-/g,""),this.optional(b)||a.length>9&&a.match(/^(?:(?:(?:00\s?|\+)44\s?)|(?:\(?0))(?:\d{2}\)?\s?\d{4}\s?\d{4}|\d{3}\)?\s?\d{3}\s?\d{3,4}|\d{4}\)?\s?(?:\d{5}|\d{3}\s?\d{3})|\d{5}\)?\s?\d{4,5})$/)},"Please specify a valid phone number"),a.validator.addMethod("phoneUS",function(a,b){return a=a.replace(/\s+/g,""),this.optional(b)||a.length>9&&a.match(/^(\+?1-?)?(\([2-9]([02-9]\d|1[02-9])\)|[2-9]([02-9]\d|1[02-9]))-?[2-9]([02-9]\d|1[02-9])-?\d{4}$/)},"Please specify a valid phone number"),a.validator.addMethod("phonesUK",function(a,b){return a=a.replace(/\(|\)|\s+|-/g,""),this.optional(b)||a.length>9&&a.match(/^(?:(?:(?:00\s?|\+)44\s?|0)(?:1\d{8,9}|[23]\d{9}|7(?:[1345789]\d{8}|624\d{6})))$/)},"Please specify a valid uk phone number"),a.validator.addMethod("postalCodeCA",function(a,b){return this.optional(b)||/^[ABCEGHJKLMNPRSTVXY]\d[A-Z] \d[A-Z]\d$/.test(a)},"Please specify a valid postal code"),a.validator.addMethod("postalcodeBR",function(a,b){return this.optional(b)||/^\d{2}.\d{3}-\d{3}?$|^\d{5}-?\d{3}?$/.test(a)},"Informe um CEP válido."),a.validator.addMethod("postalcodeIT",function(a,b){return this.optional(b)||/^\d{5}$/.test(a)},"Please specify a valid postal code"),a.validator.addMethod("postalcodeNL",function(a,b){return this.optional(b)||/^[1-9][0-9]{3}\s?[a-zA-Z]{2}$/.test(a)},"Please specify a valid postal code"),a.validator.addMethod("postcodeUK",function(a,b){return this.optional(b)||/^((([A-PR-UWYZ][0-9])|([A-PR-UWYZ][0-9][0-9])|([A-PR-UWYZ][A-HK-Y][0-9])|([A-PR-UWYZ][A-HK-Y][0-9][0-9])|([A-PR-UWYZ][0-9][A-HJKSTUW])|([A-PR-UWYZ][A-HK-Y][0-9][ABEHMNPRVWXY]))\s?([0-9][ABD-HJLNP-UW-Z]{2})|(GIR)\s?(0AA))$/i.test(a)},"Please specify a valid UK postcode"),a.validator.addMethod("require_from_group",function(b,c,d){var e=a(d[1],c.form),f=e.eq(0),g=f.data("valid_req_grp")?f.data("valid_req_grp"):a.extend({},this),h=e.filter(function(){return g.elementValue(this)}).length>=d[0];return f.data("valid_req_grp",g),a(c).data("being_validated")||(e.data("being_validated",!0),e.each(function(){g.element(this)}),e.data("being_validated",!1)),h},a.validator.format("Please fill at least {0} of these fields.")),a.validator.addMethod("skip_or_fill_minimum",function(b,c,d){var e=a(d[1],c.form),f=e.eq(0),g=f.data("valid_skip")?f.data("valid_skip"):a.extend({},this),h=e.filter(function(){return g.elementValue(this)}).length,i=0===h||h>=d[0];return f.data("valid_skip",g),a(c).data("being_validated")||(e.data("being_validated",!0),e.each(function(){g.element(this)}),e.data("being_validated",!1)),i},a.validator.format("Please either skip these fields or fill at least {0} of them.")),a.validator.addMethod("stateUS",function(a,b,c){var d,e="undefined"==typeof c,f=e||"undefined"==typeof c.caseSensitive?!1:c.caseSensitive,g=e||"undefined"==typeof c.includeTerritories?!1:c.includeTerritories,h=e||"undefined"==typeof c.includeMilitary?!1:c.includeMilitary;return d=g||h?g&&h?"^(A[AEKLPRSZ]|C[AOT]|D[CE]|FL|G[AU]|HI|I[ADLN]|K[SY]|LA|M[ADEINOPST]|N[CDEHJMVY]|O[HKR]|P[AR]|RI|S[CD]|T[NX]|UT|V[AIT]|W[AIVY])$":g?"^(A[KLRSZ]|C[AOT]|D[CE]|FL|G[AU]|HI|I[ADLN]|K[SY]|LA|M[ADEINOPST]|N[CDEHJMVY]|O[HKR]|P[AR]|RI|S[CD]|T[NX]|UT|V[AIT]|W[AIVY])$":"^(A[AEKLPRZ]|C[AOT]|D[CE]|FL|GA|HI|I[ADLN]|K[SY]|LA|M[ADEINOST]|N[CDEHJMVY]|O[HKR]|PA|RI|S[CD]|T[NX]|UT|V[AT]|W[AIVY])$":"^(A[KLRZ]|C[AOT]|D[CE]|FL|GA|HI|I[ADLN]|K[SY]|LA|M[ADEINOST]|N[CDEHJMVY]|O[HKR]|PA|RI|S[CD]|T[NX]|UT|V[AT]|W[AIVY])$",d=f?new RegExp(d):new RegExp(d,"i"),this.optional(b)||d.test(a)},"Please specify a valid state"),a.validator.addMethod("strippedminlength",function(b,c,d){return a(b).text().length>=d},a.validator.format("Please enter at least {0} characters")),a.validator.addMethod("time",function(a,b){return this.optional(b)||/^([01]\d|2[0-3]|[0-9])(:[0-5]\d){1,2}$/.test(a)},"Please enter a valid time, between 00:00 and 23:59"),a.validator.addMethod("time12h",function(a,b){return this.optional(b)||/^((0?[1-9]|1[012])(:[0-5]\d){1,2}(\ ?[AP]M))$/i.test(a)},"Please enter a valid time in 12-hour am/pm format"),a.validator.addMethod("url2",function(a,b){return this.optional(b)||/^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)*(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(a)},a.validator.messages.url),a.validator.addMethod("vinUS",function(a){if(17!==a.length)return!1;var b,c,d,e,f,g,h=["A","B","C","D","E","F","G","H","J","K","L","M","N","P","R","S","T","U","V","W","X","Y","Z"],i=[1,2,3,4,5,6,7,8,1,2,3,4,5,7,9,2,3,4,5,6,7,8,9],j=[8,7,6,5,4,3,2,10,0,9,8,7,6,5,4,3,2],k=0;for(b=0;17>b;b++){if(e=j[b],d=a.slice(b,b+1),8===b&&(g=d),isNaN(d)){for(c=0;c").attr("name",c.submitButton.name).val(a(c.submitButton).val()).appendTo(c.currentForm)),e=c.settings.submitHandler.call(c,c.currentForm,b),c.submitButton&&d.remove(),void 0!==e?e:!1):!0}return c.settings.debug&&b.preventDefault(),c.cancelSubmit?(c.cancelSubmit=!1,d()):c.form()?c.pendingRequest?(c.formSubmitted=!0,!1):d():(c.focusInvalid(),!1)})),c)},valid:function(){var b,c,d;return a(this[0]).is("form")?b=this.validate().form():(d=[],b=!0,c=a(this[0].form).validate(),this.each(function(){b=c.element(this)&&b,d=d.concat(c.errorList)}),c.errorList=d),b},rules:function(b,c){var d,e,f,g,h,i,j=this[0];if(b)switch(d=a.data(j.form,"validator").settings,e=d.rules,f=a.validator.staticRules(j),b){case"add":a.extend(f,a.validator.normalizeRule(c)),delete f.messages,e[j.name]=f,c.messages&&(d.messages[j.name]=a.extend(d.messages[j.name],c.messages));break;case"remove":return c?(i={},a.each(c.split(/\s/),function(b,c){i[c]=f[c],delete f[c],"required"===c&&a(j).removeAttr("aria-required")}),i):(delete e[j.name],f)}return g=a.validator.normalizeRules(a.extend({},a.validator.classRules(j),a.validator.attributeRules(j),a.validator.dataRules(j),a.validator.staticRules(j)),j),g.required&&(h=g.required,delete g.required,g=a.extend({required:h},g),a(j).attr("aria-required","true")),g.remote&&(h=g.remote,delete g.remote,g=a.extend(g,{remote:h})),g}}),a.extend(a.expr[":"],{blank:function(b){return!a.trim(""+a(b).val())},filled:function(b){return!!a.trim(""+a(b).val())},unchecked:function(b){return!a(b).prop("checked")}}),a.validator=function(b,c){this.settings=a.extend(!0,{},a.validator.defaults,b),this.currentForm=c,this.init()},a.validator.format=function(b,c){return 1===arguments.length?function(){var c=a.makeArray(arguments);return c.unshift(b),a.validator.format.apply(this,c)}:(arguments.length>2&&c.constructor!==Array&&(c=a.makeArray(arguments).slice(1)),c.constructor!==Array&&(c=[c]),a.each(c,function(a,c){b=b.replace(new RegExp("\\{"+a+"\\}","g"),function(){return c})}),b)},a.extend(a.validator,{defaults:{messages:{},groups:{},rules:{},errorClass:"error",validClass:"valid",errorElement:"label",focusCleanup:!1,focusInvalid:!0,errorContainer:a([]),errorLabelContainer:a([]),onsubmit:!0,ignore:":hidden",ignoreTitle:!1,onfocusin:function(a){this.lastActive=a,this.settings.focusCleanup&&(this.settings.unhighlight&&this.settings.unhighlight.call(this,a,this.settings.errorClass,this.settings.validClass),this.hideThese(this.errorsFor(a)))},onfocusout:function(a){this.checkable(a)||!(a.name in this.submitted)&&this.optional(a)||this.element(a)},onkeyup:function(b,c){var d=[16,17,18,20,35,36,37,38,39,40,45,144,225];9===c.which&&""===this.elementValue(b)||-1!==a.inArray(c.keyCode,d)||(b.name in this.submitted||b===this.lastElement)&&this.element(b)},onclick:function(a){a.name in this.submitted?this.element(a):a.parentNode.name in this.submitted&&this.element(a.parentNode)},highlight:function(b,c,d){"radio"===b.type?this.findByName(b.name).addClass(c).removeClass(d):a(b).addClass(c).removeClass(d)},unhighlight:function(b,c,d){"radio"===b.type?this.findByName(b.name).removeClass(c).addClass(d):a(b).removeClass(c).addClass(d)}},setDefaults:function(b){a.extend(a.validator.defaults,b)},messages:{required:"This field is required.",remote:"Please fix this field.",email:"Please enter a valid email address.",url:"Please enter a valid URL.",date:"Please enter a valid date.",dateISO:"Please enter a valid date ( ISO ).",number:"Please enter a valid number.",digits:"Please enter only digits.",creditcard:"Please enter a valid credit card number.",equalTo:"Please enter the same value again.",maxlength:a.validator.format("Please enter no more than {0} characters."),minlength:a.validator.format("Please enter at least {0} characters."),rangelength:a.validator.format("Please enter a value between {0} and {1} characters long."),range:a.validator.format("Please enter a value between {0} and {1}."),max:a.validator.format("Please enter a value less than or equal to {0}."),min:a.validator.format("Please enter a value greater than or equal to {0}.")},autoCreateRanges:!1,prototype:{init:function(){function b(b){var c=a.data(this.form,"validator"),d="on"+b.type.replace(/^validate/,""),e=c.settings;e[d]&&!a(this).is(e.ignore)&&e[d].call(c,this,b)}this.labelContainer=a(this.settings.errorLabelContainer),this.errorContext=this.labelContainer.length&&this.labelContainer||a(this.currentForm),this.containers=a(this.settings.errorContainer).add(this.settings.errorLabelContainer),this.submitted={},this.valueCache={},this.pendingRequest=0,this.pending={},this.invalid={},this.reset();var c,d=this.groups={};a.each(this.settings.groups,function(b,c){"string"==typeof c&&(c=c.split(/\s/)),a.each(c,function(a,c){d[c]=b})}),c=this.settings.rules,a.each(c,function(b,d){c[b]=a.validator.normalizeRule(d)}),a(this.currentForm).on("focusin.validate focusout.validate keyup.validate",":text, [type='password'], [type='file'], select, textarea, [type='number'], [type='search'], [type='tel'], [type='url'], [type='email'], [type='datetime'], [type='date'], [type='month'], [type='week'], [type='time'], [type='datetime-local'], [type='range'], [type='color'], [type='radio'], [type='checkbox']",b).on("click.validate","select, option, [type='radio'], [type='checkbox']",b),this.settings.invalidHandler&&a(this.currentForm).on("invalid-form.validate",this.settings.invalidHandler),a(this.currentForm).find("[required], [data-rule-required], .required").attr("aria-required","true")},form:function(){return this.checkForm(),a.extend(this.submitted,this.errorMap),this.invalid=a.extend({},this.errorMap),this.valid()||a(this.currentForm).triggerHandler("invalid-form",[this]),this.showErrors(),this.valid()},checkForm:function(){this.prepareForm();for(var a=0,b=this.currentElements=this.elements();b[a];a++)this.check(b[a]);return this.valid()},element:function(b){var c=this.clean(b),d=this.validationTargetFor(c),e=!0;return this.lastElement=d,void 0===d?delete this.invalid[c.name]:(this.prepareElement(d),this.currentElements=a(d),e=this.check(d)!==!1,e?delete this.invalid[d.name]:this.invalid[d.name]=!0),a(b).attr("aria-invalid",!e),this.numberOfInvalids()||(this.toHide=this.toHide.add(this.containers)),this.showErrors(),e},showErrors:function(b){if(b){a.extend(this.errorMap,b),this.errorList=[];for(var c in b)this.errorList.push({message:b[c],element:this.findByName(c)[0]});this.successList=a.grep(this.successList,function(a){return!(a.name in b)})}this.settings.showErrors?this.settings.showErrors.call(this,this.errorMap,this.errorList):this.defaultShowErrors()},resetForm:function(){a.fn.resetForm&&a(this.currentForm).resetForm(),this.submitted={},this.lastElement=null,this.prepareForm(),this.hideErrors();var b,c=this.elements().removeData("previousValue").removeAttr("aria-invalid");if(this.settings.unhighlight)for(b=0;c[b];b++)this.settings.unhighlight.call(this,c[b],this.settings.errorClass,"");else c.removeClass(this.settings.errorClass)},numberOfInvalids:function(){return this.objectLength(this.invalid)},objectLength:function(a){var b,c=0;for(b in a)c++;return c},hideErrors:function(){this.hideThese(this.toHide)},hideThese:function(a){a.not(this.containers).text(""),this.addWrapper(a).hide()},valid:function(){return 0===this.size()},size:function(){return this.errorList.length},focusInvalid:function(){if(this.settings.focusInvalid)try{a(this.findLastActive()||this.errorList.length&&this.errorList[0].element||[]).filter(":visible").focus().trigger("focusin")}catch(b){}},findLastActive:function(){var b=this.lastActive;return b&&1===a.grep(this.errorList,function(a){return a.element.name===b.name}).length&&b},elements:function(){var b=this,c={};return a(this.currentForm).find("input, select, textarea").not(":submit, :reset, :image, :disabled").not(this.settings.ignore).filter(function(){return!this.name&&b.settings.debug&&window.console&&console.error("%o has no name assigned",this),this.name in c||!b.objectLength(a(this).rules())?!1:(c[this.name]=!0,!0)})},clean:function(b){return a(b)[0]},errors:function(){var b=this.settings.errorClass.split(" ").join(".");return a(this.settings.errorElement+"."+b,this.errorContext)},reset:function(){this.successList=[],this.errorList=[],this.errorMap={},this.toShow=a([]),this.toHide=a([]),this.currentElements=a([])},prepareForm:function(){this.reset(),this.toHide=this.errors().add(this.containers)},prepareElement:function(a){this.reset(),this.toHide=this.errorsFor(a)},elementValue:function(b){var c,d=a(b),e=b.type;return"radio"===e||"checkbox"===e?this.findByName(b.name).filter(":checked").val():"number"===e&&"undefined"!=typeof b.validity?b.validity.badInput?!1:d.val():(c=d.val(),"string"==typeof c?c.replace(/\r/g,""):c)},check:function(b){b=this.validationTargetFor(this.clean(b));var c,d,e,f=a(b).rules(),g=a.map(f,function(a,b){return b}).length,h=!1,i=this.elementValue(b);for(d in f){e={method:d,parameters:f[d]};try{if(c=a.validator.methods[d].call(this,i,b,e.parameters),"dependency-mismatch"===c&&1===g){h=!0;continue}if(h=!1,"pending"===c)return void(this.toHide=this.toHide.not(this.errorsFor(b)));if(!c)return this.formatAndAdd(b,e),!1}catch(j){throw this.settings.debug&&window.console&&console.log("Exception occurred when checking element "+b.id+", check the '"+e.method+"' method.",j),j instanceof TypeError&&(j.message+=". Exception occurred when checking element "+b.id+", check the '"+e.method+"' method."),j}}if(!h)return this.objectLength(f)&&this.successList.push(b),!0},customDataMessage:function(b,c){return a(b).data("msg"+c.charAt(0).toUpperCase()+c.substring(1).toLowerCase())||a(b).data("msg")},customMessage:function(a,b){var c=this.settings.messages[a];return c&&(c.constructor===String?c:c[b])},findDefined:function(){for(var a=0;aWarning: No message defined for "+b.name+"")},formatAndAdd:function(b,c){var d=this.defaultMessage(b,c.method),e=/\$?\{(\d+)\}/g;"function"==typeof d?d=d.call(this,c.parameters,b):e.test(d)&&(d=a.validator.format(d.replace(e,"{$1}"),c.parameters)),this.errorList.push({message:d,element:b,method:c.method}),this.errorMap[b.name]=d,this.submitted[b.name]=d},addWrapper:function(a){return this.settings.wrapper&&(a=a.add(a.parent(this.settings.wrapper))),a},defaultShowErrors:function(){var a,b,c;for(a=0;this.errorList[a];a++)c=this.errorList[a],this.settings.highlight&&this.settings.highlight.call(this,c.element,this.settings.errorClass,this.settings.validClass),this.showLabel(c.element,c.message);if(this.errorList.length&&(this.toShow=this.toShow.add(this.containers)),this.settings.success)for(a=0;this.successList[a];a++)this.showLabel(this.successList[a]);if(this.settings.unhighlight)for(a=0,b=this.validElements();b[a];a++)this.settings.unhighlight.call(this,b[a],this.settings.errorClass,this.settings.validClass);this.toHide=this.toHide.not(this.toShow),this.hideErrors(),this.addWrapper(this.toShow).show()},validElements:function(){return this.currentElements.not(this.invalidElements())},invalidElements:function(){return a(this.errorList).map(function(){return this.element})},showLabel:function(b,c){var d,e,f,g=this.errorsFor(b),h=this.idOrName(b),i=a(b).attr("aria-describedby");g.length?(g.removeClass(this.settings.validClass).addClass(this.settings.errorClass),g.html(c)):(g=a("<"+this.settings.errorElement+">").attr("id",h+"-error").addClass(this.settings.errorClass).html(c||""),d=g,this.settings.wrapper&&(d=g.hide().show().wrap("<"+this.settings.wrapper+"/>").parent()),this.labelContainer.length?this.labelContainer.append(d):this.settings.errorPlacement?this.settings.errorPlacement(d,a(b)):d.insertAfter(b),g.is("label")?g.attr("for",h):0===g.parents("label[for='"+h+"']").length&&(f=g.attr("id").replace(/(:|\.|\[|\]|\$)/g,"\\$1"),i?i.match(new RegExp("\\b"+f+"\\b"))||(i+=" "+f):i=f,a(b).attr("aria-describedby",i),e=this.groups[b.name],e&&a.each(this.groups,function(b,c){c===e&&a("[name='"+b+"']",this.currentForm).attr("aria-describedby",g.attr("id"))}))),!c&&this.settings.success&&(g.text(""),"string"==typeof this.settings.success?g.addClass(this.settings.success):this.settings.success(g,b)),this.toShow=this.toShow.add(g)},errorsFor:function(b){var c=this.idOrName(b),d=a(b).attr("aria-describedby"),e="label[for='"+c+"'], label[for='"+c+"'] *";return d&&(e=e+", #"+d.replace(/\s+/g,", #")),this.errors().filter(e)},idOrName:function(a){return this.groups[a.name]||(this.checkable(a)?a.name:a.id||a.name)},validationTargetFor:function(b){return this.checkable(b)&&(b=this.findByName(b.name)),a(b).not(this.settings.ignore)[0]},checkable:function(a){return/radio|checkbox/i.test(a.type)},findByName:function(b){return a(this.currentForm).find("[name='"+b+"']")},getLength:function(b,c){switch(c.nodeName.toLowerCase()){case"select":return a("option:selected",c).length;case"input":if(this.checkable(c))return this.findByName(c.name).filter(":checked").length}return b.length},depend:function(a,b){return this.dependTypes[typeof a]?this.dependTypes[typeof a](a,b):!0},dependTypes:{"boolean":function(a){return a},string:function(b,c){return!!a(b,c.form).length},"function":function(a,b){return a(b)}},optional:function(b){var c=this.elementValue(b);return!a.validator.methods.required.call(this,c,b)&&"dependency-mismatch"},startRequest:function(a){this.pending[a.name]||(this.pendingRequest++,this.pending[a.name]=!0)},stopRequest:function(b,c){this.pendingRequest--,this.pendingRequest<0&&(this.pendingRequest=0),delete this.pending[b.name],c&&0===this.pendingRequest&&this.formSubmitted&&this.form()?(a(this.currentForm).submit(),this.formSubmitted=!1):!c&&0===this.pendingRequest&&this.formSubmitted&&(a(this.currentForm).triggerHandler("invalid-form",[this]),this.formSubmitted=!1)},previousValue:function(b){return a.data(b,"previousValue")||a.data(b,"previousValue",{old:null,valid:!0,message:this.defaultMessage(b,"remote")})},destroy:function(){this.resetForm(),a(this.currentForm).off(".validate").removeData("validator")}},classRuleSettings:{required:{required:!0},email:{email:!0},url:{url:!0},date:{date:!0},dateISO:{dateISO:!0},number:{number:!0},digits:{digits:!0},creditcard:{creditcard:!0}},addClassRules:function(b,c){b.constructor===String?this.classRuleSettings[b]=c:a.extend(this.classRuleSettings,b)},classRules:function(b){var c={},d=a(b).attr("class");return d&&a.each(d.split(" "),function(){this in a.validator.classRuleSettings&&a.extend(c,a.validator.classRuleSettings[this])}),c},normalizeAttributeRule:function(a,b,c,d){/min|max/.test(c)&&(null===b||/number|range|text/.test(b))&&(d=Number(d),isNaN(d)&&(d=void 0)),d||0===d?a[c]=d:b===c&&"range"!==b&&(a[c]=!0)},attributeRules:function(b){var c,d,e={},f=a(b),g=b.getAttribute("type");for(c in a.validator.methods)"required"===c?(d=b.getAttribute(c),""===d&&(d=!0),d=!!d):d=f.attr(c),this.normalizeAttributeRule(e,g,c,d);return e.maxlength&&/-1|2147483647|524288/.test(e.maxlength)&&delete e.maxlength,e},dataRules:function(b){var c,d,e={},f=a(b),g=b.getAttribute("type");for(c in a.validator.methods)d=f.data("rule"+c.charAt(0).toUpperCase()+c.substring(1).toLowerCase()),this.normalizeAttributeRule(e,g,c,d);return e},staticRules:function(b){var c={},d=a.data(b.form,"validator");return d.settings.rules&&(c=a.validator.normalizeRule(d.settings.rules[b.name])||{}),c},normalizeRules:function(b,c){return a.each(b,function(d,e){if(e===!1)return void delete b[d];if(e.param||e.depends){var f=!0;switch(typeof e.depends){case"string":f=!!a(e.depends,c.form).length;break;case"function":f=e.depends.call(c,c)}f?b[d]=void 0!==e.param?e.param:!0:delete b[d]}}),a.each(b,function(d,e){b[d]=a.isFunction(e)?e(c):e}),a.each(["minlength","maxlength"],function(){b[this]&&(b[this]=Number(b[this]))}),a.each(["rangelength","range"],function(){var c;b[this]&&(a.isArray(b[this])?b[this]=[Number(b[this][0]),Number(b[this][1])]:"string"==typeof b[this]&&(c=b[this].replace(/[\[\]]/g,"").split(/[\s,]+/),b[this]=[Number(c[0]),Number(c[1])]))}),a.validator.autoCreateRanges&&(null!=b.min&&null!=b.max&&(b.range=[b.min,b.max],delete b.min,delete b.max),null!=b.minlength&&null!=b.maxlength&&(b.rangelength=[b.minlength,b.maxlength],delete b.minlength,delete b.maxlength)),b},normalizeRule:function(b){if("string"==typeof b){var c={};a.each(b.split(/\s/),function(){c[this]=!0}),b=c}return b},addMethod:function(b,c,d){a.validator.methods[b]=c,a.validator.messages[b]=void 0!==d?d:a.validator.messages[b],c.length<3&&a.validator.addClassRules(b,a.validator.normalizeRule(b))},methods:{required:function(b,c,d){if(!this.depend(d,c))return"dependency-mismatch";if("select"===c.nodeName.toLowerCase()){var e=a(c).val();return e&&e.length>0}return this.checkable(c)?this.getLength(b,c)>0:b.length>0},email:function(a,b){return this.optional(b)||/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(a)},url:function(a,b){return this.optional(b)||/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(a)},date:function(a,b){return this.optional(b)||!/Invalid|NaN/.test(new Date(a).toString())},dateISO:function(a,b){return this.optional(b)||/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(a)},number:function(a,b){return this.optional(b)||/^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(a)},digits:function(a,b){return this.optional(b)||/^\d+$/.test(a)},creditcard:function(a,b){if(this.optional(b))return"dependency-mismatch";if(/[^0-9 \-]+/.test(a))return!1;var c,d,e=0,f=0,g=!1;if(a=a.replace(/\D/g,""),a.length<13||a.length>19)return!1;for(c=a.length-1;c>=0;c--)d=a.charAt(c),f=parseInt(d,10),g&&(f*=2)>9&&(f-=9),e+=f,g=!g;return e%10===0},minlength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||e>=d},maxlength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||d>=e},rangelength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||e>=d[0]&&e<=d[1]},min:function(a,b,c){return this.optional(b)||a>=c},max:function(a,b,c){return this.optional(b)||c>=a},range:function(a,b,c){return this.optional(b)||a>=c[0]&&a<=c[1]},equalTo:function(b,c,d){var e=a(d);return this.settings.onfocusout&&e.off(".validate-equalTo").on("blur.validate-equalTo",function(){a(c).valid()}),b===e.val()},remote:function(b,c,d){if(this.optional(c))return"dependency-mismatch";var e,f,g=this.previousValue(c);return this.settings.messages[c.name]||(this.settings.messages[c.name]={}),g.originalMessage=this.settings.messages[c.name].remote,this.settings.messages[c.name].remote=g.message,d="string"==typeof d&&{url:d}||d,g.old===b?g.valid:(g.old=b,e=this,this.startRequest(c),f={},f[c.name]=b,a.ajax(a.extend(!0,{mode:"abort",port:"validate"+c.name,dataType:"json",data:f,context:e.currentForm,success:function(d){var f,h,i,j=d===!0||"true"===d;e.settings.messages[c.name].remote=g.originalMessage,j?(i=e.formSubmitted,e.prepareElement(c),e.formSubmitted=i,e.successList.push(c),delete e.invalid[c.name],e.showErrors()):(f={},h=d||e.defaultMessage(c,"remote"),f[c.name]=g.message=a.isFunction(h)?h(b):h,e.invalid[c.name]=!0,e.showErrors(f)),g.valid=j,e.stopRequest(c,j)}},d)),"pending")}}});var b,c={};a.ajaxPrefilter?a.ajaxPrefilter(function(a,b,d){var e=a.port;"abort"===a.mode&&(c[e]&&c[e].abort(),c[e]=d)}):(b=a.ajax,a.ajax=function(d){var e=("mode"in d?d:a.ajaxSettings).mode,f=("port"in d?d:a.ajaxSettings).port;return"abort"===e?(c[f]&&c[f].abort(),c[f]=b.apply(this,arguments),c[f]):b.apply(this,arguments)})}); -------------------------------------------------------------------------------- /wwwroot/lib/jquery/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery", 3 | "main": "dist/jquery.js", 4 | "license": "MIT", 5 | "ignore": [ 6 | "package.json" 7 | ], 8 | "keywords": [ 9 | "jquery", 10 | "javascript", 11 | "browser", 12 | "library" 13 | ], 14 | "homepage": "https://github.com/jquery/jquery-dist", 15 | "version": "2.2.0", 16 | "_release": "2.2.0", 17 | "_resolution": { 18 | "type": "version", 19 | "tag": "2.2.0", 20 | "commit": "6fc01e29bdad0964f62ef56d01297039cdcadbe5" 21 | }, 22 | "_source": "git://github.com/jquery/jquery-dist.git", 23 | "_target": "2.2.0", 24 | "_originalSource": "jquery" 25 | } -------------------------------------------------------------------------------- /wwwroot/lib/jquery/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright jQuery Foundation and other contributors, https://jquery.org/ 2 | 3 | This software consists of voluntary contributions made by many 4 | individuals. For exact contribution history, see the revision history 5 | available at https://github.com/jquery/jquery 6 | 7 | The following license applies to all parts of this software except as 8 | documented below: 9 | 10 | ==== 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining 13 | a copy of this software and associated documentation files (the 14 | "Software"), to deal in the Software without restriction, including 15 | without limitation the rights to use, copy, modify, merge, publish, 16 | distribute, sublicense, and/or sell copies of the Software, and to 17 | permit persons to whom the Software is furnished to do so, subject to 18 | the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | 31 | ==== 32 | 33 | All files located in the node_modules and external directories are 34 | externally maintained libraries used by this software which have their 35 | own licenses; we recommend you read them, as their terms may differ from 36 | the terms above. 37 | --------------------------------------------------------------------------------