diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..df64a14 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/IdentityByExamples/.vs +/IdentityByExamples/IdentityByExamples/obj +/IdentityByExamples/IdentityByExamples/bin +/IdentityByExamples/EmailService/obj +/IdentityByExamples/EmailService/bin diff --git a/IdentityByExamples/EmailService/EmailConfiguration.cs b/IdentityByExamples/EmailService/EmailConfiguration.cs new file mode 100644 index 0000000..159d786 --- /dev/null +++ b/IdentityByExamples/EmailService/EmailConfiguration.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace EmailService +{ + public class EmailConfiguration + { + public string From { get; set; } + public string SmtpServer { get; set; } + public int Port { get; set; } + public string UserName { get; set; } + public string Password { get; set; } + } +} diff --git a/IdentityByExamples/EmailService/EmailSender.cs b/IdentityByExamples/EmailService/EmailSender.cs new file mode 100644 index 0000000..010c99e --- /dev/null +++ b/IdentityByExamples/EmailService/EmailSender.cs @@ -0,0 +1,111 @@ +using MailKit.Net.Smtp; +using MimeKit; +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace EmailService +{ + public class EmailSender : IEmailSender + { + private readonly EmailConfiguration _emailConfig; + + public EmailSender(EmailConfiguration emailConfig) + { + _emailConfig = emailConfig; + } + + public void SendEmail(Message message) + { + var emailMessage = CreateEmailMessage(message); + + Send(emailMessage); + } + + public async Task SendEmailAsync(Message message) + { + var mailMessage = CreateEmailMessage(message); + + await SendAsync(mailMessage); + } + + private MimeMessage CreateEmailMessage(Message message) + { + var emailMessage = new MimeMessage(); + emailMessage.From.Add(new MailboxAddress(_emailConfig.From)); + emailMessage.To.AddRange(message.To); + emailMessage.Subject = message.Subject; + + var bodyBuilder = new BodyBuilder { HtmlBody = string.Format("

{0}

", message.Content) }; + + if (message.Attachments != null && message.Attachments.Any()) + { + byte[] fileBytes; + foreach (var attachment in message.Attachments) + { + using (var ms = new MemoryStream()) + { + attachment.CopyTo(ms); + fileBytes = ms.ToArray(); + } + + bodyBuilder.Attachments.Add(attachment.FileName, fileBytes, ContentType.Parse(attachment.ContentType)); + } + } + + emailMessage.Body = bodyBuilder.ToMessageBody(); + return emailMessage; + } + + private void Send(MimeMessage mailMessage) + { + using (var client = new SmtpClient()) + { + try + { + client.Connect(_emailConfig.SmtpServer, _emailConfig.Port, true); + client.AuthenticationMechanisms.Remove("XOAUTH2"); + client.Authenticate(_emailConfig.UserName, _emailConfig.Password); + + client.Send(mailMessage); + } + catch + { + //log an error message or throw an exception, or both. + throw; + } + finally + { + client.Disconnect(true); + client.Dispose(); + } + } + } + + private async Task SendAsync(MimeMessage mailMessage) + { + using (var client = new SmtpClient()) + { + try + { + await client.ConnectAsync(_emailConfig.SmtpServer, _emailConfig.Port, true); + client.AuthenticationMechanisms.Remove("XOAUTH2"); + await client.AuthenticateAsync(_emailConfig.UserName, _emailConfig.Password); + + await client.SendAsync(mailMessage); + } + catch + { + //log an error message or throw an exception, or both. + throw; + } + finally + { + await client.DisconnectAsync(true); + client.Dispose(); + } + } + } + } +} diff --git a/IdentityByExamples/EmailService/EmailService.csproj b/IdentityByExamples/EmailService/EmailService.csproj new file mode 100644 index 0000000..ef1f2b2 --- /dev/null +++ b/IdentityByExamples/EmailService/EmailService.csproj @@ -0,0 +1,12 @@ + + + + netcoreapp3.1 + + + + + + + + diff --git a/IdentityByExamples/EmailService/IEmailSender.cs b/IdentityByExamples/EmailService/IEmailSender.cs new file mode 100644 index 0000000..21401f0 --- /dev/null +++ b/IdentityByExamples/EmailService/IEmailSender.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace EmailService +{ + public interface IEmailSender + { + void SendEmail(Message message); + Task SendEmailAsync(Message message); + } +} diff --git a/IdentityByExamples/EmailService/Message.cs b/IdentityByExamples/EmailService/Message.cs new file mode 100644 index 0000000..97b0b08 --- /dev/null +++ b/IdentityByExamples/EmailService/Message.cs @@ -0,0 +1,28 @@ +using Microsoft.AspNetCore.Http; +using MimeKit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace EmailService +{ + public class Message + { + public List To { get; set; } + public string Subject { get; set; } + public string Content { get; set; } + + public IFormFileCollection Attachments { get; set; } + + public Message(IEnumerable to, string subject, string content, IFormFileCollection attachments) + { + To = new List(); + + To.AddRange(to.Select(x => new MailboxAddress(x))); + Subject = subject; + Content = content; + Attachments = attachments; + } + } +} diff --git a/IdentityByExamples/IdentityByExamples.sln b/IdentityByExamples/IdentityByExamples.sln index b4a38c1..1c8e918 100644 --- a/IdentityByExamples/IdentityByExamples.sln +++ b/IdentityByExamples/IdentityByExamples.sln @@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.29519.87 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IdentityByExamples", "IdentityByExamples\IdentityByExamples.csproj", "{166DD728-A468-4863-9522-AEA30C752780}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IdentityByExamples", "IdentityByExamples\IdentityByExamples.csproj", "{166DD728-A468-4863-9522-AEA30C752780}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EmailService", "EmailService\EmailService.csproj", "{EBB8ED5D-F356-4D14-827D-D79CDC85FADB}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -15,6 +17,10 @@ Global {166DD728-A468-4863-9522-AEA30C752780}.Debug|Any CPU.Build.0 = Debug|Any CPU {166DD728-A468-4863-9522-AEA30C752780}.Release|Any CPU.ActiveCfg = Release|Any CPU {166DD728-A468-4863-9522-AEA30C752780}.Release|Any CPU.Build.0 = Release|Any CPU + {EBB8ED5D-F356-4D14-827D-D79CDC85FADB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EBB8ED5D-F356-4D14-827D-D79CDC85FADB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EBB8ED5D-F356-4D14-827D-D79CDC85FADB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EBB8ED5D-F356-4D14-827D-D79CDC85FADB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/IdentityByExamples/IdentityByExamples/Controllers/AccountController.cs b/IdentityByExamples/IdentityByExamples/Controllers/AccountController.cs new file mode 100644 index 0000000..8c6f0ac --- /dev/null +++ b/IdentityByExamples/IdentityByExamples/Controllers/AccountController.cs @@ -0,0 +1,206 @@ +using AutoMapper; +using EmailService; +using IdentityByExamples.Models; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Collections.Generic; +using System.Security.Claims; +using System.Threading.Tasks; + +namespace IdentityByExamples.Controllers +{ + public class AccountController : Controller + { + private readonly IMapper _mapper; + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + private readonly IEmailSender _emailSender; + + public AccountController(IMapper mapper, UserManager userManager, SignInManager signInManager, IEmailSender emailSender) + { + _mapper = mapper; + _userManager = userManager; + _signInManager = signInManager; + _emailSender = emailSender; + } + + [HttpGet] + public IActionResult Register() + { + return View(); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Register(UserRegistrationModel userModel) + { + if (!ModelState.IsValid) + { + return View(userModel); + } + + var user = _mapper.Map(userModel); + + var result = await _userManager.CreateAsync(user, userModel.Password); + if (!result.Succeeded) + { + foreach (var error in result.Errors) + { + ModelState.TryAddModelError(error.Code, error.Description); + } + + return View(userModel); + } + + var token = await _userManager.GenerateEmailConfirmationTokenAsync(user); + var confirmationLink = Url.Action(nameof(ConfirmEmail), "Account", new { token, email = user.Email }, Request.Scheme); + + var message = new Message(new string[] { user.Email }, "Confirmation email link", confirmationLink, null); + await _emailSender.SendEmailAsync(message); + + await _userManager.AddToRoleAsync(user, "Visitor"); + + return RedirectToAction(nameof(SuccessRegistration)); + } + + [HttpGet] + public async Task ConfirmEmail(string token, string email) + { + var user = await _userManager.FindByEmailAsync(email); + if (user == null) + return View("Error"); + + var result = await _userManager.ConfirmEmailAsync(user, token); + return View(result.Succeeded ? nameof(ConfirmEmail) : "Error"); + } + + [HttpGet] + public IActionResult SuccessRegistration() + { + return View(); + } + + [HttpGet] + public IActionResult Login(string returnUrl = null) + { + ViewData["ReturnUrl"] = returnUrl; + return View(); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Login(UserLoginModel userModel, string returnUrl = null) + { + if (!ModelState.IsValid) + { + return View(userModel); + } + + var result = await _signInManager.PasswordSignInAsync(userModel.Email, userModel.Password, userModel.RememberMe, false); + if (result.Succeeded) + { + return RedirectToLocal(returnUrl); + } + else + { + ModelState.AddModelError("", "Invalid Login Attempt"); + return View(); + } + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Logout() + { + await _signInManager.SignOutAsync(); + + return RedirectToAction(nameof(HomeController.Index), "Home"); + } + + [HttpGet] + public IActionResult ForgotPassword() + { + return View(); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task ForgotPassword(ForgotPasswordModel forgotPasswordModel) + { + if (!ModelState.IsValid) + return View(forgotPasswordModel); + + var user = await _userManager.FindByEmailAsync(forgotPasswordModel.Email); + if (user == null) + return RedirectToAction(nameof(ForgotPasswordConfirmation)); + + var token = await _userManager.GeneratePasswordResetTokenAsync(user); + var callback = Url.Action(nameof(ResetPassword), "Account", new { token, email = user.Email }, Request.Scheme); + + var message = new Message(new string[] { user.Email }, "Reset password token", callback, null); + await _emailSender.SendEmailAsync(message); + + return RedirectToAction(nameof(ForgotPasswordConfirmation)); + } + + public IActionResult ForgotPasswordConfirmation() + { + return View(); + } + + [HttpGet] + public IActionResult ResetPassword(string token, string email) + { + var model = new ResetPasswordModel { Token = token, Email = email }; + return View(model); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task ResetPassword(ResetPasswordModel resetPasswordModel) + { + if (!ModelState.IsValid) + return View(resetPasswordModel); + + var user = await _userManager.FindByEmailAsync(resetPasswordModel.Email); + if (user == null) + RedirectToAction(nameof(ResetPasswordConfirmation)); + + var resetPassResult = await _userManager.ResetPasswordAsync(user, resetPasswordModel.Token, resetPasswordModel.Password); + if (!resetPassResult.Succeeded) + { + foreach (var error in resetPassResult.Errors) + { + ModelState.TryAddModelError(error.Code, error.Description); + } + + return View(); + } + + return RedirectToAction(nameof(ResetPasswordConfirmation)); + } + + [HttpGet] + public IActionResult ResetPasswordConfirmation() + { + return View(); + } + + private IActionResult RedirectToLocal(string returnUrl) + { + if (Url.IsLocalUrl(returnUrl)) + return Redirect(returnUrl); + else + return RedirectToAction(nameof(HomeController.Index), "Home"); + } + + [HttpGet] + public IActionResult Error() + { + return View(); + } + } +} \ No newline at end of file diff --git a/IdentityByExamples/IdentityByExamples/Controllers/HomeController.cs b/IdentityByExamples/IdentityByExamples/Controllers/HomeController.cs index 338a57b..957d327 100644 --- a/IdentityByExamples/IdentityByExamples/Controllers/HomeController.cs +++ b/IdentityByExamples/IdentityByExamples/Controllers/HomeController.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.Logging; using IdentityByExamples.Models; using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Authorization; namespace IdentityByExamples.Controllers { @@ -24,6 +25,7 @@ public IActionResult Index() return View(); } + [Authorize] public async Task Employees() { var employees = await _context.Employees.ToListAsync(); diff --git a/IdentityByExamples/IdentityByExamples/CustomTokenProviders/EmailConfirmationTokenProvider.cs b/IdentityByExamples/IdentityByExamples/CustomTokenProviders/EmailConfirmationTokenProvider.cs new file mode 100644 index 0000000..38f825d --- /dev/null +++ b/IdentityByExamples/IdentityByExamples/CustomTokenProviders/EmailConfirmationTokenProvider.cs @@ -0,0 +1,22 @@ +using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace IdentityByExamples.CustomTokenProviders +{ + public class EmailConfirmationTokenProvider : DataProtectorTokenProvider where TUser : class + { + public EmailConfirmationTokenProvider(IDataProtectionProvider dataProtectionProvider, + IOptions options, + ILogger> logger) + : base(dataProtectionProvider, options, logger) + { + } + } + + public class EmailConfirmationTokenProviderOptions : DataProtectionTokenProviderOptions + { + + } +} diff --git a/IdentityByExamples/IdentityByExamples/Factory/CustomClaimsFactory.cs b/IdentityByExamples/IdentityByExamples/Factory/CustomClaimsFactory.cs new file mode 100644 index 0000000..48995d1 --- /dev/null +++ b/IdentityByExamples/IdentityByExamples/Factory/CustomClaimsFactory.cs @@ -0,0 +1,28 @@ +using IdentityByExamples.Models; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; + +namespace IdentityByExamples.Factory +{ + public class CustomClaimsFactory : UserClaimsPrincipalFactory + { + public CustomClaimsFactory(UserManager userManager, IOptions optionsAccessor) + : base(userManager, optionsAccessor) + { + } + + protected override async Task GenerateClaimsAsync(User user) + { + var identity = await base.GenerateClaimsAsync(user); + identity.AddClaim(new Claim("firstname", user.FirstName)); + identity.AddClaim(new Claim("lastname", user.LastName)); + + return identity; + } + } +} diff --git a/IdentityByExamples/IdentityByExamples/IdentityByExamples.csproj b/IdentityByExamples/IdentityByExamples/IdentityByExamples.csproj index b1232b9..5217f6c 100644 --- a/IdentityByExamples/IdentityByExamples/IdentityByExamples.csproj +++ b/IdentityByExamples/IdentityByExamples/IdentityByExamples.csproj @@ -5,6 +5,8 @@ + + @@ -15,4 +17,8 @@ + + + + diff --git a/IdentityByExamples/IdentityByExamples/IdentityByExamples.csproj.user b/IdentityByExamples/IdentityByExamples/IdentityByExamples.csproj.user new file mode 100644 index 0000000..3646450 --- /dev/null +++ b/IdentityByExamples/IdentityByExamples/IdentityByExamples.csproj.user @@ -0,0 +1,10 @@ + + + + 600 + True + False + False + + + \ No newline at end of file diff --git a/IdentityByExamples/IdentityByExamples/MappingProfile.cs b/IdentityByExamples/IdentityByExamples/MappingProfile.cs new file mode 100644 index 0000000..04d838c --- /dev/null +++ b/IdentityByExamples/IdentityByExamples/MappingProfile.cs @@ -0,0 +1,14 @@ +using AutoMapper; +using IdentityByExamples.Models; + +namespace IdentityByExamples +{ + public class MappingProfile : Profile + { + public MappingProfile() + { + CreateMap() + .ForMember(u => u.UserName, opt => opt.MapFrom(x => x.Email)); + } + } +} diff --git a/IdentityByExamples/IdentityByExamples/Migrations/20191202102202_InitialMigration.Designer.cs b/IdentityByExamples/IdentityByExamples/Migrations/20191202135856_InitialMigration.Designer.cs similarity index 97% rename from IdentityByExamples/IdentityByExamples/Migrations/20191202102202_InitialMigration.Designer.cs rename to IdentityByExamples/IdentityByExamples/Migrations/20191202135856_InitialMigration.Designer.cs index 27c8f52..755ccbb 100644 --- a/IdentityByExamples/IdentityByExamples/Migrations/20191202102202_InitialMigration.Designer.cs +++ b/IdentityByExamples/IdentityByExamples/Migrations/20191202135856_InitialMigration.Designer.cs @@ -10,7 +10,7 @@ namespace IdentityByExamples.Migrations { [DbContext(typeof(ApplicationContext))] - [Migration("20191202102202_InitialMigration")] + [Migration("20191202135856_InitialMigration")] partial class InitialMigration { protected override void BuildTargetModel(ModelBuilder modelBuilder) diff --git a/IdentityByExamples/IdentityByExamples/Migrations/20191202102202_InitialMigration.cs b/IdentityByExamples/IdentityByExamples/Migrations/20191202135856_InitialMigration.cs similarity index 100% rename from IdentityByExamples/IdentityByExamples/Migrations/20191202102202_InitialMigration.cs rename to IdentityByExamples/IdentityByExamples/Migrations/20191202135856_InitialMigration.cs diff --git a/IdentityByExamples/IdentityByExamples/Migrations/20191202142926_CreatingIdentityScheme.Designer.cs b/IdentityByExamples/IdentityByExamples/Migrations/20191202142926_CreatingIdentityScheme.Designer.cs new file mode 100644 index 0000000..15a99dc --- /dev/null +++ b/IdentityByExamples/IdentityByExamples/Migrations/20191202142926_CreatingIdentityScheme.Designer.cs @@ -0,0 +1,317 @@ +// +using System; +using IdentityByExamples.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace IdentityByExamples.Migrations +{ + [DbContext(typeof(ApplicationContext))] + [Migration("20191202142926_CreatingIdentityScheme")] + partial class CreatingIdentityScheme + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("IdentityByExamples.Models.Employee", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Age") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Position") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Employees"); + + b.HasData( + new + { + Id = new Guid("e310a6cb-6677-4aa6-93c7-2763956f7a97"), + Age = 26, + Name = "Mark Miens", + Position = "Software Developer" + }, + new + { + Id = new Guid("398d10fe-4b8d-4606-8e9c-bd2c78d4e001"), + Age = 29, + Name = "Anna Simmons", + Position = "Software Developer" + }); + }); + + modelBuilder.Entity("IdentityByExamples.Models.User", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("FirstName") + .HasColumnType("nvarchar(max)"); + + b.Property("LastName") + .HasColumnType("nvarchar(max)"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("IdentityByExamples.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("IdentityByExamples.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("IdentityByExamples.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("IdentityByExamples.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/IdentityByExamples/IdentityByExamples/Migrations/20191202142926_CreatingIdentityScheme.cs b/IdentityByExamples/IdentityByExamples/Migrations/20191202142926_CreatingIdentityScheme.cs new file mode 100644 index 0000000..8d249e2 --- /dev/null +++ b/IdentityByExamples/IdentityByExamples/Migrations/20191202142926_CreatingIdentityScheme.cs @@ -0,0 +1,221 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace IdentityByExamples.Migrations +{ + public partial class CreatingIdentityScheme : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(nullable: false), + Name = table.Column(maxLength: 256, nullable: true), + NormalizedName = table.Column(maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(nullable: false), + UserName = table.Column(maxLength: 256, nullable: true), + NormalizedUserName = table.Column(maxLength: 256, nullable: true), + Email = table.Column(maxLength: 256, nullable: true), + NormalizedEmail = table.Column(maxLength: 256, nullable: true), + EmailConfirmed = table.Column(nullable: false), + PasswordHash = table.Column(nullable: true), + SecurityStamp = table.Column(nullable: true), + ConcurrencyStamp = table.Column(nullable: true), + PhoneNumber = table.Column(nullable: true), + PhoneNumberConfirmed = table.Column(nullable: false), + TwoFactorEnabled = table.Column(nullable: false), + LockoutEnd = table.Column(nullable: true), + LockoutEnabled = table.Column(nullable: false), + AccessFailedCount = table.Column(nullable: false), + FirstName = table.Column(nullable: true), + LastName = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(nullable: false), + ClaimType = table.Column(nullable: true), + ClaimValue = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(nullable: false), + ClaimType = table.Column(nullable: true), + ClaimValue = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(nullable: false), + ProviderKey = table.Column(nullable: false), + ProviderDisplayName = table.Column(nullable: true), + UserId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(nullable: false), + RoleId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(nullable: false), + LoginProvider = table.Column(nullable: false), + Name = table.Column(nullable: false), + Value = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/IdentityByExamples/IdentityByExamples/Migrations/20191202145342_InsertedRoles.Designer.cs b/IdentityByExamples/IdentityByExamples/Migrations/20191202145342_InsertedRoles.Designer.cs new file mode 100644 index 0000000..e012464 --- /dev/null +++ b/IdentityByExamples/IdentityByExamples/Migrations/20191202145342_InsertedRoles.Designer.cs @@ -0,0 +1,333 @@ +// +using System; +using IdentityByExamples.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace IdentityByExamples.Migrations +{ + [DbContext(typeof(ApplicationContext))] + [Migration("20191202145342_InsertedRoles")] + partial class InsertedRoles + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("IdentityByExamples.Models.Employee", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Age") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Position") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Employees"); + + b.HasData( + new + { + Id = new Guid("e310a6cb-6677-4aa6-93c7-2763956f7a97"), + Age = 26, + Name = "Mark Miens", + Position = "Software Developer" + }, + new + { + Id = new Guid("398d10fe-4b8d-4606-8e9c-bd2c78d4e001"), + Age = 29, + Name = "Anna Simmons", + Position = "Software Developer" + }); + }); + + modelBuilder.Entity("IdentityByExamples.Models.User", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("FirstName") + .HasColumnType("nvarchar(max)"); + + b.Property("LastName") + .HasColumnType("nvarchar(max)"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + + b.HasData( + new + { + Id = "5e0246f5-e1b9-46ba-867e-81d1917aaaf7", + ConcurrencyStamp = "1dee1e04-abee-43db-b5d7-69c719165b88", + Name = "Visitor", + NormalizedName = "VISITOR" + }, + new + { + Id = "be7b6b91-2662-4760-8ee1-d966075e524e", + ConcurrencyStamp = "84ff6b93-365d-454a-b810-8bbe8d206491", + Name = "Administrator", + NormalizedName = "ADMINISTRATOR" + }); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("IdentityByExamples.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("IdentityByExamples.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("IdentityByExamples.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("IdentityByExamples.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/IdentityByExamples/IdentityByExamples/Migrations/20191202145342_InsertedRoles.cs b/IdentityByExamples/IdentityByExamples/Migrations/20191202145342_InsertedRoles.cs new file mode 100644 index 0000000..b720cc9 --- /dev/null +++ b/IdentityByExamples/IdentityByExamples/Migrations/20191202145342_InsertedRoles.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace IdentityByExamples.Migrations +{ + public partial class InsertedRoles : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.InsertData( + table: "AspNetRoles", + columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, + values: new object[] { "5e0246f5-e1b9-46ba-867e-81d1917aaaf7", "1dee1e04-abee-43db-b5d7-69c719165b88", "Visitor", "VISITOR" }); + + migrationBuilder.InsertData( + table: "AspNetRoles", + columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, + values: new object[] { "be7b6b91-2662-4760-8ee1-d966075e524e", "84ff6b93-365d-454a-b810-8bbe8d206491", "Administrator", "ADMINISTRATOR" }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: "5e0246f5-e1b9-46ba-867e-81d1917aaaf7"); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: "be7b6b91-2662-4760-8ee1-d966075e524e"); + } + } +} diff --git a/IdentityByExamples/IdentityByExamples/Migrations/ApplicationContextModelSnapshot.cs b/IdentityByExamples/IdentityByExamples/Migrations/ApplicationContextModelSnapshot.cs index 4744892..81f0f60 100644 --- a/IdentityByExamples/IdentityByExamples/Migrations/ApplicationContextModelSnapshot.cs +++ b/IdentityByExamples/IdentityByExamples/Migrations/ApplicationContextModelSnapshot.cs @@ -56,6 +56,275 @@ protected override void BuildModel(ModelBuilder modelBuilder) Position = "Software Developer" }); }); + + modelBuilder.Entity("IdentityByExamples.Models.User", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("FirstName") + .HasColumnType("nvarchar(max)"); + + b.Property("LastName") + .HasColumnType("nvarchar(max)"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + + b.HasData( + new + { + Id = "5e0246f5-e1b9-46ba-867e-81d1917aaaf7", + ConcurrencyStamp = "1dee1e04-abee-43db-b5d7-69c719165b88", + Name = "Visitor", + NormalizedName = "VISITOR" + }, + new + { + Id = "be7b6b91-2662-4760-8ee1-d966075e524e", + ConcurrencyStamp = "84ff6b93-365d-454a-b810-8bbe8d206491", + Name = "Administrator", + NormalizedName = "ADMINISTRATOR" + }); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("IdentityByExamples.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("IdentityByExamples.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("IdentityByExamples.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("IdentityByExamples.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); #pragma warning restore 612, 618 } } diff --git a/IdentityByExamples/IdentityByExamples/Models/ApplicationContext.cs b/IdentityByExamples/IdentityByExamples/Models/ApplicationContext.cs index 40cf9a9..1009810 100644 --- a/IdentityByExamples/IdentityByExamples/Models/ApplicationContext.cs +++ b/IdentityByExamples/IdentityByExamples/Models/ApplicationContext.cs @@ -1,9 +1,10 @@ using IdentityByExamples.Models.Configuration; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; namespace IdentityByExamples.Models { - public class ApplicationContext : DbContext + public class ApplicationContext : IdentityDbContext { public ApplicationContext(DbContextOptions options) : base(options) @@ -12,7 +13,10 @@ public ApplicationContext(DbContextOptions options) protected override void OnModelCreating(ModelBuilder modelBuilder) { + base.OnModelCreating(modelBuilder); + modelBuilder.ApplyConfiguration(new EmployeeConfiguration()); + modelBuilder.ApplyConfiguration(new RoleConfiguration()); } public DbSet Employees { get; set; } diff --git a/IdentityByExamples/IdentityByExamples/Models/Configuration/RoleConfiguration.cs b/IdentityByExamples/IdentityByExamples/Models/Configuration/RoleConfiguration.cs new file mode 100644 index 0000000..47a5a9f --- /dev/null +++ b/IdentityByExamples/IdentityByExamples/Models/Configuration/RoleConfiguration.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace IdentityByExamples.Models.Configuration +{ + public class RoleConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasData( + new IdentityRole + { + Name = "Visitor", + NormalizedName = "VISITOR" + }, + new IdentityRole + { + Name = "Administrator", + NormalizedName = "ADMINISTRATOR" + }); + } + } +} diff --git a/IdentityByExamples/IdentityByExamples/Models/ForgotPasswordModel.cs b/IdentityByExamples/IdentityByExamples/Models/ForgotPasswordModel.cs new file mode 100644 index 0000000..8568a8c --- /dev/null +++ b/IdentityByExamples/IdentityByExamples/Models/ForgotPasswordModel.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations; + +namespace IdentityByExamples.Models +{ + public class ForgotPasswordModel + { + [Required] + [EmailAddress] + public string Email { get; set; } + } +} diff --git a/IdentityByExamples/IdentityByExamples/Models/ResetPasswordModel.cs b/IdentityByExamples/IdentityByExamples/Models/ResetPasswordModel.cs new file mode 100644 index 0000000..e9fa838 --- /dev/null +++ b/IdentityByExamples/IdentityByExamples/Models/ResetPasswordModel.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations; + +namespace IdentityByExamples.Models +{ + public class ResetPasswordModel + { + [Required] + [DataType(DataType.Password)] + public string Password { get; set; } + + [DataType(DataType.Password)] + [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } + + public string Email { get; set; } + public string Token { get; set; } + } +} diff --git a/IdentityByExamples/IdentityByExamples/Models/User.cs b/IdentityByExamples/IdentityByExamples/Models/User.cs new file mode 100644 index 0000000..68dccfd --- /dev/null +++ b/IdentityByExamples/IdentityByExamples/Models/User.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Identity; + +namespace IdentityByExamples.Models +{ + public class User : IdentityUser + { + public string FirstName { get; set; } + public string LastName { get; set; } + } +} diff --git a/IdentityByExamples/IdentityByExamples/Models/UserLoginModel.cs b/IdentityByExamples/IdentityByExamples/Models/UserLoginModel.cs new file mode 100644 index 0000000..5dd64dc --- /dev/null +++ b/IdentityByExamples/IdentityByExamples/Models/UserLoginModel.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations; + +namespace IdentityByExamples.Models +{ + public class UserLoginModel + { + [Required] + [EmailAddress] + public string Email { get; set; } + + [Required] + [DataType(DataType.Password)] + public string Password { get; set; } + + [Display(Name = "Remember me?")] + public bool RememberMe { get; set; } + } +} diff --git a/IdentityByExamples/IdentityByExamples/Models/UserRegistrationModel.cs b/IdentityByExamples/IdentityByExamples/Models/UserRegistrationModel.cs new file mode 100644 index 0000000..e809060 --- /dev/null +++ b/IdentityByExamples/IdentityByExamples/Models/UserRegistrationModel.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; + +namespace IdentityByExamples.Models +{ + public class UserRegistrationModel + { + public string FirstName { get; set; } + public string LastName { get; set; } + [Required(ErrorMessage = "Email is required")] + [EmailAddress] + public string Email { get; set; } + + [Required(ErrorMessage = "Password is required")] + [DataType(DataType.Password)] + public string Password { get; set; } + + [DataType(DataType.Password)] + [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } + } +} diff --git a/IdentityByExamples/IdentityByExamples/Startup.cs b/IdentityByExamples/IdentityByExamples/Startup.cs index 2e70a61..d095981 100644 --- a/IdentityByExamples/IdentityByExamples/Startup.cs +++ b/IdentityByExamples/IdentityByExamples/Startup.cs @@ -2,10 +2,16 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using AutoMapper; +using EmailService; +using IdentityByExamples.CustomTokenProviders; +using IdentityByExamples.Factory; using IdentityByExamples.Models; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.HttpsPolicy; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -28,6 +34,38 @@ public void ConfigureServices(IServiceCollection services) services.AddDbContext(opts => opts.UseSqlServer(Configuration.GetConnectionString("sqlConnection"))); + services.AddIdentity(opt => + { + opt.Password.RequiredLength = 7; + opt.Password.RequireDigit = false; + opt.Password.RequireUppercase = false; + + opt.User.RequireUniqueEmail = true; + + opt.SignIn.RequireConfirmedEmail = true; + + opt.Tokens.EmailConfirmationTokenProvider = "emailconfirmation"; + }) + .AddEntityFrameworkStores() + .AddDefaultTokenProviders() + .AddTokenProvider>("emailconfirmation"); + + services.Configure(opt => + opt.TokenLifespan = TimeSpan.FromHours(2)); + + services.Configure(opt => + opt.TokenLifespan = TimeSpan.FromDays(3)); + + services.AddScoped, CustomClaimsFactory>(); + + services.AddAutoMapper(typeof(Startup)); + + var emailConfig = Configuration + .GetSection("EmailConfiguration") + .Get(); + services.AddSingleton(emailConfig); + services.AddScoped(); + services.AddControllersWithViews(); } @@ -49,6 +87,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseRouting(); + app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => diff --git a/IdentityByExamples/IdentityByExamples/Views/Account/ConfirmEmail.cshtml b/IdentityByExamples/IdentityByExamples/Views/Account/ConfirmEmail.cshtml new file mode 100644 index 0000000..7d7bdd7 --- /dev/null +++ b/IdentityByExamples/IdentityByExamples/Views/Account/ConfirmEmail.cshtml @@ -0,0 +1,7 @@ +

ConfirmEmail

+ +
+

+ Thank you for confirming your email. +

+
\ No newline at end of file diff --git a/IdentityByExamples/IdentityByExamples/Views/Account/Error.cshtml b/IdentityByExamples/IdentityByExamples/Views/Account/Error.cshtml new file mode 100644 index 0000000..a3d1723 --- /dev/null +++ b/IdentityByExamples/IdentityByExamples/Views/Account/Error.cshtml @@ -0,0 +1,3 @@ +

Error.

+

An error occurred while processing your request.

+ diff --git a/IdentityByExamples/IdentityByExamples/Views/Account/ForgotPassword.cshtml b/IdentityByExamples/IdentityByExamples/Views/Account/ForgotPassword.cshtml new file mode 100644 index 0000000..aa01c52 --- /dev/null +++ b/IdentityByExamples/IdentityByExamples/Views/Account/ForgotPassword.cshtml @@ -0,0 +1,23 @@ +@model IdentityByExamples.Models.ForgotPasswordModel + +

ForgotPassword

+ +
+
+
+
+
+ + + +
+
+ +
+
+
+
+ +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} +} diff --git a/IdentityByExamples/IdentityByExamples/Views/Account/ForgotPasswordConfirmation.cshtml b/IdentityByExamples/IdentityByExamples/Views/Account/ForgotPasswordConfirmation.cshtml new file mode 100644 index 0000000..b7972e8 --- /dev/null +++ b/IdentityByExamples/IdentityByExamples/Views/Account/ForgotPasswordConfirmation.cshtml @@ -0,0 +1,5 @@ +

ForgotPasswordConfirmation

+ +

+ The link has been sent, please check your email to reset your password. +

\ No newline at end of file diff --git a/IdentityByExamples/IdentityByExamples/Views/Account/Login.cshtml b/IdentityByExamples/IdentityByExamples/Views/Account/Login.cshtml new file mode 100644 index 0000000..de1286d --- /dev/null +++ b/IdentityByExamples/IdentityByExamples/Views/Account/Login.cshtml @@ -0,0 +1,36 @@ +@model IdentityByExamples.Models.UserLoginModel + +

Login

+ +
+
+
+
+
+ + + +
+
+ + + +
+
+ +
+ +
+ +
+
+
+
+ +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} +} diff --git a/IdentityByExamples/IdentityByExamples/Views/Account/Register.cshtml b/IdentityByExamples/IdentityByExamples/Views/Account/Register.cshtml new file mode 100644 index 0000000..b08eeff --- /dev/null +++ b/IdentityByExamples/IdentityByExamples/Views/Account/Register.cshtml @@ -0,0 +1,45 @@ +@model IdentityByExamples.Models.UserRegistrationModel + +

Register

+ +

UserRegistrationModel

+
+
+
+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ +
+
+
+
+ +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} +} diff --git a/IdentityByExamples/IdentityByExamples/Views/Account/ResetPassword.cshtml b/IdentityByExamples/IdentityByExamples/Views/Account/ResetPassword.cshtml new file mode 100644 index 0000000..2340864 --- /dev/null +++ b/IdentityByExamples/IdentityByExamples/Views/Account/ResetPassword.cshtml @@ -0,0 +1,27 @@ +@model IdentityByExamples.Models.ResetPasswordModel + +

ResetPassword

+ +
+
+
+
+
+ + + +
+
+ + + +
+ + +
+ +
+
+
+
+ diff --git a/IdentityByExamples/IdentityByExamples/Views/Account/ResetPasswordConfirmation.cshtml b/IdentityByExamples/IdentityByExamples/Views/Account/ResetPasswordConfirmation.cshtml new file mode 100644 index 0000000..57f1414 --- /dev/null +++ b/IdentityByExamples/IdentityByExamples/Views/Account/ResetPasswordConfirmation.cshtml @@ -0,0 +1,5 @@ +

ResetPasswordConfirmation

+ +

+ Your password has been reset. Please click here to log in. +

\ No newline at end of file diff --git a/IdentityByExamples/IdentityByExamples/Views/Account/SuccessRegistration.cshtml b/IdentityByExamples/IdentityByExamples/Views/Account/SuccessRegistration.cshtml new file mode 100644 index 0000000..28a0df4 --- /dev/null +++ b/IdentityByExamples/IdentityByExamples/Views/Account/SuccessRegistration.cshtml @@ -0,0 +1,5 @@ +

SuccessRegistration

+ +

+ Please check your email for the verification action. +

\ No newline at end of file diff --git a/IdentityByExamples/IdentityByExamples/Views/Home/Employees.cshtml b/IdentityByExamples/IdentityByExamples/Views/Home/Employees.cshtml index 7ec69d3..402fc58 100644 --- a/IdentityByExamples/IdentityByExamples/Views/Home/Employees.cshtml +++ b/IdentityByExamples/IdentityByExamples/Views/Home/Employees.cshtml @@ -51,3 +51,11 @@ } + +

