Compare commits

...

4 Commits

Author SHA1 Message Date
565252382c feat: add cache control in response
All checks were successful
Build and Deploy Docker Container / build-and-deploy (push) Successful in 1m47s
.NET Test Pipeline / build-and-test (push) Successful in 2m43s
2024-08-12 21:54:05 +03:00
80dc2e412c refactor: change Invoke to async 2024-08-12 21:36:07 +03:00
b1250616a7 refactor: use this in static method 2024-08-10 23:11:43 +03:00
c51a9cecc9 fix: storing data protection keys 2024-08-10 23:03:28 +03:00
10 changed files with 131 additions and 13 deletions

View File

@ -0,0 +1,26 @@
using System;
namespace Mirea.Api.Endpoint.Common.Attributes;
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class CacheMaxAgeAttribute : Attribute
{
public int MaxAge { get; }
public CacheMaxAgeAttribute(int days = 0, int hours = 0, int minutes = 0)
{
MaxAge = (int)new TimeSpan(days, hours, minutes, 0).TotalSeconds;
}
public CacheMaxAgeAttribute(int minutes) : this(0, 0, minutes)
{
}
public CacheMaxAgeAttribute(bool usingSetting = false)
{
if (usingSetting)
MaxAge = -1;
else
MaxAge = 0;
}
}

View File

@ -6,7 +6,7 @@ namespace Mirea.Api.Endpoint.Common.Services;
public static class UrlHelper public static class UrlHelper
{ {
public static string CurrentDomain(HttpContext context) => public static string GetCurrentDomain(this HttpContext context) =>
context.Request.Headers["X-Forwarded-Host"].FirstOrDefault() ?? context.Request.Host.Host; context.Request.Headers["X-Forwarded-Host"].FirstOrDefault() ?? context.Request.Host.Host;
private static string CreateSubPath(string? path) private static string CreateSubPath(string? path)

View File

@ -74,9 +74,11 @@ public partial class SetupController(
Response.Cookies.Append("AuthToken", token, new CookieOptions Response.Cookies.Append("AuthToken", token, new CookieOptions
{ {
Path = UrlHelper.GetSubPathWithoutFirstApiName + "api", Path = UrlHelper.GetSubPathWithoutFirstApiName + "api",
Domain = UrlHelper.CurrentDomain(ControllerContext.HttpContext), Domain = HttpContext.GetCurrentDomain(),
#if !DEBUG
Secure = true, Secure = true,
HttpOnly = true HttpOnly = true
#endif
}); });
return Ok(true); return Ok(true);
} }

View File

@ -30,9 +30,11 @@ public class AuthController(IOptionsSnapshot<Admin> user, AuthService auth, Pass
{ {
Expires = expires, Expires = expires,
Path = UrlHelper.GetSubPathWithoutFirstApiName + "api", Path = UrlHelper.GetSubPathWithoutFirstApiName + "api",
Domain = UrlHelper.CurrentDomain(ControllerContext.HttpContext), Domain = HttpContext.GetCurrentDomain(),
#if !DEBUG
Secure = true, Secure = true,
HttpOnly = true HttpOnly = true
#endif
}; };
Response.Cookies.Append(name, value, cookieOptions); Response.Cookies.Append(name, value, cookieOptions);

View File

@ -17,11 +17,14 @@ using System.Threading.Tasks;
namespace Mirea.Api.Endpoint.Controllers.V1; namespace Mirea.Api.Endpoint.Controllers.V1;
[ApiVersion("1.0")] [ApiVersion("1.0")]
[CacheMaxAge(true)]
public class ScheduleController(IMediator mediator, IOptionsSnapshot<GeneralConfig> config) : BaseController public class ScheduleController(IMediator mediator, IOptionsSnapshot<GeneralConfig> config) : BaseController
{ {
[CacheMaxAge(1, 0)]
[HttpGet("StartTerm")] [HttpGet("StartTerm")]
public ActionResult<DateOnly> GetStartTerm() => config.Value.ScheduleSettings!.StartTerm; public ActionResult<DateOnly> GetStartTerm() => config.Value.ScheduleSettings!.StartTerm;
[CacheMaxAge(1, 0)]
[HttpGet("PairPeriod")] [HttpGet("PairPeriod")]
public ActionResult<Dictionary<int, PairPeriodTime>> GetPairPeriod() => config.Value.ScheduleSettings!.PairPeriod.ConvertToDto(); public ActionResult<Dictionary<int, PairPeriodTime>> GetPairPeriod() => config.Value.ScheduleSettings!.PairPeriod.ConvertToDto();
@ -196,4 +199,4 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshot<GeneralConf
Professors = professors, Professors = professors,
LectureHalls = lectureHalls LectureHalls = lectureHalls
}); });
} }

View File

