From 8ec79ad06cbc2a27d925aca002368b5a2a00d0f6 Mon Sep 17 00:00:00 2001 From: Alexa <53471801+Alexa-RR@users.noreply.github.com> Date: Tue, 30 Jun 2026 16:10:54 +0300 Subject: [PATCH] Fix token cookie propagation --- .../Auth/IdentityServerIntegration.cs | 108 +++++++++++++++++- 1 file changed, 106 insertions(+), 2 deletions(-) diff --git a/DorkNet.Server/Auth/IdentityServerIntegration.cs b/DorkNet.Server/Auth/IdentityServerIntegration.cs index a7ec4ca..055c741 100644 --- a/DorkNet.Server/Auth/IdentityServerIntegration.cs +++ b/DorkNet.Server/Auth/IdentityServerIntegration.cs @@ -3,6 +3,7 @@ using System.Security.Claims; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; +using System.Text.Json; using Open.IdentityServer; using Open.IdentityServer.Models; using Open.IdentityServer.Services; @@ -273,7 +274,7 @@ internal static class RecRoomIdentityServerConfig ]; } -public sealed class IdentityServerGameTokenRequestMiddleware(RequestDelegate next) +public sealed class IdentityServerGameTokenRequestMiddleware(RequestDelegate next, DomainConfig domain) { public async Task InvokeAsync(HttpContext context) { @@ -324,7 +325,110 @@ public sealed class IdentityServerGameTokenRequestMiddleware(RequestDelegate nex if (changed) context.Features.Set(new FormFeature(new FormCollection(values))); - await next(context); + await InvokeAndPersistAccessTokenCookieAsync(context); + } + + private async Task InvokeAndPersistAccessTokenCookieAsync(HttpContext context) + { + var originalBody = context.Response.Body; + await using var bufferedBody = new MemoryStream(); + context.Response.Body = bufferedBody; + + try + { + await next(context); + + bufferedBody.Position = 0; + if (IsSuccessfulJsonResponse(context.Response)) + TryPersistAccessTokenCookie(context, bufferedBody); + + bufferedBody.Position = 0; + await bufferedBody.CopyToAsync(originalBody); + } + finally + { + context.Response.Body = originalBody; + } + } + + private static bool IsSuccessfulJsonResponse(HttpResponse response) => + response.StatusCode is >= 200 and < 300 + && response.ContentType?.Contains("application/json", StringComparison.OrdinalIgnoreCase) == true; + + private void TryPersistAccessTokenCookie(HttpContext context, Stream responseBody) + { + try + { + using var document = JsonDocument.Parse(responseBody); + if (!document.RootElement.TryGetProperty("access_token", out var accessTokenElement)) + return; + var accessToken = accessTokenElement.GetString(); + if (string.IsNullOrWhiteSpace(accessToken)) + return; + + var expires = DateTimeOffset.UtcNow.AddHours(12); + if (document.RootElement.TryGetProperty("expires_in", out var expiresInElement) && + expiresInElement.TryGetInt32(out var expiresIn) && + expiresIn > 0) + { + expires = DateTimeOffset.UtcNow.AddSeconds(expiresIn); + } + + var options = new CookieOptions + { + HttpOnly = true, + Secure = context.Request.IsHttps || domain.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase), + SameSite = SameSiteMode.Lax, + Path = "/", + Expires = expires, + }; + + var cookieDomain = ResolveCookieDomain(context.Request.Host.Host); + if (cookieDomain is not null) + options.Domain = cookieDomain; + + context.Response.Cookies.Append(AuthService.AccessCookieName, accessToken, options); + } + catch (JsonException) + { + // Leave the token response untouched if the upstream payload is not JSON. + } + } + + private string? ResolveCookieDomain(string host) + { + var apex = domain.Apex.Trim().TrimStart('.'); + if (string.IsNullOrWhiteSpace(apex) || + apex.Equals("localhost", StringComparison.OrdinalIgnoreCase) || + apex.Equals("127.0.0.1", StringComparison.OrdinalIgnoreCase) || + apex.Equals("::1", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + if (domain.UsesHyphenSubdomains && host.EndsWith("-" + apex, StringComparison.OrdinalIgnoreCase)) + { + var parentDomain = ResolveParentDomain(apex); + return parentDomain is null ? null : "." + parentDomain; + } + + if (host.Equals(apex, StringComparison.OrdinalIgnoreCase) || + host.EndsWith("." + apex, StringComparison.OrdinalIgnoreCase)) + { + return "." + apex; + } + + return null; + } + + private static string? ResolveParentDomain(string host) + { + var firstDot = host.IndexOf('.'); + if (firstDot <= 0 || firstDot == host.Length - 1) + return null; + + var parent = host[(firstDot + 1)..]; + return parent.Contains('.') ? parent : null; } private static bool IsTokenRequest(HttpContext context) =>