Merge pull request #4 from leonhoppe/release/v1.1.0

Release/v1.1.0
This commit is contained in:
leonhoppe
2024-09-26 12:34:07 +02:00
committed by GitHub
107 changed files with 739 additions and 128 deletions

View File

@@ -1,8 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -1,20 +0,0 @@
using HopFrame.Api.Controller;
using HopFrame.Database;
using HopFrame.Security.Authentication;
using Microsoft.Extensions.DependencyInjection;
namespace HopFrame.Api.Extensions;
public static class ServiceCollectionExtensions {
/// <summary>
/// Adds all HopFrame endpoints and the HopFrame security layer to the WebApplication
/// </summary>
/// <param name="services">The service provider to add the services to</param>
/// <typeparam name="TDbContext">The data source for all HopFrame entities</typeparam>
public static void AddHopFrame<TDbContext>(this IServiceCollection services) where TDbContext : HopDbContextBase {
services.AddMvcCore().UseSpecificControllers(typeof(SecurityController<TDbContext>));
services.AddHopFrameAuthentication<TDbContext>();
}
}

View File

@@ -1,2 +0,0 @@
# HopFrame API module
This module contains some useful endpoints for user login / register management.

View File

@@ -1,2 +0,0 @@
# HopFrame Security module
this module contains all handlers for the login and register validation. It also checks the user permissions.

View File

@@ -1,2 +0,0 @@
# HopFrame Web module
This module contains useful helpers for Blazor Apps and an Admin Dashboard.

View File

