From 0560a40876bc03fcdeac1d93d3a6d8a743fb76b6 Mon Sep 17 00:00:00 2001 From: Manuel Date: Mon, 25 Aug 2025 18:52:59 +0200 Subject: [PATCH] healthchecks --- .gitignore | 2 +- back/.program_data/app.db-shm | Bin 32768 -> 32768 bytes back/.program_data/app.db-wal | Bin 263712 -> 41232 bytes back/.program_data/imgs/systemkey.lock | 6 ++- back/DTO/UserDto.cs | 9 ++++ back/DataModels/Person.cs | 21 +++++++- back/DataModels/Photo.cs | 21 +++++++- back/DataModels/SystemKey.cs | 2 +- back/DataModels/User.cs | 35 ++++++++++-- back/Program.cs | 8 +++ back/ServicesExtensions/ServicesExtensions.cs | 21 +++++++- back/back.csproj | 9 +++- back/back.sln | 6 +++ back/controllers/UsersController.cs | 6 ++- back/healthchecks/sqlite.cs | 50 ++++++++++++++++++ .../blob/FileSystemImageStorageService.cs | 9 +++- .../Abstracts/IPersonRepository.cs | 3 +- .../Abstracts/IPhotoRepository.cs | 2 +- .../repositories/Abstracts/IUserRepository.cs | 2 +- .../data/repositories/PersonRepository.cs | 2 +- .../data/repositories/PhotoRepository.cs | 2 +- .../data/repositories/UserRepository.cs | 10 ++-- .../bussines/UserService/UserService.cs | 30 +++++++---- .../PasswordGenerator/PasswordGenerator.cs | 20 ++++++- .../engine/SystemUser/SystemUserGenerator.cs | 14 +++-- front/v2/.vscode/launch.json | 34 +++++++++--- front/v2/.vscode/tasks.json | 45 +++++----------- .../app/global-components/header/header.html | 8 +-- .../app/global-components/header/header.ts | 10 ++-- .../app/services/userService/userService.ts | 27 +++++----- .../v2/src/app/views/login-view/login-view.ts | 15 +++--- front/v2/src/models/userModel.ts | 11 ---- front/v2/src/styles.scss | 4 +- 33 files changed, 317 insertions(+), 127 deletions(-) create mode 100644 back/DTO/UserDto.cs create mode 100644 back/healthchecks/sqlite.cs diff --git a/.gitignore b/.gitignore index 635d286..7d21ccc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ *.db -back/data/ +back/.program_data/ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## diff --git a/back/.program_data/app.db-shm b/back/.program_data/app.db-shm index 82359f4c71f75551d1c7721887f11f8f4c3000bb..fbabe1e9ab2a6b057cd8411f2829a4662061f017 100644 GIT binary patch delta 226 zcmZo@U}|V!s+V}A%K!qbK+MR%AixErt$}!tNzaepWiw~3J@fU~=a<{eYqsv^xqm%g zn^g5cqriX@Nd89xP+>N(#SD`R8N~%9fnq3l^F&5T`^^g&Ik>r*8Q2)u88{d?8MuI= kpP45A@LmO!QDdL{%@4$wAv5`(6NnMQH2IG!h*8A}0JX(Pz5oCK literal 32768 zcmeI5Nluhu6h(jALhPz2)B;4&0_=eTYyg`ITVTR~4Un({x1vL~!@wmtA#Q<*-dlv| z@H!%ooRb_XArJU+=DgSY`KB?K<)x-+5B0evTVDs={gYqcd)tTKj%GIxer$~&@9gz= zd;dAFUo~Q=ufPA{^t*q*>Ti$Ua7CY2oRzzLKw{t1Zc8xgrO0Rj$c( z`BuJ@@8t)%AvdMlp1t+#P5wEc00k&O0SZun0u-PC1t>rP3Q&Lo6rcbFC_n)UP=Epy zpa2CZKmiI+fC3bt00k&O0SZun0u-PC1t>rP3Q&Lo6rcbFC_n)UP=Epypa2CZKmiKW zDB%CRhope_9zasSTPJGZofD80@Wu~F3V7#B4NiB|)OhRpzS6sHASvKYLpAW8AxH{L zHJ_6)W=1=gL@zKjP+0pBHrq(GcJ%(Lz$dmw@4774Q{iNDBA{He9R%zCR900bhTIq`)6()H62# diff --git a/back/.program_data/app.db-wal b/back/.program_data/app.db-wal index cd2476f9744a0fbc2786050f2f4601728f67897c..40def1b23866b8526ad581da4e7d80d0e2d3c3aa 100644 GIT binary patch literal 41232 zcmeI)JB-_O90zb8hbr0vcfbNvh_b+NI{c3Ba*81Dobz&G`_hC;=;HX9*m*x31_TQW z2q7^*S0o-o7lyJRfjWaaA$910=!8J(&>|2NLDF7O@3;XnxzhJ9E0O+D?4LjRFZt^C zxA6LjH;K(3o+OBMVm>Z@^ULoyf4n4Ixpw92uU>oa+-&Nxd*`2+Ui~k6i!hgHyA)m9?wH?7)Bk1Rwwb z2tWV=5P$##AOHafK;S+TcwtpNc@PX7%?s|t2aC{Pc3XEshD9#$-KVF%_q%_3cl!m- z6Km)1vl~ab5P$##AOHafKmY;|fB*y_0D*uAyz$)13H2FW-}x_p(EEP><-v=(D-^wP zJJeVFkXmjQq|*MDRd7s2)LJRh@fFM0HYUZ9=Jevbkn8dI0{a3~j0ox%H} z8vg>hK)@Fl%7Xv|AOHafKmY;|fB*y_0D-^@ER_pX>#QAOc6S+3FftRhPd9R$XVzRk z+nx?b6Td6CYEi9DJco-^O5?QA=uV5nQQYg00Izz00bZa0SG_<0uTtez*4z@X7^av z9x$c4Ea*+wNRd)?KPzPDPC|4inO?qDDo0(1786>8G~-dezdg}MNt?Hrl+a{UJ0VB6 zt9z6}qHb!@*t=%cF-n-{-*3AsSP7Z=Kd00bZa0SG_<0uX=z z1Rwx`zzZyu3s|hz>tyOiO_xcTEA^6cBPI^KkRXk#vC>8*UZM;t&C(T_inOd&({Cgy zHQH&)IV<03W{OHGTbNj8CRz~ULweGzc(DdwHV@?jp)gB@Y5ur=f#2TdzP#|^=nv!q zfnQ!I5CRZ@00bZa0SG_<0uX=z1OhIwR4$Ou_j%jh(qhB38QRm7c7Ds^g}I!(WrK$Cv?pL?~TZD)Qyd#v_~m*smxENtx>X7wGEq7rlM#?1u=Of7vPyN J8=B_=e*^C3vqk^_ literal 263712 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 W00bZa0SG_<0uWgG0+p44<^Kotbpm|= diff --git a/back/.program_data/imgs/systemkey.lock b/back/.program_data/imgs/systemkey.lock index 967ce29..718fc98 100644 --- a/back/.program_data/imgs/systemkey.lock +++ b/back/.program_data/imgs/systemkey.lock @@ -1 +1,5 @@ -{"Email":"@system","Key":"caeae1bc-3761-4b30-8627-d86af99b0a4f","Password":"M8I^7b,UF!)PIQ.A"} \ No newline at end of file +{ + "email": "sys@t.em", + "key": "c1d6bd4e-ac32-4859-b2f5-fcda1c190934", + "password": "Tx,bA%8KPn_dç8v[" +} \ No newline at end of file diff --git a/back/DTO/UserDto.cs b/back/DTO/UserDto.cs new file mode 100644 index 0000000..084863a --- /dev/null +++ b/back/DTO/UserDto.cs @@ -0,0 +1,9 @@ +using back.DataModels; + +namespace back.DTO; + +public class UserDto +{ + public string Id { get; set; } = null!; + public ICollection Roles { get; set; } = []; +} diff --git a/back/DataModels/Person.cs b/back/DataModels/Person.cs index 1a7a9ac..1c10a4b 100644 --- a/back/DataModels/Person.cs +++ b/back/DataModels/Person.cs @@ -1,11 +1,12 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Transactional.Abstractions; +using Transactional.Abstractions.Interfaces; namespace back.DataModels; [Table("Persons")] -public partial class Person: IEquatable, ISoftDeletable +public partial class Person: IEntity, ISoftDeletable { [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public string Id { get; set; } = null!; @@ -26,6 +27,7 @@ public partial class Person: IEquatable, ISoftDeletable public virtual User? User { get; set; } public virtual ICollection PhotosNavigation { get; set; } = []; + public override int GetHashCode() => HashCode.Combine(Id, Name); public override bool Equals(object? obj) @@ -38,6 +40,23 @@ public partial class Person: IEquatable, ISoftDeletable return Id == other.Id || GetHashCode() == other.GetHashCode(); } + public bool IsNull => this is null; + + public object Clone() => (Person)MemberwiseClone(); + + public int CompareTo(object? obj) + { + if(obj is null) return 1; + if (obj is not Person other) throw new ArgumentException("Object is not a Person"); + return CompareTo(other); + } + + public int CompareTo(Person? other) + { + if (other is null) return 1; + if (ReferenceEquals(this, other)) return 0; + return string.Compare(Id, other.Id, StringComparison.OrdinalIgnoreCase); + } public const string SystemPersonId = "00000000-0000-0000-0000-000000000001"; diff --git a/back/DataModels/Photo.cs b/back/DataModels/Photo.cs index aafa032..694ee86 100644 --- a/back/DataModels/Photo.cs +++ b/back/DataModels/Photo.cs @@ -1,10 +1,11 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Transactional.Abstractions.Interfaces; namespace back.DataModels; [Table("Photos")] -public partial class Photo : IEquatable +public partial class Photo : IEntity { [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public string Id { get; set; } = null!; @@ -44,4 +45,22 @@ public partial class Photo : IEquatable return Id == other.Id || GetHashCode() == other.GetHashCode(); } + + public bool IsNull => this is null; + + public object Clone() => (Photo)MemberwiseClone(); + + public int CompareTo(object? obj) + { + if (obj is null) return 1; + if (obj is not Photo other) throw new ArgumentException("Object is not a Person"); + return CompareTo(other); + } + + public int CompareTo(Photo? other) + { + if (other is null) return 1; + if (ReferenceEquals(this, other)) return 0; + return string.Compare(Id, other.Id, StringComparison.OrdinalIgnoreCase); + } } \ No newline at end of file diff --git a/back/DataModels/SystemKey.cs b/back/DataModels/SystemKey.cs index 22fd910..abc9100 100644 --- a/back/DataModels/SystemKey.cs +++ b/back/DataModels/SystemKey.cs @@ -2,7 +2,7 @@ public class SystemKey { - public string Email { get; set; } = "@system"; + public string Email { get; set; } = User.SystemUser.Email; public string Key { get; set; } = Guid.NewGuid().ToString(); public required string Password { get; set; } diff --git a/back/DataModels/User.cs b/back/DataModels/User.cs index a099e2b..5dbd692 100644 --- a/back/DataModels/User.cs +++ b/back/DataModels/User.cs @@ -1,10 +1,12 @@ +using back.DTO; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Transactional.Abstractions.Interfaces; namespace back.DataModels; [Table("Users")] -public class User : IEquatable +public class User : IEntity { [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public string Id { get; set; } = null!; @@ -31,6 +33,12 @@ public class User : IEquatable CreatedAt = createdAt.ToString("dd-MM-yyyy HH:mm:ss zz"); } + public UserDto ToDto() => new() + { + Id = Id, + Roles = Roles + }; + public bool IsAdmin() => Roles.Any(r => r.IsAdmin()); public bool IsContentManager() => Roles.Any(r => r.IsContentManager()); public bool IsUser() => Roles.Any(r => r.IsUser()); @@ -46,12 +54,33 @@ public class User : IEquatable return Id == other.Id && Email == other.Email; } + public bool IsNull => this is null; + + public object Clone() => (User)MemberwiseClone(); + + public int CompareTo(object? obj) + { + if (obj is null) return 1; + if (obj is not User other) throw new ArgumentException("Object is not a Person"); + return CompareTo(other); + } + + public int CompareTo(User? other) + { + if (other is null) return 1; + if (ReferenceEquals(this, other)) return 0; + return string.Compare(Id, other.Id, StringComparison.OrdinalIgnoreCase); + } + public const string SystemUserId = "00000000-0000-0000-0000-000000000001"; public static readonly User SystemUser = new( id: SystemUserId, - email: "@system", + email: "sys@t.em", password: "", createdAt: DateTime.UtcNow - ); + ) + { + Roles = [Role.AdminRole, Role.ContentManagerRole, Role.UserRole] + }; } \ No newline at end of file diff --git a/back/Program.cs b/back/Program.cs index 7440428..61028ea 100644 --- a/back/Program.cs +++ b/back/Program.cs @@ -1,4 +1,5 @@ using back.ServicesExtensions; +using healthchecks; namespace back; @@ -11,6 +12,13 @@ public class Program builder.Services.UseExtensions(); builder.Services.AddControllers(); + + builder.Services.AddHealthChecks(options => { + options.CacheDuration = TimeSpan.FromMinutes(30); + options.Timeout = TimeSpan.FromSeconds(5); + options.AssembliesToScan = [typeof(Program).Assembly]; + }).DiscoverHealthChecks(); + // Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi builder.Services.AddSwaggerGen(); diff --git a/back/ServicesExtensions/ServicesExtensions.cs b/back/ServicesExtensions/ServicesExtensions.cs index 5a78655..00101c8 100644 --- a/back/ServicesExtensions/ServicesExtensions.cs +++ b/back/ServicesExtensions/ServicesExtensions.cs @@ -1,8 +1,8 @@ using back.persistance.data; -using back.persistance.data.repositories; -using back.persistance.data.repositories.Abstracts; +using System.Text.Json.Serialization; using back.services.engine.SystemUser; using DependencyInjector; +using System.Text.Json; using Transactional.Abstractions.Interfaces; using Transactional.Implementations.EntityFramework; @@ -21,6 +21,23 @@ public static partial class ServicesExtensions services.AddServices(); services.AddScoped, EntityFrameworkTransactionalService>(); + + services.AddSingleton(new JsonSerializerOptions { + Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + AllowTrailingCommas = true, + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = true, + Converters = { + new JsonStringEnumConverter(JsonNamingPolicy.CamelCase), + }, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, + NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString, + ReadCommentHandling = JsonCommentHandling.Skip, + UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip, + UnknownTypeHandling = JsonUnknownTypeHandling.JsonElement, + }); using var scope = services.BuildServiceProvider().CreateScope(); scope.ServiceProvider diff --git a/back/back.csproj b/back/back.csproj index 9e2a58f..f2b09dd 100644 --- a/back/back.csproj +++ b/back/back.csproj @@ -7,8 +7,11 @@ - + + + + @@ -31,9 +34,10 @@ + - + @@ -44,6 +48,7 @@ + diff --git a/back/back.sln b/back/back.sln index 80d938b..65a05f5 100644 --- a/back/back.sln +++ b/back/back.sln @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Transactional", "..\..\nuge EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DependencyInjector", "..\..\nuget\DependencyInjector\DependencyInjector.csproj", "{DBDF84A4-235C-4F29-8626-5BD1DC255970}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "healthchecks", "..\..\nuget\healthchecks\healthchecks.csproj", "{B21E2BEF-17B7-4981-9843-C0CC36D67010}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +29,10 @@ Global {DBDF84A4-235C-4F29-8626-5BD1DC255970}.Debug|Any CPU.Build.0 = Debug|Any CPU {DBDF84A4-235C-4F29-8626-5BD1DC255970}.Release|Any CPU.ActiveCfg = Release|Any CPU {DBDF84A4-235C-4F29-8626-5BD1DC255970}.Release|Any CPU.Build.0 = Release|Any CPU + {B21E2BEF-17B7-4981-9843-C0CC36D67010}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B21E2BEF-17B7-4981-9843-C0CC36D67010}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B21E2BEF-17B7-4981-9843-C0CC36D67010}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B21E2BEF-17B7-4981-9843-C0CC36D67010}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/back/controllers/UsersController.cs b/back/controllers/UsersController.cs index 08d2b28..1568056 100644 --- a/back/controllers/UsersController.cs +++ b/back/controllers/UsersController.cs @@ -1,6 +1,8 @@ using back.DataModels; +using back.DTO; using back.services.bussines; using back.services.bussines.UserService; +using Mapster; using Microsoft.AspNetCore.Mvc; namespace back.controllers; @@ -44,14 +46,14 @@ public class UsersController(IUserService user) : ControllerBase if (user == null || string.IsNullOrEmpty(user.Email) || string.IsNullOrEmpty(user.Password)) return BadRequest(Errors.BadRequest.Description); - if (user.Email.Equals("@system", StringComparison.InvariantCultureIgnoreCase)) + if (user.Email.Equals(DataModels.User.SystemUser.Email, StringComparison.InvariantCultureIgnoreCase)) { if (string.IsNullOrEmpty(user.SystemKey)) return Unauthorized(Errors.Unauthorized.Description); var systemUser = await _user.ValidateSystemUser(user.Email, user.Password, user.SystemKey, clientId); if (systemUser == null) return Unauthorized(Errors.Unauthorized.Description); - return Ok(systemUser); + return Ok(systemUser.Adapt()); } var existingUser = await _user.Login(user.Email, user.Password, clientId); diff --git a/back/healthchecks/sqlite.cs b/back/healthchecks/sqlite.cs new file mode 100644 index 0000000..e7727a9 --- /dev/null +++ b/back/healthchecks/sqlite.cs @@ -0,0 +1,50 @@ +using back.Options; +using healthchecks; +using Microsoft.Extensions.Options; + +namespace back.healthchecks; + +[HealthCheckExecutionOptions(retryAttempts: 2, timeout: "00:00:05", retryDelay: "00:00:01", severity: HealthCheckSeverity.Critical)] +public class SqliteHealthCheck : IHealthCheck +{ + private readonly DatabaseConfig config; + public SqliteHealthCheck(IOptionsMonitor optionsSnapshot) + { + config = optionsSnapshot.Get(DatabaseConfig.DataStorage); + } + + public Task CheckAsync(CancellationToken cancellationToken = default) + { + // check if can connect to sqlite database + // then run a query to Users table to see if User.SystemUser exists + var isHealthy = false; + var details = string.Empty; + try + { + using var connection = new Microsoft.Data.Sqlite.SqliteConnection(config.ConnectionString); + connection.Open(); + using var command = connection.CreateCommand(); + command.CommandText = $"SELECT COUNT(1) FROM Users WHERE Id = '{DataModels.User.SystemUserId}';"; + var result = command.ExecuteScalar(); + if (result != null && Convert.ToInt32(result) == 1) + { + isHealthy = true; + details = "Connection to SQLite database successful and SystemUser exists."; + } + else + { + details = "Connection to SQLite database successful but SystemUser does not exist."; + } + } + catch (Exception ex) + { + details = $"Failed to connect to SQLite database: {ex.Message}"; + } + + return Task.FromResult(new HealthCheckResult(isHealthy, null) + { + Details = details, + Severity = isHealthy ? HealthCheckSeverity.Info : HealthCheckSeverity.Critical + }); + } +} diff --git a/back/persistance/blob/FileSystemImageStorageService.cs b/back/persistance/blob/FileSystemImageStorageService.cs index 1853211..ea482a6 100644 --- a/back/persistance/blob/FileSystemImageStorageService.cs +++ b/back/persistance/blob/FileSystemImageStorageService.cs @@ -87,8 +87,15 @@ public class FileSystemImageStorageService( { throw new InvalidOperationException($"File {fileName} already exists. Use Update for updating file info."); } - using var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read); + using var fileStream = new FileStream(path, options: new FileStreamOptions { + Access = FileAccess.Write, + BufferSize = 4096, + Mode = FileMode.OpenOrCreate, + Share = FileShare.Read, + }); + blobStream.Seek(0, SeekOrigin.Begin); await blobStream.CopyToAsync(fileStream); + blobStream.Seek(0, SeekOrigin.Begin); } public async Task Update(Stream blobStream, string fileName) diff --git a/back/persistance/data/repositories/Abstracts/IPersonRepository.cs b/back/persistance/data/repositories/Abstracts/IPersonRepository.cs index 6edbbd0..3820ccf 100644 --- a/back/persistance/data/repositories/Abstracts/IPersonRepository.cs +++ b/back/persistance/data/repositories/Abstracts/IPersonRepository.cs @@ -4,6 +4,7 @@ using Transactional.Abstractions.Interfaces; namespace back.persistance.data.repositories.Abstracts; -public interface IPersonRepository : IRepository, IScoped +public interface IPersonRepository : IRepository, IScoped { + } \ No newline at end of file diff --git a/back/persistance/data/repositories/Abstracts/IPhotoRepository.cs b/back/persistance/data/repositories/Abstracts/IPhotoRepository.cs index d890d63..b3cb733 100644 --- a/back/persistance/data/repositories/Abstracts/IPhotoRepository.cs +++ b/back/persistance/data/repositories/Abstracts/IPhotoRepository.cs @@ -4,5 +4,5 @@ using Transactional.Abstractions.Interfaces; namespace back.persistance.data.repositories.Abstracts; -public interface IPhotoRepository : IRepository, IScoped +public interface IPhotoRepository : IRepository, IScoped { } diff --git a/back/persistance/data/repositories/Abstracts/IUserRepository.cs b/back/persistance/data/repositories/Abstracts/IUserRepository.cs index 4c7bd6b..b5d8aab 100644 --- a/back/persistance/data/repositories/Abstracts/IUserRepository.cs +++ b/back/persistance/data/repositories/Abstracts/IUserRepository.cs @@ -4,7 +4,7 @@ using Transactional.Abstractions.Interfaces; namespace back.persistance.data.repositories.Abstracts; -public interface IUserRepository : IRepository, IScoped +public interface IUserRepository : IRepository, IScoped { Task GetByEmail(string email); Task GetUserSaltByEmail(string email); diff --git a/back/persistance/data/repositories/PersonRepository.cs b/back/persistance/data/repositories/PersonRepository.cs index 422f739..0d834b3 100644 --- a/back/persistance/data/repositories/PersonRepository.cs +++ b/back/persistance/data/repositories/PersonRepository.cs @@ -4,7 +4,7 @@ using Transactional.Implementations.EntityFramework; namespace back.persistance.data.repositories; -public class PersonRepository(DataContext context) : ReadWriteRepository(context), IPersonRepository +public class PersonRepository(DataContext context) : ReadWriteRepository(context), IPersonRepository { // Implement methods specific to Photo repository if needed } \ No newline at end of file diff --git a/back/persistance/data/repositories/PhotoRepository.cs b/back/persistance/data/repositories/PhotoRepository.cs index e69aa4d..d7c4084 100644 --- a/back/persistance/data/repositories/PhotoRepository.cs +++ b/back/persistance/data/repositories/PhotoRepository.cs @@ -4,7 +4,7 @@ using Transactional.Implementations.EntityFramework; namespace back.persistance.data.repositories; -public class PhotoRepository(DataContext context) : ReadWriteRepository(context), IPhotoRepository +public class PhotoRepository(DataContext context) : ReadWriteRepository(context), IPhotoRepository { // Implement methods specific to Photo repository if needed } diff --git a/back/persistance/data/repositories/UserRepository.cs b/back/persistance/data/repositories/UserRepository.cs index 11d01d4..0a1bc8c 100644 --- a/back/persistance/data/repositories/UserRepository.cs +++ b/back/persistance/data/repositories/UserRepository.cs @@ -5,14 +5,14 @@ using Transactional.Implementations.EntityFramework; namespace back.persistance.data.repositories; -public class UserRepository(DataContext context) : ReadWriteRepository(context), IUserRepository +public class UserRepository(DataContext context) : ReadWriteRepository(context), IUserRepository { public async Task GetByEmail(string email) { try { if (string.IsNullOrEmpty(email)) return null; - return await Entity.FirstOrDefaultAsync(u => u.Email == email); + return await Entities.FirstOrDefaultAsync(u => u.Email == email); } catch { @@ -25,7 +25,7 @@ public class UserRepository(DataContext context) : ReadWriteRepository u.Email == email); + var user = await Entities.FirstOrDefaultAsync(u => u.Email == email); return user?.Salt ?? string.Empty; } catch @@ -39,7 +39,7 @@ public class UserRepository(DataContext context) : ReadWriteRepository u.Email == email && u.Password == password); + return await Entities.FirstOrDefaultAsync(u => u.Email == email && u.Password == password); } catch { @@ -52,7 +52,7 @@ public class UserRepository(DataContext context) : ReadWriteRepository u.Email == email); + return await Entities.AnyAsync(u => u.Email == email); } catch { diff --git a/back/services/bussines/UserService/UserService.cs b/back/services/bussines/UserService/UserService.cs index d2ea61a..472dc7c 100644 --- a/back/services/bussines/UserService/UserService.cs +++ b/back/services/bussines/UserService/UserService.cs @@ -4,13 +4,15 @@ using back.persistance.data.repositories.Abstracts; using back.services.engine.Crypto; using back.services.engine.mailing; using System.Text; +using System.Text.Json; namespace back.services.bussines.UserService; public class UserService( IUserRepository userRepository, ICryptoService cryptoService, IEmailService emailService, - IBlobStorageService blobStorageService + IBlobStorageService blobStorageService, + JsonSerializerOptions jsonSerializerOptions ) : IUserService { private readonly IUserRepository _repository = userRepository ?? throw new ArgumentNullException(nameof(userRepository)); @@ -66,6 +68,14 @@ public class UserService( return existingUser; } + public async Task Login(string email, string decryptedPass) + { + var salt = await _repository.GetUserSaltByEmail(email); + var hashedPassword = _cryptoService.HashPassword(decryptedPass, salt); + var user = await _repository.Login(email, hashedPassword ?? string.Empty); + return user; + } + public async Task Login(string email, string password, string clientId) { if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(password)) return null; @@ -73,9 +83,7 @@ public class UserService( try { var decryptedPass = _cryptoService.Decrypt(clientId, password); - var salt = await _repository.GetUserSaltByEmail(email); - var hashedPassword = _cryptoService.HashPassword(decryptedPass, salt); - var user = await _repository.Login(email, hashedPassword ?? string.Empty); + var user = await Login(email, decryptedPass ?? string.Empty); return user; } catch @@ -101,21 +109,21 @@ public class UserService( public async Task ValidateSystemUser(string email, string password, string systemKey, string clientId) { - password = _cryptoService.Decrypt(clientId, password) ?? string.Empty; - systemKey = _cryptoService.Decrypt(clientId, systemKey) ?? string.Empty; - if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(password) || string.IsNullOrEmpty(systemKey)) + var decryptedPassword = _cryptoService.Decrypt(clientId, password) ?? string.Empty; + var decryptedsystemKey = _cryptoService.Decrypt(clientId, systemKey) ?? string.Empty; + if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(decryptedPassword) || string.IsNullOrEmpty(decryptedsystemKey)) { return null; } - if (!email.Equals("@system", StringComparison.InvariantCultureIgnoreCase)) + if (!email.Equals(User.SystemUser.Email, StringComparison.InvariantCultureIgnoreCase)) { return null; } var systemKeyBytes = await _blobStorageService.GetBytes("systemkey.lock"); var systemKeyString = Encoding.UTF8.GetString(systemKeyBytes ?? []); - var systemKeyObject = System.Text.Json.JsonSerializer.Deserialize(systemKeyString); - if (systemKeyObject == null || !systemKeyObject.IsValid(email, password, systemKey)) + var systemKeyObject = JsonSerializer.Deserialize(systemKeyString, jsonSerializerOptions); + if (systemKeyObject == null || !systemKeyObject.IsValid(email, decryptedPassword, decryptedsystemKey)) { return null; } @@ -128,6 +136,6 @@ public class UserService( { return null; } - return await Login(user.Email!, user.Password!, clientId); + return await Login(user.Email!, decryptedPassword); } } diff --git a/back/services/engine/PasswordGenerator/PasswordGenerator.cs b/back/services/engine/PasswordGenerator/PasswordGenerator.cs index b4eb044..1426aa6 100644 --- a/back/services/engine/PasswordGenerator/PasswordGenerator.cs +++ b/back/services/engine/PasswordGenerator/PasswordGenerator.cs @@ -5,8 +5,8 @@ public class PasswordGenerator : IPasswordGenerator public string Generate(int length, bool includeNumbers = true, bool includeMayus = true, bool includeMinus = true, bool includeSpecials = true) { const string numbers = "0123456789"; - const string mayus = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - const string minus = "abcdefghijklmnopqrstuvwxyz"; + const string mayus = "ABCÇDEFGHIJKLMNÑOPQRSTUVWXYZ"; + const string minus = "abcçdefghijklmnñopqrstuvwxyz"; const string specials = "!@#$%^&*()_+[]{}|;:,.<>?"; var characters = minus; if (includeNumbers) characters += numbers; @@ -19,6 +19,22 @@ public class PasswordGenerator : IPasswordGenerator { password[i] = characters[random.Next(characters.Length)]; } + + var positionPool = new List(); + for (int i = 0; i < length; i++) positionPool.Add(i); + var forcedRandomNumber = random.Next(0, positionPool.Count); + positionPool.RemoveAt(forcedRandomNumber); + var forcedRandomMayus = random.Next(0, positionPool.Count); + positionPool.RemoveAt(forcedRandomMayus); + var forcedRandomMinus = random.Next(0, positionPool.Count); + positionPool.RemoveAt(forcedRandomMinus); + var forcedRandomSpecial = random.Next(0, positionPool.Count); + positionPool.RemoveAt(forcedRandomSpecial); + + password[forcedRandomNumber] = numbers[random.Next(numbers.Length)]; + password[forcedRandomMayus] = mayus[random.Next(mayus.Length)]; + password[forcedRandomMinus] = minus[random.Next(minus.Length)]; + password[forcedRandomSpecial] = specials[random.Next(specials.Length)]; return new string(password); } } diff --git a/back/services/engine/SystemUser/SystemUserGenerator.cs b/back/services/engine/SystemUser/SystemUserGenerator.cs index 605f3c1..8f81afa 100644 --- a/back/services/engine/SystemUser/SystemUserGenerator.cs +++ b/back/services/engine/SystemUser/SystemUserGenerator.cs @@ -4,12 +4,14 @@ using back.persistance.data; using back.persistance.data.repositories.Abstracts; using back.services.engine.Crypto; using back.services.engine.PasswordGenerator; +using System.Text.Json; using Transactional.Abstractions.Interfaces; namespace back.services.engine.SystemUser; public class SystemUserGenerator( ITransactionalService transactional, + JsonSerializerOptions jsonSerializerOptions, IUserRepository userRepository, IPersonRepository personRepository, ICryptoService cryptoService, @@ -21,9 +23,9 @@ public class SystemUserGenerator( var systemKey = new SystemKey() { Password = passwordGenerator.Generate(16), }; - var systemKeyJson = System.Text.Json.JsonSerializer.Serialize(systemKey); + var systemKeyJson = JsonSerializer.Serialize(systemKey, options: jsonSerializerOptions); - using Stream stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(systemKeyJson)); + using Stream stream = new MemoryStream(new System.Text.UTF8Encoding(true).GetBytes(systemKeyJson)); await blobStorageService.Delete("systemkey.lock"); @@ -38,10 +40,16 @@ public class SystemUserGenerator( if (!await userRepository.Exists(User.SystemUser.Id!)) { - await transactional.DoTransaction(async () => { + await transactional.DoTransaction(async () => + { await personRepository.Insert(Person.SystemPerson); await userRepository.Insert(User.SystemUser); }); } + else + { + await userRepository.Update(User.SystemUser); + await userRepository.SaveChanges(); + } } } \ No newline at end of file diff --git a/front/v2/.vscode/launch.json b/front/v2/.vscode/launch.json index 925af83..35f90ce 100644 --- a/front/v2/.vscode/launch.json +++ b/front/v2/.vscode/launch.json @@ -1,20 +1,38 @@ { + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { - "name": "ng serve", - "type": "chrome", + "name": "FRONT: DEBUG(Edge)", "request": "launch", - "preLaunchTask": "npm: start", - "url": "http://localhost:4200/" + "type": "msedge", + "url": "http://localhost:4200", + "webRoot": "${workspaceFolder}", + "preLaunchTask": "Start Node server with nvs latest" }, { - "name": "ng test", - "type": "chrome", + "name": "Attach Edge", + "type": "msedge", + "request": "attach", + "url": "http://localhost:4200/#", + "webRoot": "${workspaceFolder}" + }, + { + "name": "Launch Edge (Test)", + "type": "msedge", "request": "launch", - "preLaunchTask": "npm: test", - "url": "http://localhost:9876/debug.html" + "url": "http://localhost:9876/debug.html", + "webRoot": "${workspaceFolder}" + }, + { + "name": "Launch Edge (E2E)", + "type": "node", + "request": "launch", + "program": "${workspaceFolder}/node_modules/protractor/bin/protractor", + "protocol": "inspector", + "args": ["${workspaceFolder}/protractor.conf.js"] } ] } diff --git a/front/v2/.vscode/tasks.json b/front/v2/.vscode/tasks.json index a298b5b..95e77ed 100644 --- a/front/v2/.vscode/tasks.json +++ b/front/v2/.vscode/tasks.json @@ -1,41 +1,20 @@ { - // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 "version": "2.0.0", "tasks": [ { - "type": "npm", - "script": "start", + "label": "Start Node server with nvs latest", + "type": "shell", + "command": "nvs use latest && npm run start", + "options": { + "cwd": "${workspaceFolder}" + }, "isBackground": true, - "problemMatcher": { - "owner": "typescript", - "pattern": "$tsc", - "background": { - "activeOnStart": true, - "beginsPattern": { - "regexp": "(.*?)" - }, - "endsPattern": { - "regexp": "bundle generation complete" - } - } - } - }, - { - "type": "npm", - "script": "test", - "isBackground": true, - "problemMatcher": { - "owner": "typescript", - "pattern": "$tsc", - "background": { - "activeOnStart": true, - "beginsPattern": { - "regexp": "(.*?)" - }, - "endsPattern": { - "regexp": "bundle generation complete" - } - } + "problemMatcher": [], + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "shared" } } ] diff --git a/front/v2/src/app/global-components/header/header.html b/front/v2/src/app/global-components/header/header.html index af71fce..e890fd8 100644 --- a/front/v2/src/app/global-components/header/header.html +++ b/front/v2/src/app/global-components/header/header.html @@ -5,15 +5,15 @@ - @if (user.isLoggedIn) { + @if (currentUser().isLoggedIn) { - } @if (user.isContentManager) { + } @if (currentUser().isContentManager) { - } @if (user.isAdmin) { + } @if (currentUser().isAdmin) { }