Identity Rescafolding and DBContext changes

This commit is contained in:
Dimitar Todorov
2022-04-04 22:48:32 +03:00
parent 0ab907582c
commit 9b29101c2d
53 changed files with 1250 additions and 339 deletions

View File

@@ -6,21 +6,14 @@ using Microsoft.EntityFrameworkCore;
namespace Data
{
public class RentACarDbContext : IdentityDbContext<IdentityUser>
public class RentACarDbContext : IdentityDbContext<User, IdentityRole, string>
{
public virtual DbSet<Car> Cars { get; set; }
public virtual DbSet<Rents> Rents { get; set; }
private UserManager<User> userManager { get; set; }
private RoleManager<IdentityRole> roleManager { get; set; }
public RentACarDbContext()
{
//TODO: initialize UserManager and RoleManager
}
public RentACarDbContext(DbContextOptions<RentACarDbContext> dbContextOptions) : base(dbContextOptions)
{
//TODO: initialize UserManager and RoleManager
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
@@ -33,57 +26,48 @@ namespace Data
protected override async void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
this.Database.EnsureCreated();
string[] roles = { "Admin", "Employee" };
foreach (string role in roles)
{
if (!await roleManager.RoleExistsAsync(role))
IdentityRole roleToCheck = await this.Roles.FirstOrDefaultAsync(roleToCheck => roleToCheck.Name == role);
if (roleToCheck == null)
{
await roleManager.CreateAsync(new IdentityRole(role));
//this.Roles.Add(new IdentityRole(role));
modelBuilder.Entity<IdentityRole>().HasData(new IdentityRole(role));
}
}
User initialUser = new User
PasswordHasher<User> passwordHasher = new PasswordHasher<User>();
User initialUser = new User();
initialUser.Id = Guid.NewGuid().ToString();
initialUser.UserName = "admin";
initialUser.PasswordHash = passwordHasher.HashPassword(initialUser, "admin");
if (this.Users.FirstOrDefaultAsync() != null)
{
UserName = "admin",
PasswordHash = "admin",
};
modelBuilder.Entity<User>().HasData(initialUser);
await userManager.AddToRoleAsync(initialUser, roles[0]);
modelBuilder.Entity<User>().HasData(initialUser);
IdentityRole<string> adminRole = await this.Roles.FirstOrDefaultAsync(role => role.Name == "Admin");
modelBuilder.Entity<IdentityUserRole<string>>().HasData(new IdentityUserRole<string> {RoleId = adminRole.Id, UserId = initialUser.Id});
}
//modelBuilder.Entity<User>().HasData(
// new User
// {
// Username = "user",
// Password = "user",
// FirstName = "User",
// LastName = "User",
// PersonalNumber = "0987654321",
// PhoneNumber = "0882750588",
// Email = "user@gmail.org",
// Role = User.RoleEnum.User
// }
//);
//modelBuilder.Entity<User>().HasData(
// new User
// {
// Username = "manager",
// Password = "manager",
// FirstName = "Manager",
// LastName = "Manager",
// PersonalNumber = "0987654321",
// PhoneNumber = "0882750588",
// Email = "manager@gmail.org",
// Role = User.RoleEnum.Manager
// }
//);
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>()
.HasIndex(user => new { user.UserName })
.IsUnique(true);
modelBuilder.Entity<Rents>().HasOne(rents => rents.User);
modelBuilder.Entity<Rents>().HasOne(rents => rents.Car);
modelBuilder.Entity<Car>().HasData(new Car()
{
Id = 1,
Brand = "Trabant"
}) ;
this.SaveChanges();
}
}

View File

