diff --git a/zero.Backoffice/Configuration/BackofficeOptions.cs b/zero.Backoffice/Configuration/BackofficeOptions.cs
index bf7bccbc..dd7b71da 100644
--- a/zero.Backoffice/Configuration/BackofficeOptions.cs
+++ b/zero.Backoffice/Configuration/BackofficeOptions.cs
@@ -26,4 +26,9 @@ public class BackofficeOptions
/// Configure search maps
///
public SearchOptions Search { get; set; } = new();
+
+ ///
+ /// Options for configuring the vite development server
+ ///
+ public ZeroDevOptions DevServer { get; set; } = new();
}
\ No newline at end of file
diff --git a/zero.Backoffice/Controllers/ZeroIndexController.cs b/zero.Backoffice/Controllers/ZeroIndexController.cs
new file mode 100644
index 00000000..8ac275b8
--- /dev/null
+++ b/zero.Backoffice/Controllers/ZeroIndexController.cs
@@ -0,0 +1,39 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace zero.Backoffice.Controllers;
+
+[ZeroAuthorize(false)]
+[DisableBrowserCache]
+public abstract class ZeroIndexController : Controller
+{
+ IZeroVue ZeroVue { get; set; }
+ IZeroOptions Options { get; set; }
+
+ public ZeroIndexController(IZeroVue zeroVue, IZeroOptions options)
+ {
+ ZeroVue = zeroVue;
+ Options = options;
+ }
+
+
+ public IActionResult Index()
+ {
+ if (Options.Version.IsNullOrEmpty())
+ {
+ return RedirectToAction("ZeroBackoffice", "Setup");
+ }
+
+ return View("Views/Zero/Index.cshtml", new ZeroBackofficeModel()
+ {
+ Port = Options.For().DevServer.Port,
+ Vue = ZeroVue
+ });
+ }
+}
+
+public class ZeroBackofficeModel
+{
+ public int Port { get; set; }
+
+ public IZeroVue Vue { get; set; }
+}
diff --git a/zero.Backoffice/Controllers/ZeroVueController.cs b/zero.Backoffice/Controllers/ZeroVueController.cs
deleted file mode 100644
index 68a23e11..00000000
--- a/zero.Backoffice/Controllers/ZeroVueController.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using Microsoft.AspNetCore.Mvc;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Converters;
-using Newtonsoft.Json.Serialization;
-using System.Threading.Tasks;
-using zero.Core.Identity;
-
-namespace zero.Web.Controllers
-{
- [ZeroAuthorize(false)]
- public class ZeroVueController : BackofficeController
- {
- private IZeroVue ZeroVue { get; set; }
-
- public ZeroVueController(IZeroVue zeroVue)
- {
- ZeroVue = zeroVue;
- }
-
-
- [HttpGet]
- public async Task Config()
- {
- JsonSerializerSettings settings = new();
- settings.Converters.Add(new StringEnumConverter(new CamelCaseNamingStrategy()));
- settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
- settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
- settings.TypeNameHandling = TypeNameHandling.None;
-
- return new JsonResult(await ZeroVue.Config(), settings);
- }
- }
-}
diff --git a/zero.Backoffice/Controllers/_ZeroBackofficeCollectionController.cs b/zero.Backoffice/Controllers/_ZeroBackofficeCollectionController.cs
index ff413319..659923ba 100644
--- a/zero.Backoffice/Controllers/_ZeroBackofficeCollectionController.cs
+++ b/zero.Backoffice/Controllers/_ZeroBackofficeCollectionController.cs
@@ -11,7 +11,7 @@ namespace zero.Backoffice;
public abstract class _ZeroBackofficeCollectionController : ZeroBackofficeController
where TEntity : ZeroIdEntity, new()
- where TCollection : ICollectionOperations
+ where TCollection : IStoreOperations
{
protected TCollection Collection { get; private set; }
diff --git a/zero.Backoffice/Modules/Countries/CountriesController.cs b/zero.Backoffice/Modules/Countries/CountriesController.cs
index 9b4bd176..8c4df0f1 100644
--- a/zero.Backoffice/Modules/Countries/CountriesController.cs
+++ b/zero.Backoffice/Modules/Countries/CountriesController.cs
@@ -13,9 +13,9 @@ namespace zero.Backoffice.Modules;
///
public class CountriesController : ZeroBackofficeApiController
{
- protected ICountriesCollection Collection { get; set; }
+ protected ICountriesStore Collection { get; set; }
- public CountriesController(ICountriesCollection collection)
+ public CountriesController(ICountriesStore collection)
{
Collection = collection;
}
diff --git a/zero.Backoffice/Modules/Languages/LanguagesController.cs b/zero.Backoffice/Modules/Languages/LanguagesController.cs
index b61b656e..386a4c41 100644
--- a/zero.Backoffice/Modules/Languages/LanguagesController.cs
+++ b/zero.Backoffice/Modules/Languages/LanguagesController.cs
@@ -11,9 +11,9 @@ using zero.Core.Identity;
namespace zero.Web.Controllers
{
[ZeroAuthorize(Permissions.Settings.Languages, PermissionsValue.Read)]
- public class LanguagesController : ZeroBackofficeCollectionController
+ public class LanguagesController : ZeroBackofficeCollectionController
{
- public LanguagesController(ILanguagesCollection collection) : base(collection)
+ public LanguagesController(ILanguagesStore collection) : base(collection)
{
PreviewTransform = (item, model) => model.Icon = "fth-globe";
}
diff --git a/zero.Backoffice/Modules/Mails/MailTemplatesController.cs b/zero.Backoffice/Modules/Mails/MailTemplatesController.cs
index 218ba21b..b3b63d2f 100644
--- a/zero.Backoffice/Modules/Mails/MailTemplatesController.cs
+++ b/zero.Backoffice/Modules/Mails/MailTemplatesController.cs
@@ -8,9 +8,9 @@ using zero.Core.Identity;
namespace zero.Web.Controllers
{
[ZeroAuthorize(Permissions.Settings.Mails, PermissionsValue.Read)]
- public class MailTemplatesController : ZeroBackofficeCollectionController
+ public class MailTemplatesController : ZeroBackofficeCollectionController
{
- public MailTemplatesController(IMailTemplatesCollection collection) : base(collection)
+ public MailTemplatesController(IMailTemplatesStore collection) : base(collection)
{
PreviewTransform = (item, model) => model.Icon = "fth-mail";
}
diff --git a/zero.Backoffice/Plugin.cs b/zero.Backoffice/Plugin.cs
index f9deaa16..cd4cc83e 100644
--- a/zero.Backoffice/Plugin.cs
+++ b/zero.Backoffice/Plugin.cs
@@ -1,24 +1,31 @@
-using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
+using System.IO;
namespace zero.Backoffice;
-internal class ZeroBackofficePlugin : ZeroPlugin
+public class ZeroBackofficePlugin : ZeroPlugin
{
+ internal Action PostConfigureServices = null;
+
+
public ZeroBackofficePlugin()
{
- Options.Name = "zero.Defaults";
+ Options.Name = "zero.Backoffice";
Options.LocalizationPaths.Add("~/Resources/Localization/zero.{lang}.json");
}
public override void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
- services.AddOptions().Bind(configuration.GetSection("Zero:Backoffice")).Configure(ConfigureOptions);
+ services.AddOptions().Bind(configuration.GetSection("Zero:Backoffice")).Configure(ConfigureOptions);
services.TryAddEnumerable(ServiceDescriptor.Transient, ZeroBackofficeMvcOptions>());
+ services.AddHostedService();
+ services.AddTransient();
// register all modules
ZeroModuleConfiguration moduleConfig = new(services, configuration);
@@ -26,12 +33,17 @@ internal class ZeroBackofficePlugin : ZeroPlugin
{
module.Register(moduleConfig);
}
+
+ PostConfigureServices?.Invoke(services, configuration);
}
- protected void ConfigureOptions(BackofficeOptions options)
+ protected void ConfigureOptions(BackofficeOptions options, IWebHostEnvironment env)
{
options.Path = "/zero";
+ options.Search.Enabled = true;
+ options.DevServer.WorkingDirectory = Path.Combine(env.ContentRootPath, "..", "Zero.Web.UI", "App");
+
options.IconSets.Add(new BackofficeIconSet()
{
Alias = "feather",
@@ -40,7 +52,6 @@ internal class ZeroBackofficePlugin : ZeroPlugin
Prefix = "fth"
});
- options.Search.Enabled = true;
//Map().Display((x, res, opts) =>
//{
// PageType pageType = opts.Pages.GetByAlias(x.PageTypeAlias);
diff --git a/zero.Backoffice/Registrations.cs b/zero.Backoffice/Registrations.cs
index 30496ba3..edcdecab 100644
--- a/zero.Backoffice/Registrations.cs
+++ b/zero.Backoffice/Registrations.cs
@@ -6,6 +6,7 @@ internal class Registrations
{
new CountriesModule(),
new SearchModule(),
- new BackofficeUICompositionModule()
+ new BackofficeUICompositionModule(),
+ new BackofficeDevServerModule()
};
}
\ No newline at end of file
diff --git a/zero.Backoffice/ServiceCollectionExtensions.cs b/zero.Backoffice/ServiceCollectionExtensions.cs
new file mode 100644
index 00000000..ad4ea2b6
--- /dev/null
+++ b/zero.Backoffice/ServiceCollectionExtensions.cs
@@ -0,0 +1,36 @@
+using Microsoft.Extensions.DependencyInjection;
+
+namespace zero.Backoffice;
+
+public static class ServiceCollectionExtensions
+{
+ public static ZeroBuilder AddBackoffice(this ZeroBuilder builder) where T : ZeroBackofficePlugin, IZeroPlugin, new()
+ {
+ return builder.AddBackoffice();
+ }
+
+ public static ZeroBuilder AddBackoffice(this ZeroBuilder builder)
+ {
+ return builder.AddBackoffice();
+ }
+
+ public static ZeroBuilder AddBackoffice(this ZeroBuilder builder, Action options) where T : ZeroBackofficePlugin, IZeroPlugin, new()
+ {
+ return builder.AddPlugin(services =>
+ {
+ T plugin = new T();
+
+ plugin.PostConfigureServices = (services, configuration) =>
+ {
+ services.Configure(opts => options(opts));
+ };
+
+ return plugin;
+ });
+ }
+
+ public static ZeroBuilder AddStories(this ZeroBuilder builder, Action options)
+ {
+ return builder.AddBackoffice(options);
+ }
+}
\ No newline at end of file
diff --git a/zero.Backoffice/Usings.cs b/zero.Backoffice/Usings.cs
index 3cf5e6e2..c355b1b8 100644
--- a/zero.Backoffice/Usings.cs
+++ b/zero.Backoffice/Usings.cs
@@ -7,7 +7,7 @@ global using System.Threading.Tasks;
global using zero.Applications;
global using zero.Architecture;
-global using zero.Collections;
+global using zero.Stores;
global using zero.Configuration;
global using zero.Context;
global using zero.Extensions;
@@ -24,4 +24,5 @@ global using zero.Backoffice.Configuration;
global using zero.Backoffice.Controllers;
global using zero.Backoffice.Models;
global using zero.Backoffice.Modules;
-global using zero.Backoffice.UIComposition;
\ No newline at end of file
+global using zero.Backoffice.UIComposition;
+global using zero.Backoffice.DevServer;
\ No newline at end of file
diff --git a/zero.Backoffice/ZeroVue.cs b/zero.Backoffice/ZeroVue.cs
index d232703b..2a03ebea 100644
--- a/zero.Backoffice/ZeroVue.cs
+++ b/zero.Backoffice/ZeroVue.cs
@@ -20,7 +20,7 @@ using zero.Core.Options;
using zero.Core.Plugins;
using zero.Web.Models;
-namespace zero.Web
+namespace zero.Backoffice
{
public class ZeroVue : IZeroVue
{
@@ -30,7 +30,7 @@ namespace zero.Web
protected IApplicationsApi ApplicationsApi { get; private set; }
- protected IAuthenticationApi AuthenticationApi { get; private set; }
+ protected IAuthenticationService AuthenticationApi { get; private set; }
protected IEnumerable Plugins { get; private set; }
@@ -43,7 +43,7 @@ namespace zero.Web
string IconSymbolsSvg { get; set; }
- public ZeroVue(IZeroOptions options, IPaths paths, IApplicationsApi applicationsApi, IAuthenticationApi authenticationApi, IEnumerable plugins, IZeroContext context, ILogger logger, IZeroStore store)
+ public ZeroVue(IZeroOptions options, IPaths paths, IApplicationsApi applicationsApi, IAuthenticationService authenticationApi, IEnumerable plugins, IZeroContext context, ILogger logger, IZeroStore store)
{
Paths = paths;
Options = options;
@@ -73,7 +73,7 @@ namespace zero.Web
config.Icons = CreateIconSets();
config.MultiApps = Options.Applications.EnableMultiple;
- BackofficeUser user = await AuthenticationApi.GetUser();
+ ZeroUser user = await AuthenticationApi.GetUser();
config.Translations = CreateTranslations(user?.LanguageId);
diff --git a/zero.Backoffice/_legacy/Controllers/AuthenticationController.cs b/zero.Backoffice/_legacy/Controllers/AuthenticationController.cs
index 4b03bafd..702519c8 100644
--- a/zero.Backoffice/_legacy/Controllers/AuthenticationController.cs
+++ b/zero.Backoffice/_legacy/Controllers/AuthenticationController.cs
@@ -10,16 +10,16 @@ namespace zero.Web.Controllers
[ZeroAuthorize(false)]
public class AuthenticationController : BackofficeController
{
- IAuthenticationApi Api;
+ IAuthenticationService Api;
- public AuthenticationController(IAuthenticationApi api)
+ public AuthenticationController(IAuthenticationService api)
{
Api = api;
}
- public async Task> GetUser() => Edit(await Api.GetUser());
+ public async Task> GetUser() => Edit(await Api.GetUser());
public EntityResult IsLoggedIn() => EntityResult.Maybe(Api.IsLoggedIn());
diff --git a/zero.Backoffice/_legacy/Controllers/SettingsController.cs b/zero.Backoffice/_legacy/Controllers/SettingsController.cs
index 59b38d2d..f09be9c7 100644
--- a/zero.Backoffice/_legacy/Controllers/SettingsController.cs
+++ b/zero.Backoffice/_legacy/Controllers/SettingsController.cs
@@ -11,12 +11,12 @@ namespace zero.Web.Controllers
[ZeroAuthorize(Permissions.Sections.Settings, PermissionsValue.Read)]
public class SettingsController : BackofficeController
{
- IAuthenticationApi AuthApi;
+ IAuthenticationService AuthApi;
IApplicationsApi ApplicationsApi;
- public SettingsController(IAuthenticationApi authApi, IApplicationsApi applicationsApi)
+ public SettingsController(IAuthenticationService authApi, IApplicationsApi applicationsApi)
{
AuthApi = authApi;
ApplicationsApi = applicationsApi;
diff --git a/zero.Backoffice/_legacy/Controllers/UserRolesController.cs b/zero.Backoffice/_legacy/Controllers/UserRolesController.cs
index 48654796..429e9128 100644
--- a/zero.Backoffice/_legacy/Controllers/UserRolesController.cs
+++ b/zero.Backoffice/_legacy/Controllers/UserRolesController.cs
@@ -11,31 +11,31 @@ namespace zero.Web.Controllers
[ZeroAuthorize(Permissions.Settings.Users, PermissionsValue.Read)]
public class UserRolesController : BackofficeController
{
- IUserRolesApi Api;
- IPermissionsApi PermissionsApi;
+ IUserRolesService Api;
+ IPermissionsService PermissionsApi;
- public UserRolesController(IUserRolesApi api, IPermissionsApi permissionsApi)
+ public UserRolesController(IUserRolesService api, IPermissionsService permissionsApi)
{
Api = api;
PermissionsApi = permissionsApi;
}
- public async Task> GetById([FromQuery] string id) => Edit(await Api.GetById(id));
+ public async Task> GetById([FromQuery] string id) => Edit(await Api.GetById(id));
- public async Task> GetAll() => await Api.GetAll();
+ public async Task> GetAll() => await Api.GetAll();
public IList GetAllPermissions() => PermissionsApi.GetAll();
[ZeroAuthorize(Permissions.Settings.Users, PermissionsValue.Update)]
- public async Task> Save([FromBody] BackofficeUserRole model) => await Api.Save(model);
+ public async Task> Save([FromBody] ZeroUserRole model) => await Api.Save(model);
[ZeroAuthorize(Permissions.Settings.Users, PermissionsValue.Update)]
- public async Task> Delete([FromQuery] string id) => await Api.Delete(id);
+ public async Task> Delete([FromQuery] string id) => await Api.Delete(id);
}
}
diff --git a/zero.Backoffice/_legacy/Controllers/UsersController.cs b/zero.Backoffice/_legacy/Controllers/UsersController.cs
index 6cef3e3f..3a68c8a3 100644
--- a/zero.Backoffice/_legacy/Controllers/UsersController.cs
+++ b/zero.Backoffice/_legacy/Controllers/UsersController.cs
@@ -12,12 +12,12 @@ namespace zero.Web.Controllers
[ZeroAuthorize(Permissions.Settings.Users, PermissionsValue.Read)]
public class UsersController : BackofficeController
{
- IUserApi Api;
- IAuthenticationApi AuthenticationApi;
- IPermissionsApi PermissionsApi;
+ IUserService Api;
+ IAuthenticationService AuthenticationApi;
+ IPermissionsService PermissionsApi;
- public UsersController(IUserApi api, IAuthenticationApi authenticationApi, IPermissionsApi permissionsApi)
+ public UsersController(IUserService api, IAuthenticationService authenticationApi, IPermissionsService permissionsApi)
{
Api = api;
AuthenticationApi = authenticationApi;
@@ -26,10 +26,10 @@ namespace zero.Web.Controllers
}
- public async Task> GetById([FromQuery] string id) => Edit(await Api.GetUserById(id));
+ public async Task> GetById([FromQuery] string id) => Edit(await Api.GetUserById(id));
- public async Task> GetAll([FromQuery] ListBackofficeQuery query) => await Api.GetByQuery(query);
+ public async Task> GetAll([FromQuery] ListBackofficeQuery query) => await Api.GetByQuery(query);
public IList GetAllPermissions() => PermissionsApi.GetAll();
@@ -55,18 +55,18 @@ namespace zero.Web.Controllers
[ZeroAuthorize]
- public async Task> UpdatePassword([FromBody] UserPasswordEditModel model)
+ public async Task> UpdatePassword([FromBody] UserPasswordEditModel model)
{
- EntityResult result;
+ EntityResult result;
if (model.NewPassword != model.ConfirmNewPassword)
{
- result = EntityResult.Fail(nameof(model.NewPassword), "@errors.changepassword.newpasswordsnotmatching");
+ result = EntityResult.Fail(nameof(model.NewPassword), "@errors.changepassword.newpasswordsnotmatching");
}
else
{
- BackofficeUser user = await AuthenticationApi.GetUser();
- result = await Api.UpdatePassword(user as BackofficeUser, model.CurrentPassword, model.NewPassword);
+ ZeroUser user = await AuthenticationApi.GetUser();
+ result = await Api.UpdatePassword(user as ZeroUser, model.CurrentPassword, model.NewPassword);
if (result.IsSuccess)
{
@@ -81,37 +81,37 @@ namespace zero.Web.Controllers
[ZeroAuthorize]
public async Task> HashPassword([FromBody] UserPasswordEditModel model)
{
- BackofficeUser user = await Api.GetUserById(model.UserId);
+ ZeroUser user = await Api.GetUserById(model.UserId);
return await Api.HashPassword(user, model.CurrentPassword, model.NewPassword, model.ConfirmNewPassword);
}
[ZeroAuthorize(Permissions.Settings.Users, PermissionsValue.Update)]
- public async Task> Disable([FromBody] BackofficeUser model)
+ public async Task> Disable([FromBody] ZeroUser model)
{
- BackofficeUser entity = await Api.GetUserById(model.Id);
+ ZeroUser entity = await Api.GetUserById(model.Id);
return await Api.Disable(entity);
}
[ZeroAuthorize(Permissions.Settings.Users, PermissionsValue.Update)]
- public async Task> Enable([FromBody] BackofficeUser model)
+ public async Task> Enable([FromBody] ZeroUser model)
{
- BackofficeUser entity = await Api.GetUserById(model.Id);
+ ZeroUser entity = await Api.GetUserById(model.Id);
return await Api.Enable(entity);
}
[ZeroAuthorize(Permissions.Settings.Users, PermissionsValue.Update)]
- public async Task> Save([FromBody] BackofficeUser model) => await Api.Save(model);
+ public async Task> Save([FromBody] ZeroUser model) => await Api.Save(model);
[ZeroAuthorize(Permissions.Settings.Users, PermissionsValue.Update)]
// TODO do not need settings.users authorization for editing current user profiles
- public async Task> SaveCurrent([FromBody] BackofficeUser model) => await Api.Save(model);
+ public async Task> SaveCurrent([FromBody] ZeroUser model) => await Api.Save(model);
[ZeroAuthorize(Permissions.Settings.Users, PermissionsValue.Update)]
- public async Task> Delete([FromQuery] string id) => await Api.Delete(id);
+ public async Task> Delete([FromQuery] string id) => await Api.Delete(id);
}
}
diff --git a/zero.Backoffice/_legacy/Defaults/ZeroBackofficePlugin.cs b/zero.Backoffice/_legacy/Defaults/ZeroBackofficePlugin.cs
index 54fece7c..9238c5ab 100644
--- a/zero.Backoffice/_legacy/Defaults/ZeroBackofficePlugin.cs
+++ b/zero.Backoffice/_legacy/Defaults/ZeroBackofficePlugin.cs
@@ -19,16 +19,16 @@ namespace zero.Web.Defaults
//services.AddAll(typeof(IValidator<>), ServiceLifetime.Scoped);
//services.AddAll(typeof(IValidator), ServiceLifetime.Scoped);
- services.AddTransient();
- services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
services.AddTransient();
services.AddTransient();
services.AddTransient();
services.AddTransient();
- services.AddTransient();
+ services.AddTransient();
services.AddTransient();
- services.AddTransient();
+ services.AddTransient();
services.AddTransient();
services.AddTransient();
diff --git a/zero.Backoffice/zero.Backoffice.csproj b/zero.Backoffice/zero.Backoffice.csproj
index 5c89cc8e..085120f1 100644
--- a/zero.Backoffice/zero.Backoffice.csproj
+++ b/zero.Backoffice/zero.Backoffice.csproj
@@ -8,40 +8,6 @@
true
-
-
-
-
-
-
-
-
-
-
- PreserveNewest
- true
- PreserveNewest
-
-
- PreserveNewest
- true
- PreserveNewest
-
-
- PreserveNewest
- true
- PreserveNewest
-
-
- true
- PreserveNewest
-
-
- true
- PreserveNewest
-
-
-
diff --git a/zero.Core/Applications/ApplicationResolver.cs b/zero.Core/Applications/ApplicationResolver.cs
index f07e6890..9f9b3feb 100644
--- a/zero.Core/Applications/ApplicationResolver.cs
+++ b/zero.Core/Applications/ApplicationResolver.cs
@@ -64,13 +64,13 @@ public class ApplicationResolver : IApplicationResolver
///
public async Task ResolveFromUser(ClaimsPrincipal user)
{
- BackofficeUser userEntity = await GetBackofficeUser(user);
+ ZeroUser userEntity = await GetBackofficeUser(user);
return await ResolveFromUser(userEntity);
}
///
- public async Task ResolveFromUser(BackofficeUser user)
+ public async Task ResolveFromUser(ZeroUser user)
{
if (user == null)
{
@@ -181,12 +181,12 @@ public class ApplicationResolver : IApplicationResolver
///
/// Get backoffice user from claims principal
///
- async Task GetBackofficeUser(ClaimsPrincipal user)
+ async Task GetBackofficeUser(ClaimsPrincipal user)
{
string userId = user.FindFirstValue(Constants.Auth.Claims.UserId);
IAsyncDocumentSession session = Store.Session(global: true);
- return await session.LoadAsync(userId);
+ return await session.LoadAsync(userId);
}
}
@@ -224,5 +224,5 @@ public interface IApplicationResolver
/// Resolves the current application from a user.
/// This method won't return apps the user has no access to.
///
- Task ResolveFromUser(BackofficeUser user);
+ Task ResolveFromUser(ZeroUser user);
}
\ No newline at end of file
diff --git a/zero.Core/Applications/Module.cs b/zero.Core/Applications/Module.cs
deleted file mode 100644
index 9edd3587..00000000
--- a/zero.Core/Applications/Module.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using Microsoft.Extensions.DependencyInjection;
-
-namespace zero.Applications;
-
-internal class ApplicationsModule : ZeroModule
-{
- ///
- public override void Register(IZeroModuleConfiguration config)
- {
- config.Services.AddScoped();
- }
-}
\ No newline at end of file
diff --git a/zero.Core/Applications/ServiceCollectionExtensions.cs b/zero.Core/Applications/ServiceCollectionExtensions.cs
new file mode 100644
index 00000000..f0a444a8
--- /dev/null
+++ b/zero.Core/Applications/ServiceCollectionExtensions.cs
@@ -0,0 +1,12 @@
+using Microsoft.Extensions.DependencyInjection;
+
+namespace zero.Applications;
+
+internal static class ServiceCollectionExtensions
+{
+ public static IServiceCollection AddZeroApplications(this IServiceCollection services)
+ {
+ services.AddScoped();
+ return services;
+ }
+}
\ No newline at end of file
diff --git a/zero.Core/Architecture/Module.cs b/zero.Core/Architecture/Module.cs
deleted file mode 100644
index ff6163d2..00000000
--- a/zero.Core/Architecture/Module.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using Microsoft.Extensions.DependencyInjection;
-
-namespace zero.Applications;
-
-internal class ArchitectureModule : ZeroModule
-{
- ///
- public override void Register(IZeroModuleConfiguration config)
- {
- config.Services.AddScoped();
- config.Services.AddScoped();
- config.Services.AddScoped();
- }
-}
\ No newline at end of file
diff --git a/zero.Core/Architecture/Modules/ZeroModule.cs b/zero.Core/Architecture/Modules/ZeroModule.cs
deleted file mode 100644
index fe602b34..00000000
--- a/zero.Core/Architecture/Modules/ZeroModule.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace zero.Architecture;
-
-public class ZeroModule
-{
- public virtual void Register(IZeroModuleConfiguration config) { }
-
- public virtual void Configure(IZeroOptions options) { }
-}
\ No newline at end of file
diff --git a/zero.Core/Architecture/Modules/ZeroModuleConfiguration.cs b/zero.Core/Architecture/Modules/ZeroModuleConfiguration.cs
deleted file mode 100644
index 6f8267b8..00000000
--- a/zero.Core/Architecture/Modules/ZeroModuleConfiguration.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-
-namespace zero.Architecture;
-
-public class ZeroModuleConfiguration : IZeroModuleConfiguration
-{
- public IServiceCollection Services { get; }
-
- public IConfiguration Configuration { get; }
-
- public ZeroModuleConfiguration(IServiceCollection servicse, IConfiguration configuration)
- {
- Services = servicse;
- Configuration = configuration;
- }
-}
-
-
-public interface IZeroModuleConfiguration
-{
- IServiceCollection Services { get; }
-
- IConfiguration Configuration { get; }
-}
diff --git a/zero.Core/Architecture/Modules/ZeroModuleInitializer.cs b/zero.Core/Architecture/Modules/ZeroModuleInitializer.cs
deleted file mode 100644
index 6e95139d..00000000
--- a/zero.Core/Architecture/Modules/ZeroModuleInitializer.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-namespace zero.Architecture;
-
-internal class ZeroModuleInitializer
-{
- public static void RegisterAll(IZeroModuleConfiguration config)
- {
- foreach (ZeroModule module in Registrations.Modules)
- {
- module.Register(config);
- }
- }
-
- public static void ConfigureAll(IZeroOptions options)
- {
- foreach (ZeroModule module in Registrations.Modules)
- {
- module.Configure(options);
- }
- }
-}
\ No newline at end of file
diff --git a/zero.Core/Architecture/ServiceCollectionExtensions.cs b/zero.Core/Architecture/ServiceCollectionExtensions.cs
new file mode 100644
index 00000000..6a3a0f9a
--- /dev/null
+++ b/zero.Core/Architecture/ServiceCollectionExtensions.cs
@@ -0,0 +1,14 @@
+using Microsoft.Extensions.DependencyInjection;
+
+namespace zero.Architecture;
+
+internal static class ServiceCollectionExtensions
+{
+ public static IServiceCollection AddZeroBlueprints(this IServiceCollection services)
+ {
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ return services;
+ }
+}
\ No newline at end of file
diff --git a/zero.Core/Collections/CountriesCollection.cs b/zero.Core/Collections/CountriesCollection.cs
deleted file mode 100644
index 62b2aa80..00000000
--- a/zero.Core/Collections/CountriesCollection.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using FluentValidation;
-
-namespace zero.Collections;
-
-public class CountriesCollection : CachableEntityCollection, ICountriesCollection
-{
- public CountriesCollection(ICollectionContext context, ICollectionCache cache) : base(context, cache) { }
-
- ///
- protected override void ValidationRules(ZeroValidator validator)
- {
- validator.RuleFor(x => x.Code).Length(2).Unique(Session);
- validator.RuleFor(x => x.Name).Length(2, 120);
- }
-}
-
-
-public interface ICountriesCollection : IEntityCollection { }
\ No newline at end of file
diff --git a/zero.Core/Communication/Module.cs b/zero.Core/Communication/Module.cs
deleted file mode 100644
index 5e70c9e4..00000000
--- a/zero.Core/Communication/Module.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using Microsoft.Extensions.DependencyInjection;
-
-namespace zero.Applications;
-
-internal class CommunicationModule : ZeroModule
-{
- ///
- public override void Register(IZeroModuleConfiguration config)
- {
- config.Services.AddScoped();
- config.Services.AddSingleton();
- config.Services.AddTransient();
- }
-}
\ No newline at end of file
diff --git a/zero.Core/Communication/ServiceCollectionExtensions.cs b/zero.Core/Communication/ServiceCollectionExtensions.cs
new file mode 100644
index 00000000..7f08b1a4
--- /dev/null
+++ b/zero.Core/Communication/ServiceCollectionExtensions.cs
@@ -0,0 +1,15 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace zero.Communication;
+
+internal static class ServiceCollectionExtensions
+{
+ public static IServiceCollection AddZeroCommunication(this IServiceCollection services)
+ {
+ services.AddScoped();
+ services.AddSingleton();
+ services.AddTransient();
+ return services;
+ }
+}
\ No newline at end of file
diff --git a/zero.Core/Configuration/Integrations/IntegrationService.cs b/zero.Core/Configuration/Integrations/IntegrationService.cs
index 6fc81362..b48f888b 100644
--- a/zero.Core/Configuration/Integrations/IntegrationService.cs
+++ b/zero.Core/Configuration/Integrations/IntegrationService.cs
@@ -4,7 +4,7 @@ namespace zero.Configuration;
public class IntegrationService : IntegrationsCollection, IIntegrationService
{
- public IntegrationService(ICollectionContext context, ILogger logger) : base(context, logger)
+ public IntegrationService(IStoreContext context, ILogger logger) : base(context, logger)
{
Options = new(false);
}
diff --git a/zero.Core/Configuration/Module.cs b/zero.Core/Configuration/Module.cs
deleted file mode 100644
index 423fd347..00000000
--- a/zero.Core/Configuration/Module.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Options;
-
-namespace zero.Configuration;
-
-internal class ConfigurationModule : ZeroModule
-{
- ///
- public override void Register(IZeroModuleConfiguration config)
- {
- config.Services.AddOptions().Bind(config.Configuration.GetSection("Zero")).Configure(opts => { });
- config.Services.AddTransient(factory => factory.GetService>().CurrentValue);
- }
-}
\ No newline at end of file
diff --git a/zero.Core/Configuration/ServiceCollectionExtensions.cs b/zero.Core/Configuration/ServiceCollectionExtensions.cs
new file mode 100644
index 00000000..dbc3d293
--- /dev/null
+++ b/zero.Core/Configuration/ServiceCollectionExtensions.cs
@@ -0,0 +1,15 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+
+namespace zero.Configuration;
+
+internal static class ServiceCollectionExtensions
+{
+ public static IServiceCollection AddZeroConfiguration(this IServiceCollection services, IConfiguration config)
+ {
+ services.AddOptions().Bind(config.GetSection("Zero")).Configure(opts => { });
+ services.AddTransient(factory => factory.GetService>().CurrentValue);
+ return services;
+ }
+}
\ No newline at end of file
diff --git a/zero.Core/Context/Module.cs b/zero.Core/Context/Module.cs
deleted file mode 100644
index 0d0bf81c..00000000
--- a/zero.Core/Context/Module.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using Microsoft.Extensions.DependencyInjection;
-
-namespace zero.Applications;
-
-internal class ContextModule : ZeroModule
-{
- ///
- public override void Register(IZeroModuleConfiguration config)
- {
- config.Services.AddScoped();
- config.Services.AddHttpContextAccessor();
- }
-}
\ No newline at end of file
diff --git a/zero.Core/Context/ServiceCollectionExtensions.cs b/zero.Core/Context/ServiceCollectionExtensions.cs
new file mode 100644
index 00000000..ec97450b
--- /dev/null
+++ b/zero.Core/Context/ServiceCollectionExtensions.cs
@@ -0,0 +1,13 @@
+using Microsoft.Extensions.DependencyInjection;
+
+namespace zero.Context;
+
+internal static class ServiceCollectionExtensions
+{
+ public static IServiceCollection AddZeroContext(this IServiceCollection services)
+ {
+ services.AddScoped();
+ services.AddHttpContextAccessor();
+ return services;
+ }
+}
\ No newline at end of file
diff --git a/zero.Core/FileStorage/ServiceCollectionExtensions.cs b/zero.Core/FileStorage/ServiceCollectionExtensions.cs
new file mode 100644
index 00000000..82ac759b
--- /dev/null
+++ b/zero.Core/FileStorage/ServiceCollectionExtensions.cs
@@ -0,0 +1,13 @@
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace zero.FileStorage;
+
+internal static class ServiceCollectionExtensions
+{
+ public static IServiceCollection AddZeroFileStorage(this IServiceCollection services)
+ {
+ services.AddScoped(factory => new Paths(factory.GetService(), true));
+ return services;
+ }
+}
\ No newline at end of file
diff --git a/zero.Core/Identity/Api/AuthenticationApi.cs b/zero.Core/Identity/Api/AuthenticationApi.cs
deleted file mode 100644
index 4ec2e558..00000000
--- a/zero.Core/Identity/Api/AuthenticationApi.cs
+++ /dev/null
@@ -1,228 +0,0 @@
-using Microsoft.AspNetCore.Identity;
-using Raven.Client.Documents;
-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.Database;
-using zero.Core.Entities;
-using zero.Core.Extensions;
-using zero.Core.Identity;
-
-namespace zero.Core.Api
-{
- public class AuthenticationApi : IAuthenticationApi
- {
- protected IZeroContext Context { get; set; }
-
- protected SignInManager SignInManager { get; private set; }
-
- protected IZeroStore Store { get; set; }
-
-
- public AuthenticationApi(IZeroContext context, SignInManager signInManager, IZeroStore store)
- {
- Context = context;
- SignInManager = signInManager;
- Store = store;
- }
-
-
- ///
- public bool IsLoggedIn()
- {
- return SignInManager.IsSignedIn(Context.BackofficeUser);
- }
-
-
- ///
- public bool IsSuper()
- {
- return Context.BackofficeUser.HasClaim(Constants.Auth.Claims.IsSuper, PermissionsValue.True);
- }
-
-
- ///
- public bool IsAdmin()
- {
- return Context.BackofficeUser.HasClaim(Constants.Auth.Claims.Role, "administrator"); // TODO use constant (in setup as well)
- }
-
-
- ///
- public async Task GetUser()
- {
- return await SignInManager.UserManager.GetUserAsync(Context.BackofficeUser);
- }
-
-
- ///
- public IList GetPermissions(string prefix = null)
- {
- return Context.BackofficeUser.Claims
- .Where(claim => claim.Type == Constants.Auth.Claims.Permission && (prefix == null || claim.Value.StartsWith(prefix)))
- .Select(claim => Permission.FromClaim(claim, prefix))
- .ToList();
- }
-
-
- ///
- public Permission GetPermission(string key = null)
- {
- Claim claim = Context.BackofficeUser.Claims.FirstOrDefault(claim => claim.Type == Constants.Auth.Claims.Permission && claim.Value.StartsWith(key + ":"));
- return Permission.FromClaim(claim);
- }
-
-
- ///
- public async Task Login(string email, string password, bool isPersistent)
- {
- EntityResult result = new EntityResult();
-
- BackofficeUser user = await SignInManager.UserManager.FindByNameAsync(email);
-
- if (user == null)
- {
- result.AddError("@login.errors.wrongcredentials"); // TODO we don't need translations here, but return an enum, so the app itself can translate the error
- return result;
- }
- // TODO probably move this logic into a custom SignInManager which overrides CanSignInAsync()
- // see https://stackoverflow.com/a/35484758/670860
- else if (!user.IsActive)
- {
- result.AddError("@login.errors.disabled");
- return result;
- }
-
- SignInResult signInResult = await SignInManager.PasswordSignInAsync(user, password, isPersistent, true);
-
- if (!signInResult.Succeeded)
- {
- if (signInResult.IsLockedOut)
- {
- result.AddError("@login.errors.lockedout");
- }
- else if (signInResult.IsNotAllowed)
- {
- result.AddError("@login.errors.notallowed");
- }
- else if (signInResult.RequiresTwoFactor)
- {
- result.AddError("@login.errors.requirestwofactor");
- }
- else
- {
- result.AddError("@login.errors.wrongcredentials");
- }
-
- return result;
- }
-
- return EntityResult.Success();
- }
-
-
- ///
- public async Task Logout()
- {
- await SignInManager.SignOutAsync();
- }
-
-
- ///
- public string GetUserId()
- {
- return SignInManager.UserManager.GetUserId(Context.BackofficeUser);
- }
-
-
- ///
- public async Task TrySwitchApp(string appId)
- {
- IZeroDocumentSession session = Store.Session(global: true);
- BackofficeUser user = await GetUser();
-
- if (user == null || appId.IsNullOrEmpty())
- {
- return false;
- }
-
- string[] allowedAppIds = user.GetAllowedAppIds();
-
- bool isMainId = appId.Equals(user.AppId, StringComparison.InvariantCultureIgnoreCase);
- bool isAllowedId = allowedAppIds.Contains(appId, StringComparer.InvariantCultureIgnoreCase);
-
- if (user.IsSuper || isMainId || isAllowedId)
- {
- user.CurrentAppId = appId;
-
- //byte[] bytes = new byte[20];
- //RandomNumberGenerator.Fill(bytes);
- //user.SecurityStamp = Base32.ToBase32(bytes); // TODO update security stamp but Base32 is .net core internal
-
- await session.StoreAsync(user);
- await session.SaveChangesAsync();
-
- return true;
- }
-
- return false;
- }
- }
-
-
- public interface IAuthenticationApi
- {
- ///
- /// Get currently logged-in user
- ///
- Task GetUser();
-
- ///
- /// Whether a user is currently logged-in
- ///
- bool IsLoggedIn();
-
- ///
- /// Whether the current user is the super user who created the zero instance
- ///
- bool IsSuper();
-
- ///
- /// Whether the current user belongs to the administrator role (will always return false if this role gets deleted)
- ///
- bool IsAdmin();
-
- ///
- /// Logs a zero-user in and sets cookie
- ///
- Task Login(string email, string password, bool isPersistent);
-
- ///
- /// Logs out the current user
- ///
- Task Logout();
-
- ///
- /// Get the ID of the currently logged in user
- ///
- string GetUserId();
-
- ///
- /// Get all permissions for the current user with an optional prefix
- ///
- IList GetPermissions(string prefix = null);
-
- ///
- /// Get a single permissions by key
- ///
- Permission GetPermission(string key = null);
-
- ///
- /// Tries to switch the currently loaded backoffice application for the current user
- ///
- Task TrySwitchApp(string appId);
- }
-}
diff --git a/zero.Core/Identity/Api/AuthorizationApi.cs b/zero.Core/Identity/Api/AuthorizationApi.cs
deleted file mode 100644
index 74740c81..00000000
--- a/zero.Core/Identity/Api/AuthorizationApi.cs
+++ /dev/null
@@ -1,143 +0,0 @@
-using Microsoft.AspNetCore.Authentication;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Identity;
-using Raven.Client.Documents;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Security.Claims;
-using System.Threading.Tasks;
-using zero.Core.Database;
-using zero.Core.Entities;
-using zero.Core.Identity;
-
-namespace zero.Core.Api
-{
- public class AuthorizationApi : IAuthorizationApi
- {
- protected IHttpContextAccessor HttpContextAccessor { get; set; }
-
- protected SignInManager SignInManager { get; private set; }
-
- protected ClaimsPrincipal Principal => HttpContextAccessor.HttpContext?.User;
-
-
- public AuthorizationApi(IHttpContextAccessor httpContextAccessor, SignInManager signInManager)
- {
- HttpContextAccessor = httpContextAccessor;
- SignInManager = signInManager;
- }
-
-
- ///
- public bool IsLoggedIn()
- {
- ClaimsPrincipal principal = HttpContextAccessor.HttpContext.User;
-
- bool isAuthenticated = principal.Identity.IsAuthenticated;
- bool isZeroUser = principal.HasClaim(Constants.Auth.Claims.IsZero, PermissionsValue.True);
-
- bool isSignedIn = SignInManager.IsSignedIn(principal);
-
- return isAuthenticated && isZeroUser && isSignedIn;
- }
-
-
- ///
- public bool IsSuper()
- {
- return Principal.HasClaim(Constants.Auth.Claims.IsSuper, PermissionsValue.True);
- }
-
-
- ///
- public bool IsAdmin()
- {
- return Principal.HasClaim(Constants.Auth.Claims.Role, "administrator"); // TODO use constant (in setup as well)
- }
-
-
- ///
- public IList GetPermissions(string prefix = null)
- {
- return Principal.Claims
- .Where(claim => claim.Type == Constants.Auth.Claims.Permission && (prefix == null || claim.Value.StartsWith(prefix)))
- .Select(claim => Permission.FromClaim(claim, prefix))
- .ToList();
- }
-
-
- ///
- public Permission GetPermission(string key = null)
- {
- Claim claim = Principal.Claims.FirstOrDefault(claim => claim.Type == Constants.Auth.Claims.Permission && claim.Value.StartsWith(key + ":"));
-
- return Permission.FromClaim(claim);
- }
-
-
- public EntityPermission GetPermissionForEntity(T model, string permissionKey) where T : ZeroEntity
- {
- EntityPermission result = new EntityPermission();
-
- if (!IsLoggedIn())
- {
- return result;
- }
-
- Type type = typeof(T);
- bool isSuperUser = Principal.HasClaim(Constants.Auth.Claims.IsSuper, PermissionsValue.True);
-
- //result.IsAppAware = AppAwareType.IsAssignableFrom(type); // TODO appx
- //result.IsShareable = result.IsAppAware && AppAwareShareableType.IsAssignableFrom(type);
-
- if (isSuperUser)
- {
- result.CanCreate = true;
- result.CanCreateShared = result.CanCreate && result.IsShareable;
- result.CanEdit = true;
- result.CanRead = true;
- result.CanDelete = true;
- return result;
- }
-
- Permission permission = GetPermission(permissionKey);
-
- if (permission != null)
- {
-
- }
-
- return result;
- }
- }
-
-
- public interface IAuthorizationApi
- {
- ///
- /// Whether a user is currently logged-in
- ///
- bool IsLoggedIn();
-
- ///
- /// Whether the current user is the super user who created the zero instance
- ///
- bool IsSuper();
-
- ///
- /// Whether the current user belongs to the administrator role (will always return false if this role gets deleted)
- ///
- bool IsAdmin();
-
- ///
- /// Get all permissions for the current user with an optional prefix
- ///
- IList GetPermissions(string prefix = null);
-
- ///
- /// Get a single permissions by key
- ///
- public Permission GetPermission(string key = null);
- }
-}
diff --git a/zero.Core/Identity/Api/PermissionsApi.cs b/zero.Core/Identity/Api/PermissionsApi.cs
deleted file mode 100644
index a75bad96..00000000
--- a/zero.Core/Identity/Api/PermissionsApi.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using zero.Core.Entities;
-using zero.Core.Identity;
-using zero.Core.Options;
-
-namespace zero.Core.Api
-{
- public class PermissionsApi : IPermissionsApi
- {
- protected IZeroOptions Options { get; set; }
-
- public PermissionsApi(IZeroOptions options)
- {
- Options = options;
- }
-
-
- ///
- public IList GetAll()
- {
- List result = Options.Permissions.GetAllItems().ToList();
- PermissionCollection spaceCollection = result.FirstOrDefault(x => x.Alias == Constants.PermissionCollections.Spaces);
- PermissionCollection moduleCollection = result.FirstOrDefault(x => x.Alias == Constants.PermissionCollections.Modules);
-
- if (spaceCollection != null)
- {
- spaceCollection.Items.Clear();
-
- foreach (Space space in Options.Spaces.GetAllItems())
- {
- spaceCollection.Items.Add(new Permission(Permissions.Spaces.PREFIX + space.Alias, space.Name, null, PermissionValueType.CRUD));
- }
- }
-
- if (moduleCollection != null)
- {
- moduleCollection.Items.Clear();
-
- foreach (ModuleType module in Options.Modules.GetAllItems())
- {
- moduleCollection.Items.Add(new Permission(Permissions.Modules.PREFIX + module.Alias, module.Name, module.Description, PermissionValueType.CRUD));
- }
- }
-
- return result;
- }
- }
-
-
- public interface IPermissionsApi
- {
- ///
- /// Get all available permissions to choose from
- ///
- IList GetAll();
- }
-}
diff --git a/zero.Core/Identity/AuthenticationBuilderExtensions.cs b/zero.Core/Identity/AuthenticationBuilderExtensions.cs
index 18c26b93..89d0eba1 100644
--- a/zero.Core/Identity/AuthenticationBuilderExtensions.cs
+++ b/zero.Core/Identity/AuthenticationBuilderExtensions.cs
@@ -9,8 +9,8 @@ namespace zero.Identity;
public static class AuthenticationBuilderExtensions
{
public static AuthenticationBuilder AddZeroBackofficeCookie(this AuthenticationBuilder builder, Action> setupAction = null)
- where TUser : BackofficeUser
- where TRole : BackofficeUserRole
+ where TUser : ZeroUser
+ where TRole : ZeroUserRole
{
return builder.AddCookie(Constants.Auth.BackofficeScheme, Constants.Auth.BackofficeDisplayName, true, b =>
{
diff --git a/zero.Core/Identity/BackofficeUserExtensions.cs b/zero.Core/Identity/BackofficeUserExtensions.cs
index 74f4d0be..b11b67de 100644
--- a/zero.Core/Identity/BackofficeUserExtensions.cs
+++ b/zero.Core/Identity/BackofficeUserExtensions.cs
@@ -2,7 +2,7 @@
public static class BackofficeUserExtensions
{
- public static string[] GetAllowedAppIds(this BackofficeUser user)
+ public static string[] GetAllowedAppIds(this ZeroUser user)
{
if (user == null)
{
diff --git a/zero.Core/Identity/Collections/UserApi.cs b/zero.Core/Identity/Collections/UserApi.cs
deleted file mode 100644
index b09e6f0a..00000000
--- a/zero.Core/Identity/Collections/UserApi.cs
+++ /dev/null
@@ -1,283 +0,0 @@
-using Microsoft.AspNetCore.Identity;
-using Raven.Client.Documents;
-using Raven.Client.Documents.Linq;
-using Raven.Client.Documents.Session;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-using zero.Core.Collections;
-using zero.Core.Entities;
-using zero.Core.Extensions;
-
-namespace zero.Core.Api
-{
- public class UserApi : BackofficeApi, IUserApi
- {
- protected UserManager UserManager { get; private set; }
-
- public UserApi(ICollectionContext store, UserManager userManager) : base(store, isCoreDatabase: true)
- {
- UserManager = userManager;
- }
-
-
- ///
- public async Task GetUserById(string id)
- {
- BackofficeUser user = await UserManager.FindByIdAsync(id);
- return user;
- }
-
-
- ///
- public async Task GetUserByEmail(string email)
- {
- BackofficeUser user = await UserManager.FindByEmailAsync(email);
- return user;
- }
-
-
- ///
- public async Task> GetByIds(params string[] ids)
- {
- return await GetByIds(ids);
- }
-
-
- ///
- public async Task> GetAll()
- {
- return await Session.Query()
- .OrderByDescending(x => x.CreatedDate)
- .ToListAsync();
- }
-
-
- ///
- public async Task> GetByQuery(ListQuery query)
- {
- string currentUserId = UserManager.GetUserId(Context.Context.BackofficeUser);
-
- query.SearchSelector = user => user.Name;
-
- return await Session.Query()
- .ToQueriedListAsync(query);
- }
-
-
- ///
- public async Task> Save(BackofficeUser model)
- {
- bool updateSecurityStamp = false;
-
- if (!model.Id.IsNullOrEmpty())
- {
- BackofficeUser origin = await GetUserById(model.Id);
- updateSecurityStamp = origin != null && model.PasswordHash != origin.PasswordHash;
- }
-
- EntityResult result = await SaveModel(model); //, new UserValidator());
-
- if (updateSecurityStamp)
- {
- await UserManager.UpdateSecurityStampAsync(model);
- }
-
- return result;
- }
-
-
- ///
- public async Task> Delete(string id)
- {
- return await DeleteById(id);
- }
-
-
- ///
- public async Task> HashPassword(BackofficeUser user, string currentPassword, string newPassword, string confirmNewPassword)
- {
- if (newPassword != confirmNewPassword)
- {
- return EntityResult.Fail(nameof(newPassword), "@errors.changepassword.newpasswordsnotmatching");
- }
-
- if (currentPassword.IsNullOrWhiteSpace() || newPassword.IsNullOrWhiteSpace())
- {
- return EntityResult.Fail(nameof(currentPassword), "@errors.changepassword.emptyfields");
- }
-
- if (user == null)
- {
- return EntityResult.Fail("@errors.changepassword.nouser");
- }
-
- if (UserManager.PasswordHasher.VerifyHashedPassword(user, user.PasswordHash, currentPassword) != PasswordVerificationResult.Success)
- {
- return EntityResult.Fail("@errors.changepassword.passwordincorrect");
- }
-
- // validate new password
- List errors = new();
- bool isValid = true;
- foreach (var v in UserManager.PasswordValidators)
- {
- var result = await v.ValidateAsync(UserManager, user, newPassword);
- if (!result.Succeeded)
- {
- if (result.Errors.Any())
- {
- errors.AddRange(result.Errors);
- }
-
- isValid = false;
- }
- }
-
- if (!isValid)
- {
- EntityResult result = EntityResult.Fail();
- foreach (IdentityError error in errors)
- {
- result.AddError(error.Description);
- }
- return result;
- }
-
- return EntityResult.Success(UserManager.PasswordHasher.HashPassword(user, newPassword));
- }
-
-
- ///
- public async Task> UpdatePassword(BackofficeUser user, string currentPassword, string newPassword)
- {
- if (currentPassword.IsNullOrWhiteSpace() || newPassword.IsNullOrWhiteSpace())
- {
- return EntityResult.Fail(nameof(currentPassword), "@errors.changepassword.emptyfields");
- }
-
- if (user == null)
- {
- return EntityResult.Fail("@errors.changepassword.nouser");
- }
-
- IdentityResult identityResult = await UserManager.ChangePasswordAsync(user as BackofficeUser, currentPassword, newPassword);
-
- if (!identityResult.Succeeded)
- {
- EntityResult result = EntityResult.Fail();
-
- foreach (IdentityError error in identityResult.Errors)
- {
- result.AddError(error.Description);
- }
-
- return result;
- }
-
- return EntityResult.Success(user);
- }
-
-
- ///
- public async Task> Enable(BackofficeUser user)
- {
- return await UpdateActiveState(user, true);
- }
-
-
- ///
- public async Task> Disable(BackofficeUser user)
- {
- return await UpdateActiveState(user, false);
- }
-
-
- ///
- /// Updates the active state of user.
- /// If IsActive=false, the user cannot login anymore
- ///
- async Task> UpdateActiveState(BackofficeUser user, bool isActive)
- {
- user.IsActive = isActive;
-
- IdentityResult identityResult = await UserManager.UpdateAsync(user as BackofficeUser);
-
- if (!identityResult.Succeeded)
- {
- EntityResult result = EntityResult.Fail();
-
- foreach (IdentityError error in identityResult.Errors)
- {
- result.AddError(error.Description);
- }
-
- return result;
- }
-
- await UserManager.UpdateSecurityStampAsync(user as BackofficeUser);
-
- return EntityResult.Success(user);
- }
- }
-
-
- public interface IUserApi : IBackofficeApi
- {
- ///
- /// Find user by id
- ///
- Task GetUserById(string id);
-
- ///
- /// Find user by email
- ///
- Task GetUserByEmail(string email);
-
- ///
- /// Get users by ids
- ///
- Task> GetByIds(params string[] ids);
-
- ///
- /// Get all users for the selected application
- ///
- Task> GetAll();
-
- ///
- /// Get all available users (with query)
- ///
- Task> GetByQuery(ListQuery query);
-
- ///
- /// Creates or updates a user
- ///
- Task> Save(BackofficeUser model);
-
- ///
- /// Deletes a user
- ///
- Task> Delete(string id);
-
- ///
- /// Changes the password of the current user.
- /// User is logged out if this operation succeeds.
- ///
- Task> UpdatePassword(BackofficeUser user, string currentPassword, string newPassword);
-
- ///
- /// Tries to hash a new password
- ///
- Task> HashPassword(BackofficeUser user, string currentPassword, string newPassword, string confirmNewPassword);
-
- ///
- /// Enables a user
- ///
- Task> Enable(BackofficeUser user);
-
- ///
- /// Disables a user
- ///
- Task> Disable(BackofficeUser user);
- }
-}
diff --git a/zero.Core/Identity/Collections/UserRolesApi.cs b/zero.Core/Identity/Collections/UserRolesApi.cs
deleted file mode 100644
index 8a6c6317..00000000
--- a/zero.Core/Identity/Collections/UserRolesApi.cs
+++ /dev/null
@@ -1,125 +0,0 @@
-using FluentValidation.Results;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Identity;
-using Raven.Client.Documents;
-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.Collections;
-using zero.Core.Entities;
-using zero.Core.Extensions;
-using zero.Core.Identity;
-using zero.Core.Validation;
-
-namespace zero.Core.Api
-{
- public class UserRolesApi : BackofficeApi, IUserRolesApi
- {
- protected IHttpContextAccessor HttpContextAccessor { get; set; }
-
- protected UserManager UserManager { get; private set; }
-
- protected RoleManager RoleManager { get; private set; }
-
- private ClaimsPrincipal Principal => HttpContextAccessor.HttpContext?.User;
-
-
- public UserRolesApi(IHttpContextAccessor httpContextAccessor, UserManager userManager, RoleManager roleManager, ICollectionContext store) : base(store, isCoreDatabase: true)
- {
- HttpContextAccessor = httpContextAccessor;
- UserManager = userManager;
- RoleManager = roleManager;
- }
-
-
- ///
- public async Task> GetAll()
- {
- return await Session.Query().OrderBy(x => x.Sort).ThenBy(x => x.Name).ToListAsync();
- }
-
-
- ///
- public async Task GetById(string id)
- {
- return await RoleManager.FindByIdAsync(id);
- }
-
-
- ///
- public async Task> Save(BackofficeUserRole model)
- {
- ValidationResult validation = await new UserRoleValidator().ValidateAsync(model);
-
- if (!validation.IsValid)
- {
- return EntityResult.Fail(validation);
- }
-
- if (model.Id.IsNullOrEmpty())
- {
- model.CreatedDate = DateTimeOffset.Now;
- }
-
- model.Alias = Safenames.Alias(model.Name);
-
- await Session.StoreAsync(model);
-
- string id = Session.Advanced.GetDocumentId(model);
-
- await Session.SaveChangesAsync();
-
- if (model.Id.IsNullOrEmpty())
- {
- model.Id = id;
- }
-
- return EntityResult.Success(model);
- }
-
-
- ///
- public async Task> Delete(string id)
- {
- BackofficeUserRole country = await Session.LoadAsync(id);
-
- if (country == null)
- {
- return EntityResult.Fail("@errors.ondelete.idnotfound");
- }
-
- Session.Delete(country);
-
- await Session.SaveChangesAsync();
-
- return EntityResult.Success();
- }
- }
-
-
- public interface IUserRolesApi : IBackofficeApi
- {
- ///
- /// Get all user roles
- ///
- Task> GetAll();
-
- ///
- /// Get role by id
- ///
- Task GetById(string id);
-
- ///
- /// Create or update a role
- ///
- Task> Save(BackofficeUserRole model);
-
- ///
- /// Deletes a role
- ///
- Task> Delete(string id);
- }
-}
diff --git a/zero.Core/Identity/IdentityModule.cs b/zero.Core/Identity/IdentityModule.cs
deleted file mode 100644
index 6b176025..00000000
--- a/zero.Core/Identity/IdentityModule.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using Microsoft.AspNetCore.Authentication.Cookies;
-using Microsoft.AspNetCore.Identity;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.DependencyInjection.Extensions;
-using Microsoft.Extensions.Options;
-
-namespace zero.Identity;
-
-internal class IdentityModule : ZeroModule
-{
- ///
- public override void Register(IZeroModuleConfiguration config)
- {
- config.Services.TryAddEnumerable(ServiceDescriptor.Singleton, PostConfigureCookieAuthenticationOptions>());
-
- config.Services.AddZeroIdentity();
- config.Services.Replace, ZeroBackofficeClaimsPrincipalFactory>();
- config.Services.Replace, RavenCoreUserStore>(ServiceLifetime.Scoped);
- config.Services.Replace, RavenCoreRoleStore>(ServiceLifetime.Scoped);
-
- config.Services.AddAuthentication(Constants.Auth.BackofficeScheme);
- //.AddZeroBackofficeCookie(); // TODO
-
- config.Services.AddAuthorization();
- }
-
- ///
- public override void Configure(IZeroOptions options)
- {
-
- }
-}
\ No newline at end of file
diff --git a/zero.Core/Identity/Models/UserClaim.cs b/zero.Core/Identity/Models/UserClaim.cs
index 523e4b76..ed3aad62 100644
--- a/zero.Core/Identity/Models/UserClaim.cs
+++ b/zero.Core/Identity/Models/UserClaim.cs
@@ -1,5 +1,4 @@
-using System.Collections.Generic;
-using System.Security.Claims;
+using System.Security.Claims;
namespace zero.Identity;
@@ -18,7 +17,7 @@ public class UserClaim
///
/// Convert to a claim
///
- public Claim ToClaim() => new Claim(Type, Value);
+ public Claim ToClaim() => new(Type, Value);
public UserClaim() { }
@@ -39,19 +38,4 @@ public class UserClaim
Type = claim?.Type;
Value = claim?.Value;
}
-}
-
-
-
-public class UserClaimComparer : IEqualityComparer
-{
- public bool Equals(UserClaim x, UserClaim y)
- {
- return (x == null && y == null) || (x.Type.Equals(y.Type, StringComparison.InvariantCultureIgnoreCase) && x.Value.Equals(y.Value, StringComparison.InvariantCultureIgnoreCase));
- }
-
- public int GetHashCode(UserClaim obj)
- {
- return (obj.Type + obj.Value).GetHashCode();
- }
-}
+}
\ No newline at end of file
diff --git a/zero.Core/Identity/Models/UserClaimComparer.cs b/zero.Core/Identity/Models/UserClaimComparer.cs
new file mode 100644
index 00000000..8765ff60
--- /dev/null
+++ b/zero.Core/Identity/Models/UserClaimComparer.cs
@@ -0,0 +1,14 @@
+namespace zero.Identity;
+
+public class UserClaimComparer : IEqualityComparer
+{
+ public bool Equals(UserClaim x, UserClaim y)
+ {
+ return (x == null && y == null) || (x.Type.Equals(y.Type, StringComparison.InvariantCultureIgnoreCase) && x.Value.Equals(y.Value, StringComparison.InvariantCultureIgnoreCase));
+ }
+
+ public int GetHashCode(UserClaim obj)
+ {
+ return (obj.Type + obj.Value).GetHashCode();
+ }
+}
diff --git a/zero.Core/Identity/Models/ZeroIdentityRole.cs b/zero.Core/Identity/Models/ZeroIdentityRole.cs
index 8a1cf5e8..0f78fbbb 100644
--- a/zero.Core/Identity/Models/ZeroIdentityRole.cs
+++ b/zero.Core/Identity/Models/ZeroIdentityRole.cs
@@ -1,6 +1,4 @@
-using System.Collections.Generic;
-
-namespace zero.Identity;
+namespace zero.Identity;
public abstract class ZeroIdentityRole : ZeroEntity
{
diff --git a/zero.Core/Identity/Models/ZeroIdentityUser.cs b/zero.Core/Identity/Models/ZeroIdentityUser.cs
index 96863743..c29dea33 100644
--- a/zero.Core/Identity/Models/ZeroIdentityUser.cs
+++ b/zero.Core/Identity/Models/ZeroIdentityUser.cs
@@ -1,6 +1,4 @@
-using System.Collections.Generic;
-
-namespace zero.Identity;
+namespace zero.Identity;
public abstract class ZeroIdentityUser : ZeroEntity
{
diff --git a/zero.Core/Identity/Models/BackofficeUser.cs b/zero.Core/Identity/Models/ZeroUser.cs
similarity index 74%
rename from zero.Core/Identity/Models/BackofficeUser.cs
rename to zero.Core/Identity/Models/ZeroUser.cs
index 513787e3..f06444c4 100644
--- a/zero.Core/Identity/Models/BackofficeUser.cs
+++ b/zero.Core/Identity/Models/ZeroUser.cs
@@ -1,7 +1,10 @@
namespace zero.Identity;
+///
+/// A zero user can access the zero API and backoffice by granting the necessary permissions
+///
[RavenCollection("Users")]
-public class BackofficeUser : ZeroIdentityUser
+public class ZeroUser : ZeroIdentityUser
{
///
/// Application the user registered in
diff --git a/zero.Core/Identity/Models/BackofficeUserRole.cs b/zero.Core/Identity/Models/ZeroUserRole.cs
similarity index 84%
rename from zero.Core/Identity/Models/BackofficeUserRole.cs
rename to zero.Core/Identity/Models/ZeroUserRole.cs
index 542d5551..930cb8d0 100644
--- a/zero.Core/Identity/Models/BackofficeUserRole.cs
+++ b/zero.Core/Identity/Models/ZeroUserRole.cs
@@ -1,7 +1,7 @@
namespace zero.Identity;
[RavenCollection("Roles")]
-public class BackofficeUserRole : ZeroIdentityRole
+public class ZeroUserRole : ZeroIdentityRole
{
///
/// Additional description
diff --git a/zero.Core/Identity/RavenRoleStore(TRole).cs b/zero.Core/Identity/RavenRoleStore(TRole).cs
index 38329af4..5139d1eb 100644
--- a/zero.Core/Identity/RavenRoleStore(TRole).cs
+++ b/zero.Core/Identity/RavenRoleStore(TRole).cs
@@ -2,10 +2,7 @@
using Raven.Client.Documents;
using Raven.Client.Documents.Linq;
using Raven.Client.Exceptions;
-using System.Collections.Generic;
using System.Security.Claims;
-using System.Threading;
-using System.Threading.Tasks;
namespace zero.Identity;
diff --git a/zero.Core/Identity/RavenUserStore(TUser).cs b/zero.Core/Identity/RavenUserStore(TUser).cs
index 49b98e5e..27f66d50 100644
--- a/zero.Core/Identity/RavenUserStore(TUser).cs
+++ b/zero.Core/Identity/RavenUserStore(TUser).cs
@@ -2,11 +2,7 @@
using Raven.Client.Documents;
using Raven.Client.Documents.Linq;
using Raven.Client.Documents.Session;
-using System.Collections.Generic;
-using System.Linq;
using System.Security.Claims;
-using System.Threading;
-using System.Threading.Tasks;
namespace zero.Identity;
diff --git a/zero.Core/Identity/RavenUserStore(TUser,TRole).cs b/zero.Core/Identity/RavenUserStore(TUser,TRole).cs
index 7a1d45b3..6ee0791a 100644
--- a/zero.Core/Identity/RavenUserStore(TUser,TRole).cs
+++ b/zero.Core/Identity/RavenUserStore(TUser,TRole).cs
@@ -1,7 +1,6 @@
using Microsoft.AspNetCore.Identity;
using Raven.Client.Documents;
using Raven.Client.Documents.Linq;
-using System.Linq;
namespace zero.Identity;
diff --git a/zero.Core/Identity/Security/SchemedSignInManager.cs b/zero.Core/Identity/Security/SchemedSignInManager.cs
index 775a0e89..d9c6f0c3 100644
--- a/zero.Core/Identity/Security/SchemedSignInManager.cs
+++ b/zero.Core/Identity/Security/SchemedSignInManager.cs
@@ -3,10 +3,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
-using System.Collections.Generic;
-using System.Linq;
using System.Security.Claims;
-using System.Threading.Tasks;
namespace zero.Identity;
diff --git a/zero.Core/Identity/Security/ZeroAuthorizeAttribute.cs b/zero.Core/Identity/Security/ZeroAuthorizeAttribute.cs
index d2370f9f..8cd27988 100644
--- a/zero.Core/Identity/Security/ZeroAuthorizeAttribute.cs
+++ b/zero.Core/Identity/Security/ZeroAuthorizeAttribute.cs
@@ -1,7 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
-using System.Collections.Generic;
-using System.Linq;
using System.Security.Claims;
namespace zero.Identity;
diff --git a/zero.Core/Identity/Security/ZeroBackofficeClaimsPrincipalFactory.cs b/zero.Core/Identity/Security/ZeroBackofficeClaimsPrincipalFactory.cs
index 1cb61b12..636fd5c5 100644
--- a/zero.Core/Identity/Security/ZeroBackofficeClaimsPrincipalFactory.cs
+++ b/zero.Core/Identity/Security/ZeroBackofficeClaimsPrincipalFactory.cs
@@ -1,15 +1,12 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
-using System.Collections.Generic;
-using System.Linq;
using System.Security.Claims;
-using System.Threading.Tasks;
namespace zero.Identity;
public class ZeroBackofficeClaimsPrincipalFactory : ZeroClaimsPrinicipalFactory
- where TUser : BackofficeUser
- where TRole : BackofficeUserRole
+ where TUser : ZeroUser
+ where TRole : ZeroUserRole
{
public ZeroBackofficeClaimsPrincipalFactory(UserManager userManager, RoleManager roleManager, IOptions optionsAccessor, IOptions> authOptions, IZeroContext zero)
: base(userManager, roleManager, optionsAccessor, authOptions, zero)
diff --git a/zero.Core/Identity/Security/ZeroClaimsPrinicipalFactory.cs b/zero.Core/Identity/Security/ZeroClaimsPrinicipalFactory.cs
index 52a2f1d8..7babb1b5 100644
--- a/zero.Core/Identity/Security/ZeroClaimsPrinicipalFactory.cs
+++ b/zero.Core/Identity/Security/ZeroClaimsPrinicipalFactory.cs
@@ -1,8 +1,6 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
-using System.Collections.Generic;
using System.Security.Claims;
-using System.Threading.Tasks;
namespace zero.Identity;
diff --git a/zero.Core/Identity/ServiceCollectionExtensions.cs b/zero.Core/Identity/ServiceCollectionExtensions.cs
new file mode 100644
index 00000000..76ff8c85
--- /dev/null
+++ b/zero.Core/Identity/ServiceCollectionExtensions.cs
@@ -0,0 +1,31 @@
+using Microsoft.AspNetCore.Authentication.Cookies;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Options;
+
+namespace zero.Identity;
+
+internal static class ServiceCollectionExtensions
+{
+ public static IServiceCollection AddZeroIdentity(this IServiceCollection services)
+ {
+ services.TryAddEnumerable(ServiceDescriptor.Singleton, PostConfigureCookieAuthenticationOptions>());
+
+ services.AddZeroIdentity();
+ services.Replace, ZeroBackofficeClaimsPrincipalFactory>();
+ services.Replace, RavenCoreUserStore>(ServiceLifetime.Scoped);
+ services.Replace, RavenCoreRoleStore>(ServiceLifetime.Scoped);
+
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+
+ services.AddAuthentication(Constants.Auth.BackofficeScheme);
+ //.AddZeroBackofficeCookie(); // TODO
+
+ services.AddAuthorization();
+ return services;
+ }
+}
\ No newline at end of file
diff --git a/zero.Core/Identity/Services/AuthenticationService.cs b/zero.Core/Identity/Services/AuthenticationService.cs
new file mode 100644
index 00000000..403b26cf
--- /dev/null
+++ b/zero.Core/Identity/Services/AuthenticationService.cs
@@ -0,0 +1,218 @@
+using Microsoft.AspNetCore.Identity;
+
+namespace zero.Identity;
+
+public class AuthenticationService : IAuthenticationService
+{
+ protected IZeroContext Context { get; set; }
+
+ protected SignInManager SignInManager { get; private set; }
+
+ protected IZeroStore Store { get; set; }
+
+
+ public AuthenticationService(IZeroContext context, SignInManager signInManager, IZeroStore store)
+ {
+ Context = context;
+ SignInManager = signInManager;
+ Store = store;
+ }
+
+
+ ///
+ public bool IsLoggedIn()
+ {
+ return SignInManager.IsSignedIn(Context.BackofficeUser);
+ }
+
+
+ ///
+ public bool IsSuper()
+ {
+ return Context.BackofficeUser.HasClaim(Constants.Auth.Claims.IsSuper, PermissionsValue.True);
+ }
+
+
+ ///
+ public bool IsAdmin()
+ {
+ return Context.BackofficeUser.HasClaim(Constants.Auth.Claims.Role, "administrator"); // TODO use constant (in setup as well)
+ }
+
+
+ ///
+ public async Task GetUser()
+ {
+ return await SignInManager.UserManager.GetUserAsync(Context.BackofficeUser);
+ }
+
+
+ ///
+ public IList GetPermissions(string prefix = null)
+ {
+ return new List(); // TODO
+ //return Context.BackofficeUser.Claims
+ // .Where(claim => claim.Type == Constants.Auth.Claims.Permission && (prefix == null || claim.Value.StartsWith(prefix)))
+ // .Select(claim => Permission.FromClaim(claim, prefix))
+ // .ToList();
+ }
+
+
+ ///
+ public Permission GetPermission(string key = null)
+ {
+ return new Permission();
+ //Claim claim = Context.BackofficeUser.Claims.FirstOrDefault(claim => claim.Type == Constants.Auth.Claims.Permission && claim.Value.StartsWith(key + ":"));
+ //return Permission.FromClaim(claim);
+ }
+
+
+ ///
+ public async Task Login(string email, string password, bool isPersistent)
+ {
+ EntityResult result = new EntityResult();
+
+ ZeroUser user = await SignInManager.UserManager.FindByNameAsync(email);
+
+ if (user == null)
+ {
+ result.AddError("@login.errors.wrongcredentials"); // TODO we don't need translations here, but return an enum, so the app itself can translate the error
+ return result;
+ }
+ // TODO probably move this logic into a custom SignInManager which overrides CanSignInAsync()
+ // see https://stackoverflow.com/a/35484758/670860
+ else if (!user.IsActive)
+ {
+ result.AddError("@login.errors.disabled");
+ return result;
+ }
+
+ SignInResult signInResult = await SignInManager.PasswordSignInAsync(user, password, isPersistent, true);
+
+ if (!signInResult.Succeeded)
+ {
+ if (signInResult.IsLockedOut)
+ {
+ result.AddError("@login.errors.lockedout");
+ }
+ else if (signInResult.IsNotAllowed)
+ {
+ result.AddError("@login.errors.notallowed");
+ }
+ else if (signInResult.RequiresTwoFactor)
+ {
+ result.AddError("@login.errors.requirestwofactor");
+ }
+ else
+ {
+ result.AddError("@login.errors.wrongcredentials");
+ }
+
+ return result;
+ }
+
+ return EntityResult.Success();
+ }
+
+
+ ///
+ public async Task Logout()
+ {
+ await SignInManager.SignOutAsync();
+ }
+
+
+ ///
+ public string GetUserId()
+ {
+ return SignInManager.UserManager.GetUserId(Context.BackofficeUser);
+ }
+
+
+ ///
+ public async Task TrySwitchApp(string appId)
+ {
+ IZeroDocumentSession session = Store.Session(global: true);
+ ZeroUser user = await GetUser();
+
+ if (user == null || appId.IsNullOrEmpty())
+ {
+ return false;
+ }
+
+ string[] allowedAppIds = user.GetAllowedAppIds();
+
+ bool isMainId = appId.Equals(user.AppId, StringComparison.InvariantCultureIgnoreCase);
+ bool isAllowedId = allowedAppIds.Contains(appId, StringComparer.InvariantCultureIgnoreCase);
+
+ if (user.IsSuper || isMainId || isAllowedId)
+ {
+ user.CurrentAppId = appId;
+
+ //byte[] bytes = new byte[20];
+ //RandomNumberGenerator.Fill(bytes);
+ //user.SecurityStamp = Base32.ToBase32(bytes); // TODO update security stamp but Base32 is .net core internal
+
+ await session.StoreAsync(user);
+ await session.SaveChangesAsync();
+
+ return true;
+ }
+
+ return false;
+ }
+}
+
+
+public interface IAuthenticationService
+{
+ ///
+ /// Get currently logged-in user
+ ///
+ Task GetUser();
+
+ ///
+ /// Whether a user is currently logged-in
+ ///
+ bool IsLoggedIn();
+
+ ///
+ /// Whether the current user is the super user who created the zero instance
+ ///
+ bool IsSuper();
+
+ ///
+ /// Whether the current user belongs to the administrator role (will always return false if this role gets deleted)
+ ///
+ bool IsAdmin();
+
+ ///
+ /// Logs a zero-user in and sets cookie
+ ///
+ Task Login(string email, string password, bool isPersistent);
+
+ ///
+ /// Logs out the current user
+ ///
+ Task Logout();
+
+ ///
+ /// Get the ID of the currently logged in user
+ ///
+ string GetUserId();
+
+ ///
+ /// Get all permissions for the current user with an optional prefix
+ ///
+ IList GetPermissions(string prefix = null);
+
+ ///
+ /// Get a single permissions by key
+ ///
+ Permission GetPermission(string key = null);
+
+ ///
+ /// Tries to switch the currently loaded backoffice application for the current user
+ ///
+ Task TrySwitchApp(string appId);
+}
\ No newline at end of file
diff --git a/zero.Core/Identity/Services/AuthorizationService.cs b/zero.Core/Identity/Services/AuthorizationService.cs
new file mode 100644
index 00000000..09f9f899
--- /dev/null
+++ b/zero.Core/Identity/Services/AuthorizationService.cs
@@ -0,0 +1,135 @@
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Identity;
+using System.Security.Claims;
+
+namespace zero.Identity;
+
+public class AuthorizationService : IAuthorizationService
+{
+ protected IHttpContextAccessor HttpContextAccessor { get; set; }
+
+ protected SignInManager SignInManager { get; private set; }
+
+ protected ClaimsPrincipal Principal => HttpContextAccessor.HttpContext?.User;
+
+
+ public AuthorizationService(IHttpContextAccessor httpContextAccessor, SignInManager signInManager)
+ {
+ HttpContextAccessor = httpContextAccessor;
+ SignInManager = signInManager;
+ }
+
+
+ ///