diff --git a/zero/Configuration/Constants.cs b/zero/Configuration/Constants.cs index fa96aa14..d9b6832d 100644 --- a/zero/Configuration/Constants.cs +++ b/zero/Configuration/Constants.cs @@ -4,11 +4,6 @@ public static partial class Constants { public const string ErrorFieldNone = "__zero_no_field"; - public static partial class Tabs - { - public const string General = "general"; - } - public static partial class Auth { public const string SystemUser = "system"; @@ -17,86 +12,11 @@ public static partial class Constants public const string BackofficeScheme = "zeroBackoffice"; public const string BackofficeCookieName = "zero.be.session"; public const string DefaultCookieName = "zero.session"; - - public static partial class Claims - { - public const string IsZero = "zero.claim.iszero"; - public const string IsSuper = "zero.claim.issuper"; - public const string UserId = "zero.claim.userid"; - public const string UserName = "zero.claim.username"; - public const string Role = "zero.claim.rolealias"; - public const string SecurityStamp = "zero.claim.securitystamp"; - public const string Email = "zero.claim.email"; - public const string Permission = "zero.claim.permission"; - public const string DefaultAppId = "zero.claim.defaultAppId"; - public const string AppId = "zero.claim.appId"; - public const string TicketExpires = "zero.claim.ticketExpires"; - } } - - public static partial class Permissions - { - public static partial class Groups - { - public const string Sections = "zero.permissions.groups.sections"; - public const string Spaces = "zero.permissions.groups.spaces"; - public const string Settings = "zero.permissions.groups.settings"; - public const string Pages = "zero.permissions.groups.pages"; - public const string Modules = "zero.permissions.groups.modules"; - } - } - - public static partial class Database { public const string ReservationPrefix = "zero."; public const string Expires = Raven.Client.Constants.Documents.Metadata.Expires; } - - public static partial class Sections - { - public const string Dashboard = "dashboard"; - public const string Pages = "pages"; - public const string Spaces = "spaces"; - public const string Media = "media"; - public const string Settings = "settings"; - } - - public static partial class Settings - { - public const string Updates = "updates"; - public const string Applications = "applications"; - public const string Users = "users"; - public const string Languages = "languages"; - public const string Translations = "translations"; - public const string Countries = "countries"; - public const string Mails = "mailTemplates"; - public const string Integrations = "integrations"; - public const string Logging = "logs"; - public const string Plugins = "plugins"; - public const string CreatePlugin = "createplugin"; - } - - public static partial class PermissionCollections - { - public const string Sections = "permissionCollectionSections"; - public const string Spaces = "permissionCollectionSpaces"; - public const string Settings = "permisssionCollectionSettings"; - public const string Modules = "permissionCollectionModules"; - } - - public static partial class Pages - { - public const string FolderAlias = "zero.folder"; - public const string DefaultRootPageTypeAlias = "root"; - public const string PageRouteProviderAlias = "zero.pages"; - } - - public static partial class Routing - { - public const string InternalRoutePrefix = "/__zero/"; - - public const string ErrorRoute = InternalRoutePrefix + "error"; - } } diff --git a/zero/Identity/IdentityBuilderExtensions.cs b/zero/Identity/IdentityBuilderExtensions.cs index 864879dd..947ab791 100644 --- a/zero/Identity/IdentityBuilderExtensions.cs +++ b/zero/Identity/IdentityBuilderExtensions.cs @@ -9,12 +9,10 @@ public static class IdentityBuilderExtensions /// /// Adds an implementation of identity information stores. /// - public static IdentityBuilder AddZeroIdentityStores(this IdentityBuilder builder) where T : class, IZeroIdentityStoreDbProvider + public static IdentityBuilder AddZeroIdentityStores(this IdentityBuilder builder) { Type userStoreType = typeof(ZeroUserStore<,>).MakeGenericType(builder.UserType, builder.RoleType); Type roleStoreType = typeof(ZeroRoleStore<>).MakeGenericType(builder.RoleType); - - builder.Services.AddScoped(); builder.Services.TryAddScoped(typeof(IUserStore<>).MakeGenericType(builder.UserType), userStoreType); builder.Services.TryAddScoped(typeof(IRoleStore<>).MakeGenericType(builder.RoleType), roleStoreType); diff --git a/zero/Identity/ServiceCollectionExtensions.cs b/zero/Identity/ServiceCollectionExtensions.cs deleted file mode 100644 index 2e087e05..00000000 --- a/zero/Identity/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,97 +0,0 @@ -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using zero.Identity; - -namespace zero.Identity; - -public static class ServiceCollectionExtensions -{ - /// - /// Adds the default identity system configuration for the specified User and Role types - /// - // public static IdentityBuilder AddZeroIdentity( - // this IServiceCollection services) - // where TUser : ZeroIdentityUser, new() - // where TRole : ZeroIdentityRole, new() - // where TStore : class, IZeroIdentityStoreDbProvider - // => services.AddIdentity(setupAction: null!); - - /// - /// Adds and configures the identity system for the specified User and Role types - /// - public static IdentityBuilder AddZeroIdentity( - this IServiceCollection services, - Action setupAction - ) - where TUser : ZeroIdentityUser, new() - where TRole : ZeroIdentityRole, new() - where TStore : class, IZeroIdentityStoreDbProvider - { - // Services used by identity - services.AddAuthentication(options => - { - options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme; - options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme; - options.DefaultSignInScheme = IdentityConstants.ExternalScheme; - }) - .AddCookie(IdentityConstants.ApplicationScheme, o => - { - o.LoginPath = new PathString("/Account/Login"); - o.Events = new CookieAuthenticationEvents - { - OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync - }; - }) - .AddCookie(IdentityConstants.ExternalScheme, o => - { - o.Cookie.Name = IdentityConstants.ExternalScheme; - o.ExpireTimeSpan = TimeSpan.FromMinutes(5); - }) - .AddCookie(IdentityConstants.TwoFactorRememberMeScheme, o => - { - o.Cookie.Name = IdentityConstants.TwoFactorRememberMeScheme; - o.Events = new CookieAuthenticationEvents - { - OnValidatePrincipal = SecurityStampValidator.ValidateAsync - }; - }) - .AddCookie(IdentityConstants.TwoFactorUserIdScheme, o => - { - o.Cookie.Name = IdentityConstants.TwoFactorUserIdScheme; - o.ExpireTimeSpan = TimeSpan.FromMinutes(5); - }); - - // Hosting doesn't add IHttpContextAccessor by default - services.AddHttpContextAccessor(); - - // Data stores - services.AddScoped(); - services.TryAddScoped, ZeroUserStore>(); - services.TryAddScoped, ZeroRoleStore>(); - - // Identity services - services.TryAddScoped, UserValidator>(); - services.TryAddScoped, PasswordValidator>(); - services.TryAddScoped, PasswordHasher>(); - services.TryAddScoped(); - services.TryAddScoped, RoleValidator>(); - services.TryAddScoped(); - services.TryAddScoped>(); - services.TryAddScoped>(); - services.TryAddScoped, UserClaimsPrincipalFactory>(); - services.TryAddScoped, DefaultUserConfirmation>(); - services.TryAddScoped>(); - services.TryAddScoped>(); - services.TryAddScoped>(); - - if (setupAction != null) - { - services.Configure(setupAction); - } - - return new IdentityBuilder(typeof(TUser), typeof(TRole), services); - } -} \ No newline at end of file diff --git a/zero/Identity/ZeroIdentityConstants.cs b/zero/Identity/ZeroIdentityConstants.cs new file mode 100644 index 00000000..28962812 --- /dev/null +++ b/zero/Identity/ZeroIdentityConstants.cs @@ -0,0 +1,31 @@ +namespace zero.Identity; + +/// +/// Represents all the options you can use to configure the cookies middleware used by the identity system. +/// +public class ZeroIdentityConstants +{ + public static class CookieNames + { + private const string CookiePrefix = "zero.id"; + + public static readonly string Application = CookiePrefix + ".app"; + public static readonly string External = CookiePrefix + ".ext"; + public static readonly string TwoFactorRememberMe = CookiePrefix + ".2fa_rem"; + public static readonly string TwoFactorUserId = CookiePrefix + ".2fa_id"; + } + + public static partial class Claims + { + private const string ClaimPrefix = "zero.claim"; + + public static readonly string IsZero = ClaimPrefix + ".iszero"; + public static readonly string UserId = ClaimPrefix + ".userid"; + public static readonly string UserName = ClaimPrefix + ".username"; + public static readonly string Role = ClaimPrefix + ".rolealias"; + public static readonly string SecurityStamp = ClaimPrefix + ".securitystamp"; + public static readonly string Email = ClaimPrefix + ".email"; + public static readonly string Permission = ClaimPrefix + ".permission"; + public static readonly string TicketExpires = ClaimPrefix + ".ticketExpires"; + } +} diff --git a/zero/Identity/ZeroIdentityExtensions.cs b/zero/Identity/ZeroIdentityExtensions.cs new file mode 100644 index 00000000..4cdbf5c3 --- /dev/null +++ b/zero/Identity/ZeroIdentityExtensions.cs @@ -0,0 +1,161 @@ +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace zero.Identity; + +public static class ZeroIdentityExtensions +{ + // + /// Adds the default identity system configuration for the specified User and Role types. + /// + /// The type representing a User in the system. + /// The type representing a Role in the system. + /// The services available in the application. + /// An for creating and configuring the identity system. + public static IdentityBuilder AddZeroIdentity(this IServiceCollection services) + where TUser : ZeroIdentityUser, new() + where TRole : ZeroIdentityRole, new() => + AddZeroIdentity(services, null); + + + /// + /// Adds and configures the identity system for the specified User and Role types. + /// + /// The type representing a User in the system. + /// The type representing a Role in the system. + /// The services available in the application. + /// An action to configure the . + /// An for creating and configuring the identity system. + public static IdentityBuilder AddZeroIdentity(this IServiceCollection services, + Action setupAction) + where TUser : ZeroIdentityUser, new() + where TRole : ZeroIdentityRole, new() + { + // Services identity depends on + services + .AddOptions() + .AddLogging(); + + // Services used by identity + services.AddAuthentication(options => + { + options.DefaultScheme = IdentityConstants.ApplicationScheme; + options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme; + options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme; + options.DefaultSignInScheme = IdentityConstants.ExternalScheme; + }) + .AddCookie(IdentityConstants.ApplicationScheme, o => + { + o.LoginPath = new PathString("/Account/Login"); + o.SlidingExpiration = true; + o.ExpireTimeSpan = TimeSpan.FromDays(90); + + o.Cookie.Name = ZeroIdentityConstants.CookieNames.Application; + o.Cookie.HttpOnly = true; + o.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; + o.Cookie.SameSite = SameSiteMode.Lax; + o.Cookie.Path = "/"; + + o.Events = new CookieAuthenticationEvents + { + OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync + }; + }) + .AddCookie(IdentityConstants.ExternalScheme, o => + { + o.Cookie.Name = ZeroIdentityConstants.CookieNames.External; + o.ExpireTimeSpan = TimeSpan.FromMinutes(5); + }) + .AddCookie(IdentityConstants.TwoFactorRememberMeScheme, o => + { + o.Cookie.Name = ZeroIdentityConstants.CookieNames.TwoFactorRememberMe; + o.Events = new CookieAuthenticationEvents + { + OnValidatePrincipal = SecurityStampValidator.ValidateAsync + }; + }) + .AddCookie(IdentityConstants.TwoFactorUserIdScheme, o => + { + o.Cookie.Name = ZeroIdentityConstants.CookieNames.TwoFactorUserId; + o.ExpireTimeSpan = TimeSpan.FromMinutes(5); + }); + + // Hosting doesn't add IHttpContextAccessor by default + services.AddHttpContextAccessor(); + + // Identity services + services.TryAddScoped, UserValidator>(); + services.TryAddScoped, PasswordValidator>(); + services.TryAddScoped, PasswordHasher>(); + services.TryAddScoped(); + services.TryAddScoped, RoleValidator>(); + + // No interface for the error describer so we can add errors without rev'ing the interface + services.TryAddScoped(); + services.TryAddScoped>(); + services.TryAddScoped>(); + services.TryAddScoped, UserClaimsPrincipalFactory>(); + services.TryAddScoped, DefaultUserConfirmation>(); + services.TryAddScoped>(); + services.TryAddScoped>(); + services.TryAddScoped>(); + + services.Configure(opts => + { + opts.ClaimsIdentity.UserIdClaimType = ZeroIdentityConstants.Claims.UserId; + opts.ClaimsIdentity.UserNameClaimType = ZeroIdentityConstants.Claims.UserName; + opts.ClaimsIdentity.RoleClaimType = ZeroIdentityConstants.Claims.Role; + opts.ClaimsIdentity.SecurityStampClaimType = ZeroIdentityConstants.Claims.SecurityStamp; + opts.ClaimsIdentity.EmailClaimType = ZeroIdentityConstants.Claims.Email; + + 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(90); }); + + if (setupAction != null) + { + services.Configure(setupAction); + } + + IdentityBuilder builder = new(typeof(TUser), typeof(TRole), services); + + builder.AddDefaultTokenProviders(); + builder.AddZeroIdentityStores(); + + return builder; + } + + + /// + /// Configures the application cookie. + /// + /// The services available in the application. + /// An action to configure the . + /// The services. + public static IServiceCollection ConfigureZeroApplicationCookie(this IServiceCollection services, Action configure) + => services.Configure(IdentityConstants.ApplicationScheme, configure); + + /// + /// Configure the external cookie. + /// + /// The services available in the application. + /// An action to configure the . + /// The services. + public static IServiceCollection ConfigureZeroExternalCookie(this IServiceCollection services, Action configure) + => services.Configure(IdentityConstants.ExternalScheme, configure); +} \ No newline at end of file diff --git a/zero/ZeroBuilder.cs b/zero/ZeroBuilder.cs index 10e319de..88c432b2 100644 --- a/zero/ZeroBuilder.cs +++ b/zero/ZeroBuilder.cs @@ -1,4 +1,5 @@ using FluentValidation; +using Microsoft.AspNetCore.Antiforgery; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -33,6 +34,8 @@ public class ZeroBuilder services.AddControllers(); services.AddRazorPages(); + services.Configure(opts => opts.Cookie.Name = "zero.antiforgery"); + // adds and discovers additional and built-in assemblies new AssemblyDiscovery(Mvc).Execute(_startupOptions.AssemblyDiscoveryRules);