diff --git a/docs/authentication.md b/docs/authentication.md
new file mode 100644
index 0000000..469ceee
--- /dev/null
+++ b/docs/authentication.md
@@ -0,0 +1,51 @@
+# HopFrame Authentication
+
+HopFrame uses a token system with a short term access token and a long term refresh token for authenticating users.
+These tokens are usually provided to the endpoints of the API / Blazor Pages through Cookies:
+
+| Cookie key | Cookie value sample | Description |
+|--------------------------------|----------------------------------------|-----------------------------|
+| HopFrame.Security.RefreshToken | `42047983-914d-418b-841a-4382614231be` | The long term refresh token |
+| HopFrame.Security.AccessToken | `d39c9432-0831-42df-8844-5e2b70f03eda` | The short term access token |
+
+The advantage of these cookies is that they are automatically set by the backend and delete themselves, when they are
+no longer valid.
+
+The access token can also be delivered through a header called `HopFrame.Authentication` or `Token`.
+It can also be delivered through a query parameter called `token`. This simplifies requests for images for example
+because you can directly specify the url in the img tag in html.
+
+## Authentication configuration
+
+You can also configure the time span that the tokens are valid using the `appsettings.json` or environment variables
+by configuring your configuration to load these.
+>**Hint**: Configuring your application to use environment variables works by simply adding
+> `builder.Configuration.AddEnvironmentVariables();` to your startup configuration before you add the
+> custom configurations / HopFrame services.
+
+### Example
+
+You can specify `Seconds`, `Minutes`, `Hours` and `Days` for either of the two token types.
+These get combined to a single time span.
+
+#### Configuration example
+```json
+ "HopFrame": {
+ "Authentication": {
+ "AccessToken": {
+ "Minutes": 30
+ },
+ "RefreshToken": {
+ "Days": 10,
+ "Hours": 5
+ }
+ }
+ }
+```
+
+#### Environment variables example
+```dotenv
+HOPFRAME__AUTHENTICATION__ACCESSTOKEN__MINUTES=30
+HOPFRAME__AUTHENTICATION__REFRESHTOKEN__DAYS=10
+HOPFRAME__AUTHENTICATION__REFRESHTOKEN__HOURS=5
+```
diff --git a/docs/permissions.md b/docs/permissions.md
new file mode 100644
index 0000000..12a17cc
--- /dev/null
+++ b/docs/permissions.md
@@ -0,0 +1,80 @@
+# HopFrame Permissions
+
+Permissions in the HopFrame are simple and effective to use.
+As discussed in the [repositories](./repositories.md) documentation, you can manage user / group permissions
+via the `IPermissionRepository` service.
+
+## How do permissions work in the HopFrame
+
+Permissions are defined using the . (dot) syntax. This enables you to nest permissions in namespaces.
+You can also give a user or a group the permission to every permission in a namespace by using the * (star) syntax.
+
+| Permission | Example | Description |
+|----------------------|-------------------------------|-------------------------------------------------------|
+| `*` | `*` | all permissions |
+| `[namespace].[name]` | `hopframe.admin.users.create` | single permission |
+| `[namespace].*` | `hopframe.admin.*` | all permissions in that namespace (works recursively) |
+
+### Reserved namespaces
+
+| Namespace | Example | Description |
+|-----------|---------------|------------------------------------------|
+| `group` | `group.admin` | The user needs to be in a specific group |
+
+### Permission Groups
+
+You can manage them through the `IGroupRepository` as described in the [repositories](./repositories.md) documentation.
+You add permissions just like you would to a user with the `IPermissionRepository`.
+You can assign a user to a group by assigning the group permission to the user:
+```csharp
+permissionRepository.AddPermission(user, "group.admin");
+```
+
+## Predefined Permissions
+
+| Permission | Description |
+|--------------------------------|-------------------------------|
+| `hopframe.admin` | Access to the admin dashboard |
+| `hopframe.admin.users.read` | View all users |
+| `hopframe.admin.users.update` | Edit a user |
+| `hopframe.admin.users.delete` | Delete a user |
+| `hopframe.admin.users.create` | Add a group |
+| `hopframe.admin.groups.read` | View all groups |
+| `hopframe.admin.groups.update` | Edit a group |
+| `hopframe.admin.groups.delete` | Delete a group |
+| `hopframe.admin.groups.create` | Add a group |
+
+### Configuring HopFrame permissions
+
+You can also configure the predefined permissions using the `appsettings.json` or environment variables
+by configuring your configuration to load these.
+>**Hint**: Configuring your application to use environment variables works by simply adding
+> `builder.Configuration.AddEnvironmentVariables();` to your startup configuration before you add the
+> custom configurations / HopFrame services.
+
+You can specify `Dashboard` for the dashboard permission and for `Users` and `Groups` you can specify
+`Create`, `Read`, `Update` and `Delete` permissions.
+
+#### Configuration example
+```json
+ "HopFrame": {
+ "Permissions": {
+ "Dashboard": "myapp.dashboard.view",
+ "Users": {
+ "Read": "myapp.read.users"
+ },
+ "Groups": {
+ "Create": "myapp.create.groups",
+ "Update": "myapp.update.groups"
+ }
+ }
+ }
+```
+
+#### Environment variables example
+```dotenv
+HOPFRAME__PERMISSIONS__DASHBOARD="myapp.dashboard.view"
+HOPFRAME__PERMISSIONS__USERS__READ="myapp.read.users"
+HOPFRAME__PERMISSIONS__GROUPS__CREATE="myapp.create.groups"
+HOPFRAME__PERMISSIONS__GROUPS__UPDATE="myapp.update.groups"
+```
diff --git a/docs/readme.md b/docs/readme.md
index 289a64c..df7f363 100644
--- a/docs/readme.md
+++ b/docs/readme.md
@@ -7,6 +7,8 @@ The HopFrame comes in two variations, you can eiter only use the backend with so
- [Database](./database.md)
- [Repositories](./repositories.md)
- [Base Models](./models.md)
+- [Authentication](./authentication.md)
+- [Permissions](./permissions.md)
## HopFrame Web API
diff --git a/src/HopFrame.Api/Extensions/ServiceCollectionExtensions.cs b/src/HopFrame.Api/Extensions/ServiceCollectionExtensions.cs
index 618a437..51eacde 100644
--- a/src/HopFrame.Api/Extensions/ServiceCollectionExtensions.cs
+++ b/src/HopFrame.Api/Extensions/ServiceCollectionExtensions.cs
@@ -4,6 +4,7 @@ using HopFrame.Api.Logic.Implementation;
using HopFrame.Database;
using HopFrame.Security.Authentication;
using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
@@ -15,23 +16,25 @@ public static class ServiceCollectionExtensions {
/// Adds all HopFrame endpoints and services to the application
///
/// The service provider to add the services to
+ /// The configuration used to configure HopFrame authentication
/// The data source for all HopFrame entities
- public static void AddHopFrame(this IServiceCollection services) where TDbContext : HopDbContextBase {
+ public static void AddHopFrame(this IServiceCollection services, ConfigurationManager configuration) where TDbContext : HopDbContextBase {
services.AddMvcCore().UseSpecificControllers(typeof(SecurityController));
- AddHopFrameNoEndpoints(services);
+ AddHopFrameNoEndpoints(services, configuration);
}
///
/// Adds all HopFrame services to the application
///
/// The service provider to add the services to
+ /// The configuration used to configure HopFrame authentication
/// The data source for all HopFrame entities
- public static void AddHopFrameNoEndpoints(this IServiceCollection services) where TDbContext : HopDbContextBase {
+ public static void AddHopFrameNoEndpoints(this IServiceCollection services, ConfigurationManager configuration) where TDbContext : HopDbContextBase {
services.AddHopFrameRepositories();
services.TryAddSingleton();
services.AddScoped();
- services.AddHopFrameAuthentication();
+ services.AddHopFrameAuthentication(configuration);
}
}
diff --git a/src/HopFrame.Api/Logic/Implementation/AuthLogic.cs b/src/HopFrame.Api/Logic/Implementation/AuthLogic.cs
index c1f3c90..acf8fb7 100644
--- a/src/HopFrame.Api/Logic/Implementation/AuthLogic.cs
+++ b/src/HopFrame.Api/Logic/Implementation/AuthLogic.cs
@@ -5,10 +5,11 @@ using HopFrame.Security.Authentication;
using HopFrame.Security.Claims;
using HopFrame.Security.Models;
using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Options;
namespace HopFrame.Api.Logic.Implementation;
-internal class AuthLogic(IUserRepository users, ITokenRepository tokens, ITokenContext tokenContext, IHttpContextAccessor accessor) : IAuthLogic {
+internal class AuthLogic(IUserRepository users, ITokenRepository tokens, ITokenContext tokenContext, IHttpContextAccessor accessor, IOptions options) : IAuthLogic {
public async Task>> Login(UserLogin login) {
var user = await users.GetUserByEmail(login.Email);
@@ -23,12 +24,12 @@ internal class AuthLogic(IUserRepository users, ITokenRepository tokens, ITokenC
var accessToken = await tokens.CreateToken(Token.AccessTokenType, user);
accessor.HttpContext?.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.Content.ToString(), new CookieOptions {
- MaxAge = HopFrameAuthentication.RefreshTokenTime,
+ MaxAge = options.Value.RefreshTokenTime,
HttpOnly = true,
Secure = true
});
accessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Content.ToString(), new CookieOptions {
- MaxAge = HopFrameAuthentication.AccessTokenTime,
+ MaxAge = options.Value.AccessTokenTime,
HttpOnly = true,
Secure = true
});
@@ -54,12 +55,12 @@ internal class AuthLogic(IUserRepository users, ITokenRepository tokens, ITokenC
var accessToken = await tokens.CreateToken(Token.AccessTokenType, user);
accessor.HttpContext?.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.Content.ToString(), new CookieOptions {
- MaxAge = HopFrameAuthentication.RefreshTokenTime,
+ MaxAge = options.Value.RefreshTokenTime,
HttpOnly = true,
Secure = true
});
accessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Content.ToString(), new CookieOptions {
- MaxAge = HopFrameAuthentication.AccessTokenTime,
+ MaxAge = options.Value.AccessTokenTime,
HttpOnly = false,
Secure = true
});
@@ -81,13 +82,13 @@ internal class AuthLogic(IUserRepository users, ITokenRepository tokens, ITokenC
if (token.Type != Token.RefreshTokenType)
return LogicResult>.Conflict("The provided token is not a refresh token");
- if (token.CreatedAt + HopFrameAuthentication.RefreshTokenTime < DateTime.Now)
+ if (token.CreatedAt + options.Value.RefreshTokenTime < DateTime.Now)
return LogicResult>.Forbidden("Refresh token is expired");
var accessToken = await tokens.CreateToken(Token.AccessTokenType, token.Owner);
accessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Content.ToString(), new CookieOptions {
- MaxAge = HopFrameAuthentication.AccessTokenTime,
+ MaxAge = options.Value.AccessTokenTime,
HttpOnly = false,
Secure = true
});
diff --git a/src/HopFrame.Security/AdminPermissions.cs b/src/HopFrame.Security/AdminPermissions.cs
deleted file mode 100644
index 7f45afc..0000000
--- a/src/HopFrame.Security/AdminPermissions.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-namespace HopFrame.Security;
-
-public static class AdminPermissions {
- public const string IsAdmin = "hopframe.admin";
-
- public const string ViewUsers = "hopframe.admin.users.view";
- public const string EditUser = "hopframe.admin.users.edit";
- public const string DeleteUser = "hopframe.admin.users.delete";
- public const string AddUser = "hopframe.admin.users.add";
-
- public const string ViewGroups = "hopframe.admin.groups.view";
- public const string EditGroup = "hopframe.admin.groups.edit";
- public const string DeleteGroup = "hopframe.admin.groups.delete";
- public const string AddGroup = "hopframe.admin.groups.add";
-}
\ No newline at end of file
diff --git a/src/HopFrame.Security/Authentication/HopFrameAuthentication.cs b/src/HopFrame.Security/Authentication/HopFrameAuthentication.cs
index 8709f91..8b0a3b1 100644
--- a/src/HopFrame.Security/Authentication/HopFrameAuthentication.cs
+++ b/src/HopFrame.Security/Authentication/HopFrameAuthentication.cs
@@ -17,23 +17,23 @@ public class HopFrameAuthentication(
UrlEncoder encoder,
ISystemClock clock,
ITokenRepository tokens,
- IPermissionRepository perms)
+ IPermissionRepository perms,
+ IOptions tokenOptions)
: AuthenticationHandler(options, logger, encoder, clock) {
- public const string SchemeName = "HopCore.Authentication";
- public static readonly TimeSpan AccessTokenTime = new(0, 0, 5, 0);
- public static readonly TimeSpan RefreshTokenTime = new(30, 0, 0, 0);
+ public const string SchemeName = "HopFrame.Authentication";
protected override async Task HandleAuthenticateAsync() {
var accessToken = Request.Cookies[ITokenContext.AccessTokenType];
if (string.IsNullOrEmpty(accessToken)) accessToken = Request.Headers[SchemeName];
if (string.IsNullOrEmpty(accessToken)) accessToken = Request.Headers["Token"];
+ if (string.IsNullOrEmpty(accessToken)) accessToken = Request.Query["token"];
if (string.IsNullOrEmpty(accessToken)) return AuthenticateResult.Fail("No Access Token provided");
var tokenEntry = await tokens.GetToken(accessToken);
if (tokenEntry is null) return AuthenticateResult.Fail("The provided Access Token does not exist");
- if (tokenEntry.CreatedAt + AccessTokenTime < DateTime.Now) return AuthenticateResult.Fail("The provided Access Token is expired");
+ if (tokenEntry.CreatedAt + tokenOptions.Value.AccessTokenTime < DateTime.Now) return AuthenticateResult.Fail("The provided Access Token is expired");
if (tokenEntry.Owner is null)
return AuthenticateResult.Fail("The provided Access Token does not match any user");
diff --git a/src/HopFrame.Security/Authentication/HopFrameAuthenticationExtensions.cs b/src/HopFrame.Security/Authentication/HopFrameAuthenticationExtensions.cs
index cf87810..e0b7d37 100644
--- a/src/HopFrame.Security/Authentication/HopFrameAuthenticationExtensions.cs
+++ b/src/HopFrame.Security/Authentication/HopFrameAuthenticationExtensions.cs
@@ -1,23 +1,28 @@
+using HopFrame.Security.Authorization;
using HopFrame.Security.Claims;
+using HopFrame.Security.Options;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace HopFrame.Security.Authentication;
public static class HopFrameAuthenticationExtensions {
-
///
/// Configures the WebApplication to use the authentication and authorization of the HopFrame API
///
/// The service provider to add the services to
- /// The database object that saves all entities that are important for the security api
+ /// The configuration used to configure HopFrame authentication
///
- public static IServiceCollection AddHopFrameAuthentication(this IServiceCollection service) {
+ public static IServiceCollection AddHopFrameAuthentication(this IServiceCollection service, ConfigurationManager configuration) {
service.TryAddSingleton();
service.AddScoped();
+ service.AddOptionsFromConfiguration(configuration);
+ service.AddOptionsFromConfiguration(configuration);
+
service.AddAuthentication(HopFrameAuthentication.SchemeName).AddScheme(HopFrameAuthentication.SchemeName, _ => {});
service.AddAuthorization();
diff --git a/src/HopFrame.Security/Authentication/HopFrameAuthenticationOptions.cs b/src/HopFrame.Security/Authentication/HopFrameAuthenticationOptions.cs
new file mode 100644
index 0000000..b996d68
--- /dev/null
+++ b/src/HopFrame.Security/Authentication/HopFrameAuthenticationOptions.cs
@@ -0,0 +1,20 @@
+using HopFrame.Security.Options;
+
+namespace HopFrame.Security.Authentication;
+
+public class HopFrameAuthenticationOptions : OptionsFromConfiguration {
+ public override string Position { get; } = "HopFrame:Authentication";
+
+ public TimeSpan AccessTokenTime => AccessToken is null ? new(0, 0, 5, 0) : new(AccessToken.Days, AccessToken.Hours, AccessToken.Minutes, AccessToken.Seconds);
+ public TimeSpan RefreshTokenTime => RefreshToken is null ? new(30, 0, 0, 0) : new(RefreshToken.Days, RefreshToken.Hours, RefreshToken.Minutes, RefreshToken.Seconds);
+
+ public TokenTime AccessToken { get; set; }
+ public TokenTime RefreshToken { get; set; }
+
+ public class TokenTime {
+ public int Days { get; set; }
+ public int Hours { get; set; }
+ public int Minutes { get; set; }
+ public int Seconds { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/HopFrame.Security/Authorization/AdminPermissionOptions.cs b/src/HopFrame.Security/Authorization/AdminPermissionOptions.cs
new file mode 100644
index 0000000..46fc7f0
--- /dev/null
+++ b/src/HopFrame.Security/Authorization/AdminPermissionOptions.cs
@@ -0,0 +1,30 @@
+using HopFrame.Security.Options;
+
+namespace HopFrame.Security.Authorization;
+
+public class AdminPermissionOptions : OptionsFromConfiguration {
+ public override string Position { get; } = "HopFrame:Permissions";
+
+ public string Dashboard { get; set; } = "hopframe.admin";
+
+ public CrudPermission Users { get; set; } = new() {
+ Read = "hopframe.admin.users.read",
+ Update = "hopframe.admin.users.update",
+ Delete = "hopframe.admin.users.delete",
+ Create = "hopframe.admin.users.create"
+ };
+
+ public CrudPermission Groups { get; set; } = new() {
+ Read = "hopframe.admin.groups.read",
+ Update = "hopframe.admin.groups.update",
+ Delete = "hopframe.admin.groups.delete",
+ Create = "hopframe.admin.groups.create"
+ };
+
+ public class CrudPermission {
+ public string Create { get; set; }
+ public string Read { get; set; }
+ public string Update { get; set; }
+ public string Delete { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/HopFrame.Security/Options/OptionsFromConfiguration.cs b/src/HopFrame.Security/Options/OptionsFromConfiguration.cs
new file mode 100644
index 0000000..0f06fb8
--- /dev/null
+++ b/src/HopFrame.Security/Options/OptionsFromConfiguration.cs
@@ -0,0 +1,5 @@
+namespace HopFrame.Security.Options;
+
+public abstract class OptionsFromConfiguration {
+ public abstract string Position { get; }
+}
\ No newline at end of file
diff --git a/src/HopFrame.Security/Options/OptionsFromConfigurationExtensions.cs b/src/HopFrame.Security/Options/OptionsFromConfigurationExtensions.cs
new file mode 100644
index 0000000..f9b62c4
--- /dev/null
+++ b/src/HopFrame.Security/Options/OptionsFromConfigurationExtensions.cs
@@ -0,0 +1,19 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace HopFrame.Security.Options;
+
+public static class OptionsFromConfigurationExtensions {
+ public static void AddOptionsFromConfiguration(this IServiceCollection services, IConfiguration configuration) where T : OptionsFromConfiguration {
+ T optionsInstance = (T)Activator.CreateInstance(typeof(T));
+ string position = optionsInstance?.Position;
+ if (position is null) {
+ throw new ArgumentException($"""Configuration "{typeof(T).Name}" has no position configured!""");
+ }
+
+ services.Configure((Action)(options => {
+ IConfigurationSection section = configuration.GetSection(position);
+ section.Bind(options);
+ }));
+ }
+}
\ No newline at end of file
diff --git a/src/HopFrame.Web.Admin/Attributes/Classes/AdminPermissionsAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Classes/AdminPermissionsAttribute.cs
index 7d68eab..dc58ffb 100644
--- a/src/HopFrame.Web.Admin/Attributes/Classes/AdminPermissionsAttribute.cs
+++ b/src/HopFrame.Web.Admin/Attributes/Classes/AdminPermissionsAttribute.cs
@@ -8,6 +8,6 @@ public sealed class AdminPermissionsAttribute(string view = null, string create
Create = create,
Update = update,
Delete = delete,
- View = view
+ Read = view
};
}
diff --git a/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs b/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs
index 65998bd..05ff528 100644
--- a/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs
+++ b/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs
@@ -24,7 +24,7 @@ public interface IAdminPageGenerator {
///
/// the specified permission
///
- IAdminPageGenerator ViewPermission(string permission);
+ IAdminPageGenerator ReadPermission(string permission);
///
/// Sets the permission needed to create a new Entry
diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs
index eb61f7d..3181c97 100644
--- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs
+++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs
@@ -48,8 +48,8 @@ internal sealed class AdminPageGenerator : IAdminPageGenerator,
return this;
}
- public IAdminPageGenerator ViewPermission(string permission) {
- Page.Permissions.View = permission;
+ public IAdminPageGenerator ReadPermission(string permission) {
+ Page.Permissions.Read = permission;
return this;
}
@@ -165,7 +165,7 @@ internal sealed class AdminPageGenerator : IAdminPageGenerator,
var attribute = attributes.Single(a => a is AdminPermissionsAttribute) as AdminPermissionsAttribute;
CreatePermission(attribute?.Permissions.Create);
UpdatePermission(attribute?.Permissions.Update);
- ViewPermission(attribute?.Permissions.View);
+ ReadPermission(attribute?.Permissions.Read);
DeletePermission(attribute?.Permissions.Delete);
}
diff --git a/src/HopFrame.Web.Admin/Models/AdminPagePermissions.cs b/src/HopFrame.Web.Admin/Models/AdminPagePermissions.cs
index e9629a6..0312aaa 100644
--- a/src/HopFrame.Web.Admin/Models/AdminPagePermissions.cs
+++ b/src/HopFrame.Web.Admin/Models/AdminPagePermissions.cs
@@ -1,7 +1,7 @@
namespace HopFrame.Web.Admin.Models;
public sealed class AdminPagePermissions {
- public string View { get; set; }
+ public string Read { get; set; }
public string Create { get; set; }
public string Update { get; set; }
public string Delete { get; set; }
diff --git a/src/HopFrame.Web/HopAdminContext.cs b/src/HopFrame.Web/HopAdminContext.cs
index 0beffd2..198ce65 100644
--- a/src/HopFrame.Web/HopAdminContext.cs
+++ b/src/HopFrame.Web/HopAdminContext.cs
@@ -1,15 +1,17 @@
using System.Text.RegularExpressions;
using HopFrame.Database.Models;
using HopFrame.Security;
+using HopFrame.Security.Authorization;
using HopFrame.Web.Admin;
using HopFrame.Web.Admin.Attributes;
using HopFrame.Web.Admin.Generators;
using HopFrame.Web.Admin.Models;
using HopFrame.Web.Provider;
+using Microsoft.Extensions.Options;
namespace HopFrame.Web;
-internal class HopAdminContext : AdminPagesContext {
+internal class HopAdminContext(IOptions options) : AdminPagesContext {
[AdminPageUrl("users")]
public AdminPage Users { get; set; }
@@ -21,10 +23,10 @@ internal class HopAdminContext : AdminPagesContext {
generator.Page()
.Description("On this page you can manage all user accounts.")
.ConfigureProvider()
- .ViewPermission(AdminPermissions.ViewUsers)
- .CreatePermission(AdminPermissions.AddUser)
- .UpdatePermission(AdminPermissions.EditUser)
- .DeletePermission(AdminPermissions.DeleteUser);
+ .ReadPermission(options.Value.Users.Read)
+ .CreatePermission(options.Value.Users.Create)
+ .UpdatePermission(options.Value.Users.Update)
+ .DeletePermission(options.Value.Users.Delete);
generator.Page().Property(u => u.Password)
.DisplayInListing(false)
@@ -64,10 +66,10 @@ internal class HopAdminContext : AdminPagesContext {
generator.Page()
.Description("On this page you can view, create, edit and delete permission groups.")
.ConfigureProvider()
- .ViewPermission(AdminPermissions.ViewGroups)
- .CreatePermission(AdminPermissions.AddGroup)
- .UpdatePermission(AdminPermissions.EditGroup)
- .DeletePermission(AdminPermissions.DeleteGroup)
+ .ReadPermission(options.Value.Groups.Read)
+ .CreatePermission(options.Value.Groups.Create)
+ .UpdatePermission(options.Value.Groups.Update)
+ .DeletePermission(options.Value.Groups.Delete)
.ListingProperty(g => g.Name);
generator.Page().Property(g => g.Name)
diff --git a/src/HopFrame.Web/Pages/Administration/AdminDashboard.razor b/src/HopFrame.Web/Pages/Administration/AdminDashboard.razor
index 7ebb3cf..fe7afb1 100644
--- a/src/HopFrame.Web/Pages/Administration/AdminDashboard.razor
+++ b/src/HopFrame.Web/Pages/Administration/AdminDashboard.razor
@@ -5,25 +5,26 @@
@using BlazorStrap
@using HopFrame.Web.Pages.Administration.Layout
@using BlazorStrap.V5
-@using HopFrame.Security
+@using HopFrame.Security.Authorization
@using HopFrame.Web.Admin.Providers
@using HopFrame.Web.Components
@using Microsoft.AspNetCore.Components.Web
+@using Microsoft.Extensions.Options
@layout AdminLayout
-
+
Admin Dashboard
@foreach (var adminPage in Pages.LoadRegisteredAdminPages()) {
-
+
@adminPage.Title
- @adminPage.Permissions.View
+ @adminPage.Permissions.Read
@adminPage.Description
Open
@@ -36,6 +37,7 @@
@inject NavigationManager Navigator
@inject IAdminPagesProvider Pages
+@inject IOptions Options
@code {
diff --git a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor
index bf4c17d..d796aeb 100644
--- a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor
+++ b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor
@@ -18,7 +18,7 @@
@using HopFrame.Web.Components
@_pageData.Title
-
+
diff --git a/src/HopFrame.Web/Pages/Administration/Layout/AdminMenu.razor b/src/HopFrame.Web/Pages/Administration/Layout/AdminMenu.razor
index a47bafb..409b002 100644
--- a/src/HopFrame.Web/Pages/Administration/Layout/AdminMenu.razor
+++ b/src/HopFrame.Web/Pages/Administration/Layout/AdminMenu.razor
@@ -24,7 +24,7 @@
Dashboard
@foreach (var adminPage in Pages.LoadRegisteredAdminPages()) {
-
+
@adminPage.Title
}
diff --git a/src/HopFrame.Web/ServiceCollectionExtensions.cs b/src/HopFrame.Web/ServiceCollectionExtensions.cs
index 548e2e9..4b6232a 100644
--- a/src/HopFrame.Web/ServiceCollectionExtensions.cs
+++ b/src/HopFrame.Web/ServiceCollectionExtensions.cs
@@ -6,12 +6,13 @@ using HopFrame.Web.Admin;
using HopFrame.Web.Services;
using HopFrame.Web.Services.Implementation;
using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace HopFrame.Web;
public static class ServiceCollectionExtensions {
- public static IServiceCollection AddHopFrame(this IServiceCollection services) where TDbContext : HopDbContextBase {
+ public static IServiceCollection AddHopFrame(this IServiceCollection services, ConfigurationManager configuration) where TDbContext : HopDbContextBase {
services.AddHttpClient();
services.AddHopFrameRepositories();
services.AddScoped();
@@ -22,7 +23,7 @@ public static class ServiceCollectionExtensions {
services.AddSweetAlert2();
services.AddBlazorStrap();
- services.AddHopFrameAuthentication();
+ services.AddHopFrameAuthentication(configuration);
return services;
}
diff --git a/src/HopFrame.Web/Services/Implementation/AuthService.cs b/src/HopFrame.Web/Services/Implementation/AuthService.cs
index e5f1ec0..6fca234 100644
--- a/src/HopFrame.Web/Services/Implementation/AuthService.cs
+++ b/src/HopFrame.Web/Services/Implementation/AuthService.cs
@@ -4,6 +4,7 @@ using HopFrame.Security.Authentication;
using HopFrame.Security.Claims;
using HopFrame.Security.Models;
using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Options;
namespace HopFrame.Web.Services.Implementation;
@@ -11,7 +12,8 @@ internal class AuthService(
IUserRepository userService,
IHttpContextAccessor httpAccessor,
ITokenRepository tokens,
- ITokenContext context)
+ ITokenContext context,
+ IOptions options)
: IAuthService {
public async Task Register(UserRegister register) {
@@ -27,12 +29,12 @@ internal class AuthService(
var accessToken = await tokens.CreateToken(Token.AccessTokenType, user);
httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.Content.ToString(), new CookieOptions {
- MaxAge = HopFrameAuthentication.RefreshTokenTime,
+ MaxAge = options.Value.RefreshTokenTime,
HttpOnly = true,
Secure = true
});
httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Content.ToString(), new CookieOptions {
- MaxAge = HopFrameAuthentication.AccessTokenTime,
+ MaxAge = options.Value.AccessTokenTime,
HttpOnly = false,
Secure = true
});
@@ -48,12 +50,12 @@ internal class AuthService(
var accessToken = await tokens.CreateToken(Token.AccessTokenType, user);
httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.Content.ToString(), new CookieOptions {
- MaxAge = HopFrameAuthentication.RefreshTokenTime,
+ MaxAge = options.Value.RefreshTokenTime,
HttpOnly = true,
Secure = true
});
httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Content.ToString(), new CookieOptions {
- MaxAge = HopFrameAuthentication.AccessTokenTime,
+ MaxAge = options.Value.AccessTokenTime,
HttpOnly = false,
Secure = true
});
@@ -77,12 +79,12 @@ internal class AuthService(
if (token is null || token.Type != Token.RefreshTokenType) return null;
- if (token.CreatedAt + HopFrameAuthentication.RefreshTokenTime < DateTime.Now) return null;
+ if (token.CreatedAt + options.Value.RefreshTokenTime < DateTime.Now) return null;
var accessToken = await tokens.CreateToken(Token.AccessTokenType, token.Owner);
httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Content.ToString(), new CookieOptions {
- MaxAge = HopFrameAuthentication.AccessTokenTime,
+ MaxAge = options.Value.AccessTokenTime,
HttpOnly = false,
Secure = true
});
@@ -95,7 +97,7 @@ internal class AuthService(
if (accessToken is null) return false;
if (accessToken.Type != Token.AccessTokenType) return false;
- if (accessToken.CreatedAt + HopFrameAuthentication.AccessTokenTime < DateTime.Now) return false;
+ if (accessToken.CreatedAt + options.Value.AccessTokenTime < DateTime.Now) return false;
if (accessToken.Owner is null) return false;
return true;
diff --git a/testing/HopFrame.Testing.Api/Program.cs b/testing/HopFrame.Testing.Api/Program.cs
index 6651ecd..b728eb3 100644
--- a/testing/HopFrame.Testing.Api/Program.cs
+++ b/testing/HopFrame.Testing.Api/Program.cs
@@ -7,7 +7,7 @@ var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
-builder.Services.AddHopFrame();
+builder.Services.AddHopFrame(builder.Configuration);
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
diff --git a/testing/HopFrame.Testing.Web/Program.cs b/testing/HopFrame.Testing.Web/Program.cs
index 7957fff..481e7fc 100644
--- a/testing/HopFrame.Testing.Web/Program.cs
+++ b/testing/HopFrame.Testing.Web/Program.cs
@@ -6,7 +6,7 @@ using HopFrame.Web.Admin;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext();
-builder.Services.AddHopFrame();
+builder.Services.AddHopFrame(builder.Configuration);
builder.Services.AddAdminContext();
// Add services to the container.
diff --git a/tests/HopFrame.Tests.Api/AuthLogicTests.cs b/tests/HopFrame.Tests.Api/AuthLogicTests.cs
index ca86b5b..a5163d2 100644
--- a/tests/HopFrame.Tests.Api/AuthLogicTests.cs
+++ b/tests/HopFrame.Tests.Api/AuthLogicTests.cs
@@ -10,6 +10,7 @@ using HopFrame.Security.Authentication;
using HopFrame.Security.Claims;
using HopFrame.Security.Models;
using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Options;
using Moq;
namespace HopFrame.Tests.Api;
@@ -75,7 +76,7 @@ public class AuthLogicTests {
.Setup(c => c.User)
.Returns(CreateDummyUser());
- return (new AuthLogic(users.Object, tokens.Object, context.Object, accessor), accessor.HttpContext);
+ return (new AuthLogic(users.Object, tokens.Object, context.Object, accessor, new OptionsWrapper(new HopFrameAuthenticationOptions())), accessor.HttpContext);
}
private User CreateDummyUser() => new() {
diff --git a/tests/HopFrame.Tests.Security/AuthenticationTests.cs b/tests/HopFrame.Tests.Security/AuthenticationTests.cs
index 7c80e7d..5cd6d44 100644
--- a/tests/HopFrame.Tests.Security/AuthenticationTests.cs
+++ b/tests/HopFrame.Tests.Security/AuthenticationTests.cs
@@ -46,7 +46,7 @@ public class AuthenticationTests {
.Setup(x => x.GetFullPermissions(It.IsAny()))
.ReturnsAsync(new List());
- var auth = new HopFrameAuthentication(options.Object, logger.Object, encoder.Object, clock.Object, tokens.Object, perms.Object);
+ var auth = new HopFrameAuthentication(options.Object, logger.Object, encoder.Object, clock.Object, tokens.Object, perms.Object, new OptionsWrapper(new HopFrameAuthenticationOptions()));
var context = new DefaultHttpContext();
if (provideCorrectToken)
context.HttpContext.Request.Headers.Append(HopFrameAuthentication.SchemeName, correctToken.Content.ToString());
diff --git a/tests/HopFrame.Tests.Web/AuthServiceTests.cs b/tests/HopFrame.Tests.Web/AuthServiceTests.cs
index a5df287..d5c5ad7 100644
--- a/tests/HopFrame.Tests.Web/AuthServiceTests.cs
+++ b/tests/HopFrame.Tests.Web/AuthServiceTests.cs
@@ -1,11 +1,13 @@
using HopFrame.Database.Models;
using HopFrame.Database.Repositories;
+using HopFrame.Security.Authentication;
using HopFrame.Security.Claims;
using HopFrame.Security.Models;
using HopFrame.Tests.Web.Extensions;
using HopFrame.Web.Services;
using HopFrame.Web.Services.Implementation;
using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Options;
using Moq;
namespace HopFrame.Tests.Web;
@@ -66,7 +68,7 @@ public class AuthServiceTests {
.Setup(c => c.AccessToken)
.Returns(providedAccessToken);
- return (new AuthService(users.Object, accessor, tokens.Object, context.Object), accessor.HttpContext);
+ return (new AuthService(users.Object, accessor, tokens.Object, context.Object, new OptionsWrapper(new HopFrameAuthenticationOptions())), accessor.HttpContext);
}
private User CreateDummyUser() => new() {