Claim details

+
    + @foreach (var claim in User.Claims) + { +
  • @claim.Type: @claim.Value
  • + } +
diff --git a/IdentityByExamples/IdentityByExamples/Views/Shared/Error.cshtml b/IdentityByExamples/IdentityByExamples/Views/Shared/Error.cshtml index a1e0478..7325a20 100644 --- a/IdentityByExamples/IdentityByExamples/Views/Shared/Error.cshtml +++ b/IdentityByExamples/IdentityByExamples/Views/Shared/Error.cshtml @@ -1,25 +1,2 @@ -@model ErrorViewModel -@{ - ViewData["Title"] = "Error"; -} - -

Error.

+

Error.

An error occurred while processing your request.

- -@if (Model.ShowRequestId) -{ -

- Request ID: @Model.RequestId -

-} - -

Development Mode

-

- Swapping to Development environment will display more detailed information about the error that occurred. -

-

- The Development environment shouldn't be enabled for deployed applications. - It can result in displaying sensitive information from exceptions to end users. - For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development - and restarting the app. -

diff --git a/IdentityByExamples/IdentityByExamples/Views/Shared/_Layout.cshtml b/IdentityByExamples/IdentityByExamples/Views/Shared/_Layout.cshtml index dc736f2..c905824 100644 --- a/IdentityByExamples/IdentityByExamples/Views/Shared/_Layout.cshtml +++ b/IdentityByExamples/IdentityByExamples/Views/Shared/_Layout.cshtml @@ -17,6 +17,7 @@