@@ -10,15 +10,15 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Data.Migrations
{
[DbContext(typeof(RentACarDbContext))]
[Migration("20220401141638_InitialMigration")]
partial class InitialMigration
[Migration("20220404172708_Changes")]
partial class Changes
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("ProductVersion", "5.0.13")
.HasAnnotation("ProductVersion", "5.0.15")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Data.Entities.Car", b =>
@@ -58,7 +58,7 @@ namespace Data.Migrations
.HasColumnType("int")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int?>("CarId")
b.Property<int>("CarId")
.HasColumnType("int");
b.Property<DateTime>("EndDate")
@@ -88,10 +88,12 @@ namespace Data.Migrations
.HasColumnType("int");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Email")
.HasColumnType("nvarchar(max)");
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("bit");
@@ -109,10 +111,12 @@ namespace Data.Migrations
.HasColumnType("datetimeoffset");
b.Property<string>("NormalizedEmail")
.HasColumnType("nvarchar(max)");
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedUserName")
.HasColumnType("nvarchar(max)");
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
@@ -133,15 +137,20 @@ namespace Data.Migrations
.HasColumnType("bit");
b.Property<string>("UserName")
.HasColumnType("nvarchar(450)");
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.HasIndex("UserName")
.IsUnique()
.HasFilter("[UserName] IS NOT NULL");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.ToTable("User");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
@@ -195,71 +204,6 @@ namespace Data.Migrations
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("bit");
b.Property<bool>("LockoutEnabled")
.HasColumnType("bit");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetimeoffset");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
b.Property<string>("PhoneNumber")
.HasColumnType("nvarchar(max)");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("bit");
b.Property<string>("SecurityStamp")
.HasColumnType("nvarchar(max)");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("bit");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
@@ -344,7 +288,9 @@ namespace Data.Migrations
{
b.HasOne("Data.Entities.Car", "Car")
.WithMany()
.HasForeignKey("CarId");
.HasForeignKey("CarId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Data.Entities.User", "User")
.WithMany()
@@ -366,7 +312,7 @@ namespace Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
b.HasOne("Data.Entities.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
@@ -375,7 +321,7 @@ namespace Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
b.HasOne("Data.Entities.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
@@ -390,7 +336,7 @@ namespace Data.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
b.HasOne("Data.Entities.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
@@ -399,7 +345,7 @@ namespace Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
b.HasOne("Data.Entities.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)

View File

@@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
namespace Data.Migrations
{
public partial class InitialMigration : Migration
public partial class Changes : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
@@ -26,6 +26,9 @@ namespace Data.Migrations
columns: table => new
{
Id = table.Column<string>(type: "nvarchar(450)", nullable: false),
FirstName = table.Column<string>(type: "nvarchar(max)", nullable: true),
LastName = table.Column<string>(type: "nvarchar(max)", nullable: true),
PersonalNumber = table.Column<string>(type: "nvarchar(max)", nullable: true),
UserName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
NormalizedUserName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
Email = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
@@ -64,34 +67,6 @@ namespace Data.Migrations
table.PrimaryKey("PK_Cars", x => x.Id);
});
migrationBuilder.CreateTable(
name: "User",
columns: table => new
{
Id = table.Column<string>(type: "nvarchar(450)", nullable: false),
FirstName = table.Column<string>(type: "nvarchar(max)", nullable: true),
LastName = table.Column<string>(type: "nvarchar(max)", nullable: true),
PersonalNumber = table.Column<string>(type: "nvarchar(max)", nullable: true),
UserName = table.Column<string>(type: "nvarchar(450)", nullable: true),
NormalizedUserName = table.Column<string>(type: "nvarchar(max)", nullable: true),
Email = table.Column<string>(type: "nvarchar(max)", nullable: true),
NormalizedEmail = table.Column<string>(type: "nvarchar(max)", nullable: true),
EmailConfirmed = table.Column<bool>(type: "bit", nullable: false),
PasswordHash = table.Column<string>(type: "nvarchar(max)", nullable: true),
SecurityStamp = table.Column<string>(type: "nvarchar(max)", nullable: true),
ConcurrencyStamp = table.Column<string>(type: "nvarchar(max)", nullable: true),
PhoneNumber = table.Column<string>(type: "nvarchar(max)", nullable: true),
PhoneNumberConfirmed = table.Column<bool>(type: "bit", nullable: false),
TwoFactorEnabled = table.Column<bool>(type: "bit", nullable: false),
LockoutEnd = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: true),
LockoutEnabled = table.Column<bool>(type: "bit", nullable: false),
AccessFailedCount = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_User", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AspNetRoleClaims",
columns: table => new
@@ -204,7 +179,7 @@ namespace Data.Migrations
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
CarId = table.Column<int>(type: "int", nullable: true),
CarId = table.Column<int>(type: "int", nullable: false),
StartDate = table.Column<DateTime>(type: "datetime2", nullable: false),
EndDate = table.Column<DateTime>(type: "datetime2", nullable: false),
UserId = table.Column<string>(type: "nvarchar(450)", nullable: true)
@@ -212,18 +187,18 @@ namespace Data.Migrations
constraints: table =>
{
table.PrimaryKey("PK_Rents", x => x.Id);
table.ForeignKey(
name: "FK_Rents_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_Rents_Cars_CarId",
column: x => x.CarId,
principalTable: "Cars",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_Rents_User_UserId",
column: x => x.UserId,
principalTable: "User",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
@@ -274,13 +249,6 @@ namespace Data.Migrations
name: "IX_Rents_UserId",
table: "Rents",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_User_UserName",
table: "User",
column: "UserName",
unique: true,
filter: "[UserName] IS NOT NULL");
}
protected override void Down(MigrationBuilder migrationBuilder)
@@ -311,9 +279,6 @@ namespace Data.Migrations
migrationBuilder.DropTable(
name: "Cars");
migrationBuilder.DropTable(
name: "User");
}
}
}

View File

@@ -16,7 +16,7 @@ namespace Data.Migrations
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("ProductVersion", "5.0.13")
.HasAnnotation("ProductVersion", "5.0.15")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Data.Entities.Car", b =>
@@ -56,7 +56,7 @@ namespace Data.Migrations
.HasColumnType("int")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int?>("CarId")
b.Property<int>("CarId")
.HasColumnType("int");
b.Property<DateTime>("EndDate")
@@ -86,10 +86,12 @@ namespace Data.Migrations
.HasColumnType("int");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Email")
.HasColumnType("nvarchar(max)");
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("bit");
@@ -107,10 +109,12 @@ namespace Data.Migrations
.HasColumnType("datetimeoffset");
b.Property<string>("NormalizedEmail")
.HasColumnType("nvarchar(max)");
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedUserName")
.HasColumnType("nvarchar(max)");
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
@@ -131,15 +135,20 @@ namespace Data.Migrations
.HasColumnType("bit");
b.Property<string>("UserName")
.HasColumnType("nvarchar(450)");
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.HasIndex("UserName")
.IsUnique()
.HasFilter("[UserName] IS NOT NULL");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.ToTable("User");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
@@ -193,71 +202,6 @@ namespace Data.Migrations
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("bit");
b.Property<bool>("LockoutEnabled")
.HasColumnType("bit");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetimeoffset");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
b.Property<string>("PhoneNumber")
.HasColumnType("nvarchar(max)");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("bit");
b.Property<string>("SecurityStamp")
.HasColumnType("nvarchar(max)");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("bit");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
@@ -342,7 +286,9 @@ namespace Data.Migrations
{
b.HasOne("Data.Entities.Car", "Car")
.WithMany()
.HasForeignKey("CarId");
.HasForeignKey("CarId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Data.Entities.User", "User")
.WithMany()
@@ -364,7 +310,7 @@ namespace Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
b.HasOne("Data.Entities.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
@@ -373,7 +319,7 @@ namespace Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
b.HasOne("Data.Entities.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
@@ -388,7 +334,7 @@ namespace Data.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
b.HasOne("Data.Entities.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
@@ -397,7 +343,7 @@ namespace Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
b.HasOne("Data.Entities.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)

View File

@@ -1,5 +1,7 @@
using System;
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
@@ -9,13 +11,13 @@ namespace Data.Entities
public class Rents
{
public int Id { get; set; }
[ForeignKey("Car")]
public int CarId { get; set; }
public virtual Car Car { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
[ForeignKey("User")]
public string UserId { get; set; }
public virtual User User { get; set; }
}
}

View File

@@ -1,5 +1,6 @@
using System;
using Data;
using Data.Entities;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI;

View File

@@ -0,0 +1,10 @@
@page
@model AccessDeniedModel
@{
ViewData["Title"] = "Access denied";
}
<header>
<h1 class="text-danger">@ViewData["Title"]</h1>
<p class="text-danger">You do not have access to this resource.</p>
</header>

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace WebApp.Areas.Identity.Pages.Account
{
public class AccessDeniedModel : PageModel
{
public void OnGet()
{
}
}
}

View File

@@ -14,9 +14,9 @@
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.Username"></label>
<input asp-for="Input.Username" class="form-control" />
<span asp-validation-for="Input.Username" class="text-danger"></span>
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" class="form-control" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.Password"></label>

View File

@@ -5,13 +5,13 @@ using System.Linq;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Data.Entities;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using Data.Entities;
namespace WebApp.Areas.Identity.Pages.Account
{
@@ -44,7 +44,8 @@ namespace WebApp.Areas.Identity.Pages.Account
public class InputModel
{
[Required]
public string Username { get; set; }
[EmailAddress]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
@@ -81,7 +82,7 @@ namespace WebApp.Areas.Identity.Pages.Account
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var result = await _signInManager.PasswordSignInAsync(Input.Username, Input.Password, Input.RememberMe, lockoutOnFailure: false);
var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");

View File

@@ -2,8 +2,8 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Data.Entities;
using Microsoft.AspNetCore.Authorization;
using Data.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

View File

@@ -0,0 +1,36 @@
@page
@model ChangePasswordModel
@{
ViewData["Title"] = "Change password";
ViewData["ActivePage"] = ManageNavPages.ChangePassword;
}
<h4>@ViewData["Title"]</h4>
<partial name="_StatusMessage" for="StatusMessage" />
<div class="row">
<div class="col-md-6">
<form id="change-password-form" method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.OldPassword"></label>
<input asp-for="Input.OldPassword" class="form-control" />
<span asp-validation-for="Input.OldPassword" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.NewPassword"></label>
<input asp-for="Input.NewPassword" class="form-control" />
<span asp-validation-for="Input.NewPassword" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.ConfirmPassword"></label>
<input asp-for="Input.ConfirmPassword" class="form-control" />
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Update password</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

View File

@@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Data.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace WebApp.Areas.Identity.Pages.Account.Manage
{
public class ChangePasswordModel : PageModel
{
private readonly UserManager<User> _userManager;
private readonly SignInManager<User> _signInManager;
private readonly ILogger<ChangePasswordModel> _logger;
public ChangePasswordModel(
UserManager<User> userManager,
SignInManager<User> signInManager,
ILogger<ChangePasswordModel> logger)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
}
[BindProperty]
public InputModel Input { get; set; }
[TempData]
public string StatusMessage { get; set; }
public class InputModel
{
[Required]
[DataType(DataType.Password)]
[Display(Name = "Current password")]
public string OldPassword { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "New password")]
public string NewPassword { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm new password")]
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
public async Task<IActionResult> OnGetAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var hasPassword = await _userManager.HasPasswordAsync(user);
if (!hasPassword)
{
return RedirectToPage("./SetPassword");
}
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var changePasswordResult = await _userManager.ChangePasswordAsync(user, Input.OldPassword, Input.NewPassword);
if (!changePasswordResult.Succeeded)
{
foreach (var error in changePasswordResult.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
return Page();
}
await _signInManager.RefreshSignInAsync(user);
_logger.LogInformation("User changed their password successfully.");
StatusMessage = "Your password has been changed.";
return RedirectToPage();
}
}
}

View File

@@ -0,0 +1,33 @@
@page
@model DeletePersonalDataModel
@{
ViewData["Title"] = "Delete Personal Data";
ViewData["ActivePage"] = ManageNavPages.PersonalData;
}
<h4>@ViewData["Title"]</h4>
<div class="alert alert-warning" role="alert">
<p>
<strong>Deleting this data will permanently remove your account, and this cannot be recovered.</strong>
</p>
</div>
<div>
<form id="delete-user" method="post" class="form-group">
<div asp-validation-summary="All" class="text-danger"></div>
@if (Model.RequirePassword)
{
<div class="form-group">
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password" class="form-control" />
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
}
<button class="btn btn-danger" type="submit">Delete data and close my account</button>
</form>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

View File

@@ -0,0 +1,84 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Data.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace WebApp.Areas.Identity.Pages.Account.Manage
{
public class DeletePersonalDataModel : PageModel
{
private readonly UserManager<User> _userManager;
private readonly SignInManager<User> _signInManager;
private readonly ILogger<DeletePersonalDataModel> _logger;
public DeletePersonalDataModel(
UserManager<User> userManager,
SignInManager<User> signInManager,
ILogger<DeletePersonalDataModel> logger)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
}
[BindProperty]
public InputModel Input { get; set; }
public class InputModel
{
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
public bool RequirePassword { get; set; }
public async Task<IActionResult> OnGet()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
RequirePassword = await _userManager.HasPasswordAsync(user);
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
RequirePassword = await _userManager.HasPasswordAsync(user);
if (RequirePassword)
{
if (!await _userManager.CheckPasswordAsync(user, Input.Password))
{
ModelState.AddModelError(string.Empty, "Incorrect password.");
return Page();
}
}
var result = await _userManager.DeleteAsync(user);
var userId = await _userManager.GetUserIdAsync(user);
if (!result.Succeeded)
{
throw new InvalidOperationException($"Unexpected error occurred deleting user with ID '{userId}'.");
}
await _signInManager.SignOutAsync();
_logger.LogInformation("User with ID '{UserId}' deleted themselves.", userId);
return Redirect("~/");
}
}
}

View File

@@ -0,0 +1,12 @@
@page
@model DownloadPersonalDataModel
@{
ViewData["Title"] = "Download Your Data";
ViewData["ActivePage"] = ManageNavPages.PersonalData;
}
<h4>@ViewData["Title"]</h4>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

View File

@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Data.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace WebApp.Areas.Identity.Pages.Account.Manage
{
public class DownloadPersonalDataModel : PageModel
{
private readonly UserManager<User> _userManager;
private readonly ILogger<DownloadPersonalDataModel> _logger;
public DownloadPersonalDataModel(
UserManager<User> userManager,
ILogger<DownloadPersonalDataModel> logger)
{
_userManager = userManager;
_logger = logger;
}
public async Task<IActionResult> OnPostAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
_logger.LogInformation("User with ID '{UserId}' asked for their personal data.", _userManager.GetUserId(User));
// Only include personal data for download
var personalData = new Dictionary<string, string>();
var personalDataProps = typeof(User).GetProperties().Where(
prop => Attribute.IsDefined(prop, typeof(PersonalDataAttribute)));
foreach (var p in personalDataProps)
{
personalData.Add(p.Name, p.GetValue(user)?.ToString() ?? "null");
}
var logins = await _userManager.GetLoginsAsync(user);
foreach (var l in logins)
{
personalData.Add($"{l.LoginProvider} external login provider key", l.ProviderKey);
}
Response.Headers.Add("Content-Disposition", "attachment; filename=PersonalData.json");
return new FileContentResult(JsonSerializer.SerializeToUtf8Bytes(personalData), "application/json");
}
}
}

View File

@@ -0,0 +1,43 @@
@page
@model EmailModel
@{
ViewData["Title"] = "Manage Email";
ViewData["ActivePage"] = ManageNavPages.Email;
}
<h4>@ViewData["Title"]</h4>
<partial name="_StatusMessage" model="Model.StatusMessage" />
<div class="row">
<div class="col-md-6">
<form id="email-form" method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Email"></label>
@if (Model.IsEmailConfirmed)
{
<div class="input-group">
<input asp-for="Email" class="form-control" disabled />
<div class="input-group-append">
<span class="input-group-text text-success font-weight-bold">✓</span>
</div>
</div>
}
else
{
<input asp-for="Email" class="form-control" disabled />
<button id="email-verification" type="submit" asp-page-handler="SendVerificationEmail" class="btn btn-link">Send verification email</button>
}
</div>
<div class="form-group">
<label asp-for="Input.NewEmail"></label>
<input asp-for="Input.NewEmail" class="form-control" />
<span asp-validation-for="Input.NewEmail" class="text-danger"></span>
</div>
<button id="change-email-button" type="submit" asp-page-handler="ChangeEmail" class="btn btn-primary">Change email</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

View File

@@ -0,0 +1,148 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text;
using System.Text.Encodings.Web;
using System.Linq;
using System.Threading.Tasks;
using Data.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
namespace WebApp.Areas.Identity.Pages.Account.Manage
{
public partial class EmailModel : PageModel
{
private readonly UserManager<User> _userManager;
private readonly SignInManager<User> _signInManager;
private readonly IEmailSender _emailSender;
public EmailModel(
UserManager<User> userManager,
SignInManager<User> signInManager,
IEmailSender emailSender)
{
_userManager = userManager;
_signInManager = signInManager;
_emailSender = emailSender;
}
public string Username { get; set; }
public string Email { get; set; }
public bool IsEmailConfirmed { get; set; }
[TempData]
public string StatusMessage { get; set; }
[BindProperty]
public InputModel Input { get; set; }
public class InputModel
{
[Required]
[EmailAddress]
[Display(Name = "New email")]
public string NewEmail { get; set; }
}
private async Task LoadAsync(User user)
{
var email = await _userManager.GetEmailAsync(user);
Email = email;
Input = new InputModel
{
NewEmail = email,
};
IsEmailConfirmed = await _userManager.IsEmailConfirmedAsync(user);
}
public async Task<IActionResult> OnGetAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
await LoadAsync(user);
return Page();
}
public async Task<IActionResult> OnPostChangeEmailAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (!ModelState.IsValid)
{
await LoadAsync(user);
return Page();
}
var email = await _userManager.GetEmailAsync(user);
if (Input.NewEmail != email)
{
var userId = await _userManager.GetUserIdAsync(user);
var code = await _userManager.GenerateChangeEmailTokenAsync(user, Input.NewEmail);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmailChange",
pageHandler: null,
values: new { userId = userId, email = Input.NewEmail, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(
Input.NewEmail,
"Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
StatusMessage = "Confirmation link to change email sent. Please check your email.";
return RedirectToPage();
}
StatusMessage = "Your email is unchanged.";
return RedirectToPage();
}
public async Task<IActionResult> OnPostSendVerificationEmailAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (!ModelState.IsValid)
{
await LoadAsync(user);
return Page();
}
var userId = await _userManager.GetUserIdAsync(user);
var email = await _userManager.GetEmailAsync(user);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = userId, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(
email,
"Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
StatusMessage = "Verification email sent. Please check your email.";
return RedirectToPage();
}
}
}

View File

@@ -0,0 +1,30 @@
@page
@model IndexModel
@{
ViewData["Title"] = "Profile";
ViewData["ActivePage"] = ManageNavPages.Index;
}
<h4>@ViewData["Title"]</h4>
<partial name="_StatusMessage" model="Model.StatusMessage" />
<div class="row">
<div class="col-md-6">
<form id="profile-form" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Username"></label>
<input asp-for="Username" class="form-control" disabled />
</div>
<div class="form-group">
<label asp-for="Input.PhoneNumber"></label>
<input asp-for="Input.PhoneNumber" class="form-control" />
<span asp-validation-for="Input.PhoneNumber" class="text-danger"></span>
</div>
<button id="update-profile-button" type="submit" class="btn btn-primary">Save</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

View File

@@ -0,0 +1,96 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Data.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace WebApp.Areas.Identity.Pages.Account.Manage
{
public partial class IndexModel : PageModel
{
private readonly UserManager<User> _userManager;
private readonly SignInManager<User> _signInManager;
public IndexModel(
UserManager<User> userManager,
SignInManager<User> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
public string Username { get; set; }
[TempData]
public string StatusMessage { get; set; }
[BindProperty]
public InputModel Input { get; set; }
public class InputModel
{
[Phone]
[Display(Name = "Phone number")]
public string PhoneNumber { get; set; }
}
private async Task LoadAsync(User user)
{
var userName = await _userManager.GetUserNameAsync(user);
var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
Username = userName;
Input = new InputModel
{
PhoneNumber = phoneNumber
};
}
public async Task<IActionResult> OnGetAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
await LoadAsync(user);
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (!ModelState.IsValid)
{
await LoadAsync(user);
return Page();
}
var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
if (Input.PhoneNumber != phoneNumber)
{
var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, Input.PhoneNumber);
if (!setPhoneResult.Succeeded)
{
StatusMessage = "Unexpected error when trying to set phone number.";
return RedirectToPage();
}
}
await _signInManager.RefreshSignInAsync(user);
StatusMessage = "Your profile has been updated";
return RedirectToPage();
}
}
}

View File

@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace WebApp.Areas.Identity.Pages.Account.Manage
{
public static class ManageNavPages
{
public static string Index => "Index";
public static string Email => "Email";
public static string ChangePassword => "ChangePassword";
public static string DownloadPersonalData => "DownloadPersonalData";
public static string DeletePersonalData => "DeletePersonalData";
public static string ExternalLogins => "ExternalLogins";
public static string PersonalData => "PersonalData";
public static string TwoFactorAuthentication => "TwoFactorAuthentication";
public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index);
public static string EmailNavClass(ViewContext viewContext) => PageNavClass(viewContext, Email);
public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword);
public static string DownloadPersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, DownloadPersonalData);
public static string DeletePersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, DeletePersonalData);
public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins);
public static string PersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, PersonalData);
public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication);
private static string PageNavClass(ViewContext viewContext, string page)
{
var activePage = viewContext.ViewData["ActivePage"] as string
?? System.IO.Path.GetFileNameWithoutExtension(viewContext.ActionDescriptor.DisplayName);
return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null;
}
}
}

View File

@@ -0,0 +1,27 @@
@page
@model PersonalDataModel
@{
ViewData["Title"] = "Personal Data";
ViewData["ActivePage"] = ManageNavPages.PersonalData;
}
<h4>@ViewData["Title"]</h4>
<div class="row">
<div class="col-md-6">
<p>Your account contains personal data that you have given us. This page allows you to download or delete that data.</p>
<p>
<strong>Deleting this data will permanently remove your account, and this cannot be recovered.</strong>
</p>
<form id="download-data" asp-page="DownloadPersonalData" method="post" class="form-group">
<button class="btn btn-primary" type="submit">Download</button>
</form>
<p>
<a id="delete" asp-page="DeletePersonalData" class="btn btn-secondary">Delete</a>
</p>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

View File

@@ -0,0 +1,34 @@
using System.Threading.Tasks;
using Data.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace WebApp.Areas.Identity.Pages.Account.Manage
{
public class PersonalDataModel : PageModel
{
private readonly UserManager<User> _userManager;
private readonly ILogger<PersonalDataModel> _logger;
public PersonalDataModel(
UserManager<User> userManager,
ILogger<PersonalDataModel> logger)
{
_userManager = userManager;
_logger = logger;
}
public async Task<IActionResult> OnGet()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
return Page();
}
}
}

