From a33656e7f5786c18cb8d3cf307bdeb0ae523a59c Mon Sep 17 00:00:00 2001 From: Tobias Klika Date: Tue, 3 Nov 2020 16:07:35 +0100 Subject: [PATCH] security stuff, oh my... --- zero.Core/Api/AuthenticationApi.cs | 36 +++- zero.Core/Constants.cs | 3 +- zero.Core/Extensions/HttpContextExtensions.cs | 41 ----- zero.Core/Identity/Defaults.cs | 16 +- zero.Core/Identity/UserIdentity.cs | 53 ++++++ zero.Core/Identity/ZeroIdentityExtensions.cs | 75 ++++++++ .../Security/ConfigureZeroCookieOptions.cs | 50 ++++++ .../ZeroAuthorizeAttribute.cs | 0 .../Security/ZeroClaimsPrinicipalFactory.cs | 94 ++++++++++ .../ZeroCookieAuthenticationEvents.cs | 166 ++++++++++++++++++ zero.Core/Security/ZeroCookieManager.cs | 37 ++++ zero.Core/zero.Core.csproj | 1 + zero.Web/ZeroBuilder.cs | 49 +----- 13 files changed, 523 insertions(+), 98 deletions(-) delete mode 100644 zero.Core/Extensions/HttpContextExtensions.cs create mode 100644 zero.Core/Identity/UserIdentity.cs create mode 100644 zero.Core/Identity/ZeroIdentityExtensions.cs create mode 100644 zero.Core/Security/ConfigureZeroCookieOptions.cs rename zero.Core/{Identity => Security}/ZeroAuthorizeAttribute.cs (100%) create mode 100644 zero.Core/Security/ZeroClaimsPrinicipalFactory.cs create mode 100644 zero.Core/Security/ZeroCookieAuthenticationEvents.cs create mode 100644 zero.Core/Security/ZeroCookieManager.cs diff --git a/zero.Core/Api/AuthenticationApi.cs b/zero.Core/Api/AuthenticationApi.cs index d58d12fa..062f347a 100644 --- a/zero.Core/Api/AuthenticationApi.cs +++ b/zero.Core/Api/AuthenticationApi.cs @@ -8,6 +8,7 @@ using System.Security.Claims; using System.Threading.Tasks; using zero.Core.Entities; using zero.Core.Identity; +using zero.Core.Security; namespace zero.Core.Api { @@ -19,14 +20,17 @@ namespace zero.Core.Api protected SignInManager SignInManager { get; private set; } + protected IUserClaimsPrincipalFactory ClaimsPrincipalFactory { get; private set; } + protected ClaimsPrincipal Principal => HttpContextAccessor.HttpContext?.User; - public AuthenticationApi(IDocumentStore raven, IHttpContextAccessor httpContextAccessor, SignInManager signInManager) + public AuthenticationApi(IDocumentStore raven, IHttpContextAccessor httpContextAccessor, SignInManager signInManager, IUserClaimsPrincipalFactory claimsPrincipalFactory) { Raven = raven; HttpContextAccessor = httpContextAccessor; SignInManager = signInManager; + ClaimsPrincipalFactory = claimsPrincipalFactory; } @@ -61,6 +65,8 @@ namespace zero.Core.Api /// public async Task GetUser() { + //var userIdClaim = Principal?.FindFirst(ClaimTypes.NameIdentifier); + return await SignInManager.UserManager.GetUserAsync(HttpContextAccessor.HttpContext.User); } @@ -105,7 +111,8 @@ namespace zero.Core.Api } - SignInResult signInResult = await SignInManager.PasswordSignInAsync(email, password, isPersistent, true); + + SignInResult signInResult = await SignInManager.CheckPasswordSignInAsync(user, password, true); if (!signInResult.Succeeded) { @@ -129,6 +136,26 @@ namespace zero.Core.Api return result; } + + + ClaimsPrincipal userPrincipal = await ClaimsPrincipalFactory.CreateAsync(user); + //var claims = new[] {new Claim(Constants.Auth.Claims.UserId, user.Id) }; + //var claimsIdentity = new ClaimsIdentity(claims, Constants.Auth.Scheme); + //var userPrincipal = new ClaimsPrincipal(claimsIdentity); + + + + userPrincipal.Identities.FirstOrDefault()?.AddClaim(new Claim(ClaimTypes.AuthenticationMethod, Constants.Auth.Scheme)); + + await SignInManager.Context.SignInAsync(Constants.Auth.Scheme, userPrincipal, new AuthenticationProperties() + { + IsPersistent = isPersistent + }); + + var xuser = HttpContextAccessor.HttpContext.User; + var xuserid = GetUserId(); + var yuser = await GetUser(); + return EntityResult.Success(); } @@ -136,15 +163,14 @@ namespace zero.Core.Api /// public async Task Logout() { - await SignInManager.SignOutAsync(); + await SignInManager.Context.SignOutAsync(Constants.Auth.Scheme); } /// public string GetUserId() { - ClaimsPrincipal principal = HttpContextAccessor.HttpContext.User; - return principal.Claims.FirstOrDefault(x => x.Type == Constants.Auth.Claims.UserId)?.Value; + return HttpContextAccessor.HttpContext.User.FindFirstValue(Constants.Auth.Claims.UserId); } } diff --git a/zero.Core/Constants.cs b/zero.Core/Constants.cs index e0a55733..cacd540d 100644 --- a/zero.Core/Constants.cs +++ b/zero.Core/Constants.cs @@ -11,7 +11,7 @@ public static class Auth { - public const string Scheme = "zeroCookies"; + public const string Scheme = "zeroBackoffice"; public const string CookieName = "zero.session"; public static class Claims @@ -25,6 +25,7 @@ public const string Permission = "zero.claim.permission"; public const string DefaultAppId = "zero.claim.defaultAppId"; public const string CurrentAppId = "zero.claim.currentAppId"; + public const string TicketExpires = "zero.claim.ticketExpires"; } } diff --git a/zero.Core/Extensions/HttpContextExtensions.cs b/zero.Core/Extensions/HttpContextExtensions.cs deleted file mode 100644 index 06133b3a..00000000 --- a/zero.Core/Extensions/HttpContextExtensions.cs +++ /dev/null @@ -1,41 +0,0 @@ -//using Microsoft.AspNetCore.Http; -//using Microsoft.AspNetCore.Identity; -//using Raven.Client.Documents; -//using Raven.Client.Documents.Linq; -//using Raven.Client.Documents.Session; -//using System; -//using System.Collections.Generic; -//using System.Linq; -//using System.Security.Claims; -//using System.Threading.Tasks; -//using zero.Core.Entities; -//using zero.Core.Identity; - -//namespace zero.Core.Extensions -//{ -// public static class HttpContextExtensions -// { -// public static string GetCurrentAppId(this HttpContext context) -// { -// ClaimsPrincipal user = context.User; - -// var permissions = user.Claims.Where(x => x.Type == Constants.Auth.Claims.Permission && ); - -// user.HasClaim(Constants.Auth.Claims.Permission, Permissions.Applications + ":" + PermissionsValue.Write) - -// // this is not the final decision, -// // as we need to check permissions too -// string currentAppId = user.CurrentAppId; - -// // set to default app id when nothing selected yet -// if (currentAppId.IsNullOrEmpty()) -// { -// currentAppId = user.AppId; -// } - -// UserManager manager; - -// manager.cla -// } -// } -//} diff --git a/zero.Core/Identity/Defaults.cs b/zero.Core/Identity/Defaults.cs index f02b809f..778d32ee 100644 --- a/zero.Core/Identity/Defaults.cs +++ b/zero.Core/Identity/Defaults.cs @@ -10,15 +10,15 @@ using zero.Core.Entities; namespace zero.Core.Identity { - public class ZeroUserStore : UserStore - { - public ZeroUserStore(IDocumentStore raven) : base(raven) { } - } + //public class ZeroUserStore : UserStore + //{ + // public ZeroUserStore(IDocumentStore raven) : base(raven) { } + //} - public class ZeroRoleStore : RoleStore - { - public ZeroRoleStore(IDocumentStore raven, IdentityErrorDescriber describer = null) : base(raven, describer) { } - } + //public class ZeroRoleStore : RoleStore + //{ + // public ZeroRoleStore(IDocumentStore raven, IdentityErrorDescriber describer = null) : base(raven, describer) { } + //} //public class ZeroUserManager : UserManager diff --git a/zero.Core/Identity/UserIdentity.cs b/zero.Core/Identity/UserIdentity.cs new file mode 100644 index 00000000..c810073c --- /dev/null +++ b/zero.Core/Identity/UserIdentity.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using zero.Core.Extensions; + +namespace zero.Core.Identity +{ + public class UserIdentity : ClaimsIdentity + { + public const string Issuer = Constants.Auth.Scheme; + + public override string AuthenticationType => Issuer; + + + public UserIdentity() { } + + private UserIdentity(ClaimsIdentity identity) : base(identity.Claims, Issuer) + { + + } + + + public static bool TryCreate(ClaimsIdentity identity, out UserIdentity user) + { + user = null; + + if (!RequiredClaims.All(claim => identity.HasClaim(x => x.Type == claim && !x.Value.IsNullOrWhiteSpace()))) + { + return false; + } + + if (!identity.HasClaim(Constants.Auth.Claims.IsSuper, PermissionsValue.True)) + { + return false; + } + + user = new UserIdentity(identity); + return true; + } + + + public static IEnumerable RequiredClaims => new[] + { + Constants.Auth.Claims.UserId, + Constants.Auth.Claims.UserName, + Constants.Auth.Claims.Role, + Constants.Auth.Claims.SecurityStamp, + Constants.Auth.Claims.CurrentAppId, + Constants.Auth.Claims.DefaultAppId, + Constants.Auth.Claims.IsZero + }; + } +} diff --git a/zero.Core/Identity/ZeroIdentityExtensions.cs b/zero.Core/Identity/ZeroIdentityExtensions.cs new file mode 100644 index 00000000..6f8b1ee8 --- /dev/null +++ b/zero.Core/Identity/ZeroIdentityExtensions.cs @@ -0,0 +1,75 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using System; +using System.Security.Claims; +using zero.Core.Entities; +using zero.Core.Security; + +namespace zero.Core.Identity +{ + public static class ZeroIdentityExtensions + { + public static IdentityBuilder AddZeroIdentity(this IServiceCollection services, Action setupAction = null) + where TUser : class, IUser + where TRole : class, IUserRole + { + services.AddHttpContextAccessor(); + services.AddOptions(); + services.AddLogging(); + + services.TryAddScoped, UserValidator>(); + services.TryAddScoped, PasswordValidator>(); + services.TryAddScoped, PasswordHasher>(); + services.TryAddScoped(); + services.TryAddScoped, DefaultUserConfirmation>(); + services.TryAddScoped(); + services.TryAddScoped>(); + + services.Configure(opts => + { + opts.ClaimsIdentity.UserIdClaimType = Constants.Auth.Claims.UserId; + opts.ClaimsIdentity.UserNameClaimType = Constants.Auth.Claims.UserName; + opts.ClaimsIdentity.RoleClaimType = Constants.Auth.Claims.Role; + opts.ClaimsIdentity.SecurityStampClaimType = Constants.Auth.Claims.SecurityStamp; + + opts.Password.RequireDigit = false; + opts.Password.RequireLowercase = false; + opts.Password.RequireUppercase = false; + opts.Password.RequireNonAlphanumeric = false; + opts.Password.RequiredLength = 8; + opts.Password.RequiredUniqueChars = 1; + + opts.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(60); + opts.Lockout.MaxFailedAccessAttempts = 5; + opts.Lockout.AllowedForNewUsers = true; + + opts.User.RequireUniqueEmail = true; + }); + + services.Configure(opts => + { + opts.ValidationInterval = TimeSpan.FromMinutes(30); + }); + + if (setupAction != null) + { + services.Configure(setupAction); + } + + IdentityBuilder builder = new IdentityBuilder(typeof(TUser), services); + + builder.AddDefaultTokenProviders(); + builder.AddUserStore>(); + builder.AddUserManager>(); + builder.AddSignInManager>(); + builder.AddClaimsPrincipalFactory>(); + + builder.AddRoles(); + builder.AddRoleManager>(); + builder.AddRoleStore>(); + + return builder; + } + } +} diff --git a/zero.Core/Security/ConfigureZeroCookieOptions.cs b/zero.Core/Security/ConfigureZeroCookieOptions.cs new file mode 100644 index 00000000..c2e3229f --- /dev/null +++ b/zero.Core/Security/ConfigureZeroCookieOptions.cs @@ -0,0 +1,50 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Options; +using System; +using zero.Core.Entities; +using zero.Core.Options; + +namespace zero.Core.Security +{ + public class ConfigureZeroCookieOptions : IConfigureNamedOptions where TUser : class, IUser + { + protected IZeroOptions Zero { get; set; } + + protected ISystemClock SystemClock { get; set; } + + + public ConfigureZeroCookieOptions(IZeroOptions zero, ISystemClock systemClock) + { + Zero = zero; + SystemClock = systemClock; + } + + + public void Configure(string name, CookieAuthenticationOptions options) + { + if (name == Constants.Auth.Scheme) + { + Configure(options); + } + } + + public void Configure(CookieAuthenticationOptions options) + { + options.SlidingExpiration = true; + options.ExpireTimeSpan = TimeSpan.FromMinutes(60); + options.Cookie.Name = Constants.Auth.CookieName; + options.Cookie.HttpOnly = true; + options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; + options.Cookie.Path = "/"; + + options.LoginPath = Zero.BackofficePath; + options.LogoutPath = Zero.BackofficePath; + options.AccessDeniedPath = Zero.BackofficePath; + + options.CookieManager = new ZeroCookieManager(Zero); + options.Events = new ZeroCookieAuthenticationEvents(Zero, SystemClock); + } + } +} diff --git a/zero.Core/Identity/ZeroAuthorizeAttribute.cs b/zero.Core/Security/ZeroAuthorizeAttribute.cs similarity index 100% rename from zero.Core/Identity/ZeroAuthorizeAttribute.cs rename to zero.Core/Security/ZeroAuthorizeAttribute.cs diff --git a/zero.Core/Security/ZeroClaimsPrinicipalFactory.cs b/zero.Core/Security/ZeroClaimsPrinicipalFactory.cs new file mode 100644 index 00000000..e51e8a15 --- /dev/null +++ b/zero.Core/Security/ZeroClaimsPrinicipalFactory.cs @@ -0,0 +1,94 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using zero.Core; +using zero.Core.Entities; +using zero.Core.Identity; + +namespace zero.Core.Security +{ + public class ZeroClaimsPrinicipalFactory : UserClaimsPrincipalFactory, IUserClaimsPrincipalFactory where TUser : class, IUser where TRole : class, IUserRole + { + public ZeroClaimsPrinicipalFactory(UserManager userManager, RoleManager roleManager, IOptions optionsAccessor) : base(userManager, roleManager, optionsAccessor) + { + + } + + public async override Task CreateAsync(TUser user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + ClaimsIdentity principal = await GenerateClaimsAsync(user); + return new ClaimsPrincipal(principal); + } + + + protected override async Task GenerateClaimsAsync(TUser user) + { + string userId = await UserManager.GetUserIdAsync(user); + string userName = await UserManager.GetUserNameAsync(user); + + List claims = new List(); + claims.Add(new Claim(Constants.Auth.Claims.IsZero, PermissionsValue.True)); + claims.Add(new Claim(Constants.Auth.Claims.UserId, userId)); + claims.Add(new Claim(Constants.Auth.Claims.UserName, userName)); + + if (UserManager.SupportsUserSecurityStamp) + { + claims.Add(new Claim(Constants.Auth.Claims.SecurityStamp, await UserManager.GetSecurityStampAsync(user))); + } + + if (UserManager.SupportsUserClaim) + { + claims.AddRange(await UserManager.GetClaimsAsync(user)); + } + + if (user.IsSuper) + { + claims.Add(new Claim(Constants.Auth.Claims.IsSuper, PermissionsValue.True)); + } + + + // get all allowed app ids + string[] appIds = claims + .Where(x => x.Type == Constants.Auth.Claims.Permission && x.Value.StartsWith(Permissions.Applications)) + .Select(x => Permission.FromClaim(x, Permissions.Applications)) + .Where(x => x.IsTrue) + .Select(x => x.NormalizedKey) + .ToArray(); + + string currentAppId = user.CurrentAppId ?? user.AppId; + + if (!user.IsSuper && !appIds.Contains(currentAppId)) + { + currentAppId = user.AppId; + } + + claims.Add(new Claim(Constants.Auth.Claims.CurrentAppId, currentAppId)); + + // add default role when user has none + if (!claims.Any(x => x.Type == Constants.Auth.Claims.Role)) + { + claims.Add(new Claim(Constants.Auth.Claims.Role, "userRoles.1-A")); // TODO this needs to be dynamic + } + + + // create the user identity + ClaimsIdentity identity = new ClaimsIdentity(claims, UserIdentity.Issuer, Constants.Auth.Claims.UserName, Constants.Auth.Claims.Role); // "Identity.Application" + + if (UserIdentity.TryCreate(identity, out UserIdentity userIdentity)) + { + return userIdentity; + } + + return null; + } + } +} diff --git a/zero.Core/Security/ZeroCookieAuthenticationEvents.cs b/zero.Core/Security/ZeroCookieAuthenticationEvents.cs new file mode 100644 index 00000000..98df9cbe --- /dev/null +++ b/zero.Core/Security/ZeroCookieAuthenticationEvents.cs @@ -0,0 +1,166 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; +using System.Linq; +using System.Security.Claims; +using System.Security.Principal; +using System.Threading.Tasks; +using zero.Core.Entities; +using zero.Core.Identity; +using zero.Core.Options; + +namespace zero.Core.Security +{ + public class ZeroCookieAuthenticationEvents : CookieAuthenticationEvents where TUser : class, IUser + { + protected IZeroOptions Zero { get; set; } + + protected ISystemClock SystemClock { get; set; } + + + public ZeroCookieAuthenticationEvents(IZeroOptions zero, ISystemClock systemClock) + { + Zero = zero; + SystemClock = systemClock; + + OnValidatePrincipal = _ValidatePrincipal; + OnSigningIn = _OnSigningIn; + OnSignedIn = _OnSignedIn; + OnSigningOut = _OnSigningOut; + OnRedirectToLogin = _OnRedirectToLogin; + } + + + /// + /// Validate user session + /// + async Task _ValidatePrincipal(CookieValidatePrincipalContext ctx) + { + ISecurityStampValidator securityStampValidator = ctx.HttpContext.RequestServices.GetRequiredService(); + SignInManager signInManager = ctx.HttpContext.RequestServices.GetRequiredService>(); + + UserIdentity identity = GetIdentity(ctx.Principal); + + if (identity == null) + { + ctx.RejectPrincipal(); + await signInManager.Context.SignOutAsync(Constants.Auth.Scheme); + return; + } + + // ensure the thread culture is set + //backOfficeIdentity.EnsureCulture(); + //await EnsureValidSessionId(ctx); + + await securityStampValidator.ValidateAsync(ctx); + + EnsureTicketRenewalIfKeepUserLoggedIn(ctx); + + // add a claim to track when the cookie expires, we use this to track time remaining + identity.AddClaim(new Claim(Constants.Auth.Claims.TicketExpires, ctx.Properties.ExpiresUtc.Value.ToString("o"), ClaimValueTypes.DateTime, UserIdentity.Issuer, UserIdentity.Issuer, identity)); + } + + + /// + /// + /// + Task _OnSigningIn(CookieSigningInContext ctx) + { + UserIdentity identity = GetIdentity(ctx.Principal); + + if (identity != null) + { + identity.AddClaim(new Claim(ClaimTypes.CookiePath, "/", ClaimValueTypes.String, UserIdentity.Issuer, UserIdentity.Issuer, identity)); + } + + return Task.CompletedTask; + } + + + /// + /// + /// + Task _OnSignedIn(CookieSignedInContext ctx) + { + ctx.HttpContext.User = ctx.Principal; + return Task.CompletedTask; + } + + + /// + /// + /// + Task _OnSigningOut(CookieSigningOutContext ctx) + { + ctx.Options.CookieManager.DeleteCookie(ctx.HttpContext, Constants.Auth.CookieName, new CookieOptions() { Path = "/" }); + return Task.CompletedTask; + } + + + /// + /// + /// + Task _OnRedirectToLogin(RedirectContext ctx) + { + ctx.Response.StatusCode = 401; + return Task.CompletedTask; + } + + + /// + /// Build backoffice user identity from a matching principal identity + /// + UserIdentity GetIdentity(IPrincipal principal) + { + if (principal.Identity is UserIdentity user) + { + return user; + } + + if (principal is ClaimsPrincipal claimsPrincipal) + { + user = claimsPrincipal.Identities.OfType().FirstOrDefault(); + if (user != null) + { + return user; + } + } + + if (principal.Identity is ClaimsIdentity claimsIdentity && claimsIdentity.IsAuthenticated) + { + if (UserIdentity.TryCreate(claimsIdentity, out user)) + { + return user; + } + } + + return null; + } + + + /// + /// Ensures the ticket is renewed if the is set to true and the current request is for the get user seconds endpoint + /// + void EnsureTicketRenewalIfKeepUserLoggedIn(CookieValidatePrincipalContext context) + { + //if (!Zero.KeepUserLoggedIn) return; // TODO + + var currentUtc = SystemClock.UtcNow; + var issuedUtc = context.Properties.IssuedUtc; + var expiresUtc = context.Properties.ExpiresUtc; + + if (expiresUtc.HasValue && issuedUtc.HasValue) + { + var timeElapsed = currentUtc.Subtract(issuedUtc.Value); + var timeRemaining = expiresUtc.Value.Subtract(currentUtc); + + if (timeRemaining < timeElapsed) + { + context.ShouldRenew = true; + } + } + } + } +} diff --git a/zero.Core/Security/ZeroCookieManager.cs b/zero.Core/Security/ZeroCookieManager.cs new file mode 100644 index 00000000..ef68af29 --- /dev/null +++ b/zero.Core/Security/ZeroCookieManager.cs @@ -0,0 +1,37 @@ +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; +using System; +using zero.Core.Extensions; +using zero.Core.Options; + +namespace zero.Core.Security +{ + public class ZeroCookieManager : ChunkingCookieManager, ICookieManager + { + protected IZeroOptions Zero { get; set; } + + + public ZeroCookieManager(IZeroOptions zero) + { + Zero = zero; + } + + + /// + /// Explicitly implement this so that we filter the request + /// + string ICookieManager.GetRequestCookie(HttpContext context, string key) + { + Uri requestUri = new Uri(context.Request.GetEncodedUrl(), UriKind.RelativeOrAbsolute); + string path = Zero.BackofficePath.EnsureStartsWith('/').TrimEnd('/'); + + if (!context.Request.Path.ToString().StartsWith(path)) + { + return null; + } + + return GetRequestCookie(context, key); + } + } +} diff --git a/zero.Core/zero.Core.csproj b/zero.Core/zero.Core.csproj index cb75fc87..422c8516 100644 --- a/zero.Core/zero.Core.csproj +++ b/zero.Core/zero.Core.csproj @@ -20,6 +20,7 @@ + diff --git a/zero.Web/ZeroBuilder.cs b/zero.Web/ZeroBuilder.cs index d659d803..cd38ce40 100644 --- a/zero.Web/ZeroBuilder.cs +++ b/zero.Web/ZeroBuilder.cs @@ -21,6 +21,7 @@ using zero.Core.Extensions; using zero.Core.Identity; using zero.Core.Options; using zero.Core.Plugins; +using zero.Core.Security; using zero.Core.Validation; using zero.Web.Controllers; using zero.Web.Defaults; @@ -99,6 +100,7 @@ namespace zero.Web // add default services + Services.AddHttpContextAccessor(); Services.AddScoped(); Services.AddTransient(); @@ -165,49 +167,10 @@ namespace zero.Web /// void ConfigureIdentity() { - Services.AddIdentity(opts => - { - opts.ClaimsIdentity.UserIdClaimType = Constants.Auth.Claims.UserId; - opts.ClaimsIdentity.UserNameClaimType = Constants.Auth.Claims.UserName; - opts.ClaimsIdentity.RoleClaimType = Constants.Auth.Claims.Role; - opts.ClaimsIdentity.SecurityStampClaimType = Constants.Auth.Claims.SecurityStamp; - - opts.Password.RequireDigit = false; - opts.Password.RequireLowercase = false; - opts.Password.RequireUppercase = false; - opts.Password.RequireNonAlphanumeric = false; - opts.Password.RequiredLength = 8; - opts.Password.RequiredUniqueChars = 1; - - opts.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5); - opts.Lockout.MaxFailedAccessAttempts = 5; - opts.Lockout.AllowedForNewUsers = true; - }) - .AddClaimsPrincipalFactory() - .AddDefaultTokenProviders(); - - Services.ConfigureApplicationCookie(opts => - { - //opts.Cookie.Path = // TODO use backoffice path - opts.Cookie.Name = Constants.Auth.CookieName; - //opts.Cookie.Path = "/zero"; // TODO dynamic - opts.SlidingExpiration = true; - opts.ExpireTimeSpan = TimeSpan.FromMinutes(60); - - // override redirect to login page (handled by vue frontend) and return a 401 instead - opts.Events.OnRedirectToLogin = (context) => - { - context.Response.StatusCode = 401; - return Task.CompletedTask; - }; - }); - - Services.AddTransient, ZeroUserStore>(); - Services.AddTransient, ZeroRoleStore>(); - - Services.AddScoped>(); - Services.AddScoped>(); - Services.AddScoped>(); + Services.AddZeroIdentity(); + Services.AddAuthentication(Constants.Auth.Scheme) + .AddCookie(Constants.Auth.Scheme); + Services.ConfigureOptions>(); }