diff --git a/Jube.App/Controllers/Authentication/AuthenticationController.cs b/Jube.App/Controllers/Authentication/AuthenticationController.cs index 1d1b651b..18eb6f24 100644 --- a/Jube.App/Controllers/Authentication/AuthenticationController.cs +++ b/Jube.App/Controllers/Authentication/AuthenticationController.cs @@ -2,12 +2,12 @@ * * This file is part of Jube™ software. * - * Jube™ is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License + * Jube™ is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - * Jube™ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * Jube™ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. - * You should have received a copy of the GNU Affero General Public License along with Jube™. If not, + * You should have received a copy of the GNU Affero General Public License along with Jube™. If not, * see . */ @@ -15,13 +15,11 @@ using System.IdentityModel.Tokens.Jwt; using System.Net; using Jube.App.Code; -using Jube.App.Dto.Authentication; -using Jube.App.Validators.Authentication; using Jube.Data.Context; -using Jube.Data.Poco; -using Jube.Data.Repository; -using Jube.Data.Security; using Jube.Engine.Helpers; +using Jube.Service.Dto.Authentication; +using Jube.Service.Exceptions.Authentication; +using Jube.Validations.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -33,195 +31,118 @@ namespace Jube.App.Controllers.Authentication [AllowAnonymous] public class AuthenticationController : Controller { - private readonly IHttpContextAccessor _contextAccessor; + private readonly IHttpContextAccessor contextAccessor; - private readonly DbContext _dbContext; - private readonly DynamicEnvironment.DynamicEnvironment _dynamicEnvironment; + private readonly DbContext dbContext; + private readonly DynamicEnvironment.DynamicEnvironment dynamicEnvironment; + private readonly Service.Authentication.Authentication service; - public AuthenticationController(DynamicEnvironment.DynamicEnvironment dynamicEnvironment,IHttpContextAccessor contextAccessor) + public AuthenticationController(DynamicEnvironment.DynamicEnvironment dynamicEnvironment, + IHttpContextAccessor contextAccessor) { - _dynamicEnvironment = dynamicEnvironment; - _dbContext = DataConnectionDbContext.GetDbContextDataConnection(_dynamicEnvironment.AppSettings("ConnectionString")); - _contextAccessor = contextAccessor; + this.dynamicEnvironment = dynamicEnvironment; + dbContext = + DataConnectionDbContext.GetDbContextDataConnection(this.dynamicEnvironment.AppSettings("ConnectionString")); + this.contextAccessor = contextAccessor; + service = new Service.Authentication.Authentication(dbContext); } - + protected override void Dispose(bool disposing) { if (disposing) { - _dbContext.Close(); - _dbContext.Dispose(); + dbContext.Close(); + dbContext.Dispose(); } + base.Dispose(disposing); } [HttpPost("ByUserNamePassword")] - [ProducesResponseType(typeof(AuthenticationResponseDto), (int) HttpStatusCode.OK)] - public ActionResult ExhaustiveSearchInstance([FromBody] AuthenticationRequestDto model) + [ProducesResponseType(typeof(AuthenticationResponseDto), (int)HttpStatusCode.OK)] + public ActionResult ExhaustiveSearchInstance( + [FromBody] AuthenticationRequestDto model) { - var userRegistryRepository = new UserRegistryRepository(_dbContext); var validator = new AuthenticationRequestDtoValidator(); - var results = validator.Validate(model); - if (!results.IsValid) return BadRequest(); - - var userLogin = new UserLogin - { - RemoteIp = _contextAccessor.HttpContext?.Connection.RemoteIpAddress?.ToString(), - LocalIp = _contextAccessor.HttpContext?.Connection.LocalIpAddress?.ToString() - }; - - var userRegistry = userRegistryRepository.GetByUserName(model.UserName); - if (userRegistry == null) + try { - LogLoginFailed(userLogin,model.UserName,1); - return Unauthorized(); - } + model.UserAgent = contextAccessor.HttpContext?.Connection.RemoteIpAddress?.ToString(); + model.LocalIp = contextAccessor.HttpContext?.Connection.LocalIpAddress?.ToString(); + model.UserAgent = Request.Headers.UserAgent.ToString(); - if (userRegistry.Active != 1) - { - LogLoginFailed(userLogin,userRegistry.Name,2); - return Unauthorized(); + service.AuthenticateByUserNamePassword(model, dynamicEnvironment.AppSettings("PasswordHashingKey")); } - - if (userRegistry.PasswordLocked == 1) + catch (PasswordExpiredException) { - LogLoginFailed(userLogin,userRegistry.Name,3); - return Unauthorized(); + return Forbid(); } - - if (!userRegistry.PasswordExpiryDate.HasValue - || string.IsNullOrEmpty(userRegistry.Password) - || !userRegistry.PasswordCreatedDate.HasValue) + catch (PasswordNewMustChangeException) { - LogLoginFailed(userLogin,userRegistry.Name,4); - return Unauthorized(); + return Forbid(); } - - if (!HashPassword.Verify(userRegistry.Password, - model.Password, - _dynamicEnvironment.AppSettings("PasswordHashingKey"))) + catch (Exception) { - userRegistryRepository.IncrementFailedPassword(userRegistry.Id); - - if (userRegistry.FailedPasswordCount > 8) - { - userRegistryRepository.SetLocked(userRegistry.Id); - } - - LogLoginFailed(userLogin,userRegistry.Name,5); - return Unauthorized(); } - if (!string.IsNullOrEmpty(model.NewPassword)) - { - var hashedPassword = HashPassword.GenerateHash(model.NewPassword, - _dynamicEnvironment.AppSettings("PasswordHashingKey")); - - userRegistryRepository.SetPassword(userRegistry.Id,hashedPassword,DateTime.Now.AddDays(90)); - } - else - { - if (!(DateTime.Now <= userRegistry.PasswordExpiryDate.Value)) return Forbid(); - } - + var authenticationDto = SetAuthenticationCookie(model); + return Ok(authenticationDto); + } + + private AuthenticationResponseDto SetAuthenticationCookie(AuthenticationRequestDto model) + { var token = Jwt.CreateToken(model.UserName, - _dynamicEnvironment.AppSettings("JWTKey"), - _dynamicEnvironment.AppSettings("JWTValidIssuer"), - _dynamicEnvironment.AppSettings("JWTValidAudience") + dynamicEnvironment.AppSettings("JWTKey"), + dynamicEnvironment.AppSettings("JWTValidIssuer"), + dynamicEnvironment.AppSettings("JWTValidAudience") ); var expiration = DateTime.Now.AddMinutes(15); - + var authenticationDto = new AuthenticationResponseDto { Token = new JwtSecurityTokenHandler().WriteToken(token), Expiration = expiration }; - + var cookieOptions = new CookieOptions { Expires = expiration, HttpOnly = true }; - - Response.Cookies.Append("authentication", - authenticationDto.Token,cookieOptions); - - LogLoginSuccess(userLogin,userRegistry.Name); - if (userRegistry.FailedPasswordCount > 0) - { - userRegistryRepository.ResetFailedPasswordCount(userRegistry.Id); - } - - return Ok(authenticationDto); + Response.Cookies.Append("authentication", + authenticationDto.Token, cookieOptions); + return authenticationDto; } [Authorize] [HttpPost("ChangePassword")] - [ProducesResponseType(typeof(AuthenticationResponseDto), (int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(AuthenticationResponseDto), (int)HttpStatusCode.OK)] public ActionResult ChangePassword([FromBody] ChangePasswordRequestDto model) { if (User.Identity == null) return Ok(); - var userRegistryRepository = new UserRegistryRepository(_dbContext,User.Identity.Name); - var validator = new ChangePasswordRequestDtoValidator(); + var validator = new ChangePasswordRequestDtoValidator(); + var results = validator.Validate(model); - + if (!results.IsValid) return BadRequest(); - - var userRegistry = userRegistryRepository.GetByUserName(User.Identity.Name); - - if (!HashPassword.Verify(userRegistry.Password, - model.Password, - _dynamicEnvironment.AppSettings("PasswordHashingKey"))) + + try + { + service.ChangePassword(User.Identity.Name, model, + dynamicEnvironment.AppSettings("PasswordHashingKey")); + } + catch (BadCredentialsException) { return Unauthorized(); } - - var hashedPassword = HashPassword.GenerateHash(model.NewPassword, - _dynamicEnvironment.AppSettings("PasswordHashingKey")); - - userRegistryRepository.SetPassword(userRegistry.Id,hashedPassword,DateTime.Now.AddDays(90)); - - return Ok(); - } - - private void LogLoginFailed(UserLogin userLogin,string createdUser,int failureTypeId) - { - var userLoginRepository = new UserLoginRepository(_dbContext,createdUser); - userLogin.Failed = 1; - userLogin.FailureTypeId = failureTypeId; - - if (_contextAccessor.HttpContext?.Connection.RemoteIpAddress != null) - userLogin.LocalIp = _contextAccessor.HttpContext.Connection.RemoteIpAddress.ToString(); - - if (_contextAccessor.HttpContext?.Connection.LocalIpAddress != null) - userLogin.LocalIp = _contextAccessor.HttpContext.Connection.LocalIpAddress.ToString(); - - userLogin.UserAgent = Request.Headers["User-Agent"].ToString(); - userLoginRepository.Insert(userLogin); - } - - private void LogLoginSuccess(UserLogin userLogin,string createdUser) - { - var userLoginRepository = new UserLoginRepository(_dbContext,createdUser); - userLogin.Failed = 0; - - if (_contextAccessor.HttpContext?.Connection.RemoteIpAddress != null) - userLogin.LocalIp = _contextAccessor.HttpContext.Connection.RemoteIpAddress.ToString(); - - if (_contextAccessor.HttpContext?.Connection.LocalIpAddress != null) - userLogin.LocalIp = _contextAccessor.HttpContext.Connection.LocalIpAddress.ToString(); - - userLogin.UserAgent = Request.Headers["User-Agent"].ToString(); - - userLoginRepository.Insert(userLogin); + return Ok(); } } } \ No newline at end of file diff --git a/Jube.App/Jube.App.csproj b/Jube.App/Jube.App.csproj index cd962da3..e659c6f4 100644 --- a/Jube.App/Jube.App.csproj +++ b/Jube.App/Jube.App.csproj @@ -71,6 +71,8 @@ + + diff --git a/Jube.App/Startup.cs b/Jube.App/Startup.cs index 3573e561..74aed728 100755 --- a/Jube.App/Startup.cs +++ b/Jube.App/Startup.cs @@ -233,7 +233,7 @@ public void ConfigureServices(IServiceCollection services) Console.WriteLine(@"Copyright (C) 2022-present Jube Holdings Limited."); Console.WriteLine(@""); - Console.WriteLine(@"This software is Jube™. Welcome."); + Console.WriteLine(@"This software is Jube. Welcome."); Console.WriteLine(@""); Console.Write( @"Jube™ is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version."); diff --git a/Jube.App/wwwroot/js/Account/Authentication.js b/Jube.App/wwwroot/js/Account/Authentication.js index d3bc9869..f35c656a 100644 --- a/Jube.App/wwwroot/js/Account/Authentication.js +++ b/Jube.App/wwwroot/js/Account/Authentication.js @@ -50,7 +50,9 @@ $( document ).ready(function() { let data = { userName: $("#UserName").val(), password: $("#Password").val(), - newPassword: $("#NewPassword").val() + newPassword: $("#NewPassword").val(), + repeatNewPassword: $("#VerifyNewPassword").val(), + PasswordChangeState: isChange }; $.ajax({ diff --git a/Jube.App/Dto/Authentication/AuthenticationRequestDto.cs b/Jube.Blazor/Components/Code/Helpers/DataConnectionDbContext.cs similarity index 63% rename from Jube.App/Dto/Authentication/AuthenticationRequestDto.cs rename to Jube.Blazor/Components/Code/Helpers/DataConnectionDbContext.cs index 36ffc8cc..6aa224ab 100644 --- a/Jube.App/Dto/Authentication/AuthenticationRequestDto.cs +++ b/Jube.Blazor/Components/Code/Helpers/DataConnectionDbContext.cs @@ -11,12 +11,18 @@ * see . */ -namespace Jube.App.Dto.Authentication +using Jube.Data.Context; +using LinqToDB.Configuration; + +namespace Jube.Blazor.Components.Code.Helpers; + +public static class DataConnectionDbContext { - public class AuthenticationRequestDto + public static DbContext GetDbContextDataConnection(string connectionString) { - public string UserName { get; set; } - public string Password { get; set; } - public string NewPassword { get; set; } + var builder = new LinqToDbConnectionOptionsBuilder(); + builder.UsePostgreSQL(connectionString); + var connection = builder.Build(); + return new DbContext(connection); } } \ No newline at end of file diff --git a/Jube.Blazor/Components/Code/NavigationManagerExtensions.cs b/Jube.Blazor/Components/Code/NavigationManagerExtensions.cs index b8c2dbd1..be940987 100644 --- a/Jube.Blazor/Components/Code/NavigationManagerExtensions.cs +++ b/Jube.Blazor/Components/Code/NavigationManagerExtensions.cs @@ -1,3 +1,16 @@ +/* Copyright (C) 2022-present Jube Holdings Limited. + * + * This file is part of Jube™ software. + * + * Jube™ is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * Jube™ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License along with Jube™. If not, + * see . + */ + using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.WebUtilities; @@ -5,7 +18,7 @@ namespace Jube.Blazor.Components.Code; public static class NavigationManagerExtensions { - public static bool TryGetQueryString(this NavigationManager navManager, string key, out T value) + public static bool TryGetQueryString(this NavigationManager navManager, string key, out T? value) { var uri = navManager.ToAbsoluteUri(navManager.Uri); diff --git a/Jube.Blazor/Components/Layout/MainLayout.razor b/Jube.Blazor/Components/Layout/MainLayout.razor index 3add003d..d03f795e 100644 --- a/Jube.Blazor/Components/Layout/MainLayout.razor +++ b/Jube.Blazor/Components/Layout/MainLayout.razor @@ -1,4 +1,6 @@ @inherits LayoutComponentBase +@inject IStringLocalizer Localizer + diff --git a/Jube.Blazor/Components/Pages/Account/Login.razor b/Jube.Blazor/Components/Pages/Account/Login.razor index bfed87f3..f371616f 100644 --- a/Jube.Blazor/Components/Pages/Account/Login.razor +++ b/Jube.Blazor/Components/Pages/Account/Login.razor @@ -1,27 +1,220 @@ +@* Copyright (C) 2022-present Jube Holdings Limited. + * + * This file is part of Jube™ software. + * + * Jube™ is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * Jube™ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License along with Jube™. If not, + * see . + *@ + @page "/account/login" @using Jube.Blazor.Components.Code +@using Jube.Blazor.Components.Code.Helpers +@using Jube.Data.Context +@using Jube.DynamicEnvironment +@using Jube.Service.Dto.Authentication +@using Jube.Service.Exceptions.Authentication +@using System.Security.Claims +@using Jube.Data.Poco @inject AuthenticationStateProvider AuthenticationStateProvider @inject NavigationManager NavigationManager -

Login

+@inject DynamicEnvironment DynamicEnvironment +@inject IHttpContextAccessor HttpContextAccessor; +@inject IStringLocalizer Localizer; + +

@Localizer["Pages:Account:Login:Login"]

- - - OnClickLogin()) Text="Login" ButtonStyle="ButtonStyle.Primary" Size="ButtonSize.Medium"/> - + + + + + + + + + + @Localizer["Pages:Account:Login:UserName"] + + + + + + + + @Localizer["Pages:Account:Login:Password"] + + + + + + + + + + + + + + + @Localizer["Pages:Account:Login:NewPassword"] + + + + + + + + @Localizer["Pages:Account:Login:RepeatNewPassword"] + + + + + + + + + + + + + + @Localizer["Pages:Account:Login:MustChangePassword"] + + + @Localizer["Pages:Account:Login:InvalidCredentialsTryAgain"] + @code { [CascadingParameter] private Task? AuthenticationState { get; set; } - private string userTextboxValue = "Administrator"; - private async Task OnClickLogin() + private readonly AuthenticationRequestDto authenticationRequestDto = new(); + private DbContext? dbContext; + private Service.Authentication.Authentication? service; + + private bool showChangeCredentialsAndHideLogin; + + @* ReSharper disable once NotAccessedField.Local *@ + private bool showInvalidCredentialsAlert; + + @* ReSharper disable once NotAccessedField.Local *@ + private bool showChangeCredentialsAlert; + + protected override void OnInitialized() + { + dbContext = + DataConnectionDbContext.GetDbContextDataConnection(DynamicEnvironment.AppSettings("ConnectionString")); + service = new Service.Authentication.Authentication(dbContext); + } + + private void OnCloseInvalidCredentials() + { + showInvalidCredentialsAlert = false; + } + + private void OnCloseChangeCredentials() + { + showChangeCredentialsAlert = false; + } + + private async Task OnValidSubmit() { - if (AuthenticationState != null) + if (AuthenticationState == null) return; + + if (authenticationRequestDto is { UserName: not null, Password: not null }) { - var authState = await ((CustomAuthenticationStateProvider) AuthenticationStateProvider).ChangeUser(userTextboxValue, "123456", "Associate"); + HideAllAlerts(); + + UserRegistry? userRegistry; + try + { + userRegistry = AuthenticateByUserNamePassword(); + } + catch (PasswordExpiredException) + { + ShowChangeCredentialsPanelAndAlert(); + + return; + } + catch (PasswordNewMustChangeException) + { + ShowChangeCredentialsPanelAndAlert(); + + return; + } + catch (Exception) + { + ShowInvalidCredentials(); + + return; + } + + if (userRegistry != null) + { + await CreateAuthenticationAndClaims(userRegistry); + + NavigateToReturnUriOrDefault(); - NavigationManager.TryGetQueryString("ReturnUrl", out var returnUri); - NavigationManager.NavigateTo(!string.IsNullOrEmpty(returnUri) ? returnUri : "counter"); + return; + } + + ShowInvalidCredentials(); } } + private void ShowInvalidCredentials() + { + showInvalidCredentialsAlert = true; + } + + private void HideAllAlerts() + { + showInvalidCredentialsAlert = false; + showChangeCredentialsAlert = false; + } + + private void ShowChangeCredentialsPanelAndAlert() + { + showChangeCredentialsAndHideLogin = true; + showChangeCredentialsAlert = true; + authenticationRequestDto.PasswordChangeState = true; + } + + private void NavigateToReturnUriOrDefault() + { + NavigationManager.TryGetQueryString("ReturnUrl", out var returnUri); + NavigationManager.NavigateTo(!string.IsNullOrEmpty(returnUri) ? returnUri : "counter"); + } + + private UserRegistry? AuthenticateByUserNamePassword() + { + authenticationRequestDto.RemoteIp = HttpContextAccessor.HttpContext?.Connection.RemoteIpAddress?.ToString(); + authenticationRequestDto.LocalIp = HttpContextAccessor.HttpContext?.Connection.LocalIpAddress?.ToString(); + authenticationRequestDto.UserAgent = HttpContextAccessor.HttpContext?.Request.Headers.UserAgent.ToString(); + + return service?.AuthenticateByUserNamePassword(authenticationRequestDto, + DynamicEnvironment.AppSettings("PasswordHashingKey")); + } + + private async Task CreateAuthenticationAndClaims(UserRegistry userRegistry) + { + if (authenticationRequestDto.UserName == null) throw new AuthenticationAndClaimsCreationException(); + + var authenticationState = await ((CustomAuthenticationStateProvider)AuthenticationStateProvider) + .ChangeUser(authenticationRequestDto.UserName, userRegistry.Id.ToString(), + userRegistry.RoleRegistryId.ToString()); + + var user = authenticationState.User; + var permissionValidation = new Data.Security.PermissionValidation(); + var permissionValidationDto = await permissionValidation.GetPermissionsAsync(dbContext, user.Identity?.Name); + + var claims = permissionValidationDto.Permissions + .Select(permission => new Claim("Permission", permission.ToString())).ToList(); + claims.AddRange(user.Claims); + + user.AddIdentity(new ClaimsIdentity(claims)); + } } \ No newline at end of file diff --git a/Jube.Blazor/Components/Pages/Counter.razor b/Jube.Blazor/Components/Pages/Counter.razor index a35b2ae5..a6d49fa1 100644 --- a/Jube.Blazor/Components/Pages/Counter.razor +++ b/Jube.Blazor/Components/Pages/Counter.razor @@ -1,10 +1,23 @@ +@* Copyright (C) 2022-present Jube Holdings Limited. + * + * This file is part of Jube™ software. + * + * Jube™ is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * Jube™ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License along with Jube™. If not, + * see . + *@ + @page "/" @page "/counter" @using Microsoft.AspNetCore.Authorization Counter -@attribute [Authorize] +@attribute [Authorize(Policy = "HasCounterPermission")]

Counter

diff --git a/Jube.Blazor/Components/_Imports.razor b/Jube.Blazor/Components/_Imports.razor index 74d3d8d5..3f6c045d 100644 --- a/Jube.Blazor/Components/_Imports.razor +++ b/Jube.Blazor/Components/_Imports.razor @@ -1,3 +1,16 @@ +@* Copyright (C) 2022-present Jube Holdings Limited. + * + * This file is part of Jube™ software. + * + * Jube™ is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * Jube™ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License along with Jube™. If not, + * see . + *@ + @using System.Net.Http @using System.Net.Http.Json @using Microsoft.AspNetCore.Components.Authorization @@ -10,4 +23,7 @@ @using Jube.Blazor @using Jube.Blazor.Components @using Radzen -@using Radzen.Blazor \ No newline at end of file +@using Radzen.Blazor +@using Blazored.FluentValidation +@using Microsoft.Extensions.Localization +@using Jube.Blazor.Resources \ No newline at end of file diff --git a/Jube.Blazor/Jube.Blazor.csproj b/Jube.Blazor/Jube.Blazor.csproj index 8308a419..e43135a5 100644 --- a/Jube.Blazor/Jube.Blazor.csproj +++ b/Jube.Blazor/Jube.Blazor.csproj @@ -8,11 +8,15 @@ + + + + @@ -20,4 +24,17 @@
+ + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + diff --git a/Jube.Blazor/Program.cs b/Jube.Blazor/Program.cs index 6d7be303..06bdb7fc 100644 --- a/Jube.Blazor/Program.cs +++ b/Jube.Blazor/Program.cs @@ -1,31 +1,75 @@ +/* Copyright (C) 2022-present Jube Holdings Limited. + * + * This file is part of Jube™ software. + * + * Jube™ is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * Jube™ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License along with Jube™. If not, + * see . + */ + +using FluentMigrator.Runner; +using FluentValidation; using Microsoft.AspNetCore.Identity; using Jube.Blazor.Components; using Jube.Blazor.Components.Code; +using Jube.DynamicEnvironment; +using Jube.Migrations.Baseline; +using log4net; using Microsoft.AspNetCore.Components.Authorization; using Radzen; var builder = WebApplication.CreateBuilder(args); +var log = LogManager.GetLogger(typeof(ILog)); +builder.Services.AddSingleton(log); + +var dynamicEnvironment = new DynamicEnvironment(log); +builder.Services.AddSingleton(dynamicEnvironment); + builder.Services.AddRazorComponents() .AddInteractiveServerComponents() .AddInteractiveWebAssemblyComponents(); builder.Services.AddRadzenComponents(); +builder.Services.AddLocalization(); + builder.Services.AddCascadingAuthenticationState(); builder.Services.AddAuthentication(options => { options.DefaultScheme = IdentityConstants.ApplicationScheme; options.DefaultSignInScheme = IdentityConstants.ExternalScheme; - }) .AddIdentityCookies(); +builder.Services.AddAuthorizationBuilder() + .AddPolicy("HasCounterPermission", p => p.RequireClaim("Permission", "1")); + builder.Services.AddScoped(); +builder.Services + .AddValidatorsFromAssemblyContaining(); + builder.Services.AddDatabaseDeveloperPageExceptionFilter(); +builder.Services.AddHttpContextAccessor(); + +if (dynamicEnvironment.AppSettings("EnableMigration").Equals("True", StringComparison.OrdinalIgnoreCase)) +{ + RunFluentMigrator(dynamicEnvironment); + + var cacheConnectionString = dynamicEnvironment.AppSettings("CacheConnectionString"); + if (cacheConnectionString != null) + { + RunFluentMigrator(dynamicEnvironment); + } +} + var app = builder.Build(); if (app.Environment.IsDevelopment()) @@ -47,4 +91,27 @@ .AddInteractiveServerRenderMode() .AddInteractiveWebAssemblyRenderMode(); -app.Run(); \ No newline at end of file +app.UseRequestLocalization(new RequestLocalizationOptions() + .SetDefaultCulture("en-US") + .AddSupportedCultures(["en-US"]) + .AddSupportedUICultures(["en-US"])); + +app.Run(); +return; + +void RunFluentMigrator(DynamicEnvironment dynamicEnvironment) +{ +#pragma warning disable ASP0000 + var serviceCollection = new ServiceCollection().AddFluentMigratorCore() + .AddSingleton(dynamicEnvironment) + .ConfigureRunner(rb => rb + .AddPostgres11_0() + .WithGlobalConnectionString(dynamicEnvironment.AppSettings("ConnectionString")) + .ScanIn(typeof(AddActivationWatcherTableIndex).Assembly).For.Migrations()) + .BuildServiceProvider(false); +#pragma warning restore ASP0000 + + using var scope = serviceCollection.CreateScope(); + var runner = serviceCollection.GetRequiredService(); + runner.MigrateUp(); +} \ No newline at end of file diff --git a/Jube.Blazor/Properties/launchSettings.json b/Jube.Blazor/Properties/launchSettings.json deleted file mode 100644 index 900ce81b..00000000 --- a/Jube.Blazor/Properties/launchSettings.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:31533", - "sslPort": 44328 - } - }, - "profiles": { - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "applicationUrl": "http://localhost:5131", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "https": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "applicationUrl": "https://localhost:7075;http://localhost:5131", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } - } diff --git a/Jube.Blazor/Resources/Resources.Designer.cs b/Jube.Blazor/Resources/Resources.Designer.cs new file mode 100644 index 00000000..5e813fb0 --- /dev/null +++ b/Jube.Blazor/Resources/Resources.Designer.cs @@ -0,0 +1,235 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Jube.Blazor.Resources { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Jube.Blazor.Resources.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Your password must contain at least one number.. + /// + internal static string _10 { + get { + return ResourceManager.GetString("10", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Your password must contain at least one lowercase letter.. + /// + internal static string _2 { + get { + return ResourceManager.GetString("2", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Your password must contain at least one uppercase letter.. + /// + internal static string _3 { + get { + return ResourceManager.GetString("3", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Your password length must not exceed 16.. + /// + internal static string _4 { + get { + return ResourceManager.GetString("4", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Your password length must be at least 8.. + /// + internal static string _5 { + get { + return ResourceManager.GetString("5", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Please enter a password.. + /// + internal static string _6 { + get { + return ResourceManager.GetString("6", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Please enter a user name.. + /// + internal static string _7 { + get { + return ResourceManager.GetString("7", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Passwords do not match.. + /// + internal static string Jube_Validations_Authentication_AuthenticationRequestDtoValidator_PasswordNotMatch { + get { + return ResourceManager.GetString("Jube.Validations.Authentication.AuthenticationRequestDtoValidator.PasswordNotMatc" + + "h", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Your password must contain at least one (!? *.).. + /// + internal static string Jube_Validations_Authentication_AuthenticationRequestDtoValidator_SpecialChracter { + get { + return ResourceManager.GetString("Jube.Validations.Authentication.AuthenticationRequestDtoValidator.SpecialChracter" + + "", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Verify Credentials. + /// + internal static string Login_VerifyCredentials { + get { + return ResourceManager.GetString("Login:VerifyCredentials", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Change Credentials. + /// + internal static string Pages_Account_Login_ChangeCredentials { + get { + return ResourceManager.GetString("Pages:Account:Login:ChangeCredentials", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid Credentials. Please try again of contact your administrator.. + /// + internal static string Pages_Account_Login_InvalidCredentialsTryAgain { + get { + return ResourceManager.GetString("Pages:Account:Login:InvalidCredentialsTryAgain", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Login. + /// + internal static string Pages_Account_Login_Login { + get { + return ResourceManager.GetString("Pages:Account:Login:Login", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Password expired and must be changed.. + /// + internal static string Pages_Account_Login_MustChangePassword { + get { + return ResourceManager.GetString("Pages:Account:Login:MustChangePassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to New Password. + /// + internal static string Pages_Account_Login_NewPassword { + get { + return ResourceManager.GetString("Pages:Account:Login:NewPassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Password. + /// + internal static string Pages_Account_Login_Password { + get { + return ResourceManager.GetString("Pages:Account:Login:Password", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Repeat New Password. + /// + internal static string Pages_Account_Login_RepeatNewPassword { + get { + return ResourceManager.GetString("Pages:Account:Login:RepeatNewPassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to User Name. + /// + internal static string Pages_Account_Login_UserName { + get { + return ResourceManager.GetString("Pages:Account:Login:UserName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Verify Credentials. + /// + internal static string Pages_Account_Login_VerifyCredentials { + get { + return ResourceManager.GetString("Pages:Account:Login:VerifyCredentials", resourceCulture); + } + } + } +} diff --git a/Jube.Blazor/Resources/Resources.resx b/Jube.Blazor/Resources/Resources.resx new file mode 100644 index 00000000..84e99de5 --- /dev/null +++ b/Jube.Blazor/Resources/Resources.resx @@ -0,0 +1,78 @@ + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Verify Credentials + + + Login + + + Verify Credentials + + + User Name + + + Password + + + Change Credentials + + + New Password + + + Repeat New Password + + + Password expired and must be changed. + + + Invalid Credentials. Please try again of contact your administrator. + + + Passwords do not match. + + + Your password must contain at least one (!? *.). + + + Your password must contain at least one lowercase letter. + + + Your password must contain at least one uppercase letter. + + + Your password length must not exceed 16. + + + Your password length must be at least 8. + + + Please enter a password. + + + Please enter a user name. + + + Your password must contain at least one number. + + \ No newline at end of file diff --git a/Jube.Service/Authentication/Authentication.cs b/Jube.Service/Authentication/Authentication.cs new file mode 100644 index 00000000..991cd87c --- /dev/null +++ b/Jube.Service/Authentication/Authentication.cs @@ -0,0 +1,130 @@ +/* Copyright (C) 2022-present Jube Holdings Limited. + * + * This file is part of Jube™ software. + * + * Jube™ is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * Jube™ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License along with Jube™. If not, + * see . + */ + +using Jube.Data.Context; +using Jube.Data.Poco; +using Jube.Data.Repository; +using Jube.Data.Security; +using Jube.Service.Dto.Authentication; +using Jube.Service.Exceptions.Authentication; + +namespace Jube.Service.Authentication; + +public class Authentication(DbContext dbContext) +{ + public UserRegistry AuthenticateByUserNamePassword(AuthenticationRequestDto authenticationRequestDto, + string? passwordHashingKey) + { + var userRegistryRepository = new UserRegistryRepository(dbContext); + var userRegistry = userRegistryRepository.GetByUserName(authenticationRequestDto.UserName); + + var userLogin = new UserLogin + { + RemoteIp = authenticationRequestDto.RemoteIp, + LocalIp = authenticationRequestDto.UserAgent + }; + + if (userRegistry == null) + { + LogLoginFailed(userLogin, authenticationRequestDto.UserName ?? "", 1); + throw new NoUserException(); + } + + if (userRegistry.Active != 1) + { + LogLoginFailed(userLogin, userRegistry.Name, 2); + throw new NotActiveException(); + } + + if (userRegistry.PasswordLocked == 1) + { + LogLoginFailed(userLogin, userRegistry.Name, 3); + throw new PasswordLockedException(); + } + + if (!userRegistry.PasswordExpiryDate.HasValue + || string.IsNullOrEmpty(userRegistry.Password) + || !userRegistry.PasswordCreatedDate.HasValue) + { + LogLoginFailed(userLogin, userRegistry.Name, 4); + throw new PasswordNewMustChangeException(); + } + + if (!HashPassword.Verify(userRegistry.Password, authenticationRequestDto.Password, passwordHashingKey)) + { + userRegistryRepository.IncrementFailedPassword(userRegistry.Id); + + if (userRegistry.FailedPasswordCount > 8) + { + userRegistryRepository.SetLocked(userRegistry.Id); + } + + LogLoginFailed(userLogin, userRegistry.Name, 5); + + throw new BadCredentialsException(); + } + + if (!string.IsNullOrEmpty(authenticationRequestDto.NewPassword)) + { + var hashedPassword = HashPassword.GenerateHash(authenticationRequestDto.NewPassword, passwordHashingKey); + + userRegistryRepository.SetPassword(userRegistry.Id, hashedPassword, DateTime.Now.AddDays(90)); + } + else + { + if (!(DateTime.Now <= userRegistry.PasswordExpiryDate.Value)) + throw new PasswordExpiredException(); + } + + LogLoginSuccess(userLogin, userRegistry.Name); + + if (userRegistry.FailedPasswordCount > 0) + { + userRegistryRepository.ResetFailedPasswordCount(userRegistry.Id); + } + + return userRegistry; + } + + public void ChangePassword(string? userName, ChangePasswordRequestDto changePasswordRequestDto, + string? passwordHashingKey) + { + var userRegistryRepository = new UserRegistryRepository(dbContext); + var userRegistry = userRegistryRepository.GetByUserName(userName); + + if (!HashPassword.Verify(userRegistry.Password, + changePasswordRequestDto.Password, passwordHashingKey)) + { + throw new BadCredentialsException(); + } + + var hashedPassword = HashPassword.GenerateHash(changePasswordRequestDto.NewPassword, passwordHashingKey); + + userRegistryRepository.SetPassword(userRegistry.Id, hashedPassword, DateTime.Now.AddDays(90)); + } + + private void LogLoginFailed(UserLogin userLogin, string createdUser, int failureTypeId) + { + var userLoginRepository = new UserLoginRepository(dbContext, createdUser); + userLogin.Failed = 1; + userLogin.FailureTypeId = failureTypeId; + userLoginRepository.Insert(userLogin); + } + + private void LogLoginSuccess(UserLogin userLogin, string createdUser) + { + var userLoginRepository = new UserLoginRepository(dbContext, createdUser); + userLogin.Failed = 0; + userLoginRepository.Insert(userLogin); + } +} \ No newline at end of file diff --git a/Jube.Service/Dto/Authentication/AuthenticationRequestDto.cs b/Jube.Service/Dto/Authentication/AuthenticationRequestDto.cs new file mode 100644 index 00000000..92155f77 --- /dev/null +++ b/Jube.Service/Dto/Authentication/AuthenticationRequestDto.cs @@ -0,0 +1,27 @@ +/* Copyright (C) 2022-present Jube Holdings Limited. + * + * This file is part of Jube™ software. + * + * Jube™ is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * Jube™ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License along with Jube™. If not, + * see . + */ + +namespace Jube.Service.Dto.Authentication +{ + public class AuthenticationRequestDto + { + public string? UserName { get; set; } + public string? Password { get; set; } + public string? NewPassword { get; set; } + public string? RepeatNewPassword { get; set; } + public string? RemoteIp { get; set; } + public string? LocalIp { get; set; } + public string? UserAgent { get; set; } + public bool PasswordChangeState { get; set; } + } +} \ No newline at end of file diff --git a/Jube.App/Dto/Authentication/AuthenticationResponseDto.cs b/Jube.Service/Dto/Authentication/AuthenticationResponseDto.cs similarity index 89% rename from Jube.App/Dto/Authentication/AuthenticationResponseDto.cs rename to Jube.Service/Dto/Authentication/AuthenticationResponseDto.cs index 37054cc0..c6ea0003 100644 --- a/Jube.App/Dto/Authentication/AuthenticationResponseDto.cs +++ b/Jube.Service/Dto/Authentication/AuthenticationResponseDto.cs @@ -11,13 +11,11 @@ * see . */ -using System; - -namespace Jube.App.Dto.Authentication +namespace Jube.Service.Dto.Authentication { public class AuthenticationResponseDto { - public string Token { get; set; } + public string? Token { get; set; } public DateTime Expiration { get; set; } } } \ No newline at end of file diff --git a/Jube.App/Dto/Authentication/ChangePasswordRequestDto.cs b/Jube.Service/Dto/Authentication/ChangePasswordRequestDto.cs similarity index 85% rename from Jube.App/Dto/Authentication/ChangePasswordRequestDto.cs rename to Jube.Service/Dto/Authentication/ChangePasswordRequestDto.cs index 23217156..74ab928e 100644 --- a/Jube.App/Dto/Authentication/ChangePasswordRequestDto.cs +++ b/Jube.Service/Dto/Authentication/ChangePasswordRequestDto.cs @@ -11,11 +11,11 @@ * see . */ -namespace Jube.App.Dto.Authentication +namespace Jube.Service.Dto.Authentication { public class ChangePasswordRequestDto { - public string Password { get; set; } - public string NewPassword { get; set; } + public string? Password { get; set; } + public string? NewPassword { get; set; } } } \ No newline at end of file diff --git a/Jube.Service/Exceptions/Authentication/AuthenticationAndClaimsCreationException.cs b/Jube.Service/Exceptions/Authentication/AuthenticationAndClaimsCreationException.cs new file mode 100644 index 00000000..d925c89d --- /dev/null +++ b/Jube.Service/Exceptions/Authentication/AuthenticationAndClaimsCreationException.cs @@ -0,0 +1,19 @@ +/* Copyright (C) 2022-present Jube Holdings Limited. + * + * This file is part of Jube™ software. + * + * Jube™ is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * Jube™ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License along with Jube™. If not, + * see . + */ + +namespace Jube.Service.Exceptions.Authentication; + +public class AuthenticationAndClaimsCreationException : Exception +{ + +} \ No newline at end of file diff --git a/Jube.Service/Exceptions/Authentication/BadCredentialsException.cs b/Jube.Service/Exceptions/Authentication/BadCredentialsException.cs new file mode 100644 index 00000000..d273063c --- /dev/null +++ b/Jube.Service/Exceptions/Authentication/BadCredentialsException.cs @@ -0,0 +1,19 @@ +/* Copyright (C) 2022-present Jube Holdings Limited. + * + * This file is part of Jube™ software. + * + * Jube™ is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * Jube™ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License along with Jube™. If not, + * see . + */ + +namespace Jube.Service.Exceptions.Authentication; + +public class BadCredentialsException : Exception +{ + +} \ No newline at end of file diff --git a/Jube.Service/Exceptions/Authentication/NoUserException.cs b/Jube.Service/Exceptions/Authentication/NoUserException.cs new file mode 100644 index 00000000..9dd445a5 --- /dev/null +++ b/Jube.Service/Exceptions/Authentication/NoUserException.cs @@ -0,0 +1,19 @@ +/* Copyright (C) 2022-present Jube Holdings Limited. + * + * This file is part of Jube™ software. + * + * Jube™ is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * Jube™ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License along with Jube™. If not, + * see . + */ + +namespace Jube.Service.Exceptions.Authentication; + +public class NoUserException : Exception +{ + +} \ No newline at end of file diff --git a/Jube.Service/Exceptions/Authentication/NotActiveException.cs b/Jube.Service/Exceptions/Authentication/NotActiveException.cs new file mode 100644 index 00000000..24a44650 --- /dev/null +++ b/Jube.Service/Exceptions/Authentication/NotActiveException.cs @@ -0,0 +1,19 @@ +/* Copyright (C) 2022-present Jube Holdings Limited. + * + * This file is part of Jube™ software. + * + * Jube™ is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * Jube™ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License along with Jube™. If not, + * see . + */ + +namespace Jube.Service.Exceptions.Authentication; + +public class NotActiveException : Exception +{ + +} \ No newline at end of file diff --git a/Jube.Service/Exceptions/Authentication/PasswordExpiredException.cs b/Jube.Service/Exceptions/Authentication/PasswordExpiredException.cs new file mode 100644 index 00000000..de1511f6 --- /dev/null +++ b/Jube.Service/Exceptions/Authentication/PasswordExpiredException.cs @@ -0,0 +1,19 @@ +/* Copyright (C) 2022-present Jube Holdings Limited. + * + * This file is part of Jube™ software. + * + * Jube™ is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * Jube™ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License along with Jube™. If not, + * see . + */ + +namespace Jube.Service.Exceptions.Authentication; + +public class PasswordExpiredException : Exception +{ + +} \ No newline at end of file diff --git a/Jube.Service/Exceptions/Authentication/PasswordLockedException.cs b/Jube.Service/Exceptions/Authentication/PasswordLockedException.cs new file mode 100644 index 00000000..0e47bee2 --- /dev/null +++ b/Jube.Service/Exceptions/Authentication/PasswordLockedException.cs @@ -0,0 +1,19 @@ +/* Copyright (C) 2022-present Jube Holdings Limited. + * + * This file is part of Jube™ software. + * + * Jube™ is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * Jube™ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License along with Jube™. If not, + * see . + */ + +namespace Jube.Service.Exceptions.Authentication; + +public class PasswordLockedException : Exception +{ + +} \ No newline at end of file diff --git a/Jube.Service/Exceptions/Authentication/PasswordNewMustChangeException.cs b/Jube.Service/Exceptions/Authentication/PasswordNewMustChangeException.cs new file mode 100644 index 00000000..3aa7e121 --- /dev/null +++ b/Jube.Service/Exceptions/Authentication/PasswordNewMustChangeException.cs @@ -0,0 +1,19 @@ +/* Copyright (C) 2022-present Jube Holdings Limited. + * + * This file is part of Jube™ software. + * + * Jube™ is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * Jube™ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License along with Jube™. If not, + * see . + */ + +namespace Jube.Service.Exceptions.Authentication; + +public class PasswordNewMustChangeException : Exception +{ + +} \ No newline at end of file diff --git a/Jube.Service/Jube.Service.csproj b/Jube.Service/Jube.Service.csproj new file mode 100644 index 00000000..0835476b --- /dev/null +++ b/Jube.Service/Jube.Service.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + diff --git a/Jube.App/Validators/Authentication/AuthenticationRequestDtoValidator.cs b/Jube.Validations/Authentication/AuthenticationRequestDtoValidator.cs similarity index 75% rename from Jube.App/Validators/Authentication/AuthenticationRequestDtoValidator.cs rename to Jube.Validations/Authentication/AuthenticationRequestDtoValidator.cs index 22dfd009..f4da3a97 100644 --- a/Jube.App/Validators/Authentication/AuthenticationRequestDtoValidator.cs +++ b/Jube.Validations/Authentication/AuthenticationRequestDtoValidator.cs @@ -2,19 +2,19 @@ * * This file is part of Jube™ software. * - * Jube™ is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License + * Jube™ is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - * Jube™ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * Jube™ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. - * You should have received a copy of the GNU Affero General Public License along with Jube™. If not, + * You should have received a copy of the GNU Affero General Public License along with Jube™. If not, * see . */ using FluentValidation; -using Jube.App.Dto.Authentication; +using Jube.Service.Dto.Authentication; -namespace Jube.App.Validators.Authentication +namespace Jube.Validations.Authentication { public class AuthenticationRequestDtoValidator : AbstractValidator { @@ -29,7 +29,10 @@ public AuthenticationRequestDtoValidator() .Matches(@"[a-z]+").WithMessage("Your password must contain at least one lowercase letter.") .Matches(@"[0-9]+").WithMessage("Your password must contain at least one number.") .Matches(@"[\!\?\*\.]+").WithMessage("Your password must contain at least one (!? *.).") - .When(x => !string.IsNullOrEmpty(x.NewPassword)); + .Equal(e => e.RepeatNewPassword).WithMessage("Repeat New Password.") + .When(w => !string.IsNullOrEmpty(w.NewPassword)); + RuleFor(p => p.NewPassword) + .NotEmpty().When(w => w.PasswordChangeState); } } } \ No newline at end of file diff --git a/Jube.App/Validators/Authentication/ChangePasswordRequestDtoValidator.cs b/Jube.Validations/Authentication/ChangePasswordRequestDtoValidator.cs similarity index 95% rename from Jube.App/Validators/Authentication/ChangePasswordRequestDtoValidator.cs rename to Jube.Validations/Authentication/ChangePasswordRequestDtoValidator.cs index 07c8908e..690cfa50 100644 --- a/Jube.App/Validators/Authentication/ChangePasswordRequestDtoValidator.cs +++ b/Jube.Validations/Authentication/ChangePasswordRequestDtoValidator.cs @@ -12,9 +12,9 @@ */ using FluentValidation; -using Jube.App.Dto.Authentication; +using Jube.Service.Dto.Authentication; -namespace Jube.App.Validators.Authentication +namespace Jube.Validations.Authentication { public class ChangePasswordRequestDtoValidator : AbstractValidator { diff --git a/Jube.Validations/Jube.Validations.csproj b/Jube.Validations/Jube.Validations.csproj new file mode 100644 index 00000000..45e5252f --- /dev/null +++ b/Jube.Validations/Jube.Validations.csproj @@ -0,0 +1,36 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + ..\..\..\..\..\Program Files\dotnet\shared\Microsoft.AspNetCore.App\8.0.1\Microsoft.Extensions.Localization.Abstractions.dll + + + + + + True + True + Resources.resx + + + True + True + Resources.resx + + + + diff --git a/Jube.sln b/Jube.sln index 5ed68735..68b77d00 100644 --- a/Jube.sln +++ b/Jube.sln @@ -34,6 +34,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jube.Extensions", "Jube.Ext EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jube.Blazor", "Jube.Blazor\Jube.Blazor.csproj", "{6CFE6CC4-C251-40C7-A6C0-126914B9F086}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jube.Service", "Jube.Service\Jube.Service.csproj", "{07CE278B-820F-496B-8461-683A5558E03A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jube.Validations", "Jube.Validations\Jube.Validations.csproj", "{6080B03C-B1F7-4AEB-A046-2BA8FEA634BA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -112,6 +116,14 @@ Global {6CFE6CC4-C251-40C7-A6C0-126914B9F086}.Debug|Any CPU.Build.0 = Debug|Any CPU {6CFE6CC4-C251-40C7-A6C0-126914B9F086}.Release|Any CPU.ActiveCfg = Release|Any CPU {6CFE6CC4-C251-40C7-A6C0-126914B9F086}.Release|Any CPU.Build.0 = Release|Any CPU + {07CE278B-820F-496B-8461-683A5558E03A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07CE278B-820F-496B-8461-683A5558E03A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07CE278B-820F-496B-8461-683A5558E03A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07CE278B-820F-496B-8461-683A5558E03A}.Release|Any CPU.Build.0 = Release|Any CPU + {6080B03C-B1F7-4AEB-A046-2BA8FEA634BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6080B03C-B1F7-4AEB-A046-2BA8FEA634BA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6080B03C-B1F7-4AEB-A046-2BA8FEA634BA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6080B03C-B1F7-4AEB-A046-2BA8FEA634BA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution EndGlobalSection