mirror of
https://github.com/hmaxnl/DotBased.git
synced 2025-04-05 22:11:27 +02:00
Merge 6c67276dca
into 21675300bb
This commit is contained in:
commit
419b58a97a
0
Blazor.Wasm/App.razor
Normal file → Executable file
0
Blazor.Wasm/App.razor
Normal file → Executable file
0
Blazor.Wasm/Blazor.Wasm.csproj
Normal file → Executable file
0
Blazor.Wasm/Blazor.Wasm.csproj
Normal file → Executable file
0
Blazor.Wasm/Layout/MainLayout.razor
Normal file → Executable file
0
Blazor.Wasm/Layout/MainLayout.razor
Normal file → Executable file
0
Blazor.Wasm/Layout/MainLayout.razor.css
Normal file → Executable file
0
Blazor.Wasm/Layout/MainLayout.razor.css
Normal file → Executable file
0
Blazor.Wasm/Layout/NavMenu.razor
Normal file → Executable file
0
Blazor.Wasm/Layout/NavMenu.razor
Normal file → Executable file
0
Blazor.Wasm/Layout/NavMenu.razor.css
Normal file → Executable file
0
Blazor.Wasm/Layout/NavMenu.razor.css
Normal file → Executable file
0
Blazor.Wasm/Pages/Counter.razor
Normal file → Executable file
0
Blazor.Wasm/Pages/Counter.razor
Normal file → Executable file
0
Blazor.Wasm/Pages/Home.razor
Normal file → Executable file
0
Blazor.Wasm/Pages/Home.razor
Normal file → Executable file
0
Blazor.Wasm/Pages/Weather.razor
Normal file → Executable file
0
Blazor.Wasm/Pages/Weather.razor
Normal file → Executable file
0
Blazor.Wasm/Program.cs
Normal file → Executable file
0
Blazor.Wasm/Program.cs
Normal file → Executable file
0
Blazor.Wasm/Properties/launchSettings.json
Normal file → Executable file
0
Blazor.Wasm/Properties/launchSettings.json
Normal file → Executable file
0
Blazor.Wasm/_Imports.razor
Normal file → Executable file
0
Blazor.Wasm/_Imports.razor
Normal file → Executable file
0
Blazor.Wasm/wwwroot/css/app.css
Normal file → Executable file
0
Blazor.Wasm/wwwroot/css/app.css
Normal file → Executable file
0
Blazor.Wasm/wwwroot/css/bootstrap/bootstrap.min.css
vendored
Normal file → Executable file
0
Blazor.Wasm/wwwroot/css/bootstrap/bootstrap.min.css
vendored
Normal file → Executable file
0
Blazor.Wasm/wwwroot/css/bootstrap/bootstrap.min.css.map
Normal file → Executable file
0
Blazor.Wasm/wwwroot/css/bootstrap/bootstrap.min.css.map
Normal file → Executable file
0
Blazor.Wasm/wwwroot/favicon.png
Normal file → Executable file
0
Blazor.Wasm/wwwroot/favicon.png
Normal file → Executable file
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
0
Blazor.Wasm/wwwroot/icon-192.png
Normal file → Executable file
0
Blazor.Wasm/wwwroot/icon-192.png
Normal file → Executable file
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
0
Blazor.Wasm/wwwroot/index.html
Normal file → Executable file
0
Blazor.Wasm/wwwroot/index.html
Normal file → Executable file
0
Blazor.Wasm/wwwroot/sample-data/weather.json
Normal file → Executable file
0
Blazor.Wasm/wwwroot/sample-data/weather.json
Normal file → Executable file
0
DotBased.ASP.Auth/AuthDataCache.cs
Normal file → Executable file
0
DotBased.ASP.Auth/AuthDataCache.cs
Normal file → Executable file
0
DotBased.ASP.Auth/AuthenticationService.cs
Normal file → Executable file
0
DotBased.ASP.Auth/AuthenticationService.cs
Normal file → Executable file
0
DotBased.ASP.Auth/BasedAuthConfiguration.cs
Normal file → Executable file
0
DotBased.ASP.Auth/BasedAuthConfiguration.cs
Normal file → Executable file
0
DotBased.ASP.Auth/BasedAuthDefaults.cs
Normal file → Executable file
0
DotBased.ASP.Auth/BasedAuthDefaults.cs
Normal file → Executable file
0
DotBased.ASP.Auth/BasedServerAuthenticationStateProvider.cs
Normal file → Executable file
0
DotBased.ASP.Auth/BasedServerAuthenticationStateProvider.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Domains/Auth/AuthenticationStateModel.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Domains/Auth/AuthenticationStateModel.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Domains/Auth/PermissionModel.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Domains/Auth/PermissionModel.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Domains/Auth/RoleModel.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Domains/Auth/RoleModel.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Domains/Identity/GroupItemModel.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Domains/Identity/GroupItemModel.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Domains/Identity/GroupModel.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Domains/Identity/GroupModel.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Domains/Identity/UserItemModel.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Domains/Identity/UserItemModel.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Domains/Identity/UserModel.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Domains/Identity/UserModel.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Domains/LoginModel.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Domains/LoginModel.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Domains/RegisterModel.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Domains/RegisterModel.cs
Normal file → Executable file
0
DotBased.ASP.Auth/DotBased.ASP.Auth.csproj
Normal file → Executable file
0
DotBased.ASP.Auth/DotBased.ASP.Auth.csproj
Normal file → Executable file
2
DotBased.ASP.Auth/DotBasedAuthDependencyInjection.cs
Normal file → Executable file
2
DotBased.ASP.Auth/DotBasedAuthDependencyInjection.cs
Normal file → Executable file
|
@ -30,7 +30,7 @@ public static class DotBasedAuthDependencyInjection
|
||||||
services.AddAuthentication(options =>
|
services.AddAuthentication(options =>
|
||||||
{
|
{
|
||||||
options.DefaultScheme = BasedAuthDefaults.AuthenticationScheme;
|
options.DefaultScheme = BasedAuthDefaults.AuthenticationScheme;
|
||||||
});/*.AddScheme<BasedAuthenticationHandlerOptions, BasedAuthenticationHandler>(BasedAuthDefaults.AuthenticationScheme, null);*/
|
});
|
||||||
services.AddAuthorization();
|
services.AddAuthorization();
|
||||||
services.AddCascadingAuthenticationState();
|
services.AddCascadingAuthenticationState();
|
||||||
return services;
|
return services;
|
||||||
|
|
0
DotBased.ASP.Auth/IAuthDataRepository.cs
Normal file → Executable file
0
DotBased.ASP.Auth/IAuthDataRepository.cs
Normal file → Executable file
0
DotBased.ASP.Auth/ISessionStateProvider.cs
Normal file → Executable file
0
DotBased.ASP.Auth/ISessionStateProvider.cs
Normal file → Executable file
0
DotBased.ASP.Auth/MemoryAuthDataRepository.cs
Normal file → Executable file
0
DotBased.ASP.Auth/MemoryAuthDataRepository.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Models/Configuration/AuthConfiguration.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Models/Configuration/AuthConfiguration.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Models/Configuration/CacheConfiguration.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Models/Configuration/CacheConfiguration.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Models/Configuration/LockoutConfiguration.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Models/Configuration/LockoutConfiguration.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Models/Configuration/PasswordConfiguration.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Models/Configuration/PasswordConfiguration.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Models/Configuration/ProviderConfiguration.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Models/Configuration/ProviderConfiguration.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Models/Configuration/RepositoryConfiguration.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Models/Configuration/RepositoryConfiguration.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Models/Configuration/UserConfiguration.cs
Normal file → Executable file
0
DotBased.ASP.Auth/Models/Configuration/UserConfiguration.cs
Normal file → Executable file
0
DotBased.ASP.Auth/SecurityManager.cs
Normal file → Executable file
0
DotBased.ASP.Auth/SecurityManager.cs
Normal file → Executable file
0
DotBased.ASP.Auth/SecurityService.cs
Normal file → Executable file
0
DotBased.ASP.Auth/SecurityService.cs
Normal file → Executable file
|
@ -1,11 +0,0 @@
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
namespace DotBased.AspNet.Auth;
|
|
||||||
|
|
||||||
public static class BasedAuthExtensions
|
|
||||||
{
|
|
||||||
public static IServiceCollection AddBasedAuthentication(this IServiceCollection services)
|
|
||||||
{
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
}
|
|
67
DotBased.AspNet.Authority.EFCore/AuthorityContext.cs
Normal file
67
DotBased.AspNet.Authority.EFCore/AuthorityContext.cs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
using DotBased.AspNet.Authority.EFCore.Models;
|
||||||
|
using DotBased.AspNet.Authority.Models.Authority;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace DotBased.AspNet.Authority.EFCore;
|
||||||
|
|
||||||
|
public class AuthorityContext(DbContextOptions<AuthorityContext> options) : DbContext(options)
|
||||||
|
{
|
||||||
|
public DbSet<AuthorityAttribute> Attributes { get; set; }
|
||||||
|
public DbSet<AuthorityGroup> Groups { get; set; }
|
||||||
|
public DbSet<AuthorityRole> Roles { get; set; }
|
||||||
|
public DbSet<AuthorityUser> Users { get; set; }
|
||||||
|
|
||||||
|
public DbSet<RoleGroup> RoleGroup { get; set; }
|
||||||
|
public DbSet<RoleUser> RoleUser { get; set; }
|
||||||
|
public DbSet<UserGroup> UserGroup { get; set; }
|
||||||
|
|
||||||
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
modelBuilder.Entity<AuthorityAttribute>(attributeEntity =>
|
||||||
|
{
|
||||||
|
attributeEntity.ToTable("authority_attributes");
|
||||||
|
attributeEntity.HasKey(a => new { a.BoundId, a.AttributeKey });
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<AuthorityGroup>(groupEntity =>
|
||||||
|
{
|
||||||
|
groupEntity.ToTable("authority_groups");
|
||||||
|
groupEntity.HasKey(x => x.Id);
|
||||||
|
groupEntity.HasMany(g => g.Attributes).WithOne().HasForeignKey(a => a.BoundId).OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<AuthorityRole>(roleEntity =>
|
||||||
|
{
|
||||||
|
roleEntity.ToTable("authority_roles");
|
||||||
|
roleEntity.HasKey(x => x.Id);
|
||||||
|
roleEntity.HasMany(r => r.Attributes).WithOne().HasForeignKey(a => a.BoundId).OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<AuthorityUser>(userEntity =>
|
||||||
|
{
|
||||||
|
userEntity.ToTable("authority_users");
|
||||||
|
userEntity.HasKey(x => x.Id);
|
||||||
|
userEntity.HasMany(u => u.Attributes).WithOne().HasForeignKey(a => a.BoundId).OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<RoleGroup>(rgEntity =>
|
||||||
|
{
|
||||||
|
rgEntity.ToTable("role_group");
|
||||||
|
rgEntity.HasKey(rg => new { rg.RoleId, rg.GroupId });
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<RoleUser>(ruEntity =>
|
||||||
|
{
|
||||||
|
ruEntity.ToTable("role_user");
|
||||||
|
ruEntity.HasKey(ru => new { ru.RoleId, ru.UserId });
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<UserGroup>(ugEntity =>
|
||||||
|
{
|
||||||
|
ugEntity.ToTable("user_group");
|
||||||
|
ugEntity.HasKey(ug => new { ug.UserId, ug.GroupId });
|
||||||
|
});
|
||||||
|
|
||||||
|
base.OnModelCreating(modelBuilder);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\DotBased.AspNet.Authority\DotBased.AspNet.Authority.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.12" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.12">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.12" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
19
DotBased.AspNet.Authority.EFCore/Extensions.cs
Normal file
19
DotBased.AspNet.Authority.EFCore/Extensions.cs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
using DotBased.AspNet.Authority.EFCore.Repositories;
|
||||||
|
using DotBased.AspNet.Authority.Repositories;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace DotBased.AspNet.Authority.EFCore;
|
||||||
|
|
||||||
|
public static class Extensions
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddAuthorityContext(this IServiceCollection services, Action<DbContextOptionsBuilder> options)
|
||||||
|
{
|
||||||
|
services.AddDbContextFactory<AuthorityContext>(options);
|
||||||
|
services.AddScoped<IAttributeRepository, AttributeRepository>();
|
||||||
|
services.AddScoped<IGroupRepository, GroupRepository>();
|
||||||
|
services.AddScoped<IRoleRepository, RoleRepository>();
|
||||||
|
services.AddScoped<IUserRepository, UserRepository>();
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
7
DotBased.AspNet.Authority.EFCore/Models/RoleGroup.cs
Normal file
7
DotBased.AspNet.Authority.EFCore/Models/RoleGroup.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace DotBased.AspNet.Authority.EFCore.Models;
|
||||||
|
|
||||||
|
public class RoleGroup
|
||||||
|
{
|
||||||
|
public Guid RoleId { get; set; }
|
||||||
|
public Guid GroupId { get; set; }
|
||||||
|
}
|
7
DotBased.AspNet.Authority.EFCore/Models/RoleUser.cs
Normal file
7
DotBased.AspNet.Authority.EFCore/Models/RoleUser.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace DotBased.AspNet.Authority.EFCore.Models;
|
||||||
|
|
||||||
|
public class RoleUser
|
||||||
|
{
|
||||||
|
public Guid RoleId { get; set; }
|
||||||
|
public Guid UserId { get; set; }
|
||||||
|
}
|
7
DotBased.AspNet.Authority.EFCore/Models/UserGroup.cs
Normal file
7
DotBased.AspNet.Authority.EFCore/Models/UserGroup.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace DotBased.AspNet.Authority.EFCore.Models;
|
||||||
|
|
||||||
|
public class UserGroup
|
||||||
|
{
|
||||||
|
public Guid UserId { get; set; }
|
||||||
|
public Guid GroupId { get; set; }
|
||||||
|
}
|
23
DotBased.AspNet.Authority.EFCore/README.md
Normal file
23
DotBased.AspNet.Authority.EFCore/README.md
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# EF Core database
|
||||||
|
|
||||||
|
## Add migration project
|
||||||
|
```csharp
|
||||||
|
options.UseSqlite("Data Source=dev-dotbased.db", c => c.MigrationsAssembly("PROJECT-NAME"));
|
||||||
|
```
|
||||||
|
|
||||||
|
## EF Tool
|
||||||
|
|
||||||
|
Add migration
|
||||||
|
```shell
|
||||||
|
dotnet ef migrations add MIGRATION-NAME --project PROJECT-NAME
|
||||||
|
```
|
||||||
|
|
||||||
|
Remove migrations
|
||||||
|
```shell
|
||||||
|
dotnet ef migrations remove --project PROJECT-NAME
|
||||||
|
```
|
||||||
|
|
||||||
|
Update database
|
||||||
|
```shell
|
||||||
|
dotnet ef database update --project PROJECT-NAME
|
||||||
|
```
|
|
@ -0,0 +1,33 @@
|
||||||
|
using DotBased.AspNet.Authority.Models.Authority;
|
||||||
|
using DotBased.AspNet.Authority.Repositories;
|
||||||
|
|
||||||
|
namespace DotBased.AspNet.Authority.EFCore.Repositories;
|
||||||
|
|
||||||
|
public class AttributeRepository : IAttributeRepository
|
||||||
|
{
|
||||||
|
public Task<ListResult<AuthorityAttributeItem>> GetAttributesAsync(int limit = 20, int offset = 0, string search = "",
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Result<AuthorityAttribute>> GetAttributeByIdAsync(string id, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Result<AuthorityAttribute>> CreateAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Result<AuthorityAttribute>> UpdateAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Result> DeleteAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
using DotBased.AspNet.Authority.Models.Authority;
|
||||||
|
using DotBased.AspNet.Authority.Repositories;
|
||||||
|
|
||||||
|
namespace DotBased.AspNet.Authority.EFCore.Repositories;
|
||||||
|
|
||||||
|
public class GroupRepository : IGroupRepository
|
||||||
|
{
|
||||||
|
public Task<ListResult<AuthorityGroupItem>> GetGroupsAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Result<AuthorityAttribute>> GetGroupByIdAsync(string id, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Result<AuthorityAttribute>> CreateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Result<AuthorityAttribute>> UpdateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Result> DeleteGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
using DotBased.AspNet.Authority.Models.Authority;
|
||||||
|
using DotBased.AspNet.Authority.Repositories;
|
||||||
|
|
||||||
|
namespace DotBased.AspNet.Authority.EFCore.Repositories;
|
||||||
|
|
||||||
|
public class RoleRepository : IRoleRepository
|
||||||
|
{
|
||||||
|
public Task<ListResult<AuthorityRoleItem>> GetRolesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Result<AuthorityRole>> GetRoleByIdAsync(string id, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Result<AuthorityRole>> CreateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Result<AuthorityRole>> UpdateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Result> DeleteRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
217
DotBased.AspNet.Authority.EFCore/Repositories/UserRepository.cs
Normal file
217
DotBased.AspNet.Authority.EFCore/Repositories/UserRepository.cs
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
using DotBased.AspNet.Authority.Models.Authority;
|
||||||
|
using DotBased.AspNet.Authority.Repositories;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace DotBased.AspNet.Authority.EFCore.Repositories;
|
||||||
|
|
||||||
|
public class UserRepository(IDbContextFactory<AuthorityContext> contextFactory) : IUserRepository
|
||||||
|
{
|
||||||
|
public async Task<ListResult<AuthorityUserItem>> GetAuthorityUsersAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
var query = context.Users.AsQueryable();
|
||||||
|
if (!string.IsNullOrWhiteSpace(search))
|
||||||
|
{
|
||||||
|
query = query.Where(u =>
|
||||||
|
$"{u.Id} {u.Name} {u.UserName} {u.EmailAddress} {u.PhoneNumber}".Contains(search,
|
||||||
|
StringComparison.CurrentCultureIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalCount = query.Count();
|
||||||
|
var selected = query.Skip(offset).Take(limit).Select(u => new AuthorityUserItem()
|
||||||
|
{
|
||||||
|
Id = u.Id,
|
||||||
|
UserName = u.UserName,
|
||||||
|
EmailAddress = u.EmailAddress,
|
||||||
|
PhoneNumber = u.PhoneNumber
|
||||||
|
});
|
||||||
|
return ListResult<AuthorityUserItem>.Ok(selected, totalCount, limit, offset);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return ListResult<AuthorityUserItem>.Failed("Failed to get users.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result<AuthorityUser>> GetAuthorityUserByIdAsync(string id, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
if (!Guid.TryParse(id, out var guid))
|
||||||
|
{
|
||||||
|
return Result<AuthorityUser>.Failed("Invalid id!");
|
||||||
|
}
|
||||||
|
|
||||||
|
var user = await context.Users.FirstOrDefaultAsync(u => u.Id == guid, cancellationToken: cancellationToken);
|
||||||
|
return Result<AuthorityUser>.HandleResult(user, "User not found.");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return Result<AuthorityUser>.Failed("Failed to get user.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result<AuthorityUser>> CreateUserAsync(AuthorityUser user, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
if (user.Id == Guid.Empty)
|
||||||
|
{
|
||||||
|
return Result<AuthorityUser>.Failed("Id cannot be empty!");
|
||||||
|
}
|
||||||
|
var entity = context.Users.Add(user);
|
||||||
|
var saveResult = await context.SaveChangesAsync(cancellationToken);
|
||||||
|
return saveResult <= 0 ? Result<AuthorityUser>.Failed("Failed to create user!") : Result<AuthorityUser>.Ok(entity.Entity);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return Result<AuthorityUser>.Failed("Failed to create user.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result<AuthorityUser>> UpdateUserAsync(AuthorityUser user, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
var usr = await context.Users.FirstOrDefaultAsync(u => u.Id == user.Id, cancellationToken: cancellationToken);
|
||||||
|
if (usr == null)
|
||||||
|
{
|
||||||
|
return Result<AuthorityUser>.Failed("User not found!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usr.Version != user.Version || usr.SecurityVersion != user.SecurityVersion)
|
||||||
|
{
|
||||||
|
return Result<AuthorityUser>.Failed("Version validation failed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
var entity = context.Users.Update(user);
|
||||||
|
var saveResult = await context.SaveChangesAsync(cancellationToken);
|
||||||
|
return saveResult <= 0 ? Result<AuthorityUser>.Failed("Failed to save updated user!") : Result<AuthorityUser>.Ok(entity.Entity);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return Result<AuthorityUser>.Failed("Failed to update user!", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result> DeleteUserAsync(AuthorityUser user, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
var usr = await context.Users.FirstOrDefaultAsync(u => u.Id == user.Id, cancellationToken: cancellationToken);
|
||||||
|
if (usr == null)
|
||||||
|
{
|
||||||
|
return Result.Failed("User not found!");
|
||||||
|
}
|
||||||
|
context.Users.Remove(usr);
|
||||||
|
var saveResult = await context.SaveChangesAsync(cancellationToken);
|
||||||
|
return saveResult <= 0 ? Result.Failed("Failed to delete user!") : Result.Ok();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return Result.Failed("Failed to delete user!", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result<AuthorityUser>> GetUserByEmailAsync(string email, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
var usr = await context.Users.FirstOrDefaultAsync(u => u.EmailAddress == email, cancellationToken: cancellationToken);
|
||||||
|
return Result<AuthorityUser>.HandleResult(usr, "User not found by given email address.");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return Result<AuthorityUser>.Failed("An error occured while getting the user.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result> SetVersionAsync(AuthorityUser user, long version, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
var usr = await context.Users.FirstOrDefaultAsync(u => u.Id == user.Id, cancellationToken);
|
||||||
|
if (usr == null)
|
||||||
|
{
|
||||||
|
return Result.Failed("Failed to find user with given id!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usr.Version != user.Version)
|
||||||
|
{
|
||||||
|
return Result.Failed("Stored user version doesn't match current user version!");
|
||||||
|
}
|
||||||
|
|
||||||
|
usr.Version = version;
|
||||||
|
context.Users.Update(usr);
|
||||||
|
var saveResult = await context.SaveChangesAsync(cancellationToken);
|
||||||
|
return saveResult <= 0 ? Result.Failed("Failed to update user!") : Result.Ok();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return Result.Failed("An error occured while updating the version.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result<long>> GetVersionAsync(AuthorityUser user, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
var usrVersion = await context.Users.Where(u => u.Id == user.Id).Select(u => u.Version).FirstOrDefaultAsync(cancellationToken);
|
||||||
|
return Result<long>.HandleResult(usrVersion, "Failed to get user version!");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return Result<long>.Failed("An error occured while getting the user version.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result> SetSecurityVersionAsync(AuthorityUser user, long securityVersion, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
var usr = await context.Users.FirstOrDefaultAsync(u => u.Id == user.Id, cancellationToken);
|
||||||
|
if (usr == null)
|
||||||
|
{
|
||||||
|
return Result.Failed("Failed to find user with given id!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usr.SecurityVersion != user.SecurityVersion)
|
||||||
|
{
|
||||||
|
return Result.Failed("Stored user version doesn't match current user version!");
|
||||||
|
}
|
||||||
|
|
||||||
|
usr.SecurityVersion = securityVersion;
|
||||||
|
context.Users.Update(usr);
|
||||||
|
var saveResult = await context.SaveChangesAsync(cancellationToken);
|
||||||
|
return saveResult <= 0 ? Result.Failed("Failed to update user!") : Result.Ok();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return Result.Failed("An error occured while updating the security version.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result<long>> GetSecurityVersionAsync(AuthorityUser user, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
var usrVersion = await context.Users.Where(u => u.Id == user.Id).Select(u => u.SecurityVersion).FirstOrDefaultAsync(cancellationToken);
|
||||||
|
return Result<long>.HandleResult(usrVersion, "Failed to get user security version!");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return Result<long>.Failed("An error occured while getting the user security version.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
DotBased.AspNet.Authority/Attributes/ProtectAttribute.cs
Executable file
10
DotBased.AspNet.Authority/Attributes/ProtectAttribute.cs
Executable file
|
@ -0,0 +1,10 @@
|
||||||
|
namespace DotBased.AspNet.Authority.Attributes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates to protect the property before saving/loading to the repository.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Property)]
|
||||||
|
public class ProtectAttribute : Attribute
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
13
DotBased.AspNet.Authority/AuthorityBuilder.cs
Executable file
13
DotBased.AspNet.Authority/AuthorityBuilder.cs
Executable file
|
@ -0,0 +1,13 @@
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace DotBased.AspNet.Authority;
|
||||||
|
|
||||||
|
public class AuthorityBuilder
|
||||||
|
{
|
||||||
|
public AuthorityBuilder(IServiceCollection services)
|
||||||
|
{
|
||||||
|
Services = services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IServiceCollection Services { get; }
|
||||||
|
}
|
11
DotBased.AspNet.Authority/AuthorityDefaults.cs
Executable file
11
DotBased.AspNet.Authority/AuthorityDefaults.cs
Executable file
|
@ -0,0 +1,11 @@
|
||||||
|
namespace DotBased.AspNet.Authority;
|
||||||
|
|
||||||
|
public static class AuthorityDefaults
|
||||||
|
{
|
||||||
|
public static class Scheme
|
||||||
|
{
|
||||||
|
public const string AuthenticationScheme = "Authority.Scheme.Authentication";
|
||||||
|
public const string ExternalScheme = "Authority.Scheme.External";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
57
DotBased.AspNet.Authority/AuthorityProviderExtensions.cs
Executable file
57
DotBased.AspNet.Authority/AuthorityProviderExtensions.cs
Executable file
|
@ -0,0 +1,57 @@
|
||||||
|
using DotBased.AspNet.Authority.Crypto;
|
||||||
|
using DotBased.AspNet.Authority.Managers;
|
||||||
|
using DotBased.AspNet.Authority.Models.Options;
|
||||||
|
using DotBased.AspNet.Authority.Validators;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
|
||||||
|
namespace DotBased.AspNet.Authority;
|
||||||
|
|
||||||
|
public static class AuthorityProviderExtensions
|
||||||
|
{
|
||||||
|
public static AuthorityBuilder AddAuthority(this IServiceCollection services, Action<AuthorityOptions>? optionsAction = null)
|
||||||
|
{
|
||||||
|
if (optionsAction != null)
|
||||||
|
{
|
||||||
|
services.AddOptions();
|
||||||
|
services.Configure<AuthorityOptions>(optionsAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
services.TryAddScoped<ICryptographer, Cryptographer>();
|
||||||
|
services.TryAddScoped<IPasswordHasher, PasswordHasher>();
|
||||||
|
services.TryAddScoped<IPasswordValidator, PasswordOptionsValidator>();
|
||||||
|
services.TryAddScoped<IPasswordValidator, PasswordEqualsValidator>();
|
||||||
|
services.TryAddScoped<IUserValidator, UserValidator>();
|
||||||
|
/*services.TryAddScoped<IEmailVerifier, EmailVerifier>();
|
||||||
|
services.TryAddScoped<IPhoneNumberVerifier, PhoneNumberVerifier>();
|
||||||
|
services.TryAddScoped<IUserVerifier, UserVerifier>();*/
|
||||||
|
services.TryAddScoped<AuthorityManager>();
|
||||||
|
return new AuthorityBuilder(services);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AuthorityBuilder AddAuthorityRepository<TRepository>(this AuthorityBuilder authorityBuilder) where TRepository : class
|
||||||
|
{
|
||||||
|
return authorityBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AuthorityBuilder MapAuthorityEndpoints(this AuthorityBuilder builder)
|
||||||
|
{
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Type GetBaseGenericArgumentType<TModel>(Type baseType)
|
||||||
|
{
|
||||||
|
var userGenericBaseTypeDefinition = typeof(TModel).BaseType?.GetGenericTypeDefinition();
|
||||||
|
if (userGenericBaseTypeDefinition != null && userGenericBaseTypeDefinition == baseType)
|
||||||
|
{
|
||||||
|
var userBaseGenericArguments = userGenericBaseTypeDefinition.GetGenericArguments();
|
||||||
|
if (userBaseGenericArguments.Length <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Base implementation does not have the required generic argument.", nameof(TModel));
|
||||||
|
}
|
||||||
|
|
||||||
|
return userBaseGenericArguments[0];
|
||||||
|
}
|
||||||
|
throw new ArgumentException($"Given object {typeof(TModel).Name} does not have the base implementation type of: {baseType.Name}", nameof(TModel));
|
||||||
|
}
|
||||||
|
}
|
14
DotBased.AspNet.Authority/Crypto/Cryptographer.cs
Executable file
14
DotBased.AspNet.Authority/Crypto/Cryptographer.cs
Executable file
|
@ -0,0 +1,14 @@
|
||||||
|
namespace DotBased.AspNet.Authority.Crypto;
|
||||||
|
|
||||||
|
public class Cryptographer : ICryptographer
|
||||||
|
{
|
||||||
|
public Task<string?> EncryptAsync(string data)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<string?> DecryptAsync(string data)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
7
DotBased.AspNet.Authority/Crypto/ICryptographer.cs
Executable file
7
DotBased.AspNet.Authority/Crypto/ICryptographer.cs
Executable file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace DotBased.AspNet.Authority.Crypto;
|
||||||
|
|
||||||
|
public interface ICryptographer
|
||||||
|
{
|
||||||
|
public Task<string?> EncryptAsync(string data);
|
||||||
|
public Task<string?> DecryptAsync(string data);
|
||||||
|
}
|
6
DotBased.AspNet.Authority/Crypto/IPasswordHasher.cs
Executable file
6
DotBased.AspNet.Authority/Crypto/IPasswordHasher.cs
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace DotBased.AspNet.Authority.Crypto;
|
||||||
|
|
||||||
|
public interface IPasswordHasher
|
||||||
|
{
|
||||||
|
public Task<string> HashPasswordAsync(string password);
|
||||||
|
}
|
9
DotBased.AspNet.Authority/Crypto/PasswordHasher.cs
Executable file
9
DotBased.AspNet.Authority/Crypto/PasswordHasher.cs
Executable file
|
@ -0,0 +1,9 @@
|
||||||
|
namespace DotBased.AspNet.Authority.Crypto;
|
||||||
|
|
||||||
|
public class PasswordHasher : IPasswordHasher
|
||||||
|
{
|
||||||
|
public async Task<string> HashPasswordAsync(string password)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
15
DotBased.AspNet.Auth/DotBased.AspNet.Auth.csproj → DotBased.AspNet.Authority/DotBased.AspNet.Authority.csproj
Normal file → Executable file
15
DotBased.AspNet.Auth/DotBased.AspNet.Auth.csproj → DotBased.AspNet.Authority/DotBased.AspNet.Authority.csproj
Normal file → Executable file
|
@ -7,8 +7,9 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Models\" />
|
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions">
|
||||||
<Folder Include="Repositories\" />
|
<HintPath>..\..\..\.nuget\packages\microsoft.extensions.dependencyinjection.abstractions\8.0.2\lib\net8.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -16,16 +17,12 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="Microsoft.AspNetCore.Authentication">
|
<Folder Include="Models\Data\" />
|
||||||
<HintPath>..\..\..\..\..\usr\lib64\dotnet\shared\Microsoft.AspNetCore.App\8.0.11\Microsoft.AspNetCore.Authentication.dll</HintPath>
|
<Folder Include="Models\Security\" />
|
||||||
</Reference>
|
|
||||||
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions">
|
|
||||||
<HintPath>..\..\..\.nuget\packages\microsoft.extensions.dependencyinjection.abstractions\8.0.2\lib\net8.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.11" />
|
<PackageReference Include="Microsoft.Extensions.Options" Version="9.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
10
DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs
Executable file
10
DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs
Executable file
|
@ -0,0 +1,10 @@
|
||||||
|
namespace DotBased.AspNet.Authority.Managers;
|
||||||
|
|
||||||
|
public partial class AuthorityManager
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* - Validate User & Group
|
||||||
|
* - Check if user is already in group (if already in group return)
|
||||||
|
* - Add to UsersGroups table
|
||||||
|
*/
|
||||||
|
}
|
97
DotBased.AspNet.Authority/Managers/AuthorityManager.cs
Executable file
97
DotBased.AspNet.Authority/Managers/AuthorityManager.cs
Executable file
|
@ -0,0 +1,97 @@
|
||||||
|
using System.Reflection;
|
||||||
|
using DotBased.AspNet.Authority.Attributes;
|
||||||
|
using DotBased.AspNet.Authority.Crypto;
|
||||||
|
using DotBased.AspNet.Authority.Models.Options;
|
||||||
|
using DotBased.AspNet.Authority.Repositories;
|
||||||
|
using DotBased.AspNet.Authority.Validators;
|
||||||
|
using DotBased.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace DotBased.AspNet.Authority.Managers;
|
||||||
|
|
||||||
|
public partial class AuthorityManager(
|
||||||
|
IOptions<AuthorityOptions> options,
|
||||||
|
IServiceProvider services,
|
||||||
|
ICryptographer cryptographer,
|
||||||
|
IUserRepository userRepository,
|
||||||
|
IRoleRepository roleRepository,
|
||||||
|
IPasswordHasher passwordHasher)
|
||||||
|
{
|
||||||
|
private readonly ILogger _logger = LogService.RegisterLogger<AuthorityManager>();
|
||||||
|
|
||||||
|
public IServiceProvider Services { get; } = services;
|
||||||
|
public AuthorityOptions Options { get; } = options.Value;
|
||||||
|
public ICryptographer Cryptographer { get; } = cryptographer;
|
||||||
|
|
||||||
|
public IUserRepository UserRepository { get; } = userRepository;
|
||||||
|
public IRoleRepository RoleRepository { get; } = roleRepository;
|
||||||
|
|
||||||
|
public IPasswordHasher PasswordHasher { get; } = passwordHasher;
|
||||||
|
|
||||||
|
public IEnumerable<IPasswordValidator> PasswordValidators { get; } = [];
|
||||||
|
public IEnumerable<IUserValidator> UserValidators { get; } = [];
|
||||||
|
|
||||||
|
|
||||||
|
public long GenerateVersion() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Protect or unprotect the properties with the <see cref="ProtectAttribute"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">The data model</param>
|
||||||
|
/// <param name="protection">True for protect false for unprotect.</param>
|
||||||
|
/// <typeparam name="TModel">The class with the properties to protect.</typeparam>
|
||||||
|
public async Task HandlePropertyProtection<TModel>(TModel data, bool protection)
|
||||||
|
{
|
||||||
|
var props = GetProtectedPropertiesValues(data);
|
||||||
|
if (props.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var handledProperties = 0;
|
||||||
|
foreach (var property in props)
|
||||||
|
{
|
||||||
|
if (property.PropertyType != typeof(string))
|
||||||
|
{
|
||||||
|
_logger.Warning("Property({PropName}) with type: {PropType} detected, encrypting only supports strings! Skipping property!", property.Name, property.PropertyType);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
string? cryptString;
|
||||||
|
if (protection)
|
||||||
|
{
|
||||||
|
cryptString = await Cryptographer.EncryptAsync(property.GetValue(data)?.ToString() ?? string.Empty);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cryptString = await Cryptographer.DecryptAsync(property.GetValue(data)?.ToString() ?? string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cryptString == null)
|
||||||
|
{
|
||||||
|
_logger.Warning("{Protection} failed for property {PropName}", protection ? "Encryption" : "Decryption", property.Name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
property.SetValue(data, cryptString);
|
||||||
|
handledProperties++;
|
||||||
|
}
|
||||||
|
_logger.Debug("{HandledPropCount}/{TotalPropCount} protection properties handled!", handledProperties, props.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsPropertyProtected<TModel>(string propertyName)
|
||||||
|
{
|
||||||
|
var protectedProperties = GetProtectedProperties<TModel>();
|
||||||
|
var propertyFound = protectedProperties.Where(propInfo => propInfo.Name == propertyName);
|
||||||
|
return propertyFound.Any();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PropertyInfo> GetProtectedPropertiesValues<TModel>(TModel model)
|
||||||
|
{
|
||||||
|
var protectedProperties = GetProtectedProperties<TModel>();
|
||||||
|
return protectedProperties.Count != 0 ? protectedProperties : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PropertyInfo> GetProtectedProperties<TModel>()
|
||||||
|
=> typeof(TModel).GetProperties().Where(p => Attribute.IsDefined(p, typeof(ProtectAttribute))).ToList();
|
||||||
|
}
|
65
DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs
Executable file
65
DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs
Executable file
|
@ -0,0 +1,65 @@
|
||||||
|
using DotBased.AspNet.Authority.Models.Authority;
|
||||||
|
|
||||||
|
namespace DotBased.AspNet.Authority.Managers;
|
||||||
|
|
||||||
|
public partial class AuthorityManager
|
||||||
|
{
|
||||||
|
public async Task<Result<AuthorityRole>> CreateRoleAsync(AuthorityRole role, CancellationToken? cancellationToken = null)
|
||||||
|
{
|
||||||
|
return Result<AuthorityRole>.Failed("Not implemented!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result> DeleteRoleAsync(AuthorityRole role, CancellationToken? cancellationToken = null)
|
||||||
|
{
|
||||||
|
return Result.Failed("Not implemented!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result<AuthorityRole>> UpdateRoleAsync(AuthorityRole role, CancellationToken? cancellationToken = null)
|
||||||
|
{
|
||||||
|
return Result<AuthorityRole>.Failed("Not implemented!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ListResult<AuthorityRole>> GetRolesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken? cancellationToken = null)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Search by role name & id
|
||||||
|
* Order by name, created date, creator? (paging)
|
||||||
|
*/
|
||||||
|
return ListResult<AuthorityRole>.Failed("Not implemented!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddRoleToUserAsync(AuthorityUser user, AuthorityRole role, CancellationToken? cancellationToken = null)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
- Validate User & Role
|
||||||
|
- Check if role is already in linked to user (if user already has the role, return)
|
||||||
|
- Add to UsersRoles table
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RemoveRoleFromUserAsync(AuthorityRole role, AuthorityUser user, CancellationToken? cancellationToken = null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddRoleToGroupAsync(AuthorityRole role, AuthorityGroup group, CancellationToken? cancellationToken = null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get all roles (including group roles) that the user has.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user">The user to get the roles from</param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
public async Task<ListResult<AuthorityRole>> GetUserRolesAsync(AuthorityUser user, CancellationToken? cancellationToken = null)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* - Validate user
|
||||||
|
* - Get user groups (id)
|
||||||
|
* - Get roles contained from user
|
||||||
|
* - Get roles contained from groups (if any)
|
||||||
|
* - Order by (for paging)
|
||||||
|
*/
|
||||||
|
|
||||||
|
return ListResult<AuthorityRole>.Failed("Not implemented!");
|
||||||
|
}
|
||||||
|
}
|
95
DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs
Executable file
95
DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs
Executable file
|
@ -0,0 +1,95 @@
|
||||||
|
using DotBased.AspNet.Authority.Models;
|
||||||
|
using DotBased.AspNet.Authority.Models.Authority;
|
||||||
|
using DotBased.AspNet.Authority.Models.Validation;
|
||||||
|
|
||||||
|
namespace DotBased.AspNet.Authority.Managers;
|
||||||
|
|
||||||
|
public partial class AuthorityManager
|
||||||
|
{
|
||||||
|
public async Task<ValidationResult> ValidatePasswordAsync(AuthorityUser user, string password)
|
||||||
|
{
|
||||||
|
List<ValidationError> errors = [];
|
||||||
|
foreach (var validator in PasswordValidators)
|
||||||
|
{
|
||||||
|
var validatorResult = await validator.ValidatePasswordAsync(this, user, password);
|
||||||
|
if (!validatorResult.Success)
|
||||||
|
{
|
||||||
|
errors.AddRange(validatorResult.Errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.Count > 0 ? ValidationResult.Failed(errors) : ValidationResult.Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ValidationResult> ValidateUserAsync(AuthorityUser user)
|
||||||
|
{
|
||||||
|
List<ValidationError> errors = [];
|
||||||
|
foreach (var userValidator in UserValidators)
|
||||||
|
{
|
||||||
|
var validationResult = await userValidator.ValidateUserAsync(this, user);
|
||||||
|
if (!validationResult.Success)
|
||||||
|
{
|
||||||
|
errors.AddRange(validationResult.Errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.Count > 0 ? ValidationResult.Failed(errors) : ValidationResult.Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ListResult<AuthorityUserItem>> SearchUsersAsync(string query, int maxResults = 20, int offset = 0, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var result = await UserRepository.GetAuthorityUsersAsync(maxResults, offset, query, cancellationToken);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AuthorityResult<AuthorityUser>> UpdatePasswordAsync(AuthorityUser user, string password, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var passwordValidation = await ValidatePasswordAsync(user, password);
|
||||||
|
if (!passwordValidation.Success)
|
||||||
|
{
|
||||||
|
List<ValidationError> errors = [];
|
||||||
|
errors.AddRange(passwordValidation.Errors);
|
||||||
|
return AuthorityResult<AuthorityUser>.Failed(errors, ResultFailReason.Validation);
|
||||||
|
}
|
||||||
|
|
||||||
|
user.PasswordHash = await PasswordHasher.HashPasswordAsync(password);
|
||||||
|
user.SecurityVersion = GenerateVersion();
|
||||||
|
|
||||||
|
var updateResult = await UserRepository.UpdateUserAsync(user, cancellationToken);
|
||||||
|
return AuthorityResult<AuthorityUser>.FromResult(updateResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AuthorityResult<AuthorityUser>> CreateUserAsync(AuthorityUser userModel, string password, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var userValidation = await ValidateUserAsync(userModel);
|
||||||
|
var passwordValidation = await ValidatePasswordAsync(userModel, password);
|
||||||
|
if (!userValidation.Success || !passwordValidation.Success)
|
||||||
|
{
|
||||||
|
List<ValidationError> errors = [];
|
||||||
|
errors.AddRange(userValidation.Errors);
|
||||||
|
errors.AddRange(passwordValidation.Errors);
|
||||||
|
return AuthorityResult<AuthorityUser>.Failed(errors, ResultFailReason.Validation);
|
||||||
|
}
|
||||||
|
|
||||||
|
userModel.Version = GenerateVersion();
|
||||||
|
userModel.SecurityVersion = GenerateVersion();
|
||||||
|
var hashedPassword = await PasswordHasher.HashPasswordAsync(password);
|
||||||
|
userModel.PasswordHash = hashedPassword;
|
||||||
|
|
||||||
|
var userCreationResult = await UserRepository.CreateUserAsync(userModel, cancellationToken);
|
||||||
|
|
||||||
|
return AuthorityResult<AuthorityUser>.FromResult(userCreationResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result<AuthorityUser>> UpdateUserAsync(AuthorityUser model, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var updateResult = await UserRepository.UpdateUserAsync(model, cancellationToken);
|
||||||
|
return updateResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result> DeleteUserAsync(AuthorityUser model, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var deleteResult = await UserRepository.DeleteUserAsync(model, cancellationToken);
|
||||||
|
return deleteResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
18
DotBased.AspNet.Authority/Models/Authority/AuthorityAttribute.cs
Executable file
18
DotBased.AspNet.Authority/Models/Authority/AuthorityAttribute.cs
Executable file
|
@ -0,0 +1,18 @@
|
||||||
|
namespace DotBased.AspNet.Authority.Models.Authority;
|
||||||
|
|
||||||
|
public class AuthorityAttribute(string attributeKey, Guid bound)
|
||||||
|
{
|
||||||
|
public AuthorityAttribute() : this(string.Empty, Guid.NewGuid())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Guid BoundId { get; set; } = bound;
|
||||||
|
|
||||||
|
public string AttributeKey { get; set; } = attributeKey;
|
||||||
|
|
||||||
|
public string AttributeValue { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string? Type { get; set; }
|
||||||
|
|
||||||
|
public long Version { get; set; }
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace DotBased.AspNet.Authority.Models.Authority;
|
||||||
|
|
||||||
|
public class AuthorityAttributeItem
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
18
DotBased.AspNet.Authority/Models/Authority/AuthorityGroup.cs
Executable file
18
DotBased.AspNet.Authority/Models/Authority/AuthorityGroup.cs
Executable file
|
@ -0,0 +1,18 @@
|
||||||
|
namespace DotBased.AspNet.Authority.Models.Authority;
|
||||||
|
|
||||||
|
public class AuthorityGroup()
|
||||||
|
{
|
||||||
|
public AuthorityGroup(string name) : this()
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
public long Version { get; set; }
|
||||||
|
|
||||||
|
public DateTime CreatedDate { get; set; } = DateTime.Now;
|
||||||
|
public ICollection<AuthorityAttribute> Attributes { get; set; } = [];
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace DotBased.AspNet.Authority.Models.Authority;
|
||||||
|
|
||||||
|
public class AuthorityGroupItem
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
21
DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs
Executable file
21
DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs
Executable file
|
@ -0,0 +1,21 @@
|
||||||
|
namespace DotBased.AspNet.Authority.Models.Authority;
|
||||||
|
|
||||||
|
public class AuthorityRole()
|
||||||
|
{
|
||||||
|
public AuthorityRole(string name) : this()
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
public long Version { get; set; }
|
||||||
|
|
||||||
|
public DateTime CreatedDate { get; set; } = DateTime.Now;
|
||||||
|
|
||||||
|
public IEnumerable<AuthorityAttribute> Attributes { get; set; } = [];
|
||||||
|
|
||||||
|
public override string ToString() => Name ?? string.Empty;
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace DotBased.AspNet.Authority.Models.Authority;
|
||||||
|
|
||||||
|
public class AuthorityRoleItem
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
60
DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs
Executable file
60
DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs
Executable file
|
@ -0,0 +1,60 @@
|
||||||
|
using System.Text;
|
||||||
|
using DotBased.AspNet.Authority.Attributes;
|
||||||
|
|
||||||
|
namespace DotBased.AspNet.Authority.Models.Authority;
|
||||||
|
|
||||||
|
public class AuthorityUser()
|
||||||
|
{
|
||||||
|
public AuthorityUser(string userName) : this()
|
||||||
|
{
|
||||||
|
UserName = userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
public bool Enabled { get; set; }
|
||||||
|
|
||||||
|
public bool Confirmed { get; set; }
|
||||||
|
|
||||||
|
public bool Locked { get; set; }
|
||||||
|
|
||||||
|
public DateTime LockedDate { get; set; }
|
||||||
|
|
||||||
|
public string UserName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string? PasswordHash { get; set; }
|
||||||
|
|
||||||
|
public DateTime CreatedDate { get; set; } = DateTime.Now;
|
||||||
|
|
||||||
|
public bool TwoFactorEnabled { get; set; }
|
||||||
|
|
||||||
|
public long Version { get; set; }
|
||||||
|
|
||||||
|
public long SecurityVersion { get; set; }
|
||||||
|
|
||||||
|
[Protect]
|
||||||
|
public string? EmailAddress { get; set; }
|
||||||
|
|
||||||
|
public bool EmailConfirmed { get; set; }
|
||||||
|
|
||||||
|
[Protect]
|
||||||
|
public string? PhoneNumber { get; set; }
|
||||||
|
|
||||||
|
public bool PhoneNumberConfirmed { get; set; }
|
||||||
|
|
||||||
|
public ICollection<AuthorityAttribute> Attributes { get; set; } = [];
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
var strBuilder = new StringBuilder();
|
||||||
|
strBuilder.Append(!string.IsNullOrWhiteSpace(Name) ? Name : UserName);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(EmailAddress)) return strBuilder.ToString();
|
||||||
|
|
||||||
|
strBuilder.Append(strBuilder.Length == 0 ? EmailAddress : $" ({EmailAddress})");
|
||||||
|
|
||||||
|
return strBuilder.ToString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
namespace DotBased.AspNet.Authority.Models.Authority;
|
||||||
|
|
||||||
|
public class AuthorityUserItem
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public string UserName { get; set; } = string.Empty;
|
||||||
|
public string? EmailAddress { get; set; } = string.Empty;
|
||||||
|
public string? PhoneNumber { get; set; } = string.Empty;
|
||||||
|
}
|
41
DotBased.AspNet.Authority/Models/AuthorityResult.cs
Executable file
41
DotBased.AspNet.Authority/Models/AuthorityResult.cs
Executable file
|
@ -0,0 +1,41 @@
|
||||||
|
using DotBased.AspNet.Authority.Models.Validation;
|
||||||
|
|
||||||
|
namespace DotBased.AspNet.Authority.Models;
|
||||||
|
|
||||||
|
public class AuthorityResult<TResultValue> : Result<TResultValue>
|
||||||
|
{
|
||||||
|
public static AuthorityResult<TResultValue> FromResult(Result<TResultValue> result) => new AuthorityResult<TResultValue>(result);
|
||||||
|
|
||||||
|
public AuthorityResult(Result<TResultValue> result) : base(result)
|
||||||
|
{
|
||||||
|
Reason = ResultFailReason.Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthorityResult(bool success, string errorMessage = "", TResultValue? value = default, ResultFailReason reason = ResultFailReason.None, List<ValidationError>? errors = null) : base(success, errorMessage, value, null)
|
||||||
|
{
|
||||||
|
Success = success;
|
||||||
|
Message = errorMessage;
|
||||||
|
Value = value;
|
||||||
|
Reason = reason;
|
||||||
|
ValidationErrors = errors;
|
||||||
|
}
|
||||||
|
public ResultFailReason Reason { get; }
|
||||||
|
public List<ValidationError>? ValidationErrors { get; }
|
||||||
|
|
||||||
|
|
||||||
|
public static AuthorityResult<TResultValue> Ok(TResultValue? value) => new AuthorityResult<TResultValue>(true, value:value);
|
||||||
|
|
||||||
|
public static AuthorityResult<TResultValue> Error(string errorMessage, ResultFailReason reason = ResultFailReason.Error) =>
|
||||||
|
new AuthorityResult<TResultValue>(false, errorMessage, reason:reason);
|
||||||
|
|
||||||
|
public static AuthorityResult<TResultValue> Failed(List<ValidationError> errors, ResultFailReason reason = ResultFailReason.None)
|
||||||
|
=> new AuthorityResult<TResultValue>(false, errors:errors, reason:reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ResultFailReason
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Unknown,
|
||||||
|
Validation,
|
||||||
|
Error
|
||||||
|
}
|
11
DotBased.AspNet.Authority/Models/Options/AuthorityOptions.cs
Executable file
11
DotBased.AspNet.Authority/Models/Options/AuthorityOptions.cs
Executable file
|
@ -0,0 +1,11 @@
|
||||||
|
namespace DotBased.AspNet.Authority.Models.Options;
|
||||||
|
|
||||||
|
public class AuthorityOptions
|
||||||
|
{
|
||||||
|
public LockdownOptions Lockdown { get; set; } = new();
|
||||||
|
public LockoutOptions Lockout { get; set; } = new();
|
||||||
|
public PasswordOptions Password { get; set; } = new();
|
||||||
|
public ProviderOptions Provider { get; set; } = new();
|
||||||
|
public RepositoryOptions Repository { get; set; } = new();
|
||||||
|
public UserOptions User { get; set; } = new();
|
||||||
|
}
|
7
DotBased.AspNet.Authority/Models/Options/ListOption.cs
Executable file
7
DotBased.AspNet.Authority/Models/Options/ListOption.cs
Executable file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace DotBased.AspNet.Authority.Models.Options;
|
||||||
|
|
||||||
|
public enum ListOption
|
||||||
|
{
|
||||||
|
Blacklist,
|
||||||
|
Whitelist
|
||||||
|
}
|
6
DotBased.AspNet.Authority/Models/Options/LockdownOptions.cs
Executable file
6
DotBased.AspNet.Authority/Models/Options/LockdownOptions.cs
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace DotBased.AspNet.Authority.Models.Options;
|
||||||
|
|
||||||
|
public class LockdownOptions
|
||||||
|
{
|
||||||
|
public bool EnableLockout { get; set; }
|
||||||
|
}
|
8
DotBased.AspNet.Authority/Models/Options/LockoutOptions.cs
Executable file
8
DotBased.AspNet.Authority/Models/Options/LockoutOptions.cs
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace DotBased.AspNet.Authority.Models.Options;
|
||||||
|
|
||||||
|
public class LockoutOptions
|
||||||
|
{
|
||||||
|
public bool EnableLockout { get; set; } = true;
|
||||||
|
public int FailedAttempts { get; set; } = 3;
|
||||||
|
public TimeSpan LockoutTimeout { get; set; } = TimeSpan.FromMinutes(30);
|
||||||
|
}
|
14
DotBased.AspNet.Authority/Models/Options/PasswordOptions.cs
Executable file
14
DotBased.AspNet.Authority/Models/Options/PasswordOptions.cs
Executable file
|
@ -0,0 +1,14 @@
|
||||||
|
namespace DotBased.AspNet.Authority.Models.Options;
|
||||||
|
|
||||||
|
public class PasswordOptions
|
||||||
|
{
|
||||||
|
public int RequiredLength { get; set; } = 10;
|
||||||
|
public int MinimalUniqueChars { get; set; } = 1;
|
||||||
|
public bool RequireLowercase { get; set; }
|
||||||
|
public bool RequireUppercase { get; set; }
|
||||||
|
public bool RequireDigit { get; set; }
|
||||||
|
public bool RequireNonAlphanumeric { get; set; }
|
||||||
|
|
||||||
|
public List<string> PasswordBlackList { get; set; } = ["password", "1234"];
|
||||||
|
public StringComparer PasswordBlackListComparer { get; set; } = StringComparer.OrdinalIgnoreCase;
|
||||||
|
}
|
6
DotBased.AspNet.Authority/Models/Options/ProviderOptions.cs
Executable file
6
DotBased.AspNet.Authority/Models/Options/ProviderOptions.cs
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace DotBased.AspNet.Authority.Models.Options;
|
||||||
|
|
||||||
|
public class ProviderOptions
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
10
DotBased.AspNet.Authority/Models/Options/RepositoryOptions.cs
Executable file
10
DotBased.AspNet.Authority/Models/Options/RepositoryOptions.cs
Executable file
|
@ -0,0 +1,10 @@
|
||||||
|
namespace DotBased.AspNet.Authority.Models.Options;
|
||||||
|
|
||||||
|
public class RepositoryOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Use data encryption when a property has the <see cref="DotBased.AspNet.Authority.Attributes.ProtectAttribute"/> defined.
|
||||||
|
/// <value>Default: true</value>
|
||||||
|
/// </summary>
|
||||||
|
public bool UseDataProtection { get; set; } = true;
|
||||||
|
}
|
8
DotBased.AspNet.Authority/Models/Options/SignInOptions.cs
Executable file
8
DotBased.AspNet.Authority/Models/Options/SignInOptions.cs
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace DotBased.AspNet.Authority.Models.Options;
|
||||||
|
|
||||||
|
public class SignInOptions
|
||||||
|
{
|
||||||
|
public bool RequireVerifiedEmail { get; set; }
|
||||||
|
public bool RequireVerifiedPhoneNumber { get; set; }
|
||||||
|
public bool RequireConfirmedAccount { get; set; }
|
||||||
|
}
|
12
DotBased.AspNet.Authority/Models/Options/UserOptions.cs
Executable file
12
DotBased.AspNet.Authority/Models/Options/UserOptions.cs
Executable file
|
@ -0,0 +1,12 @@
|
||||||
|
namespace DotBased.AspNet.Authority.Models.Options;
|
||||||
|
|
||||||
|
public class UserOptions
|
||||||
|
{
|
||||||
|
public bool EnableRegister { get; set; }
|
||||||
|
public bool RequireUniqueEmail { get; set; }
|
||||||
|
public string UserNameCharacters { get; set; } = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@";
|
||||||
|
public ListOption UserNameCharacterListType { get; set; } = ListOption.Whitelist;
|
||||||
|
|
||||||
|
public List<string> UserNameBlackList { get; set; } = ["admin", "administrator", "dev", "developer"];
|
||||||
|
public StringComparer UserNameBlackListComparer { get; set; } = StringComparer.OrdinalIgnoreCase;
|
||||||
|
}
|
24
DotBased.AspNet.Authority/Models/Validation/ValidationError.cs
Executable file
24
DotBased.AspNet.Authority/Models/Validation/ValidationError.cs
Executable file
|
@ -0,0 +1,24 @@
|
||||||
|
namespace DotBased.AspNet.Authority.Models.Validation;
|
||||||
|
|
||||||
|
public class ValidationError
|
||||||
|
{
|
||||||
|
public ValidationError(string validator, string errorCode, string description)
|
||||||
|
{
|
||||||
|
Validator = validator;
|
||||||
|
ErrorCode = errorCode;
|
||||||
|
Description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The validator name that generated this error.
|
||||||
|
/// </summary>
|
||||||
|
public string Validator { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// The error code
|
||||||
|
/// </summary>
|
||||||
|
public string ErrorCode { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// Error description
|
||||||
|
/// </summary>
|
||||||
|
public string Description { get; }
|
||||||
|
}
|
21
DotBased.AspNet.Authority/Models/Validation/ValidationResult.cs
Executable file
21
DotBased.AspNet.Authority/Models/Validation/ValidationResult.cs
Executable file
|
@ -0,0 +1,21 @@
|
||||||
|
namespace DotBased.AspNet.Authority.Models.Validation;
|
||||||
|
|
||||||
|
public class ValidationResult
|
||||||
|
{
|
||||||
|
public ValidationResult(bool success, IEnumerable<ValidationError>? errors = null)
|
||||||
|
{
|
||||||
|
if (errors != null)
|
||||||
|
{
|
||||||
|
Errors = errors.ToList();
|
||||||
|
}
|
||||||
|
Success = success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Success { get; }
|
||||||
|
public IReadOnlyList<ValidationError> Errors { get; } = [];
|
||||||
|
|
||||||
|
public static ValidationResult Failed(IEnumerable<ValidationError> errors) => new(false, errors);
|
||||||
|
public static ValidationResult Ok() => new(true);
|
||||||
|
|
||||||
|
public override string ToString() => Success ? "Success" : $"Failed ({Errors.Count} errors)";
|
||||||
|
}
|
12
DotBased.AspNet.Authority/Repositories/IAttributeRepository.cs
Executable file
12
DotBased.AspNet.Authority/Repositories/IAttributeRepository.cs
Executable file
|
@ -0,0 +1,12 @@
|
||||||
|
using DotBased.AspNet.Authority.Models.Authority;
|
||||||
|
|
||||||
|
namespace DotBased.AspNet.Authority.Repositories;
|
||||||
|
|
||||||
|
public interface IAttributeRepository
|
||||||
|
{
|
||||||
|
public Task<ListResult<AuthorityAttributeItem>> GetAttributesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default);
|
||||||
|
public Task<Result<AuthorityAttribute>> GetAttributeByIdAsync(string id, CancellationToken cancellationToken = default);
|
||||||
|
public Task<Result<AuthorityAttribute>> CreateAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default);
|
||||||
|
public Task<Result<AuthorityAttribute>> UpdateAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default);
|
||||||
|
public Task<Result> DeleteAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default);
|
||||||
|
}
|
12
DotBased.AspNet.Authority/Repositories/IGroupRepository.cs
Executable file
12
DotBased.AspNet.Authority/Repositories/IGroupRepository.cs
Executable file
|
@ -0,0 +1,12 @@
|
||||||
|
using DotBased.AspNet.Authority.Models.Authority;
|
||||||
|
|
||||||
|
namespace DotBased.AspNet.Authority.Repositories;
|
||||||
|
|
||||||
|
public interface IGroupRepository
|
||||||
|
{
|
||||||
|
public Task<ListResult<AuthorityGroupItem>> GetGroupsAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default);
|
||||||
|
public Task<Result<AuthorityAttribute>> GetGroupByIdAsync(string id, CancellationToken cancellationToken = default);
|
||||||
|
public Task<Result<AuthorityAttribute>> CreateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default);
|
||||||
|
public Task<Result<AuthorityAttribute>> UpdateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default);
|
||||||
|
public Task<Result> DeleteGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default);
|
||||||
|
}
|
12
DotBased.AspNet.Authority/Repositories/IRoleRepository.cs
Executable file
12
DotBased.AspNet.Authority/Repositories/IRoleRepository.cs
Executable file
|
@ -0,0 +1,12 @@
|
||||||
|
using DotBased.AspNet.Authority.Models.Authority;
|
||||||
|
|
||||||
|
namespace DotBased.AspNet.Authority.Repositories;
|
||||||
|
|
||||||
|
public interface IRoleRepository
|
||||||
|
{
|
||||||
|
public Task<ListResult<AuthorityRoleItem>> GetRolesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default);
|
||||||
|
public Task<Result<AuthorityRole>> GetRoleByIdAsync(string id, CancellationToken cancellationToken = default);
|
||||||
|
public Task<Result<AuthorityRole>> CreateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default);
|
||||||
|
public Task<Result<AuthorityRole>> UpdateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default);
|
||||||
|
public Task<Result> DeleteRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default);
|
||||||
|
}
|
17
DotBased.AspNet.Authority/Repositories/IUserRepository.cs
Executable file
17
DotBased.AspNet.Authority/Repositories/IUserRepository.cs
Executable file
|
@ -0,0 +1,17 @@
|
||||||
|
using DotBased.AspNet.Authority.Models.Authority;
|
||||||
|
|
||||||
|
namespace DotBased.AspNet.Authority.Repositories;
|
||||||
|
|
||||||
|
public interface IUserRepository
|
||||||
|
{
|
||||||
|
public Task<ListResult<AuthorityUserItem>> GetAuthorityUsersAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default);
|
||||||
|
public Task<Result<AuthorityUser>> GetAuthorityUserByIdAsync(string id, CancellationToken cancellationToken = default);
|
||||||
|
public Task<Result<AuthorityUser>> CreateUserAsync(AuthorityUser user, CancellationToken cancellationToken = default);
|
||||||
|
public Task<Result<AuthorityUser>> UpdateUserAsync(AuthorityUser user, CancellationToken cancellationToken = default);
|
||||||
|
public Task<Result> DeleteUserAsync(AuthorityUser user, CancellationToken cancellationToken = default);
|
||||||
|
public Task<Result<AuthorityUser>> GetUserByEmailAsync(string email, CancellationToken cancellationToken = default);
|
||||||
|
public Task<Result> SetVersionAsync(AuthorityUser user, long version, CancellationToken cancellationToken = default);
|
||||||
|
public Task<Result<long>> GetVersionAsync(AuthorityUser user, CancellationToken cancellationToken = default);
|
||||||
|
public Task<Result> SetSecurityVersionAsync(AuthorityUser user, long securityVersion, CancellationToken cancellationToken = default);
|
||||||
|
public Task<Result<long>> GetSecurityVersionAsync(AuthorityUser user, CancellationToken cancellationToken = default);
|
||||||
|
}
|
10
DotBased.AspNet.Authority/Validators/IPasswordValidator.cs
Executable file
10
DotBased.AspNet.Authority/Validators/IPasswordValidator.cs
Executable file
|
@ -0,0 +1,10 @@
|
||||||
|
using DotBased.AspNet.Authority.Managers;
|
||||||
|
using DotBased.AspNet.Authority.Models.Authority;
|
||||||
|
using DotBased.AspNet.Authority.Models.Validation;
|
||||||
|
|
||||||
|
namespace DotBased.AspNet.Authority.Validators;
|
||||||
|
|
||||||
|
public interface IPasswordValidator
|
||||||
|
{
|
||||||
|
public Task<ValidationResult> ValidatePasswordAsync(AuthorityManager manager, AuthorityUser user, string password);
|
||||||
|
}
|
10
DotBased.AspNet.Authority/Validators/IUserValidator.cs
Executable file
10
DotBased.AspNet.Authority/Validators/IUserValidator.cs
Executable file
|
@ -0,0 +1,10 @@
|
||||||
|
using DotBased.AspNet.Authority.Managers;
|
||||||
|
using DotBased.AspNet.Authority.Models.Authority;
|
||||||
|
using DotBased.AspNet.Authority.Models.Validation;
|
||||||
|
|
||||||
|
namespace DotBased.AspNet.Authority.Validators;
|
||||||
|
|
||||||
|
public interface IUserValidator
|
||||||
|
{
|
||||||
|
public Task<ValidationResult> ValidateUserAsync(AuthorityManager manager, AuthorityUser user);
|
||||||
|
}
|
22
DotBased.AspNet.Authority/Validators/PasswordEqualsValidator.cs
Executable file
22
DotBased.AspNet.Authority/Validators/PasswordEqualsValidator.cs
Executable file
|
@ -0,0 +1,22 @@
|
||||||
|
using DotBased.AspNet.Authority.Managers;
|
||||||
|
using DotBased.AspNet.Authority.Models.Authority;
|
||||||
|
using DotBased.AspNet.Authority.Models.Validation;
|
||||||
|
|
||||||
|
namespace DotBased.AspNet.Authority.Validators;
|
||||||
|
|
||||||
|
public class PasswordEqualsValidator : IPasswordValidator
|
||||||
|
{
|
||||||
|
private const string ValidatorId = "Authority.Validator.Password.Equals";
|
||||||
|
private const string ValidationBase = "Authority.Validation.Password";
|
||||||
|
public async Task<ValidationResult> ValidatePasswordAsync(AuthorityManager userManager, AuthorityUser user, string password)
|
||||||
|
{
|
||||||
|
List<ValidationError> errors = [];
|
||||||
|
var hashedPassword = await userManager.PasswordHasher.HashPasswordAsync(password);
|
||||||
|
if (user.PasswordHash != null && user.PasswordHash.Equals(hashedPassword, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.InUse", "User uses this password already!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Count > 0 ? ValidationResult.Failed(errors) : ValidationResult.Ok();
|
||||||
|
}
|
||||||
|
}
|
66
DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs
Executable file
66
DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs
Executable file
|
@ -0,0 +1,66 @@
|
||||||
|
using DotBased.AspNet.Authority.Managers;
|
||||||
|
using DotBased.AspNet.Authority.Models.Authority;
|
||||||
|
using DotBased.AspNet.Authority.Models.Validation;
|
||||||
|
using DotBased.Extensions;
|
||||||
|
|
||||||
|
namespace DotBased.AspNet.Authority.Validators;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates the password against the options that is configured.
|
||||||
|
/// </summary>
|
||||||
|
public class PasswordOptionsValidator : IPasswordValidator
|
||||||
|
{
|
||||||
|
private const string ValidatorId = "Authority.Validator.Password.Options";
|
||||||
|
private const string ValidationBase = "Authority.Validation.Password";
|
||||||
|
|
||||||
|
public async Task<ValidationResult> ValidatePasswordAsync(AuthorityManager userManager, AuthorityUser user, string password)
|
||||||
|
{
|
||||||
|
if (userManager == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(userManager), "User manager is not provided!");
|
||||||
|
}
|
||||||
|
var passwordOptions = userManager.Options.Password;
|
||||||
|
var errors = new List<ValidationError>();
|
||||||
|
|
||||||
|
if (password.IsNullOrEmpty() || password.Length < passwordOptions.RequiredLength)
|
||||||
|
{
|
||||||
|
errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.Required.Length", $"Password needs to have a minimum length of {passwordOptions.RequiredLength}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (passwordOptions.RequireDigit && !ContainsDigit(password))
|
||||||
|
{
|
||||||
|
errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.Required.Digit", "Password must contain a digit!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (passwordOptions.RequireNonAlphanumeric && ContainsNonAlphanumeric(password))
|
||||||
|
{
|
||||||
|
errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.Required.NonAlphanumeric", "Password must contain a non alphanumeric character."));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (passwordOptions.RequireLowercase && password.Any(char.IsLower))
|
||||||
|
{
|
||||||
|
errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.Required.Lowercase", "Password must contains at least one lowercase character."));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (passwordOptions.RequireUppercase && password.Any(char.IsUpper))
|
||||||
|
{
|
||||||
|
errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.Required.Uppercase", "Password must contains at least one uppercase character."));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (passwordOptions.PasswordBlackList.Count != 0 && passwordOptions.PasswordBlackList.Contains(password, passwordOptions.PasswordBlackListComparer))
|
||||||
|
{
|
||||||
|
errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.Blacklisted", "Given password is not allowed (blacklisted)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (passwordOptions.MinimalUniqueChars > 0 && password.Distinct().Count() < passwordOptions.MinimalUniqueChars)
|
||||||
|
{
|
||||||
|
errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.UniqueChars", $"Password must contain at least {passwordOptions.MinimalUniqueChars} unique chars."));
|
||||||
|
}
|
||||||
|
|
||||||
|
return await Task.FromResult(errors.Count > 0 ? ValidationResult.Failed(errors) : ValidationResult.Ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ContainsDigit(string strVal) => strVal.Any(char.IsDigit);
|
||||||
|
|
||||||
|
private bool ContainsNonAlphanumeric(string strVal) => !strVal.Any(char.IsLetterOrDigit);
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user