diff --git a/src/Guestbooky/Guestbooky.API/Configurations/APISettings.cs b/src/Guestbooky/Guestbooky.API/Configurations/APISettings.cs new file mode 100644 index 0000000..048568c --- /dev/null +++ b/src/Guestbooky/Guestbooky.API/Configurations/APISettings.cs @@ -0,0 +1,8 @@ +using Guestbooky.API.Enums; + +namespace Guestbooky.API.Configurations; + +public class APISettings +{ + public required RunningEnvironment RunningEnvironment { get; set; } +} \ No newline at end of file diff --git a/src/Guestbooky/Guestbooky.API/Controllers/AuthController.cs b/src/Guestbooky/Guestbooky.API/Controllers/AuthController.cs index 88cbd0b..fdfa2a0 100644 --- a/src/Guestbooky/Guestbooky.API/Controllers/AuthController.cs +++ b/src/Guestbooky/Guestbooky.API/Controllers/AuthController.cs @@ -1,4 +1,5 @@ -using Guestbooky.API.DTOs.Auth; +using Guestbooky.API.Configurations; +using Guestbooky.API.DTOs.Auth; using Guestbooky.Application.UseCases.AuthenticateUser; using Guestbooky.Application.UseCases.RefreshToken; using MediatR; @@ -12,11 +13,13 @@ public class AuthController : ControllerBase { private readonly IMediator _mediator; private readonly ILogger _logger; + private readonly APISettings _apiSettings; - public AuthController(IMediator mediator, ILogger logger) + public AuthController(IMediator mediator, ILogger logger, APISettings apiSettings) { _mediator = mediator; _logger = logger; + _apiSettings = apiSettings; } [ProducesResponseType(typeof(LoginResponseDto), 200)] @@ -35,6 +38,7 @@ public class AuthController : ControllerBase if(result.IsAuthenticated) { _logger.LogInformation("Authentication successful. Returning LoginResponse."); + SetJwtCookie(result.Token); return Ok(new LoginResponseDto(result.Token, result.RefreshToken)); } else @@ -80,4 +84,16 @@ public class AuthController : ControllerBase return Problem($"An error occurred on the server: {e.Message}", statusCode: StatusCodes.Status500InternalServerError); } } + + private void SetJwtCookie(string token) + { + var cookieOptions = new CookieOptions + { + HttpOnly = true, + Secure = _apiSettings.RunningEnvironment == Enums.RunningEnvironment.Production, + SameSite = SameSiteMode.Strict, + Expires = DateTimeOffset.UtcNow.AddHours(2) + }; + Response.Cookies.Append("token", token, cookieOptions); + } } diff --git a/src/Guestbooky/Guestbooky.API/Enums/RunningEnvironment.cs b/src/Guestbooky/Guestbooky.API/Enums/RunningEnvironment.cs new file mode 100644 index 0000000..13da4b2 --- /dev/null +++ b/src/Guestbooky/Guestbooky.API/Enums/RunningEnvironment.cs @@ -0,0 +1,8 @@ +namespace Guestbooky.API.Enums; + +public enum RunningEnvironment +{ + Unknown = 0, + Development, + Production +} \ No newline at end of file diff --git a/src/Guestbooky/Guestbooky.API/Program.cs b/src/Guestbooky/Guestbooky.API/Program.cs index c804c51..32ccf82 100644 --- a/src/Guestbooky/Guestbooky.API/Program.cs +++ b/src/Guestbooky/Guestbooky.API/Program.cs @@ -1,11 +1,13 @@ using Guestbooky.Application.DependencyInjection; using Guestbooky.Infrastructure.DependencyInjection; using Guestbooky.Infrastructure.Environment; +using Guestbooky.API.Configurations; +using Guestbooky.API.Enums; +using Guestbooky.API.Validations; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using System.Text; using Serilog; -using Guestbooky.API.Validations; namespace Guestbooky.API { @@ -74,9 +76,10 @@ namespace Guestbooky.API var corsOrigins = builder.Configuration[Constants.CORS_ORIGINS]?.Split(',') ?? Array.Empty(); cfg.AddPolicy(name: "local", policy => { - policy.WithExposedHeaders("Content-Range", "Accept-Ranges") + policy.WithExposedHeaders("Content-Range", "Accept-Ranges", "Set-Cookie") .WithOrigins(corsOrigins) .AllowAnyHeader() + .AllowCredentials() .WithMethods("GET", "POST", "DELETE", "OPTIONS"); }); }); @@ -86,7 +89,11 @@ namespace Guestbooky.API options.InvalidModelStateResponseFactory = InvalidModelStateResponseFactory.DefaultInvalidModelStateResponse; }); - builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + builder.Services.AddAuthentication(o => + { + o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }) .AddJwtBearer(o => { o.RequireHttpsMetadata = false; @@ -102,12 +109,22 @@ namespace Guestbooky.API ValidateLifetime = true, ClockSkew = TimeSpan.Zero }; + o.Events = new JwtBearerEvents + { + OnMessageReceived = context => + { + context.Token = context.Request.Cookies["token"]; + return Task.CompletedTask; + } + }; }); builder.Services.AddInfrastructure(builder.Configuration); builder.Services.AddApplication(); + builder.Services.AddSingleton(new APISettings(){ RunningEnvironment = GetRunningEnvironment(builder.Configuration["ASPNETCORE_ENVIRONMENT"]!) }); + if (builder.Environment.IsDevelopment()) { @@ -170,5 +187,12 @@ namespace Guestbooky.API _ => conf.MinimumLevel.Information() }; + public static RunningEnvironment GetRunningEnvironment(string env) => env.ToUpper() switch + { + "DEVELOPMENT" => RunningEnvironment.Development, + "PRODUCTION" => RunningEnvironment.Production, + _ => RunningEnvironment.Unknown + }; + } } diff --git a/src/Guestbooky/Guestbooky.Infrastructure/Persistence/Repositories/MongoGuestbookMessageRepository.cs b/src/Guestbooky/Guestbooky.Infrastructure/Persistence/Repositories/MongoGuestbookMessageRepository.cs index 47eaef0..d6005db 100644 --- a/src/Guestbooky/Guestbooky.Infrastructure/Persistence/Repositories/MongoGuestbookMessageRepository.cs +++ b/src/Guestbooky/Guestbooky.Infrastructure/Persistence/Repositories/MongoGuestbookMessageRepository.cs @@ -53,7 +53,7 @@ namespace Guestbooky.Infrastructure.Persistence.Repositories var messageDtos = await _messages.Find(_ => true) .SortBy(x => x.Timestamp) .Skip((int?)offset) - .Limit(50) + .Limit(10) .ToCursorAsync(cancellationToken); return messageDtos.ToEnumerable().Select(MapToDomainModel).ToArray(); }