139 lines
5.7 KiB
C#
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);
|
|
}
|