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 82359f4..fbabe1e 100644 Binary files a/back/.program_data/app.db-shm and b/back/.program_data/app.db-shm differ diff --git a/back/.program_data/app.db-wal b/back/.program_data/app.db-wal index cd2476f..40def1b 100644 Binary files a/back/.program_data/app.db-wal and b/back/.program_data/app.db-wal differ 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) { }