View File

@@ -0,0 +1,35 @@
@page
@model SetPasswordModel
@{
ViewData["Title"] = "Set password";
ViewData["ActivePage"] = ManageNavPages.ChangePassword;
}
<h4>Set your password</h4>
<partial name="_StatusMessage" for="StatusMessage" />
<p class="text-info">
You do not have a local username/password for this site. Add a local
account so you can log in without an external login.
</p>
<div class="row">
<div class="col-md-6">
<form id="set-password-form" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.NewPassword"></label>
<input asp-for="Input.NewPassword" class="form-control" />
<span asp-validation-for="Input.NewPassword" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.ConfirmPassword"></label>
<input asp-for="Input.ConfirmPassword" class="form-control" />
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Set password</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

View File

@@ -0,0 +1,93 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Data.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace WebApp.Areas.Identity.Pages.Account.Manage
{
public class SetPasswordModel : PageModel
{
private readonly UserManager<User> _userManager;
private readonly SignInManager<User> _signInManager;
public SetPasswordModel(
UserManager<User> userManager,
SignInManager<User> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
[BindProperty]
public InputModel Input { get; set; }
[TempData]
public string StatusMessage { get; set; }
public class InputModel
{
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "New password")]
public string NewPassword { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm new password")]
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
public async Task<IActionResult> OnGetAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var hasPassword = await _userManager.HasPasswordAsync(user);
if (hasPassword)
{
return RedirectToPage("./ChangePassword");
}
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var addPasswordResult = await _userManager.AddPasswordAsync(user, Input.NewPassword);
if (!addPasswordResult.Succeeded)
{
foreach (var error in addPasswordResult.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
return Page();
}
await _signInManager.RefreshSignInAsync(user);
StatusMessage = "Your password has been set.";
return RedirectToPage();
}
}
}

View File

@@ -0,0 +1,29 @@
@{
if (ViewData.TryGetValue("ParentLayout", out var parentLayout))
{
Layout = (string)parentLayout;
}
else
{
Layout = "/Areas/Identity/Pages/_Layout.cshtml";
}
}
<h2>Manage your account</h2>
<div>
<h4>Change your account settings</h4>
<hr />
<div class="row">
<div class="col-md-3">
<partial name="_ManageNav" />
</div>
<div class="col-md-9">
@RenderBody()
</div>
</div>
</div>
@section Scripts {
@RenderSection("Scripts", required: false)
}

View File

@@ -0,0 +1,15 @@
@inject SignInManager<User> SignInManager
@{
var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any();
}
<ul class="nav nav-pills flex-column">
<li class="nav-item"><a class="nav-link @ManageNavPages.IndexNavClass(ViewContext)" id="profile" asp-page="./Index">Profile</a></li>
<li class="nav-item"><a class="nav-link @ManageNavPages.EmailNavClass(ViewContext)" id="email" asp-page="./Email">Email</a></li>
<li class="nav-item"><a class="nav-link @ManageNavPages.ChangePasswordNavClass(ViewContext)" id="change-password" asp-page="./ChangePassword">Password</a></li>
@if (hasExternalLogins)
{
<li id="external-logins" class="nav-item"><a id="external-login" class="nav-link @ManageNavPages.ExternalLoginsNavClass(ViewContext)" asp-page="./ExternalLogins">External logins</a></li>
}
<li class="nav-item"><a class="nav-link @ManageNavPages.TwoFactorAuthenticationNavClass(ViewContext)" id="two-factor" asp-page="./TwoFactorAuthentication">Two-factor authentication</a></li>
<li class="nav-item"><a class="nav-link @ManageNavPages.PersonalDataNavClass(ViewContext)" id="personal-data" asp-page="./PersonalData">Personal data</a></li>
</ul>

View File

@@ -0,0 +1,10 @@
@model string
@if (!String.IsNullOrEmpty(Model))
{
var statusMessageClass = Model.StartsWith("Error") ? "danger" : "success";
<div class="alert alert-@statusMessageClass alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
@Model
</div>
}

View File

@@ -0,0 +1 @@
@using WebApp.Areas.Identity.Pages.Account.Manage

View File

@@ -17,36 +17,6 @@
<input asp-for="Input.Email" class="form-control" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.FirstName"></label>
<input asp-for="Input.FirstName" class="form-control" />
<span asp-validation-for="Input.FirstName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.LastName"></label>
<input asp-for="Input.LastName" class="form-control" />
<span asp-validation-for="Input.LastName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.IsAdmin"></label>
<input asp-for="Input.IsAdmin" class="form-control" />
<span asp-validation-for="Input.IsAdmin" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.EGN"></label>
<input asp-for="Input.EGN" class="form-control" />
<span asp-validation-for="Input.EGN" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.PhoneNumber"></label>
<input asp-for="Input.PhoneNumber" class="form-control" />
<span asp-validation-for="Input.PhoneNumber" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.UserName"></label>
<input asp-for="Input.UserName" class="form-control" />
<span asp-validation-for="Input.UserName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password" class="form-control" />

View File

@@ -5,10 +5,9 @@ using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Data;
using Data.Entities;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Data.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
@@ -24,19 +23,19 @@ namespace WebApp.Areas.Identity.Pages.Account
private readonly SignInManager<User> _signInManager;
private readonly UserManager<User> _userManager;
private readonly ILogger<RegisterModel> _logger;
private readonly RentACarDbContext _rentACartDbContext;
private readonly IEmailSender _emailSender;
public RegisterModel(
UserManager<User> userManager,
SignInManager<User> signInManager,
ILogger<RegisterModel> logger,
RentACarDbContext rentACarDbContext)
{
IEmailSender emailSender)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
_rentACartDbContext = rentACarDbContext;
}
_emailSender = emailSender;
}
[BindProperty]
public InputModel Input { get; set; }
@@ -52,25 +51,6 @@ namespace WebApp.Areas.Identity.Pages.Account
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
public string UserName { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public bool IsAdmin { get; set; }
[Required]
[StringLength(10, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 10)]
public string EGN { get; set; }
[Required]
[DataType(DataType.PhoneNumber)]
[StringLength(10, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 10)]
public string PhoneNumber { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
@@ -95,23 +75,33 @@ namespace WebApp.Areas.Identity.Pages.Account
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
var user = new User { Id = Guid.NewGuid().ToString(), UserName = Input.UserName, Email = Input.Email, FirstName = Input.FirstName, LastName = Input.LastName, PhoneNumber = Input.PhoneNumber, PersonalNumber = Input.EGN};
var user = new User { UserName = Input.Email, Email = Input.Email };
var result = await _userManager.CreateAsync(user, Input.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
await _signInManager.SignInAsync(user, isPersistent: false);
if (Input.IsAdmin == true)
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = user.Id, code = code, returnUrl = returnUrl },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
if (_userManager.Options.SignIn.RequireConfirmedAccount)
{
await _userManager.AddToRoleAsync(user, "Admin");
return RedirectToPage("RegisterConfirmation", new { email = Input.Email, returnUrl = returnUrl });
}
else
{
await _userManager.AddToRoleAsync(user, "User");
await _signInManager.SignInAsync(user, isPersistent: false);
return LocalRedirect(returnUrl);
}
return LocalRedirect(returnUrl);
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);

View File

@@ -0,0 +1,37 @@
@page
@model ResetPasswordModel
@{
ViewData["Title"] = "Reset password";
}
<h1>@ViewData["Title"]</h1>
<h4>Reset your password.</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input asp-for="Input.Code" type="hidden" />
<div class="form-group">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" class="form-control" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password" class="form-control" />
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.ConfirmPassword"></label>
<input asp-for="Input.ConfirmPassword" class="form-control" />
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Reset</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

View File

@@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Data.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
namespace WebApp.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class ResetPasswordModel : PageModel
{
private readonly UserManager<User> _userManager;
public ResetPasswordModel(UserManager<User> userManager)
{
_userManager = userManager;
}
[BindProperty]
public InputModel Input { get; set; }
public class InputModel
{
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
public string Code { get; set; }
}
public IActionResult OnGet(string code = null)
{
if (code == null)
{
return BadRequest("A code must be supplied for password reset.");
}
else
{
Input = new InputModel
{
Code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code))
};
return Page();
}
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _userManager.FindByEmailAsync(Input.Email);
if (user == null)
{
// Don't reveal that the user does not exist
return RedirectToPage("./ResetPasswordConfirmation");
}
var result = await _userManager.ResetPasswordAsync(user, Input.Code, Input.Password);
if (result.Succeeded)
{
return RedirectToPage("./ResetPasswordConfirmation");
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
return Page();
}
}
}

