Files
itworx-knowledge-hub-Public/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/AuthEndpoints.cs
T

139 lines
5.7 KiB
C#

using ItWorx.KnowledgeHub.Api.Security;
using ItWorx.KnowledgeHub.Application.Identity;
using ItWorx.KnowledgeHub.Application.Identity.UseCases;
using ItWorx.KnowledgeHub.Application.Ingestion;
using ItWorx.KnowledgeHub.Application.Platform;
using ItWorx.KnowledgeHub.Contracts.Auth;
using ItWorx.KnowledgeHub.Contracts.Errors;
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
namespace ItWorx.KnowledgeHub.Api.Endpoints;
public static class AuthEndpoints
{
public static IEndpointRouteBuilder MapAuthEndpoints(this IEndpointRouteBuilder app)
{
var group = app.MapGroup("/api/v1/auth").WithTags("Auth");
group.MapGet("/csrf", GetCsrfTokenAsync);
group.MapGet("/session", GetSessionAsync);
group.MapPost("/login", LoginAsync)
.RequireRateLimiting(RateLimitPolicies.Login);
group.MapPost("/logout", LogoutAsync)
.AddEndpointFilter<AntiforgeryEndpointFilter>();
group.MapPost("/recovery/redeem", RedeemRecoveryAsync)
.RequireRateLimiting(RateLimitPolicies.Recovery);
return app;
}
private static Ok<CsrfTokenResponse> GetCsrfTokenAsync(HttpContext httpContext, IAntiforgery antiforgery)
{
var tokens = antiforgery.GetAndStoreTokens(httpContext);
return TypedResults.Ok(new CsrfTokenResponse(tokens.RequestToken!, tokens.HeaderName!));
}
private static async Task<Ok<SessionResponse>> GetSessionAsync(
GetSessionUseCase useCase,
IPlatformSettingsReader platformSettings,
CancellationToken cancellationToken)
{
var session = await useCase.ExecuteAsync(cancellationToken);
var ingestion = (await platformSettings.GetEffectiveAsync(cancellationToken)).Values.IngestionProposals;
return TypedResults.Ok(ToSessionResponse(session, ingestion));
}
private static async Task<Results<Ok<SessionResponse>, ProblemHttpResult>> LoginAsync(
[FromBody] LoginRequest request,
LoginUseCase loginUseCase,
GetSessionUseCase sessionUseCase,
IPlatformSettingsReader platformSettings,
HttpContext httpContext,
CancellationToken cancellationToken)
{
var correlationId = httpContext.Items[CorrelationIdMiddleware.ItemKey] as string;
var outcome = await loginUseCase.ExecuteAsync(
new LoginCommand(request.Email, request.Password, correlationId),
cancellationToken);
switch (outcome.Status)
{
case PasswordSignInStatus.Success:
var session = await sessionUseCase.ExecuteAsync(cancellationToken);
var ingestion = (await platformSettings.GetEffectiveAsync(cancellationToken)).Values.IngestionProposals;
return TypedResults.Ok(ToSessionResponse(session, ingestion));
case PasswordSignInStatus.LockedOut:
return TypedResults.Problem(
statusCode: StatusCodes.Status429TooManyRequests,
type: ProblemTypes.AccountLockedOut,
title: "Account tijdelijk vergrendeld.",
detail: "Te veel mislukte aanmeldpogingen. Probeer het later opnieuw.");
// Deliberately identical to the default InvalidCredentials case: a disabled
// account must not be distinguishable from a wrong password.
case PasswordSignInStatus.AccountDisabled:
default:
return TypedResults.Problem(
statusCode: StatusCodes.Status401Unauthorized,
type: ProblemTypes.InvalidCredentials,
title: "Aanmelden mislukt.",
detail: "E-mailadres of wachtwoord is onjuist.");
}
}
private static async Task<Ok> LogoutAsync(
LogoutUseCase useCase,
HttpContext httpContext,
CancellationToken cancellationToken)
{
var correlationId = httpContext.Items[CorrelationIdMiddleware.ItemKey] as string;
await useCase.ExecuteAsync(correlationId, cancellationToken);
return TypedResults.Ok();
}
private static async Task<Results<Ok, ProblemHttpResult>> RedeemRecoveryAsync(
[FromBody] RecoveryRedeemRequest request,
RedeemRecoveryTokenUseCase useCase,
HttpContext httpContext,
CancellationToken cancellationToken)
{
var correlationId = httpContext.Items[CorrelationIdMiddleware.ItemKey] as string;
var result = await useCase.ExecuteAsync(request.Token, request.NewPassword, correlationId, cancellationToken);
return result.Status switch
{
RedeemRecoveryTokenStatus.Success => TypedResults.Ok(),
RedeemRecoveryTokenStatus.InvalidOrExpiredToken => TypedResults.Problem(
statusCode: StatusCodes.Status400BadRequest,
type: ProblemTypes.InvalidRecoveryToken,
title: "Ongeldige of verlopen herstelcode.",
detail: "Vraag een nieuwe herstelcode aan bij een beheerder."),
_ => TypedResults.Problem(
statusCode: StatusCodes.Status422UnprocessableEntity,
type: ProblemTypes.Validation,
title: "Ongeldig wachtwoord.",
detail: string.Join(" ", result.Errors))
};
}
private static SessionResponse ToSessionResponse(SessionInfo session, ItWorx.KnowledgeHub.Domain.Platform.IngestionProposalPolicyValues ingestionOptions) => new(
session.Authenticated,
session.SetupRequired,
session.UserId,
session.DisplayName,
session.Email,
session.WorkspaceId,
session.Role?.ToString(),
session.Authenticated ? new FeatureFlagsResponse(ingestionOptions.Enabled) : null);
}