@@ -1,18 +1,16 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Database", "HopFrame.Database\HopFrame.Database.csproj", "{003120AE-F38B-4632-8497-BE4505189627}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Database", "src\HopFrame.Database\HopFrame.Database.csproj", "{003120AE-F38B-4632-8497-BE4505189627}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Testing", "Testing", "{58703056-8DAD-4221-BBE3-42425D2F4929}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RestApiTest", "test\RestApiTest\RestApiTest.csproj", "{921159CE-AF75-44C3-A3F9-6B9B1A4E85CF}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RestApiTest", "RestApiTest\RestApiTest.csproj", "{921159CE-AF75-44C3-A3F9-6B9B1A4E85CF}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Security", "src\HopFrame.Security\HopFrame.Security.csproj", "{7F82E1C6-4A42-4337-9E03-2EE6429D004F}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Security", "HopFrame.Security\HopFrame.Security.csproj", "{7F82E1C6-4A42-4337-9E03-2EE6429D004F}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Api", "src\HopFrame.Api\HopFrame.Api.csproj", "{1E821490-AEDC-4F55-B758-52F4FADAB53A}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Api", "HopFrame.Api\HopFrame.Api.csproj", "{1E821490-AEDC-4F55-B758-52F4FADAB53A}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Web", "src\HopFrame.Web\HopFrame.Web.csproj", "{3BE585BC-13A5-4BE4-A806-E9EC2D825956}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Web", "HopFrame.Web\HopFrame.Web.csproj", "{3BE585BC-13A5-4BE4-A806-E9EC2D825956}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrontendTest", "test\FrontendTest\FrontendTest.csproj", "{8F983A37-63CF-48D5-988D-58B78EF8AECD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrontendTest", "FrontendTest\FrontendTest.csproj", "{8F983A37-63CF-48D5-988D-58B78EF8AECD}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -46,7 +44,5 @@ Global
{8F983A37-63CF-48D5-988D-58B78EF8AECD}.Release|Any CPU.Build.0 = Release|Any CPU {8F983A37-63CF-48D5-988D-58B78EF8AECD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{921159CE-AF75-44C3-A3F9-6B9B1A4E85CF} = {58703056-8DAD-4221-BBE3-42425D2F4929}
{8F983A37-63CF-48D5-988D-58B78EF8AECD} = {58703056-8DAD-4221-BBE3-42425D2F4929}
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

View File

@@ -6,3 +6,74 @@ A simple backend management api for ASP.NET Core Web APIs
- [x] User authentication - [x] User authentication
- [x] Permission management - [x] Permission management
- [x] Frontend dashboards - [x] Frontend dashboards
# Usage
There are two different versions of HopFrame, either the Web API version or the full Blazor web version.
## Ho to use the Web API version
1. Add the HopFrame.Api library to your project:
```
dotnet add package HopFrame.Api
```
2. Create a DbContext that inherits the ``HopDbContext`` and add a data source
```csharp
public class DatabaseContext : HopDbContextBase {
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlite("...");
}
}
```
3. Add the DbContext and HopFrame to your services
```csharp
builder.Services.AddDbContext<DatabaseContext>();
builder.Services.AddHopFrame<DatabaseContext>();
```
## How to use the Blazor API
1. Add the HopFrame.Web library to your project
```
dotnet add package HopFrame.Web
```
2. Create a DbContext that inherits the ``HopDbContext`` and add a data source
```csharp
public class DatabaseContext : HopDbContextBase {
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlite("...");
}
}
```
3. Add the DbContext and HopFrame to your services
```csharp
builder.Services.AddDbContext<DatabaseContext>();
builder.Services.AddHopFrame<DatabaseContext>();
```
4. Add the authentication middleware to your app
```csharp
app.UseMiddleware<AuthMiddleware>();
```
5. Add the HopFrame pages to your Razor components
```csharp
app.MapRazorComponents<App>()
.AddHopFrameAdminPages()
.AddInteractiveServerRenderMode();
```

View File

@@ -13,12 +13,6 @@ namespace HopFrame.Security {
} }
} }
namespace HopFrame.Web {
class RegisterData {
+RepeatedPassword: string
}
}
namespace HopFrame.Api { namespace HopFrame.Api {
class SingleValueResult<TValue> { class SingleValueResult<TValue> {
+Value: TValue +Value: TValue
@@ -29,6 +23,4 @@ namespace HopFrame.Api {
} }
} }
UserRegister <|-- RegisterData
@enduml @enduml

View File

@@ -11,9 +11,6 @@ namespace HopFrame.Database {
} }
class TokenEntry { class TokenEntry {
{static} +RefreshTokenType: int = 0
{static} +AccessTokenType: int = 1
+Type: int +Type: int
+Token: string +Token: string
+UserId: string +UserId: string

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

13
docs/README.md Normal file
View File

@@ -0,0 +1,13 @@
# HopFrame documentation
These sides contain all documentation available for the HopFrame modules
## Content
| Topic | Description | Document |
|----------|------------------------------------------------|-----------------------|
| Models | All models used by the HopFrame | [link](./models.md) |
| Services | All services provided by the HopFrame | [link](./services.md) |
| Usage | How to properly implement the HopFrame modules | [link](./usage.md) |
## Dependencies
Both the HopFrame.Api and HopFrame.Web modules are dependent on the HopFrame.Database and HopFrame.Security modules.
So all models and services provided by these modules are available in the other modules as well.

View File

@@ -6,16 +6,16 @@ This page shows all models that HopFrame uses.
## Base Models ## Base Models
These are the models used by the various database services. These are the models used by the various database services.
![](../Diagrams/Models/img/BaseModels.svg) ![](./Diagrams/Models/img/BaseModels.svg)
## API Models ## API Models
These are the models used by the REST API and the Blazor API. These are the models used by the REST API and the Blazor API.
![](../Diagrams/Models/img/ApiModels.svg) ![](./Diagrams/Models/img/ApiModels.svg)
## Database Models ## Database Models
These are the models that correspond to the scheme in the Database These are the models that correspond to the scheme in the Database
![](../Diagrams/Models/img/DatabaseModels.svg) ![](./Diagrams/Models/img/DatabaseModels.svg)

145
docs/services.md Normal file
View File

@@ -0,0 +1,145 @@
# HopFrame Services
This page describes all services provided by the HopFrame.
You can use these services by specifying them as a dependency. All of them are scoped dependencies.
## HopFrame.Security
### ITokenContext
This service provides the information given by the current request
```csharp
public interface ITokenContext {
bool IsAuthenticated { get; }
User User { get; }
Guid AccessToken { get; }
}
```
### IUserService
This service simplifies the data access of the user table in the database.
```csharp
public interface IUserService {
Task<IList<User>> GetUsers();
Task<User> GetUser(Guid userId);
Task<User> GetUserByEmail(string email);
Task<User> GetUserByUsername(string username);
Task<User> AddUser(UserRegister user);
Task UpdateUser(User user);
Task DeleteUser(User user);
Task<bool> CheckUserPassword(User user, string password);
Task ChangePassword(User user, string password);
}
```
### IPermissionService
This service handles all permission and group interactions with the data source.
```csharp
public interface IPermissionService {
Task<bool> HasPermission(string permission, Guid user);
Task<IList<PermissionGroup>> GetPermissionGroups();
Task<PermissionGroup> GetPermissionGroup(string name);
Task EditPermissionGroup(PermissionGroup group);
Task<IList<PermissionGroup>> GetUserPermissionGroups(User user);
Task RemoveGroupFromUser(User user, PermissionGroup group);
Task<PermissionGroup> CreatePermissionGroup(string name, bool isDefault = false, string description = null);
Task DeletePermissionGroup(PermissionGroup group);
Task<Permission> GetPermission(string name, IPermissionOwner owner);
Task AddPermission(IPermissionOwner owner, string permission);
Task RemovePermission(Permission permission);
Task<string[]> GetFullPermissions(string user);
}
```
## HopFrame.Api
### LogicResult
Logic result is an extension of the ActionResult for an ApiController. It provides simple Http status results with either a message or data by specifying the generic type.
```csharp
public class LogicResult : ILogicResult {
public static LogicResult Ok();
public static LogicResult BadRequest();
public static LogicResult BadRequest(string message);
public static LogicResult Forbidden();
public static LogicResult Forbidden(string message);
public static LogicResult NotFound();
public static LogicResult NotFound(string message);
public static LogicResult Conflict();
public static LogicResult Conflict(string message);
public static LogicResult Forward(LogicResult result);
public static LogicResult Forward<T>(ILogicResult<T> result);
public static implicit operator ActionResult(LogicResult v);
}
public class LogicResult<T> : ILogicResult<T> {
public static LogicResult<T> Ok();
public static LogicResult<T> Ok(T result);
...
}
```
### IAuthLogic
This service handles all logic needed to provide the authentication endpoints by using the LogicResults.
```csharp
public interface IAuthLogic {
Task<LogicResult<SingleValueResult<string>>> Login(UserLogin login);
Task<LogicResult<SingleValueResult<string>>> Register(UserRegister register);
Task<LogicResult<SingleValueResult<string>>> Authenticate();
Task<LogicResult> Logout();
Task<LogicResult> Delete(UserPasswordValidation validation);
}
```
## HopFrame.Web
### IAuthService
This service handles all the authentication like login or register. It properly creates all tokens so the user can be identified
```csharp
public interface IAuthService {
Task Register(UserRegister register);
Task<bool> Login(UserLogin login);
Task Logout();
Task<TokenEntry> RefreshLogin();
Task<bool> IsLoggedIn();
}
```

70
docs/usage.md Normal file
View File

@@ -0,0 +1,70 @@
# HopFrame Usage
There are two different versions of HopFrame, either the Web API version or the full Blazor web version.
## Ho to use the Web API version
1. Add the HopFrame.Api library to your project:
```
dotnet add package HopFrame.Api
```
2. Create a DbContext that inherits the ``HopDbContext`` and add a data source
```csharp
public class DatabaseContext : HopDbContextBase {
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlite("...");
}
}
```
3. Add the DbContext and HopFrame to your services
```csharp
builder.Services.AddDbContext<DatabaseContext>();
builder.Services.AddHopFrame<DatabaseContext>();
```
## How to use the Blazor API
1. Add the HopFrame.Web library to your project
```
dotnet add package HopFrame.Web
```
2. Create a DbContext that inherits the ``HopDbContext`` and add a data source
```csharp
public class DatabaseContext : HopDbContextBase {
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlite("...");
}
}
```
3. Add the DbContext and HopFrame to your services
```csharp
builder.Services.AddDbContext<DatabaseContext>();
builder.Services.AddHopFrame<DatabaseContext>();
```
4. Add the authentication middleware to your app
```csharp
app.UseMiddleware<AuthMiddleware>();
```
5. Add the HopFrame pages to your Razor components
```csharp
app.MapRazorComponents<App>()
.AddHopFrameAdminPages()
.AddInteractiveServerRenderMode();
```

View File

@@ -0,0 +1,38 @@
using HopFrame.Api.Logic;
using HopFrame.Api.Models;
using HopFrame.Security.Authorization;
using HopFrame.Security.Models;
using Microsoft.AspNetCore.Mvc;
namespace HopFrame.Api.Controller;
[ApiController]
[Route("authentication")]
public class SecurityController(IAuthLogic auth) : ControllerBase {
[HttpPut("login")]
public async Task<ActionResult<SingleValueResult<string>>> Login([FromBody] UserLogin login) {
return await auth.Login(login);
}
[HttpPost("register")]
public async Task<ActionResult<SingleValueResult<string>>> Register([FromBody] UserRegister register) {
return await auth.Register(register);
}
[HttpGet("authenticate")]
public async Task<ActionResult<SingleValueResult<string>>> Authenticate() {
return await auth.Authenticate();
}
[HttpDelete("logout"), Authorized]
public async Task<ActionResult> Logout() {
return await auth.Logout();
}
[HttpDelete("delete"), Authorized]
public async Task<ActionResult> Delete([FromBody] UserPasswordValidation validation) {
return await auth.Delete(validation);
}
}

View File

@@ -0,0 +1,36 @@
using HopFrame.Api.Controller;
using HopFrame.Api.Logic;
using HopFrame.Api.Logic.Implementation;
using HopFrame.Database;
using HopFrame.Security.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace HopFrame.Api.Extensions;
public static class ServiceCollectionExtensions {
/// <summary>
/// Adds all HopFrame endpoints and services to the application
/// </summary>
/// <param name="services">The service provider to add the services to</param>
/// <typeparam name="TDbContext">The data source for all HopFrame entities</typeparam>
public static void AddHopFrame<TDbContext>(this IServiceCollection services) where TDbContext : HopDbContextBase {
services.AddMvcCore().UseSpecificControllers(typeof(SecurityController));
AddHopFrameNoEndpoints<TDbContext>(services);
}
/// <summary>
/// Adds all HopFrame services to the application
/// </summary>
/// <param name="services">The service provider to add the services to</param>
/// <typeparam name="TDbContext">The data source for all HopFrame entities</typeparam>
public static void AddHopFrameNoEndpoints<TDbContext>(this IServiceCollection services) where TDbContext : HopDbContextBase {
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<IAuthLogic, AuthLogic<TDbContext>>();
services.AddHopFrameAuthentication<TDbContext>();
}
}

View File

@@ -5,8 +5,12 @@
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable> <Nullable>disable</Nullable>
<PackageId>HopFrame.Api</PackageId>
<Version>1.1.0</Version>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>
<IsPackable>true</IsPackable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -0,0 +1,16 @@
using HopFrame.Api.Models;
using HopFrame.Security.Models;
namespace HopFrame.Api.Logic;
public interface IAuthLogic {
Task<LogicResult<SingleValueResult<string>>> Login(UserLogin login);
Task<LogicResult<SingleValueResult<string>>> Register(UserRegister register);
Task<LogicResult<SingleValueResult<string>>> Authenticate();
Task<LogicResult> Logout();
Task<LogicResult> Delete(UserPasswordValidation validation);
}

View File

@@ -1,30 +1,24 @@
using HopFrame.Api.Logic;
using HopFrame.Api.Models; using HopFrame.Api.Models;
using HopFrame.Database; using HopFrame.Database;
using HopFrame.Database.Models.Entries; using HopFrame.Database.Models.Entries;
using HopFrame.Security.Authentication; using HopFrame.Security.Authentication;
using HopFrame.Security.Authorization;
using HopFrame.Security.Claims; using HopFrame.Security.Claims;
using HopFrame.Security.Models; using HopFrame.Security.Models;
using HopFrame.Security.Services; using HopFrame.Security.Services;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace HopFrame.Api.Controller; namespace HopFrame.Api.Logic.Implementation;
[ApiController] public class AuthLogic<TDbContext>(TDbContext context, IUserService users, ITokenContext tokenContext, IHttpContextAccessor accessor) : IAuthLogic where TDbContext : HopDbContextBase {
[Route("authentication")]
public class SecurityController<TDbContext>(TDbContext context, IUserService users, ITokenContext tokenContext) : ControllerBase where TDbContext : HopDbContextBase {
[HttpPut("login")] public async Task<LogicResult<SingleValueResult<string>>> Login(UserLogin login) {
public async Task<ActionResult<SingleValueResult<string>>> Login([FromBody] UserLogin login) {
var user = await users.GetUserByEmail(login.Email); var user = await users.GetUserByEmail(login.Email);
if (user is null) if (user is null)
return LogicResult<SingleValueResult<string>>.NotFound("The provided email address was not found"); return LogicResult<SingleValueResult<string>>.NotFound("The provided email address was not found");
if (await users.CheckUserPassword(user, login.Password)) if (!await users.CheckUserPassword(user, login.Password))
return LogicResult<SingleValueResult<string>>.Forbidden("The provided password is not correct"); return LogicResult<SingleValueResult<string>>.Forbidden("The provided password is not correct");
var refreshToken = new TokenEntry { var refreshToken = new TokenEntry {
@@ -40,11 +34,16 @@ public class SecurityController<TDbContext>(TDbContext context, IUserService use
UserId = user.Id.ToString() UserId = user.Id.ToString()
}; };
HttpContext.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.Token, new CookieOptions { accessor.HttpContext?.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.Token, new CookieOptions {
MaxAge = HopFrameAuthentication<TDbContext>.RefreshTokenTime, MaxAge = HopFrameAuthentication<TDbContext>.RefreshTokenTime,
HttpOnly = true, HttpOnly = true,
Secure = true Secure = true
}); });
accessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Token, new CookieOptions {
MaxAge = HopFrameAuthentication<TDbContext>.AccessTokenTime,
HttpOnly = true,
Secure = true
});
await context.Tokens.AddRangeAsync(refreshToken, accessToken); await context.Tokens.AddRangeAsync(refreshToken, accessToken);
await context.SaveChangesAsync(); await context.SaveChangesAsync();
@@ -52,8 +51,7 @@ public class SecurityController<TDbContext>(TDbContext context, IUserService use
return LogicResult<SingleValueResult<string>>.Ok(accessToken.Token); return LogicResult<SingleValueResult<string>>.Ok(accessToken.Token);
} }
[HttpPost("register")] public async Task<LogicResult<SingleValueResult<string>>> Register(UserRegister register) {
public async Task<ActionResult<SingleValueResult<string>>> Register([FromBody] UserRegister register) {
if (register.Password.Length < 8) if (register.Password.Length < 8)
return LogicResult<SingleValueResult<string>>.Conflict("Password needs to be at least 8 characters long"); return LogicResult<SingleValueResult<string>>.Conflict("Password needs to be at least 8 characters long");
@@ -79,12 +77,12 @@ public class SecurityController<TDbContext>(TDbContext context, IUserService use
await context.Tokens.AddRangeAsync(refreshToken, accessToken); await context.Tokens.AddRangeAsync(refreshToken, accessToken);
await context.SaveChangesAsync(); await context.SaveChangesAsync();
HttpContext.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.Token, new CookieOptions { accessor.HttpContext?.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.Token, new CookieOptions {
MaxAge = HopFrameAuthentication<TDbContext>.RefreshTokenTime, MaxAge = HopFrameAuthentication<TDbContext>.RefreshTokenTime,
HttpOnly = true, HttpOnly = true,
Secure = true Secure = true
}); });
HttpContext.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Token, new CookieOptions { accessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Token, new CookieOptions {
MaxAge = HopFrameAuthentication<TDbContext>.AccessTokenTime, MaxAge = HopFrameAuthentication<TDbContext>.AccessTokenTime,
HttpOnly = false, HttpOnly = false,
Secure = true Secure = true
@@ -93,9 +91,8 @@ public class SecurityController<TDbContext>(TDbContext context, IUserService use
return LogicResult<SingleValueResult<string>>.Ok(accessToken.Token); return LogicResult<SingleValueResult<string>>.Ok(accessToken.Token);
} }
[HttpGet("authenticate")] public async Task<LogicResult<SingleValueResult<string>>> Authenticate() {
public async Task<ActionResult<SingleValueResult<string>>> Authenticate() { var refreshToken = accessor.HttpContext?.Request.Cookies[ITokenContext.RefreshTokenType];
var refreshToken = HttpContext.Request.Cookies[ITokenContext.RefreshTokenType];
if (string.IsNullOrEmpty(refreshToken)) if (string.IsNullOrEmpty(refreshToken))
return LogicResult<SingleValueResult<string>>.Conflict("Refresh token not provided"); return LogicResult<SingleValueResult<string>>.Conflict("Refresh token not provided");
@@ -118,7 +115,7 @@ public class SecurityController<TDbContext>(TDbContext context, IUserService use
await context.Tokens.AddAsync(accessToken); await context.Tokens.AddAsync(accessToken);
await context.SaveChangesAsync(); await context.SaveChangesAsync();
HttpContext.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Token, new CookieOptions { accessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Token, new CookieOptions {
MaxAge = HopFrameAuthentication<TDbContext>.AccessTokenTime, MaxAge = HopFrameAuthentication<TDbContext>.AccessTokenTime,
HttpOnly = false, HttpOnly = false,
Secure = true Secure = true
@@ -127,10 +124,9 @@ public class SecurityController<TDbContext>(TDbContext context, IUserService use
return LogicResult<SingleValueResult<string>>.Ok(accessToken.Token); return LogicResult<SingleValueResult<string>>.Ok(accessToken.Token);
} }
[HttpDelete("logout"), Authorized] public async Task<LogicResult> Logout() {
public async Task<ActionResult> Logout() { var accessToken = accessor.HttpContext?.User.GetAccessTokenId();
var accessToken = HttpContext.User.GetAccessTokenId(); var refreshToken = accessor.HttpContext?.Request.Cookies[ITokenContext.RefreshTokenType];
var refreshToken = HttpContext.Request.Cookies[ITokenContext.RefreshTokenType];
if (string.IsNullOrEmpty(accessToken) || string.IsNullOrEmpty(refreshToken)) if (string.IsNullOrEmpty(accessToken) || string.IsNullOrEmpty(refreshToken))
return LogicResult.Conflict("access or refresh token not provided"); return LogicResult.Conflict("access or refresh token not provided");
@@ -147,22 +143,22 @@ public class SecurityController<TDbContext>(TDbContext context, IUserService use
context.Tokens.Remove(tokenEntries[1]); context.Tokens.Remove(tokenEntries[1]);
await context.SaveChangesAsync(); await context.SaveChangesAsync();
HttpContext.Response.Cookies.Delete(ITokenContext.RefreshTokenType); accessor.HttpContext?.Response.Cookies.Delete(ITokenContext.RefreshTokenType);
HttpContext.Response.Cookies.Delete(ITokenContext.AccessTokenType); accessor.HttpContext?.Response.Cookies.Delete(ITokenContext.AccessTokenType);
return LogicResult.Ok(); return LogicResult.Ok();
} }
[HttpDelete("delete"), Authorized] public async Task<LogicResult> Delete(UserPasswordValidation validation) {
public async Task<ActionResult> Delete([FromBody] UserPasswordValidation validation) {
var user = tokenContext.User; var user = tokenContext.User;
if (await users.CheckUserPassword(user, validation.Password)) if (!await users.CheckUserPassword(user, validation.Password))
return LogicResult.Forbidden("The provided password is not correct"); return LogicResult.Forbidden("The provided password is not correct");
await users.DeleteUser(user); await users.DeleteUser(user);
HttpContext.Response.Cookies.Delete(ITokenContext.RefreshTokenType); accessor.HttpContext?.Response.Cookies.Delete(ITokenContext.RefreshTokenType);
accessor.HttpContext?.Response.Cookies.Delete(ITokenContext.AccessTokenType);
return LogicResult.Ok(); return LogicResult.Ok();
} }

100
src/HopFrame.Api/README.md Normal file
View File

@@ -0,0 +1,100 @@
# HopFrame API module
This module contains some useful endpoints for user login / register management.
## Ho to use the Web API version
1. Add the HopFrame.Api library to your project:
```
dotnet add package HopFrame.Api
```
2. Create a DbContext that inherits the ``HopDbContext`` and add a data source
```csharp
public class DatabaseContext : HopDbContextBase {
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlite("...");
}
}
```
3. Add the DbContext and HopFrame to your services
```csharp
builder.Services.AddDbContext<DatabaseContext>();
builder.Services.AddHopFrame<DatabaseContext>();
```
# Endpoints
By default, the module provides a controller for handling authentication based requests by the user.
You can explore the contoller by the build in swagger site from ASP .NET.
## Disable the Endpoints
```csharp
builder.Services.AddDbContext<DatabaseContext>();
//builder.Services.AddHopFrame<DatabaseContext>();
services.AddHopFrameNoEndpoints<TDbContext>();
```
# Services added in this module
You can use these services by specifying them as a dependency. All of them are scoped dependencies.
## LogicResult
Logic result is an extension of the ActionResult for an ApiController. It provides simple Http status results with either a message or data by specifying the generic type.
```csharp
public class LogicResult : ILogicResult {
public static LogicResult Ok();
public static LogicResult BadRequest();
public static LogicResult BadRequest(string message);
public static LogicResult Forbidden();
public static LogicResult Forbidden(string message);
public static LogicResult NotFound();
public static LogicResult NotFound(string message);
public static LogicResult Conflict();
public static LogicResult Conflict(string message);
public static LogicResult Forward(LogicResult result);
public static LogicResult Forward<T>(ILogicResult<T> result);
public static implicit operator ActionResult(LogicResult v);
}
public class LogicResult<T> : ILogicResult<T> {
public static LogicResult<T> Ok();
public static LogicResult<T> Ok(T result);
...
}
```
## IAuthLogic
This service handles all logic needed to provide the authentication endpoints by using the LogicResults.
```csharp
public interface IAuthLogic {
Task<LogicResult<SingleValueResult<string>>> Login(UserLogin login);
Task<LogicResult<SingleValueResult<string>>> Register(UserRegister register);
Task<LogicResult<SingleValueResult<string>>> Authenticate();
Task<LogicResult> Logout();
Task<LogicResult> Delete(UserPasswordValidation validation);
}
```

View File

@@ -5,8 +5,12 @@
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable> <Nullable>disable</Nullable>
<PackageId>HopFrame.Database</PackageId>
<Version>1.1.0</Version>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>
<IsPackable>true</IsPackable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -1,4 +1,4 @@
namespace HopFrame.Web; namespace HopFrame.Security;
public static class AdminPermissions { public static class AdminPermissions {
public const string IsAdmin = "hopframe.admin"; public const string IsAdmin = "hopframe.admin";

View File

@@ -29,6 +29,8 @@ public class HopFrameAuthentication<TDbContext>(
protected override async Task<AuthenticateResult> HandleAuthenticateAsync() { protected override async Task<AuthenticateResult> HandleAuthenticateAsync() {
var accessToken = Request.Cookies[ITokenContext.AccessTokenType]; 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)) return AuthenticateResult.Fail("No Access Token provided"); if (string.IsNullOrEmpty(accessToken)) return AuthenticateResult.Fail("No Access Token provided");
var tokenEntry = await context.Tokens.SingleOrDefaultAsync(token => token.Token == accessToken); var tokenEntry = await context.Tokens.SingleOrDefaultAsync(token => token.Token == accessToken);

View File

@@ -6,12 +6,16 @@ public static class PermissionValidator {
var permLow = permission.ToLower(); var permLow = permission.ToLower();
var permsLow = permissions.Select(perm => perm.ToLower()).ToArray(); var permsLow = permissions.Select(perm => perm.ToLower()).ToArray();
if (permsLow.Any(perm => perm == permLow || perm == "*")) return true; if (permsLow.Any(perm =>
perm == permLow ||
(perm.Length > permLow.Length && perm.StartsWith(permLow) && perm.ToCharArray()[permLow.Length] == '.') ||
perm == "*"))
return true;
foreach (var perm in permsLow) { foreach (var perm in permsLow) {
if (!perm.EndsWith(".*")) continue; if (!perm.EndsWith(".*")) continue;
var permissionGroup = perm.Replace(".*", ""); var permissionGroup = perm.Substring(0, perm.Length - 1);
if (permLow.StartsWith(permissionGroup)) return true; if (permLow.StartsWith(permissionGroup)) return true;
} }

View File

@@ -6,8 +6,12 @@
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable> <Nullable>disable</Nullable>
<RootNamespace>HopFrame.Security</RootNamespace> <RootNamespace>HopFrame.Security</RootNamespace>
<PackageId>HopFrame.Security</PackageId>
<Version>1.1.0</Version>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>
<IsPackable>true</IsPackable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -0,0 +1,74 @@
# HopFrame Security module
this module contains all handlers for the login and register validation. It also checks the user permissions.
# Services added in this module
You can use these services by specifying them as a dependency. All of them are scoped dependencies.
## ITokenContext
This service provides the information given by the current request
```csharp
public interface ITokenContext {
bool IsAuthenticated { get; }
User User { get; }
Guid AccessToken { get; }
}
```
## IUserService
This service simplifies the data access of the user table in the database.
```csharp
public interface IUserService {
Task<IList<User>> GetUsers();
Task<User> GetUser(Guid userId);
Task<User> GetUserByEmail(string email);
Task<User> GetUserByUsername(string username);
Task<User> AddUser(UserRegister user);
Task UpdateUser(User user);
Task DeleteUser(User user);
Task<bool> CheckUserPassword(User user, string password);
Task ChangePassword(User user, string password);
}
```
## IPermissionService
This service handles all permission and group interactions with the data source.
```csharp
public interface IPermissionService {
Task<bool> HasPermission(string permission, Guid user);
Task<IList<PermissionGroup>> GetPermissionGroups();
Task<PermissionGroup> GetPermissionGroup(string name);
Task EditPermissionGroup(PermissionGroup group);
Task<IList<PermissionGroup>> GetUserPermissionGroups(User user);
Task RemoveGroupFromUser(User user, PermissionGroup group);
Task<PermissionGroup> CreatePermissionGroup(string name, bool isDefault = false, string description = null);
Task DeletePermissionGroup(PermissionGroup group);
Task<Permission> GetPermission(string name, IPermissionOwner owner);
Task AddPermission(IPermissionOwner owner, string permission);
Task RemovePermission(Permission permission);
Task<string[]> GetFullPermissions(string user);
}
```

View File

@@ -2,6 +2,13 @@ using HopFrame.Database.Models;
namespace HopFrame.Security.Services; namespace HopFrame.Security.Services;
/// <summary>
/// permission system:<br/>
/// - "*" -> all rights<br/>
/// - "group.[name]" -> group member<br/>
/// - "[namespace].[name]" -> single permission<br/>
/// - "[namespace].*" -> all permissions in the namespace
/// </summary>
public interface IPermissionService { public interface IPermissionService {
Task<bool> HasPermission(string permission, Guid user); Task<bool> HasPermission(string permission, Guid user);

View File

@@ -0,0 +1,16 @@
namespace HopFrame.Web;
[Obsolete("Use HopFrame.Security.AdminPermissions instead")]
public static class AdminPermissions {
public const string IsAdmin = Security.AdminPermissions.IsAdmin;
public const string ViewUsers = Security.AdminPermissions.ViewUsers;
public const string EditUser = Security.AdminPermissions.EditUser;
public const string DeleteUser = Security.AdminPermissions.DeleteUser;
public const string AddUser = Security.AdminPermissions.AddUser;
public const string ViewGroups = Security.AdminPermissions.ViewGroups;
public const string EditGroup = Security.AdminPermissions.EditGroup;
public const string DeleteGroup = Security.AdminPermissions.DeleteGroup;
public const string AddGroup = Security.AdminPermissions.AddGroup;
}

View File

@@ -167,7 +167,7 @@
} }
if (_isEdit) { if (_isEdit) {
if (!(await Permissions.HasPermission(AdminPermissions.EditGroup, Context.User.Id))) { if (!(await Permissions.HasPermission(Security.AdminPermissions.EditGroup, Context.User.Id))) {
await NoEditPermissions(); await NoEditPermissions();
return; return;
} }
@@ -202,7 +202,7 @@
} }
if (_isEdit) { if (_isEdit) {
if (!(await Permissions.HasPermission(AdminPermissions.EditGroup, Context.User.Id))) { if (!(await Permissions.HasPermission(Security.AdminPermissions.EditGroup, Context.User.Id))) {
await NoEditPermissions(); await NoEditPermissions();
return; return;
} }
@@ -219,7 +219,7 @@
private async Task AddGroup() { private async Task AddGroup() {
if (_isEdit) { if (_isEdit) {
if (!(await Permissions.HasPermission(AdminPermissions.EditGroup, Context.User.Id))) { if (!(await Permissions.HasPermission(Security.AdminPermissions.EditGroup, Context.User.Id))) {
await NoEditPermissions(); await NoEditPermissions();
return; return;
} }
@@ -239,7 +239,7 @@
return; return;
} }
if (!(await Permissions.HasPermission(AdminPermissions.AddGroup, Context.User.Id))) { if (!(await Permissions.HasPermission(Security.AdminPermissions.AddGroup, Context.User.Id))) {
await NoAddPermissions(); await NoAddPermissions();
return; return;
} }

View File

@@ -69,7 +69,7 @@
} }
private async Task AddUser() { private async Task AddUser() {
if (!(await Permissions.HasPermission(AdminPermissions.AddUser, Auth.User.Id))) { if (!(await Permissions.HasPermission(Security.AdminPermissions.AddUser, Auth.User.Id))) {
await NoAddPermissions(); await NoAddPermissions();
return; return;
} }

View File

@@ -118,7 +118,7 @@
private string _permissionToAdd; private string _permissionToAdd;
public async Task ShowAsync(User user) { public async Task ShowAsync(User user) {
if (!(await Permissions.HasPermission(AdminPermissions.EditUser, Auth.User.Id))) { if (!(await Permissions.HasPermission(Security.AdminPermissions.EditUser, Auth.User.Id))) {
await NoEditPermissions(); await NoEditPermissions();
return; return;
} }
@@ -130,7 +130,7 @@
} }
private async Task AddGroup() { private async Task AddGroup() {
if (!(await Permissions.HasPermission(AdminPermissions.EditUser, Auth.User.Id))) { if (!(await Permissions.HasPermission(Security.AdminPermissions.EditUser, Auth.User.Id))) {
await NoEditPermissions(); await NoEditPermissions();
return; return;
} }
@@ -158,7 +158,7 @@
} }
private async Task RemoveGroup(PermissionGroup group) { private async Task RemoveGroup(PermissionGroup group) {
if (!(await Permissions.HasPermission(AdminPermissions.EditUser, Auth.User.Id))) { if (!(await Permissions.HasPermission(Security.AdminPermissions.EditUser, Auth.User.Id))) {
await NoEditPermissions(); await NoEditPermissions();
return; return;
} }
@@ -186,7 +186,7 @@
} }
private async Task AddPermission() { private async Task AddPermission() {
if (!(await Permissions.HasPermission(AdminPermissions.EditUser, Auth.User.Id))) { if (!(await Permissions.HasPermission(Security.AdminPermissions.EditUser, Auth.User.Id))) {
await NoEditPermissions(); await NoEditPermissions();
return; return;
} }
@@ -213,7 +213,7 @@
} }
private async Task RemovePermission(Permission perm) { private async Task RemovePermission(Permission perm) {
if (!(await Permissions.HasPermission(AdminPermissions.EditUser, Auth.User.Id))) { if (!(await Permissions.HasPermission(Security.AdminPermissions.EditUser, Auth.User.Id))) {
await NoEditPermissions(); await NoEditPermissions();
return; return;
} }
@@ -241,7 +241,7 @@
} }
private async void EditUser() { private async void EditUser() {
if (!(await Permissions.HasPermission(AdminPermissions.EditUser, Auth.User.Id))) { if (!(await Permissions.HasPermission(Security.AdminPermissions.EditUser, Auth.User.Id))) {
await NoEditPermissions(); await NoEditPermissions();
return; return;
} }

View File

@@ -5,8 +5,12 @@
<Nullable>disable</Nullable> <Nullable>disable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc> <AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<PackageId>HopFrame.Web</PackageId>
<Version>1.1.0</Version>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>
<IsPackable>true</IsPackable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -16,7 +16,7 @@
@using HopFrame.Web.Pages.Administration.Layout @using HopFrame.Web.Pages.Administration.Layout
<PageTitle>Groups</PageTitle> <PageTitle>Groups</PageTitle>
<AuthorizedView Permission="@AdminPermissions.ViewGroups" RedirectIfUnauthorized="administration/login?redirect=/administration/groups"/> <AuthorizedView Permission="@Security.AdminPermissions.ViewGroups" RedirectIfUnauthorized="administration/login?redirect=/administration/groups"/>
<GroupAddModal ReloadPage="Reload" @ref="_groupAddModal"/> <GroupAddModal ReloadPage="Reload" @ref="_groupAddModal"/>
@@ -32,7 +32,7 @@
<input class="form-control me-2 input-dark" type="search" placeholder="Search" aria-label="Search" @bind="_searchText"> <input class="form-control me-2 input-dark" type="search" placeholder="Search" aria-label="Search" @bind="_searchText">
<BSButton Color="BSColor.Success" IsOutlined="true" type="submit">Search</BSButton> <BSButton Color="BSColor.Success" IsOutlined="true" type="submit">Search</BSButton>
</form> </form>
<AuthorizedView Permission="@AdminPermissions.AddGroup"> <AuthorizedView Permission="@Security.AdminPermissions.AddGroup">
<BSButton IsSubmit="false" Color="BSColor.Success" Target="add-user" OnClick="() => _groupAddModal.ShowAsync()">Add Group</BSButton> <BSButton IsSubmit="false" Color="BSColor.Success" Target="add-user" OnClick="() => _groupAddModal.ShowAsync()">Add Group</BSButton>
</AuthorizedView> </AuthorizedView>
</div> </div>
@@ -112,8 +112,8 @@
protected override async Task OnInitializedAsync() { protected override async Task OnInitializedAsync() {
_groups = await Permissions.GetPermissionGroups(); _groups = await Permissions.GetPermissionGroups();
_hasEditPrivileges = await Permissions.HasPermission(AdminPermissions.EditGroup, Auth.User.Id); _hasEditPrivileges = await Permissions.HasPermission(Security.AdminPermissions.EditGroup, Auth.User.Id);
_hasDeletePrivileges = await Permissions.HasPermission(AdminPermissions.DeleteGroup, Auth.User.Id); _hasDeletePrivileges = await Permissions.HasPermission(Security.AdminPermissions.DeleteGroup, Auth.User.Id);
} }
private async Task Reload() { private async Task Reload() {

View File

@@ -2,7 +2,7 @@
@using BlazorStrap.V5 @using BlazorStrap.V5
@inherits LayoutComponentBase @inherits LayoutComponentBase
<AuthorizedView Permission="@AdminPermissions.IsAdmin" RedirectIfUnauthorized="administration/login" /> <AuthorizedView Permission="@Security.AdminPermissions.IsAdmin" RedirectIfUnauthorized="administration/login" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">

View File

@@ -53,13 +53,13 @@
Name = "Users", Name = "Users",
Url = "administration/users", Url = "administration/users",
Description = "On this page you can manage all user accounts.", Description = "On this page you can manage all user accounts.",
Permission = AdminPermissions.ViewUsers Permission = Security.AdminPermissions.ViewUsers
}, },
new () { new () {
Name = "Groups", Name = "Groups",
Url = "administration/groups", Url = "administration/groups",
Description = "On this page you can view, create, edit and delete permission groups.", Description = "On this page you can view, create, edit and delete permission groups.",
Permission = AdminPermissions.ViewGroups Permission = Security.AdminPermissions.ViewGroups
} }
}; };

View File

@@ -16,7 +16,7 @@
@using HopFrame.Web.Components.Administration @using HopFrame.Web.Components.Administration
<PageTitle>Users</PageTitle> <PageTitle>Users</PageTitle>
<AuthorizedView Permission="@AdminPermissions.ViewUsers" RedirectIfUnauthorized="administration/login?redirect=/administration/users"/> <AuthorizedView Permission="@Security.AdminPermissions.ViewUsers" RedirectIfUnauthorized="administration/login?redirect=/administration/users"/>
<UserAddModal @ref="_userAddModal" ReloadPage="Reload"/> <UserAddModal @ref="_userAddModal" ReloadPage="Reload"/>
<UserEditModal @ref="_userEditModal" ReloadPage="Reload"/> <UserEditModal @ref="_userEditModal" ReloadPage="Reload"/>
@@ -33,7 +33,7 @@
<input class="form-control me-2 input-dark" type="search" placeholder="Search" aria-label="Search" @bind="_searchText"> <input class="form-control me-2 input-dark" type="search" placeholder="Search" aria-label="Search" @bind="_searchText">
<BSButton Color="BSColor.Success" IsOutlined="true" type="submit">Search</BSButton> <BSButton Color="BSColor.Success" IsOutlined="true" type="submit">Search</BSButton>
</form> </form>
<AuthorizedView Permission="@AdminPermissions.AddUser"> <AuthorizedView Permission="@Security.AdminPermissions.AddUser">
<BSButton IsSubmit="false" Color="BSColor.Success" Target="add-user" OnClick="() => _userAddModal.ShowAsync()">Add User</BSButton> <BSButton IsSubmit="false" Color="BSColor.Success" Target="add-user" OnClick="() => _userAddModal.ShowAsync()">Add User</BSButton>
</AuthorizedView> </AuthorizedView>
</div> </div>
@@ -123,8 +123,8 @@
_userGroups.Add(user.Id, groups.LastOrDefault()); _userGroups.Add(user.Id, groups.LastOrDefault());
} }
_hasEditPrivileges = await PermissionsService.HasPermission(AdminPermissions.EditUser, Auth.User.Id); _hasEditPrivileges = await PermissionsService.HasPermission(Security.AdminPermissions.EditUser, Auth.User.Id);
_hasDeletePrivileges = await PermissionsService.HasPermission(AdminPermissions.DeleteUser, Auth.User.Id); _hasDeletePrivileges = await PermissionsService.HasPermission(Security.AdminPermissions.DeleteUser, Auth.User.Id);
} }
private async Task Reload() { private async Task Reload() {

View File

@@ -0,0 +1,60 @@
# HopFrame Web module
This module contains useful helpers for Blazor Apps and an Admin Dashboard.
## How to use the Blazor API
1. Add the HopFrame.Web library to your project
```
dotnet add package HopFrame.Web
```
2. Create a DbContext that inherits the ``HopDbContext`` and add a data source
```csharp
public class DatabaseContext : HopDbContextBase {
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlite("...");
}
}
```
3. Add the DbContext and HopFrame to your services
```csharp
builder.Services.AddDbContext<DatabaseContext>();
builder.Services.AddHopFrame<DatabaseContext>();
```
4. Add the authentication middleware to your app
```csharp
app.UseMiddleware<AuthMiddleware>();
```
5. Add the HopFrame pages to your Razor components
```csharp
app.MapRazorComponents<App>()
.AddHopFrameAdminPages()
.AddInteractiveServerRenderMode();
```
# Services added in this module
You can use these services by specifying them as a dependency. All of them are scoped dependencies.
## IAuthService
This service handles all the authentication like login or register. It properly creates all tokens so the user can be identified
```csharp
public interface IAuthService {
Task Register(UserRegister register);
Task<bool> Login(UserLogin login);
Task Logout();
Task<TokenEntry> RefreshLogin();
Task<bool> IsLoggedIn();
}
```

View File

@@ -10,7 +10,7 @@ using Microsoft.Extensions.DependencyInjection;
namespace HopFrame.Web; namespace HopFrame.Web;
public static class ServiceCollectionExtensions { public static class ServiceCollectionExtensions {
public static IServiceCollection AddHopFrameServices<TDbContext>(this IServiceCollection services) where TDbContext : HopDbContextBase { public static IServiceCollection AddHopFrame<TDbContext>(this IServiceCollection services) where TDbContext : HopDbContextBase {
services.AddHttpClient(); services.AddHttpClient();
services.AddScoped<IAuthService, AuthService<TDbContext>>(); services.AddScoped<IAuthService, AuthService<TDbContext>>();
services.AddTransient<AuthMiddleware>(); services.AddTransient<AuthMiddleware>();
@@ -19,8 +19,6 @@ public static class ServiceCollectionExtensions {
services.AddSweetAlert2(); services.AddSweetAlert2();
services.AddBlazorStrap(); services.AddBlazorStrap();
//TODO: Use https://blazorstrap.io/V5/V5
services.AddHopFrameAuthentication<TDbContext>(); services.AddHopFrameAuthentication<TDbContext>();
return services; return services;

View File

@@ -7,6 +7,6 @@ public class DatabaseContext : HopDbContextBase {
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
base.OnConfiguring(optionsBuilder); base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlite("Data Source=C:\\Users\\Remote\\Documents\\Projekte\\HopFrame\\RestApiTest\\bin\\Debug\\net8.0\\test.db;Mode=ReadWrite;"); optionsBuilder.UseSqlite("Data Source=C:\\Users\\Remote\\Documents\\Projekte\\HopFrame\\test\\RestApiTest\\bin\\Debug\\net8.0\\test.db;Mode=ReadWrite;");
} }
} }

View File

@@ -6,10 +6,6 @@
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\HopFrame.Web\HopFrame.Web.csproj" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.7" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.7" />
</ItemGroup> </ItemGroup>
@@ -19,4 +15,8 @@
<_ContentIncludedByDefault Remove="wwwroot\bootstrap\bootstrap.min.css.map" /> <_ContentIncludedByDefault Remove="wwwroot\bootstrap\bootstrap.min.css.map" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\HopFrame.Web\HopFrame.Web.csproj" />
</ItemGroup>
</Project> </Project>

View File

@@ -5,7 +5,7 @@ using HopFrame.Web;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<DatabaseContext>(); builder.Services.AddDbContext<DatabaseContext>();
builder.Services.AddHopFrameServices<DatabaseContext>(); builder.Services.AddHopFrame<DatabaseContext>();
// Add services to the container. // Add services to the container.
builder.Services.AddRazorComponents() builder.Services.AddRazorComponents()

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Some files were not shown because too many files have changed in this diff Show More