diff --git a/WebApi/Authentication/CurrentUser.cs b/WebApi/Authentication/CurrentUser.cs index f2921b6..cedf335 100644 --- a/WebApi/Authentication/CurrentUser.cs +++ b/WebApi/Authentication/CurrentUser.cs @@ -12,7 +12,7 @@ public sealed class CurrentUser : IBindableFromHttpContext IsAuthenticated = isAuthenticated.HasValue && isAuthenticated.Value; 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; Username = principal.FindFirstValue(ClaimTypes.Name); @@ -24,7 +24,7 @@ public sealed class CurrentUser : IBindableFromHttpContext public bool IsAuthenticated { get; } - public Guid? Id { get; } + public long? Id { get; } public string? Username { get; } @@ -32,7 +32,7 @@ public sealed class CurrentUser : IBindableFromHttpContext 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); diff --git a/WebApi/Authentication/JwtBearerEventsHandler.cs b/WebApi/Authentication/JwtBearerEventsHandler.cs index 4819d10..5891465 100644 --- a/WebApi/Authentication/JwtBearerEventsHandler.cs +++ b/WebApi/Authentication/JwtBearerEventsHandler.cs @@ -5,21 +5,49 @@ using System.Security.Claims; namespace WebApi.Authentication; -public sealed class JwtBearerEventsHandler(IMediator mediator) : JwtBearerEvents +public sealed class JwtBearerEventsHandler(IMediator mediator, ILogger logger) : JwtBearerEvents { public override async Task TokenValidated(TokenValidatedContext tokenValidatedContext) { var principal = tokenValidatedContext.Principal!; - var id = principal.FindFirstValue(ClaimTypes.NameIdentifier)!; - var guidId = Guid.Parse(id); + var rawId = principal.FindFirstValue(ClaimTypes.NameIdentifier); + 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 lastName = principal.FindFirstValue(ClaimTypes.Surname); - await mediator.Send( - new CreateUserCommand(guidId, username, firstName, lastName), - tokenValidatedContext.HttpContext.RequestAborted); + try + { + await mediator.Send( + new CreateUserCommand(userId, username, firstName, lastName), + 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; } } diff --git a/WebApi/DataTransferObjects/Users/CreateUserRequest.cs b/WebApi/DataTransferObjects/Users/CreateUserRequest.cs index 5937975..24c7442 100644 --- a/WebApi/DataTransferObjects/Users/CreateUserRequest.cs +++ b/WebApi/DataTransferObjects/Users/CreateUserRequest.cs @@ -1,7 +1,7 @@ namespace WebApi.DataTransferObjects.Users; public record CreateUserRequest( - Guid Id, + long Id, string Name, string? FirstName, string? SecondName); diff --git a/WebApi/Endpoints/PlayListEndpoints.cs b/WebApi/Endpoints/PlayListEndpoints.cs index e2a0b0f..36ca9d9 100644 --- a/WebApi/Endpoints/PlayListEndpoints.cs +++ b/WebApi/Endpoints/PlayListEndpoints.cs @@ -74,7 +74,7 @@ public static class PlayListEndpoints private static async Task GetPlayListsByUserId( IMediator mediator, - Guid userId, + long userId, CancellationToken cancellationToken, string? name, int pageNumber = 1, diff --git a/WebApi/Endpoints/TrackEndpoints.cs b/WebApi/Endpoints/TrackEndpoints.cs index 4cd9839..44ad9dc 100644 --- a/WebApi/Endpoints/TrackEndpoints.cs +++ b/WebApi/Endpoints/TrackEndpoints.cs @@ -83,7 +83,7 @@ public static class TrackEndpoints private static async Task GetTracksByUserId( IMediator mediator, - Guid userId, + long userId, CancellationToken cancellationToken, string? name, int pageNumber = 1, diff --git a/WebApi/Endpoints/UserEndpoints.cs b/WebApi/Endpoints/UserEndpoints.cs index 63bb430..c966c89 100644 --- a/WebApi/Endpoints/UserEndpoints.cs +++ b/WebApi/Endpoints/UserEndpoints.cs @@ -43,7 +43,7 @@ public static class UserEndpoints private static async Task GetUserById( IMediator mediator, - Guid id, + long id, CancellationToken cancellationToken) { var result = await mediator.Send(new GetUserByIdQuery(id), cancellationToken); diff --git a/WebApi/Scalar/ScalarOptionsSetup.cs b/WebApi/Scalar/ScalarOptionsSetup.cs index b78ca39..add12dc 100644 --- a/WebApi/Scalar/ScalarOptionsSetup.cs +++ b/WebApi/Scalar/ScalarOptionsSetup.cs @@ -25,6 +25,7 @@ public sealed class ScalarOptionsSetup(IOptions opt flow.Pkce = Pkce.Sha256; flow.SelectedScopes = _configuration.Scopes; flow.RedirectUri = _configuration.ScalarRedirectUri; + flow.RefreshUrl = _configuration.TokenEndpoint; } ) .HideModels(); }