diff --git a/.vscode/launch.json b/.vscode/launch.json index 6691e2c..93bbaef 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,36 +1,56 @@ { - // Use IntelliSense to find out which attributes exist for C# debugging - // Use hover for the description of the existing attributes - // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md - "version": "0.2.0", - "configurations": [ - { - "name": ".NET Core Launch (web)", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/Apps/Website/bin/Debug/net5.0/Website.dll", - "args": [], - "cwd": "${workspaceFolder}/Apps/Website", - "stopAtEntry": false, - // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser - "serverReadyAction": { - "action": "openExternally", - "pattern": "\\bNow listening on:\\s+(https?://\\S+)" - }, - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "sourceFileMap": { - "/Views": "${workspaceFolder}/Views" - } - }, - { - "name": ".NET Core Attach", - "type": "coreclr", - "request": "attach", - "processId": "${command:pickProcess}" - } - ] -} \ No newline at end of file + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Launch (api)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Apps/Api/bin/Debug/net5.0/Api.dll", + "args": [], + "cwd": "${workspaceFolder}/Apps/Api", + "stopAtEntry": false, + "serverReadyAction": { + "action": "openExternally", + "pattern": "\\bNow listening on:\\s+(https?://\\S+)" + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Views" + } + }, + { + "name": ".NET Core Launch (web)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/Apps/Website/bin/Debug/net5.0/Website.dll", + "args": [], + "cwd": "${workspaceFolder}/Apps/Website", + "stopAtEntry": false, + // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser + "serverReadyAction": { + "action": "openExternally", + "pattern": "\\bNow listening on:\\s+(https?://\\S+)" + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Views" + } + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickProcess}" + } + ] +} diff --git a/Apps/Api/Api.csproj b/Apps/Api/Api.csproj index f245fe5..d748d6a 100644 --- a/Apps/Api/Api.csproj +++ b/Apps/Api/Api.csproj @@ -11,6 +11,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Apps/Api/Controllers/AccountsController.cs b/Apps/Api/Controllers/AccountsController.cs new file mode 100644 index 0000000..ef3b315 --- /dev/null +++ b/Apps/Api/Controllers/AccountsController.cs @@ -0,0 +1,70 @@ +using System; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Api.Models; +using HN.Infrastructure.Identity; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.IdentityModel.Tokens; + +namespace Api.Controllers +{ + [ApiController] + [Route("api/[controller]")] + public sealed class AccountsController : ControllerBase + { + private readonly UserManager _usersManager; + private readonly SignInManager _signinManager; + private readonly TokenValidationParameters _tokenParameters; + + public AccountsController(UserManager usersManager, SignInManager signinManager, TokenValidationParameters tokenParameters) + { + _usersManager = usersManager; + _signinManager = signinManager; + _tokenParameters = tokenParameters; + } + + [Authorize] + public IActionResult GetUsers() + { + return Ok(_usersManager.Users.ToArray()); + } + + [HttpPost("login")] + public async Task Login(LoginViewModel command) + { + var user = await _usersManager.FindByNameAsync(command.Username); + + if (user == null) + { + return NotFound(); + } + + var result = await _signinManager.CheckPasswordSignInAsync(user, command.Password, false); + + if (!result.Succeeded) + { + return BadRequest(); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(new Claim[] + { + new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), + new Claim(ClaimTypes.Name, command.Username), + }), + + Expires = DateTime.UtcNow.AddDays(7), + Issuer = _tokenParameters.ValidIssuer, + Audience = _tokenParameters.ValidAudience, + SigningCredentials = new SigningCredentials(_tokenParameters.IssuerSigningKey, SecurityAlgorithms.HmacSha256Signature) + }; + + return Ok(new JwtSecurityTokenHandler().CreateEncodedJwt(tokenDescriptor)); + } + } +} \ No newline at end of file diff --git a/Apps/Api/Controllers/LinksController.cs b/Apps/Api/Controllers/LinksController.cs index 9fe3cdb..d53038f 100644 --- a/Apps/Api/Controllers/LinksController.cs +++ b/Apps/Api/Controllers/LinksController.cs @@ -102,7 +102,7 @@ namespace Api.Controllers { var result = await _bus.Send(command); - return CreatedAtAction(nameof(GetLinkById), new { id = result }); + return CreatedAtAction(nameof(GetLinkById), new { id = result }, null); } } } \ No newline at end of file diff --git a/Apps/Api/Models/LoginViewModel.cs b/Apps/Api/Models/LoginViewModel.cs new file mode 100644 index 0000000..fc39401 --- /dev/null +++ b/Apps/Api/Models/LoginViewModel.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; + +namespace Api.Models +{ + public sealed class LoginViewModel + { + [Required] + public string Username { get; set; } + + [Required] + public string Password { get; set; } + } +} \ No newline at end of file diff --git a/Apps/Api/Startup.cs b/Apps/Api/Startup.cs index bebf575..fb524d5 100644 --- a/Apps/Api/Startup.cs +++ b/Apps/Api/Startup.cs @@ -1,24 +1,19 @@ -using System; -using HN.Application; +using System.Text; using HN.Infrastructure; +using HN.Infrastructure.Identity; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.IdentityModel.Tokens; namespace Api { - public class MockExecutingContext : IExecutingUserProvider - { - public Guid GetCurrentUserId() - { - return Guid.NewGuid(); - } - } - public class Startup { public Startup(IConfiguration configuration) @@ -32,7 +27,7 @@ namespace Api // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { - services.AddHN(Configuration).ResolveConnectedUserWith(); + services.AddHN(Configuration).ResolveConnectedUserWith(); services.AddHttpContextAccessor(); // Permet d'avoir des routes en lowercase @@ -42,6 +37,31 @@ namespace Api options.LowercaseQueryStrings = true; }); + // Ajout de l'authentification + var tokenParams = new TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + ValidIssuer = Configuration["JwtIssuer"], + ValidAudience = Configuration["JwtAudience"], + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtSecurityKey"])) + }; + + services.AddSingleton(tokenParams); + + services.AddIdentityCore() + .AddRoles() + .AddEntityFrameworkStores() + .AddSignInManager(); + + services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(o => + { + o.TokenValidationParameters = tokenParams; + }); + services.AddControllers(); services.AddSwaggerDocument(d => { @@ -66,6 +86,9 @@ namespace Api app.UseRouting(); + app.UseAuthentication(); + app.UseAuthorization(); + app.UseEndpoints(endpoints => { endpoints.MapControllers(); diff --git a/Apps/Api/appsettings.json b/Apps/Api/appsettings.json index 55f4a8f..a4417a6 100644 --- a/Apps/Api/appsettings.json +++ b/Apps/Api/appsettings.json @@ -9,5 +9,8 @@ "ConnectionStrings": { "Default": "Data Source=../Website/hn.db" }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "JwtIssuer": "http://localhost", + "JwtAudience": "http://localhost", + "JwtSecurityKey": "CTtgxbcSFbpJmdmLDnr3Y8h5RWseN7t5" } diff --git a/Apps/Api/swagger.json b/Apps/Api/swagger.json index cdc6792..e8d9d6e 100644 --- a/Apps/Api/swagger.json +++ b/Apps/Api/swagger.json @@ -6,6 +6,60 @@ "version": "1.0.0" }, "paths": { + "/api/accounts": { + "get": { + "tags": [ + "Accounts" + ], + "operationId": "Accounts_GetUsers", + "responses": { + "200": { + "description": "", + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + } + } + } + }, + "/api/accounts/login": { + "post": { + "tags": [ + "Accounts" + ], + "operationId": "Accounts_Login", + "requestBody": { + "x-name": "command", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoginViewModel" + } + } + }, + "required": true, + "x-position": 1 + }, + "responses": { + "200": { + "description": "", + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + } + } + } + }, "/api/links": { "get": { "tags": [ @@ -232,6 +286,24 @@ }, "components": { "schemas": { + "LoginViewModel": { + "type": "object", + "additionalProperties": false, + "required": [ + "username", + "password" + ], + "properties": { + "username": { + "type": "string", + "minLength": 1 + }, + "password": { + "type": "string", + "minLength": 1 + } + } + }, "LinkDto": { "type": "object", "additionalProperties": false, diff --git a/Apps/Website/Controllers/AccountsController.cs b/Apps/Website/Controllers/AccountsController.cs index 0215466..daea63f 100644 --- a/Apps/Website/Controllers/AccountsController.cs +++ b/Apps/Website/Controllers/AccountsController.cs @@ -65,15 +65,7 @@ namespace Website.Controllers return View(); } - var user = await _userManager.FindByNameAsync(command.Username); - - if (user == null) - { - ModelState.AddModelError(nameof(LoginViewModel.Username), "Could not verify user identity"); - return View(); - } - - var result = await _signInManager.PasswordSignInAsync(user, command.Password, true, false); + var result = await _signInManager.PasswordSignInAsync(command.Username, command.Password, true, false); if (!result.Succeeded) {