This commit is contained in:
john 2025-05-15 17:47:20 +02:00
parent 0dc41337da
commit 14fd359ea8
28 changed files with 156 additions and 52 deletions

View file

@ -0,0 +1,53 @@
using System.Security.Claims;
using System.Text.Encodings.Web;
using Femto.Api.Sessions;
using Femto.Common;
using Femto.Modules.Auth.Application;
using Femto.Modules.Auth.Application.Commands.ValidateSession;
using Femto.Modules.Auth.Errors;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;
namespace Femto.Api.Auth;
internal class SessionAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
IAuthenticationModule authModule,
CurrentUserContext currentUserContext
) : AuthenticationHandler<AuthenticationSchemeOptions>(options, logger, encoder)
{
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
var sessionId = this.Request.Cookies["session"];
if (string.IsNullOrWhiteSpace(sessionId))
return AuthenticateResult.NoResult();
try
{
var result = await authModule.PostCommand(new ValidateSessionCommand(sessionId));
var claims = new List<Claim>
{
new(ClaimTypes.Name, result.Username),
new("sub", result.UserId.ToString()),
new("user_id", result.UserId.ToString()),
};
var identity = new ClaimsIdentity(claims, this.Scheme.Name);
var principal = new ClaimsPrincipal(identity);
this.Context.SetSession(result.Session);
currentUserContext.CurrentUser = new CurrentUser(result.UserId, result.Username);
return AuthenticateResult.Success(
new AuthenticationTicket(principal, this.Scheme.Name)
);
}
catch (InvalidSessionError)
{
return AuthenticateResult.Fail("Invalid session");
}
}
}

View file

@ -22,8 +22,8 @@ public class AuthController(IAuthenticationModule authModule) : ControllerBase
return new LoginResponse(result.UserId, result.Username);
}
[HttpPost("signup")]
public async Task<ActionResult<SignupResponse>> Signup([FromBody] SignupRequest request)
[HttpPost("register")]
public async Task<ActionResult<RegisterResponse>> Register([FromBody] RegisterRequest request)
{
var result = await authModule.PostCommand(
new RegisterCommand(request.Username, request.Password)
@ -31,7 +31,7 @@ public class AuthController(IAuthenticationModule authModule) : ControllerBase
HttpContext.SetSession(result.Session);
return new SignupResponse(result.UserId, result.Username);
return new RegisterResponse(result.UserId, result.Username);
}
[HttpPost("delete-session")]

View file

@ -0,0 +1,3 @@
namespace Femto.Api.Controllers.Auth;
public record RegisterRequest(string Username, string Password, string SignupCode, string? Email);

View file

@ -0,0 +1,3 @@
namespace Femto.Api.Controllers.Auth;
public record RegisterResponse(Guid UserId, string Username);

View file

@ -1,3 +0,0 @@
namespace Femto.Api.Controllers.Auth;
public record SignupRequest(string Username, string Password, string SignupCode, string? Email);

View file

@ -1,3 +0,0 @@
namespace Femto.Api.Controllers.Auth;
public record SignupResponse(Guid UserId, string Username);

View file

@ -4,6 +4,7 @@ using Femto.Modules.Blog.Application;
using Femto.Modules.Blog.Application.Commands.CreatePost;
using Femto.Modules.Blog.Application.Queries.GetPosts;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Femto.Api.Controllers.Posts;
@ -13,6 +14,7 @@ namespace Femto.Api.Controllers.Posts;
public class PostsController(IBlogModule blogModule) : ControllerBase
{
[HttpGet]
[Authorize]
public async Task<ActionResult<GetAllPublicPostsResponse>> GetAllPublicPosts(
[FromQuery] GetPublicPostsSearchParams searchParams,
CancellationToken cancellationToken

View file

@ -0,0 +1,8 @@
using Femto.Common;
namespace Femto.Api;
internal class CurrentUserContext : ICurrentUserContext
{
public CurrentUser? CurrentUser { get; set; }
}

View file

@ -28,4 +28,8 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Folder Include="Middleware\" />
</ItemGroup>
</Project>

View file

@ -1,8 +1,14 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Femto.Api;
using Femto.Api.Auth;
using Femto.Common;
using Femto.Modules.Auth.Application;
using Femto.Modules.Blog.Application;
using Femto.Modules.Media.Application;
using Microsoft.AspNetCore.Authentication;
const string CorsPolicyName = "DefaultCorsPolicy";
var builder = WebApplication.CreateBuilder(args);
@ -20,15 +26,18 @@ builder.Services.InitializeBlogModule(connectionString);
builder.Services.InitializeMediaModule(connectionString, blobStorageRoot);
builder.Services.InitializeAuthenticationModule(connectionString);
builder.Services.AddScoped<CurrentUserContext, CurrentUserContext>();
builder.Services.AddScoped<ICurrentUserContext>(s => s.GetRequiredService<CurrentUserContext>());
builder.Services.AddControllers();
builder.Services.AddCors(options =>
{
options.AddPolicy(
"DefaultCorsPolicy",
CorsPolicyName,
b =>
{
b.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin();
b.AllowAnyHeader().AllowAnyMethod().WithOrigins("http://localhost:5173");
}
);
});
@ -44,9 +53,18 @@ builder
options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString;
});
builder
.Services.AddAuthentication("SessionAuth")
.AddScheme<AuthenticationSchemeOptions, SessionAuthenticationHandler>(
"SessionAuth",
options => { }
);
builder.Services.AddAuthorization(); // if not already added
var app = builder.Build();
app.UseCors("DefaultCorsPolicy");
app.UseCors(CorsPolicyName);
if (app.Environment.IsDevelopment())
{

View file

@ -12,8 +12,8 @@ internal static class HttpContextSessionExtensions
new CookieOptions
{
HttpOnly = true,
Secure = true,
SameSite = SameSiteMode.Strict,
// Secure = true,
// SameSite = SameSiteMode.Strict,
Expires = session.Expires,
}
);