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