@ -0,0 +1,81 @@
using Cronos;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Mirea.Api.Endpoint.Common.Attributes;
using Mirea.Api.Endpoint.Common.Settings;
using System;
using System.Reflection;
using System.Threading.Tasks;
namespace Mirea.Api.Endpoint.Middleware;
public class CacheMaxAgeMiddleware(RequestDelegate next, IServiceProvider serviceProvider)
{
public async Task InvokeAsync(HttpContext context)
{
if (!context.Response.StatusCode.ToString().StartsWith('2'))
{
await next(context);
return;
}
var endpoint = context.GetEndpoint();
var actionDescriptor = endpoint?.Metadata.GetMetadata<Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor>();
if (actionDescriptor == null)
{
await next(context);
return;
}
var controllerType = actionDescriptor.ControllerTypeInfo;
var methodInfo = actionDescriptor.MethodInfo;
var maxAgeAttribute = methodInfo.GetCustomAttribute<CacheMaxAgeAttribute>() ?? controllerType.GetCustomAttribute<CacheMaxAgeAttribute>();
if (maxAgeAttribute == null)
{
await next(context);
return;
}
switch (maxAgeAttribute.MaxAge)
{
case < 0:
{
DateTime? nextDate;
var now = DateTime.UtcNow;
using (var scope = serviceProvider.CreateScope())
{
var updateCronString = scope.ServiceProvider.GetRequiredService<IOptionsSnapshot<GeneralConfig>>().Value.ScheduleSettings?.CronUpdateSchedule;
if (string.IsNullOrEmpty(updateCronString) ||
!CronExpression.TryParse(updateCronString, CronFormat.Standard, out var updateCron))
{
await next(context);
return;
}
nextDate = updateCron.GetNextOccurrence(now);
}
if (!nextDate.HasValue)
{
await next(context);
return;
}
context.Response.Headers.CacheControl = "max-age=" + (int)(nextDate.Value - now).TotalSeconds;
break;
}
case > 0:
context.Response.Headers.CacheControl = "max-age=" + maxAgeAttribute.MaxAge;
break;
}
await next(context);
}
}

View File

@ -11,7 +11,7 @@ namespace Mirea.Api.Endpoint.Middleware;
public class CustomExceptionHandlerMiddleware(RequestDelegate next) public class CustomExceptionHandlerMiddleware(RequestDelegate next)
{ {
public async Task Invoke(HttpContext context) public async Task InvokeAsync(HttpContext context)
{ {
try try
{ {

View File

@ -6,7 +6,7 @@ namespace Mirea.Api.Endpoint.Middleware;
public class JwtRevocationMiddleware(RequestDelegate next) public class JwtRevocationMiddleware(RequestDelegate next)
{ {
public async Task Invoke(HttpContext context, IRevokedToken revokedTokenStore) public async Task InvokeAsync(HttpContext context, IRevokedToken revokedTokenStore)
{ {
if (context.Request.Headers.ContainsKey("Authorization")) if (context.Request.Headers.ContainsKey("Authorization"))
{ {

View File

@ -13,7 +13,7 @@ public class MaintenanceModeMiddleware(RequestDelegate next, IMaintenanceModeSer
return endpoint?.Metadata.GetMetadata<MaintenanceModeIgnoreAttribute>() != null; return endpoint?.Metadata.GetMetadata<MaintenanceModeIgnoreAttribute>() != null;
} }
public async Task Invoke(HttpContext context) public async Task InvokeAsync(HttpContext context)
{ {
if (!maintenanceModeService.IsMaintenanceMode && !maintenanceModeNotConfigureService.IsMaintenanceMode || IsIgnoreMaintenanceMode(context)) if (!maintenanceModeService.IsMaintenanceMode && !maintenanceModeNotConfigureService.IsMaintenanceMode || IsIgnoreMaintenanceMode(context))
await next(context); await next(context);

View File

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
@ -94,13 +95,15 @@ public class Program
builder.Services.AddJwtToken(builder.Configuration); builder.Services.AddJwtToken(builder.Configuration);
builder.Services.AddSecurity(builder.Configuration); builder.Services.AddSecurity(builder.Configuration);
builder.Services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(PathBuilder.Combine("DataProtection")));
var app = builder.Build(); var app = builder.Build();
app.UseForwardedHeaders(); app.UseForwardedHeaders();
app.UseStaticFiles(UrlHelper.GetSubPath.TrimEnd('/')); app.UseStaticFiles(UrlHelper.GetSubPath.TrimEnd('/'));
app.UseCors("AllowAll"); app.UseCors("AllowAll");
app.UseCustomSerilog(); app.UseCustomSerilog();
app.UseForwardedHeaders();
using (var scope = app.Services.CreateScope()) using (var scope = app.Services.CreateScope())
{ {
@ -122,9 +125,10 @@ public class Program
app.UseCustomSwagger(app.Services); app.UseCustomSwagger(app.Services);
app.UseMiddleware<MaintenanceModeMiddleware>();
app.UseMiddleware<CustomExceptionHandlerMiddleware>(); app.UseMiddleware<CustomExceptionHandlerMiddleware>();
app.UseMiddleware<MaintenanceModeMiddleware>();
app.UseMiddleware<JwtRevocationMiddleware>(); app.UseMiddleware<JwtRevocationMiddleware>();
app.UseMiddleware<CacheMaxAgeMiddleware>();
app.UseHttpsRedirection(); app.UseHttpsRedirection();