From 0a8258017ac15d0280a8c94bbe343c138256831d Mon Sep 17 00:00:00 2001 From: colesm Date: Mon, 8 Dec 2025 12:27:05 +0100 Subject: [PATCH] MAJ avec l'authentifiation --- .../DTO/Login/Request/ConnectLoginDto.cs | 7 +++ PyroFetes/DTO/Login/Request/CreateLoginDto.cs | 24 +++++++++ PyroFetes/DTO/Login/Request/UpdateLoginDto.cs | 8 +++ .../DTO/Login/Response/GetLoginConnectDto.cs | 6 +++ PyroFetes/DTO/Login/Response/GetLoginDto.cs | 10 ++++ .../Endpoints/Login/CreateLoginEndpoint.cs | 53 +++++++++++++++++++ .../Endpoints/Login/DeleteLoginEndpoint.cs | 34 ++++++++++++ .../Endpoints/Login/GetAllLoginEndpoint.cs | 29 ++++++++++ PyroFetes/Endpoints/Login/GetLoginEndpoint.cs | 40 ++++++++++++++ .../Endpoints/Login/UpdateLoginEndpoint.cs | 43 +++++++++++++++ .../Endpoints/Login/UserLoginEndpoint.cs | 48 +++++++++++++++++ package-lock.json | 18 +++++++ package.json | 5 ++ 13 files changed, 325 insertions(+) create mode 100644 PyroFetes/DTO/Login/Request/ConnectLoginDto.cs create mode 100644 PyroFetes/DTO/Login/Request/CreateLoginDto.cs create mode 100644 PyroFetes/DTO/Login/Request/UpdateLoginDto.cs create mode 100644 PyroFetes/DTO/Login/Response/GetLoginConnectDto.cs create mode 100644 PyroFetes/DTO/Login/Response/GetLoginDto.cs create mode 100644 PyroFetes/Endpoints/Login/CreateLoginEndpoint.cs create mode 100644 PyroFetes/Endpoints/Login/DeleteLoginEndpoint.cs create mode 100644 PyroFetes/Endpoints/Login/GetAllLoginEndpoint.cs create mode 100644 PyroFetes/Endpoints/Login/GetLoginEndpoint.cs create mode 100644 PyroFetes/Endpoints/Login/UpdateLoginEndpoint.cs create mode 100644 PyroFetes/Endpoints/Login/UserLoginEndpoint.cs create mode 100644 package-lock.json create mode 100644 package.json diff --git a/PyroFetes/DTO/Login/Request/ConnectLoginDto.cs b/PyroFetes/DTO/Login/Request/ConnectLoginDto.cs new file mode 100644 index 0000000..fb6c5e2 --- /dev/null +++ b/PyroFetes/DTO/Login/Request/ConnectLoginDto.cs @@ -0,0 +1,7 @@ +namespace PyroFetes.DTO.Login.Request; + +public class ConnectLoginDto +{ + public string? Name { get; set; } + public string? Password { get; set; } +} \ No newline at end of file diff --git a/PyroFetes/DTO/Login/Request/CreateLoginDto.cs b/PyroFetes/DTO/Login/Request/CreateLoginDto.cs new file mode 100644 index 0000000..d290150 --- /dev/null +++ b/PyroFetes/DTO/Login/Request/CreateLoginDto.cs @@ -0,0 +1,24 @@ +using System.ComponentModel.DataAnnotations; + +// Nécessaire pour les validations + +namespace PyroFetes.DTO.Login.Request; + +public class CreateLoginDto +{ + [Required(ErrorMessage = "Le nom est requis.")] + [StringLength(50, MinimumLength = 3, ErrorMessage = "L'identifiant doit faire entre 3 et 50 caractères.")] + public string Name { get; set; } = string.Empty; + + [Required(ErrorMessage = "L'emil est requis.")] + [StringLength(50, MinimumLength = 3)] + public string Email { get; set; } = string.Empty; + + [Required(ErrorMessage = "Le mot de passe est requis.")] + [MinLength(6, ErrorMessage = "Le mot de passe doit contenir au moins 6 caractères.")] + public string Password { get; set; } = string.Empty; + + // Ajout du champ Rôle (Optionnel, par défaut "User") + // Cela te permet d'envoyer "Admin" via Swagger + public string Fonction { get; set; } = "User"; +} \ No newline at end of file diff --git a/PyroFetes/DTO/Login/Request/UpdateLoginDto.cs b/PyroFetes/DTO/Login/Request/UpdateLoginDto.cs new file mode 100644 index 0000000..2dfa1a7 --- /dev/null +++ b/PyroFetes/DTO/Login/Request/UpdateLoginDto.cs @@ -0,0 +1,8 @@ +namespace PyroFetes.DTO.Login.Request; + +public class UpdateLoginDto +{ + public int Id { get; set; } + public string? Name { get; set; } + public string? Password { get; set; } +} \ No newline at end of file diff --git a/PyroFetes/DTO/Login/Response/GetLoginConnectDto.cs b/PyroFetes/DTO/Login/Response/GetLoginConnectDto.cs new file mode 100644 index 0000000..bb122de --- /dev/null +++ b/PyroFetes/DTO/Login/Response/GetLoginConnectDto.cs @@ -0,0 +1,6 @@ +namespace PyroFetes.DTO.Login.Response; + +public class GetLoginConnectDto +{ + public string? Token { get; set; } +} \ No newline at end of file diff --git a/PyroFetes/DTO/Login/Response/GetLoginDto.cs b/PyroFetes/DTO/Login/Response/GetLoginDto.cs new file mode 100644 index 0000000..27bd924 --- /dev/null +++ b/PyroFetes/DTO/Login/Response/GetLoginDto.cs @@ -0,0 +1,10 @@ +namespace PyroFetes.DTO.Login.Response; + +public class GetLoginDto +{ + public int Id { get; set; } + public string? Name { get; set; } = string.Empty; + public string? Email { get; set; } = string.Empty; + public string? Password { get; set; } = string.Empty; + public string? Fonction { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/PyroFetes/Endpoints/Login/CreateLoginEndpoint.cs b/PyroFetes/Endpoints/Login/CreateLoginEndpoint.cs new file mode 100644 index 0000000..c226740 --- /dev/null +++ b/PyroFetes/Endpoints/Login/CreateLoginEndpoint.cs @@ -0,0 +1,53 @@ +using FastEndpoints; +using Microsoft.EntityFrameworkCore; +using PasswordGenerator; +using PyroFetes.DTO.Login.Request; +using PyroFetes.DTO.Login.Response; + +namespace PyroFetes.Endpoints.Login; + +public class CreateLoginEndpoint(PyroFetesDbContext database) : Endpoint +{ + public override void Configure() + { + Post("/logins"); + //Roles("Admin"); + AllowAnonymous(); + } + + public override async Task HandleAsync(CreateLoginDto req, CancellationToken ct) + { + bool exists = await database.Users.AnyAsync(x => x.Name == req.Name, ct); + if (exists) + { + AddError("Ce nom d'utilisateur est déjà utilisé."); + await Send.ErrorsAsync(400, ct); + return; + } + + string? salt = new Password().IncludeLowercase().IncludeUppercase().IncludeNumeric().LengthRequired(24).Next(); + + Models.User login = new Models.User() + { + Name = req.Name, + Email = req.Email, + Password = BCrypt.Net.BCrypt.HashPassword(req.Password + salt), + Salt = salt, + + Fonction = string.IsNullOrEmpty(req.Fonction) ? "User" : req.Fonction + }; + + database.Users.Add(login); + await database.SaveChangesAsync(ct); + + GetLoginDto responseDto = new() + { + Id = login.Id, + Name = login.Name, + Email = login.Email, + Fonction = login.Fonction + }; + + await Send.OkAsync(responseDto, ct); + } +} \ No newline at end of file diff --git a/PyroFetes/Endpoints/Login/DeleteLoginEndpoint.cs b/PyroFetes/Endpoints/Login/DeleteLoginEndpoint.cs new file mode 100644 index 0000000..7a200fa --- /dev/null +++ b/PyroFetes/Endpoints/Login/DeleteLoginEndpoint.cs @@ -0,0 +1,34 @@ +using FastEndpoints; +using Microsoft.EntityFrameworkCore; + +namespace PyroFetes.Endpoints.Login; + +public class DeleteLoginRequest +{ + public int Id { get; set; } +} + +public class DeleteLoginEndpoint(PyroFetesDbContext database) : Endpoint +{ + public override void Configure() + { + Delete("/logins/{@Id}", x => new {x.Id}); + AllowAnonymous(); + } + + public override async Task HandleAsync(DeleteLoginRequest req, CancellationToken ct) + { + Models.User? login = await database.Users.SingleOrDefaultAsync(x => x.Id == req.Id, ct); + + if (login == null) + { + await Send.NotFoundAsync(ct); + return; + } + + database.Users.Remove(login); + await database.SaveChangesAsync(ct); + + await Send.NoContentAsync(ct); + } +} \ No newline at end of file diff --git a/PyroFetes/Endpoints/Login/GetAllLoginEndpoint.cs b/PyroFetes/Endpoints/Login/GetAllLoginEndpoint.cs new file mode 100644 index 0000000..555cc76 --- /dev/null +++ b/PyroFetes/Endpoints/Login/GetAllLoginEndpoint.cs @@ -0,0 +1,29 @@ +using FastEndpoints; +using Microsoft.EntityFrameworkCore; +using PyroFetes.DTO.Login.Response; + +namespace PyroFetes.Endpoints.Login; + +public class GetAllLoginEndpoint(PyroFetesDbContext database) : EndpointWithoutRequest> +{ + public override void Configure() + { + Get("/logins"); + AllowAnonymous(); + } + + public override async Task HandleAsync(CancellationToken ct) + { + List logins = await database.Users + .Select(login => new GetLoginDto() + { + Id = login.Id, + Name = login.Name, + Password = login.Password, + Fonction = login.Fonction + }) + .ToListAsync(ct); + + await Send.OkAsync(logins, ct); + } +} \ No newline at end of file diff --git a/PyroFetes/Endpoints/Login/GetLoginEndpoint.cs b/PyroFetes/Endpoints/Login/GetLoginEndpoint.cs new file mode 100644 index 0000000..558c6db --- /dev/null +++ b/PyroFetes/Endpoints/Login/GetLoginEndpoint.cs @@ -0,0 +1,40 @@ +using FastEndpoints; +using Microsoft.EntityFrameworkCore; +using PyroFetes.DTO.Login.Response; + +namespace PyroFetes.Endpoints.Login; + +public class GetLoginRequest +{ + public int Id { get; set; } +} + +public class GetLoginEndpoint(PyroFetesDbContext database) : Endpoint +{ + public override void Configure() + { + Get("/logins/{@Id}", x => new {x.Id}); + AllowAnonymous(); + } + + public override async Task HandleAsync(GetLoginRequest req, CancellationToken ct) + { + Models.User? login = await database.Users + .SingleOrDefaultAsync(x => x.Id == req.Id, ct); + + if (login == null) + { + await Send.NotFoundAsync(ct); + return; + } + + GetLoginDto responseDto = new() + { + Id = login.Id, + Name = login.Name, + Fonction = login.Fonction + }; + + await Send.OkAsync(responseDto, ct); + } +} \ No newline at end of file diff --git a/PyroFetes/Endpoints/Login/UpdateLoginEndpoint.cs b/PyroFetes/Endpoints/Login/UpdateLoginEndpoint.cs new file mode 100644 index 0000000..053213f --- /dev/null +++ b/PyroFetes/Endpoints/Login/UpdateLoginEndpoint.cs @@ -0,0 +1,43 @@ +using FastEndpoints; +using Microsoft.EntityFrameworkCore; +using PasswordGenerator; +using PyroFetes.DTO.Login.Request; +using PyroFetes.DTO.Login.Response; + +namespace PyroFetes.Endpoints.Login; + +public class UpdateLoginEndpoint(PyroFetesDbContext database) : Endpoint +{ + public override void Configure() + { + Put("/logins/{@Id}", x => new {x.Id}); + AllowAnonymous(); + } + + public override async Task HandleAsync(UpdateLoginDto req, CancellationToken ct) + { + Models.User? login = await database.Users.SingleOrDefaultAsync(x => x.Id == req.Id, ct); + + if (login == null) + { + await Send.NotFoundAsync(ct); + return; + } + + string? salt = new Password().IncludeLowercase().IncludeUppercase().IncludeNumeric().LengthRequired(24).Next(); + + login.Name = req.Name; + login.Password = BCrypt.Net.BCrypt.HashPassword(req.Password + salt); + login.Salt = salt; + await database.SaveChangesAsync(ct); + + GetLoginDto responseDto = new() + { + Id = login.Id, + Name = login.Name, + Fonction = login.Fonction + }; + + await Send.OkAsync(responseDto, ct); + } +} \ No newline at end of file diff --git a/PyroFetes/Endpoints/Login/UserLoginEndpoint.cs b/PyroFetes/Endpoints/Login/UserLoginEndpoint.cs new file mode 100644 index 0000000..b90d196 --- /dev/null +++ b/PyroFetes/Endpoints/Login/UserLoginEndpoint.cs @@ -0,0 +1,48 @@ +using FastEndpoints; +using FastEndpoints.Security; +using Microsoft.EntityFrameworkCore; +using PyroFetes.DTO.Login.Request; +using PyroFetes.DTO.Login.Response; + +namespace PyroFetes.Endpoints.Login; + +public class UserLoginEndpoint(PyroFetesDbContext database) : Endpoint +{ + public override void Configure() + { + Post("/login"); + AllowAnonymous(); + } + + public override async Task HandleAsync(ConnectLoginDto req, CancellationToken ct) + { + Models.User? login = await database.Users.SingleOrDefaultAsync(x => x.Name == req.Name, ct); + + if (login == null) + { + await Send.UnauthorizedAsync(ct); + return; + } + + if (BCrypt.Net.BCrypt.Verify(req.Password + login.Salt, login.Password)) + { + string jwtToken = JwtBearer.CreateToken( + o => + { + o.SigningKey = "ThisIsASuperSecretJwtKeyThatIsAtLeast32CharsLong"; + o.ExpireAt = DateTime.UtcNow.AddMinutes(15); + if (login.Fonction != null) o.User.Roles.Add(login.Fonction); + o.User.Claims.Add(("Username", login.Name)!); + o.User["UserId"] = "001"; + }); + + GetLoginConnectDto responseDto = new() + { + Token = jwtToken + }; + + await Send.OkAsync(responseDto, ct); + } + else await Send.UnauthorizedAsync(ct); + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..ee414d1 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,18 @@ +{ + "name": "PyroFete", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "zone.js": "^0.16.0" + } + }, + "node_modules/zone.js": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.16.0.tgz", + "integrity": "sha512-LqLPpIQANebrlxY6jKcYKdgN5DTXyyHAKnnWWjE5pPfEQ4n7j5zn7mOEEpwNZVKGqx3kKKmvplEmoBrvpgROTA==", + "license": "MIT" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..2b76f29 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "zone.js": "^0.16.0" + } +}