@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Microsoft.AspNetCore": "Warning"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# 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>();
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
# HopFrame Security module
|
|
||||||
this module contains all handlers for the login and register validation. It also checks the user permissions.
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
16
HopFrame.sln
16
HopFrame.sln
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
13
docs/README.md
Normal 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.
|
||||||
@@ -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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## 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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## 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
|
||||||
|
|
||||||

|

|
||||||
145
docs/services.md
Normal file
145
docs/services.md
Normal 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
70
docs/usage.md
Normal 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();
|
||||||
|
```
|
||||||
38
src/HopFrame.Api/Controller/SecurityController.cs
Normal file
38
src/HopFrame.Api/Controller/SecurityController.cs
Normal 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("api/v1/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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
36
src/HopFrame.Api/Extensions/ServiceCollectionExtensions.cs
Normal file
36
src/HopFrame.Api/Extensions/ServiceCollectionExtensions.cs
Normal 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>();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
16
src/HopFrame.Api/Logic/IAuthLogic.cs
Normal file
16
src/HopFrame.Api/Logic/IAuthLogic.cs
Normal 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);
|
||||||
|
}
|
||||||
@@ -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 {
|
public async Task<LogicResult<SingleValueResult<string>>> Login(UserLogin login) {
|
||||||
|
|
||||||
[HttpPut("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,12 +34,12 @@ 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
|
||||||
});
|
});
|
||||||
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 = true,
|
HttpOnly = true,
|
||||||
Secure = true
|
Secure = true
|
||||||
@@ -57,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");
|
||||||
|
|
||||||
@@ -84,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
|
||||||
@@ -98,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");
|
||||||
@@ -123,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
|
||||||
@@ -132,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");
|
||||||
@@ -152,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
100
src/HopFrame.Api/README.md
Normal 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);
|
||||||
|
}
|
||||||
|
```
|
||||||
74
src/HopFrame.Security/README.md
Normal file
74
src/HopFrame.Security/README.md
Normal 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);
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -41,3 +41,20 @@ This module contains useful helpers for Blazor Apps and an Admin Dashboard.
|
|||||||
.AddHopFrameAdminPages()
|
.AddHopFrameAdminPages()
|
||||||
.AddInteractiveServerRenderMode();
|
.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();
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -18,8 +18,6 @@ public static class ServiceCollectionExtensions {
|
|||||||
// Component library's
|
// Component library's
|
||||||
services.AddSweetAlert2();
|
services.AddSweetAlert2();
|
||||||
services.AddBlazorStrap();
|
services.AddBlazorStrap();
|
||||||
|
|
||||||
//TODO: Use https://blazorstrap.io/V5/V5
|
|
||||||
|
|
||||||
services.AddHopFrameAuthentication<TDbContext>();
|
services.AddHopFrameAuthentication<TDbContext>();
|
||||||
|
|
||||||
@@ -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;");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
|
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
Reference in New Issue
Block a user