Skip to content

Commit

Permalink
fix(templates): resolve bit boilerplate middleware order issues #6286 (
Browse files Browse the repository at this point in the history
  • Loading branch information
ysmoradi authored Dec 11, 2023
1 parent eece207 commit 81b71d4
Show file tree
Hide file tree
Showing 10 changed files with 76 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,16 @@
"src/Shared/Dtos/Products/**",
"src/Boilerplate.Server/Controllers/Categories/**",
"src/Boilerplate.Server/Controllers/Products/**",
"src/Boilerplate.Server/Controllers/Dashboard/**",
"src/Boilerplate.Server/Data/Configurations/Category/**",
"src/Boilerplate.Server/Data/Configurations/Product/**",
"src/Boilerplate.Server/Mappers/CategoriesMapper.cs",
"src/Boilerplate.Server/Mappers/ProductsMapper.cs",
"src/Boilerplate.Server/Models/Categories/**",
"src/Boilerplate.Server/Models/Products/**",
"src/Client/Boilerplate.Client.Core/Controllers/Categories/**",
"src/Client/Boilerplate.Client.Core/Controllers/Products/**",
"src/Client/Boilerplate.Client.Core/Controllers/Dashboard/**",
"src/Client/Boilerplate.Client.Core/Components/Pages/Categories/**",
"src/Client/Boilerplate.Client.Core/Components/Pages/Dashboard/**",
"src/Client/Boilerplate.Client.Core/Components/Pages/Products/**"]
Expand All @@ -155,6 +159,7 @@
"src/Boilerplate.Server/Controllers/Todo/**",
"src/Boilerplate.Server/Mappers/TodoMapper.cs",
"src/Boilerplate.Server/Models/Todo/**",
"src/Client/Boilerplate.Client.Core/Controllers/Todo/**",
"src/Client/Boilerplate.Client.Core/Components/Pages/Todo/**"]
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ public class AppSecureJwtDataFormat(AppSettings appSettings, TokenValidationPara
{
try
{
if (string.IsNullOrEmpty(protectedText))
{
return NotSignedIn();
}

var handler = new JwtSecurityTokenHandler();
ClaimsPrincipal? principal = handler.ValidateToken(protectedText, validationParameters, out var validToken);
var validJwt = (JwtSecurityToken)validToken;
Expand All @@ -26,9 +31,9 @@ public class AppSecureJwtDataFormat(AppSettings appSettings, TokenValidationPara
}, IdentityConstants.BearerScheme);
return data;
}
catch
catch (Exception exp)
{
return NotSignedIn();
throw new UnauthorizedException(nameof(AppStrings.UnauthorizedException), exp);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Net;
using System.Reflection;
using System.Runtime.Loader;
using System.Web;
using Boilerplate.Client.Core.Services;
using Boilerplate.Server.Components;
using HealthChecks.UI.Client;
Expand All @@ -13,18 +14,33 @@ namespace Boilerplate.Server.Startup;

public class Middlewares
{
/// <summary>
/// https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-8.0#middleware-order
/// </summary>
public static void Use(WebApplication app, IHostEnvironment env, IConfiguration configuration)
{
app.UseForwardedHeaders();

if (AppRenderMode.MultilingualEnabled)
{
var supportedCultures = CultureInfoManager.SupportedCultures.Select(sc => CultureInfoManager.CreateCultureInfo(sc.code)).ToArray();
app.UseRequestLocalization(new RequestLocalizationOptions
{
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures,
ApplyCurrentCultureToResponseHeaders = true
}.SetDefaultCulture(CultureInfoManager.DefaultCulture.code));
}

app.UseExceptionHandler("/", createScopeForErrors: true);

if (env.IsDevelopment())
{
app.UseWebAssemblyDebugging();
}
else
{
app.UseHttpsRedirection();
app.UseResponseCompression();
}

Configure_401_403_404_Pages(app);
Expand All @@ -45,23 +61,17 @@ public static void Use(WebApplication app, IHostEnvironment env, IConfiguration
app.UseCors(options => options.WithOrigins("https://0.0.0.0" /*BlazorHybrid*/, "app://0.0.0.0" /*BlazorHybrid*/)
.AllowAnyHeader().AllowAnyMethod());

app.UseResponseCaching();
app.UseAuthentication();
app.UseAuthorization();
app.UseAntiforgery();

if (AppRenderMode.MultilingualEnabled)
if (env.IsDevelopment() is false)
{
var supportedCultures = CultureInfoManager.SupportedCultures.Select(sc => CultureInfoManager.CreateCultureInfo(sc.code)).ToArray();
app.UseRequestLocalization(new RequestLocalizationOptions
{
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures,
ApplyCurrentCultureToResponseHeaders = true
}.SetDefaultCulture(CultureInfoManager.DefaultCulture.code));
app.UseResponseCompression();
}

app.UseExceptionHandler("/", createScopeForErrors: true);
app.UseResponseCaching();

app.UseAntiforgery();

app.UseSwagger();

Expand Down Expand Up @@ -98,10 +108,15 @@ public static void Use(WebApplication app, IHostEnvironment env, IConfiguration
}

// Handle the rest of requests with blazor
app.MapRazorComponents<App>()
var blazorApp = app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(AssemblyLoadContext.Default.Assemblies.Where(asm => asm.GetName().Name?.Contains("Boilerplate") is true).Except([Assembly.GetExecutingAssembly()]).ToArray());

if (AppRenderMode.PrerenderEnabled is false)
{
blazorApp.AllowAnonymous(); // Server may not check authorization for pages when there's no pre rendering, let the client handle it.
}
}

/// <summary>
Expand All @@ -114,11 +129,6 @@ public static void Use(WebApplication app, IHostEnvironment env, IConfiguration
/// </summary>
private static void Configure_401_403_404_Pages(WebApplication app)
{
if (AppRenderMode.PrerenderEnabled is false)
{
return;
}

app.Use(async (context, next) =>
{
if (context.Request.Path.HasValue)
Expand Down Expand Up @@ -147,7 +157,10 @@ private static void Configure_401_403_404_Pages(WebApplication app)
{
bool is403 = httpContext.Response.StatusCode is 403;

httpContext.Response.Redirect($"/not-authorized?redirect-url={httpContext.Request.GetEncodedPathAndQuery()}&isForbidden={(is403 ? "true" : "false")}");
var qs = HttpUtility.ParseQueryString(httpContext.Request.QueryString.Value ?? string.Empty);
qs.Remove("try_refreshing_token");
var redirectUrl = UriHelper.BuildRelative(httpContext.Request.PathBase, httpContext.Request.Path, new QueryString(qs.ToString()));
httpContext.Response.Redirect($"/not-authorized?redirect-url={redirectUrl}&isForbidden={(is403 ? "true" : "false")}");
}
else if (httpContext.Response.StatusCode is 404 &&
httpContext.GetEndpoint() is null /* Please be aware that certain endpoints, particularly those associated with web API actions, may intentionally return a 404 error. */)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ protected override async Task OnInitAsync()

user = await userController.GetCurrentUser(CurrentCancellationToken);

var access_token = await PrerenderStateService.GetValue($"{nameof(NavMenu)}-access_token", AuthTokenProvider.GetAccessTokenAsync);
profileImageUrlBase = $"{Configuration.GetApiServerAddress()}Attachment/GetProfileImage?access_token={access_token}&file=";
var access_token = await PrerenderStateService.GetValue(AuthTokenProvider.GetAccessTokenAsync);
profileImageUrlBase = $"{Configuration.GetApiServerAddress()}api/Attachment/GetProfileImage?access_token={access_token}&file=";

SetProfileImageUrl();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ protected override async Task OnInitAsync()
{
await LoadEditProfileData();

var access_token = await PrerenderStateService.GetValue($"{nameof(EditProfilePage)}-access_token", AuthTokenProvider.GetAccessTokenAsync);
var access_token = await PrerenderStateService.GetValue(AuthTokenProvider.GetAccessTokenAsync);

profileImageUploadUrl = $"{Configuration.GetApiServerAddress()}Attachment/UploadProfileImage?access_token={access_token}";
profileImageUrl = $"{Configuration.GetApiServerAddress()}Attachment/GetProfileImage?access_token={access_token}";
profileImageRemoveUrl = $"Attachment/RemoveProfileImage?access_token={access_token}";
profileImageUploadUrl = $"{Configuration.GetApiServerAddress()}api/Attachment/UploadProfileImage?access_token={access_token}";
profileImageUrl = $"{Configuration.GetApiServerAddress()}api/Attachment/GetProfileImage?access_token={access_token}";
profileImageRemoveUrl = $"api/Attachment/RemoveProfileImage?access_token={access_token}";
}
finally
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<AuthorizeView>
<div class="main">
<div><img src="images/icons/error-triangle.svg" /></div>
<div><img src="_content/Boilerplate.Client.Core/images/icons/error-triangle.svg" /></div>
<h2 class="title">@Localizer[nameof(AppStrings.ForbiddenException)]</h2>
<h4 class="description">@Localizer[nameof(AppStrings.YouAreSignInAs)] @user.GetUserName()</h4>
<div class="buttons">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ protected override async Task OnAfterFirstRenderAsync()
// Following this procedure, the newly acquired access token may now include the necessary roles or claims.
// To prevent infinitie redirect loop, let's append refresh_token=false to the url, so we only redirect in case no refresh_token=false is present

if (string.IsNullOrEmpty(refresh_token) is false && RedirectUrl?.Contains("refresh_token=false", StringComparison.InvariantCulture) is null or false)
if (string.IsNullOrEmpty(refresh_token) is false && RedirectUrl?.Contains("try_refreshing_token=false", StringComparison.InvariantCulture) is null or false)
{
await AuthenticationManager.RefreshToken();

Expand All @@ -30,7 +30,7 @@ protected override async Task OnAfterFirstRenderAsync()
if (RedirectUrl is not null)
{
var @char = RedirectUrl.Contains('?') ? '&' : '?'; // The RedirectUrl may already include a query string.
NavigationManager.NavigateTo($"{RedirectUrl}{@char}refresh_token=false");
NavigationManager.NavigateTo($"{RedirectUrl}{@char}try_refreshing_token=false");
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ public override async Task<AuthenticationState> GetAuthenticationStateAsync()
try
{
var refreshTokenResponse = await identityController.Refresh(new() { RefreshToken = refresh_token });

await StoreToken(refreshTokenResponse!);
access_token = refreshTokenResponse!.AccessToken;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Boilerplate.Client.Core.Services.Contracts;
using System.Runtime.CompilerServices;

namespace Boilerplate.Client.Core.Services.Contracts;

/// <summary>
/// This service simplifies the process of persisting application state in Pre-Rendering mode
Expand All @@ -13,5 +15,10 @@ public interface IPrerenderStateService
/// one can easily use the following method (<see cref="GetValue"/>) in the OnInit lifecycle method of the Blazor components or pages
/// to retrieve everything that requires an async-await (like current user's info).
/// </summary>
Task<T?> GetValue<T>(Func<Task<T?>> factory,
[CallerLineNumber] int lineNumber = 0,
[CallerMemberName] string memberName = "",
[CallerFilePath] string filePath = "");

Task<T?> GetValue<T>(string key, Func<Task<T?>> factory);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//-:cnd:noEmit

using System.Runtime.CompilerServices;

namespace Boilerplate.Client.Core.Services;

/// <summary>
Expand All @@ -17,6 +19,19 @@ public PrerenderStateService(PersistentComponentState? persistentComponentState
this.persistentComponentState = persistentComponentState;
}

public async Task<T?> GetValue<T>(Func<Task<T?>> factory,
[CallerLineNumber] int lineNumber = 0,
[CallerMemberName] string memberName = "",
[CallerFilePath] string filePath = "")
{
if (AppRenderMode.PrerenderEnabled is false)
return await factory();

string key = $"{filePath.Split('\\').LastOrDefault()} {memberName} {lineNumber}";

return await GetValue(key, factory);
}

public async Task<T?> GetValue<T>(string key, Func<Task<T?>> factory)
{
if (AppRenderMode.PrerenderEnabled is false)
Expand Down

0 comments on commit 81b71d4

Please sign in to comment.