diff --git a/DotBased.ASP.Auth/BasedAuthBuilder.cs b/DotBased.ASP.Auth/BasedAuthBuilder.cs index e9a824b..c5e276c 100644 --- a/DotBased.ASP.Auth/BasedAuthBuilder.cs +++ b/DotBased.ASP.Auth/BasedAuthBuilder.cs @@ -14,6 +14,13 @@ public class BasedAuthBuilder configurationAction?.Invoke(Configuration); services.AddSingleton(Configuration); + if (Configuration.AuthDataProviderType == null) + throw new ArgumentNullException(nameof(Configuration.AuthDataProviderType), $"No '{nameof(IAuthDataProvider)}' configured!"); + services.AddScoped(typeof(IAuthDataProvider), Configuration.AuthDataProviderType); + if (Configuration.SessionStateProviderType == null) + throw new ArgumentNullException(nameof(Configuration.SessionStateProviderType), $"No '{nameof(ISessionStateProvider)}' configured!"); + services.AddScoped(typeof(ISessionStateProvider), Configuration.SessionStateProviderType); + services.AddScoped(); services.AddScoped(); @@ -26,6 +33,4 @@ public class BasedAuthBuilder } public BasedAuthConfiguration Configuration { get; } private readonly IServiceCollection _services; - - public void AddSessionStateProvider() where TSessionStateProviderType : ISessionStateProvider => _services.AddScoped(typeof(ISessionStateProvider), typeof(TSessionStateProviderType)); } \ No newline at end of file diff --git a/DotBased.ASP.Auth/BasedAuthConfiguration.cs b/DotBased.ASP.Auth/BasedAuthConfiguration.cs index ad12d36..4800b61 100644 --- a/DotBased.ASP.Auth/BasedAuthConfiguration.cs +++ b/DotBased.ASP.Auth/BasedAuthConfiguration.cs @@ -1,5 +1,3 @@ -using DotBased.ASP.Auth.Services; - namespace DotBased.ASP.Auth; public class BasedAuthConfiguration @@ -9,12 +7,36 @@ public class BasedAuthConfiguration /// public bool AllowRegistration { get; set; } //TODO: Callback when a user registers, so the application can handle sending emails or generate a code to complete the registration. + //TODO: Callback for validation email, phone number + /// + /// Allow no passwords on users, not recommended! + /// + public bool AllowEmptyPassword { get; set; } = false; + /// + /// This path is used for redirecting to the login page. + /// public string LoginPath { get; set; } = string.Empty; + /// + /// The path that will be used if the logout is requested. + /// public string LogoutPath { get; set; } = string.Empty; /// /// The max age before a AuthenticationState will expire (default: 7 days). /// public TimeSpan AuthenticationStateMaxAgeBeforeExpire { get; set; } = TimeSpan.FromDays(7); - //TODO: Data seeding - public Action? SeedData { get; set; } + /// + /// Can be used to seed a default user and/or group for first time use. + /// + public Action? SeedData { get; set; } + + public Type? AuthDataProviderType { get; private set; } + + public void SetDataProviderType() where TDataProviderType : IAuthDataProvider => + AuthDataProviderType = typeof(TDataProviderType); + + public Type? SessionStateProviderType { get; private set; } + + public void SetSessionStateProviderType() + where TSessionStateProviderType : ISessionStateProvider => + SessionStateProviderType = typeof(TSessionStateProviderType); } \ No newline at end of file diff --git a/DotBased.ASP.Auth/DotBasedAuthDependencyInjection.cs b/DotBased.ASP.Auth/DotBasedAuthDependencyInjection.cs index 38e315d..8762aaa 100644 --- a/DotBased.ASP.Auth/DotBasedAuthDependencyInjection.cs +++ b/DotBased.ASP.Auth/DotBasedAuthDependencyInjection.cs @@ -1,3 +1,7 @@ +using DotBased.ASP.Auth.Scheme; +using DotBased.ASP.Auth.Services; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Components.Authorization; using Microsoft.Extensions.DependencyInjection; namespace DotBased.ASP.Auth; @@ -10,9 +14,47 @@ public static class DotBasedAuthDependencyInjection /// Use the app.UseAuthentication() and app.UseAuthorization()! /// Service collection /// DotBased auth configuration - public static BasedAuthBuilder UseBasedServerAuth(this IServiceCollection services, Action? configurationAction = null) + public static IServiceCollection AddBasedServerAuth(this IServiceCollection services, Action? configurationAction = null) { - var authBuilder = new BasedAuthBuilder(services, configurationAction); - return authBuilder; + /*var authBuilder = new BasedAuthBuilder(services, configurationAction); + return authBuilder;*/ + var Configuration = new BasedAuthConfiguration(); + configurationAction?.Invoke(Configuration); + + services.AddSingleton(Configuration); + if (Configuration.AuthDataProviderType == null) + throw new ArgumentNullException(nameof(Configuration.AuthDataProviderType), $"No '{nameof(IAuthDataProvider)}' configured!"); + services.AddScoped(typeof(IAuthDataProvider), Configuration.AuthDataProviderType); + if (Configuration.SessionStateProviderType == null) + throw new ArgumentNullException(nameof(Configuration.SessionStateProviderType), $"No '{nameof(ISessionStateProvider)}' configured!"); + services.AddScoped(typeof(ISessionStateProvider), Configuration.SessionStateProviderType); + + services.AddScoped(); + + services.AddScoped(); + services.AddAuthentication(options => + { + options.DefaultScheme = BasedAuthenticationHandler.AuthenticationScheme; + }).AddScheme(BasedAuthenticationHandler.AuthenticationScheme, null); + services.AddAuthorization(); + services.AddCascadingAuthenticationState(); + return services; + } + + public static WebApplication UseBasedServerAuth(this WebApplication app) + { + app.UseAuthentication(); + app.UseAuthorization(); + + // Data + var authConfig = app.Services.GetService(); + if (authConfig == null) + throw new NullReferenceException($"{nameof(BasedAuthConfiguration)} is null!"); + if (authConfig.AuthDataProviderType == null) + throw new NullReferenceException($"{nameof(authConfig.AuthDataProviderType)} is null, cannot instantiate an instance of {nameof(IAuthDataProvider)}"); + var dataProvider = (IAuthDataProvider?)Activator.CreateInstance(authConfig.AuthDataProviderType); + if (dataProvider != null) authConfig.SeedData?.Invoke(dataProvider); + + return app; } } \ No newline at end of file diff --git a/DotBased.ASP.Auth/IAuthDataProvider.cs b/DotBased.ASP.Auth/IAuthDataProvider.cs index 468b820..910d3c8 100644 --- a/DotBased.ASP.Auth/IAuthDataProvider.cs +++ b/DotBased.ASP.Auth/IAuthDataProvider.cs @@ -1,6 +1,5 @@ using DotBased.ASP.Auth.Domains.Auth; using DotBased.ASP.Auth.Domains.Identity; -using DotBased.Objects; namespace DotBased.ASP.Auth; diff --git a/DotBased.ASP.Auth/MemoryAuthDataProvider.cs b/DotBased.ASP.Auth/MemoryAuthDataProvider.cs new file mode 100644 index 0000000..1843e4c --- /dev/null +++ b/DotBased.ASP.Auth/MemoryAuthDataProvider.cs @@ -0,0 +1,83 @@ +using DotBased.ASP.Auth.Domains.Auth; +using DotBased.ASP.Auth.Domains.Identity; + +namespace DotBased.ASP.Auth; +/// +/// In memory data provider, for testing only! +/// +public class MemoryAuthDataProvider : IAuthDataProvider +{ + private Dictionary _userDict = []; + private Dictionary _groupDict = []; + private Dictionary _authenticationDict = []; + + public async Task CreateUserAsync(UserModel user) + { + throw new NotImplementedException(); + } + + public async Task UpdateUserAsync(UserModel user) + { + throw new NotImplementedException(); + } + + public async Task DeleteUserAsync(UserModel user) + { + throw new NotImplementedException(); + } + + public async Task> GetUserAsync(string id, string email, string username) + { + throw new NotImplementedException(); + } + + public async Task> GetUsersAsync(int start = 0, int amount = 30, string search = "") + { + throw new NotImplementedException(); + } + + public async Task CreateGroupAsync(GroupModel group) + { + throw new NotImplementedException(); + } + + public async Task UpdateGroupAsync(GroupModel group) + { + throw new NotImplementedException(); + } + + public async Task DeleteGroupAsync(GroupModel group) + { + throw new NotImplementedException(); + } + + public async Task> GetGroupAsync(string id) + { + throw new NotImplementedException(); + } + + public async Task> GetGroupsAsync(int start = 0, int amount = 30, string search = "") + { + throw new NotImplementedException(); + } + + public async Task CreateAuthenticationStateAsync(AuthenticationStateModel authenticationState) + { + throw new NotImplementedException(); + } + + public async Task UpdateAuthenticationStateAsync(AuthenticationStateModel authenticationState) + { + throw new NotImplementedException(); + } + + public async Task DeleteAuthenticationStateAsync(AuthenticationStateModel authenticationState) + { + throw new NotImplementedException(); + } + + public async Task> GetAuthenticationStateAsync(string id) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/DotBased.ASP.Auth/Services/AuthService.cs b/DotBased.ASP.Auth/Services/AuthService.cs index ab447a8..308e512 100644 --- a/DotBased.ASP.Auth/Services/AuthService.cs +++ b/DotBased.ASP.Auth/Services/AuthService.cs @@ -1,6 +1,43 @@ +using DotBased.ASP.Auth.Domains; +using DotBased.ASP.Auth.Domains.Auth; +using DotBased.Extensions; +using DotBased.Logging; + namespace DotBased.ASP.Auth.Services; public class AuthService { - + public AuthService(IAuthDataProvider dataProvider) + { + _dataProvider = dataProvider; + _logger = LogService.RegisterLogger(typeof(AuthService)); + } + + private readonly IAuthDataProvider _dataProvider; + private readonly ILogger _logger; + + public async Task> LoginAsync(LoginModel login) + { + if (login.UserName.IsNullOrWhiteSpace()) + return Result.Failed("Username argument is empty!"); + var userResult = await _dataProvider.GetUserAsync(string.Empty, login.Email, login.UserName); + //TODO: validate user password and create a session state + return Result.Failed(""); + } + + public async Task Logout(string state) + { + if (state.IsNullOrWhiteSpace()) + return Result.Failed($"Argument {nameof(state)} is empty!"); + var stateResult = await _dataProvider.GetAuthenticationStateAsync(state); + if (!stateResult.Success || stateResult.Value == null) + return stateResult; + var authState = stateResult.Value; + //TODO: Update state to logged out and update the state + + var updatedStateResult = await _dataProvider.UpdateAuthenticationStateAsync(authState); + if (updatedStateResult.Success) return updatedStateResult; + _logger.Warning(updatedStateResult.Message); + return updatedStateResult; + } } \ No newline at end of file