Authorize API
All checks were successful
Game Ideas build for PR / build_blazor_app (pull_request) Successful in 40s

This commit is contained in:
2025-04-21 01:52:01 +02:00
parent 7aedbff784
commit b3212c5e0d
13 changed files with 130 additions and 74 deletions

View File

@@ -1,14 +1,18 @@
@using GameIdeas.BlazorApp.Layouts @using GameIdeas.BlazorApp.Layouts
@using Microsoft.AspNetCore.Components.Authorization
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>

View File

@@ -1,12 +1,26 @@
using GameIdeas.Shared.Dto; using GameIdeas.Shared.Dto;
using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;
namespace GameIdeas.BlazorApp.Helpers; namespace GameIdeas.BlazorApp.Helpers;
public static class GameHelper public static class GameHelper
{ {
public static void WriteTrackingDto(GameDetailDto game) public static void WriteTrackingDto(GameDetailDto game, AuthenticationState authState)
{ {
game.CreationUserId = 100000; if (authState == null)
{
throw new ArgumentNullException(nameof(authState), "Authentication state missing");
}
var userId = authState.User.FindFirstValue(ClaimTypes.Sid);
if (userId == null)
{
throw new ArgumentNullException(nameof(authState), "user state missing");
}
game.CreationUserId = userId;
game.CreationDate = DateTime.Now; game.CreationDate = DateTime.Now;
} }

View File

@@ -5,8 +5,10 @@ using GameIdeas.BlazorApp.Shared.Components.Select.Models;
using GameIdeas.BlazorApp.Shared.Components.Slider; using GameIdeas.BlazorApp.Shared.Components.Slider;
using GameIdeas.Shared.Dto; using GameIdeas.Shared.Dto;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Forms; using Microsoft.AspNetCore.Components.Forms;
using Microsoft.JSInterop; using Microsoft.JSInterop;
using System.Security.Claims;
namespace GameIdeas.BlazorApp.Pages.Games.Components; namespace GameIdeas.BlazorApp.Pages.Games.Components;
@@ -14,6 +16,7 @@ public partial class GameCreationForm
{ {
[Inject] private IJSRuntime Js { get; set; } = default!; [Inject] private IJSRuntime Js { get; set; } = default!;
[Inject] private IGameGateway GameGateway { get; set; } = default!; [Inject] private IGameGateway GameGateway { get; set; } = default!;
[Inject] private AuthenticationStateProvider AuthenticationState { get; set; } = default!;
[CascadingParameter] private Popup? Popup { get; set; } [CascadingParameter] private Popup? Popup { get; set; }
[Parameter] public CategoriesDto? Categories { get; set; } [Parameter] public CategoriesDto? Categories { get; set; }
[Parameter] public EventCallback OnSubmit { get; set; } [Parameter] public EventCallback OnSubmit { get; set; }
@@ -33,7 +36,6 @@ public partial class GameCreationForm
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
await Js.InvokeVoidAsync("resizeGameForm"); await Js.InvokeVoidAsync("resizeGameForm");
} }
private void HandleOnCancel() private void HandleOnCancel()
@@ -52,7 +54,9 @@ public partial class GameCreationForm
{ {
IsLoading = true; IsLoading = true;
GameHelper.WriteTrackingDto(GameDto); var authState = await AuthenticationState.GetAuthenticationStateAsync();
GameHelper.WriteTrackingDto(GameDto, authState);
var gameId = await GameGateway.CreateGame(GameDto); var gameId = await GameGateway.CreateGame(GameDto);
if (gameId != 0) if (gameId != 0)
@@ -68,6 +72,7 @@ public partial class GameCreationForm
finally finally
{ {
IsLoading = false; IsLoading = false;
StateHasChanged();
} }
} }
} }

View File

@@ -4,6 +4,8 @@
@using GameIdeas.BlazorApp.Shared.Components.Select.Models @using GameIdeas.BlazorApp.Shared.Components.Select.Models
@using GameIdeas.BlazorApp.Shared.Models @using GameIdeas.BlazorApp.Shared.Models
@using GameIdeas.Resources @using GameIdeas.Resources
@using GameIdeas.Shared.Constants
@using Microsoft.AspNetCore.Components.Authorization
@inherits ComponentBase @inherits ComponentBase
@@ -15,23 +17,26 @@
@ChildContent @ChildContent
<div class="account-add-container"> <div class="account-add-container">
<div class="add-container"> <AuthorizeView Roles="@GlobalConstants.ADMIN_MEMBER">
<div class="add-buttons"> <Authorized>
<div class="first-button button"> <div class="add-buttons">
<svg class="button-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <div class="first-button button">
<path d="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z" />
</svg>
</div>
<Select @ref="SelectListAdd" TItem="KeyValuePair<AddType, string>" THeader="object"
ValuesChanged=HandleAddTypeClicked Params=SelectParams Theme="SelectTheme.Navigation">
<div class="second-button button">
<svg class="button-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <svg class="button-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M1 3H23L12 22" /> <path d="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z" />
</svg> </svg>
</div> </div>
</Select> <Select @ref="SelectListAdd" TItem="KeyValuePair<AddType, string>" THeader="object"
</div> ValuesChanged=HandleAddTypeClicked Params=SelectParams Theme="SelectTheme.Navigation">
</div> <div class="second-button button">
<svg class="button-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M1 3H23L12 22" />
</svg>
</div>
</Select>
</div>
</Authorized>
</AuthorizeView>
<UserMenu /> <UserMenu />
</div> </div>
</div> </div>

