From 865890f1ad422b5430644bef2911276a5faf4d19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Can=20Karag=C3=B6z?= Date: Mon, 12 Oct 2026 21:07:20 +0300 Subject: [PATCH] Fix Endpoint Contracts --- .../Auth/Response/AuthResponseResolver.cs | 16 +++++++++++++- .../ClientProfileAuthResponseAdapter.cs | 12 +++++++++++ .../Endpoints/UAuthEndpointRegistrar.cs | 6 +++--- .../Options/UAuthCookieOptions.cs | 3 ++- .../Services/UAuthAuthorizationClient.cs | 2 +- .../Services/UAuthUserIdentifierClient.cs | 2 +- .../UserLifecycleTests.cs | 21 +++++++++++++++++++ 7 files changed, 55 insertions(+), 7 deletions(-) diff --git a/src/CodeBeam.UltimateAuth.Server/Auth/Response/AuthResponseResolver.cs b/src/CodeBeam.UltimateAuth.Server/Auth/Response/AuthResponseResolver.cs index 41e7e278..f0c4f6d7 100644 --- a/src/CodeBeam.UltimateAuth.Server/Auth/Response/AuthResponseResolver.cs +++ b/src/CodeBeam.UltimateAuth.Server/Auth/Response/AuthResponseResolver.cs @@ -2,6 +2,7 @@ using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.Options; using CodeBeam.UltimateAuth.Server.Options; +using Microsoft.Net.Http.Headers; namespace CodeBeam.UltimateAuth.Server.Auth; @@ -76,10 +77,23 @@ private static void ValidateDelivery(CredentialResponseOptions delivery) if (delivery.Mode == TokenResponseMode.Body) throw new NotSupportedException($"Body credential delivery is not supported for '{delivery.Kind}'."); - if (delivery.Mode == TokenResponseMode.Cookie && delivery.Cookie is null) + if (delivery.Mode != TokenResponseMode.Cookie) + return; + + if (delivery.Cookie is null) { throw new InvalidOperationException($"Credential '{delivery.Kind}' is configured as Cookie but no cookie options were bound."); } + + try + { + var cookie = new SetCookieHeaderValue(delivery.Cookie.Name, "validation"); + _ = cookie.ToString(); + } + catch (ArgumentException ex) + { + throw new InvalidOperationException($"Invalid cookie name configured for '{delivery.Kind}': '{delivery.Cookie.Name}'.", ex); + } } private static EffectiveRedirectResponse ResolveRedirect(AuthFlowType flowType, UAuthResponseOptions bound) diff --git a/src/CodeBeam.UltimateAuth.Server/Auth/Response/ClientProfileAuthResponseAdapter.cs b/src/CodeBeam.UltimateAuth.Server/Auth/Response/ClientProfileAuthResponseAdapter.cs index cb64e072..7799673d 100644 --- a/src/CodeBeam.UltimateAuth.Server/Auth/Response/ClientProfileAuthResponseAdapter.cs +++ b/src/CodeBeam.UltimateAuth.Server/Auth/Response/ClientProfileAuthResponseAdapter.cs @@ -12,6 +12,10 @@ public UAuthResponseOptions Adapt(UAuthResponseOptions template, UAuthClientProf { var configured = effectiveOptions.Options.AuthResponse; + ValidateConfiguredDelivery(configured.SessionIdDelivery); + ValidateConfiguredDelivery(configured.AccessTokenDelivery); + ValidateConfiguredDelivery(configured.RefreshTokenDelivery); + return new UAuthResponseOptions { SessionIdDelivery = AdaptCredential(template.SessionIdDelivery, GrantKind.Session, clientProfile), @@ -77,4 +81,12 @@ private static LogoutRedirectOptions MergeLogout(LogoutRedirectOptions template, AllowReturnUrlOverride = configured.AllowReturnUrlOverride }; } + + private static void ValidateConfiguredDelivery(CredentialResponseOptions delivery) + { + if (delivery.Mode == TokenResponseMode.Body) + { + throw new NotSupportedException($"Body credential delivery is not currently supported for '{delivery.Kind}'."); + } + } } diff --git a/src/CodeBeam.UltimateAuth.Server/Endpoints/UAuthEndpointRegistrar.cs b/src/CodeBeam.UltimateAuth.Server/Endpoints/UAuthEndpointRegistrar.cs index 686deff8..4fe2f145 100644 --- a/src/CodeBeam.UltimateAuth.Server/Endpoints/UAuthEndpointRegistrar.cs +++ b/src/CodeBeam.UltimateAuth.Server/Endpoints/UAuthEndpointRegistrar.cs @@ -72,8 +72,8 @@ public void MapEndpoints(RouteGroupBuilder rootGroup, UAuthServerOptions options group.MapPost("/refresh", async ([FromServices] IRefreshEndpointHandler h, HttpContext ctx) => await h.RefreshAsync(ctx)).WithMetadata(new AuthFlowMetadata(AuthFlowType.RefreshSession)); - group.MapPost("/reauth", async ([FromServices] IReauthEndpointHandler h, HttpContext ctx) - => await h.ReauthAsync(ctx)).WithMetadata(new AuthFlowMetadata(AuthFlowType.Reauthentication)); + //group.MapPost("/reauth", async ([FromServices] IReauthEndpointHandler h, HttpContext ctx) + // => await h.ReauthAsync(ctx)).WithMetadata(new AuthFlowMetadata(AuthFlowType.Reauthentication)); if (Enabled(UAuthActions.Flows.LogoutDeviceAdmin)) @@ -206,7 +206,7 @@ public void MapEndpoints(RouteGroupBuilder rootGroup, UAuthServerOptions options if (Enabled(UAuthActions.Users.CreateAdmin)) adminUsers.MapPost("/create", async ([FromServices] IUserEndpointHandler h, HttpContext ctx) - => await h.CreateAsync(ctx)).WithMetadata(new AuthFlowMetadata(AuthFlowType.UserManagement)); + => await h.CreateAdminAsync(ctx)).WithMetadata(new AuthFlowMetadata(AuthFlowType.UserManagement)); if (Enabled(UAuthActions.Users.ChangeStatusAdmin)) adminUsers.MapPost("/{userKey}/status", async ([FromServices] IUserEndpointHandler h, UserKey userKey, HttpContext ctx) diff --git a/src/CodeBeam.UltimateAuth.Server/Options/UAuthCookieOptions.cs b/src/CodeBeam.UltimateAuth.Server/Options/UAuthCookieOptions.cs index cd0af2ed..c48afbf5 100644 --- a/src/CodeBeam.UltimateAuth.Server/Options/UAuthCookieOptions.cs +++ b/src/CodeBeam.UltimateAuth.Server/Options/UAuthCookieOptions.cs @@ -36,6 +36,7 @@ public sealed class UAuthCookieOptions SameSite = SameSite, Path = Path, MaxAge = MaxAge, - Lifetime = Lifetime.Clone() + Lifetime = Lifetime.Clone(), + Domain = Domain }; } diff --git a/src/client/CodeBeam.UltimateAuth.Client/Services/UAuthAuthorizationClient.cs b/src/client/CodeBeam.UltimateAuth.Client/Services/UAuthAuthorizationClient.cs index 306dbc3b..bc4d79c7 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Services/UAuthAuthorizationClient.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Services/UAuthAuthorizationClient.cs @@ -25,7 +25,7 @@ public UAuthAuthorizationClient(IUAuthRequestClient request, IUAuthClientEvents public async Task> CheckAsync(AuthorizationCheckRequest request) { - var raw = await _request.SendJsonAsync(Url("/authorization/check"), request); + var raw = await _request.SendJsonAsync(Url("/me/authorization/check"), request); return UAuthResultMapper.FromJson(raw); } diff --git a/src/client/CodeBeam.UltimateAuth.Client/Services/UAuthUserIdentifierClient.cs b/src/client/CodeBeam.UltimateAuth.Client/Services/UAuthUserIdentifierClient.cs index 2c21b0f9..01ea8d27 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Services/UAuthUserIdentifierClient.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Services/UAuthUserIdentifierClient.cs @@ -149,7 +149,7 @@ public async Task DeleteUserAsync(UserKey userKey, DeleteUserIdenti public async Task> CheckAvailabilityAsync(CheckUserIdentifierAvailabilityRequest request) { - var raw = await _request.SendJsonAsync(Url("/users/identifiers/check-availability"), request); + var raw = await _request.SendJsonAsync(Url("/me/identifiers/check-availability"), request); return UAuthResultMapper.FromJson(raw); } } diff --git a/tests/CodeBeam.UltimateAuth.Tests.Integration/UserLifecycleTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Integration/UserLifecycleTests.cs index 2d97dd87..3cb5251e 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Integration/UserLifecycleTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Integration/UserLifecycleTests.cs @@ -641,6 +641,27 @@ await profileFactory .Equal($"{prefix}-Delta", $"{prefix}-Echo"); } + [Fact] + public async Task AdminCreate_WhenAnonymous_ShouldReturnUnauthorized() + { + _factory.Clock.Reset(); + + using var client = CreateClient(); + + var response = await client.PostAsJsonAsync( + "/auth/admin/users/create", + new CreateUserRequest + { + UserName = $"admin-create-{Guid.NewGuid():N}", + Password = $"Test-{Guid.NewGuid():N}!", + DisplayName = "Unauthorized Creation" + }); + + response.StatusCode.Should() + .Be(HttpStatusCode.Unauthorized); + } + + // ------------------------------------------------------- // Helpers // -------------------------------------------------------