diff --git a/server/StudySharp.API/Controllers/AccountController.cs b/server/StudySharp.API/Controllers/AccountController.cs index 69ff796..237cc0c 100644 --- a/server/StudySharp.API/Controllers/AccountController.cs +++ b/server/StudySharp.API/Controllers/AccountController.cs @@ -13,7 +13,7 @@ namespace StudySharp.API.Controllers { [Authorize] [ApiController] - [Route("api/[controller]")] + [Route("api/account")] public class AccountController : ControllerBase { private readonly IMediator _mediator; @@ -63,7 +63,7 @@ public async Task Logout() public async Task> RefreshToken([FromBody] RefreshTokenRequest refreshTokenRequest) { var accessToken = await HttpContext.GetTokenAsync("Bearer", "access_token"); - var userName = User.Identity.Name; + var userName = User.Identity?.Name; var refreshTokenCommand = new RefreshTokenCommand { AccessToken = accessToken, diff --git a/server/StudySharp.API/Responses/Auth/RefreshTokenResponse.cs b/server/StudySharp.API/Responses/Auth/RefreshTokenResponse.cs index 2756d6c..461d1a1 100644 --- a/server/StudySharp.API/Responses/Auth/RefreshTokenResponse.cs +++ b/server/StudySharp.API/Responses/Auth/RefreshTokenResponse.cs @@ -1,6 +1,10 @@ namespace StudySharp.API.Responses.Auth { - public class RefreshTokenResponse : LoginResponse + public sealed class RefreshTokenResponse { + public string UserName { get; set; } + public string Role { get; set; } + public string AccessToken { get; set; } + public string RefreshToken { get; set; } } } diff --git a/server/StudySharp.API/Startup.cs b/server/StudySharp.API/Startup.cs index f12f441..1985dd8 100644 --- a/server/StudySharp.API/Startup.cs +++ b/server/StudySharp.API/Startup.cs @@ -95,6 +95,8 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) endpoints.MapControllers(); }) .EnsureDbMigrated(); + + StudySharpDbContextSeedData.InitializeAsync(app.ApplicationServices, Configuration); } } } diff --git a/server/StudySharp.API/appsettings.json b/server/StudySharp.API/appsettings.json index 203e224..bf59e02 100644 --- a/server/StudySharp.API/appsettings.json +++ b/server/StudySharp.API/appsettings.json @@ -1,6 +1,7 @@ { "ConnectionStrings": { - "Default": "User ID=shrjmebswieehw;Password=7494580e3ea3832fc3944caf4e7c172867a35ddf48ba8607755190c3fadfd674;Host=ec2-34-240-75-196.eu-west-1.compute.amazonaws.com;Port=5432;Database=dcaqfc0mb9rvaj;Pooling=true;sslmode=Require;TrustServerCertificate=true;" + "Default": "User ID=shrjmebswieehw;Password=7494580e3ea3832fc3944caf4e7c172867a35ddf48ba8607755190c3fadfd674;Host=ec2-34-240-75-196.eu-west-1.compute.amazonaws.com;Port=5432;Database=dcaqfc0mb9rvaj;Pooling=true;sslmode=Require;TrustServerCertificate=true;", + "Local": "User ID=postgres;Password=postgres;Host=localhost;Port=5432;Database=StudySharp" }, "Logging": { "LogLevel": { @@ -17,8 +18,12 @@ "accessTokenExpiration": 20, "refreshTokenExpiration": 60 }, - "EmailConfig":{ + "EmailConfig": { "Name": "StudySharp", "From": "kotohomka@gmail.com" + }, + "AdminCredentials": { + "UserName": "barcat75@gmail.com", + "Password": "HuskTheBest75_" } } diff --git a/server/StudySharp.ApplicationServices/Commands/AddTagCommand.cs b/server/StudySharp.ApplicationServices/Commands/AddTagCommand.cs index db1e973..3fd04da 100644 --- a/server/StudySharp.ApplicationServices/Commands/AddTagCommand.cs +++ b/server/StudySharp.ApplicationServices/Commands/AddTagCommand.cs @@ -2,11 +2,10 @@ using System.Threading.Tasks; using FluentValidation; using MediatR; -using Microsoft.EntityFrameworkCore; using StudySharp.Domain.Constants; using StudySharp.Domain.General; using StudySharp.Domain.Models; -using StudySharp.Domain.Validators; +using StudySharp.Domain.ValidationRules; using StudySharp.DomainServices; namespace StudySharp.ApplicationServices.Commands diff --git a/server/StudySharp.ApplicationServices/Commands/Auth/LoginCommand.cs b/server/StudySharp.ApplicationServices/Commands/Auth/LoginCommand.cs index 07f4085..1ff5914 100644 --- a/server/StudySharp.ApplicationServices/Commands/Auth/LoginCommand.cs +++ b/server/StudySharp.ApplicationServices/Commands/Auth/LoginCommand.cs @@ -2,10 +2,12 @@ using System.Security.Claims; using System.Threading; using System.Threading.Tasks; +using FluentValidation; using MediatR; using Microsoft.AspNetCore.Identity; using StudySharp.ApplicationServices.JwtAuthService; using StudySharp.ApplicationServices.JwtAuthService.ResultModels; +using StudySharp.ApplicationServices.ValidationRules.Auth; using StudySharp.Domain.Constants; using StudySharp.Domain.General; using StudySharp.DomainServices; @@ -18,24 +20,36 @@ public sealed class LoginCommand : IRequest> public string Password { get; set; } } + public class LoginCommandValidator : AbstractValidator + { + public LoginCommandValidator(ILoginRules rules) + { + RuleFor(_ => _.Email) + .NotEmpty() + .WithMessage(ErrorConstants.InvalidCredentials) + .MustAsync(rules.UserIsRegistered) + .WithMessage(_ => ErrorConstants.InvalidCredentials); + + RuleFor(_ => _.Password) + .NotEmpty() + .WithMessage(ErrorConstants.InvalidCredentials); + } + } + public sealed class LoginCommandHandler : IRequestHandler> { private readonly UserManager _userManager; private readonly IJwtService _jwtService; - public LoginCommandHandler(UserManager userManager, IJwtService jwtAuthManager) + public LoginCommandHandler(UserManager userManager, IJwtService jwtService) { _userManager = userManager; - _jwtService = jwtAuthManager; + _jwtService = jwtService; } public async Task> Handle(LoginCommand request, CancellationToken cancellationToken) { var user = await _userManager.FindByNameAsync(request.Email); - if (user == null) - { - return OperationResult.Fail(ErrorConstants.InvalidCredentials); - } var isSucceeded = await _userManager.CheckPasswordAsync(user, request.Password); diff --git a/server/StudySharp.ApplicationServices/Commands/Auth/LogoutCommand.cs b/server/StudySharp.ApplicationServices/Commands/Auth/LogoutCommand.cs index c548367..39fcf7a 100644 --- a/server/StudySharp.ApplicationServices/Commands/Auth/LogoutCommand.cs +++ b/server/StudySharp.ApplicationServices/Commands/Auth/LogoutCommand.cs @@ -1,8 +1,10 @@ using System.Threading; using System.Threading.Tasks; +using FluentValidation; using MediatR; using Microsoft.AspNetCore.Identity; using StudySharp.ApplicationServices.JwtAuthService; +using StudySharp.ApplicationServices.ValidationRules.Auth; using StudySharp.Domain.Constants; using StudySharp.Domain.General; using StudySharp.DomainServices; @@ -14,24 +16,32 @@ public sealed class LogoutCommand : IRequest public string UserName { get; set; } } + public class LogoutCommandValidator : AbstractValidator + { + public LogoutCommandValidator(ILogoutRules rules) + { + RuleFor(_ => _.UserName) + .NotEmpty() + .WithMessage(string.Format(ErrorConstants.FieldIsRequired, nameof(ApplicationUser.UserName))) + .MustAsync(rules.UserIsRegistered) + .WithMessage(_ => string.Format(ErrorConstants.EntityNotFound, "User", nameof(ApplicationUser.Email), _.UserName)); + } + } + public sealed class LogoutCommandHandler : IRequestHandler { private readonly UserManager _userManager; private readonly IJwtService _jwtService; - public LogoutCommandHandler(UserManager userManager, IJwtService jwtAuthManager) + public LogoutCommandHandler(UserManager userManager, IJwtService jwtService) { _userManager = userManager; - _jwtService = jwtAuthManager; + _jwtService = jwtService; } public async Task Handle(LogoutCommand request, CancellationToken cancellationToken) { var user = await _userManager.FindByNameAsync(request.UserName); - if (user == null) - { - return OperationResult.Fail(string.Format(ErrorConstants.EntityNotFound, "User", "Email", request.UserName)); - } _jwtService.RemoveRefreshTokenByUserName(user.UserName); diff --git a/server/StudySharp.ApplicationServices/Commands/Auth/RefsreshTokenCommand.cs b/server/StudySharp.ApplicationServices/Commands/Auth/RefsreshTokenCommand.cs index 98203ea..0b64109 100644 --- a/server/StudySharp.ApplicationServices/Commands/Auth/RefsreshTokenCommand.cs +++ b/server/StudySharp.ApplicationServices/Commands/Auth/RefsreshTokenCommand.cs @@ -1,11 +1,13 @@ using System; using System.Threading; using System.Threading.Tasks; +using FluentValidation; using MediatR; using Microsoft.AspNetCore.Identity; using Microsoft.IdentityModel.Tokens; using StudySharp.ApplicationServices.JwtAuthService; using StudySharp.ApplicationServices.JwtAuthService.ResultModels; +using StudySharp.ApplicationServices.ValidationRules.Auth; using StudySharp.Domain.Constants; using StudySharp.Domain.General; using StudySharp.DomainServices; @@ -19,14 +21,34 @@ public sealed class RefreshTokenCommand : IRequest> + public class RefreshTokenCommandValidator : AbstractValidator + { + public RefreshTokenCommandValidator(IRefreshTokenRules rules) + { + RuleFor(_ => _.UserName) + .NotEmpty() + .WithMessage(string.Format(ErrorConstants.FieldIsRequired, nameof(ApplicationUser.Email))) + .MustAsync(rules.UserIsRegistered) + .WithMessage(_ => string.Format(ErrorConstants.EntityNotFound, "User", nameof(ApplicationUser.Email), _.UserName)); + + RuleFor(_ => _.AccessToken) + .NotEmpty() + .WithMessage(string.Format(ErrorConstants.FieldIsRequired, nameof(RefreshTokenCommand.AccessToken))); + + RuleFor(_ => _.RefreshToken) + .NotEmpty() + .WithMessage(string.Format(ErrorConstants.FieldIsRequired, nameof(RefreshTokenCommand.RefreshToken))); + } + } + + public sealed class RefreshTokenCommandHandler : IRequestHandler> { private readonly UserManager _userManager; private readonly IJwtService _jwtService; - public RefsreshTokenCommandHandler(IJwtService jwtAuthManager, UserManager userManager) + public RefreshTokenCommandHandler(IJwtService jwtService, UserManager userManager) { - _jwtService = jwtAuthManager; + _jwtService = jwtService; _userManager = userManager; } @@ -35,15 +57,6 @@ public async Task> Handle(RefreshTokenComman try { var user = await _userManager.FindByNameAsync(request.UserName); - if (user == null) - { - return OperationResult.Fail(string.Format(ErrorConstants.EntityNotFound, "User", "Email", request.UserName)); - } - - if (string.IsNullOrEmpty(request.RefreshToken)) - { - return OperationResult.Fail(ErrorConstants.InvalidToken); - } var jwtResult = _jwtService.Refresh(request.RefreshToken, request.AccessToken, DateTime.Now); return OperationResult.Ok(new RefreshTokenResult diff --git a/server/StudySharp.ApplicationServices/Commands/Auth/RegisterNewUserCommand.cs b/server/StudySharp.ApplicationServices/Commands/Auth/RegisterNewUserCommand.cs index b8f645e..7bac0bc 100644 --- a/server/StudySharp.ApplicationServices/Commands/Auth/RegisterNewUserCommand.cs +++ b/server/StudySharp.ApplicationServices/Commands/Auth/RegisterNewUserCommand.cs @@ -1,8 +1,10 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using FluentValidation; using MediatR; using Microsoft.AspNetCore.Identity; +using StudySharp.ApplicationServices.ValidationRules.Auth; using StudySharp.Domain.Constants; using StudySharp.Domain.General; using StudySharp.DomainServices; @@ -16,6 +18,28 @@ public sealed class RegisterNewUserCommand : IRequest public string ConfirmPassword { get; set; } } + public class RegisterNewUserCommandValidator : AbstractValidator + { + public RegisterNewUserCommandValidator(IRegisterNewUserValidationRules rules) + { + RuleFor(_ => _.Email) + .NotEmpty() + .WithMessage(string.Format(ErrorConstants.FieldIsRequired, nameof(RegisterNewUserCommand.Email))) + .MustAsync(rules.UserNameIsUnique) + .WithMessage(_ => string.Format(ErrorConstants.EntityAlreadyExists, "User", nameof(RegisterNewUserCommand.Email), _.Email)); + + RuleFor(_ => _.Password) + .NotEmpty() + .WithMessage(string.Format(ErrorConstants.FieldIsRequired, nameof(RegisterNewUserCommand.Password))); + + RuleFor(_ => _.ConfirmPassword) + .NotEmpty() + .WithMessage(string.Format(ErrorConstants.FieldIsRequired, "Confirm Password")) + .Must((registerNewUserCommand, confirmPassword) => registerNewUserCommand.Password.Trim().Equals(confirmPassword.Trim())) + .WithMessage(ErrorConstants.PasswordsDoNotMatch); + } + } + public sealed class RegisterNewUserCommandHandler : IRequestHandler { private readonly UserManager _userManager; @@ -27,17 +51,6 @@ public RegisterNewUserCommandHandler(UserManager userManager) public async Task Handle(RegisterNewUserCommand request, CancellationToken cancellationToken) { - var user = await _userManager.FindByNameAsync(request.Email); - if (user != null) - { - return OperationResult.Fail(string.Format(ErrorConstants.EntityAlreadyExists, "User", nameof(request.Email), request.Email)); - } - - if (!request.Password.Trim().Equals(request.ConfirmPassword.Trim())) - { - return OperationResult.Fail(ErrorConstants.PasswordsDoNotMatch); - } - var newUser = new ApplicationUser { UserName = request.Email, diff --git a/server/StudySharp.ApplicationServices/JwtAuthService/JwtAuthServiceExtensions.cs b/server/StudySharp.ApplicationServices/JwtAuthService/JwtAuthServiceExtensions.cs new file mode 100644 index 0000000..8c4b115 --- /dev/null +++ b/server/StudySharp.ApplicationServices/JwtAuthService/JwtAuthServiceExtensions.cs @@ -0,0 +1,42 @@ +using System; +using System.Text; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; + +namespace StudySharp.ApplicationServices.JwtAuthService +{ + public static class JwtAuthServiceExtensions + { + public static IServiceCollection AddJwtAuthService(this IServiceCollection services, IConfiguration configuration) + { + var jwtTokenConfig = configuration.GetSection(JwtTokenConfig.JwtTokenConfigSection).Get(); + services.Configure(options => configuration.GetSection(JwtTokenConfig.JwtTokenConfigSection).Bind(options)); + services.AddAuthentication(x => + { + x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }).AddJwtBearer(x => + { + x.RequireHttpsMetadata = false; + x.SaveToken = true; + x.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidIssuer = jwtTokenConfig.Issuer, + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtTokenConfig.Secret)), + ValidAudience = jwtTokenConfig.Audience, + ValidateAudience = true, + ValidateLifetime = true, + ClockSkew = TimeSpan.Zero, + }; + }); + services.AddSingleton(); + services.AddHostedService(); + + return services; + } + } +} diff --git a/server/StudySharp.ApplicationServices/JwtAuthService/JwtRefreshTokenCache.cs b/server/StudySharp.ApplicationServices/JwtAuthService/JwtRefreshTokenCache.cs index 792edde..b789c05 100644 --- a/server/StudySharp.ApplicationServices/JwtAuthService/JwtRefreshTokenCache.cs +++ b/server/StudySharp.ApplicationServices/JwtAuthService/JwtRefreshTokenCache.cs @@ -31,13 +31,13 @@ private void DoWork(object state) public Task StopAsync(CancellationToken stoppingToken) { - _timer?.Change(Timeout.Infinite, 0); + _timer.Change(Timeout.Infinite, 0); return Task.CompletedTask; } public void Dispose() { - _timer?.Dispose(); + _timer.Dispose(); GC.SuppressFinalize(this); } } diff --git a/server/StudySharp.ApplicationServices/JwtAuthService/JwtService.cs b/server/StudySharp.ApplicationServices/JwtAuthService/JwtService.cs index 10a054e..8300e99 100644 --- a/server/StudySharp.ApplicationServices/JwtAuthService/JwtService.cs +++ b/server/StudySharp.ApplicationServices/JwtAuthService/JwtService.cs @@ -7,6 +7,7 @@ using System.Security.Cryptography; using System.Text; using System.Text.Json.Serialization; +using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; namespace StudySharp.ApplicationServices.JwtAuthService @@ -28,11 +29,11 @@ public class JwtService : IJwtService private readonly JwtTokenConfig _jwtTokenConfig; private readonly byte[] _secret; - public JwtService(JwtTokenConfig jwtTokenConfig) + public JwtService(IOptions jwtTokenConfigOptions) { - _jwtTokenConfig = jwtTokenConfig; + _jwtTokenConfig = jwtTokenConfigOptions.Value; _usersRefreshTokens = new ConcurrentDictionary(); - _secret = Encoding.ASCII.GetBytes(jwtTokenConfig.Secret); + _secret = Encoding.ASCII.GetBytes(jwtTokenConfigOptions.Value.Secret); } // optional: clean up expired refresh tokens @@ -122,7 +123,7 @@ public JwtAuthResult Refresh(string refreshToken, string accessToken, DateTime n ValidAudience = _jwtTokenConfig.Audience, ValidateAudience = true, ValidateLifetime = true, - ClockSkew = TimeSpan.FromMinutes(1), + ClockSkew = TimeSpan.Zero, }, out var validatedToken); return (principal, validatedToken as JwtSecurityToken); diff --git a/server/StudySharp.ApplicationServices/JwtAuthService/JwtTokenConfig.cs b/server/StudySharp.ApplicationServices/JwtAuthService/JwtTokenConfig.cs index e9ed420..3ba6438 100644 --- a/server/StudySharp.ApplicationServices/JwtAuthService/JwtTokenConfig.cs +++ b/server/StudySharp.ApplicationServices/JwtAuthService/JwtTokenConfig.cs @@ -4,6 +4,8 @@ namespace StudySharp.ApplicationServices.JwtAuthService { public sealed class JwtTokenConfig { + public const string JwtTokenConfigSection = "jwtTokenConfig"; + [JsonPropertyName("secret")] public string Secret { get; set; } diff --git a/server/StudySharp.ApplicationServices/JwtAuthService/ResultModels/LoginResult.cs b/server/StudySharp.ApplicationServices/JwtAuthService/ResultModels/LoginResult.cs index c2cb94d..7269718 100644 --- a/server/StudySharp.ApplicationServices/JwtAuthService/ResultModels/LoginResult.cs +++ b/server/StudySharp.ApplicationServices/JwtAuthService/ResultModels/LoginResult.cs @@ -1,6 +1,6 @@ namespace StudySharp.ApplicationServices.JwtAuthService.ResultModels { - public class LoginResult + public sealed class LoginResult { public string UserName { get; set; } public string Role { get; set; } diff --git a/server/StudySharp.ApplicationServices/JwtAuthService/ResultModels/RefreshTokenResult.cs b/server/StudySharp.ApplicationServices/JwtAuthService/ResultModels/RefreshTokenResult.cs index 2acb52c..019c575 100644 --- a/server/StudySharp.ApplicationServices/JwtAuthService/ResultModels/RefreshTokenResult.cs +++ b/server/StudySharp.ApplicationServices/JwtAuthService/ResultModels/RefreshTokenResult.cs @@ -1,6 +1,10 @@ namespace StudySharp.ApplicationServices.JwtAuthService.ResultModels { - public sealed class RefreshTokenResult : LoginResult + public sealed class RefreshTokenResult { + public string UserName { get; set; } + public string Role { get; set; } + public string AccessToken { get; set; } + public string RefreshToken { get; set; } } } diff --git a/server/StudySharp.ApplicationServices/Startup.cs b/server/StudySharp.ApplicationServices/Startup.cs index ea04293..b0a4faa 100644 --- a/server/StudySharp.ApplicationServices/Startup.cs +++ b/server/StudySharp.ApplicationServices/Startup.cs @@ -1,15 +1,11 @@ -using System; -using System.Reflection; -using System.Text; +using System.Reflection; using MediatR; -using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.IdentityModel.Tokens; using StudySharp.ApplicationServices.EmailService; using StudySharp.ApplicationServices.Infrastructure.EmailService; using StudySharp.ApplicationServices.JwtAuthService; -using IValidationRule = StudySharp.Domain.Validators.IValidationRule; +using IValidationRule = StudySharp.Domain.ValidationRules.IValidationRule; namespace StudySharp.ApplicationServices { @@ -28,30 +24,7 @@ public static IServiceCollection AddApplicationServices( .AsMatchingInterface() .WithScopedLifetime(); }); - var jwtTokenConfig = configuration.GetSection("jwtTokenConfig").Get(); - services.AddSingleton(jwtTokenConfig); - services.AddAuthentication(x => - { - x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; - x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; - }).AddJwtBearer(x => - { - x.RequireHttpsMetadata = false; - x.SaveToken = true; - x.TokenValidationParameters = new TokenValidationParameters - { - ValidateIssuer = true, - ValidIssuer = jwtTokenConfig.Issuer, - ValidateIssuerSigningKey = true, - IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtTokenConfig.Secret)), - ValidAudience = jwtTokenConfig.Audience, - ValidateAudience = true, - ValidateLifetime = true, - ClockSkew = TimeSpan.Zero, - }; - }); - services.AddSingleton(); - services.AddHostedService(); + services.AddJwtAuthService(configuration); services.AddMediatR(Assembly.GetExecutingAssembly()); return services; } diff --git a/server/StudySharp.ApplicationServices/ValidationRules/Auth/LoginRules.cs b/server/StudySharp.ApplicationServices/ValidationRules/Auth/LoginRules.cs new file mode 100644 index 0000000..6bf7b76 --- /dev/null +++ b/server/StudySharp.ApplicationServices/ValidationRules/Auth/LoginRules.cs @@ -0,0 +1,28 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using StudySharp.Domain.ValidationRules; +using StudySharp.DomainServices; + +namespace StudySharp.ApplicationServices.ValidationRules.Auth +{ + public interface ILoginRules : IValidationRule + { + Task UserIsRegistered(string userName, CancellationToken cancellationToken); + } + + public sealed class LoginRules : ILoginRules + { + private readonly StudySharpDbContext _context; + + public LoginRules(StudySharpDbContext context) + { + _context = context; + } + + public async Task UserIsRegistered(string userName, CancellationToken cancellationToken) + { + return await _context.Users.AnyAsync(_ => _.UserName.ToLower().Equals(userName.ToLower()), cancellationToken); + } + } +} diff --git a/server/StudySharp.ApplicationServices/ValidationRules/Auth/LogoutRules.cs b/server/StudySharp.ApplicationServices/ValidationRules/Auth/LogoutRules.cs new file mode 100644 index 0000000..650e2aa --- /dev/null +++ b/server/StudySharp.ApplicationServices/ValidationRules/Auth/LogoutRules.cs @@ -0,0 +1,28 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using StudySharp.Domain.ValidationRules; +using StudySharp.DomainServices; + +namespace StudySharp.ApplicationServices.ValidationRules.Auth +{ + public interface ILogoutRules : IValidationRule + { + Task UserIsRegistered(string userName, CancellationToken cancellationToken); + } + + public sealed class LogoutRules : ILogoutRules + { + private readonly StudySharpDbContext _context; + + public LogoutRules(StudySharpDbContext context) + { + _context = context; + } + + public async Task UserIsRegistered(string userName, CancellationToken cancellationToken) + { + return await _context.Users.AnyAsync(_ => _.UserName.ToLower().Equals(userName.ToLower()), cancellationToken); + } + } +} diff --git a/server/StudySharp.ApplicationServices/ValidationRules/Auth/RefreshTokenRules.cs b/server/StudySharp.ApplicationServices/ValidationRules/Auth/RefreshTokenRules.cs new file mode 100644 index 0000000..c31e720 --- /dev/null +++ b/server/StudySharp.ApplicationServices/ValidationRules/Auth/RefreshTokenRules.cs @@ -0,0 +1,28 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using StudySharp.Domain.ValidationRules; +using StudySharp.DomainServices; + +namespace StudySharp.ApplicationServices.ValidationRules.Auth +{ + public interface IRefreshTokenRules : IValidationRule + { + Task UserIsRegistered(string userName, CancellationToken cancellationToken); + } + + public sealed class RefreshTokenRules : IRefreshTokenRules + { + private readonly StudySharpDbContext _context; + + public RefreshTokenRules(StudySharpDbContext context) + { + _context = context; + } + + public async Task UserIsRegistered(string userName, CancellationToken cancellationToken) + { + return await _context.Users.AnyAsync(_ => _.UserName.ToLower().Equals(userName.ToLower()), cancellationToken); + } + } +} diff --git a/server/StudySharp.ApplicationServices/ValidationRules/Auth/RegisterNewUserValidationRules.cs b/server/StudySharp.ApplicationServices/ValidationRules/Auth/RegisterNewUserValidationRules.cs new file mode 100644 index 0000000..0e98488 --- /dev/null +++ b/server/StudySharp.ApplicationServices/ValidationRules/Auth/RegisterNewUserValidationRules.cs @@ -0,0 +1,28 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using StudySharp.Domain.ValidationRules; +using StudySharp.DomainServices; + +namespace StudySharp.ApplicationServices.ValidationRules.Auth +{ + public interface IRegisterNewUserValidationRules : IValidationRule + { + Task UserNameIsUnique(string userName, CancellationToken cancellationToken); + } + + public sealed class RegisterNewUserValidationRules : IRegisterNewUserValidationRules + { + private readonly StudySharpDbContext _context; + + public RegisterNewUserValidationRules(StudySharpDbContext context) + { + _context = context; + } + + public async Task UserNameIsUnique(string userName, CancellationToken cancellationToken) + { + return await _context.Users.AllAsync(_ => !_.UserName.ToLower().Equals(userName.ToLower()), cancellationToken); + } + } +} diff --git a/server/StudySharp.ApplicationServices/ValidationRules/TagRules.cs b/server/StudySharp.ApplicationServices/ValidationRules/TagRules.cs index ab54f4f..7830ed1 100644 --- a/server/StudySharp.ApplicationServices/ValidationRules/TagRules.cs +++ b/server/StudySharp.ApplicationServices/ValidationRules/TagRules.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; -using StudySharp.Domain.Validators; +using StudySharp.Domain.ValidationRules; using StudySharp.DomainServices; namespace StudySharp.ApplicationServices.ValidationRules diff --git a/server/StudySharp.Domain/ValidationRules/ITagRules.cs b/server/StudySharp.Domain/ValidationRules/ITagRules.cs index 3173f45..d3b2e94 100644 --- a/server/StudySharp.Domain/ValidationRules/ITagRules.cs +++ b/server/StudySharp.Domain/ValidationRules/ITagRules.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; -namespace StudySharp.Domain.Validators +namespace StudySharp.Domain.ValidationRules { public interface ITagRules : IValidationRule { diff --git a/server/StudySharp.Domain/ValidationRules/IValidationRule.cs b/server/StudySharp.Domain/ValidationRules/IValidationRule.cs index ce07d52..b37fd6c 100644 --- a/server/StudySharp.Domain/ValidationRules/IValidationRule.cs +++ b/server/StudySharp.Domain/ValidationRules/IValidationRule.cs @@ -1,4 +1,4 @@ -namespace StudySharp.Domain.Validators +namespace StudySharp.Domain.ValidationRules { public interface IValidationRule { diff --git a/server/StudySharp.DomainServices/Constants/ConnectionStrings.cs b/server/StudySharp.DomainServices/Constants/ConnectionStrings.cs index 5aec4c8..de39213 100644 --- a/server/StudySharp.DomainServices/Constants/ConnectionStrings.cs +++ b/server/StudySharp.DomainServices/Constants/ConnectionStrings.cs @@ -3,5 +3,6 @@ public static class ConnectionStrings { public const string Default = "Default"; + public const string Local = "Local"; } } diff --git a/server/StudySharp.DomainServices/StudySharpDbContextSeedData.cs b/server/StudySharp.DomainServices/StudySharpDbContextSeedData.cs new file mode 100644 index 0000000..0f4ec7a --- /dev/null +++ b/server/StudySharp.DomainServices/StudySharpDbContextSeedData.cs @@ -0,0 +1,61 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using StudySharp.Domain.Enums; + +namespace StudySharp.DomainServices +{ + public static class StudySharpDbContextSeedData + { + private const string AdminCredentialsSection = "AdminCredentials"; + private const string AdminUserName = "UserName"; + private const string AdminPassword = "Password"; + + public static async Task InitializeAsync(IServiceProvider serviceProvider, IConfiguration configuration) + { + using var scope = serviceProvider.CreateScope(); + + var userManager = scope.ServiceProvider.GetRequiredService>(); + var roleManager = scope.ServiceProvider.GetRequiredService>>(); + + await GenerateAndSeedRoles(roleManager); + await GenerateAndSeedAdmin(userManager, configuration); + } + + private static async Task GenerateAndSeedRoles(RoleManager> roleManager) + { + if (await roleManager.Roles.AnyAsync()) + { + return; + } + + foreach (var roleName in Enum.GetNames()) + { + await roleManager.CreateAsync(new IdentityRole(roleName)); + } + } + + private static async Task GenerateAndSeedAdmin(UserManager userManager, IConfiguration configuration) + { + var userName = configuration.GetSection(AdminCredentialsSection).GetValue(AdminUserName); + var password = configuration.GetSection(AdminCredentialsSection).GetValue(AdminPassword); + + if (await userManager.Users.AnyAsync(_ => _.NormalizedUserName.Equals(userName.ToUpper()))) + { + return; + } + + var admin = new ApplicationUser + { + Email = userName, + UserName = userName, + }; + + await userManager.CreateAsync(admin, password); + await userManager.AddToRoleAsync(admin, DomainRoles.Admin.ToString()); + } + } +} \ No newline at end of file