View File

@@ -29,15 +29,12 @@
align-items: flex-end; align-items: flex-end;
} }
.add-container {
margin-right: 40px;
}
.add-buttons { .add-buttons {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
background: var(--violet); background: var(--violet);
border-radius: var(--small-radius); border-radius: var(--small-radius);
margin-right: 40px;
} }
.button { .button {

View File

@@ -12,36 +12,34 @@
@if (ContentVisile) @if (ContentVisile)
{ {
<div class="content"> <div class="content">
<CascadingAuthenticationState> <AuthorizeView Roles="@GlobalConstants.ADMIN_MEMBER">
<AuthorizeView Roles="@($"{GlobalConstants.ADMINISTRATOR}, {GlobalConstants.MEMBER}")"> <Authorized>
<Authorized> <div class="menu-element">
<div class="menu-element"> @ResourcesKey.CategoriesManager
@ResourcesKey.CategoriesManager </div>
</div> <span class="line"></span>
<span class="line"></span> </Authorized>
</Authorized> </AuthorizeView>
</AuthorizeView>
<AuthorizeView Roles="@GlobalConstants.ADMINISTRATOR"> <AuthorizeView Roles="@GlobalConstants.ADMINISTRATOR">
<Authorized> <Authorized>
<div class="menu-element"> <div class="menu-element">
@ResourcesKey.UserManager @ResourcesKey.UserManager
</div> </div>
<span class="line"></span> <span class="line"></span>
</Authorized> </Authorized>
</AuthorizeView> </AuthorizeView>
<AuthorizeView> <AuthorizeView>
<Authorized> <Authorized>
<div class="menu-element" @onclick="HandleLogoutClicked"> <div class="menu-element" @onclick="HandleLogoutClicked">
@ResourcesKey.Logout @ResourcesKey.Logout
</div> </div>
</Authorized> </Authorized>
<NotAuthorized> <NotAuthorized>
<Login AuthGateway="AuthGateway" /> <Login AuthGateway="AuthGateway" />
</NotAuthorized> </NotAuthorized>
</AuthorizeView> </AuthorizeView>
</CascadingAuthenticationState>
</div> </div>
} }
</div> </div>

View File

@@ -3,10 +3,15 @@ using System.Net.Http.Headers;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Text.Json; using System.Text.Json;
using System.Text; using System.Text;
using Blazored.LocalStorage;
using GameIdeas.Shared.Constants;
namespace GameIdeas.BlazorApp.Services; namespace GameIdeas.BlazorApp.Services;
public class HttpClientService(IHttpClientFactory httpClientFactory, ILoggerFactory loggerFactory) : IHttpClientService public class HttpClientService(
IHttpClientFactory httpClientFactory,
ILoggerFactory loggerFactory,
ILocalStorageService localStorage) : IHttpClientService
{ {
private readonly HttpClient httpClient = httpClientFactory.CreateClient("GameIdeas.WebAPI"); private readonly HttpClient httpClient = httpClientFactory.CreateClient("GameIdeas.WebAPI");
private readonly ILogger<HttpClientService> logger = loggerFactory.CreateLogger<HttpClientService>(); private readonly ILogger<HttpClientService> logger = loggerFactory.CreateLogger<HttpClientService>();
@@ -25,6 +30,8 @@ public class HttpClientService(IHttpClientFactory httpClientFactory, ILoggerFact
public async Task<T?> PostAsync<T>(string url, object data) public async Task<T?> PostAsync<T>(string url, object data)
{ {
await SetAuthorizationHeader();
var jsonContent = JsonSerializer.Serialize(data, _optionsCamelCase); var jsonContent = JsonSerializer.Serialize(data, _optionsCamelCase);
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json"); var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync(url, content); var response = await httpClient.PostAsync(url, content);
@@ -32,8 +39,11 @@ public class HttpClientService(IHttpClientFactory httpClientFactory, ILoggerFact
return await GetResultValue<T>(response, ResourcesKey.ErrorWhenPostingData); return await GetResultValue<T>(response, ResourcesKey.ErrorWhenPostingData);
} }
public async Task<T?> PutAsync<T>(string url, object data) public async Task<T?> PutAsync<T>(string url, object data)
{ {
await SetAuthorizationHeader();
var jsonContent = JsonSerializer.Serialize(data, _optionsCamelCase); var jsonContent = JsonSerializer.Serialize(data, _optionsCamelCase);
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json"); var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
var response = await httpClient.PutAsync(url, content); var response = await httpClient.PutAsync(url, content);
@@ -43,6 +53,7 @@ public class HttpClientService(IHttpClientFactory httpClientFactory, ILoggerFact
public async Task<T?> DeleteAsync<T>(string? url) public async Task<T?> DeleteAsync<T>(string? url)
{ {
await SetAuthorizationHeader();
var response = await httpClient.DeleteAsync(url); var response = await httpClient.DeleteAsync(url);
return await GetResultValue<T>(response, ResourcesKey.ErrorWhenDeletingData); return await GetResultValue<T>(response, ResourcesKey.ErrorWhenDeletingData);
@@ -50,6 +61,7 @@ public class HttpClientService(IHttpClientFactory httpClientFactory, ILoggerFact
public async Task<T?> FetchDataAsync<T>(string? url) public async Task<T?> FetchDataAsync<T>(string? url)
{ {
await SetAuthorizationHeader();
var response = await httpClient.GetAsync(url); var response = await httpClient.GetAsync(url);
return await GetResultValue<T>(response, ResourcesKey.ErrorWhenFetchingData); return await GetResultValue<T>(response, ResourcesKey.ErrorWhenFetchingData);
@@ -57,6 +69,7 @@ public class HttpClientService(IHttpClientFactory httpClientFactory, ILoggerFact
public async Task<byte[]?> FetchBytesAsync(string? url) public async Task<byte[]?> FetchBytesAsync(string? url)
{ {
await SetAuthorizationHeader();
var response = await httpClient.GetAsync(url); var response = await httpClient.GetAsync(url);
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
@@ -71,6 +84,7 @@ public class HttpClientService(IHttpClientFactory httpClientFactory, ILoggerFact
public async Task<Stream?> FetchStreamAsync(string? url) public async Task<Stream?> FetchStreamAsync(string? url)
{ {
await SetAuthorizationHeader();
var response = await httpClient.GetAsync(url); var response = await httpClient.GetAsync(url);
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
@@ -84,6 +98,8 @@ public class HttpClientService(IHttpClientFactory httpClientFactory, ILoggerFact
public async Task<T?> PostFileAsync<T>(string? url, Stream fileStream, string fileName, string contentType) public async Task<T?> PostFileAsync<T>(string? url, Stream fileStream, string fileName, string contentType)
{ {
await SetAuthorizationHeader();
using var content = new MultipartFormDataContent(); using var content = new MultipartFormDataContent();
var streamContent = new StreamContent(fileStream); var streamContent = new StreamContent(fileStream);
@@ -122,4 +138,11 @@ public class HttpClientService(IHttpClientFactory httpClientFactory, ILoggerFact
throw new HttpRequestException( throw new HttpRequestException(
$"{errorMessage} + StatusCode: {response.StatusCode} + Reason: {response.ReasonPhrase}"); $"{errorMessage} + StatusCode: {response.StatusCode} + Reason: {response.ReasonPhrase}");
} }
private async Task SetAuthorizationHeader()
{
var token = await localStorage.GetItemAsStringAsync(GlobalConstants.LS_AUTH_STORAGE_KEY);
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("bearer", token);
}
} }

View File

@@ -2,6 +2,7 @@
using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims; using System.Security.Claims;
using System.IdentityModel.Tokens.Jwt; using System.IdentityModel.Tokens.Jwt;
using GameIdeas.Shared.Constants;
namespace GameIdeas.BlazorApp.Services; namespace GameIdeas.BlazorApp.Services;
@@ -9,7 +10,7 @@ public class JwtAuthenticationStateProvider(ILocalStorageService localStorage) :
{ {
public override async Task<AuthenticationState> GetAuthenticationStateAsync() public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{ {
var savedToken = await localStorage.GetItemAsStringAsync("authToken"); var savedToken = await localStorage.GetItemAsStringAsync(GlobalConstants.LS_AUTH_STORAGE_KEY);
if (!string.IsNullOrWhiteSpace(savedToken)) if (!string.IsNullOrWhiteSpace(savedToken))
{ {
@@ -23,7 +24,7 @@ public class JwtAuthenticationStateProvider(ILocalStorageService localStorage) :
} }
catch catch
{ {
await localStorage.RemoveItemAsync("authToken"); await localStorage.RemoveItemAsync(GlobalConstants.LS_AUTH_STORAGE_KEY);
} }
} }
@@ -32,14 +33,14 @@ public class JwtAuthenticationStateProvider(ILocalStorageService localStorage) :
public async Task NotifyUserAuthenticationAsync(string token) public async Task NotifyUserAuthenticationAsync(string token)
{ {
await localStorage.SetItemAsStringAsync("authToken", token); await localStorage.SetItemAsStringAsync(GlobalConstants.LS_AUTH_STORAGE_KEY, token);
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
} }
public async Task NotifyUserLogoutAsync() public async Task NotifyUserLogoutAsync()
{ {
await localStorage.RemoveItemAsync("authToken"); await localStorage.RemoveItemAsync(GlobalConstants.LS_AUTH_STORAGE_KEY);
var nobody = new ClaimsPrincipal(new ClaimsIdentity()); var nobody = new ClaimsPrincipal(new ClaimsIdentity());
NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(nobody))); NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(nobody)));

View File

@@ -6,11 +6,14 @@ public class GlobalConstants
public readonly static Guid ADMINISTRATOR_USER_ID = Guid.Parse("{2AB56FCB-0CDE-4DAE-AC9C-FC7635B0D18A}"); public readonly static Guid ADMINISTRATOR_USER_ID = Guid.Parse("{2AB56FCB-0CDE-4DAE-AC9C-FC7635B0D18A}");
public readonly static Guid MEMBER_ID = Guid.Parse("{BCE14DEA-1748-4A76-8485-ADEE83DF5EFD}"); public readonly static Guid MEMBER_ID = Guid.Parse("{BCE14DEA-1748-4A76-8485-ADEE83DF5EFD}");
public readonly static string ADMINISTRATOR = "Administrateur"; public const string ADMINISTRATOR = "Administrateur";
public readonly static string MEMBER = "Membre"; public const string MEMBER = "Membre";
public const string ADMIN_MEMBER = $"{ADMINISTRATOR}, {MEMBER}";
public readonly static int JWT_DURATION_HOUR = 12; public const int JWT_DURATION_HOUR = 12;
public readonly static int NUMBER_PER_PAGE = 50; public const int NUMBER_PER_PAGE = 50;
public const string LS_AUTH_STORAGE_KEY = "authToken";
} }

View File

@@ -6,9 +6,9 @@ public class GameDetailDto
public string? Title { get; set; } public string? Title { get; set; }
public DateTime? ReleaseDate { get; set; } public DateTime? ReleaseDate { get; set; }
public DateTime? CreationDate { get; set; } public DateTime? CreationDate { get; set; }
public int CreationUserId { get; set; } public string CreationUserId { get; set; } = string.Empty;
public DateTime? ModificationDate { get; set; } public DateTime? ModificationDate { get; set; }
public int? ModificationUserId { get; set; } public string? ModificationUserId { get; set; }
public double? StorageSpace { get; set; } public double? StorageSpace { get; set; }
public string? Description { get; set; } public string? Description { get; set; }
public int Interest { get; set; } = 3; public int Interest { get; set; } = 3;

View File

@@ -1,5 +1,7 @@
using GameIdeas.Shared.Dto; using GameIdeas.Shared.Constants;
using GameIdeas.Shared.Dto;
using GameIdeas.WebAPI.Services.Games; using GameIdeas.WebAPI.Services.Games;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace GameIdeas.WebAPI.Controllers; namespace GameIdeas.WebAPI.Controllers;
@@ -42,6 +44,7 @@ public class GameController(
} }
} }
[Authorize(Roles = GlobalConstants.ADMIN_MEMBER)]
[HttpPost("Create")] [HttpPost("Create")]
public async Task<ActionResult<int>> CreateGame([FromBody] GameDetailDto game) public async Task<ActionResult<int>> CreateGame([FromBody] GameDetailDto game)
{ {
@@ -57,6 +60,7 @@ public class GameController(
} }
} }
[Authorize(Roles = GlobalConstants.ADMIN_MEMBER)]
[HttpPut("Update")] [HttpPut("Update")]
public async Task<ActionResult<int>> UpdateGame([FromBody] GameDetailDto game) public async Task<ActionResult<int>> UpdateGame([FromBody] GameDetailDto game)
{ {
@@ -72,6 +76,7 @@ public class GameController(
} }
} }
[Authorize(Roles = GlobalConstants.ADMIN_MEMBER)]
[HttpDelete("Delete/{id:int}")] [HttpDelete("Delete/{id:int}")]
public async Task<ActionResult<bool>> DeleteGame(int id) public async Task<ActionResult<bool>> DeleteGame(int id)
{ {

View File

@@ -28,6 +28,7 @@ public class UserService(UserManager<User> userManager) : IUserService
List<Claim> authClaims = List<Claim> authClaims =
[ [
new Claim(ClaimTypes.Name, user.UserName ?? string.Empty), new Claim(ClaimTypes.Name, user.UserName ?? string.Empty),
new Claim(ClaimTypes.Sid, user.Id),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
]; ];