diff --git a/Directory.Packages.props b/Directory.Packages.props index 98a3848..822d193 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -31,7 +31,7 @@ - + @@ -39,13 +39,13 @@ - + - + - + @@ -63,8 +63,8 @@ - - - + + + diff --git a/InvoiceReminder.API/Endpoints/GoogleOAuthEndpoints.cs b/InvoiceReminder.API/Endpoints/GoogleOAuthEndpoints.cs index ec4cc91..d94a412 100644 --- a/InvoiceReminder.API/Endpoints/GoogleOAuthEndpoints.cs +++ b/InvoiceReminder.API/Endpoints/GoogleOAuthEndpoints.cs @@ -29,6 +29,7 @@ private static void MapGetAuthUrl(RouteGroupBuilder endpoint) .RequireAuthorization() .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status500InternalServerError); } @@ -64,6 +65,7 @@ private static void MapRevoke(RouteGroupBuilder endpoint) .RequireAuthorization() .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status500InternalServerError); } } diff --git a/InvoiceReminder.API/Endpoints/InvoiceEndpoints.cs b/InvoiceReminder.API/Endpoints/InvoiceEndpoints.cs index 58c31d5..f4b8a94 100644 --- a/InvoiceReminder.API/Endpoints/InvoiceEndpoints.cs +++ b/InvoiceReminder.API/Endpoints/InvoiceEndpoints.cs @@ -34,6 +34,7 @@ private static void MapGetInvoices(RouteGroupBuilder endpoint) .RequireAuthorization() .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status500InternalServerError); } @@ -51,6 +52,7 @@ private static void MapGetInvoice(RouteGroupBuilder endpoint) .RequireAuthorization() .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status404NotFound) .Produces(StatusCodes.Status500InternalServerError); } @@ -70,6 +72,7 @@ private static void MapGetInvoiceByBarcode(RouteGroupBuilder endpoint) .RequireAuthorization() .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status404NotFound) .Produces(StatusCodes.Status500InternalServerError); } @@ -89,6 +92,7 @@ private static void MapCreateInvoice(RouteGroupBuilder endpoint) .RequireAuthorization() .Produces(StatusCodes.Status201Created) .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status500InternalServerError); } @@ -107,6 +111,7 @@ private static void MapUpdateInvoice(RouteGroupBuilder endpoint) .RequireAuthorization() .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status500InternalServerError); } @@ -125,6 +130,7 @@ private static void MapDeleteInvoice(RouteGroupBuilder endpoint) .RequireAuthorization() .Produces(StatusCodes.Status204NoContent) .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status500InternalServerError); } } diff --git a/InvoiceReminder.API/Endpoints/JobScheduleEndpoints.cs b/InvoiceReminder.API/Endpoints/JobScheduleEndpoints.cs index ed9e853..10e9199 100644 --- a/InvoiceReminder.API/Endpoints/JobScheduleEndpoints.cs +++ b/InvoiceReminder.API/Endpoints/JobScheduleEndpoints.cs @@ -34,6 +34,7 @@ private static void MapGetJobSchedules(RouteGroupBuilder endpoint) .RequireAuthorization() .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status500InternalServerError); } @@ -51,8 +52,9 @@ private static void MapGetJobSchedule(RouteGroupBuilder endpoint) .WithName("GetJobSchedule") .RequireAuthorization() .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status404NotFound) .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status404NotFound) .Produces(StatusCodes.Status500InternalServerError); } @@ -70,8 +72,9 @@ private static void MapGetJobScheduleByUserId(RouteGroupBuilder endpoint) .WithName("GetJobScheduleByUserId") .RequireAuthorization() .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status404NotFound) .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status404NotFound) .Produces(StatusCodes.Status500InternalServerError); } @@ -91,6 +94,7 @@ private static void MapCreateJobSchedule(RouteGroupBuilder endpoint) .RequireAuthorization() .Produces(StatusCodes.Status201Created) .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status500InternalServerError); } @@ -110,6 +114,7 @@ private static void MapUpdateJobSchedule(RouteGroupBuilder endpoint) .RequireAuthorization() .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status500InternalServerError); } @@ -129,6 +134,7 @@ private static void MapDeleteJobSchedule(RouteGroupBuilder endpoint) .RequireAuthorization() .Produces(StatusCodes.Status204NoContent) .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status500InternalServerError); } } diff --git a/InvoiceReminder.API/Endpoints/LoginEndpoints.cs b/InvoiceReminder.API/Endpoints/LoginEndpoints.cs index 9f085ea..43d3254 100644 --- a/InvoiceReminder.API/Endpoints/LoginEndpoints.cs +++ b/InvoiceReminder.API/Endpoints/LoginEndpoints.cs @@ -31,7 +31,13 @@ private static void MapLogin(IEndpointRouteBuilder endpoints) var result = await appService.ValidateUserPasswordAsync(request.Email, request.Password, ct); return result.IsSuccess - ? Results.Ok(jwtProvider.Generate(new UserClaims { Id = result.Value.Id, Email = result.Value.Email })) + ? Results.Ok(jwtProvider.Generate(new UserClaims + { + Id = result.Value.Id, + Name = result.Value.Name, + Email = result.Value.Email, + TelegramChatId = result.Value.TelegramChatId + })) : Results.Unauthorized(); }) .WithName("Login") diff --git a/InvoiceReminder.API/Endpoints/ScanEmailDefinitionEndpoints.cs b/InvoiceReminder.API/Endpoints/ScanEmailDefinitionEndpoints.cs index 6986288..bd65d6c 100644 --- a/InvoiceReminder.API/Endpoints/ScanEmailDefinitionEndpoints.cs +++ b/InvoiceReminder.API/Endpoints/ScanEmailDefinitionEndpoints.cs @@ -35,6 +35,7 @@ private static void MapGetScanEmailDefinitions(RouteGroupBuilder endpoint) .RequireAuthorization() .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status500InternalServerError); } @@ -53,6 +54,7 @@ private static void MapGetScanEmailDefinition(RouteGroupBuilder endpoint) .RequireAuthorization() .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status404NotFound) .Produces(StatusCodes.Status500InternalServerError); } @@ -72,6 +74,7 @@ private static void MapGetByUserId(RouteGroupBuilder endpoint) .RequireAuthorization() .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status404NotFound) .Produces(StatusCodes.Status500InternalServerError); } @@ -91,6 +94,7 @@ private static void MapGetBySenderEmailAddress(RouteGroupBuilder endpoint) .RequireAuthorization() .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status404NotFound) .Produces(StatusCodes.Status500InternalServerError); } @@ -111,6 +115,7 @@ private static void MapCreateScanEmailDefinition(RouteGroupBuilder endpoint) .RequireAuthorization() .Produces(StatusCodes.Status201Created) .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status500InternalServerError); } @@ -130,6 +135,7 @@ private static void MapUpdateScanEmailDefinition(RouteGroupBuilder endpoint) .RequireAuthorization() .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status500InternalServerError); } @@ -149,6 +155,7 @@ private static void MapDeleteScanEmailDefinition(RouteGroupBuilder endpoint) .RequireAuthorization() .Produces(StatusCodes.Status204NoContent) .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status500InternalServerError); } } diff --git a/InvoiceReminder.API/Endpoints/SendMessageEndpoints.cs b/InvoiceReminder.API/Endpoints/SendMessageEndpoints.cs index a52811d..2f84fdd 100644 --- a/InvoiceReminder.API/Endpoints/SendMessageEndpoints.cs +++ b/InvoiceReminder.API/Endpoints/SendMessageEndpoints.cs @@ -30,6 +30,7 @@ private static void MapSendMessage(RouteGroupBuilder endpoint) .WithName("SendMessage") .RequireAuthorization() .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status500InternalServerError); } diff --git a/InvoiceReminder.API/Endpoints/UserEndpoints.cs b/InvoiceReminder.API/Endpoints/UserEndpoints.cs index fd6b971..3b0985d 100644 --- a/InvoiceReminder.API/Endpoints/UserEndpoints.cs +++ b/InvoiceReminder.API/Endpoints/UserEndpoints.cs @@ -18,6 +18,7 @@ public void RegisterEndpoints(IEndpointRouteBuilder endpoints) MapCreateUser(endpoint); MapCreateUsers(endpoint); MapUpdateUser(endpoint); + MapUpdateBasicUserInfo(endpoint); MapDeleteUser(endpoint); } @@ -35,6 +36,7 @@ private static void MapGetUsers(RouteGroupBuilder endpoint) .RequireAuthorization() .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status500InternalServerError); } @@ -51,6 +53,8 @@ private static void MapGetUser(RouteGroupBuilder endpoint) .WithName("GetUser") .RequireAuthorization() .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status404NotFound) .Produces(StatusCodes.Status500InternalServerError); } @@ -69,6 +73,8 @@ private static void MapGetUserByEmail(RouteGroupBuilder endpoint) .WithName("GetUserByEmail") .RequireAuthorization() .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status404NotFound) .Produces(StatusCodes.Status500InternalServerError); } @@ -85,7 +91,7 @@ private static void MapCreateUser(RouteGroupBuilder endpoint) : Results.Problem(result.Error); }) .WithName("CreateUser") - .RequireAuthorization() + .AllowAnonymous() .Produces(StatusCodes.Status201Created) .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status500InternalServerError); @@ -107,6 +113,7 @@ private static void MapCreateUsers(RouteGroupBuilder endpoint) .RequireAuthorization() .Produces(StatusCodes.Status201Created) .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status500InternalServerError); } @@ -125,6 +132,26 @@ private static void MapUpdateUser(RouteGroupBuilder endpoint) .RequireAuthorization() .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status500InternalServerError); + } + + private static void MapUpdateBasicUserInfo(RouteGroupBuilder endpoint) + { + _ = endpoint.MapPatch("/", + async (IUserAppService appService, [FromBody] UserViewModel viewModel, CancellationToken ct) => + { + var result = await appService.UpdateBasicUserInfoAsync(viewModel, ct); + + return result.IsSuccess + ? Results.Ok(result.Value) + : Results.Problem(result.Error); + }) + .WithName("UpdateBasicUserInfo") + .RequireAuthorization() + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status500InternalServerError); } @@ -143,6 +170,7 @@ private static void MapDeleteUser(RouteGroupBuilder endpoint) .RequireAuthorization() .Produces(StatusCodes.Status204NoContent) .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status500InternalServerError); } } diff --git a/InvoiceReminder.API/Endpoints/UserPasswordEndpoints.cs b/InvoiceReminder.API/Endpoints/UserPasswordEndpoints.cs index 997330e..fbe8c57 100644 --- a/InvoiceReminder.API/Endpoints/UserPasswordEndpoints.cs +++ b/InvoiceReminder.API/Endpoints/UserPasswordEndpoints.cs @@ -14,6 +14,7 @@ public void RegisterEndpoints(IEndpointRouteBuilder endpoints) MapCreateUserPassword(endpoint); MapCreateUsersPassword(endpoint); + MapChangeUserPassword(endpoint); MapDeleteUserPassword(endpoint); MapUpdateUserPassword(endpoint); } @@ -33,6 +34,7 @@ private static void MapCreateUserPassword(RouteGroupBuilder endpoint) .RequireAuthorization() .Produces(StatusCodes.Status201Created) .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status500InternalServerError); } @@ -52,6 +54,27 @@ private static void MapCreateUsersPassword(RouteGroupBuilder endpoint) .RequireAuthorization() .Produces(StatusCodes.Status201Created) .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status500InternalServerError); + } + + private static void MapChangeUserPassword(RouteGroupBuilder endpoint) + { + _ = endpoint.MapPatch("/", + async (IUserPasswordAppService appService, [FromBody] UserPasswordViewModel viewModel, + CancellationToken ct) => + { + var result = await appService.ChangePasswordAsync(viewModel, ct); + + return result.IsSuccess + ? Results.Ok(result.Value) + : Results.Problem(result.Error); + }) + .WithName("ChangeUserPassword") + .RequireAuthorization() + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status500InternalServerError); } @@ -71,6 +94,7 @@ private static void MapUpdateUserPassword(RouteGroupBuilder endpoint) .RequireAuthorization() .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status500InternalServerError); } @@ -90,6 +114,7 @@ private static void MapDeleteUserPassword(RouteGroupBuilder endpoint) .RequireAuthorization() .Produces(StatusCodes.Status204NoContent) .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status500InternalServerError); } } diff --git a/InvoiceReminder.API/Program.cs b/InvoiceReminder.API/Program.cs index 40fc1dd..2c3a427 100644 --- a/InvoiceReminder.API/Program.cs +++ b/InvoiceReminder.API/Program.cs @@ -11,6 +11,21 @@ builder.Services.AddExceptionHandler(); builder.Services.AddInfrastructure(); builder.Services.AddOpenApi(opt => opt.AddDocumentTransformer()); + +builder.Services.AddCors(opt => + opt.AddPolicy("CorsPolicy", policy => + { + var allowedOrigins = builder.Configuration.GetSection("Cors:AllowedOrigins").Get() ?? []; + + _ = policy + .AllowAnyMethod() + .AllowCredentials() + .WithOrigins(allowedOrigins) + .WithHeaders("Content-Type", "Authorization"); + } + ) +); + builder.Services.AddHealthChecks().AddNpgSql ( connectionString: builder.Configuration.GetConnectionString("DataBaseConnection"), @@ -32,6 +47,7 @@ _ = app.UseHsts(); } +app.UseCors("CorsPolicy"); app.RegisterEndpoints(); app.UseExceptionHandler(); app.UseStatusCodePages(); diff --git a/InvoiceReminder.API/appsettings.json b/InvoiceReminder.API/appsettings.json index 766550e..37ff50a 100644 --- a/InvoiceReminder.API/appsettings.json +++ b/InvoiceReminder.API/appsettings.json @@ -8,6 +8,11 @@ "AllowedHosts": "*", "Timeout": 120, "ProviderName": "Npgsql", + "Cors": { + "AllowedOrigins": [ + "SECRET_ORIGIN" + ] + }, "appKeys": { "googleOauthClientId": "SECRET_CLIENT_ID", "googleOauthClientSecret": "SECRET_CLIENT_SECRET", diff --git a/InvoiceReminder.Application/AppServices/UserAppService.cs b/InvoiceReminder.Application/AppServices/UserAppService.cs index c42829e..c97d675 100644 --- a/InvoiceReminder.Application/AppServices/UserAppService.cs +++ b/InvoiceReminder.Application/AppServices/UserAppService.cs @@ -74,4 +74,21 @@ public async Task> ValidateUserPasswordAsync( ? Result.Failure("User not Found.") : Result.Success(entity.Adapt()); } + + public async Task> UpdateBasicUserInfoAsync( + UserViewModel viewModel, + CancellationToken cancellationToken = default) + { + if (viewModel is null) + { + return Result.Failure($"Parameter {nameof(viewModel)} was Null."); + } + + var entity = viewModel.Adapt(); + var result = await _repository.UpdateBasicUserInfoAsync(entity, cancellationToken); + + return result + ? Result.Success(result) + : Result.Failure("User not Found!"); + } } diff --git a/InvoiceReminder.Application/AppServices/UserPasswordAppService.cs b/InvoiceReminder.Application/AppServices/UserPasswordAppService.cs index 337bd2d..ba9f73f 100644 --- a/InvoiceReminder.Application/AppServices/UserPasswordAppService.cs +++ b/InvoiceReminder.Application/AppServices/UserPasswordAppService.cs @@ -15,8 +15,10 @@ public class UserPasswordAppService : BaseAppService("Security:ParallelismFactor"); _repository = repository; @@ -78,6 +80,31 @@ public override async Task> BulkInsertAsync( return Result.Success(result); } + public async Task> ChangePasswordAsync( + UserPasswordViewModel viewModel, + CancellationToken cancellationToken = default) + { + if (viewModel is null) + { + return Result.Failure("The provided object data was Null."); + } + + if (string.IsNullOrWhiteSpace(viewModel.PasswordHash)) + { + return Result.Failure("Password is required."); + } + + (var pHash, var pSalt) = viewModel.PasswordHash.HashPassword(_parallelismFactor); + viewModel.PasswordHash = pHash; + viewModel.PasswordSalt = pSalt; + + var result = await _repository.ChangePasswordAsync(viewModel.Adapt(), cancellationToken); + + return result + ? Result.Success(result) + : Result.Failure("Failed to change the password."); + } + public async Task> GetByUserIdAsync( Guid userId, CancellationToken cancellationToken = default) diff --git a/InvoiceReminder.Application/Interfaces/IUserAppService.cs b/InvoiceReminder.Application/Interfaces/IUserAppService.cs index 7c2fc1a..f22a857 100644 --- a/InvoiceReminder.Application/Interfaces/IUserAppService.cs +++ b/InvoiceReminder.Application/Interfaces/IUserAppService.cs @@ -7,6 +7,7 @@ namespace InvoiceReminder.Application.Interfaces; public interface IUserAppService : IBaseAppService { Task> GetByEmailAsync(string value, CancellationToken cancellationToken = default); + Task> UpdateBasicUserInfoAsync(UserViewModel viewModel, CancellationToken cancellationToken = default); Task> ValidateUserPasswordAsync(string email, string password, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default); } diff --git a/InvoiceReminder.Application/Interfaces/IUserPasswordAppService.cs b/InvoiceReminder.Application/Interfaces/IUserPasswordAppService.cs index 1823030..67e486d 100644 --- a/InvoiceReminder.Application/Interfaces/IUserPasswordAppService.cs +++ b/InvoiceReminder.Application/Interfaces/IUserPasswordAppService.cs @@ -7,4 +7,5 @@ namespace InvoiceReminder.Application.Interfaces; public interface IUserPasswordAppService : IBaseAppService { Task> GetByUserIdAsync(Guid userId, CancellationToken cancellationToken = default); + Task> ChangePasswordAsync(UserPasswordViewModel viewModel, CancellationToken cancellationToken = default); } diff --git a/InvoiceReminder.ArchitectureTests/InvoiceReminder.ArchitectureTests.csproj b/InvoiceReminder.ArchitectureTests/InvoiceReminder.ArchitectureTests.csproj index 70853db..803b79a 100644 --- a/InvoiceReminder.ArchitectureTests/InvoiceReminder.ArchitectureTests.csproj +++ b/InvoiceReminder.ArchitectureTests/InvoiceReminder.ArchitectureTests.csproj @@ -1,4 +1,4 @@ - + true diff --git a/InvoiceReminder.Authentication/Abstractions/UserClaims.cs b/InvoiceReminder.Authentication/Abstractions/UserClaims.cs index 7824f0f..0595e4b 100644 --- a/InvoiceReminder.Authentication/Abstractions/UserClaims.cs +++ b/InvoiceReminder.Authentication/Abstractions/UserClaims.cs @@ -3,5 +3,7 @@ namespace InvoiceReminder.Authentication.Abstractions; public record UserClaims { public required Guid Id { get; init; } + public required string Name { get; init; } public required string Email { get; init; } + public required long TelegramChatId { get; set; } } diff --git a/InvoiceReminder.Authentication/Jwt/JwtProvider.cs b/InvoiceReminder.Authentication/Jwt/JwtProvider.cs index 8c57650..36e7002 100644 --- a/InvoiceReminder.Authentication/Jwt/JwtProvider.cs +++ b/InvoiceReminder.Authentication/Jwt/JwtProvider.cs @@ -37,7 +37,9 @@ public JwtObject Generate(UserClaims user) var claims = new Claim[] { new (JwtRegisteredClaimNames.Sub, user.Id.ToString()), - new (JwtRegisteredClaimNames.Email, user.Email) + new (JwtRegisteredClaimNames.Name, user.Name), + new (JwtRegisteredClaimNames.Email, user.Email), + new ("telegramChatId", user.TelegramChatId.ToString()) }; var symmetricKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtOptions.SecretKey)); diff --git a/InvoiceReminder.Data/Interfaces/IUserPasswordRepository.cs b/InvoiceReminder.Data/Interfaces/IUserPasswordRepository.cs index e990407..1b4041f 100644 --- a/InvoiceReminder.Data/Interfaces/IUserPasswordRepository.cs +++ b/InvoiceReminder.Data/Interfaces/IUserPasswordRepository.cs @@ -5,4 +5,5 @@ namespace InvoiceReminder.Data.Interfaces; public interface IUserPasswordRepository : IBaseRepository { Task GetByUserIdAsync(Guid userId, CancellationToken cancellationToken = default); + Task ChangePasswordAsync(UserPassword userPassword, CancellationToken cancellationToken = default); } diff --git a/InvoiceReminder.Data/Interfaces/IUserRepository.cs b/InvoiceReminder.Data/Interfaces/IUserRepository.cs index c381d5c..86c3352 100644 --- a/InvoiceReminder.Data/Interfaces/IUserRepository.cs +++ b/InvoiceReminder.Data/Interfaces/IUserRepository.cs @@ -5,4 +5,5 @@ namespace InvoiceReminder.Data.Interfaces; public interface IUserRepository : IBaseRepository { Task GetByEmailAsync(string value, CancellationToken cancellationToken = default); + Task UpdateBasicUserInfoAsync(User user, CancellationToken cancellationToken = default); } diff --git a/InvoiceReminder.Data/Repository/UserPasswordRepository.cs b/InvoiceReminder.Data/Repository/UserPasswordRepository.cs index 9828bd5..fcb68f6 100644 --- a/InvoiceReminder.Data/Repository/UserPasswordRepository.cs +++ b/InvoiceReminder.Data/Repository/UserPasswordRepository.cs @@ -21,6 +21,58 @@ public UserPasswordRepository(CoreDbContext dbContext, ILogger ChangePasswordAsync(UserPassword userPassword, CancellationToken cancellationToken = default) + { + var result = false; + var query = """ + update invoice_reminder.user_password + set + password_hash = @passwordHash, + password_salt = @passwordSalt, + updated_at = now() + where user_id = @userId + """; + + try + { + var parameters = new DynamicParameters(new + { + passwordHash = userPassword.PasswordHash, + passwordSalt = userPassword.PasswordSalt, + userId = userPassword.UserId + }); + var command = new CommandDefinition(query, parameters, cancellationToken: cancellationToken); + + result = await _dbConnection.ExecuteAsync(command) > 0; + } + catch (OperationCanceledException ex) when (cancellationToken.IsCancellationRequested) + { + var method = $"{nameof(UserPasswordRepository)}.{nameof(ChangePasswordAsync)}"; + var contextualInfo = $"Method {method} execution was interrupted by a CancellationToken Request..."; + + if (_logger.IsEnabled(LogLevel.Warning)) + { + _logger.LogWarning(ex, LogExceptionMessage, contextualInfo, ex.Message); + } + + throw new OperationCanceledException(contextualInfo, ex, cancellationToken); + } + catch (Exception ex) + { + var method = $"{nameof(UserPasswordRepository)}.{nameof(ChangePasswordAsync)}"; + var contextualInfo = $"Exception raised while updating DB >> {method}(...)"; + + if (_logger.IsEnabled(LogLevel.Error)) + { + _logger.LogError(ex, LogExceptionMessage, contextualInfo, ex.Message); + } + + throw new DataLayerException(contextualInfo, ex); + } + + return result; + } + public async Task GetByUserIdAsync(Guid userId, CancellationToken cancellationToken = default) { UserPassword userPassword = default; diff --git a/InvoiceReminder.Data/Repository/UserRepository.cs b/InvoiceReminder.Data/Repository/UserRepository.cs index 0c2b93e..94d74a7 100644 --- a/InvoiceReminder.Data/Repository/UserRepository.cs +++ b/InvoiceReminder.Data/Repository/UserRepository.cs @@ -7,6 +7,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using System.Data; +using System.Data.Common; namespace InvoiceReminder.Data.Repository; @@ -145,4 +146,63 @@ public override async Task GetByIdAsync(Guid id, CancellationToken cancell return result.FirstOrDefault().Value; } + + public async Task UpdateBasicUserInfoAsync(User user, CancellationToken cancellationToken = default) + { + var result = false; + var query = """ + update invoice_reminder.user + set name = @Name, + email = @Email, + telegram_chat_id = @TelegramChatId, + updated_at = now() + where id = @Id + """; + + try + { + var parameters = new DynamicParameters(new { user.Name, user.Email, user.TelegramChatId, user.Id }); + var command = new CommandDefinition(query, parameters, cancellationToken: cancellationToken); + + result = await _dbConnection.ExecuteAsync(command) > 0; + } + catch (OperationCanceledException ex) when (cancellationToken.IsCancellationRequested) + { + var method = $"{nameof(UserRepository)}.{nameof(UpdateBasicUserInfoAsync)}"; + var contextualInfo = $"Method {method} execution was interrupted by a CancellationToken Request..."; + + if (_logger.IsEnabled(LogLevel.Warning)) + { + _logger.LogWarning(ex, LogExceptionMessage, contextualInfo, ex.Message); + } + + throw new OperationCanceledException(contextualInfo, ex, cancellationToken); + } + catch (DbException ex) + { + var method = $"{nameof(UserRepository)}.{nameof(UpdateBasicUserInfoAsync)}"; + var contextualInfo = $"Database update failed while executing >> {method}(...)"; + + if (_logger.IsEnabled(LogLevel.Error)) + { + _logger.LogError(ex, LogExceptionMessage, contextualInfo, ex.Message); + } + + throw new DataLayerException(contextualInfo, ex); + } + catch (Exception ex) + { + var method = $"{nameof(UserRepository)}.{nameof(UpdateBasicUserInfoAsync)}"; + var contextualInfo = $"Exception raised while updating Entity >> {method}(...)"; + + if (_logger.IsEnabled(LogLevel.Error)) + { + _logger.LogError(ex, LogExceptionMessage, contextualInfo, ex.Message); + } + + throw new DataLayerException(contextualInfo, ex); + } + + return result; + } } diff --git a/InvoiceReminder.IntegrationTests/InvoiceReminder.IntegrationTests.csproj b/InvoiceReminder.IntegrationTests/InvoiceReminder.IntegrationTests.csproj index 4e07c9e..9e79e2e 100644 --- a/InvoiceReminder.IntegrationTests/InvoiceReminder.IntegrationTests.csproj +++ b/InvoiceReminder.IntegrationTests/InvoiceReminder.IntegrationTests.csproj @@ -1,4 +1,4 @@ - + CA1873 diff --git a/InvoiceReminder.UnitTests.API/Endpoints/UserEndpointsTests.cs b/InvoiceReminder.UnitTests.API/Endpoints/UserEndpointsTests.cs index 93e2a3c..1a85a20 100644 --- a/InvoiceReminder.UnitTests.API/Endpoints/UserEndpointsTests.cs +++ b/InvoiceReminder.UnitTests.API/Endpoints/UserEndpointsTests.cs @@ -385,11 +385,10 @@ public async Task GetUserByEmail_WhenUserIsAuthenticatedButNotExists_ShouldRetur #region MapPost Tests [TestMethod] - public async Task CreateUser_WhenUserIsAuthenticated_ShouldReturnCreated() + public async Task CreateUser_ShouldReturnCreated() { // Arrange var request = new HttpRequestMessage(HttpMethod.Post, basepath); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "test_token"); var userViewModel = _userViewModelFaker.Generate(); var expectedResult = Result.Success(userViewModel); @@ -397,10 +396,6 @@ public async Task CreateUser_WhenUserIsAuthenticated_ShouldReturnCreated() _ = _userAppService.AddAsync(Arg.Any(), Arg.Any()) .Returns(expectedResult); - _ = _authorizationService.AuthorizeAsync(Arg.Any(), Arg.Any(), - Arg.Any>()) - .Returns(Task.FromResult(AuthorizationResult.Success())); - // Act request.Content = JsonContent.Create(userViewModel); var response = await _client.SendAsync(request, TestContext.CancellationToken); @@ -415,28 +410,10 @@ public async Task CreateUser_WhenUserIsAuthenticated_ShouldReturnCreated() } [TestMethod] - public async Task CreateUser_WhenUserIsNotAuthenticated_ShouldReturnUnauthorized() - { - // Arrange - var request = new HttpRequestMessage(HttpMethod.Post, basepath); - - _ = _authorizationService.AuthorizeAsync(Arg.Any(), Arg.Any(), - Arg.Any>()) - .Returns(Task.FromResult(AuthorizationResult.Failed())); - - // Act - var response = await _client.SendAsync(request, TestContext.CancellationToken); - - // Assert - response.StatusCode.ShouldBe(HttpStatusCode.Unauthorized); - } - - [TestMethod] - public async Task CreateUser_WhenUserIsAuthenticatedButServiceFails_ShouldReturnInternalServerError() + public async Task CreateUser_WhenServiceFails_ShouldReturnInternalServerError() { // Arrange var request = new HttpRequestMessage(HttpMethod.Post, basepath); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "test_token"); var userViewModel = _userViewModelFaker.Generate(); var expectedResult = Result.Failure("Service error"); @@ -444,10 +421,6 @@ public async Task CreateUser_WhenUserIsAuthenticatedButServiceFails_ShouldReturn _ = _userAppService.AddAsync(Arg.Any(), Arg.Any()) .Returns(expectedResult); - _ = _authorizationService.AuthorizeAsync(Arg.Any(), Arg.Any(), - Arg.Any>()) - .Returns(Task.FromResult(AuthorizationResult.Success())); - // Act request.Content = JsonContent.Create(userViewModel); var response = await _client.SendAsync(request, TestContext.CancellationToken); diff --git a/InvoiceReminder.UnitTests.API/InvoiceReminder.UnitTests.API.csproj b/InvoiceReminder.UnitTests.API/InvoiceReminder.UnitTests.API.csproj index d18b759..298ec50 100644 --- a/InvoiceReminder.UnitTests.API/InvoiceReminder.UnitTests.API.csproj +++ b/InvoiceReminder.UnitTests.API/InvoiceReminder.UnitTests.API.csproj @@ -1,4 +1,4 @@ - + true diff --git a/InvoiceReminder.UnitTests.Application/InvoiceReminder.UnitTests.Application.csproj b/InvoiceReminder.UnitTests.Application/InvoiceReminder.UnitTests.Application.csproj index 0a9af9a..199d191 100644 --- a/InvoiceReminder.UnitTests.Application/InvoiceReminder.UnitTests.Application.csproj +++ b/InvoiceReminder.UnitTests.Application/InvoiceReminder.UnitTests.Application.csproj @@ -1,4 +1,4 @@ - + true diff --git a/InvoiceReminder.UnitTests.Domain/InvoiceReminder.UnitTests.Domain.csproj b/InvoiceReminder.UnitTests.Domain/InvoiceReminder.UnitTests.Domain.csproj index f3effe2..f1eced2 100644 --- a/InvoiceReminder.UnitTests.Domain/InvoiceReminder.UnitTests.Domain.csproj +++ b/InvoiceReminder.UnitTests.Domain/InvoiceReminder.UnitTests.Domain.csproj @@ -1,4 +1,4 @@ - + true diff --git a/InvoiceReminder.UnitTests.ExternalServices/InvoiceReminder.UnitTests.ExternalServices.csproj b/InvoiceReminder.UnitTests.ExternalServices/InvoiceReminder.UnitTests.ExternalServices.csproj index 0e6d6fb..271ba4a 100644 --- a/InvoiceReminder.UnitTests.ExternalServices/InvoiceReminder.UnitTests.ExternalServices.csproj +++ b/InvoiceReminder.UnitTests.ExternalServices/InvoiceReminder.UnitTests.ExternalServices.csproj @@ -1,4 +1,4 @@ - + CA1873 diff --git a/InvoiceReminder.UnitTests.Infrastructure/Authentication/JwtProviderTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Authentication/JwtProviderTests.cs index de68f6c..c1a0571 100644 --- a/InvoiceReminder.UnitTests.Infrastructure/Authentication/JwtProviderTests.cs +++ b/InvoiceReminder.UnitTests.Infrastructure/Authentication/JwtProviderTests.cs @@ -71,7 +71,9 @@ public void JwtProvider_Generate_ValidUser_ShouldReturnToken() var user = new UserClaims { Id = Guid.NewGuid(), - Email = "user@test.com" + Name = "Test User", + Email = "user@test.com", + TelegramChatId = 1234567890 }; // Act diff --git a/InvoiceReminder.UnitTests.Infrastructure/InvoiceReminder.UnitTests.Infrastructure.csproj b/InvoiceReminder.UnitTests.Infrastructure/InvoiceReminder.UnitTests.Infrastructure.csproj index f025a73..f7a5494 100644 --- a/InvoiceReminder.UnitTests.Infrastructure/InvoiceReminder.UnitTests.Infrastructure.csproj +++ b/InvoiceReminder.UnitTests.Infrastructure/InvoiceReminder.UnitTests.Infrastructure.csproj @@ -1,4 +1,4 @@ - + true diff --git a/InvoiceReminder.UnitTests.JobScheduler/InvoiceReminder.UnitTests.JobScheduler.csproj b/InvoiceReminder.UnitTests.JobScheduler/InvoiceReminder.UnitTests.JobScheduler.csproj index ecd0d7c..2a4b76c 100644 --- a/InvoiceReminder.UnitTests.JobScheduler/InvoiceReminder.UnitTests.JobScheduler.csproj +++ b/InvoiceReminder.UnitTests.JobScheduler/InvoiceReminder.UnitTests.JobScheduler.csproj @@ -1,4 +1,4 @@ - + CA1873