View File

@@ -0,0 +1,10 @@
@model string
@if (!String.IsNullOrEmpty(Model))
{
var statusMessageClass = Model.StartsWith("Error") ? "danger" : "success";
<div class="alert alert-@statusMessageClass alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
@Model
</div>
}

View File

@@ -2,3 +2,4 @@
@using WebApp.Areas.Identity
@using WebApp.Areas.Identity.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@using Data.Entities

View File

@@ -1,3 +1,4 @@
@{

@{
Layout = "/Views/Shared/_Layout.cshtml";
}

View File

@@ -7,6 +7,8 @@ using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using Data;
using Data.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Authorization;
namespace WebApp.Controllers
{
@@ -44,6 +46,7 @@ namespace WebApp.Controllers
}
// GET: Cars/Create
[Authorize(Roles = "Admin")]
public IActionResult Create()
{
return View();

View File

@@ -29,8 +29,7 @@ namespace API
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<RentACarDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddIdentity<User, IdentityRole>(options =>
{
@@ -42,7 +41,10 @@ namespace API
options.User.RequireUniqueEmail = false;
})
.AddRoles<IdentityRole>()
.AddDefaultUI()
.AddEntityFrameworkStores<RentACarDbContext>();
services.AddControllersWithViews();
services.AddRazorPages();
}

View File

@@ -7,10 +7,10 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="5.0.13" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="5.0.12" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="5.0.12" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.12" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.12">
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="5.0.15" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="5.0.15" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.15" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.15">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>