├── DosFactores ├── wwwroot │ ├── js │ │ ├── site.min.js │ │ └── site.js │ ├── favicon.ico │ ├── lib │ │ ├── bootstrap │ │ │ ├── dist │ │ │ │ ├── fonts │ │ │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ │ │ └── glyphicons-halflings-regular.woff2 │ │ │ │ └── js │ │ │ │ │ └── npm.js │ │ │ ├── .bower.json │ │ │ └── LICENSE │ │ ├── jquery │ │ │ ├── .bower.json │ │ │ └── LICENSE.txt │ │ ├── jquery-validation │ │ │ ├── .bower.json │ │ │ ├── LICENSE.md │ │ │ └── dist │ │ │ │ └── additional-methods.min.js │ │ └── jquery-validation-unobtrusive │ │ │ ├── .bower.json │ │ │ └── jquery.validate.unobtrusive.min.js │ ├── css │ │ ├── site.min.css │ │ └── site.css │ └── images │ │ ├── banner2.svg │ │ ├── banner1.svg │ │ ├── banner3.svg │ │ └── banner4.svg ├── .bowerrc ├── Views │ ├── _ViewStart.cshtml │ ├── Home │ │ ├── About.cshtml │ │ ├── Contact.cshtml │ │ └── Index.cshtml │ ├── Account │ │ ├── ForgotPasswordConfirmation.cshtml │ │ ├── ExternalLoginFailure.cshtml │ │ ├── AccessDenied.cshtml │ │ ├── Lockout.cshtml │ │ ├── ResetPasswordConfirmation.cshtml │ │ ├── ConfirmEmail.cshtml │ │ ├── SendCode.cshtml │ │ ├── ForgotPassword.cshtml │ │ ├── ExternalLoginConfirmation.cshtml │ │ ├── VerifyCode.cshtml │ │ ├── Register.cshtml │ │ ├── ResetPassword.cshtml │ │ └── Login.cshtml │ ├── _ViewImports.cshtml │ ├── Shared │ │ ├── Error.cshtml │ │ ├── _LoginPartial.cshtml │ │ ├── _ValidationScriptsPartial.cshtml │ │ └── _Layout.cshtml │ └── Manage │ │ ├── AddPhoneNumber.cshtml │ │ ├── VerifyPhoneNumber.cshtml │ │ ├── SetPassword.cshtml │ │ ├── ChangePassword.cshtml │ │ ├── ManageLogins.cshtml │ │ └── Index.cshtml ├── appsettings.Development.json ├── bower.json ├── Services │ ├── ISmsSender.cs │ ├── IEmailSender.cs │ └── MessageServices.cs ├── Models │ ├── ManageViewModels │ │ ├── FactorViewModel.cs │ │ ├── RemoveLoginViewModel.cs │ │ ├── AddPhoneNumberViewModel.cs │ │ ├── ConfigureTwoFactorViewModel.cs │ │ ├── ManageLoginsViewModel.cs │ │ ├── VerifyPhoneNumberViewModel.cs │ │ ├── IndexViewModel.cs │ │ ├── SetPasswordViewModel.cs │ │ └── ChangePasswordViewModel.cs │ ├── AccountViewModels │ │ ├── ForgotPasswordViewModel.cs │ │ ├── ExternalLoginConfirmationViewModel.cs │ │ ├── SendCodeViewModel.cs │ │ ├── LoginViewModel.cs │ │ ├── VerifyCodeViewModel.cs │ │ ├── ResetPasswordViewModel.cs │ │ └── RegisterViewModel.cs │ └── ApplicationUser.cs ├── appsettings.json ├── Program.cs ├── bundleconfig.json ├── Properties │ └── launchSettings.json ├── Controllers │ ├── HomeController.cs │ └── ManageController.cs ├── Data │ ├── ApplicationDbContext.cs │ └── Migrations │ │ ├── ApplicationDbContextModelSnapshot.cs │ │ ├── 00000000000000_CreateIdentitySchema.Designer.cs │ │ └── 00000000000000_CreateIdentitySchema.cs ├── Providers │ └── GoogleAuthenticatorProvider.cs ├── DosFactores.csproj └── Startup.cs ├── README.md ├── Google.Authenticator ├── Google.Authenticator.csproj ├── SetupCode.cs └── TwoFactorAuthentication.cs ├── DosFactores.sln ├── Instrucciones.md └── .gitignore /DosFactores/wwwroot/js/site.min.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /DosFactores/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "wwwroot/lib" 3 | } 4 | -------------------------------------------------------------------------------- /DosFactores/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Write your Javascript code. 2 | -------------------------------------------------------------------------------- /DosFactores/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DosFactores 2 | Autenticación de dos factores en ASP.NET Core 3 | -------------------------------------------------------------------------------- /DosFactores/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thatcsharpguy/DosFactores/master/DosFactores/wwwroot/favicon.ico -------------------------------------------------------------------------------- /Google.Authenticator/Google.Authenticator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp1.1 5 | 6 | 7 | -------------------------------------------------------------------------------- /DosFactores/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 | -------------------------------------------------------------------------------- /DosFactores/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thatcsharpguy/DosFactores/master/DosFactores/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /DosFactores/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thatcsharpguy/DosFactores/master/DosFactores/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /DosFactores/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thatcsharpguy/DosFactores/master/DosFactores/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /DosFactores/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thatcsharpguy/DosFactores/master/DosFactores/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /DosFactores/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 | -------------------------------------------------------------------------------- /DosFactores/Views/Account/ExternalLoginFailure.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Login Failure"; 3 | } 4 | 5 |
6 |

@ViewData["Title"].

7 |

Unsuccessful login with service.

8 |
9 | -------------------------------------------------------------------------------- /DosFactores/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /DosFactores/Views/Account/AccessDenied.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Access Denied"; 3 | } 4 | 5 |
6 |

Access Denied.

7 |

You do not have access to this resource.

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

Locked out.

7 |

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

8 |
9 | -------------------------------------------------------------------------------- /DosFactores/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 | -------------------------------------------------------------------------------- /DosFactores/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using DosFactores 2 | @using DosFactores.Models 3 | @using DosFactores.Models.AccountViewModels 4 | @using DosFactores.Models.ManageViewModels 5 | @using Microsoft.AspNetCore.Identity 6 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 7 | -------------------------------------------------------------------------------- /DosFactores/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 | -------------------------------------------------------------------------------- /DosFactores/Views/Account/ConfirmEmail.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Confirm Email"; 3 | } 4 | 5 |

@ViewData["Title"].

6 |
7 |

8 | Thank you for confirming your email. Please Click here to Log in. 9 |

