Adding initial login code
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
namespace mediasrv.ApplicationCore.Constants;
|
||||
|
||||
public class AuthorizationConstants
|
||||
{
|
||||
public const string AUTH_KEY = "AuthKeyOfDoomThatMustBeAMinimumNumberOfBytes";
|
||||
|
||||
// TODO: Don't use this in production
|
||||
public const string DEFAULT_PASSWORD = "Pass@word1";
|
||||
|
||||
// TODO: Change this to an environment variable
|
||||
public const string JWT_SECRET_KEY = "SecretKeyOfDoomThatMustBeAMinimumNumberOfBytes";
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Http.Json;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using BlazorShared.Authorization;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BlazorAdmin;
|
||||
|
||||
public class CustomAuthStateProvider : AuthenticationStateProvider
|
||||
{
|
||||
// TODO: Get Default Cache Duration from Config
|
||||
private static readonly TimeSpan UserCacheRefreshInterval = TimeSpan.FromSeconds(60);
|
||||
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ILogger<CustomAuthStateProvider> _logger;
|
||||
|
||||
private DateTimeOffset _userLastCheck = DateTimeOffset.FromUnixTimeSeconds(0);
|
||||
private ClaimsPrincipal _cachedUser = new ClaimsPrincipal(new ClaimsIdentity());
|
||||
|
||||
public CustomAuthStateProvider(HttpClient httpClient,
|
||||
ILogger<CustomAuthStateProvider> logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
|
||||
{
|
||||
return new AuthenticationState(await GetUser(useCache: true));
|
||||
}
|
||||
|
||||
private async ValueTask<ClaimsPrincipal> GetUser(bool useCache = false)
|
||||
{
|
||||
var now = DateTimeOffset.Now;
|
||||
if (useCache && now < _userLastCheck + UserCacheRefreshInterval)
|
||||
{
|
||||
return _cachedUser;
|
||||
}
|
||||
|
||||
_cachedUser = await FetchUser();
|
||||
_userLastCheck = now;
|
||||
|
||||
return _cachedUser;
|
||||
}
|
||||
|
||||
private async Task<ClaimsPrincipal> FetchUser()
|
||||
{
|
||||
UserInfo user = null;
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Fetching user details from web api.");
|
||||
user = await _httpClient.GetFromJsonAsync<UserInfo>("User");
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
_logger.LogWarning(exc, "Fetching user failed.");
|
||||
}
|
||||
|
||||
if (user == null || !user.IsAuthenticated)
|
||||
{
|
||||
return new ClaimsPrincipal(new ClaimsIdentity());
|
||||
}
|
||||
|
||||
var identity = new ClaimsIdentity(
|
||||
nameof(CustomAuthStateProvider),
|
||||
user.NameClaimType,
|
||||
user.RoleClaimType);
|
||||
|
||||
if (user.Claims != null)
|
||||
{
|
||||
foreach (var claim in user.Claims)
|
||||
{
|
||||
identity.AddClaim(new Claim(claim.Type, claim.Value));
|
||||
}
|
||||
}
|
||||
|
||||
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", user.Token);
|
||||
|
||||
return new ClaimsPrincipal(identity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace BlazorAdmin.Helpers;
|
||||
|
||||
public class BlazorComponent : ComponentBase
|
||||
{
|
||||
private readonly RefreshBroadcast _refresh = RefreshBroadcast.Instance;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_refresh.RefreshRequested += DoRefresh;
|
||||
base.OnInitialized();
|
||||
}
|
||||
|
||||
public void CallRequestRefresh()
|
||||
{
|
||||
_refresh.CallRequestRefresh();
|
||||
}
|
||||
|
||||
private void DoRefresh()
|
||||
{
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace BlazorAdmin.Helpers;
|
||||
|
||||
public class BlazorLayoutComponent : LayoutComponentBase
|
||||
{
|
||||
private readonly RefreshBroadcast _refresh = RefreshBroadcast.Instance;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_refresh.RefreshRequested += DoRefresh;
|
||||
base.OnInitialized();
|
||||
}
|
||||
|
||||
public void CallRequestRefresh()
|
||||
{
|
||||
_refresh.CallRequestRefresh();
|
||||
}
|
||||
|
||||
private void DoRefresh()
|
||||
{
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
|
||||
namespace BlazorAdmin.Helpers;
|
||||
|
||||
internal sealed class RefreshBroadcast
|
||||
{
|
||||
private static readonly Lazy<RefreshBroadcast>
|
||||
Lazy =
|
||||
new Lazy<RefreshBroadcast>
|
||||
(() => new RefreshBroadcast());
|
||||
|
||||
public static RefreshBroadcast Instance => Lazy.Value;
|
||||
|
||||
private RefreshBroadcast()
|
||||
{
|
||||
}
|
||||
|
||||
public event Action RefreshRequested;
|
||||
public void CallRequestRefresh()
|
||||
{
|
||||
RefreshRequested?.Invoke();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using BlazorAdmin.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace BlazorAdmin.Helpers;
|
||||
|
||||
public class ToastComponent : ComponentBase, IDisposable
|
||||
{
|
||||
[Inject]
|
||||
ToastService ToastService
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
protected string Heading
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
protected string Message
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
protected bool IsVisible
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
protected string BackgroundCssClass
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
protected string IconCssClass
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
ToastService.OnShow += ShowToast;
|
||||
ToastService.OnHide += HideToast;
|
||||
}
|
||||
private void ShowToast(string message, ToastLevel level)
|
||||
{
|
||||
BuildToastSettings(level, message);
|
||||
IsVisible = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
private void HideToast()
|
||||
{
|
||||
IsVisible = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
private void BuildToastSettings(ToastLevel level, string message)
|
||||
{
|
||||
switch (level)
|
||||
{
|
||||
case ToastLevel.Info:
|
||||
BackgroundCssClass = "bg-info";
|
||||
IconCssClass = "info";
|
||||
Heading = "Info";
|
||||
break;
|
||||
case ToastLevel.Success:
|
||||
BackgroundCssClass = "bg-success";
|
||||
IconCssClass = "check";
|
||||
Heading = "Success";
|
||||
break;
|
||||
case ToastLevel.Warning:
|
||||
BackgroundCssClass = "bg-warning";
|
||||
IconCssClass = "exclamation";
|
||||
Heading = "Warning";
|
||||
break;
|
||||
case ToastLevel.Error:
|
||||
BackgroundCssClass = "bg-danger";
|
||||
IconCssClass = "times";
|
||||
Heading = "Error";
|
||||
break;
|
||||
}
|
||||
Message = message;
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
ToastService.OnShow -= ShowToast;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
@page "/logout"
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject HttpClient HttpClient
|
||||
@inherits BlazorAdmin.Helpers.BlazorComponent
|
||||
|
||||
@code {
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await HttpClient.PostAsync("User/Logout", null);
|
||||
await new Route(JSRuntime).RouteOutside("/Identity/Account/Login");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace BlazorShared.Authorization;
|
||||
|
||||
public class ClaimValue
|
||||
{
|
||||
public ClaimValue()
|
||||
{
|
||||
}
|
||||
|
||||
public ClaimValue(string type, string value)
|
||||
{
|
||||
Type = type;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public string Type { get; set; }
|
||||
public string Value { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace BlazorShared.Authorization;
|
||||
|
||||
public static class Constants
|
||||
{
|
||||
public static class Roles
|
||||
{
|
||||
public const string ADMINISTRATORS = "Administrators";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BlazorShared.Authorization;
|
||||
|
||||
public class UserInfo
|
||||
{
|
||||
public static readonly UserInfo Anonymous = new UserInfo();
|
||||
public bool IsAuthenticated { get; set; }
|
||||
public string NameClaimType { get; set; }
|
||||
public string RoleClaimType { get; set; }
|
||||
public string Token { get; set; }
|
||||
public IEnumerable<ClaimValue> Claims { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
|
||||
namespace mediasrv.Infrastructure.Identity;
|
||||
|
||||
public class AppIdentityDbContext : IdentityDbContext<ApplicationUser>
|
||||
{
|
||||
public AppIdentityDbContext(DbContextOptions<AppIdentityDbContext> options)
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
base.OnModelCreating(builder);
|
||||
// Customize the ASP.NET Identity model and override the defaults if needed.
|
||||
// For example, you can rename the ASP.NET Identity table names and more.
|
||||
// Add your customizations after calling base.OnModelCreating(builder);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using mediasrv.ApplicationCore.Constants;
|
||||
|
||||
namespace mediasrv.Infrastructure.Identity;
|
||||
|
||||
public class AppIdentityDbContextSeed
|
||||
{
|
||||
public static async Task SeedAsync(AppIdentityDbContext identityDbContext, UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager)
|
||||
{
|
||||
|
||||
if (identityDbContext.Database.IsSqlServer())
|
||||
{
|
||||
identityDbContext.Database.Migrate();
|
||||
}
|
||||
|
||||
await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS));
|
||||
|
||||
var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" };
|
||||
await userManager.CreateAsync(defaultUser, AuthorizationConstants.DEFAULT_PASSWORD);
|
||||
|
||||
string adminUserName = "admin@microsoft.com";
|
||||
var adminUser = new ApplicationUser { UserName = adminUserName, Email = adminUserName };
|
||||
await userManager.CreateAsync(adminUser, AuthorizationConstants.DEFAULT_PASSWORD);
|
||||
adminUser = await userManager.FindByNameAsync(adminUserName);
|
||||
if (adminUser != null)
|
||||
{
|
||||
await userManager.AddToRoleAsync(adminUser, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace mediasrv.Infrastructure.Identity;
|
||||
|
||||
public class ApplicationUser : IdentityUser
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using mediasrv.ApplicationCore.Constants;
|
||||
using mediasrv.ApplicationCore.Interfaces;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
|
||||
namespace Microsoft.eShopWeb.Infrastructure.Identity;
|
||||
|
||||
public class IdentityTokenClaimService : ITokenClaimsService
|
||||
{
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
|
||||
public IdentityTokenClaimService(UserManager<ApplicationUser> userManager)
|
||||
{
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
public async Task<string> GetTokenAsync(string userName)
|
||||
{
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY);
|
||||
var user = await _userManager.FindByNameAsync(userName);
|
||||
if (user == null) throw new UserNotFoundException(userName);
|
||||
var roles = await _userManager.GetRolesAsync(user);
|
||||
var claims = new List<Claim> { new Claim(ClaimTypes.Name, userName) };
|
||||
|
||||
foreach (var role in roles)
|
||||
{
|
||||
claims.Add(new Claim(ClaimTypes.Role, role));
|
||||
}
|
||||
|
||||
var tokenDescriptor = new SecurityTokenDescriptor
|
||||
{
|
||||
Subject = new ClaimsIdentity(claims.ToArray()),
|
||||
Expires = DateTime.UtcNow.AddDays(7),
|
||||
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
|
||||
};
|
||||
var token = tokenHandler.CreateToken(tokenDescriptor);
|
||||
return tokenHandler.WriteToken(token);
|
||||
}
|
||||
}
|
||||
Generated
+273
@@ -0,0 +1,273 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Microsoft.eShopWeb.Infrastructure.Identity;
|
||||
|
||||
namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppIdentityDbContext))]
|
||||
[Migration("20201202111612_InitialIdentityModel")]
|
||||
partial class InitialIdentityModel
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.UseIdentityColumns()
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128)
|
||||
.HasAnnotation("ProductVersion", "5.0.0");
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("RoleNameIndex")
|
||||
.HasFilter("[NormalizedName] IS NOT NULL");
|
||||
|
||||
b.ToTable("AspNetRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.UseIdentityColumn();
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.UseIdentityColumn();
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", 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.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
+218
@@ -0,0 +1,218 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations;
|
||||
|
||||
public partial class InitialIdentityModel : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetRoles",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "nvarchar(450)", nullable: false),
|
||||
Name = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
||||
NormalizedName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
||||
ConcurrencyStamp = table.Column<string>(type: "nvarchar(max)", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AspNetRoles", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetUsers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "nvarchar(450)", nullable: false),
|
||||
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),
|
||||
NormalizedEmail = table.Column<string>(type: "nvarchar(256)", maxLength: 256, 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_AspNetUsers", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetRoleClaims",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
RoleId = table.Column<string>(type: "nvarchar(450)", nullable: false),
|
||||
ClaimType = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
ClaimValue = table.Column<string>(type: "nvarchar(max)", 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<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
UserId = table.Column<string>(type: "nvarchar(450)", nullable: false),
|
||||
ClaimType = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
ClaimValue = table.Column<string>(type: "nvarchar(max)", 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<string>(type: "nvarchar(450)", nullable: false),
|
||||
ProviderKey = table.Column<string>(type: "nvarchar(450)", nullable: false),
|
||||
ProviderDisplayName = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
UserId = table.Column<string>(type: "nvarchar(450)", 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<string>(type: "nvarchar(450)", nullable: false),
|
||||
RoleId = table.Column<string>(type: "nvarchar(450)", 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<string>(type: "nvarchar(450)", nullable: false),
|
||||
LoginProvider = table.Column<string>(type: "nvarchar(450)", nullable: false),
|
||||
Name = table.Column<string>(type: "nvarchar(450)", nullable: false),
|
||||
Value = table.Column<string>(type: "nvarchar(max)", 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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Microsoft.eShopWeb.Infrastructure.Identity;
|
||||
|
||||
namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppIdentityDbContext))]
|
||||
partial class AppIdentityDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.UseIdentityColumns()
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128)
|
||||
.HasAnnotation("ProductVersion", "5.0.0");
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("RoleNameIndex")
|
||||
.HasFilter("[NormalizedName] IS NOT NULL");
|
||||
|
||||
b.ToTable("AspNetRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.UseIdentityColumn();
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.UseIdentityColumn();
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", 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.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace mediasrv.Infrastructure.Identity;
|
||||
|
||||
public class UserNotFoundException : Exception
|
||||
{
|
||||
public UserNotFoundException(string userName) : base($"No user found with username: {userName}")
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace mediasrv.PublicApi.AuthEndpoints;
|
||||
|
||||
public class AuthenticateRequest : BaseRequest
|
||||
{
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace mediasrv.PublicApi.AuthEndpoints;
|
||||
|
||||
public class AuthenticateResponse : BaseResponse
|
||||
{
|
||||
public AuthenticateResponse(Guid correlationId) : base(correlationId)
|
||||
{
|
||||
}
|
||||
|
||||
public AuthenticateResponse()
|
||||
{
|
||||
}
|
||||
public bool Result { get; set; } = false;
|
||||
public string Token { get; set; } = string.Empty;
|
||||
public string Username { get; set; } = string.Empty;
|
||||
public bool IsLockedOut { get; set; } = false;
|
||||
public bool IsNotAllowed { get; set; } = false;
|
||||
public bool RequiresTwoFactor { get; set; } = false;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace mediasrv.PublicApi.AuthEndpoints;
|
||||
|
||||
public class ClaimValue
|
||||
{
|
||||
public ClaimValue()
|
||||
{
|
||||
}
|
||||
|
||||
public ClaimValue(string type, string value)
|
||||
{
|
||||
Type = type;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public string Type { get; set; } = string.Empty;
|
||||
public string Value { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace mediasrv.PublicApi.AuthEndpoints;
|
||||
|
||||
public class UserInfo
|
||||
{
|
||||
public static readonly UserInfo Anonymous = new UserInfo();
|
||||
public bool IsAuthenticated { get; set; }
|
||||
public string NameClaimType { get; set; } = string.Empty;
|
||||
public string RoleClaimType { get; set; } = string.Empty;
|
||||
public IEnumerable<ClaimValue> Claims { get; set; } = new List<ClaimValue>();
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ardalis.ApiEndpoints;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||
using Microsoft.eShopWeb.Infrastructure.Identity;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
|
||||
namespace mediasrv.PublicApi.AuthEndpoints;
|
||||
|
||||
/// <summary>
|
||||
/// Authenticates a user
|
||||
/// </summary>
|
||||
public class AuthenticateEndpoint : EndpointBaseAsync
|
||||
.WithRequest<AuthenticateRequest>
|
||||
.WithActionResult<AuthenticateResponse>
|
||||
{
|
||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||
private readonly ITokenClaimsService _tokenClaimsService;
|
||||
|
||||
public AuthenticateEndpoint(SignInManager<ApplicationUser> signInManager,
|
||||
ITokenClaimsService tokenClaimsService)
|
||||
{
|
||||
_signInManager = signInManager;
|
||||
_tokenClaimsService = tokenClaimsService;
|
||||
}
|
||||
|
||||
[HttpPost("api/authenticate")]
|
||||
[SwaggerOperation(
|
||||
Summary = "Authenticates a user",
|
||||
Description = "Authenticates a user",
|
||||
OperationId = "auth.authenticate",
|
||||
Tags = new[] { "AuthEndpoints" })
|
||||
]
|
||||
public override async Task<ActionResult<AuthenticateResponse>> HandleAsync(AuthenticateRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var response = new AuthenticateResponse(request.CorrelationId());
|
||||
|
||||
// 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.Email, Input.Password, Input.RememberMe, lockoutOnFailure: true);
|
||||
var result = await _signInManager.PasswordSignInAsync(request.Username, request.Password, false, true);
|
||||
|
||||
response.Result = result.Succeeded;
|
||||
response.IsLockedOut = result.IsLockedOut;
|
||||
response.IsNotAllowed = result.IsNotAllowed;
|
||||
response.RequiresTwoFactor = result.RequiresTwoFactor;
|
||||
response.Username = request.Username;
|
||||
|
||||
if (result.Succeeded)
|
||||
{
|
||||
response.Token = await _tokenClaimsService.GetTokenAsync(request.Username);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using BlazorShared.Models;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Exceptions;
|
||||
|
||||
namespace mediasrv.PublicApi.Middleware;
|
||||
|
||||
public class ExceptionMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
public ExceptionMiddleware(RequestDelegate next)
|
||||
{
|
||||
_next = next;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext httpContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _next(httpContext);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await HandleExceptionAsync(httpContext, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleExceptionAsync(HttpContext context, Exception exception)
|
||||
{
|
||||
context.Response.ContentType = "application/json";
|
||||
|
||||
if (exception is DuplicateException duplicationException)
|
||||
{
|
||||
context.Response.StatusCode = (int)HttpStatusCode.Conflict;
|
||||
await context.Response.WriteAsync(new ErrorDetails()
|
||||
{
|
||||
StatusCode = context.Response.StatusCode,
|
||||
Message = duplicationException.Message
|
||||
}.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
|
||||
await context.Response.WriteAsync(new ErrorDetails()
|
||||
{
|
||||
StatusCode = context.Response.StatusCode,
|
||||
Message = exception.Message
|
||||
}.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
|
||||
namespace BlazorAdmin.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// This is needed until 5.0 ships with native support
|
||||
/// https://www.pragimtech.com/blog/blazor/inputselect-does-not-support-system.int32/
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
public class CustomInputSelect<TValue> : InputSelect<TValue>
|
||||
{
|
||||
protected override bool TryParseValueFromString(string value, out TValue result,
|
||||
out string validationErrorMessage)
|
||||
{
|
||||
if (typeof(TValue) == typeof(int))
|
||||
{
|
||||
if (int.TryParse(value, out var resultInt))
|
||||
{
|
||||
result = (TValue)(object)resultInt;
|
||||
validationErrorMessage = null;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = default;
|
||||
validationErrorMessage =
|
||||
$"The selected value {value} is not a valid number.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return base.TryParseValueFromString(value, out result,
|
||||
out validationErrorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
@inject AuthenticationStateProvider AuthStateProvider
|
||||
@inject IJSRuntime JSRuntime
|
||||
|
||||
@inherits BlazorAdmin.Helpers.BlazorLayoutComponent
|
||||
|
||||
|
||||
<AuthorizeView Roles=@BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS>
|
||||
<div class="sidebar">
|
||||
<NavMenu />
|
||||
</div>
|
||||
</AuthorizeView>
|
||||
|
||||
<div class="main">
|
||||
|
||||
<div class="top-row px-4">
|
||||
<a href="https://github.com/dotnet-architecture/eShopOnWeb" target="_blank" class="ml-md-auto">About eShopOnWeb</a>
|
||||
</div>
|
||||
|
||||
<div class="content px-4">
|
||||
<Toast></Toast>
|
||||
@Body
|
||||
</div>
|
||||
</div>
|
||||
@code
|
||||
{
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
|
||||
|
||||
if (authState.User == null)
|
||||
{
|
||||
await new Route(JSRuntime).RouteOutside("/Identity/Account/Login");
|
||||
}
|
||||
CallRequestRefresh();
|
||||
}
|
||||
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
@inherits BlazorAdmin.Helpers.BlazorComponent
|
||||
<div class="top-row pl-4 navbar navbar-dark">
|
||||
<a class="navbar-brand" href="">eShopOnWeb Admin</a>
|
||||
<button class="navbar-toggler" @onclick="ToggleNavMenu">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="admin" Match="NavLinkMatch.All">
|
||||
<span class="oi oi-home" aria-hidden="true"></span> Home
|
||||
</NavLink>
|
||||
</li>
|
||||
<AuthorizeView>
|
||||
<Authorized>
|
||||
<li class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="manage/my-account" Match="NavLinkMatch.All">
|
||||
<span class="oi oi-person" aria-hidden="true"></span> @context.User.Identity.Name
|
||||
|
||||
</NavLink>
|
||||
</li>
|
||||
<li class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="logout">
|
||||
<span class="oi oi-account-logout" aria-hidden="true"></span> Logout
|
||||
</NavLink>
|
||||
</li>
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
|
||||
private bool collapseNavMenu = true;
|
||||
|
||||
private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
|
||||
|
||||
private void ToggleNavMenu()
|
||||
{
|
||||
collapseNavMenu = !collapseNavMenu;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
@using System.Web;
|
||||
|
||||
@inject NavigationManager Navigation
|
||||
@inject IJSRuntime JsRuntime
|
||||
|
||||
@code {
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
var returnUrl = HttpUtility.UrlEncode($"/{Uri.EscapeDataString(Navigation.ToBaseRelativePath(Navigation.Uri))}");
|
||||
JsRuntime.InvokeVoidAsync("location.replace", $"Identity/Account/Login?returnUrl={returnUrl}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
@inherits BlazorAdmin.Helpers.BlazorComponent
|
||||
|
||||
@namespace BlazorAdmin.Shared
|
||||
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
@inherits BlazorAdmin.Helpers.ToastComponent
|
||||
|
||||
@namespace BlazorAdmin.Shared
|
||||
|
||||
<div class="toast @(IsVisible ? "toast-visible" : null) @BackgroundCssClass">
|
||||
<div class="toast-icon">
|
||||
<i class="fa fa-@IconCssClass" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div class="toast-body">
|
||||
<h5>@Heading</h5>
|
||||
<p>@Message</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,14 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
||||
[assembly: HostingStartup(typeof(Microsoft.eShopWeb.Web.Areas.Identity.IdentityHostingStartup))]
|
||||
namespace Microsoft.eShopWeb.Web.Areas.Identity;
|
||||
|
||||
public class IdentityHostingStartup : IHostingStartup
|
||||
{
|
||||
public void Configure(IWebHostBuilder builder)
|
||||
{
|
||||
builder.ConfigureServices((context, services) =>
|
||||
{
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
@page
|
||||
@model ConfirmEmailModel
|
||||
@{
|
||||
ViewData["Title"] = "Confirm email";
|
||||
}
|
||||
|
||||
<h2>@ViewData["Title"]</h2>
|
||||
<div>
|
||||
<p>
|
||||
Thank you for confirming your email.
|
||||
</p>
|
||||
</div>
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using mediasrv.Infrastructure.Identity;
|
||||
|
||||
namespace mediasrv.Web.Areas.Identity.Pages.Account;
|
||||
|
||||
[AllowAnonymous]
|
||||
public class ConfirmEmailModel : PageModel
|
||||
{
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
|
||||
public ConfirmEmailModel(UserManager<ApplicationUser> userManager)
|
||||
{
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnGetAsync(string userId, string code)
|
||||
{
|
||||
if (userId == null || code == null)
|
||||
{
|
||||
return RedirectToPage("/Index");
|
||||
}
|
||||
|
||||
var user = await _userManager.FindByIdAsync(userId);
|
||||
if (user == null)
|
||||
{
|
||||
return NotFound($"Unable to load user with ID '{userId}'.");
|
||||
}
|
||||
|
||||
var result = await _userManager.ConfirmEmailAsync(user, code);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
throw new InvalidOperationException($"Error confirming email for user with ID '{userId}':");
|
||||
}
|
||||
|
||||
return Page();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
@page
|
||||
@model LoginModel
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Log in";
|
||||
}
|
||||
|
||||
|
||||
<div class="container account-login-container">
|
||||
<h2>@ViewData["Title"]</h2>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<section>
|
||||
<form method="post">
|
||||
<hr />
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<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">
|
||||
<div class="checkbox">
|
||||
<label asp-for="Input.RememberMe">
|
||||
<input asp-for="Input.RememberMe" />
|
||||
@Html.DisplayNameFor(m => m.Input.RememberMe)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-default">Log in</button>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<p>
|
||||
<a asp-page="./ForgotPassword">Forgot your password?</a>
|
||||
</p>
|
||||
<p>
|
||||
<a asp-page="./Register" asp-route-returnUrl="@Model.ReturnUrl">Register as a new user</a>
|
||||
</p>
|
||||
</div>
|
||||
<p>
|
||||
Note that for demo purposes you don't need to register and can login with these credentials:
|
||||
</p>
|
||||
<p>
|
||||
User: <b>demouser@microsoft.com</b> OR <b>admin@microsoft.com</b>
|
||||
</p>
|
||||
<p>
|
||||
Password: <b>Pass@word1</b>
|
||||
</p>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<partial name="_ValidationScriptsPartial" />
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Ardalis.GuardClauses;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using mediasrv.ApplicationCore.Interfaces;
|
||||
using mediasrv.Infrastructure.Identity;
|
||||
|
||||
namespace media.Web.Areas.Identity.Pages.Account;
|
||||
|
||||
[AllowAnonymous]
|
||||
public class LoginModel : PageModel
|
||||
{
|
||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||
private readonly ILogger<LoginModel> _logger;
|
||||
private readonly IBasketService _basketService;
|
||||
|
||||
public LoginModel(SignInManager<ApplicationUser> signInManager, ILogger<LoginModel> logger, IBasketService basketService)
|
||||
{
|
||||
_signInManager = signInManager;
|
||||
_logger = logger;
|
||||
_basketService = basketService;
|
||||
}
|
||||
|
||||
[BindProperty]
|
||||
public required InputModel Input { get; set; }
|
||||
|
||||
public IList<AuthenticationScheme>? ExternalLogins { get; set; }
|
||||
|
||||
public string? ReturnUrl { get; set; }
|
||||
|
||||
[TempData]
|
||||
public string? ErrorMessage { get; set; }
|
||||
|
||||
public class InputModel
|
||||
{
|
||||
[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; }
|
||||
}
|
||||
|
||||
public async Task OnGetAsync(string? returnUrl = null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ErrorMessage))
|
||||
{
|
||||
ModelState.AddModelError(string.Empty, ErrorMessage);
|
||||
}
|
||||
|
||||
returnUrl = returnUrl ?? Url.Content("~/");
|
||||
|
||||
// Clear the existing external cookie to ensure a clean login process
|
||||
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
|
||||
|
||||
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
|
||||
|
||||
ReturnUrl = returnUrl;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAsync(string? returnUrl = null)
|
||||
{
|
||||
returnUrl = returnUrl ?? Url.Content("~/");
|
||||
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
// 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.Email, Input.Password, Input.RememberMe, lockoutOnFailure: true);
|
||||
var result = await _signInManager.PasswordSignInAsync(Input!.Email!, Input!.Password!,
|
||||
false, true);
|
||||
|
||||
if (result.Succeeded)
|
||||
{
|
||||
_logger.LogInformation("User logged in.");
|
||||
await TransferAnonymousBasketToUserAsync(Input?.Email);
|
||||
return LocalRedirect(returnUrl);
|
||||
}
|
||||
if (result.RequiresTwoFactor)
|
||||
{
|
||||
return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input?.RememberMe });
|
||||
}
|
||||
if (result.IsLockedOut)
|
||||
{
|
||||
_logger.LogWarning("User account locked out.");
|
||||
return RedirectToPage("./Lockout");
|
||||
}
|
||||
else
|
||||
{
|
||||
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
|
||||
return Page();
|
||||
}
|
||||
}
|
||||
|
||||
// If we got this far, something failed, redisplay form
|
||||
return Page();
|
||||
}
|
||||
|
||||
private async Task TransferAnonymousBasketToUserAsync(string? userName)
|
||||
{
|
||||
if (Request.Cookies.ContainsKey(Constants.BASKET_COOKIENAME))
|
||||
{
|
||||
var anonymousId = Request.Cookies[Constants.BASKET_COOKIENAME];
|
||||
if (Guid.TryParse(anonymousId, out var _))
|
||||
{
|
||||
Guard.Against.NullOrEmpty(userName, nameof(userName));
|
||||
await _basketService.TransferBasketAsync(anonymousId, userName);
|
||||
}
|
||||
Response.Cookies.Delete(Constants.BASKET_COOKIENAME);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
@page
|
||||
@model LogoutModel
|
||||
@{
|
||||
ViewData["Title"] = "Log out";
|
||||
}
|
||||
|
||||
<header>
|
||||
<h1>@ViewData["Title"]</h1>
|
||||
<p>You have successfully logged out of the application.</p>
|
||||
</header>
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.eShopWeb.Infrastructure.Identity;
|
||||
using Microsoft.eShopWeb.Web.Configuration;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account;
|
||||
|
||||
//TODO : replace IMemoryCache by distributed cache if you are in multi-host scenario
|
||||
public class LogoutModel : PageModel
|
||||
{
|
||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||
private readonly ILogger<LogoutModel> _logger;
|
||||
private readonly IMemoryCache _cache;
|
||||
|
||||
public LogoutModel(SignInManager<ApplicationUser> signInManager, ILogger<LogoutModel> logger, IMemoryCache cache)
|
||||
{
|
||||
_signInManager = signInManager;
|
||||
_logger = logger;
|
||||
_cache = cache;
|
||||
}
|
||||
|
||||
public void OnGet()
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPost(string? returnUrl = null)
|
||||
{
|
||||
await _signInManager.SignOutAsync();
|
||||
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
var userId = _signInManager.Context.User.Claims.First(c => c.Type == ClaimTypes.Name);
|
||||
var identityKey = _signInManager.Context.Request.Cookies[ConfigureCookieSettings.IdentifierCookieName];
|
||||
_cache.Set($"{userId.Value}:{identityKey}", identityKey, new MemoryCacheEntryOptions
|
||||
{
|
||||
AbsoluteExpiration = DateTime.Now.AddMinutes(ConfigureCookieSettings.ValidityMinutesPeriod)
|
||||
});
|
||||
|
||||
_logger.LogInformation("User logged out.");
|
||||
if (returnUrl != null)
|
||||
{
|
||||
return LocalRedirect(returnUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
return RedirectToPage("/Index");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
@page
|
||||
@model RegisterModel
|
||||
@{
|
||||
ViewData["Title"] = "Register";
|
||||
}
|
||||
|
||||
<div class="container">
|
||||
|
||||
<h2>@ViewData["Title"]</h2>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<form asp-route-returnUrl="@Model.ReturnUrl" method="post">
|
||||
<h4>Create a new account.</h4>
|
||||
<hr />
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div class="form-group">
|
||||
<label asp-for="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-default">Register</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<partial name="_ValidationScriptsPartial" />
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
using Ardalis.GuardClauses;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Identity.UI.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.eShopWeb.Infrastructure.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account;
|
||||
|
||||
[AllowAnonymous]
|
||||
public class RegisterModel : PageModel
|
||||
{
|
||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly ILogger<RegisterModel> _logger;
|
||||
private readonly IEmailSender _emailSender;
|
||||
|
||||
public RegisterModel(
|
||||
UserManager<ApplicationUser> userManager,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
ILogger<RegisterModel> logger,
|
||||
IEmailSender emailSender)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_signInManager = signInManager;
|
||||
_logger = logger;
|
||||
_emailSender = emailSender;
|
||||
}
|
||||
|
||||
[BindProperty]
|
||||
public required InputModel Input { get; set; }
|
||||
|
||||
public string? ReturnUrl { get; set; }
|
||||
|
||||
public class InputModel
|
||||
{
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
[Display(Name = "Email")]
|
||||
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)]
|
||||
[Display(Name = "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 void OnGet(string? returnUrl = null)
|
||||
{
|
||||
ReturnUrl = returnUrl;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAsync(string? returnUrl = null)
|
||||
{
|
||||
returnUrl = returnUrl ?? Url.Content("~/");
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
var user = new ApplicationUser { 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.");
|
||||
|
||||
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
||||
var callbackUrl = Url.Page(
|
||||
"/Account/ConfirmEmail",
|
||||
pageHandler: null,
|
||||
values: new { userId = user.Id, code = code },
|
||||
protocol: Request.Scheme);
|
||||
|
||||
Guard.Against.Null(callbackUrl, nameof(callbackUrl));
|
||||
await _emailSender.SendEmailAsync(Input!.Email!, "Confirm your email",
|
||||
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
|
||||
|
||||
await _signInManager.SignInAsync(user, isPersistent: false);
|
||||
return LocalRedirect(returnUrl);
|
||||
}
|
||||
foreach (var error in result.Errors)
|
||||
{
|
||||
ModelState.AddModelError(string.Empty, error.Description);
|
||||
}
|
||||
}
|
||||
|
||||
// If we got this far, something failed, redisplay form
|
||||
return Page();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
@using mediasrv.Web.Areas.Identity.Pages.Account
|
||||
@@ -0,0 +1,18 @@
|
||||
<environment include="Development,Docker">
|
||||
<script src="~/Identity/lib/jquery-validation/dist/jquery.validate.js"></script>
|
||||
<script src="~/Identity/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
|
||||
</environment>
|
||||
<environment exclude="Development,Docker">
|
||||
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.17.0/jquery.validate.min.js"
|
||||
asp-fallback-src="~/Identity/lib/jquery-validation/dist/jquery.validate.min.js"
|
||||
asp-fallback-test="window.jQuery && window.jQuery.validator"
|
||||
crossorigin="anonymous"
|
||||
integrity="sha384-rZfj/ogBloos6wzLGpPkkOr/gpkBNLZ6b6yLy4o+ok+t/SAKlL5mvXLr0OXNi1Hp">
|
||||
</script>
|
||||
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.9/jquery.validate.unobtrusive.min.js"
|
||||
asp-fallback-src="~/Identity/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
|
||||
asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive"
|
||||
crossorigin="anonymous"
|
||||
integrity="sha384-ifv0TYDWxBHzvAk2Z0n8R434FL1Rlv/Av18DXE43N/1rvHyOG4izKst0f2iSLdds">
|
||||
</script>
|
||||
</environment>
|
||||
@@ -0,0 +1,5 @@
|
||||
@using Microsoft.AspNetCore.Identity
|
||||
@using Microsoft.eShopWeb.Web.Areas.Identity
|
||||
@using Microsoft.eShopWeb.Infrastructure.Identity
|
||||
@namespace Microsoft.eShopWeb.Web.Areas.Identity.Pages
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@@ -0,0 +1,3 @@
|
||||
@{
|
||||
Layout = "/Views/Shared/_Layout.cshtml";
|
||||
}
|
||||
Reference in New Issue
Block a user