diff --git a/.idea/.idea.HopFrame/.idea/dataSources.xml b/.idea/.idea.HopFrame/.idea/dataSources.xml
index 56f170e..ded00e9 100644
--- a/.idea/.idea.HopFrame/.idea/dataSources.xml
+++ b/.idea/.idea.HopFrame/.idea/dataSources.xml
@@ -5,7 +5,7 @@
sqlite.xerial
true
org.sqlite.JDBC
- jdbc:sqlite:$PROJECT_DIR$/RestApiTest/bin/Debug/net8.0/test.db
+ jdbc:sqlite:$PROJECT_DIR$/test/RestApiTest/bin/Debug/net8.0/test.db
diff --git a/src/HopFrame.Api/Extensions/ServiceCollectionExtensions.cs b/src/HopFrame.Api/Extensions/ServiceCollectionExtensions.cs
index 29feb38..618a437 100644
--- a/src/HopFrame.Api/Extensions/ServiceCollectionExtensions.cs
+++ b/src/HopFrame.Api/Extensions/ServiceCollectionExtensions.cs
@@ -27,10 +27,11 @@ public static class ServiceCollectionExtensions {
/// The service provider to add the services to
/// The data source for all HopFrame entities
public static void AddHopFrameNoEndpoints(this IServiceCollection services) where TDbContext : HopDbContextBase {
+ services.AddHopFrameRepositories();
services.TryAddSingleton();
- services.AddScoped>();
+ services.AddScoped();
- services.AddHopFrameAuthentication();
+ services.AddHopFrameAuthentication();
}
}
diff --git a/src/HopFrame.Api/Logic/Implementation/AuthLogic.cs b/src/HopFrame.Api/Logic/Implementation/AuthLogic.cs
index 405c2cf..e792add 100644
--- a/src/HopFrame.Api/Logic/Implementation/AuthLogic.cs
+++ b/src/HopFrame.Api/Logic/Implementation/AuthLogic.cs
@@ -1,16 +1,14 @@
using HopFrame.Api.Models;
-using HopFrame.Database;
-using HopFrame.Database.Models.Entries;
+using HopFrame.Database.Models;
+using HopFrame.Database.Repositories;
using HopFrame.Security.Authentication;
using HopFrame.Security.Claims;
using HopFrame.Security.Models;
-using HopFrame.Security.Services;
using Microsoft.AspNetCore.Http;
-using Microsoft.EntityFrameworkCore;
namespace HopFrame.Api.Logic.Implementation;
-public class AuthLogic(TDbContext context, IUserService users, ITokenContext tokenContext, IHttpContextAccessor accessor) : IAuthLogic where TDbContext : HopDbContextBase {
+public class AuthLogic(IUserRepository users, ITokenRepository tokens, ITokenContext tokenContext, IHttpContextAccessor accessor) : IAuthLogic {
public async Task>> Login(UserLogin login) {
var user = await users.GetUserByEmail(login.Email);
@@ -21,34 +19,21 @@ public class AuthLogic(TDbContext context, IUserService users, IToke
if (!await users.CheckUserPassword(user, login.Password))
return LogicResult>.Forbidden("The provided password is not correct");
- var refreshToken = new TokenEntry {
- CreatedAt = DateTime.Now,
- Token = Guid.NewGuid().ToString(),
- Type = TokenEntry.RefreshTokenType,
- UserId = user.Id.ToString()
- };
- var accessToken = new TokenEntry {
- CreatedAt = DateTime.Now,
- Token = Guid.NewGuid().ToString(),
- Type = TokenEntry.AccessTokenType,
- UserId = user.Id.ToString()
- };
+ var refreshToken = await tokens.CreateToken(Token.RefreshTokenType, user);
+ var accessToken = await tokens.CreateToken(Token.AccessTokenType, user);
- accessor.HttpContext?.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.Token, new CookieOptions {
- MaxAge = HopFrameAuthentication.RefreshTokenTime,
+ accessor.HttpContext?.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.Content.ToString(), new CookieOptions {
+ MaxAge = HopFrameAuthentication.RefreshTokenTime,
HttpOnly = true,
Secure = true
});
- accessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Token, new CookieOptions {
- MaxAge = HopFrameAuthentication.AccessTokenTime,
+ accessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Content.ToString(), new CookieOptions {
+ MaxAge = HopFrameAuthentication.AccessTokenTime,
HttpOnly = true,
Secure = true
});
- await context.Tokens.AddRangeAsync(refreshToken, accessToken);
- await context.SaveChangesAsync();
-
- return LogicResult>.Ok(accessToken.Token);
+ return LogicResult>.Ok(accessToken.Content.ToString());
}
public async Task>> Register(UserRegister register) {
@@ -59,36 +44,27 @@ public class AuthLogic(TDbContext context, IUserService users, IToke
if (allUsers.Any(user => user.Username == register.Username || user.Email == register.Email))
return LogicResult>.Conflict("Username or Email is already registered");
- var user = await users.AddUser(register);
+ var user = await users.AddUser(new User {
+ Username = register.Username,
+ Email = register.Email,
+ Password = register.Password
+ });
- var refreshToken = new TokenEntry {
- CreatedAt = DateTime.Now,
- Token = Guid.NewGuid().ToString(),
- Type = TokenEntry.RefreshTokenType,
- UserId = user.Id.ToString()
- };
- var accessToken = new TokenEntry {
- CreatedAt = DateTime.Now,
- Token = Guid.NewGuid().ToString(),
- Type = TokenEntry.AccessTokenType,
- UserId = user.Id.ToString()
- };
+ var refreshToken = await tokens.CreateToken(Token.RefreshTokenType, user);
+ var accessToken = await tokens.CreateToken(Token.AccessTokenType, user);
- await context.Tokens.AddRangeAsync(refreshToken, accessToken);
- await context.SaveChangesAsync();
-
- accessor.HttpContext?.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.Token, new CookieOptions {
- MaxAge = HopFrameAuthentication.RefreshTokenTime,
+ accessor.HttpContext?.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.Content.ToString(), new CookieOptions {
+ MaxAge = HopFrameAuthentication.RefreshTokenTime,
HttpOnly = true,
Secure = true
});
- accessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Token, new CookieOptions {
- MaxAge = HopFrameAuthentication.AccessTokenTime,
+ accessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Content.ToString(), new CookieOptions {
+ MaxAge = HopFrameAuthentication.AccessTokenTime,
HttpOnly = false,
Secure = true
});
- return LogicResult>.Ok(accessToken.Token);
+ return LogicResult>.Ok(accessToken.Content.ToString());
}
public async Task>> Authenticate() {
@@ -97,31 +73,26 @@ public class AuthLogic(TDbContext context, IUserService users, IToke
if (string.IsNullOrEmpty(refreshToken))
return LogicResult>.Conflict("Refresh token not provided");
- var token = await context.Tokens.SingleOrDefaultAsync(token => token.Token == refreshToken && token.Type == TokenEntry.RefreshTokenType);
-
+ var token = await tokens.GetToken(refreshToken);
+
+ if (token.Type != Token.RefreshTokenType)
+ return LogicResult>.BadRequest("The provided token is not a refresh token");
+
if (token is null)
return LogicResult>.NotFound("Refresh token not valid");
- if (token.CreatedAt + HopFrameAuthentication.RefreshTokenTime < DateTime.Now)
+ if (token.CreatedAt + HopFrameAuthentication.RefreshTokenTime < DateTime.Now)
return LogicResult>.Conflict("Refresh token is expired");
- var accessToken = new TokenEntry {
- CreatedAt = DateTime.Now,
- Token = Guid.NewGuid().ToString(),
- Type = TokenEntry.AccessTokenType,
- UserId = token.UserId
- };
-
- await context.Tokens.AddAsync(accessToken);
- await context.SaveChangesAsync();
+ var accessToken = await tokens.CreateToken(Token.AccessTokenType, token.Owner);
- accessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Token, new CookieOptions {
- MaxAge = HopFrameAuthentication.AccessTokenTime,
+ accessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Content.ToString(), new CookieOptions {
+ MaxAge = HopFrameAuthentication.AccessTokenTime,
HttpOnly = false,
Secure = true
});
- return LogicResult>.Ok(accessToken.Token);
+ return LogicResult>.Ok(accessToken.Content.ToString());
}
public async Task Logout() {
@@ -131,17 +102,7 @@ public class AuthLogic(TDbContext context, IUserService users, IToke
if (string.IsNullOrEmpty(accessToken) || string.IsNullOrEmpty(refreshToken))
return LogicResult.Conflict("access or refresh token not provided");
- var tokenEntries = await context.Tokens.Where(token =>
- (token.Token == accessToken && token.Type == TokenEntry.AccessTokenType) ||
- (token.Token == refreshToken && token.Type == TokenEntry.RefreshTokenType))
- .ToArrayAsync();
-
- if (tokenEntries.Length != 2)
- return LogicResult.NotFound("One or more of the provided tokens was not found");
-
- context.Tokens.Remove(tokenEntries[0]);
- context.Tokens.Remove(tokenEntries[1]);
- await context.SaveChangesAsync();
+ await tokens.DeleteUserTokens(tokenContext.User);
accessor.HttpContext?.Response.Cookies.Delete(ITokenContext.RefreshTokenType);
accessor.HttpContext?.Response.Cookies.Delete(ITokenContext.AccessTokenType);
diff --git a/src/HopFrame.Security/EncryptionManager.cs b/src/HopFrame.Database/EncryptionManager.cs
similarity index 96%
rename from src/HopFrame.Security/EncryptionManager.cs
rename to src/HopFrame.Database/EncryptionManager.cs
index 8f5037b..32bb2d5 100644
--- a/src/HopFrame.Security/EncryptionManager.cs
+++ b/src/HopFrame.Database/EncryptionManager.cs
@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
-namespace HopFrame.Security;
+namespace HopFrame.Database;
public static class EncryptionManager {
diff --git a/src/HopFrame.Database/HopDbContextBase.cs b/src/HopFrame.Database/HopDbContextBase.cs
index 2ae1fe6..21342ea 100644
--- a/src/HopFrame.Database/HopDbContextBase.cs
+++ b/src/HopFrame.Database/HopDbContextBase.cs
@@ -1,4 +1,4 @@
-using HopFrame.Database.Models.Entries;
+using HopFrame.Database.Models;
using Microsoft.EntityFrameworkCore;
namespace HopFrame.Database;
@@ -8,25 +8,27 @@ namespace HopFrame.Database;
///
public abstract class HopDbContextBase : DbContext {
- public virtual DbSet Users { get; set; }
- public virtual DbSet Permissions { get; set; }
- public virtual DbSet Tokens { get; set; }
- public virtual DbSet Groups { get; set; }
+ public virtual DbSet Users { get; set; }
+ public virtual DbSet Permissions { get; set; }
+ public virtual DbSet Tokens { get; set; }
+ public virtual DbSet Groups { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
- modelBuilder.Entity();
- modelBuilder.Entity();
- modelBuilder.Entity();
- modelBuilder.Entity();
- }
+ modelBuilder.Entity()
+ .HasMany(u => u.Tokens)
+ .WithOne(t => t.Owner)
+ .OnDelete(DeleteBehavior.Cascade);
- ///
- /// Gets executed when a user is deleted through the IUserService from the
- /// HopFrame.Security package. You can override this method to also delete
- /// related user specific entries in the database
- ///
- ///
- public virtual void OnUserDelete(UserEntry user) {}
+ modelBuilder.Entity()
+ .HasMany(u => u.Permissions)
+ .WithOne(p => p.User)
+ .OnDelete(DeleteBehavior.Cascade);
+
+ modelBuilder.Entity()
+ .HasMany(g => g.Permissions)
+ .WithOne(p => p.Group)
+ .OnDelete(DeleteBehavior.Cascade);
+ }
}
\ No newline at end of file
diff --git a/src/HopFrame.Database/HopFrame.Database.csproj b/src/HopFrame.Database/HopFrame.Database.csproj
index d88b757..f2bcba7 100644
--- a/src/HopFrame.Database/HopFrame.Database.csproj
+++ b/src/HopFrame.Database/HopFrame.Database.csproj
@@ -14,6 +14,7 @@
+
diff --git a/src/HopFrame.Database/Models/Entries/GroupEntry.cs b/src/HopFrame.Database/Models/Entries/GroupEntry.cs
deleted file mode 100644
index 830d466..0000000
--- a/src/HopFrame.Database/Models/Entries/GroupEntry.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System.ComponentModel;
-using System.ComponentModel.DataAnnotations;
-
-namespace HopFrame.Database.Models.Entries;
-
-public class GroupEntry {
- [Key, Required, MaxLength(50)]
- public string Name { get; set; }
-
- [Required, DefaultValue(false)]
- public bool Default { get; set; }
-
- [MaxLength(500)]
- public string Description { get; set; }
-
- [Required]
- public DateTime CreatedAt { get; set; }
-}
\ No newline at end of file
diff --git a/src/HopFrame.Database/Models/Entries/PermissionEntry.cs b/src/HopFrame.Database/Models/Entries/PermissionEntry.cs
deleted file mode 100644
index 2f8bdae..0000000
--- a/src/HopFrame.Database/Models/Entries/PermissionEntry.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
-
-namespace HopFrame.Database.Models.Entries;
-
-public sealed class PermissionEntry {
- [Key, Required, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
- public long RecordId { get; set; }
-
- [Required, MaxLength(255)]
- public string PermissionText { get; set; }
-
- [Required, MinLength(36), MaxLength(36)]
- public string UserId { get; set; }
-
- [Required]
- public DateTime GrantedAt { get; set; }
-}
\ No newline at end of file
diff --git a/src/HopFrame.Database/Models/Entries/UserEntry.cs b/src/HopFrame.Database/Models/Entries/UserEntry.cs
deleted file mode 100644
index 2bc1a12..0000000
--- a/src/HopFrame.Database/Models/Entries/UserEntry.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using System.ComponentModel.DataAnnotations;
-
-namespace HopFrame.Database.Models.Entries;
-
-public class UserEntry {
- [Key, Required, MinLength(36), MaxLength(36)]
- public string Id { get; set; }
-
- [MaxLength(50)]
- public string Username { get; set; }
-
- [Required, MaxLength(50), EmailAddress]
- public string Email { get; set; }
-
- [Required, MinLength(8), MaxLength(255)]
- public string Password { get; set; }
-
- [Required]
- public DateTime CreatedAt { get; set; }
-}
\ No newline at end of file
diff --git a/src/HopFrame.Database/Models/ModelExtensions.cs b/src/HopFrame.Database/Models/ModelExtensions.cs
deleted file mode 100644
index 4600afd..0000000
--- a/src/HopFrame.Database/Models/ModelExtensions.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-using HopFrame.Database.Models.Entries;
-
-namespace HopFrame.Database.Models;
-
-public static class ModelExtensions {
-
- ///
- /// Converts the database model to a friendly user model
- ///
- /// the database model
- /// the data source for the permissions and users
- ///
- public static User ToUserModel(this UserEntry entry, HopDbContextBase contextBase) {
- var user = new User {
- Id = Guid.Parse(entry.Id),
- Username = entry.Username,
- Email = entry.Email,
- CreatedAt = entry.CreatedAt
- };
-
- user.Permissions = contextBase.Permissions
- .Where(perm => perm.UserId == entry.Id)
- .Select(perm => perm.ToPermissionModel())
- .ToList();
-
- return user;
- }
-
- public static Permission ToPermissionModel(this PermissionEntry entry) {
- Guid.TryParse(entry.UserId, out var userId);
-
- return new Permission {
- Owner = userId,
- PermissionName = entry.PermissionText,
- GrantedAt = entry.GrantedAt,
- Id = entry.RecordId
- };
- }
-
- public static PermissionGroup ToPermissionGroup(this GroupEntry entry, HopDbContextBase contextBase) {
- var group = new PermissionGroup {
- Name = entry.Name,
- IsDefaultGroup = entry.Default,
- Description = entry.Description,
- CreatedAt = entry.CreatedAt
- };
-
- group.Permissions = contextBase.Permissions
- .Where(perm => perm.UserId == group.Name)
- .Select(perm => perm.ToPermissionModel())
- .ToList();
-
- return group;
- }
-
-}
\ No newline at end of file
diff --git a/src/HopFrame.Database/Models/Permission.cs b/src/HopFrame.Database/Models/Permission.cs
index e6fbe14..db111ba 100644
--- a/src/HopFrame.Database/Models/Permission.cs
+++ b/src/HopFrame.Database/Models/Permission.cs
@@ -1,10 +1,26 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Text.Json.Serialization;
+
namespace HopFrame.Database.Models;
-public sealed class Permission {
+public class Permission {
+
+ [Key, Required, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; init; }
+
+ [Required, MaxLength(255)]
public string PermissionName { get; set; }
- public Guid Owner { get; set; }
+
+ [Required]
public DateTime GrantedAt { get; set; }
+
+ [ForeignKey("UserId"), JsonIgnore]
+ public virtual User User { get; set; }
+
+ [ForeignKey("GroupName"), JsonIgnore]
+ public virtual PermissionGroup Group { get; set; }
+
}
-public interface IPermissionOwner {}
+public interface IPermissionOwner;
diff --git a/src/HopFrame.Database/Models/PermissionGroup.cs b/src/HopFrame.Database/Models/PermissionGroup.cs
index 3472e39..7a70ebd 100644
--- a/src/HopFrame.Database/Models/PermissionGroup.cs
+++ b/src/HopFrame.Database/Models/PermissionGroup.cs
@@ -1,9 +1,22 @@
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+
namespace HopFrame.Database.Models;
public class PermissionGroup : IPermissionOwner {
+
+ [Key, Required, MaxLength(50)]
public string Name { get; init; }
+
+ [Required, DefaultValue(false)]
public bool IsDefaultGroup { get; set; }
+
+ [MaxLength(500)]
public string Description { get; set; }
+
+ [Required]
public DateTime CreatedAt { get; set; }
- public IList Permissions { get; set; }
+
+ public virtual IList Permissions { get; set; }
+
}
\ No newline at end of file
diff --git a/src/HopFrame.Database/Models/Entries/TokenEntry.cs b/src/HopFrame.Database/Models/Token.cs
similarity index 62%
rename from src/HopFrame.Database/Models/Entries/TokenEntry.cs
rename to src/HopFrame.Database/Models/Token.cs
index d33b307..a42d367 100644
--- a/src/HopFrame.Database/Models/Entries/TokenEntry.cs
+++ b/src/HopFrame.Database/Models/Token.cs
@@ -1,8 +1,10 @@
using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Text.Json.Serialization;
-namespace HopFrame.Database.Models.Entries;
+namespace HopFrame.Database.Models;
-public class TokenEntry {
+public class Token {
public const int RefreshTokenType = 0;
public const int AccessTokenType = 1;
@@ -15,11 +17,11 @@ public class TokenEntry {
public int Type { get; set; }
[Key, Required, MinLength(36), MaxLength(36)]
- public string Token { get; set; }
-
- [Required, MinLength(36), MaxLength(36)]
- public string UserId { get; set; }
+ public Guid Content { get; set; }
[Required]
public DateTime CreatedAt { get; set; }
+
+ [ForeignKey("UserId"), JsonIgnore]
+ public virtual User Owner { get; set; }
}
\ No newline at end of file
diff --git a/src/HopFrame.Database/Models/User.cs b/src/HopFrame.Database/Models/User.cs
index e97d720..971d899 100644
--- a/src/HopFrame.Database/Models/User.cs
+++ b/src/HopFrame.Database/Models/User.cs
@@ -1,9 +1,28 @@
+using System.ComponentModel.DataAnnotations;
+using System.Text.Json.Serialization;
+
namespace HopFrame.Database.Models;
-public sealed class User : IPermissionOwner {
+public class User : IPermissionOwner {
+
+ [Key, Required, MinLength(36), MaxLength(36)]
public Guid Id { get; init; }
+
+ [MaxLength(50)]
public string Username { get; set; }
+
+ [Required, MaxLength(50), EmailAddress]
public string Email { get; set; }
+
+ [Required, MinLength(8), MaxLength(255), JsonIgnore]
+ public string Password { get; set; }
+
+ [Required]
public DateTime CreatedAt { get; set; }
- public IList Permissions { get; set; }
+
+ public virtual IList Permissions { get; set; }
+
+ [JsonIgnore]
+ public virtual IList Tokens { get; set; }
+
}
\ No newline at end of file
diff --git a/src/HopFrame.Security/Authorization/PermissionValidator.cs b/src/HopFrame.Database/PermissionValidator.cs
similarity index 94%
rename from src/HopFrame.Security/Authorization/PermissionValidator.cs
rename to src/HopFrame.Database/PermissionValidator.cs
index d3a844b..de19dfc 100644
--- a/src/HopFrame.Security/Authorization/PermissionValidator.cs
+++ b/src/HopFrame.Database/PermissionValidator.cs
@@ -1,4 +1,4 @@
-namespace HopFrame.Security.Authorization;
+namespace HopFrame.Database;
public static class PermissionValidator {
diff --git a/src/HopFrame.Database/Repositories/IGroupRepository.cs b/src/HopFrame.Database/Repositories/IGroupRepository.cs
new file mode 100644
index 0000000..fde75de
--- /dev/null
+++ b/src/HopFrame.Database/Repositories/IGroupRepository.cs
@@ -0,0 +1,19 @@
+using HopFrame.Database.Models;
+
+namespace HopFrame.Database.Repositories;
+
+public interface IGroupRepository {
+ Task> GetPermissionGroups();
+
+ Task> GetDefaultGroups();
+
+ Task GetPermissionGroup(string name);
+
+ Task EditPermissionGroup(PermissionGroup group);
+
+ Task CreatePermissionGroup(PermissionGroup group);
+
+ Task DeletePermissionGroup(PermissionGroup group);
+
+ internal Task> GetFullGroupPermissions(string group);
+}
\ No newline at end of file
diff --git a/src/HopFrame.Database/Repositories/IPermissionRepository.cs b/src/HopFrame.Database/Repositories/IPermissionRepository.cs
new file mode 100644
index 0000000..5971e34
--- /dev/null
+++ b/src/HopFrame.Database/Repositories/IPermissionRepository.cs
@@ -0,0 +1,23 @@
+using HopFrame.Database.Models;
+
+namespace HopFrame.Database.Repositories;
+
+public interface IPermissionRepository {
+ Task HasPermission(IPermissionOwner owner, params string[] permissions);
+
+ ///
+ /// permission system:
+ /// - "*" -> all rights
+ /// - "group.[name]" -> group member
+ /// - "[namespace].[name]" -> single permission
+ /// - "[namespace].*" -> all permissions in the namespace
+ ///
+ ///
+ ///
+ ///
+ Task AddPermission(IPermissionOwner owner, string permission);
+
+ Task RemovePermission(IPermissionOwner owner, string permission);
+
+ public Task> GetFullPermissions(IPermissionOwner owner);
+}
\ No newline at end of file
diff --git a/src/HopFrame.Database/Repositories/ITokenRepository.cs b/src/HopFrame.Database/Repositories/ITokenRepository.cs
new file mode 100644
index 0000000..19b38ac
--- /dev/null
+++ b/src/HopFrame.Database/Repositories/ITokenRepository.cs
@@ -0,0 +1,9 @@
+using HopFrame.Database.Models;
+
+namespace HopFrame.Database.Repositories;
+
+public interface ITokenRepository {
+ public Task GetToken(string content);
+ public Task CreateToken(int type, User owner);
+ public Task DeleteUserTokens(User owner);
+}
\ No newline at end of file
diff --git a/src/HopFrame.Security/Services/IUserService.cs b/src/HopFrame.Database/Repositories/IUserRepository.cs
similarity index 71%
rename from src/HopFrame.Security/Services/IUserService.cs
rename to src/HopFrame.Database/Repositories/IUserRepository.cs
index 5109dea..e24fd57 100644
--- a/src/HopFrame.Security/Services/IUserService.cs
+++ b/src/HopFrame.Database/Repositories/IUserRepository.cs
@@ -1,9 +1,8 @@
using HopFrame.Database.Models;
-using HopFrame.Security.Models;
-namespace HopFrame.Security.Services;
+namespace HopFrame.Database.Repositories;
-public interface IUserService {
+public interface IUserRepository {
Task> GetUsers();
Task GetUser(Guid userId);
@@ -12,12 +11,12 @@ public interface IUserService {
Task GetUserByUsername(string username);
- Task AddUser(UserRegister user);
+ Task AddUser(User user);
///
/// IMPORTANT:
/// This function does not add or remove any permissions to the user.
- /// For that please use
+ /// For that please use
///
Task UpdateUser(User user);
diff --git a/src/HopFrame.Database/Repositories/Implementation/GroupRepository.cs b/src/HopFrame.Database/Repositories/Implementation/GroupRepository.cs
new file mode 100644
index 0000000..3906b1b
--- /dev/null
+++ b/src/HopFrame.Database/Repositories/Implementation/GroupRepository.cs
@@ -0,0 +1,71 @@
+using HopFrame.Database.Models;
+using Microsoft.EntityFrameworkCore;
+
+namespace HopFrame.Database.Repositories.Implementation;
+
+internal sealed class GroupRepository(TDbContext context) : IGroupRepository where TDbContext : HopDbContextBase {
+ public async Task> GetPermissionGroups() {
+ return await context.Groups
+ .Include(g => g.Permissions)
+ .ToListAsync();
+ }
+
+ public async Task> GetDefaultGroups() {
+ return await context.Groups
+ .Include(g => g.Permissions)
+ .Where(g => g.IsDefaultGroup)
+ .ToListAsync();
+ }
+
+ public async Task GetPermissionGroup(string name) {
+ return await context.Groups
+ .Include(g => g.Permissions)
+ .Where(g => g.Name == name)
+ .SingleOrDefaultAsync();
+ }
+
+ public async Task EditPermissionGroup(PermissionGroup group) {
+ var orig = await context.Groups.SingleOrDefaultAsync(g => g.Name == group.Name);
+
+ if (orig is null) return;
+
+ var entity = context.Groups.Update(orig);
+
+ entity.Entity.IsDefaultGroup = group.IsDefaultGroup;
+ entity.Entity.Description = group.Description;
+ entity.Entity.Permissions = group.Permissions;
+
+ await context.SaveChangesAsync();
+ }
+
+ public async Task CreatePermissionGroup(PermissionGroup group) {
+ group.CreatedAt = DateTime.Now;
+ await context.Groups.AddAsync(group);
+ await context.SaveChangesAsync();
+ return group;
+ }
+
+ public async Task DeletePermissionGroup(PermissionGroup group) {
+ context.Groups.Remove(group);
+ await context.SaveChangesAsync();
+ }
+
+ public async Task> GetFullGroupPermissions(string group) {
+ var permissions = await context.Permissions
+ .Include(p => p.Group)
+ .Where(p => p.Group != null)
+ .Where(p => p.Group.Name == group)
+ .Select(p => p.PermissionName)
+ .ToListAsync();
+
+ var groups = permissions
+ .Where(p => p.StartsWith("group."))
+ .ToList();
+
+ foreach (var subgroup in groups) {
+ permissions.AddRange(await GetFullGroupPermissions(subgroup));
+ }
+
+ return permissions;
+ }
+}
\ No newline at end of file
diff --git a/src/HopFrame.Database/Repositories/Implementation/PermissionRepository.cs b/src/HopFrame.Database/Repositories/Implementation/PermissionRepository.cs
new file mode 100644
index 0000000..45bcfd8
--- /dev/null
+++ b/src/HopFrame.Database/Repositories/Implementation/PermissionRepository.cs
@@ -0,0 +1,89 @@
+using HopFrame.Database.Models;
+using Microsoft.EntityFrameworkCore;
+
+namespace HopFrame.Database.Repositories.Implementation;
+
+internal sealed class PermissionRepository(TDbContext context, IGroupRepository groupRepository) : IPermissionRepository where TDbContext : HopDbContextBase {
+ public async Task HasPermission(IPermissionOwner owner, params string[] permissions) {
+ var perms = (await GetFullPermissions(owner)).ToArray();
+
+ foreach (var permission in permissions) {
+ if (!PermissionValidator.IncludesPermission(permission, perms)) return false;
+ }
+
+ return true;
+ }
+
+ public async Task AddPermission(IPermissionOwner owner, string permission) {
+ var entry = new Permission {
+ GrantedAt = DateTime.Now,
+ PermissionName = permission
+ };
+
+ if (owner is User user) {
+ entry.User = user;
+ }else if (owner is PermissionGroup group) {
+ entry.Group = group;
+ }
+
+ await context.Permissions.AddAsync(entry);
+ await context.SaveChangesAsync();
+ return entry;
+ }
+
+ public async Task RemovePermission(IPermissionOwner owner, string permission) {
+ Permission entry = null;
+
+ if (owner is User user) {
+ entry = await context.Permissions
+ .Include(p => p.User)
+ .Where(p => p.User != null)
+ .Where(p => p.User.Id == user.Id)
+ .Where(p => p.PermissionName == permission)
+ .SingleOrDefaultAsync();
+ }else if (owner is PermissionGroup group) {
+ entry = await context.Permissions
+ .Include(p => p.Group)
+ .Where(p => p.Group != null)
+ .Where(p =>p.Group.Name == group.Name)
+ .Where(p => p.PermissionName == permission)
+ .SingleOrDefaultAsync();
+ }
+
+ if (entry is not null) {
+ context.Permissions.Remove(entry);
+ await context.SaveChangesAsync();
+ }
+ }
+
+ public async Task> GetFullPermissions(IPermissionOwner owner) {
+ var permissions = new List();
+
+ if (owner is User user) {
+ var perms = await context.Permissions
+ .Include(p => p.User)
+ .Where(p => p.User != null)
+ .Where(p => p.User.Id == user.Id)
+ .ToListAsync();
+
+ permissions.AddRange(perms.Select(p => p.PermissionName));
+ }else if (owner is PermissionGroup group) {
+ var perms = await context.Permissions
+ .Include(p => p.Group)
+ .Where(p => p.Group != null)
+ .Where(p =>p.Group.Name == group.Name)
+ .ToListAsync();
+
+ permissions.AddRange(perms.Select(p => p.PermissionName));
+ }
+
+ var groups = permissions
+ .Where(p => p.StartsWith("group."))
+ .ToList();
+ foreach (var group in groups) {
+ permissions.AddRange(await groupRepository.GetFullGroupPermissions(group));
+ }
+
+ return permissions;
+ }
+}
\ No newline at end of file
diff --git a/src/HopFrame.Database/Repositories/Implementation/TokenRepository.cs b/src/HopFrame.Database/Repositories/Implementation/TokenRepository.cs
new file mode 100644
index 0000000..70f727a
--- /dev/null
+++ b/src/HopFrame.Database/Repositories/Implementation/TokenRepository.cs
@@ -0,0 +1,41 @@
+using HopFrame.Database.Models;
+using Microsoft.EntityFrameworkCore;
+
+namespace HopFrame.Database.Repositories.Implementation;
+
+internal sealed class TokenRepository(TDbContext context) : ITokenRepository where TDbContext : HopDbContextBase {
+
+ public async Task GetToken(string content) {
+ var success = Guid.TryParse(content, out Guid guid);
+ if (!success) return null;
+
+ return await context.Tokens
+ .Include(t => t.Owner)
+ .Where(t => t.Content == guid)
+ .SingleOrDefaultAsync();
+ }
+
+ public async Task CreateToken(int type, User owner) {
+ var token = new Token {
+ CreatedAt = DateTime.Now,
+ Content = Guid.NewGuid(),
+ Type = type,
+ Owner = owner
+ };
+
+ await context.Tokens.AddAsync(token);
+ await context.SaveChangesAsync();
+
+ return token;
+ }
+
+ public async Task DeleteUserTokens(User owner) {
+ var tokens = await context.Tokens
+ .Include(t => t.Owner)
+ .Where(t => t.Owner.Id == owner.Id)
+ .ToListAsync();
+
+ context.Tokens.RemoveRange(tokens);
+ await context.SaveChangesAsync();
+ }
+}
\ No newline at end of file
diff --git a/src/HopFrame.Database/Repositories/Implementation/UserRepository.cs b/src/HopFrame.Database/Repositories/Implementation/UserRepository.cs
new file mode 100644
index 0000000..9c8e322
--- /dev/null
+++ b/src/HopFrame.Database/Repositories/Implementation/UserRepository.cs
@@ -0,0 +1,116 @@
+using System.Globalization;
+using System.Text;
+using HopFrame.Database.Models;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Query;
+
+namespace HopFrame.Database.Repositories.Implementation;
+
+internal sealed class UserRepository(TDbContext context, IGroupRepository groupRepository) : IUserRepository where TDbContext : HopDbContextBase {
+
+ private IIncludableQueryable> IncludeReferences() {
+ return context.Users
+ .Include(u => u.Permissions)
+ .ThenInclude(p => p.Group)
+ .Include(u => u.Tokens);
+ }
+
+ public async Task> GetUsers() {
+ return await IncludeReferences()
+ .ToListAsync();
+ }
+
+ public async Task GetUser(Guid userId) {
+ return await IncludeReferences()
+ .Where(u => u.Id == userId)
+ .SingleOrDefaultAsync();
+ }
+
+ public async Task GetUserByEmail(string email) {
+ return await IncludeReferences()
+ .Where(u => u.Email == email)
+ .SingleOrDefaultAsync();
+ }
+
+ public async Task GetUserByUsername(string username) {
+ return await IncludeReferences()
+ .Where(u => u.Username == username)
+ .SingleOrDefaultAsync();
+ }
+
+ public async Task AddUser(User user) {
+ if (await GetUserByEmail(user.Email) is not null) return null;
+ if (await GetUserByUsername(user.Username) is not null) return null;
+
+ var entry = new User {
+ Id = Guid.NewGuid(),
+ Email = user.Email,
+ Username = user.Username,
+ CreatedAt = DateTime.Now,
+ Permissions = user.Permissions,
+ Tokens = user.Tokens
+ };
+ entry.Password = EncryptionManager.Hash(user.Password, Encoding.Default.GetBytes(entry.CreatedAt.ToString(CultureInfo.InvariantCulture)));
+
+ var defaultGroups = await groupRepository.GetDefaultGroups();
+ foreach (var group in defaultGroups) {
+ entry.Permissions.Add(new Permission {
+ PermissionName = group.Name,
+ //TODO: Check if user needs to be set
+ });
+ }
+
+ await context.Users.AddAsync(entry);
+
+
+ await context.SaveChangesAsync();
+ return entry;
+ }
+
+ public async Task UpdateUser(User user) {
+ var entry = await IncludeReferences()
+ .SingleOrDefaultAsync(entry => entry.Id == user.Id);
+ if (entry is null) return;
+
+ entry.Email = user.Email;
+ entry.Username = user.Username;
+ entry.Permissions = user.Permissions;
+ entry.Tokens = user.Tokens;
+
+ await context.SaveChangesAsync();
+ }
+
+ public async Task DeleteUser(User user) {
+ var entry = await context.Users
+ .SingleOrDefaultAsync(entry => entry.Id == user.Id);
+
+ if (entry is null) return;
+
+ context.Users.Remove(entry);
+
+ await context.SaveChangesAsync();
+ }
+
+ public async Task CheckUserPassword(User user, string password) {
+ var hash = EncryptionManager.Hash(password, Encoding.Default.GetBytes(user.CreatedAt.ToString(CultureInfo.InvariantCulture)));
+
+ var entry = await context.Users
+ .Where(entry => entry.Id == user.Id)
+ .SingleOrDefaultAsync();
+
+ return entry.Password == hash;
+ }
+
+ public async Task ChangePassword(User user, string password) {
+ var entry = await context.Users
+ .Where(entry => entry.Id == user.Id)
+ .SingleOrDefaultAsync();
+
+ if (entry is null) return;
+
+ var hash = EncryptionManager.Hash(password, Encoding.Default.GetBytes(user.CreatedAt.ToString(CultureInfo.InvariantCulture)));
+ entry.Password = hash;
+ await context.SaveChangesAsync();
+ }
+
+}
\ No newline at end of file
diff --git a/src/HopFrame.Database/ServiceCollectionExtensions.cs b/src/HopFrame.Database/ServiceCollectionExtensions.cs
new file mode 100644
index 0000000..ab07d5a
--- /dev/null
+++ b/src/HopFrame.Database/ServiceCollectionExtensions.cs
@@ -0,0 +1,18 @@
+using HopFrame.Database.Repositories;
+using HopFrame.Database.Repositories.Implementation;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace HopFrame.Database;
+
+public static class ServiceCollectionExtensions {
+
+ public static IServiceCollection AddHopFrameRepositories(this IServiceCollection services) where TDbContext : HopDbContextBase {
+ services.AddScoped>();
+ services.AddScoped>();
+ services.AddScoped>();
+ services.AddScoped>();
+
+ return services;
+ }
+
+}
\ No newline at end of file
diff --git a/src/HopFrame.Security/Authentication/HopFrameAuthentication.cs b/src/HopFrame.Security/Authentication/HopFrameAuthentication.cs
index 8232d4c..9c65b14 100644
--- a/src/HopFrame.Security/Authentication/HopFrameAuthentication.cs
+++ b/src/HopFrame.Security/Authentication/HopFrameAuthentication.cs
@@ -1,10 +1,8 @@
using System.Security.Claims;
using System.Text.Encodings.Web;
-using HopFrame.Database;
+using HopFrame.Database.Repositories;
using HopFrame.Security.Claims;
-using HopFrame.Security.Services;
using Microsoft.AspNetCore.Authentication;
-using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@@ -13,15 +11,15 @@ using Microsoft.Extensions.Options;
namespace HopFrame.Security.Authentication;
-public class HopFrameAuthentication(
+public class HopFrameAuthentication(
IOptionsMonitor options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock,
- TDbContext context,
- IPermissionService perms)
- : AuthenticationHandler(options, logger, encoder, clock)
- where TDbContext : HopDbContextBase {
+ ITokenRepository tokens,
+ IUserRepository users,
+ IPermissionRepository perms)
+ : AuthenticationHandler(options, logger, encoder, clock) {
public const string SchemeName = "HopCore.Authentication";
public static readonly TimeSpan AccessTokenTime = new(0, 0, 5, 0);
@@ -32,21 +30,21 @@ public class HopFrameAuthentication(
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");
-
- var tokenEntry = await context.Tokens.SingleOrDefaultAsync(token => token.Token == accessToken);
+
+ 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 (!await context.Users.AnyAsync(user => user.Id == tokenEntry.UserId))
+ if (tokenEntry.Owner is null)
return AuthenticateResult.Fail("The provided Access Token does not match any user");
var claims = new List {
new(HopFrameClaimTypes.AccessTokenId, accessToken),
- new(HopFrameClaimTypes.UserId, tokenEntry.UserId)
+ new(HopFrameClaimTypes.UserId, tokenEntry.Owner.Id.ToString())
};
- var permissions = await perms.GetFullPermissions(tokenEntry.UserId);
+ var permissions = await perms.GetFullPermissions(tokenEntry.Owner);
claims.AddRange(permissions.Select(perm => new Claim(HopFrameClaimTypes.Permission, perm)));
var principal = new ClaimsPrincipal();
diff --git a/src/HopFrame.Security/Authentication/HopFrameAuthenticationExtensions.cs b/src/HopFrame.Security/Authentication/HopFrameAuthenticationExtensions.cs
index f604c34..cf87810 100644
--- a/src/HopFrame.Security/Authentication/HopFrameAuthenticationExtensions.cs
+++ b/src/HopFrame.Security/Authentication/HopFrameAuthenticationExtensions.cs
@@ -1,7 +1,4 @@
-using HopFrame.Database;
using HopFrame.Security.Claims;
-using HopFrame.Security.Services;
-using HopFrame.Security.Services.Implementation;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
@@ -17,13 +14,11 @@ public static class HopFrameAuthenticationExtensions {
/// The service provider to add the services to
/// The database object that saves all entities that are important for the security api
///
- public static IServiceCollection AddHopFrameAuthentication(this IServiceCollection service) where TDbContext : HopDbContextBase {
+ public static IServiceCollection AddHopFrameAuthentication(this IServiceCollection service) {
service.TryAddSingleton();
- service.AddScoped>();
- service.AddScoped>();
- service.AddScoped>();
+ service.AddScoped();
- service.AddAuthentication(HopFrameAuthentication.SchemeName).AddScheme>(HopFrameAuthentication.SchemeName, _ => {});
+ service.AddAuthentication(HopFrameAuthentication.SchemeName).AddScheme(HopFrameAuthentication.SchemeName, _ => {});
service.AddAuthorization();
return service;
diff --git a/src/HopFrame.Security/Authorization/AuthorizedFilter.cs b/src/HopFrame.Security/Authorization/AuthorizedFilter.cs
index 13f5932..f78cdc0 100644
--- a/src/HopFrame.Security/Authorization/AuthorizedFilter.cs
+++ b/src/HopFrame.Security/Authorization/AuthorizedFilter.cs
@@ -1,3 +1,4 @@
+using HopFrame.Database;
using HopFrame.Security.Claims;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Authorization;
diff --git a/src/HopFrame.Security/Claims/ITokenContext.cs b/src/HopFrame.Security/Claims/ITokenContext.cs
index 3b4f5e9..6b5a590 100644
--- a/src/HopFrame.Security/Claims/ITokenContext.cs
+++ b/src/HopFrame.Security/Claims/ITokenContext.cs
@@ -20,5 +20,5 @@ public interface ITokenContext {
///
/// The access token the user provided
///
- Guid AccessToken { get; }
+ Token AccessToken { get; }
}
\ No newline at end of file
diff --git a/src/HopFrame.Security/Claims/TokenContextImplementor.cs b/src/HopFrame.Security/Claims/TokenContextImplementor.cs
index dbdae9e..dd50a08 100644
--- a/src/HopFrame.Security/Claims/TokenContextImplementor.cs
+++ b/src/HopFrame.Security/Claims/TokenContextImplementor.cs
@@ -1,15 +1,13 @@
-using HopFrame.Database;
using HopFrame.Database.Models;
+using HopFrame.Database.Repositories;
using Microsoft.AspNetCore.Http;
namespace HopFrame.Security.Claims;
-internal sealed class TokenContextImplementor(IHttpContextAccessor accessor, TDbContext context) : ITokenContext where TDbContext : HopDbContextBase {
+internal sealed class TokenContextImplementor(IHttpContextAccessor accessor, IUserRepository users, ITokenRepository tokens) : ITokenContext {
public bool IsAuthenticated => !string.IsNullOrEmpty(accessor.HttpContext?.User.GetAccessTokenId());
- public User User => context.Users
- .SingleOrDefault(user => user.Id == accessor.HttpContext.User.GetUserId())?
- .ToUserModel(context);
-
- public Guid AccessToken => Guid.Parse(accessor.HttpContext?.User.GetAccessTokenId() ?? Guid.Empty.ToString());
+ public User User => users.GetUser(Guid.Parse(accessor.HttpContext?.User.GetUserId() ?? Guid.Empty.ToString())).GetAwaiter().GetResult();
+
+ public Token AccessToken => tokens.GetToken(accessor.HttpContext?.User.GetAccessTokenId()).GetAwaiter().GetResult();
}
\ No newline at end of file
diff --git a/src/HopFrame.Security/Services/IPermissionService.cs b/src/HopFrame.Security/Services/IPermissionService.cs
deleted file mode 100644
index 38a9000..0000000
--- a/src/HopFrame.Security/Services/IPermissionService.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using HopFrame.Database.Models;
-
-namespace HopFrame.Security.Services;
-
-///
-/// permission system:
-/// - "*" -> all rights
-/// - "group.[name]" -> group member
-/// - "[namespace].[name]" -> single permission
-/// - "[namespace].*" -> all permissions in the namespace
-///
-public interface IPermissionService {
-
- Task HasPermission(string permission, Guid user);
-
- Task> GetPermissionGroups();
-
- Task GetPermissionGroup(string name);
-
- Task EditPermissionGroup(PermissionGroup group);
-
- Task> GetUserPermissionGroups(User user);
-
- Task RemoveGroupFromUser(User user, PermissionGroup group);
-
- Task CreatePermissionGroup(string name, bool isDefault = false, string description = null);
-
- Task DeletePermissionGroup(PermissionGroup group);
-
- Task GetPermission(string name, IPermissionOwner owner);
-
- ///
- /// permission system:
- /// - "*" -> all rights
- /// - "group.[name]" -> group member
- /// - "[namespace].[name]" -> single permission
- /// - "[namespace].*" -> all permissions in the namespace
- ///
- ///
- ///
- ///
- Task AddPermission(IPermissionOwner owner, string permission);
-
- Task RemovePermission(Permission permission);
-
- Task GetFullPermissions(string user);
-
-}
\ No newline at end of file
diff --git a/src/HopFrame.Security/Services/Implementation/PermissionService.cs b/src/HopFrame.Security/Services/Implementation/PermissionService.cs
deleted file mode 100644
index ac0e156..0000000
--- a/src/HopFrame.Security/Services/Implementation/PermissionService.cs
+++ /dev/null
@@ -1,178 +0,0 @@
-using HopFrame.Database;
-using HopFrame.Database.Models;
-using HopFrame.Database.Models.Entries;
-using HopFrame.Security.Authorization;
-using HopFrame.Security.Claims;
-using Microsoft.EntityFrameworkCore;
-
-namespace HopFrame.Security.Services.Implementation;
-
-internal sealed class PermissionService(TDbContext context, ITokenContext current) : IPermissionService where TDbContext : HopDbContextBase {
- public async Task HasPermission(string permission) {
- return await HasPermission(permission, current.User.Id);
- }
-
- public async Task HasPermissions(params string[] permissions) {
- var user = current.User.Id.ToString();
- var perms = await GetFullPermissions(user);
-
- foreach (var permission in permissions) {
- if (!PermissionValidator.IncludesPermission(permission, perms)) return false;
- }
-
- return true;
- }
-
- public async Task HasAnyPermission(params string[] permissions) {
- var user = current.User.Id.ToString();
- var perms = await GetFullPermissions(user);
-
- foreach (var permission in permissions) {
- if (PermissionValidator.IncludesPermission(permission, perms)) return true;
- }
-
- return false;
- }
-
- public async Task HasPermission(string permission, Guid user) {
- var permissions = await GetFullPermissions(user.ToString());
-
- return PermissionValidator.IncludesPermission(permission, permissions);
- }
-
- public async Task> GetPermissionGroups() {
- return await context.Groups
- .Select(group => group.ToPermissionGroup(context))
- .ToListAsync();
- }
-
- public Task GetPermissionGroup(string name) {
- return context.Groups
- .Where(group => group.Name == name)
- .Select(group => group.ToPermissionGroup(context))
- .SingleOrDefaultAsync();
- }
-
- public async Task EditPermissionGroup(PermissionGroup group) {
- var orig = await context.Groups.SingleOrDefaultAsync(g => g.Name == group.Name);
-
- if (orig is null) return;
-
- var entity = context.Groups.Update(orig);
-
- entity.Entity.Default = group.IsDefaultGroup;
- entity.Entity.Description = group.Description;
-
- await context.SaveChangesAsync();
- }
-
- public async Task> GetUserPermissionGroups(User user) {
- var groups = await context.Groups.ToListAsync();
- var perms = await GetFullPermissions(user.Id.ToString());
-
- return groups
- .Where(group => perms.Contains(group.Name))
- .Select(group => group.ToPermissionGroup(context))
- .ToList();
- }
-
- public async Task RemoveGroupFromUser(User user, PermissionGroup group) {
- var entry = await context.Permissions
- .Where(perm => perm.PermissionText == group.Name && perm.UserId == user.Id.ToString())
- .SingleOrDefaultAsync();
-
- if (entry is null) return;
-
- context.Permissions.Remove(entry);
- await context.SaveChangesAsync();
- }
-
- public async Task CreatePermissionGroup(string name, bool isDefault = false, string description = null) {
- var group = new GroupEntry {
- Name = name,
- Description = description,
- Default = isDefault,
- CreatedAt = DateTime.Now
- };
-
- await context.Groups.AddAsync(group);
-
- if (isDefault) {
- var users = await context.Users.ToListAsync();
-
- foreach (var user in users) {
- await context.Permissions.AddAsync(new PermissionEntry {
- GrantedAt = DateTime.Now,
- PermissionText = group.Name,
- UserId = user.Id
- });
- }
- }
-
- await context.SaveChangesAsync();
-
- return group.ToPermissionGroup(context);
- }
-
- public async Task DeletePermissionGroup(PermissionGroup group) {
- var entry = await context.Groups.SingleOrDefaultAsync(entry => entry.Name == group.Name);
- context.Groups.Remove(entry);
-
- var permissions = await context.Permissions
- .Where(perm => perm.UserId == group.Name || perm.PermissionText == group.Name)
- .ToListAsync();
-
- if (permissions.Count > 0) {
- context.Permissions.RemoveRange(permissions);
- }
-
- await context.SaveChangesAsync();
- }
-
- public async Task GetPermission(string name, IPermissionOwner owner) {
- var ownerId = (owner is User user) ? user.Id.ToString() : ((PermissionGroup)owner).Name;
-
- return await context.Permissions
- .Where(perm => perm.PermissionText == name && perm.UserId == ownerId)
- .Select(perm => perm.ToPermissionModel())
- .SingleOrDefaultAsync();
- }
-
- public async Task AddPermission(IPermissionOwner owner, string permission) {
- var userId = owner is User user ? user.Id.ToString() : (owner as PermissionGroup)?.Name;
-
- await context.Permissions.AddAsync(new PermissionEntry {
- UserId = userId,
- PermissionText = permission,
- GrantedAt = DateTime.Now
- });
- await context.SaveChangesAsync();
- }
-
- public async Task RemovePermission(Permission permission) {
- var entry = await context.Permissions.SingleOrDefaultAsync(entry => entry.RecordId == permission.Id);
- context.Permissions.Remove(entry);
- await context.SaveChangesAsync();
- }
-
- public async Task GetFullPermissions(string user) {
- var permissions = await context.Permissions
- .Where(perm => perm.UserId == user)
- .Select(perm => perm.PermissionText)
- .ToListAsync();
-
- var groups = permissions
- .Where(perm => perm.StartsWith("group."))
- .ToList();
-
- var groupPerms = new List();
- foreach (var group in groups) {
- var perms = await GetFullPermissions(group);
- groupPerms.AddRange(perms);
- }
-
- permissions.AddRange(groupPerms);
-
- return permissions.ToArray();
- }
-}
\ No newline at end of file
diff --git a/src/HopFrame.Security/Services/Implementation/UserService.cs b/src/HopFrame.Security/Services/Implementation/UserService.cs
deleted file mode 100644
index 0e19b58..0000000
--- a/src/HopFrame.Security/Services/Implementation/UserService.cs
+++ /dev/null
@@ -1,128 +0,0 @@
-using System.Globalization;
-using System.Text;
-using HopFrame.Database;
-using HopFrame.Database.Models;
-using HopFrame.Database.Models.Entries;
-using HopFrame.Security.Models;
-using Microsoft.EntityFrameworkCore;
-
-namespace HopFrame.Security.Services.Implementation;
-
-internal sealed class UserService(TDbContext context) : IUserService where TDbContext : HopDbContextBase {
- public async Task> GetUsers() {
- return await context.Users
- .Select(user => user.ToUserModel(context))
- .ToListAsync();
- }
-
- public Task GetUser(Guid userId) {
- var id = userId.ToString();
-
- return context.Users
- .Where(user => user.Id == id)
- .Select(user => user.ToUserModel(context))
- .SingleOrDefaultAsync();
- }
-
- public Task GetUserByEmail(string email) {
- return context.Users
- .Where(user => user.Email == email)
- .Select(user => user.ToUserModel(context))
- .SingleOrDefaultAsync();
- }
-
- public Task GetUserByUsername(string username) {
- return context.Users
- .Where(user => user.Username == username)
- .Select(user => user.ToUserModel(context))
- .SingleOrDefaultAsync();
- }
-
- public async Task AddUser(UserRegister user) {
- if (await GetUserByEmail(user.Email) is not null) return null;
- if (await GetUserByUsername(user.Username) is not null) return null;
-
- var entry = new UserEntry {
- Id = Guid.NewGuid().ToString(),
- Email = user.Email,
- Username = user.Username,
- CreatedAt = DateTime.Now
- };
- entry.Password = EncryptionManager.Hash(user.Password, Encoding.Default.GetBytes(entry.CreatedAt.ToString(CultureInfo.InvariantCulture)));
-
- await context.Users.AddAsync(entry);
-
- var defaultGroups = await context.Groups
- .Where(group => group.Default)
- .Select(group => "group." + group.Name)
- .ToListAsync();
-
- await context.Permissions.AddRangeAsync(defaultGroups.Select(group => new PermissionEntry {
- GrantedAt = DateTime.Now,
- PermissionText = group,
- UserId = entry.Id
- }));
-
- await context.SaveChangesAsync();
- return entry.ToUserModel(context);
- }
-
- public async Task UpdateUser(User user) {
- var id = user.Id.ToString();
- var entry = await context.Users
- .SingleOrDefaultAsync(entry => entry.Id == id);
- if (entry is null) return;
-
- entry.Email = user.Email;
- entry.Username = user.Username;
-
- await context.SaveChangesAsync();
- }
-
- public async Task DeleteUser(User user) {
- var id = user.Id.ToString();
- var entry = await context.Users
- .SingleOrDefaultAsync(entry => entry.Id == id);
-
- if (entry is null) return;
-
- context.Users.Remove(entry);
-
- var userTokens = await context.Tokens
- .Where(token => token.UserId == id)
- .ToArrayAsync();
- context.Tokens.RemoveRange(userTokens);
-
- var userPermissions = await context.Permissions
- .Where(perm => perm.UserId == id)
- .ToArrayAsync();
- context.Permissions.RemoveRange(userPermissions);
-
- context.OnUserDelete(entry);
-
- await context.SaveChangesAsync();
- }
-
- public async Task CheckUserPassword(User user, string password) {
- var id = user.Id.ToString();
- var hash = EncryptionManager.Hash(password, Encoding.Default.GetBytes(user.CreatedAt.ToString(CultureInfo.InvariantCulture)));
-
- var entry = await context.Users
- .Where(entry => entry.Id == id)
- .SingleOrDefaultAsync();
-
- return entry.Password == hash;
- }
-
- public async Task ChangePassword(User user, string password) {
- var entry = await context.Users
- .Where(entry => entry.Id == user.Id.ToString())
- .SingleOrDefaultAsync();
-
- if (entry is null) return;
-
- var hash = EncryptionManager.Hash(password, Encoding.Default.GetBytes(user.CreatedAt.ToString(CultureInfo.InvariantCulture)));
- entry.Password = hash;
- await context.SaveChangesAsync();
- }
-}
\ No newline at end of file
diff --git a/src/HopFrame.Web/AuthMiddleware.cs b/src/HopFrame.Web/AuthMiddleware.cs
index bc0c0c5..509ad83 100644
--- a/src/HopFrame.Web/AuthMiddleware.cs
+++ b/src/HopFrame.Web/AuthMiddleware.cs
@@ -1,14 +1,13 @@
using System.Security.Claims;
-using HopFrame.Database;
+using HopFrame.Database.Repositories;
using HopFrame.Security.Authentication;
using HopFrame.Security.Claims;
-using HopFrame.Security.Services;
using HopFrame.Web.Services;
using Microsoft.AspNetCore.Http;
namespace HopFrame.Web;
-public sealed class AuthMiddleware(IAuthService auth, IPermissionService perms) : IMiddleware {
+public sealed class AuthMiddleware(IAuthService auth, IPermissionRepository perms) : IMiddleware {
public async Task InvokeAsync(HttpContext context, RequestDelegate next) {
var loggedIn = await auth.IsLoggedIn();
@@ -20,14 +19,14 @@ public sealed class AuthMiddleware(IAuthService auth, IPermissionService perms)
}
var claims = new List {
- new(HopFrameClaimTypes.AccessTokenId, token.Token),
- new(HopFrameClaimTypes.UserId, token.UserId)
+ new(HopFrameClaimTypes.AccessTokenId, token.Content.ToString()),
+ new(HopFrameClaimTypes.UserId, token.Owner.Id.ToString())
};
- var permissions = await perms.GetFullPermissions(token.UserId);
+ var permissions = await perms.GetFullPermissions(token.Owner);
claims.AddRange(permissions.Select(perm => new Claim(HopFrameClaimTypes.Permission, perm)));
- context.User.AddIdentity(new ClaimsIdentity(claims, HopFrameAuthentication.SchemeName));
+ context.User.AddIdentity(new ClaimsIdentity(claims, HopFrameAuthentication.SchemeName));
}
await next?.Invoke(context);
diff --git a/src/HopFrame.Web/Components/Administration/GroupAddModal.razor b/src/HopFrame.Web/Components/Administration/GroupAddModal.razor
index d1e2723..b23c216 100644
--- a/src/HopFrame.Web/Components/Administration/GroupAddModal.razor
+++ b/src/HopFrame.Web/Components/Administration/GroupAddModal.razor
@@ -6,8 +6,8 @@
@using BlazorStrap.V5
@using CurrieTechnologies.Razor.SweetAlert2
@using HopFrame.Database.Models
+@using HopFrame.Database.Repositories
@using HopFrame.Security.Claims
-@using HopFrame.Security.Services
@using HopFrame.Web.Model
@@ -115,7 +115,8 @@
-@inject IPermissionService Permissions
+@inject IGroupRepository Groups
+@inject IPermissionRepository Permissions
@inject SweetAlertService Alerts
@inject ITokenContext Context
@@ -133,7 +134,7 @@
private bool _isEdit;
public async Task ShowAsync(PermissionGroup group = null) {
- _allGroups = await Permissions.GetPermissionGroups();
+ _allGroups = await Groups.GetPermissionGroups();
if (group is not null) {
_group = new PermissionGroupAdd {
@@ -167,7 +168,7 @@
}
if (_isEdit) {
- if (!(await Permissions.HasPermission(Security.AdminPermissions.EditGroup, Context.User.Id))) {
+ if (!await Permissions.HasPermission(Context.User, Security.AdminPermissions.EditGroup)) {
await NoEditPermissions();
return;
}
@@ -184,8 +185,7 @@
private async Task RemovePermission(Permission permission) {
if (_isEdit) {
- var perm = await Permissions.GetPermission(permission.PermissionName, _group);
- await Permissions.RemovePermission(perm);
+ await Permissions.RemovePermission(_group, permission.PermissionName);
}
_group.Permissions.Remove(permission);
@@ -202,7 +202,7 @@
}
if (_isEdit) {
- if (!(await Permissions.HasPermission(Security.AdminPermissions.EditGroup, Context.User.Id))) {
+ if (!await Permissions.HasPermission(Context.User, Security.AdminPermissions.EditGroup)) {
await NoEditPermissions();
return;
}
@@ -219,12 +219,12 @@
private async Task AddGroup() {
if (_isEdit) {
- if (!(await Permissions.HasPermission(Security.AdminPermissions.EditGroup, Context.User.Id))) {
+ if (!await Permissions.HasPermission(Context.User, Security.AdminPermissions.EditGroup)) {
await NoEditPermissions();
return;
}
- await Permissions.EditPermissionGroup(_group);
+ await Groups.EditPermissionGroup(_group);
if (ReloadPage is not null)
await ReloadPage.Invoke();
@@ -239,7 +239,7 @@
return;
}
- if (!(await Permissions.HasPermission(Security.AdminPermissions.AddGroup, Context.User.Id))) {
+ if (!await Permissions.HasPermission(Context.User, Security.AdminPermissions.AddGroup)) {
await NoAddPermissions();
return;
}
@@ -255,11 +255,7 @@
return;
}
- var dbGroup = await Permissions.CreatePermissionGroup("group." + _group.GroupName, _group.IsDefaultGroup, _group.Description);
-
- foreach (var permission in _group.Permissions) {
- await Permissions.AddPermission(dbGroup, permission.PermissionName);
- }
+ await Groups.CreatePermissionGroup(_group);
if (ReloadPage is not null)
await ReloadPage.Invoke();
diff --git a/src/HopFrame.Web/Components/Administration/UserAddModal.razor b/src/HopFrame.Web/Components/Administration/UserAddModal.razor
index 96ff62b..44b47b3 100644
--- a/src/HopFrame.Web/Components/Administration/UserAddModal.razor
+++ b/src/HopFrame.Web/Components/Administration/UserAddModal.razor
@@ -6,8 +6,8 @@
@using BlazorStrap.V5
@using CurrieTechnologies.Razor.SweetAlert2
@using HopFrame.Database.Models
+@using HopFrame.Database.Repositories
@using HopFrame.Security.Claims
-@using HopFrame.Security.Services
@using HopFrame.Web.Model
@@ -47,8 +47,9 @@
-@inject IUserService Users
-@inject IPermissionService Permissions
+@inject IUserRepository Users
+@inject IPermissionRepository Permissions
+@inject IGroupRepository Groups
@inject SweetAlertService Alerts
@inject ITokenContext Auth
@@ -62,14 +63,14 @@
private BSModalBase _modal;
public async Task ShowAsync() {
- _allGroups = await Permissions.GetPermissionGroups();
+ _allGroups = await Groups.GetPermissionGroups();
_allUsers = await Users.GetUsers();
await _modal.ShowAsync();
}
private async Task AddUser() {
- if (!(await Permissions.HasPermission(Security.AdminPermissions.AddUser, Auth.User.Id))) {
+ if (!(await Permissions.HasPermission(Auth.User, Security.AdminPermissions.AddUser))) {
await NoAddPermissions();
return;
}
@@ -104,7 +105,11 @@
return;
}
- var user = await Users.AddUser(_user);
+ var user = await Users.AddUser(new User {
+ Username = _user.Username,
+ Email = _user.Email,
+ Password = _user.Password
+ });
if (!string.IsNullOrWhiteSpace(_user.Group)) {
await Permissions.AddPermission(user, _user.Group);
diff --git a/src/HopFrame.Web/Components/Administration/UserEditModal.razor b/src/HopFrame.Web/Components/Administration/UserEditModal.razor
index 930dba6..ef0efa6 100644
--- a/src/HopFrame.Web/Components/Administration/UserEditModal.razor
+++ b/src/HopFrame.Web/Components/Administration/UserEditModal.razor
@@ -6,8 +6,8 @@
@using BlazorStrap.V5
@using CurrieTechnologies.Razor.SweetAlert2
@using HopFrame.Database.Models
+@using HopFrame.Database.Repositories
@using HopFrame.Security.Claims
-@using HopFrame.Security.Services
@using HopFrame.Web.Model
@@ -100,8 +100,9 @@
-@inject IUserService Users
-@inject IPermissionService Permissions
+@inject IUserRepository Users
+@inject IPermissionRepository Permissions
+@inject IGroupRepository Groups
@inject SweetAlertService Alerts
@inject ITokenContext Auth
@@ -118,19 +119,19 @@
private string _permissionToAdd;
public async Task ShowAsync(User user) {
- if (!(await Permissions.HasPermission(Security.AdminPermissions.EditUser, Auth.User.Id))) {
+ if (!await Permissions.HasPermission(Auth.User, Security.AdminPermissions.EditUser)) {
await NoEditPermissions();
return;
}
_user = user;
- _userGroups = await Permissions.GetUserPermissionGroups(_user);
- _allGroups = await Permissions.GetPermissionGroups();
+ _userGroups = _user.Permissions.Where(p => p.PermissionName.StartsWith("group.")).Select(p => p.Group).ToList();
+ _allGroups = await Groups.GetPermissionGroups();
await _modal.ShowAsync();
}
private async Task AddGroup() {
- if (!(await Permissions.HasPermission(Security.AdminPermissions.EditUser, Auth.User.Id))) {
+ if (!await Permissions.HasPermission(Auth.User, Security.AdminPermissions.EditUser)) {
await NoEditPermissions();
return;
}
@@ -158,7 +159,7 @@
}
private async Task RemoveGroup(PermissionGroup group) {
- if (!(await Permissions.HasPermission(Security.AdminPermissions.EditUser, Auth.User.Id))) {
+ if (!await Permissions.HasPermission(Auth.User, Security.AdminPermissions.EditUser)) {
await NoEditPermissions();
return;
}
@@ -172,7 +173,7 @@
});
if (result.IsConfirmed) {
- await Permissions.RemoveGroupFromUser(_user, group);
+ await Permissions.RemovePermission(_user, group.Name);
_userGroups.Remove(group);
StateHasChanged();
@@ -186,7 +187,7 @@
}
private async Task AddPermission() {
- if (!(await Permissions.HasPermission(Security.AdminPermissions.EditUser, Auth.User.Id))) {
+ if (!await Permissions.HasPermission(Auth.User, Security.AdminPermissions.EditUser)) {
await NoEditPermissions();
return;
}
@@ -200,8 +201,7 @@
return;
}
- await Permissions.AddPermission(_user, _permissionToAdd);
- _user.Permissions.Add(await Permissions.GetPermission(_permissionToAdd, _user));
+ _user.Permissions.Add(await Permissions.AddPermission(_user, _permissionToAdd));
_permissionToAdd = "";
await Alerts.FireAsync(new SweetAlertOptions {
@@ -213,7 +213,7 @@
}
private async Task RemovePermission(Permission perm) {
- if (!(await Permissions.HasPermission(Security.AdminPermissions.EditUser, Auth.User.Id))) {
+ if (!await Permissions.HasPermission(Auth.User, Security.AdminPermissions.EditUser)) {
await NoEditPermissions();
return;
}
@@ -227,7 +227,7 @@
});
if (result.IsConfirmed) {
- await Permissions.RemovePermission(perm);
+ await Permissions.RemovePermission(perm.User, perm.PermissionName);
_user.Permissions.Remove(perm);
StateHasChanged();
@@ -241,7 +241,7 @@
}
private async void EditUser() {
- if (!(await Permissions.HasPermission(Security.AdminPermissions.EditUser, Auth.User.Id))) {
+ if (!await Permissions.HasPermission(Auth.User, Security.AdminPermissions.EditUser)) {
await NoEditPermissions();
return;
}
diff --git a/src/HopFrame.Web/Components/AuthorizedView.razor b/src/HopFrame.Web/Components/AuthorizedView.razor
index 5c38d4d..f87a3be 100644
--- a/src/HopFrame.Web/Components/AuthorizedView.razor
+++ b/src/HopFrame.Web/Components/AuthorizedView.razor
@@ -1,4 +1,4 @@
-@using HopFrame.Security.Authorization
+@using HopFrame.Database
@using HopFrame.Security.Claims
@using Microsoft.AspNetCore.Http
diff --git a/src/HopFrame.Web/Pages/Administration/GroupsPage.razor b/src/HopFrame.Web/Pages/Administration/GroupsPage.razor
index 9040d63..4591f60 100644
--- a/src/HopFrame.Web/Pages/Administration/GroupsPage.razor
+++ b/src/HopFrame.Web/Pages/Administration/GroupsPage.razor
@@ -11,8 +11,8 @@
@using BlazorStrap.V5
@using CurrieTechnologies.Razor.SweetAlert2
@using HopFrame.Database.Models
+@using HopFrame.Database.Repositories
@using HopFrame.Security.Claims
-@using HopFrame.Security.Services
@using HopFrame.Web.Pages.Administration.Layout
Groups
@@ -94,7 +94,8 @@
-@inject IPermissionService Permissions
+@inject IGroupRepository Groups
+@inject IPermissionRepository Permissions
@inject ITokenContext Auth
@inject SweetAlertService Alerts
@@ -110,23 +111,23 @@
private GroupAddModal _groupAddModal;
protected override async Task OnInitializedAsync() {
- _groups = await Permissions.GetPermissionGroups();
+ _groups = await Groups.GetPermissionGroups();
- _hasEditPrivileges = await Permissions.HasPermission(Security.AdminPermissions.EditGroup, Auth.User.Id);
- _hasDeletePrivileges = await Permissions.HasPermission(Security.AdminPermissions.DeleteGroup, Auth.User.Id);
+ _hasEditPrivileges = await Permissions.HasPermission(Auth.User, Security.AdminPermissions.EditGroup);
+ _hasDeletePrivileges = await Permissions.HasPermission(Auth.User, Security.AdminPermissions.DeleteGroup);
}
private async Task Reload() {
_groups = new List();
- _groups = await Permissions.GetPermissionGroups();
+ _groups = await Groups.GetPermissionGroups();
OrderBy(_currentOrder, false);
StateHasChanged();
}
private async Task Search() {
- var groups = await Permissions.GetPermissionGroups();
+ var groups = await Groups.GetPermissionGroups();
if (!string.IsNullOrWhiteSpace(_searchText)) {
groups = groups
@@ -166,7 +167,7 @@
});
if (result.IsConfirmed) {
- await Permissions.DeletePermissionGroup(group);
+ await Groups.DeletePermissionGroup(group);
await Reload();
await Alerts.FireAsync(new SweetAlertOptions {
diff --git a/src/HopFrame.Web/Pages/Administration/UsersPage.razor b/src/HopFrame.Web/Pages/Administration/UsersPage.razor
index 0bd7e96..7d36ce4 100644
--- a/src/HopFrame.Web/Pages/Administration/UsersPage.razor
+++ b/src/HopFrame.Web/Pages/Administration/UsersPage.razor
@@ -7,12 +7,12 @@
@using CurrieTechnologies.Razor.SweetAlert2
@using HopFrame.Database.Models
@using HopFrame.Security.Claims
-@using HopFrame.Security.Services
@using HopFrame.Web.Pages.Administration.Layout
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web
@using HopFrame.Web.Components
@using BlazorStrap.V5
+@using HopFrame.Database.Repositories
@using HopFrame.Web.Components.Administration
Users
@@ -95,8 +95,8 @@
-@inject IUserService UserService
-@inject IPermissionService PermissionsService
+@inject IUserRepository UserService
+@inject IPermissionRepository PermissionsService
@inject SweetAlertService Alerts
@inject ITokenContext Auth
@@ -119,12 +119,12 @@
_users = await UserService.GetUsers();
foreach (var user in _users) {
- var groups = await PermissionsService.GetUserPermissionGroups(user);
+ var groups = user.Permissions.Where(p => p.PermissionName.StartsWith("group.")).Select(p => p.Group).ToList();
_userGroups.Add(user.Id, groups.LastOrDefault());
}
- _hasEditPrivileges = await PermissionsService.HasPermission(Security.AdminPermissions.EditUser, Auth.User.Id);
- _hasDeletePrivileges = await PermissionsService.HasPermission(Security.AdminPermissions.DeleteUser, Auth.User.Id);
+ _hasEditPrivileges = await PermissionsService.HasPermission(Auth.User, Security.AdminPermissions.EditUser);
+ _hasDeletePrivileges = await PermissionsService.HasPermission(Auth.User, Security.AdminPermissions.DeleteUser);
}
private async Task Reload() {
@@ -134,7 +134,7 @@
_users = await UserService.GetUsers();
foreach (var user in _users) {
- var groups = await PermissionsService.GetUserPermissionGroups(user);
+ var groups = user.Permissions.Where(p => p.PermissionName.StartsWith("group.")).Select(p => p.Group).ToList();
_userGroups.Add(user.Id, groups.LastOrDefault());
}
diff --git a/src/HopFrame.Web/ServiceCollectionExtensions.cs b/src/HopFrame.Web/ServiceCollectionExtensions.cs
index b6770d7..a9ab84e 100644
--- a/src/HopFrame.Web/ServiceCollectionExtensions.cs
+++ b/src/HopFrame.Web/ServiceCollectionExtensions.cs
@@ -12,14 +12,15 @@ namespace HopFrame.Web;
public static class ServiceCollectionExtensions {
public static IServiceCollection AddHopFrame(this IServiceCollection services) where TDbContext : HopDbContextBase {
services.AddHttpClient();
- services.AddScoped>();
+ services.AddHopFrameRepositories();
+ services.AddScoped();
services.AddTransient();
// Component library's
services.AddSweetAlert2();
services.AddBlazorStrap();
- services.AddHopFrameAuthentication();
+ services.AddHopFrameAuthentication();
return services;
}
diff --git a/src/HopFrame.Web/Services/IAuthService.cs b/src/HopFrame.Web/Services/IAuthService.cs
index f3e588c..218957b 100644
--- a/src/HopFrame.Web/Services/IAuthService.cs
+++ b/src/HopFrame.Web/Services/IAuthService.cs
@@ -1,4 +1,4 @@
-using HopFrame.Database.Models.Entries;
+using HopFrame.Database.Models;
using HopFrame.Security.Models;
namespace HopFrame.Web.Services;
@@ -8,6 +8,6 @@ public interface IAuthService {
Task Login(UserLogin login);
Task Logout();
- Task RefreshLogin();
+ Task RefreshLogin();
Task IsLoggedIn();
}
\ No newline at end of file
diff --git a/src/HopFrame.Web/Services/Implementation/AuthService.cs b/src/HopFrame.Web/Services/Implementation/AuthService.cs
index 682397a..6a96a97 100644
--- a/src/HopFrame.Web/Services/Implementation/AuthService.cs
+++ b/src/HopFrame.Web/Services/Implementation/AuthService.cs
@@ -1,47 +1,38 @@
-using HopFrame.Database;
-using HopFrame.Database.Models.Entries;
+using HopFrame.Database.Models;
+using HopFrame.Database.Repositories;
using HopFrame.Security.Authentication;
using HopFrame.Security.Claims;
using HopFrame.Security.Models;
-using HopFrame.Security.Services;
using Microsoft.AspNetCore.Http;
-using Microsoft.EntityFrameworkCore;
namespace HopFrame.Web.Services.Implementation;
-internal class AuthService(
- IUserService userService,
+internal class AuthService(
+ IUserRepository userService,
IHttpContextAccessor httpAccessor,
- TDbContext context)
- : IAuthService where TDbContext : HopDbContextBase {
+ ITokenRepository tokens,
+ ITokenContext context)
+ : IAuthService {
public async Task Register(UserRegister register) {
- var user = await userService.AddUser(register);
+ var user = await userService.AddUser(new User {
+ Username = register.Username,
+ Email = register.Email,
+ Password = register.Password
+ });
+
if (user is null) return;
+
+ var refreshToken = await tokens.CreateToken(Token.RefreshTokenType, user);
+ var accessToken = await tokens.CreateToken(Token.AccessTokenType, user);
- var refreshToken = new TokenEntry {
- CreatedAt = DateTime.Now,
- Token = Guid.NewGuid().ToString(),
- Type = TokenEntry.RefreshTokenType,
- UserId = user.Id.ToString()
- };
- var accessToken = new TokenEntry {
- CreatedAt = DateTime.Now,
- Token = Guid.NewGuid().ToString(),
- Type = TokenEntry.AccessTokenType,
- UserId = user.Id.ToString()
- };
-
- context.Tokens.AddRange(refreshToken, accessToken);
- await context.SaveChangesAsync();
-
- httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.Token, new CookieOptions {
- MaxAge = HopFrameAuthentication.RefreshTokenTime,
+ httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.Content.ToString(), new CookieOptions {
+ MaxAge = HopFrameAuthentication.RefreshTokenTime,
HttpOnly = true,
Secure = true
});
- httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Token, new CookieOptions {
- MaxAge = HopFrameAuthentication.AccessTokenTime,
+ httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Content.ToString(), new CookieOptions {
+ MaxAge = HopFrameAuthentication.AccessTokenTime,
HttpOnly = false,
Secure = true
});
@@ -53,29 +44,16 @@ internal class AuthService(
if (user == null) return false;
if (await userService.CheckUserPassword(user, login.Password) == false) return false;
- var refreshToken = new TokenEntry {
- CreatedAt = DateTime.Now,
- Token = Guid.NewGuid().ToString(),
- Type = TokenEntry.RefreshTokenType,
- UserId = user.Id.ToString()
- };
- var accessToken = new TokenEntry {
- CreatedAt = DateTime.Now,
- Token = Guid.NewGuid().ToString(),
- Type = TokenEntry.AccessTokenType,
- UserId = user.Id.ToString()
- };
+ var refreshToken = await tokens.CreateToken(Token.RefreshTokenType, user);
+ var accessToken = await tokens.CreateToken(Token.AccessTokenType, user);
- context.Tokens.AddRange(refreshToken, accessToken);
- await context.SaveChangesAsync();
-
- httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.Token, new CookieOptions {
- MaxAge = HopFrameAuthentication.RefreshTokenTime,
+ httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.Content.ToString(), new CookieOptions {
+ MaxAge = HopFrameAuthentication.RefreshTokenTime,
HttpOnly = true,
Secure = true
});
- httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Token, new CookieOptions {
- MaxAge = HopFrameAuthentication.AccessTokenTime,
+ httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Content.ToString(), new CookieOptions {
+ MaxAge = HopFrameAuthentication.AccessTokenTime,
HttpOnly = false,
Secure = true
});
@@ -84,67 +62,27 @@ internal class AuthService(
}
public async Task Logout() {
- var accessToken = httpAccessor.HttpContext?.Request.Cookies[ITokenContext.AccessTokenType];
- var refreshToken = httpAccessor.HttpContext?.Request.Cookies[ITokenContext.RefreshTokenType];
-
- var tokenEntries = await context.Tokens.Where(token =>
- (token.Token == accessToken && token.Type == TokenEntry.AccessTokenType) ||
- (token.Token == refreshToken && token.Type == TokenEntry.RefreshTokenType))
- .ToArrayAsync();
-
- context.Tokens.Remove(tokenEntries[0]);
- context.Tokens.Remove(tokenEntries[1]);
- await context.SaveChangesAsync();
+ await tokens.DeleteUserTokens(context.User);
httpAccessor.HttpContext?.Response.Cookies.Delete(ITokenContext.RefreshTokenType);
httpAccessor.HttpContext?.Response.Cookies.Delete(ITokenContext.AccessTokenType);
}
- public async Task RefreshLogin() {
+ public async Task RefreshLogin() {
var refreshToken = httpAccessor.HttpContext?.Request.Cookies[ITokenContext.RefreshTokenType];
if (string.IsNullOrWhiteSpace(refreshToken)) return null;
-
- var token = await context.Tokens.SingleOrDefaultAsync(token => token.Token == refreshToken && token.Type == TokenEntry.RefreshTokenType);
- if (token is null) return null;
+ var token = await tokens.GetToken(refreshToken);
- var oldAccessTokens = context.Tokens
- .AsEnumerable()
- .Where(old =>
- old.Type == TokenEntry.AccessTokenType &&
- old.UserId == token.UserId &&
- old.CreatedAt + HopFrameAuthentication.AccessTokenTime < DateTime.Now)
- .ToList();
- if (oldAccessTokens.Count != 0)
- context.Tokens.RemoveRange(oldAccessTokens);
+ if (token is null || token.Type != Token.RefreshTokenType) return null;
- var oldRefreshTokens = context.Tokens
- .AsEnumerable()
- .Where(old =>
- old.Type == TokenEntry.RefreshTokenType &&
- old.UserId == token.UserId &&
- old.CreatedAt + HopFrameAuthentication.RefreshTokenTime < DateTime.Now)
- .ToList();
- if (oldRefreshTokens.Count != 0)
- context.Tokens.RemoveRange(oldRefreshTokens);
+ if (token.CreatedAt + HopFrameAuthentication.RefreshTokenTime < DateTime.Now) return null;
- await context.SaveChangesAsync();
+ var accessToken = await tokens.CreateToken(Token.AccessTokenType, token.Owner);
- if (token.CreatedAt + HopFrameAuthentication.RefreshTokenTime < DateTime.Now) return null;
-
- var accessToken = new TokenEntry {
- CreatedAt = DateTime.Now,
- Token = Guid.NewGuid().ToString(),
- Type = TokenEntry.AccessTokenType,
- UserId = token.UserId
- };
-
- await context.Tokens.AddAsync(accessToken);
- await context.SaveChangesAsync();
-
- httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Token, new CookieOptions {
- MaxAge = HopFrameAuthentication.AccessTokenTime,
+ httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Content.ToString(), new CookieOptions {
+ MaxAge = HopFrameAuthentication.AccessTokenTime,
HttpOnly = false,
Secure = true
});
@@ -155,12 +93,13 @@ internal class AuthService(
public async Task IsLoggedIn() {
var accessToken = httpAccessor.HttpContext?.Request.Cookies[ITokenContext.AccessTokenType];
if (string.IsNullOrEmpty(accessToken)) return false;
-
- var tokenEntry = await context.Tokens.SingleOrDefaultAsync(token => token.Token == accessToken);
+
+ var tokenEntry = await tokens.GetToken(accessToken);
if (tokenEntry is null) return false;
- if (tokenEntry.CreatedAt + HopFrameAuthentication.AccessTokenTime < DateTime.Now) return false;
- if (!await context.Users.AnyAsync(user => user.Id == tokenEntry.UserId)) return false;
+ if (tokenEntry.Type != Token.AccessTokenType) return false;
+ if (tokenEntry.CreatedAt + HopFrameAuthentication.AccessTokenTime < DateTime.Now) return false;
+ if (tokenEntry.Owner is null) return false;
return true;
}
diff --git a/test/RestApiTest/Controllers/TestController.cs b/test/RestApiTest/Controllers/TestController.cs
index f13bb74..092784f 100644
--- a/test/RestApiTest/Controllers/TestController.cs
+++ b/test/RestApiTest/Controllers/TestController.cs
@@ -1,17 +1,54 @@
+using HopFrame.Api.Logic;
using HopFrame.Database.Models;
using HopFrame.Security.Authorization;
using HopFrame.Security.Claims;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using RestApiTest.Models;
namespace RestApiTest.Controllers;
[ApiController]
[Route("test")]
-public class TestController(ITokenContext userContext) : ControllerBase {
+public class TestController(ITokenContext userContext, DatabaseContext context) : ControllerBase {
[HttpGet("permissions"), Authorized]
public ActionResult> Permissions() {
return new ActionResult>(userContext.User.Permissions);
}
+
+ [HttpGet("generate")]
+ public async Task GenerateData() {
+ var employee = new Employee() {
+ Name = "Max Mustermann"
+ };
+
+ await context.AddAsync(employee);
+ await context.SaveChangesAsync();
+
+ var address = new Address() {
+ City = "Musterstadt",
+ Country = "Musterland",
+ State = "Musterbundesland",
+ ZipCode = 12345,
+ AddressDetails = "Musterstraße 5",
+ Employee = employee
+ };
+
+ await context.AddAsync(address);
+ await context.SaveChangesAsync();
+
+ return LogicResult.Ok();
+ }
+
+ [HttpGet("employees")]
+ public async Task>> GetEmployees() {
+ return LogicResult>.Ok(await context.Employees.Include(e => e.Address).ToListAsync());
+ }
+
+ [HttpGet("addresses")]
+ public async Task>> GetAddresses() {
+ return LogicResult>.Ok(await context.Addresses.Include(e => e.Employee).ToListAsync());
+ }
}
\ No newline at end of file
diff --git a/test/RestApiTest/DatabaseContext.cs b/test/RestApiTest/DatabaseContext.cs
index 3133353..1167277 100644
--- a/test/RestApiTest/DatabaseContext.cs
+++ b/test/RestApiTest/DatabaseContext.cs
@@ -1,12 +1,25 @@
using HopFrame.Database;
using Microsoft.EntityFrameworkCore;
+using RestApiTest.Models;
namespace RestApiTest;
public class DatabaseContext : HopDbContextBase {
+
+ public DbSet Employees { get; set; }
+ public DbSet Addresses { get; set; }
+
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlite("Data Source=C:\\Users\\Remote\\Documents\\Projekte\\HopFrame\\test\\RestApiTest\\bin\\Debug\\net8.0\\test.db;Mode=ReadWrite;");
}
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder) {
+ base.OnModelCreating(modelBuilder);
+
+ modelBuilder.Entity()
+ .HasOne(e => e.Address)
+ .WithOne(a => a.Employee);
+ }
}
\ No newline at end of file
diff --git a/test/RestApiTest/Models/Address.cs b/test/RestApiTest/Models/Address.cs
new file mode 100644
index 0000000..386114d
--- /dev/null
+++ b/test/RestApiTest/Models/Address.cs
@@ -0,0 +1,18 @@
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Text.Json.Serialization;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+
+namespace RestApiTest.Models;
+
+public class Address {
+ [ForeignKey("Employee")]
+ public int AddressId { get; set; }
+ public string AddressDetails { get; set; }
+ public string City { get; set; }
+ public int ZipCode { get; set; }
+ public string State { get; set; }
+ public string Country { get; set; }
+
+ [JsonIgnore]
+ public virtual Employee Employee { get; set; }
+}
\ No newline at end of file
diff --git a/test/RestApiTest/Models/Employee.cs b/test/RestApiTest/Models/Employee.cs
new file mode 100644
index 0000000..6f70edc
--- /dev/null
+++ b/test/RestApiTest/Models/Employee.cs
@@ -0,0 +1,8 @@
+namespace RestApiTest.Models;
+
+public class Employee {
+ public int EmployeeId { get; set; }
+ public string Name { get; set; }
+
+ public virtual Address Address { get; set; }
+}
\ No newline at end of file
diff --git a/test/RestApiTest/RestApiTest.csproj b/test/RestApiTest/RestApiTest.csproj
index 2549297..5c6c301 100644
--- a/test/RestApiTest/RestApiTest.csproj
+++ b/test/RestApiTest/RestApiTest.csproj
@@ -2,7 +2,7 @@
net8.0
- enable
+ disable
enable