10 |
11 | -------------------------------------------------------------------------------- /DosFactores/Services/ISmsSender.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace DosFactores.Services 7 | { 8 | public interface ISmsSender 9 | { 10 | Task SendSmsAsync(string number, string message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /DosFactores/Models/ManageViewModels/FactorViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace DosFactores.Models.ManageViewModels 7 | { 8 | public class FactorViewModel 9 | { 10 | public string Purpose { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /DosFactores/Services/IEmailSender.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace DosFactores.Services 7 | { 8 | public interface IEmailSender 9 | { 10 | Task SendEmailAsync(string email, string subject, string message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /DosFactores/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-DosFactores-b96d62e5-2472-4f50-a45e-7c211bd2a738;Trusted_Connection=True;MultipleActiveResultSets=true" 4 | }, 5 | "Logging": { 6 | "IncludeScopes": false, 7 | "LogLevel": { 8 | "Default": "Warning" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Google.Authenticator/SetupCode.cs: -------------------------------------------------------------------------------- 1 | namespace Google.Authenticator 2 | { 3 | public class SetupCode 4 | { 5 | public string Account { get; internal set; } 6 | public string AccountSecretKey { get; internal set; } 7 | public string ManualEntryKey { get; internal set; } 8 | public string QrCodeSetupImageUrl { get; internal set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /DosFactores/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 DosFactores.Models.AccountViewModels 8 | { 9 | public class ForgotPasswordViewModel 10 | { 11 | [Required] 12 | [EmailAddress] 13 | public string Email { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /DosFactores/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 DosFactores.Models.ManageViewModels 8 | { 9 | public class RemoveLoginViewModel 10 | { 11 | public string LoginProvider { get; set; } 12 | public string ProviderKey { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /DosFactores/wwwroot/css/site.min.css: -------------------------------------------------------------------------------- 1 | body{padding-top:50px;padding-bottom:20px}.body-content{padding-left:15px;padding-right:15px}input,select,textarea{max-width:280px}.carousel-caption p{font-size:20px;line-height:1.4}.btn-bracketed::before{display:inline-block;content:"[";padding-right:.5em}.btn-bracketed::after{display:inline-block;content:"]";padding-left:.5em}.carousel-inner .item img[src$=".svg"]{width:100%}@media screen and (max-width:767px){.carousel-caption{display:none}} -------------------------------------------------------------------------------- /DosFactores/Models/AccountViewModels/ExternalLoginConfirmationViewModel.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 DosFactores.Models.AccountViewModels 8 | { 9 | public class ExternalLoginConfirmationViewModel 10 | { 11 | [Required] 12 | [EmailAddress] 13 | public string Email { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /DosFactores/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.EntityFrameworkCore; 6 | 7 | namespace DosFactores.Models 8 | { 9 | // Add profile data for application users by adding properties to the ApplicationUser class 10 | public class ApplicationUser : IdentityUser 11 | { 12 | public virtual string TfaKey { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /DosFactores/Models/ManageViewModels/AddPhoneNumberViewModel.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 DosFactores.Models.ManageViewModels 8 | { 9 | public class AddPhoneNumberViewModel 10 | { 11 | [Required] 12 | [Phone] 13 | [Display(Name = "Phone number")] 14 | public string PhoneNumber { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /DosFactores/Models/ManageViewModels/ConfigureTwoFactorViewModel.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 | 7 | namespace DosFactores.Models.ManageViewModels 8 | { 9 | public class ConfigureTwoFactorViewModel 10 | { 11 | public string SelectedProvider { get; set; } 12 | 13 | public ICollection Providers { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /DosFactores/Models/ManageViewModels/ManageLoginsViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Http.Authentication; 6 | using Microsoft.AspNetCore.Identity; 7 | 8 | namespace DosFactores.Models.ManageViewModels 9 | { 10 | public class ManageLoginsViewModel 11 | { 12 | public IList CurrentLogins { get; set; } 13 | 14 | public IList OtherLogins { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /DosFactores/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 | -------------------------------------------------------------------------------- /DosFactores/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') -------------------------------------------------------------------------------- /DosFactores/Models/ManageViewModels/VerifyPhoneNumberViewModel.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 DosFactores.Models.ManageViewModels 8 | { 9 | public class VerifyPhoneNumberViewModel 10 | { 11 | [Required] 12 | public string Code { get; set; } 13 | 14 | [Required] 15 | [Phone] 16 | [Display(Name = "Phone number")] 17 | public string PhoneNumber { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /DosFactores/Models/AccountViewModels/SendCodeViewModel.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 | 7 | namespace DosFactores.Models.AccountViewModels 8 | { 9 | public class SendCodeViewModel 10 | { 11 | public string SelectedProvider { get; set; } 12 | 13 | public ICollection Providers { get; set; } 14 | 15 | public string ReturnUrl { get; set; } 16 | 17 | public bool RememberMe { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /DosFactores/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 DosFactores.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 | -------------------------------------------------------------------------------- /DosFactores/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 | } -------------------------------------------------------------------------------- /DosFactores/Models/ManageViewModels/IndexViewModel.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 DosFactores.Models.ManageViewModels 8 | { 9 | public class IndexViewModel 10 | { 11 | public bool HasPassword { get; set; } 12 | 13 | public IList Logins { get; set; } 14 | 15 | public string PhoneNumber { get; set; } 16 | 17 | public string TwoFactorAuthenticatorQrCode { get; set; } 18 | 19 | public bool TwoFactor { get; set; } 20 | 21 | public bool BrowserRemembered { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /DosFactores/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.Hosting; 7 | 8 | namespace DosFactores 9 | { 10 | public class Program 11 | { 12 | public static void Main(string[] args) 13 | { 14 | var host = new WebHostBuilder() 15 | .UseKestrel() 16 | .UseContentRoot(Directory.GetCurrentDirectory()) 17 | .UseIISIntegration() 18 | .UseStartup() 19 | .UseApplicationInsights() 20 | .Build(); 21 | 22 | host.Run(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /DosFactores/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 | -------------------------------------------------------------------------------- /DosFactores/Models/AccountViewModels/VerifyCodeViewModel.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 DosFactores.Models.AccountViewModels 8 | { 9 | public class VerifyCodeViewModel 10 | { 11 | [Required] 12 | public string Provider { get; set; } 13 | 14 | [Required] 15 | public string Code { get; set; } 16 | 17 | public string ReturnUrl { get; set; } 18 | 19 | [Display(Name = "Remember this browser?")] 20 | public bool RememberBrowser { get; set; } 21 | 22 | [Display(Name = "Remember me?")] 23 | public bool RememberMe { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /DosFactores/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "https://localhost:44385/", 7 | "sslPort": 44385 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "DosFactores": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "http://localhost:52051" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /DosFactores/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Error"; 3 | } 4 | 5 |

Error.

6 |

An error occurred while processing your request.

7 | 8 |

Development Mode

9 |

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

12 |

13 | 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. 14 |

15 | -------------------------------------------------------------------------------- /DosFactores/Views/Account/SendCode.cshtml: -------------------------------------------------------------------------------- 1 | @model SendCodeViewModel 2 | @{ 3 | ViewData["Title"] = "Send Verification Code"; 4 | } 5 | 6 |

@ViewData["Title"].

7 | 8 |
9 | 10 |
11 |
12 | Select Two-Factor Authentication Provider: 13 | 14 | 15 |
16 |
17 |
18 | 19 | @section Scripts { 20 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); } 21 | } 22 | -------------------------------------------------------------------------------- /DosFactores/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace DosFactores.Controllers 8 | { 9 | public class HomeController : Controller 10 | { 11 | public IActionResult Index() 12 | { 13 | return View(); 14 | } 15 | 16 | public IActionResult About() 17 | { 18 | ViewData["Message"] = "Your application description page."; 19 | 20 | return View(); 21 | } 22 | 23 | public IActionResult Contact() 24 | { 25 | ViewData["Message"] = "Your contact page."; 26 | 27 | return View(); 28 | } 29 | 30 | public IActionResult Error() 31 | { 32 | return View(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /DosFactores/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 DosFactores.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 | } 23 | -------------------------------------------------------------------------------- /DosFactores/Services/MessageServices.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace DosFactores.Services 7 | { 8 | // This class is used by the application to send Email and SMS 9 | // when you turn on two-factor authentication in ASP.NET Identity. 10 | // For more details see this link https://go.microsoft.com/fwlink/?LinkID=532713 11 | public class AuthMessageSender : IEmailSender, ISmsSender 12 | { 13 | public Task SendEmailAsync(string email, string subject, string message) 14 | { 15 | // Plug in your email service here to send an email. 16 | return Task.FromResult(0); 17 | } 18 | 19 | public Task SendSmsAsync(string number, string message) 20 | { 21 | // Plug in your SMS service here to send a text message. 22 | return Task.FromResult(0); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /DosFactores/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 DosFactores.Models; 8 | 9 | namespace DosFactores.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 | -------------------------------------------------------------------------------- /DosFactores/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 DosFactores.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 | -------------------------------------------------------------------------------- /DosFactores/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 DosFactores.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 | -------------------------------------------------------------------------------- /DosFactores/Views/Manage/AddPhoneNumber.cshtml: -------------------------------------------------------------------------------- 1 | @model AddPhoneNumberViewModel 2 | @{ 3 | ViewData["Title"] = "Add Phone Number"; 4 | } 5 | 6 |

@ViewData["Title"].

7 |
8 |

Add a phone number.

9 |
10 |
11 |
12 | 13 |
14 | 15 | 16 |
17 |
18 |
19 |
20 | 21 |
22 |
23 |
24 | 25 | @section Scripts { 26 | @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } 27 | } 28 | -------------------------------------------------------------------------------- /DosFactores/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 DosFactores.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 | } 28 | -------------------------------------------------------------------------------- /DosFactores/Views/Shared/_LoginPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @using DosFactores.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 | -------------------------------------------------------------------------------- /DosFactores/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 | } -------------------------------------------------------------------------------- /DosFactores/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 | } -------------------------------------------------------------------------------- /DosFactores/Views/Manage/VerifyPhoneNumber.cshtml: -------------------------------------------------------------------------------- 1 | @model VerifyPhoneNumberViewModel 2 | @{ 3 | ViewData["Title"] = "Verify Phone Number"; 4 | } 5 | 6 |

@ViewData["Title"].

7 | 8 |
9 | 10 |

Add a phone number.

11 |
@ViewData["Status"]
12 |
13 |
14 |
15 | 16 |
17 | 18 | 19 |
20 |
21 |
22 |
23 | 24 |
25 |
26 |
27 | 28 | @section Scripts { 29 | @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } 30 | } 31 | -------------------------------------------------------------------------------- /DosFactores/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 | -------------------------------------------------------------------------------- /DosFactores/Views/Account/ForgotPassword.cshtml: -------------------------------------------------------------------------------- 1 | @model ForgotPasswordViewModel 2 | @{ 3 | ViewData["Title"] = "Forgot your password?"; 4 | } 5 | 6 |

@ViewData["Title"]

7 |

8 | For more information on how to enable reset password please see this article. 9 |

10 | 11 | @*
12 |

Enter your email.

13 |
14 |
15 |
16 | 17 |
18 | 19 | 20 |
21 |
22 |
23 |
24 | 25 |
26 |
27 |
*@ 28 | 29 | @section Scripts { 30 | @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } 31 | } 32 | -------------------------------------------------------------------------------- /DosFactores/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 | /* Set widths on the form inputs since otherwise they're 100% wide */ 14 | input, 15 | select, 16 | textarea { 17 | max-width: 280px; 18 | } 19 | 20 | /* Carousel */ 21 | .carousel-caption p { 22 | font-size: 20px; 23 | line-height: 1.4; 24 | } 25 | 26 | /* buttons and links extension to use brackets: [ click me ] */ 27 | .btn-bracketed::before { 28 | display: inline-block; 29 | content: "["; 30 | padding-right: 0.5em; 31 | } 32 | 33 | .btn-bracketed::after { 34 | display: inline-block; 35 | content: "]"; 36 | padding-left: 0.5em; 37 | } 38 | 39 | /* Make .svg files in the carousel display properly in older browsers */ 40 | .carousel-inner .item img[src$=".svg"] { 41 | width: 100%; 42 | } 43 | 44 | /* Hide/rearrange for smaller screens */ 45 | @media screen and (max-width: 767px) { 46 | /* Hide captions */ 47 | .carousel-caption { 48 | display: none; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /DosFactores/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 | -------------------------------------------------------------------------------- /DosFactores/Views/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /DosFactores/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 | } -------------------------------------------------------------------------------- /DosFactores/Views/Account/ExternalLoginConfirmation.cshtml: -------------------------------------------------------------------------------- 1 | @model ExternalLoginConfirmationViewModel 2 | @{ 3 | ViewData["Title"] = "Register"; 4 | } 5 | 6 |

@ViewData["Title"].

7 |

Associate your @ViewData["LoginProvider"] account.

8 | 9 |
10 |

Association Form

11 |
12 |
13 | 14 |

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

19 |
20 | 21 |
22 | 23 | 24 |
25 |
26 |
27 |
28 | 29 |
30 |
31 |
32 | 33 | @section Scripts { 34 | @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } 35 | } 36 | -------------------------------------------------------------------------------- /DosFactores/Views/Account/VerifyCode.cshtml: -------------------------------------------------------------------------------- 1 | @model VerifyCodeViewModel 2 | @{ 3 | ViewData["Title"] = "Verify"; 4 | } 5 | 6 |

@ViewData["Title"].

7 | 8 |
9 |
10 | 11 | 12 |

@ViewData["Status"]

13 |
14 |
15 | 16 |
17 | 18 | 19 |
20 |
21 |
22 |
23 |
24 | 25 | 26 |
27 |
28 |
29 |
30 |
31 | 32 |
33 |
34 |
35 | 36 | @section Scripts { 37 | @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } 38 | } 39 | -------------------------------------------------------------------------------- /DosFactores/Views/Manage/SetPassword.cshtml: -------------------------------------------------------------------------------- 1 | @model SetPasswordViewModel 2 | @{ 3 | ViewData["Title"] = "Set Password"; 4 | } 5 | 6 |

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

10 | 11 |
12 |

Set your password

13 |
14 |
15 |
16 | 17 |
18 | 19 | 20 |
21 |
22 |
23 | 24 |
25 | 26 | 27 |
28 |
29 |
30 |
31 | 32 |
33 |
34 |
35 | 36 | @section Scripts { 37 | @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } 38 | } 39 | -------------------------------------------------------------------------------- /DosFactores.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26228.4 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DosFactores", "DosFactores\DosFactores.csproj", "{10BA64ED-1909-4436-AEFA-8E283BAFB06F}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Google.Authenticator", "Google.Authenticator\Google.Authenticator.csproj", "{B2034AF9-BC36-4A1F-8115-3EA705BA03E4}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {10BA64ED-1909-4436-AEFA-8E283BAFB06F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {10BA64ED-1909-4436-AEFA-8E283BAFB06F}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {10BA64ED-1909-4436-AEFA-8E283BAFB06F}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {10BA64ED-1909-4436-AEFA-8E283BAFB06F}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {B2034AF9-BC36-4A1F-8115-3EA705BA03E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {B2034AF9-BC36-4A1F-8115-3EA705BA03E4}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {B2034AF9-BC36-4A1F-8115-3EA705BA03E4}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {B2034AF9-BC36-4A1F-8115-3EA705BA03E4}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /DosFactores/Views/Account/Register.cshtml: -------------------------------------------------------------------------------- 1 | @model RegisterViewModel 2 | @{ 3 | ViewData["Title"] = "Register"; 4 | } 5 | 6 |

@ViewData["Title"].

7 | 8 |
9 |

Create a new account.

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 |
35 | 36 |
37 |
38 |
39 | 40 | @section Scripts { 41 | @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } 42 | } 43 | -------------------------------------------------------------------------------- /DosFactores/Views/Manage/ChangePassword.cshtml: -------------------------------------------------------------------------------- 1 | @model ChangePasswordViewModel 2 | @{ 3 | ViewData["Title"] = "Change Password"; 4 | } 5 | 6 |

@ViewData["Title"].

7 | 8 |
9 |

Change Password Form

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 |
35 | 36 |
37 |
38 |
39 | 40 | @section Scripts { 41 | @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } 42 | } 43 | -------------------------------------------------------------------------------- /DosFactores/Views/Account/ResetPassword.cshtml: -------------------------------------------------------------------------------- 1 | @model ResetPasswordViewModel 2 | @{ 3 | ViewData["Title"] = "Reset password"; 4 | } 5 | 6 |

@ViewData["Title"].

7 | 8 |
9 |

Reset your password.

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 |
35 |
36 | 37 |
38 |
39 |
40 | 41 | @section Scripts { 42 | @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } 43 | } 44 | -------------------------------------------------------------------------------- /DosFactores/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 | -------------------------------------------------------------------------------- /DosFactores/Providers/GoogleAuthenticatorProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using DosFactores.Data; 3 | using DosFactores.Models; 4 | using Google.Authenticator; 5 | using Microsoft.AspNetCore.Identity; 6 | 7 | namespace DosFactores.Providers 8 | { 9 | public class GoogleAuthenticatorProvider : IUserTwoFactorTokenProvider 10 | { 11 | public static readonly string ProviderName = "GoogleAuthenticator"; 12 | 13 | private readonly ApplicationDbContext _dbContext; 14 | 15 | public GoogleAuthenticatorProvider(ApplicationDbContext dbContext) 16 | { 17 | _dbContext = dbContext; 18 | } 19 | 20 | public async Task GenerateAsync(string purpose, UserManager manager, ApplicationUser user) 21 | { 22 | var userName = await manager.GetUserNameAsync(user); 23 | var authenticator = new TwoFactorAuthenticator(); 24 | var code = authenticator.GenerateSetupCode(userName, userName, 100, 100); 25 | return code.ManualEntryKey; 26 | } 27 | 28 | public Task ValidateAsync(string purpose, string token, UserManager manager, ApplicationUser user) 29 | { 30 | return Task.Factory.StartNew(() => 31 | { 32 | var authenticator = new TwoFactorAuthenticator(); 33 | return authenticator.ValidateTwoFactorPIN(user.TfaKey, token); 34 | }); 35 | } 36 | 37 | public Task CanGenerateTwoFactorTokenAsync(UserManager manager, ApplicationUser user) 38 | { 39 | return Task.Factory.StartNew(() => 40 | { 41 | // Verify if the user is ellegible for 2FA 42 | return true; 43 | }); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /DosFactores/Views/Manage/ManageLogins.cshtml: -------------------------------------------------------------------------------- 1 | @model ManageLoginsViewModel 2 | @using Microsoft.AspNetCore.Http.Authentication 3 | @{ 4 | ViewData["Title"] = "Manage your external logins"; 5 | } 6 | 7 |

@ViewData["Title"].

8 | 9 |

@ViewData["StatusMessage"]

10 | @if (Model.CurrentLogins.Count > 0) 11 | { 12 |

Registered Logins

13 | 14 | 15 | @for (var index = 0; index < Model.CurrentLogins.Count; index++) 16 | { 17 | 18 | 19 | 35 | 36 | } 37 | 38 |
@Model.CurrentLogins[index].LoginProvider 20 | @if ((bool)ViewData["ShowRemoveButton"]) 21 | { 22 |
23 |
24 | 25 | 26 | 27 |
28 |
29 | } 30 | else 31 | { 32 | @:   33 | } 34 |
39 | } 40 | @if (Model.OtherLogins.Count > 0) 41 | { 42 |

Add another service to log in.

43 |
44 |
45 |
46 |

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

52 |
53 |
54 | } 55 | -------------------------------------------------------------------------------- /DosFactores/DosFactores.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp1.1 5 | 6 | 7 | 8 | $(PackageTargetFallback);portable-net45+win8+wp8+wpa81; 9 | 10 | 11 | 12 | aspnet-DosFactores-89d873e4-498b-4126-920c-68524dfd9718 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /DosFactores/Views/Manage/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model IndexViewModel 2 | @{ 3 | ViewData["Title"] = "Manage your account"; 4 | } 5 | 6 |

@ViewData["Title"].

7 |

@ViewData["StatusMessage"]

8 | 9 |
10 |

Change your account settings

11 |
12 |
13 |
Password:
14 |
15 | @if (Model.HasPassword) 16 | { 17 | Change 18 | } 19 | else 20 | { 21 | Create 22 | } 23 |
24 |
External Logins:
25 |
26 | 27 | @Model.Logins.Count Manage 28 |
29 |
Phone Number:
30 |
31 |

32 | Phone Numbers can be used as a second factor of verification in two-factor authentication. 33 | See this article 34 | for details on setting up this ASP.NET application to support two-factor authentication using SMS. 35 |

36 | @*@(Model.PhoneNumber ?? "None") 37 | @if (Model.PhoneNumber != null) 38 | { 39 |
40 | Change 41 |
42 | [] 43 |
44 | } 45 | else 46 | { 47 | Add 48 | }*@ 49 |
50 | 51 |
Two-Factor Authentication:
52 |
53 | @if (Model.TwoFactorAuthenticatorQrCode != null) 54 | { 55 | 56 |
57 | Disabled 58 |
59 | } 60 | else 61 | { 62 | if (Model.TwoFactor) 63 | { 64 |
65 | Enabled 66 |
67 | } 68 | else 69 | { 70 |
71 | Disabled 72 |
73 | } 74 | } 75 |
76 |
77 |
78 | -------------------------------------------------------------------------------- /DosFactores/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @inject Microsoft.ApplicationInsights.AspNetCore.JavaScriptSnippet JavaScriptSnippet 2 | 3 | 4 | 5 | 6 | 7 | @ViewData["Title"] - DosFactores 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | @Html.Raw(JavaScriptSnippet.FullScript) 20 | 21 | 22 | 43 |
44 | @RenderBody() 45 |
46 |
47 |

© 2017 - DosFactores

48 |
49 |
50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 69 | 70 | 71 | 72 | @RenderSection("Scripts", required: false) 73 | 74 | 75 | -------------------------------------------------------------------------------- /Instrucciones.md: -------------------------------------------------------------------------------- 1 | 1. Nuevo proyecto: ASP.NET Core Web Application (.NET Core) 2 | 2. Template: Web Application 3 | 3. Change Authentication: Individual User Accounts 4 | 4. Agregar el paquete Microsoft.AspNetCore.Rewrite 5 | 5. Agregar el código en Startup.cs:44 6 | 7 | ``` 8 | // Enforce SSL 9 | services.Configure(options => 10 | { 11 | options.Filters.Add(new RequireHttpsAttribute()); 12 | }); 13 | ``` 14 | 15 | 6. Agregar el código en Startup.cs:81 16 | 17 | ``` 18 | // Requires using Microsoft.AspNetCore.Rewrite; 19 | var options = new RewriteOptions() 20 | .AddRedirectToHttps(); 21 | ``` 22 | 23 | 7. Habilitar SSL en Propiedades -> Debug 24 | 8. Agregar el código en ApplicationUser.cs:12 25 | 26 | ``` 27 | public virtual string TfaKey { get; set; } 28 | ``` 29 | 30 | 9. Agregar el código en 00000000000000_CreateIdentitySchema.cs:60 31 | 32 | ``` 33 | TfaKey = table.Column(maxLength: 32, nullable: true), 34 | ``` 35 | 36 | 10. Agregar paquete Microsoft.AspNetCore.Session 37 | 11. Agregar el código en Startup.cs:60 38 | 39 | ``` 40 | // Add session support 41 | services.AddSession(options => 42 | { 43 | // Set a short timeout for easy testing. 44 | options.IdleTimeout = TimeSpan.FromSeconds(10); 45 | options.CookieHttpOnly = true; 46 | }); 47 | ``` 48 | 49 | 12. Agrear el código en Startup.cs:98 50 | 51 | ``` 52 | app.UseSession(); 53 | ``` 54 | 55 | 13. Agregar nuevo proyecto (Google.Authenticator): Class Library (.NET Core) 56 | 14. Añadir clases: SetupCode y TwoFactorAuthenticator (Ver https://github.com/BrandonPotter/GoogleAuthenticator) 57 | 15. Agregar referencia de Goolge.Authenticator -> DosFactores 58 | 16. Agregar clase GoogleAuthenticatorProvider (Ver: http://www.domstamand.com/two-factor-authentication-in-asp-net-identity-3-using-totp-authenticator/) 59 | 17. Agregar el código en Startup.cs:57 60 | 61 | ``` 62 | .AddTokenProvider(GoogleAuthenticatorProvider.ProviderName, typeof(GoogleAuthenticatorProvider)) 63 | ``` 64 | 65 | 18. Agregar el código en IndexViewModel.cs:17 66 | 67 | ``` 68 | public string TwoFactorAuthenticatorQrCode { get; set; } 69 | ``` 70 | 71 | 19. Agregar el código correspondiente para la generación del QR en el archivo ManageController.cs: (64, 123 y 168) 72 | 73 | ``` 74 | TwoFactorAuthenticatorQrCode = TempData["AuthenticatorQr"]?.ToString(), 75 | ``` 76 | 77 | ``` 78 | // POST: /Manage/RequestTwoFactorAuthentication 79 | [HttpPost] 80 | [ValidateAntiForgeryToken] 81 | public async Task RequestTwoFactorAuthentication() 82 | { 83 | var user = await GetCurrentUserAsync(); 84 | if (user != null) 85 | { 86 | var tfaKey = Guid.NewGuid().ToString("N"); 87 | user.TfaKey = tfaKey; 88 | await _userManager.UpdateAsync(user); 89 | var authenticator = new TwoFactorAuthenticator(); 90 | var code = authenticator.GenerateSetupCode(user.UserName, tfaKey, 300, 300); 91 | TempData["AuthenticatorQr"] = code.QrCodeSetupImageUrl; 92 | _logger.LogInformation(1, "User enabled two-factor authentication."); 93 | } 94 | return RedirectToAction(nameof(Index), "Manage"); 95 | } 96 | ``` 97 | 98 | ``` 99 | user.TfaKey = null; 100 | await _userManager.UpdateAsync(user); 101 | ``` 102 | 103 | 20. Reemplazar el código del
de la autenticación de dos factores por 104 | 105 | ``` 106 | @if (Model.TwoFactorAuthenticatorQrCode != null) 107 | { 108 | 109 |
110 | Disabled 111 |
112 | } 113 | else 114 | { 115 | if (Model.TwoFactor) 116 | { 117 |
118 | Enabled 119 |
120 | } 121 | else 122 | { 123 |
124 | Disabled 125 |
126 | } 127 | } 128 | ``` 129 | 130 | 21. Ejecutar Update-Database -------------------------------------------------------------------------------- /DosFactores/Views/Account/Login.cshtml: -------------------------------------------------------------------------------- 1 | @using System.Collections.Generic 2 | @using Microsoft.AspNetCore.Http 3 | @using Microsoft.AspNetCore.Http.Authentication 4 | @model LoginViewModel 5 | @inject SignInManager SignInManager 6 | 7 | @{ 8 | ViewData["Title"] = "Log in"; 9 | } 10 | 11 |

@ViewData["Title"].

12 |
13 |
14 |
15 |
16 |

Use a local account to log in.

17 |
18 |
19 |
20 | 21 |
22 | 23 | 24 |
25 |
26 |
27 | 28 |
29 | 30 | 31 |
32 |
33 |
34 |
35 |
36 | 40 |
41 |
42 |
43 |
44 |
45 | 46 |
47 |
48 |

49 | Register as a new user? 50 |

51 |

52 | Forgot your password? 53 |

54 |
55 |
56 |
57 |
58 |
59 |

Use another service to log in.

60 |
61 | @{ 62 | var loginProviders = SignInManager.GetExternalAuthenticationSchemes().ToList(); 63 | if (loginProviders.Count == 0) 64 | { 65 |
66 |

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

70 |
71 | } 72 | else 73 | { 74 |
75 |
76 |

77 | @foreach (var provider in loginProviders) 78 | { 79 | 80 | } 81 |

82 |
83 |
84 | } 85 | } 86 |
87 |
88 |
89 | 90 | @section Scripts { 91 | @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } 92 | } 93 | -------------------------------------------------------------------------------- /DosFactores/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.Hosting; 7 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 8 | using Microsoft.EntityFrameworkCore; 9 | using Microsoft.Extensions.Configuration; 10 | using Microsoft.Extensions.DependencyInjection; 11 | using Microsoft.Extensions.Logging; 12 | using DosFactores.Data; 13 | using DosFactores.Models; 14 | using DosFactores.Providers; 15 | using DosFactores.Services; 16 | using Microsoft.AspNetCore.Mvc; 17 | using Microsoft.AspNetCore.Rewrite; 18 | 19 | namespace DosFactores 20 | { 21 | public class Startup 22 | { 23 | public Startup(IHostingEnvironment env) 24 | { 25 | var builder = new ConfigurationBuilder() 26 | .SetBasePath(env.ContentRootPath) 27 | .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) 28 | .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true); 29 | 30 | if (env.IsDevelopment()) 31 | { 32 | // For more details on using the user secret store see https://go.microsoft.com/fwlink/?LinkID=532709 33 | builder.AddUserSecrets(); 34 | } 35 | 36 | builder.AddEnvironmentVariables(); 37 | Configuration = builder.Build(); 38 | } 39 | 40 | public IConfigurationRoot Configuration { get; } 41 | 42 | // This method gets called by the runtime. Use this method to add services to the container. 43 | public void ConfigureServices(IServiceCollection services) 44 | { 45 | // Enforce SSL 46 | services.Configure(options => 47 | { 48 | options.Filters.Add(new RequireHttpsAttribute()); 49 | }); 50 | 51 | // Add framework services. 52 | services.AddDbContext(options => 53 | options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); 54 | 55 | services.AddIdentity() 56 | .AddEntityFrameworkStores() 57 | .AddTokenProvider(GoogleAuthenticatorProvider.ProviderName, typeof(GoogleAuthenticatorProvider)) 58 | .AddDefaultTokenProviders(); 59 | 60 | services.AddMvc(); 61 | 62 | // Add session support 63 | services.AddSession(options => 64 | { 65 | // Set a short timeout for easy testing. 66 | options.IdleTimeout = TimeSpan.FromSeconds(10); 67 | options.CookieHttpOnly = true; 68 | }); 69 | 70 | // Add application services. 71 | services.AddTransient(); 72 | services.AddTransient(); 73 | } 74 | 75 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 76 | public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) 77 | { 78 | loggerFactory.AddConsole(Configuration.GetSection("Logging")); 79 | loggerFactory.AddDebug(); 80 | 81 | if (env.IsDevelopment()) 82 | { 83 | app.UseDeveloperExceptionPage(); 84 | app.UseDatabaseErrorPage(); 85 | app.UseBrowserLink(); 86 | } 87 | else 88 | { 89 | app.UseExceptionHandler("/Home/Error"); 90 | } 91 | 92 | // Requires using Microsoft.AspNetCore.Rewrite; 93 | var options = new RewriteOptions() 94 | .AddRedirectToHttps(); 95 | 96 | app.UseStaticFiles(); 97 | 98 | app.UseIdentity(); 99 | 100 | app.UseSession(); 101 | 102 | // Add external authentication middleware below. To configure them please see https://go.microsoft.com/fwlink/?LinkID=532715 103 | 104 | app.UseMvc(routes => 105 | { 106 | routes.MapRoute( 107 | name: "default", 108 | template: "{controller=Home}/{action=Index}/{id?}"); 109 | }); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /.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 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | 84 | # Visual Studio profiler 85 | *.psess 86 | *.vsp 87 | *.vspx 88 | *.sap 89 | 90 | # TFS 2012 Local Workspace 91 | $tf/ 92 | 93 | # Guidance Automation Toolkit 94 | *.gpState 95 | 96 | # ReSharper is a .NET coding add-in 97 | _ReSharper*/ 98 | *.[Rr]e[Ss]harper 99 | *.DotSettings.user 100 | 101 | # JustCode is a .NET coding add-in 102 | .JustCode 103 | 104 | # TeamCity is a build add-in 105 | _TeamCity* 106 | 107 | # DotCover is a Code Coverage Tool 108 | *.dotCover 109 | 110 | # NCrunch 111 | _NCrunch_* 112 | .*crunch*.local.xml 113 | nCrunchTemp_* 114 | 115 | # MightyMoose 116 | *.mm.* 117 | AutoTest.Net/ 118 | 119 | # Web workbench (sass) 120 | .sass-cache/ 121 | 122 | # Installshield output folder 123 | [Ee]xpress/ 124 | 125 | # DocProject is a documentation generator add-in 126 | DocProject/buildhelp/ 127 | DocProject/Help/*.HxT 128 | DocProject/Help/*.HxC 129 | DocProject/Help/*.hhc 130 | DocProject/Help/*.hhk 131 | DocProject/Help/*.hhp 132 | DocProject/Help/Html2 133 | DocProject/Help/html 134 | 135 | # Click-Once directory 136 | publish/ 137 | 138 | # Publish Web Output 139 | *.[Pp]ublish.xml 140 | *.azurePubxml 141 | # TODO: Comment the next line if you want to checkin your web deploy settings 142 | # but database connection strings (with potential passwords) will be unencrypted 143 | *.pubxml 144 | *.publishproj 145 | 146 | # NuGet Packages 147 | *.nupkg 148 | # The packages folder can be ignored because of Package Restore 149 | **/packages/* 150 | # except build/, which is used as an MSBuild target. 151 | !**/packages/build/ 152 | # Uncomment if necessary however generally it will be regenerated when needed 153 | #!**/packages/repositories.config 154 | 155 | # Microsoft Azure Build Output 156 | csx/ 157 | *.build.csdef 158 | 159 | # Microsoft Azure Emulator 160 | ecf/ 161 | rcf/ 162 | 163 | # Microsoft Azure ApplicationInsights config file 164 | ApplicationInsights.config 165 | 166 | # Windows Store app package directory 167 | AppPackages/ 168 | BundleArtifacts/ 169 | 170 | # Visual Studio cache files 171 | # files ending in .cache can be ignored 172 | *.[Cc]ache 173 | # but keep track of directories ending in .cache 174 | !*.[Cc]ache/ 175 | 176 | # Others 177 | ClientBin/ 178 | ~$* 179 | *~ 180 | *.dbmdl 181 | *.dbproj.schemaview 182 | *.pfx 183 | *.publishsettings 184 | node_modules/ 185 | orleans.codegen.cs 186 | 187 | # RIA/Silverlight projects 188 | Generated_Code/ 189 | 190 | # Backup & report files from converting an old project file 191 | # to a newer Visual Studio version. Backup files are not needed, 192 | # because we have git ;-) 193 | _UpgradeReport_Files/ 194 | Backup*/ 195 | UpgradeLog*.XML 196 | UpgradeLog*.htm 197 | 198 | # SQL Server files 199 | *.mdf 200 | *.ldf 201 | 202 | # Business Intelligence projects 203 | *.rdl.data 204 | *.bim.layout 205 | *.bim_*.settings 206 | 207 | # Microsoft Fakes 208 | FakesAssemblies/ 209 | 210 | # GhostDoc plugin setting file 211 | *.GhostDoc.xml 212 | 213 | # Node.js Tools for Visual Studio 214 | .ntvs_analysis.dat 215 | 216 | # Visual Studio 6 build log 217 | *.plg 218 | 219 | # Visual Studio 6 workspace options file 220 | *.opt 221 | 222 | # Visual Studio LightSwitch build output 223 | **/*.HTMLClient/GeneratedArtifacts 224 | **/*.DesktopClient/GeneratedArtifacts 225 | **/*.DesktopClient/ModelManifest.xml 226 | **/*.Server/GeneratedArtifacts 227 | **/*.Server/ModelManifest.xml 228 | _Pvt_Extensions 229 | 230 | # Paket dependency manager 231 | .paket/paket.exe 232 | 233 | # FAKE - F# Make 234 | .fake/ -------------------------------------------------------------------------------- /DosFactores/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); -------------------------------------------------------------------------------- /DosFactores/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 | -------------------------------------------------------------------------------- /DosFactores/wwwroot/images/banner2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /DosFactores/Data/Migrations/ApplicationDbContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.EntityFrameworkCore; 6 | using Microsoft.EntityFrameworkCore.Infrastructure; 7 | using Microsoft.EntityFrameworkCore.Metadata; 8 | using Microsoft.EntityFrameworkCore.Migrations; 9 | 10 | namespace DosFactores.Data.Migrations 11 | { 12 | [DbContext(typeof(ApplicationDbContext))] 13 | partial class ApplicationDbContextModelSnapshot : ModelSnapshot 14 | { 15 | protected override void BuildModel(ModelBuilder modelBuilder) 16 | { 17 | modelBuilder 18 | .HasAnnotation("ProductVersion", "1.0.0-rc3") 19 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 20 | 21 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole", b => 22 | { 23 | b.Property("Id"); 24 | 25 | b.Property("ConcurrencyStamp") 26 | .IsConcurrencyToken(); 27 | 28 | b.Property("Name") 29 | .HasAnnotation("MaxLength", 256); 30 | 31 | b.Property("NormalizedName") 32 | .HasAnnotation("MaxLength", 256); 33 | 34 | b.HasKey("Id"); 35 | 36 | b.HasIndex("NormalizedName") 37 | .HasName("RoleNameIndex"); 38 | 39 | b.ToTable("AspNetRoles"); 40 | }); 41 | 42 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRoleClaim", b => 43 | { 44 | b.Property("Id") 45 | .ValueGeneratedOnAdd(); 46 | 47 | b.Property("ClaimType"); 48 | 49 | b.Property("ClaimValue"); 50 | 51 | b.Property("RoleId") 52 | .IsRequired(); 53 | 54 | b.HasKey("Id"); 55 | 56 | b.HasIndex("RoleId"); 57 | 58 | b.ToTable("AspNetRoleClaims"); 59 | }); 60 | 61 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserClaim", b => 62 | { 63 | b.Property("Id") 64 | .ValueGeneratedOnAdd(); 65 | 66 | b.Property("ClaimType"); 67 | 68 | b.Property("ClaimValue"); 69 | 70 | b.Property("UserId") 71 | .IsRequired(); 72 | 73 | b.HasKey("Id"); 74 | 75 | b.HasIndex("UserId"); 76 | 77 | b.ToTable("AspNetUserClaims"); 78 | }); 79 | 80 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserLogin", b => 81 | { 82 | b.Property("LoginProvider"); 83 | 84 | b.Property("ProviderKey"); 85 | 86 | b.Property("ProviderDisplayName"); 87 | 88 | b.Property("UserId") 89 | .IsRequired(); 90 | 91 | b.HasKey("LoginProvider", "ProviderKey"); 92 | 93 | b.HasIndex("UserId"); 94 | 95 | b.ToTable("AspNetUserLogins"); 96 | }); 97 | 98 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserRole", b => 99 | { 100 | b.Property("UserId"); 101 | 102 | b.Property("RoleId"); 103 | 104 | b.HasKey("UserId", "RoleId"); 105 | 106 | b.HasIndex("RoleId"); 107 | 108 | b.HasIndex("UserId"); 109 | 110 | b.ToTable("AspNetUserRoles"); 111 | }); 112 | 113 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserToken", b => 114 | { 115 | b.Property("UserId"); 116 | 117 | b.Property("LoginProvider"); 118 | 119 | b.Property("Name"); 120 | 121 | b.Property("Value"); 122 | 123 | b.HasKey("UserId", "LoginProvider", "Name"); 124 | 125 | b.ToTable("AspNetUserTokens"); 126 | }); 127 | 128 | modelBuilder.Entity("DosFactores.Models.ApplicationUser", b => 129 | { 130 | b.Property("Id"); 131 | 132 | b.Property("AccessFailedCount"); 133 | 134 | b.Property("ConcurrencyStamp") 135 | .IsConcurrencyToken(); 136 | 137 | b.Property("Email") 138 | .HasAnnotation("MaxLength", 256); 139 | 140 | b.Property("EmailConfirmed"); 141 | 142 | b.Property("LockoutEnabled"); 143 | 144 | b.Property("LockoutEnd"); 145 | 146 | b.Property("NormalizedEmail") 147 | .HasAnnotation("MaxLength", 256); 148 | 149 | b.Property("NormalizedUserName") 150 | .HasAnnotation("MaxLength", 256); 151 | 152 | b.Property("PasswordHash"); 153 | 154 | b.Property("PhoneNumber"); 155 | 156 | b.Property("PhoneNumberConfirmed"); 157 | 158 | b.Property("SecurityStamp"); 159 | 160 | b.Property("TwoFactorEnabled"); 161 | 162 | b.Property("UserName") 163 | .HasAnnotation("MaxLength", 256); 164 | 165 | b.HasKey("Id"); 166 | 167 | b.HasIndex("NormalizedEmail") 168 | .HasName("EmailIndex"); 169 | 170 | b.HasIndex("NormalizedUserName") 171 | .IsUnique() 172 | .HasName("UserNameIndex"); 173 | 174 | b.ToTable("AspNetUsers"); 175 | }); 176 | 177 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRoleClaim", b => 178 | { 179 | b.HasOne("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole") 180 | .WithMany("Claims") 181 | .HasForeignKey("RoleId") 182 | .OnDelete(DeleteBehavior.Cascade); 183 | }); 184 | 185 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserClaim", b => 186 | { 187 | b.HasOne("DosFactores.Models.ApplicationUser") 188 | .WithMany("Claims") 189 | .HasForeignKey("UserId") 190 | .OnDelete(DeleteBehavior.Cascade); 191 | }); 192 | 193 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserLogin", b => 194 | { 195 | b.HasOne("DosFactores.Models.ApplicationUser") 196 | .WithMany("Logins") 197 | .HasForeignKey("UserId") 198 | .OnDelete(DeleteBehavior.Cascade); 199 | }); 200 | 201 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserRole", b => 202 | { 203 | b.HasOne("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole") 204 | .WithMany("Users") 205 | .HasForeignKey("RoleId") 206 | .OnDelete(DeleteBehavior.Cascade); 207 | 208 | b.HasOne("DosFactores.Models.ApplicationUser") 209 | .WithMany("Roles") 210 | .HasForeignKey("UserId") 211 | .OnDelete(DeleteBehavior.Cascade); 212 | }); 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /DosFactores/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.EntityFrameworkCore; 6 | using Microsoft.EntityFrameworkCore.Infrastructure; 7 | using Microsoft.EntityFrameworkCore.Metadata; 8 | using Microsoft.EntityFrameworkCore.Migrations; 9 | 10 | namespace DosFactores.Data.Migrations 11 | { 12 | [DbContext(typeof(ApplicationDbContext))] 13 | [Migration("00000000000000_CreateIdentitySchema")] 14 | partial class CreateIdentitySchema 15 | { 16 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 17 | { 18 | modelBuilder 19 | .HasAnnotation("ProductVersion", "1.0.0-rc3") 20 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 21 | 22 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole", b => 23 | { 24 | b.Property("Id"); 25 | 26 | b.Property("ConcurrencyStamp") 27 | .IsConcurrencyToken(); 28 | 29 | b.Property("Name") 30 | .HasAnnotation("MaxLength", 256); 31 | 32 | b.Property("NormalizedName") 33 | .HasAnnotation("MaxLength", 256); 34 | 35 | b.HasKey("Id"); 36 | 37 | b.HasIndex("NormalizedName") 38 | .HasName("RoleNameIndex"); 39 | 40 | b.ToTable("AspNetRoles"); 41 | }); 42 | 43 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRoleClaim", b => 44 | { 45 | b.Property("Id") 46 | .ValueGeneratedOnAdd(); 47 | 48 | b.Property("ClaimType"); 49 | 50 | b.Property("ClaimValue"); 51 | 52 | b.Property("RoleId") 53 | .IsRequired(); 54 | 55 | b.HasKey("Id"); 56 | 57 | b.HasIndex("RoleId"); 58 | 59 | b.ToTable("AspNetRoleClaims"); 60 | }); 61 | 62 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserClaim", b => 63 | { 64 | b.Property("Id") 65 | .ValueGeneratedOnAdd(); 66 | 67 | b.Property("ClaimType"); 68 | 69 | b.Property("ClaimValue"); 70 | 71 | b.Property("UserId") 72 | .IsRequired(); 73 | 74 | b.HasKey("Id"); 75 | 76 | b.HasIndex("UserId"); 77 | 78 | b.ToTable("AspNetUserClaims"); 79 | }); 80 | 81 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserLogin", b => 82 | { 83 | b.Property("LoginProvider"); 84 | 85 | b.Property("ProviderKey"); 86 | 87 | b.Property("ProviderDisplayName"); 88 | 89 | b.Property("UserId") 90 | .IsRequired(); 91 | 92 | b.HasKey("LoginProvider", "ProviderKey"); 93 | 94 | b.HasIndex("UserId"); 95 | 96 | b.ToTable("AspNetUserLogins"); 97 | }); 98 | 99 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserRole", b => 100 | { 101 | b.Property("UserId"); 102 | 103 | b.Property("RoleId"); 104 | 105 | b.HasKey("UserId", "RoleId"); 106 | 107 | b.HasIndex("RoleId"); 108 | 109 | b.HasIndex("UserId"); 110 | 111 | b.ToTable("AspNetUserRoles"); 112 | }); 113 | 114 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserToken", b => 115 | { 116 | b.Property("UserId"); 117 | 118 | b.Property("LoginProvider"); 119 | 120 | b.Property("Name"); 121 | 122 | b.Property("Value"); 123 | 124 | b.HasKey("UserId", "LoginProvider", "Name"); 125 | 126 | b.ToTable("AspNetUserTokens"); 127 | }); 128 | 129 | modelBuilder.Entity("DosFactores.Models.ApplicationUser", b => 130 | { 131 | b.Property("Id"); 132 | 133 | b.Property("AccessFailedCount"); 134 | 135 | b.Property("ConcurrencyStamp") 136 | .IsConcurrencyToken(); 137 | 138 | b.Property("Email") 139 | .HasAnnotation("MaxLength", 256); 140 | 141 | b.Property("EmailConfirmed"); 142 | 143 | b.Property("LockoutEnabled"); 144 | 145 | b.Property("LockoutEnd"); 146 | 147 | b.Property("NormalizedEmail") 148 | .HasAnnotation("MaxLength", 256); 149 | 150 | b.Property("NormalizedUserName") 151 | .HasAnnotation("MaxLength", 256); 152 | 153 | b.Property("PasswordHash"); 154 | 155 | b.Property("PhoneNumber"); 156 | 157 | b.Property("PhoneNumberConfirmed"); 158 | 159 | b.Property("SecurityStamp"); 160 | 161 | b.Property("TwoFactorEnabled"); 162 | 163 | b.Property("UserName") 164 | .HasAnnotation("MaxLength", 256); 165 | 166 | b.HasKey("Id"); 167 | 168 | b.HasIndex("NormalizedEmail") 169 | .HasName("EmailIndex"); 170 | 171 | b.HasIndex("NormalizedUserName") 172 | .IsUnique() 173 | .HasName("UserNameIndex"); 174 | 175 | b.ToTable("AspNetUsers"); 176 | }); 177 | 178 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRoleClaim", b => 179 | { 180 | b.HasOne("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole") 181 | .WithMany("Claims") 182 | .HasForeignKey("RoleId") 183 | .OnDelete(DeleteBehavior.Cascade); 184 | }); 185 | 186 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserClaim", b => 187 | { 188 | b.HasOne("DosFactores.Models.ApplicationUser") 189 | .WithMany("Claims") 190 | .HasForeignKey("UserId") 191 | .OnDelete(DeleteBehavior.Cascade); 192 | }); 193 | 194 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserLogin", b => 195 | { 196 | b.HasOne("DosFactores.Models.ApplicationUser") 197 | .WithMany("Logins") 198 | .HasForeignKey("UserId") 199 | .OnDelete(DeleteBehavior.Cascade); 200 | }); 201 | 202 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserRole", b => 203 | { 204 | b.HasOne("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole") 205 | .WithMany("Users") 206 | .HasForeignKey("RoleId") 207 | .OnDelete(DeleteBehavior.Cascade); 208 | 209 | b.HasOne("DosFactores.Models.ApplicationUser") 210 | .WithMany("Roles") 211 | .HasForeignKey("UserId") 212 | .OnDelete(DeleteBehavior.Cascade); 213 | }); 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /DosFactores/wwwroot/images/banner1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /DosFactores/Data/Migrations/00000000000000_CreateIdentitySchema.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Migrations; 7 | 8 | namespace DosFactores.Data.Migrations 9 | { 10 | public partial class CreateIdentitySchema : Migration 11 | { 12 | protected override void Up(MigrationBuilder migrationBuilder) 13 | { 14 | migrationBuilder.CreateTable( 15 | name: "AspNetRoles", 16 | columns: table => new 17 | { 18 | Id = table.Column(nullable: false), 19 | ConcurrencyStamp = table.Column(nullable: true), 20 | Name = table.Column(maxLength: 256, nullable: true), 21 | NormalizedName = table.Column(maxLength: 256, nullable: true) 22 | }, 23 | constraints: table => 24 | { 25 | table.PrimaryKey("PK_AspNetRoles", x => x.Id); 26 | }); 27 | 28 | migrationBuilder.CreateTable( 29 | name: "AspNetUserTokens", 30 | columns: table => new 31 | { 32 | UserId = table.Column(nullable: false), 33 | LoginProvider = table.Column(nullable: false), 34 | Name = table.Column(nullable: false), 35 | Value = table.Column(nullable: true) 36 | }, 37 | constraints: table => 38 | { 39 | table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); 40 | }); 41 | 42 | migrationBuilder.CreateTable( 43 | name: "AspNetUsers", 44 | columns: table => new 45 | { 46 | Id = table.Column(nullable: false), 47 | AccessFailedCount = table.Column(nullable: false), 48 | ConcurrencyStamp = table.Column(nullable: true), 49 | Email = table.Column(maxLength: 256, nullable: true), 50 | EmailConfirmed = table.Column(nullable: false), 51 | LockoutEnabled = table.Column(nullable: false), 52 | LockoutEnd = table.Column(nullable: true), 53 | NormalizedEmail = table.Column(maxLength: 256, nullable: true), 54 | NormalizedUserName = table.Column(maxLength: 256, nullable: true), 55 | PasswordHash = table.Column(nullable: true), 56 | PhoneNumber = table.Column(nullable: true), 57 | PhoneNumberConfirmed = table.Column(nullable: false), 58 | SecurityStamp = table.Column(nullable: true), 59 | TwoFactorEnabled = table.Column(nullable: false), 60 | TfaKey = table.Column(maxLength: 32, nullable: true), 61 | UserName = table.Column(maxLength: 256, nullable: true) 62 | }, 63 | constraints: table => 64 | { 65 | table.PrimaryKey("PK_AspNetUsers", x => x.Id); 66 | }); 67 | 68 | migrationBuilder.CreateTable( 69 | name: "AspNetRoleClaims", 70 | columns: table => new 71 | { 72 | Id = table.Column(nullable: false) 73 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 74 | ClaimType = table.Column(nullable: true), 75 | ClaimValue = table.Column(nullable: true), 76 | RoleId = table.Column(nullable: false) 77 | }, 78 | constraints: table => 79 | { 80 | table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); 81 | table.ForeignKey( 82 | name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", 83 | column: x => x.RoleId, 84 | principalTable: "AspNetRoles", 85 | principalColumn: "Id", 86 | onDelete: ReferentialAction.Cascade); 87 | }); 88 | 89 | migrationBuilder.CreateTable( 90 | name: "AspNetUserClaims", 91 | columns: table => new 92 | { 93 | Id = table.Column(nullable: false) 94 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 95 | ClaimType = table.Column(nullable: true), 96 | ClaimValue = table.Column(nullable: true), 97 | UserId = table.Column(nullable: false) 98 | }, 99 | constraints: table => 100 | { 101 | table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); 102 | table.ForeignKey( 103 | name: "FK_AspNetUserClaims_AspNetUsers_UserId", 104 | column: x => x.UserId, 105 | principalTable: "AspNetUsers", 106 | principalColumn: "Id", 107 | onDelete: ReferentialAction.Cascade); 108 | }); 109 | 110 | migrationBuilder.CreateTable( 111 | name: "AspNetUserLogins", 112 | columns: table => new 113 | { 114 | LoginProvider = table.Column(nullable: false), 115 | ProviderKey = table.Column(nullable: false), 116 | ProviderDisplayName = table.Column(nullable: true), 117 | UserId = table.Column(nullable: false) 118 | }, 119 | constraints: table => 120 | { 121 | table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); 122 | table.ForeignKey( 123 | name: "FK_AspNetUserLogins_AspNetUsers_UserId", 124 | column: x => x.UserId, 125 | principalTable: "AspNetUsers", 126 | principalColumn: "Id", 127 | onDelete: ReferentialAction.Cascade); 128 | }); 129 | 130 | migrationBuilder.CreateTable( 131 | name: "AspNetUserRoles", 132 | columns: table => new 133 | { 134 | UserId = table.Column(nullable: false), 135 | RoleId = table.Column(nullable: false) 136 | }, 137 | constraints: table => 138 | { 139 | table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); 140 | table.ForeignKey( 141 | name: "FK_AspNetUserRoles_AspNetRoles_RoleId", 142 | column: x => x.RoleId, 143 | principalTable: "AspNetRoles", 144 | principalColumn: "Id", 145 | onDelete: ReferentialAction.Cascade); 146 | table.ForeignKey( 147 | name: "FK_AspNetUserRoles_AspNetUsers_UserId", 148 | column: x => x.UserId, 149 | principalTable: "AspNetUsers", 150 | principalColumn: "Id", 151 | onDelete: ReferentialAction.Cascade); 152 | }); 153 | 154 | migrationBuilder.CreateIndex( 155 | name: "RoleNameIndex", 156 | table: "AspNetRoles", 157 | column: "NormalizedName"); 158 | 159 | migrationBuilder.CreateIndex( 160 | name: "IX_AspNetRoleClaims_RoleId", 161 | table: "AspNetRoleClaims", 162 | column: "RoleId"); 163 | 164 | migrationBuilder.CreateIndex( 165 | name: "IX_AspNetUserClaims_UserId", 166 | table: "AspNetUserClaims", 167 | column: "UserId"); 168 | 169 | migrationBuilder.CreateIndex( 170 | name: "IX_AspNetUserLogins_UserId", 171 | table: "AspNetUserLogins", 172 | column: "UserId"); 173 | 174 | migrationBuilder.CreateIndex( 175 | name: "IX_AspNetUserRoles_RoleId", 176 | table: "AspNetUserRoles", 177 | column: "RoleId"); 178 | 179 | migrationBuilder.CreateIndex( 180 | name: "IX_AspNetUserRoles_UserId", 181 | table: "AspNetUserRoles", 182 | column: "UserId"); 183 | 184 | migrationBuilder.CreateIndex( 185 | name: "EmailIndex", 186 | table: "AspNetUsers", 187 | column: "NormalizedEmail"); 188 | 189 | migrationBuilder.CreateIndex( 190 | name: "UserNameIndex", 191 | table: "AspNetUsers", 192 | column: "NormalizedUserName", 193 | unique: true); 194 | } 195 | 196 | protected override void Down(MigrationBuilder migrationBuilder) 197 | { 198 | migrationBuilder.DropTable( 199 | name: "AspNetRoleClaims"); 200 | 201 | migrationBuilder.DropTable( 202 | name: "AspNetUserClaims"); 203 | 204 | migrationBuilder.DropTable( 205 | name: "AspNetUserLogins"); 206 | 207 | migrationBuilder.DropTable( 208 | name: "AspNetUserRoles"); 209 | 210 | migrationBuilder.DropTable( 211 | name: "AspNetUserTokens"); 212 | 213 | migrationBuilder.DropTable( 214 | name: "AspNetRoles"); 215 | 216 | migrationBuilder.DropTable( 217 | name: "AspNetUsers"); 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /DosFactores/wwwroot/images/banner3.svg: -------------------------------------------------------------------------------- 1 | banner3b -------------------------------------------------------------------------------- /DosFactores/wwwroot/images/banner4.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Google.Authenticator/TwoFactorAuthentication.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Security.Cryptography; 5 | using System.Text; 6 | 7 | namespace Google.Authenticator 8 | { 9 | public class TwoFactorAuthenticator 10 | { 11 | public static DateTime _epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); 12 | public TimeSpan DefaultClockDriftTolerance { get; set; } 13 | public bool UseManagedSha1Algorithm { get; set; } 14 | public bool TryUnmanagedAlgorithmOnFailure { get; set; } 15 | 16 | public TwoFactorAuthenticator() : this(true, true) { } 17 | 18 | public TwoFactorAuthenticator(bool useManagedSha1, bool useUnmanagedOnFail) 19 | { 20 | DefaultClockDriftTolerance = TimeSpan.FromMinutes(5); 21 | UseManagedSha1Algorithm = useManagedSha1; 22 | TryUnmanagedAlgorithmOnFailure = useUnmanagedOnFail; 23 | } 24 | 25 | /// 26 | /// Generate a setup code for a Google Authenticator user to scan. 27 | /// 28 | /// Account Title (no spaces) 29 | /// Account Secret Key 30 | /// QR Code Width 31 | /// QR Code Height 32 | /// SetupCode object 33 | public SetupCode GenerateSetupCode(string accountTitleNoSpaces, string accountSecretKey, int qrCodeWidth, int qrCodeHeight) 34 | { 35 | return GenerateSetupCode(null, accountTitleNoSpaces, accountSecretKey, qrCodeWidth, qrCodeHeight); 36 | } 37 | 38 | /// 39 | /// Generate a setup code for a Google Authenticator user to scan (with issuer ID). 40 | /// 41 | /// Issuer ID (the name of the system, i.e. 'MyApp') 42 | /// Account Title (no spaces) 43 | /// Account Secret Key 44 | /// QR Code Width 45 | /// QR Code Height 46 | /// SetupCode object 47 | public SetupCode GenerateSetupCode(string issuer, string accountTitleNoSpaces, string accountSecretKey, int qrCodeWidth, int qrCodeHeight) 48 | { 49 | return GenerateSetupCode(issuer, accountTitleNoSpaces, accountSecretKey, qrCodeWidth, qrCodeHeight, false); 50 | } 51 | 52 | /// 53 | /// Generate a setup code for a Google Authenticator user to scan (with issuer ID). 54 | /// 55 | /// Issuer ID (the name of the system, i.e. 'MyApp') 56 | /// Account Title (no spaces) 57 | /// Account Secret Key 58 | /// QR Code Width 59 | /// QR Code Height 60 | /// Use HTTPS instead of HTTP 61 | /// SetupCode object 62 | public SetupCode GenerateSetupCode(string issuer, string accountTitleNoSpaces, string accountSecretKey, int qrCodeWidth, int qrCodeHeight, bool useHttps) 63 | { 64 | if (accountTitleNoSpaces == null) { throw new NullReferenceException("Account Title is null"); } 65 | 66 | accountTitleNoSpaces = accountTitleNoSpaces.Replace(" ", ""); 67 | 68 | SetupCode sC = new SetupCode(); 69 | sC.Account = accountTitleNoSpaces; 70 | sC.AccountSecretKey = accountSecretKey; 71 | 72 | string encodedSecretKey = EncodeAccountSecretKey(accountSecretKey); 73 | sC.ManualEntryKey = encodedSecretKey; 74 | 75 | string provisionUrl = null; 76 | 77 | if (string.IsNullOrEmpty(issuer)) 78 | { 79 | provisionUrl = UrlEncode(String.Format("otpauth://totp/{0}?secret={1}", accountTitleNoSpaces, encodedSecretKey)); 80 | } 81 | else 82 | { 83 | provisionUrl = UrlEncode(String.Format("otpauth://totp/{0}?secret={1}&issuer={2}", accountTitleNoSpaces, encodedSecretKey, UrlEncode(issuer))); 84 | } 85 | 86 | string protocol = useHttps ? "https" : "http"; 87 | string url = String.Format("{0}://chart.googleapis.com/chart?cht=qr&chs={1}x{2}&chl={3}", protocol, qrCodeWidth, qrCodeHeight, provisionUrl); 88 | 89 | sC.QrCodeSetupImageUrl = url; 90 | 91 | return sC; 92 | } 93 | 94 | private string UrlEncode(string value) 95 | { 96 | StringBuilder result = new StringBuilder(); 97 | string validChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~"; 98 | 99 | foreach (char symbol in value) 100 | { 101 | if (validChars.IndexOf(symbol) != -1) 102 | { 103 | result.Append(symbol); 104 | } 105 | else 106 | { 107 | result.Append('%' + String.Format("{0:X2}", (int)symbol)); 108 | } 109 | } 110 | 111 | return result.ToString().Replace(" ", "%20"); 112 | } 113 | 114 | private string EncodeAccountSecretKey(string accountSecretKey) 115 | { 116 | //if (accountSecretKey.Length < 10) 117 | //{ 118 | // accountSecretKey = accountSecretKey.PadRight(10, '0'); 119 | //} 120 | 121 | //if (accountSecretKey.Length > 12) 122 | //{ 123 | // accountSecretKey = accountSecretKey.Substring(0, 12); 124 | //} 125 | 126 | return Base32Encode(Encoding.UTF8.GetBytes(accountSecretKey)); 127 | } 128 | 129 | private string Base32Encode(byte[] data) 130 | { 131 | int inByteSize = 8; 132 | int outByteSize = 5; 133 | char[] alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".ToCharArray(); 134 | 135 | int i = 0, index = 0, digit = 0; 136 | int current_byte, next_byte; 137 | StringBuilder result = new StringBuilder((data.Length + 7) * inByteSize / outByteSize); 138 | 139 | while (i < data.Length) 140 | { 141 | current_byte = (data[i] >= 0) ? data[i] : (data[i] + 256); // Unsign 142 | 143 | /* Is the current digit going to span a byte boundary? */ 144 | if (index > (inByteSize - outByteSize)) 145 | { 146 | if ((i + 1) < data.Length) 147 | next_byte = (data[i + 1] >= 0) ? data[i + 1] : (data[i + 1] + 256); 148 | else 149 | next_byte = 0; 150 | 151 | digit = current_byte & (0xFF >> index); 152 | index = (index + outByteSize) % inByteSize; 153 | digit <<= index; 154 | digit |= next_byte >> (inByteSize - index); 155 | i++; 156 | } 157 | else 158 | { 159 | digit = (current_byte >> (inByteSize - (index + outByteSize))) & 0x1F; 160 | index = (index + outByteSize) % inByteSize; 161 | if (index == 0) 162 | i++; 163 | } 164 | result.Append(alphabet[digit]); 165 | } 166 | 167 | return result.ToString(); 168 | } 169 | 170 | public string GeneratePINAtInterval(string accountSecretKey, long counter, int digits = 6) 171 | { 172 | return GenerateHashedCode(accountSecretKey, counter, digits); 173 | } 174 | 175 | internal string GenerateHashedCode(string secret, long iterationNumber, int digits = 6) 176 | { 177 | byte[] key = Encoding.UTF8.GetBytes(secret); 178 | return GenerateHashedCode(key, iterationNumber, digits); 179 | } 180 | 181 | internal string GenerateHashedCode(byte[] key, long iterationNumber, int digits = 6) 182 | { 183 | byte[] counter = BitConverter.GetBytes(iterationNumber); 184 | 185 | if (BitConverter.IsLittleEndian) 186 | { 187 | Array.Reverse(counter); 188 | } 189 | 190 | HMACSHA1 hmac = getHMACSha1Algorithm(key); 191 | 192 | byte[] hash = hmac.ComputeHash(counter); 193 | 194 | int offset = hash[hash.Length - 1] & 0xf; 195 | 196 | // Convert the 4 bytes into an integer, ignoring the sign. 197 | int binary = 198 | ((hash[offset] & 0x7f) << 24) 199 | | (hash[offset + 1] << 16) 200 | | (hash[offset + 2] << 8) 201 | | (hash[offset + 3]); 202 | 203 | int password = binary % (int)Math.Pow(10, digits); 204 | return password.ToString(new string('0', digits)); 205 | } 206 | 207 | private long GetCurrentCounter() 208 | { 209 | return GetCurrentCounter(DateTime.UtcNow, _epoch, 30); 210 | } 211 | 212 | private long GetCurrentCounter(DateTime now, DateTime epoch, int timeStep) 213 | { 214 | return (long)(now - epoch).TotalSeconds / timeStep; 215 | } 216 | 217 | /// 218 | /// Creates a HMACSHA1 algorithm to use to hash the counter bytes. By default, this will attempt to use 219 | /// the managed SHA1 class (SHA1Manager) and on exception (FIPS-compliant machine policy, etc) will attempt 220 | /// to use the unmanaged SHA1 class (SHA1CryptoServiceProvider). 221 | /// 222 | /// User's secret key, in bytes 223 | /// HMACSHA1 cryptographic algorithm 224 | private HMACSHA1 getHMACSha1Algorithm(byte[] key) 225 | { 226 | HMACSHA1 hmac; 227 | 228 | try 229 | { 230 | hmac = new HMACSHA1(key);//, UseManagedSha1Algorithm); 231 | } 232 | catch (InvalidOperationException ioe) 233 | { 234 | if (UseManagedSha1Algorithm && TryUnmanagedAlgorithmOnFailure) 235 | { 236 | try 237 | { 238 | hmac = new HMACSHA1(key); 239 | } 240 | catch (InvalidOperationException ioe2) 241 | { 242 | throw ioe2; 243 | } 244 | } 245 | else 246 | { 247 | throw ioe; 248 | } 249 | } 250 | 251 | return hmac; 252 | } 253 | 254 | public bool ValidateTwoFactorPIN(string accountSecretKey, string twoFactorCodeFromClient) 255 | { 256 | return ValidateTwoFactorPIN(accountSecretKey, twoFactorCodeFromClient, DefaultClockDriftTolerance); 257 | } 258 | 259 | public bool ValidateTwoFactorPIN(string accountSecretKey, string twoFactorCodeFromClient, TimeSpan timeTolerance) 260 | { 261 | var codes = GetCurrentPINs(accountSecretKey, timeTolerance); 262 | return codes.Any(c => c == twoFactorCodeFromClient); 263 | } 264 | 265 | public string GetCurrentPIN(string accountSecretKey) 266 | { 267 | return GeneratePINAtInterval(accountSecretKey, GetCurrentCounter()); 268 | } 269 | 270 | public string GetCurrentPIN(string accountSecretKey, DateTime now) 271 | { 272 | return GeneratePINAtInterval(accountSecretKey, GetCurrentCounter(now, _epoch, 30)); 273 | } 274 | 275 | public string[] GetCurrentPINs(string accountSecretKey) 276 | { 277 | return GetCurrentPINs(accountSecretKey, DefaultClockDriftTolerance); 278 | } 279 | 280 | public string[] GetCurrentPINs(string accountSecretKey, TimeSpan timeTolerance) 281 | { 282 | List codes = new List(); 283 | long iterationCounter = GetCurrentCounter(); 284 | int iterationOffset = 0; 285 | 286 | if (timeTolerance.TotalSeconds > 30) 287 | { 288 | iterationOffset = Convert.ToInt32(timeTolerance.TotalSeconds / 30.00); 289 | } 290 | 291 | long iterationStart = iterationCounter - iterationOffset; 292 | long iterationEnd = iterationCounter + iterationOffset; 293 | 294 | for (long counter = iterationStart; counter <= iterationEnd; counter++) 295 | { 296 | codes.Add(GeneratePINAtInterval(accountSecretKey, counter)); 297 | } 298 | 299 | return codes.ToArray(); 300 | } 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /DosFactores/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 _userManager; 21 | private readonly SignInManager _signInManager; 22 | private readonly string _externalCookieScheme; 23 | private readonly IEmailSender _emailSender; 24 | private readonly ISmsSender _smsSender; 25 | private readonly ILogger _logger; 26 | 27 | public ManageController( 28 | UserManager userManager, 29 | SignInManager signInManager, 30 | IOptions identityCookieOptions, 31 | IEmailSender emailSender, 32 | ISmsSender smsSender, 33 | ILoggerFactory loggerFactory) 34 | { 35 | _userManager = userManager; 36 | _signInManager = signInManager; 37 | _externalCookieScheme = identityCookieOptions.Value.ExternalCookieAuthenticationScheme; 38 | _emailSender = emailSender; 39 | _smsSender = smsSender; 40 | _logger = loggerFactory.CreateLogger(); 41 | } 42 | 43 | // 44 | // GET: /Manage/Index 45 | [HttpGet] 46 | public async Task Index(ManageMessageId? message = null) 47 | { 48 | ViewData["StatusMessage"] = 49 | message == ManageMessageId.ChangePasswordSuccess ? "Your password has been changed." 50 | : message == ManageMessageId.SetPasswordSuccess ? "Your password has been set." 51 | : message == ManageMessageId.SetTwoFactorSuccess ? "Your two-factor authentication provider has been set." 52 | : message == ManageMessageId.Error ? "An error has occurred." 53 | : message == ManageMessageId.AddPhoneSuccess ? "Your phone number was added." 54 | : message == ManageMessageId.RemovePhoneSuccess ? "Your phone number was removed." 55 | : ""; 56 | 57 | var user = await GetCurrentUserAsync(); 58 | if (user == null) 59 | { 60 | return View("Error"); 61 | } 62 | var model = new IndexViewModel 63 | { 64 | TwoFactorAuthenticatorQrCode = TempData["AuthenticatorQr"]?.ToString(), 65 | HasPassword = await _userManager.HasPasswordAsync(user), 66 | PhoneNumber = await _userManager.GetPhoneNumberAsync(user), 67 | TwoFactor = await _userManager.GetTwoFactorEnabledAsync(user), 68 | Logins = await _userManager.GetLoginsAsync(user), 69 | BrowserRemembered = await _signInManager.IsTwoFactorClientRememberedAsync(user) 70 | }; 71 | return View(model); 72 | } 73 | 74 | // 75 | // POST: /Manage/RemoveLogin 76 | [HttpPost] 77 | [ValidateAntiForgeryToken] 78 | public async Task RemoveLogin(RemoveLoginViewModel account) 79 | { 80 | ManageMessageId? message = ManageMessageId.Error; 81 | var user = await GetCurrentUserAsync(); 82 | if (user != null) 83 | { 84 | var result = await _userManager.RemoveLoginAsync(user, account.LoginProvider, account.ProviderKey); 85 | if (result.Succeeded) 86 | { 87 | await _signInManager.SignInAsync(user, isPersistent: false); 88 | message = ManageMessageId.RemoveLoginSuccess; 89 | } 90 | } 91 | return RedirectToAction(nameof(ManageLogins), new { Message = message }); 92 | } 93 | 94 | // 95 | // GET: /Manage/AddPhoneNumber 96 | public IActionResult AddPhoneNumber() 97 | { 98 | return View(); 99 | } 100 | 101 | // 102 | // POST: /Manage/AddPhoneNumber 103 | [HttpPost] 104 | [ValidateAntiForgeryToken] 105 | public async Task AddPhoneNumber(AddPhoneNumberViewModel model) 106 | { 107 | if (!ModelState.IsValid) 108 | { 109 | return View(model); 110 | } 111 | // Generate the token and send it 112 | var user = await GetCurrentUserAsync(); 113 | if (user == null) 114 | { 115 | return View("Error"); 116 | } 117 | var code = await _userManager.GenerateChangePhoneNumberTokenAsync(user, model.PhoneNumber); 118 | await _smsSender.SendSmsAsync(model.PhoneNumber, "Your security code is: " + code); 119 | return RedirectToAction(nameof(VerifyPhoneNumber), new { PhoneNumber = model.PhoneNumber }); 120 | } 121 | 122 | // 123 | // POST: /Manage/RequestTwoFactorAuthentication 124 | [HttpPost] 125 | [ValidateAntiForgeryToken] 126 | public async Task RequestTwoFactorAuthentication() 127 | { 128 | var user = await GetCurrentUserAsync(); 129 | if (user != null) 130 | { 131 | var tfaKey = Guid.NewGuid().ToString("N"); 132 | user.TfaKey = tfaKey; 133 | await _userManager.UpdateAsync(user); 134 | var authenticator = new TwoFactorAuthenticator(); 135 | var code = authenticator.GenerateSetupCode($"DosFactores ({user.UserName})", tfaKey, 300, 300); 136 | TempData["AuthenticatorQr"] = code.QrCodeSetupImageUrl; 137 | _logger.LogInformation(1, "User enabled two-factor authentication."); 138 | } 139 | return RedirectToAction(nameof(Index), "Manage"); 140 | } 141 | 142 | // 143 | // POST: /Manage/EnableTwoFactorAuthentication 144 | [HttpPost] 145 | [ValidateAntiForgeryToken] 146 | public async Task EnableTwoFactorAuthentication() 147 | { 148 | var user = await GetCurrentUserAsync(); 149 | if (user != null) 150 | { 151 | await _userManager.SetTwoFactorEnabledAsync(user, true); 152 | await _signInManager.SignInAsync(user, isPersistent: false); 153 | _logger.LogInformation(1, "User enabled two-factor authentication."); 154 | } 155 | return RedirectToAction(nameof(Index), "Manage"); 156 | } 157 | 158 | // 159 | // POST: /Manage/DisableTwoFactorAuthentication 160 | [HttpPost] 161 | [ValidateAntiForgeryToken] 162 | public async Task DisableTwoFactorAuthentication() 163 | { 164 | var user = await GetCurrentUserAsync(); 165 | if (user != null) 166 | { 167 | await _userManager.SetTwoFactorEnabledAsync(user, false); 168 | await _signInManager.SignInAsync(user, isPersistent: false); 169 | _logger.LogInformation(2, "User disabled two-factor authentication."); 170 | } 171 | return RedirectToAction(nameof(Index), "Manage"); 172 | } 173 | 174 | // 175 | // GET: /Manage/VerifyPhoneNumber 176 | [HttpGet] 177 | public async Task VerifyPhoneNumber(string phoneNumber) 178 | { 179 | var user = await GetCurrentUserAsync(); 180 | if (user == null) 181 | { 182 | return View("Error"); 183 | } 184 | var code = await _userManager.GenerateChangePhoneNumberTokenAsync(user, phoneNumber); 185 | // Send an SMS to verify the phone number 186 | return phoneNumber == null ? View("Error") : View(new VerifyPhoneNumberViewModel { PhoneNumber = phoneNumber }); 187 | } 188 | 189 | // 190 | // POST: /Manage/VerifyPhoneNumber 191 | [HttpPost] 192 | [ValidateAntiForgeryToken] 193 | public async Task VerifyPhoneNumber(VerifyPhoneNumberViewModel model) 194 | { 195 | if (!ModelState.IsValid) 196 | { 197 | return View(model); 198 | } 199 | var user = await GetCurrentUserAsync(); 200 | if (user != null) 201 | { 202 | var result = await _userManager.ChangePhoneNumberAsync(user, model.PhoneNumber, model.Code); 203 | if (result.Succeeded) 204 | { 205 | await _signInManager.SignInAsync(user, isPersistent: false); 206 | return RedirectToAction(nameof(Index), new { Message = ManageMessageId.AddPhoneSuccess }); 207 | } 208 | } 209 | // If we got this far, something failed, redisplay the form 210 | ModelState.AddModelError(string.Empty, "Failed to verify phone number"); 211 | return View(model); 212 | } 213 | 214 | // 215 | // POST: /Manage/RemovePhoneNumber 216 | [HttpPost] 217 | [ValidateAntiForgeryToken] 218 | public async Task RemovePhoneNumber() 219 | { 220 | var user = await GetCurrentUserAsync(); 221 | if (user != null) 222 | { 223 | var result = await _userManager.SetPhoneNumberAsync(user, null); 224 | if (result.Succeeded) 225 | { 226 | await _signInManager.SignInAsync(user, isPersistent: false); 227 | return RedirectToAction(nameof(Index), new { Message = ManageMessageId.RemovePhoneSuccess }); 228 | } 229 | } 230 | return RedirectToAction(nameof(Index), new { Message = ManageMessageId.Error }); 231 | } 232 | 233 | // 234 | // GET: /Manage/ChangePassword 235 | [HttpGet] 236 | public IActionResult ChangePassword() 237 | { 238 | return View(); 239 | } 240 | 241 | // 242 | // POST: /Manage/ChangePassword 243 | [HttpPost] 244 | [ValidateAntiForgeryToken] 245 | public async Task ChangePassword(ChangePasswordViewModel model) 246 | { 247 | if (!ModelState.IsValid) 248 | { 249 | return View(model); 250 | } 251 | var user = await GetCurrentUserAsync(); 252 | if (user != null) 253 | { 254 | var result = await _userManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword); 255 | if (result.Succeeded) 256 | { 257 | await _signInManager.SignInAsync(user, isPersistent: false); 258 | _logger.LogInformation(3, "User changed their password successfully."); 259 | return RedirectToAction(nameof(Index), new { Message = ManageMessageId.ChangePasswordSuccess }); 260 | } 261 | AddErrors(result); 262 | return View(model); 263 | } 264 | return RedirectToAction(nameof(Index), new { Message = ManageMessageId.Error }); 265 | } 266 | 267 | // 268 | // GET: /Manage/SetPassword 269 | [HttpGet] 270 | public IActionResult SetPassword() 271 | { 272 | return View(); 273 | } 274 | 275 | // 276 | // POST: /Manage/SetPassword 277 | [HttpPost] 278 | [ValidateAntiForgeryToken] 279 | public async Task SetPassword(SetPasswordViewModel model) 280 | { 281 | if (!ModelState.IsValid) 282 | { 283 | return View(model); 284 | } 285 | 286 | var user = await GetCurrentUserAsync(); 287 | if (user != null) 288 | { 289 | var result = await _userManager.AddPasswordAsync(user, model.NewPassword); 290 | if (result.Succeeded) 291 | { 292 | await _signInManager.SignInAsync(user, isPersistent: false); 293 | return RedirectToAction(nameof(Index), new { Message = ManageMessageId.SetPasswordSuccess }); 294 | } 295 | AddErrors(result); 296 | return View(model); 297 | } 298 | return RedirectToAction(nameof(Index), new { Message = ManageMessageId.Error }); 299 | } 300 | 301 | //GET: /Manage/ManageLogins 302 | [HttpGet] 303 | public async Task ManageLogins(ManageMessageId? message = null) 304 | { 305 | ViewData["StatusMessage"] = 306 | message == ManageMessageId.RemoveLoginSuccess ? "The external login was removed." 307 | : message == ManageMessageId.AddLoginSuccess ? "The external login was added." 308 | : message == ManageMessageId.Error ? "An error has occurred." 309 | : ""; 310 | var user = await GetCurrentUserAsync(); 311 | if (user == null) 312 | { 313 | return View("Error"); 314 | } 315 | var userLogins = await _userManager.GetLoginsAsync(user); 316 | var otherLogins = _signInManager.GetExternalAuthenticationSchemes().Where(auth => userLogins.All(ul => auth.AuthenticationScheme != ul.LoginProvider)).ToList(); 317 | ViewData["ShowRemoveButton"] = user.PasswordHash != null || userLogins.Count > 1; 318 | return View(new ManageLoginsViewModel 319 | { 320 | CurrentLogins = userLogins, 321 | OtherLogins = otherLogins 322 | }); 323 | } 324 | 325 | // 326 | // POST: /Manage/LinkLogin 327 | [HttpPost] 328 | [ValidateAntiForgeryToken] 329 | public async Task LinkLogin(string provider) 330 | { 331 | // Clear the existing external cookie to ensure a clean login process 332 | await HttpContext.Authentication.SignOutAsync(_externalCookieScheme); 333 | 334 | // Request a redirect to the external login provider to link a login for the current user 335 | var redirectUrl = Url.Action(nameof(LinkLoginCallback), "Manage"); 336 | var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, _userManager.GetUserId(User)); 337 | return Challenge(properties, provider); 338 | } 339 | 340 | // 341 | // GET: /Manage/LinkLoginCallback 342 | [HttpGet] 343 | public async Task LinkLoginCallback() 344 | { 345 | var user = await GetCurrentUserAsync(); 346 | if (user == null) 347 | { 348 | return View("Error"); 349 | } 350 | var info = await _signInManager.GetExternalLoginInfoAsync(await _userManager.GetUserIdAsync(user)); 351 | if (info == null) 352 | { 353 | return RedirectToAction(nameof(ManageLogins), new { Message = ManageMessageId.Error }); 354 | } 355 | var result = await _userManager.AddLoginAsync(user, info); 356 | var message = ManageMessageId.Error; 357 | if (result.Succeeded) 358 | { 359 | message = ManageMessageId.AddLoginSuccess; 360 | // Clear the existing external cookie to ensure a clean login process 361 | await HttpContext.Authentication.SignOutAsync(_externalCookieScheme); 362 | } 363 | return RedirectToAction(nameof(ManageLogins), new { Message = message }); 364 | } 365 | 366 | #region Helpers 367 | 368 | private void AddErrors(IdentityResult result) 369 | { 370 | foreach (var error in result.Errors) 371 | { 372 | ModelState.AddModelError(string.Empty, error.Description); 373 | } 374 | } 375 | 376 | public enum ManageMessageId 377 | { 378 | AddPhoneSuccess, 379 | AddLoginSuccess, 380 | ChangePasswordSuccess, 381 | SetTwoFactorSuccess, 382 | SetPasswordSuccess, 383 | RemoveLoginSuccess, 384 | RemovePhoneSuccess, 385 | Error 386 | } 387 | 388 | private Task GetCurrentUserAsync() 389 | { 390 | return _userManager.GetUserAsync(HttpContext.User); 391 | } 392 | 393 | #endregion 394 | } 395 | } 396 | --------------------------------------------------------------------------------