This commit is contained in:
2026-05-31 18:25:40 +02:00
parent 1bd8bb4bd6
commit 1e06f60a1b
7 changed files with 43 additions and 14 deletions
+3 -3
View File
@@ -12,7 +12,7 @@ public sealed class CurrentUser : IBindableFromHttpContext<CurrentUser>
IsAuthenticated = isAuthenticated.HasValue && isAuthenticated.Value; IsAuthenticated = isAuthenticated.HasValue && isAuthenticated.Value;
var rawId = principal.FindFirstValue(ClaimTypes.NameIdentifier); var rawId = principal.FindFirstValue(ClaimTypes.NameIdentifier);
if (Guid.TryParse(rawId, out var id) && id != Guid.Empty) if (long.TryParse(rawId, out var id) && id != 0)
Id = id; Id = id;
Username = principal.FindFirstValue(ClaimTypes.Name); Username = principal.FindFirstValue(ClaimTypes.Name);
@@ -24,7 +24,7 @@ public sealed class CurrentUser : IBindableFromHttpContext<CurrentUser>
public bool IsAuthenticated { get; } public bool IsAuthenticated { get; }
public Guid? Id { get; } public long? Id { get; }
public string? Username { get; } public string? Username { get; }
@@ -32,7 +32,7 @@ public sealed class CurrentUser : IBindableFromHttpContext<CurrentUser>
public string? LastName { get; } public string? LastName { get; }
public Guid RequiredId => Id ?? throw new InvalidOperationException("User Id claim is missing or invalid."); public long RequiredId => Id ?? throw new InvalidOperationException("User Id claim is missing or invalid.");
public bool HasClaim(string type) => Principal.HasClaim(c => c.Type == type); public bool HasClaim(string type) => Principal.HasClaim(c => c.Type == type);
@@ -5,21 +5,49 @@ using System.Security.Claims;
namespace WebApi.Authentication; namespace WebApi.Authentication;
public sealed class JwtBearerEventsHandler(IMediator mediator) : JwtBearerEvents public sealed class JwtBearerEventsHandler(IMediator mediator, ILogger<JwtBearerEventsHandler> logger) : JwtBearerEvents
{ {
public override async Task TokenValidated(TokenValidatedContext tokenValidatedContext) public override async Task TokenValidated(TokenValidatedContext tokenValidatedContext)
{ {
var principal = tokenValidatedContext.Principal!; var principal = tokenValidatedContext.Principal!;
var id = principal.FindFirstValue(ClaimTypes.NameIdentifier)!; var rawId = principal.FindFirstValue(ClaimTypes.NameIdentifier);
var guidId = Guid.Parse(id); if (!long.TryParse(rawId, out var userId))
{
logger.LogWarning("Token subject '{Subject}' is not a numeric id; skipping user provisioning", rawId);
return;
}
var username = principal.FindFirstValue(ClaimTypes.Name)
?? principal.FindFirstValue("preferred_username")
?? principal.FindFirstValue(ClaimTypes.Email)
?? rawId!;
var username = principal.FindFirstValue(ClaimTypes.Name)!;
var firstName = principal.FindFirstValue(ClaimTypes.GivenName); var firstName = principal.FindFirstValue(ClaimTypes.GivenName);
var lastName = principal.FindFirstValue(ClaimTypes.Surname); var lastName = principal.FindFirstValue(ClaimTypes.Surname);
try
{
await mediator.Send( await mediator.Send(
new CreateUserCommand(guidId, username, firstName, lastName), new CreateUserCommand(userId, username, firstName, lastName),
tokenValidatedContext.HttpContext.RequestAborted); tokenValidatedContext.HttpContext.RequestAborted);
} }
catch (Exception exception)
{
logger.LogError(exception, "Failed to provision user {UserId} during token validation", userId);
}
}
public override Task AuthenticationFailed(AuthenticationFailedContext context)
{
logger.LogWarning(context.Exception, "JWT authentication failed");
return Task.CompletedTask;
}
public override Task Challenge(JwtBearerChallengeContext context)
{
if (context.AuthenticateFailure is not null)
logger.LogWarning("JWT challenge: {Error} - {Description}", context.Error, context.ErrorDescription);
return Task.CompletedTask;
}
} }
@@ -1,7 +1,7 @@
namespace WebApi.DataTransferObjects.Users; namespace WebApi.DataTransferObjects.Users;
public record CreateUserRequest( public record CreateUserRequest(
Guid Id, long Id,
string Name, string Name,
string? FirstName, string? FirstName,
string? SecondName); string? SecondName);
+1 -1
View File
@@ -74,7 +74,7 @@ public static class PlayListEndpoints
private static async Task<IResult> GetPlayListsByUserId( private static async Task<IResult> GetPlayListsByUserId(
IMediator mediator, IMediator mediator,
Guid userId, long userId,
CancellationToken cancellationToken, CancellationToken cancellationToken,
string? name, string? name,
int pageNumber = 1, int pageNumber = 1,
+1 -1
View File
@@ -83,7 +83,7 @@ public static class TrackEndpoints
private static async Task<IResult> GetTracksByUserId( private static async Task<IResult> GetTracksByUserId(
IMediator mediator, IMediator mediator,
Guid userId, long userId,
CancellationToken cancellationToken, CancellationToken cancellationToken,
string? name, string? name,
int pageNumber = 1, int pageNumber = 1,
+1 -1
View File
@@ -43,7 +43,7 @@ public static class UserEndpoints
private static async Task<IResult> GetUserById( private static async Task<IResult> GetUserById(
IMediator mediator, IMediator mediator,
Guid id, long id,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var result = await mediator.Send(new GetUserByIdQuery(id), cancellationToken); var result = await mediator.Send(new GetUserByIdQuery(id), cancellationToken);
+1
View File
@@ -25,6 +25,7 @@ public sealed class ScalarOptionsSetup(IOptions<AuthenticationConfiguration> opt
flow.Pkce = Pkce.Sha256; flow.Pkce = Pkce.Sha256;
flow.SelectedScopes = _configuration.Scopes; flow.SelectedScopes = _configuration.Scopes;
flow.RedirectUri = _configuration.ScalarRedirectUri; flow.RedirectUri = _configuration.ScalarRedirectUri;
flow.RefreshUrl = _configuration.TokenEndpoint;
} ) } )
.HideModels(); .HideModels();
} }