diff --git a/DorkNet.Server/Controllers/API/Config/V1/ServiceUrlsController.cs b/DorkNet.Server/Controllers/API/Config/V1/ServiceUrlsController.cs index fe6b770..b43f0b6 100644 --- a/DorkNet.Server/Controllers/API/Config/V1/ServiceUrlsController.cs +++ b/DorkNet.Server/Controllers/API/Config/V1/ServiceUrlsController.cs @@ -15,5 +15,5 @@ public class ServiceUrlsController(DomainConfig domain) : ControllerBase { [HttpGet("services")] public ActionResult> Get() - => Ok(ConfigService.BuildServiceUrlMap(domain.Apex)); + => Ok(ConfigService.BuildServiceUrlMap(domain)); } diff --git a/DorkNet.Server/Controllers/API/Notification/V2/NotificationController.cs b/DorkNet.Server/Controllers/API/Notification/V2/NotificationController.cs index c605673..61f8b57 100644 --- a/DorkNet.Server/Controllers/API/Notification/V2/NotificationController.cs +++ b/DorkNet.Server/Controllers/API/Notification/V2/NotificationController.cs @@ -16,7 +16,7 @@ public class NotificationController(DomainConfig domain) : ControllerBase public IActionResult Connect() => StatusCode(StatusCodes.Status410Gone, new { error = "use_signalr_hub", - hint = $"connect to {domain.Sub("notify")}/hub/v1 instead", + hint = $"connect to {domain.Url("notify", "/hub/v1")} instead", }); /// GET api/notification/hub/v1 — returns the @@ -27,7 +27,7 @@ public class NotificationController(DomainConfig domain) : ControllerBase [HttpGet("/api/notification/hub/v1")] public IActionResult HubInfo() => Ok(new { - Url = $"https://{domain.Sub("notify")}/hub/v1", + Url = domain.Url("notify", "/hub/v1"), Protocol = "signalr", Version = 1, }); diff --git a/DorkNet.Server/Controllers/Ns/NsController.cs b/DorkNet.Server/Controllers/Ns/NsController.cs index 2712a54..32c250e 100644 --- a/DorkNet.Server/Controllers/Ns/NsController.cs +++ b/DorkNet.Server/Controllers/Ns/NsController.cs @@ -37,13 +37,13 @@ public class NsController : ControllerBase // code: locker)" in BootSequence. [HttpGet("/", Order = -1000)] public ActionResult> Root() => - Ok(ConfigService.BuildServiceUrlMap(_domain.Apex)); + Ok(ConfigService.BuildServiceUrlMap(_domain)); // Some builds fetch /v1 or /v1/services directly [HttpGet("/v1")] [HttpGet("/v1/services")] public ActionResult> Services() => - Ok(ConfigService.BuildServiceUrlMap(_domain.Apex)); + Ok(ConfigService.BuildServiceUrlMap(_domain)); // Ping / health used by the client keep-alive loop [HttpGet("/v1/ping")] diff --git a/DorkNet.Server/Services/ConfigService.cs b/DorkNet.Server/Services/ConfigService.cs index e4f71d2..f92d35c 100644 --- a/DorkNet.Server/Services/ConfigService.cs +++ b/DorkNet.Server/Services/ConfigService.cs @@ -25,9 +25,9 @@ public class ConfigService(IConfiguration config, DomainConfig domain) return new RecRoomConfig { MessageOfTheDay = config["Server:MOTD"] ?? "Welcome to the private server!", - CdnBaseUri = $"https://{domain.Sub("cdn")}", + CdnBaseUri = domain.Url("cdn"), PhotonConfig = photon, - ServiceUrls = BuildServiceUrlMap(apex), + ServiceUrls = BuildServiceUrlMap(domain), ConfigTable = new Dictionary { // Season keys the 2019/2020 client checks at startup @@ -110,12 +110,14 @@ public class ConfigService(IConfiguration config, DomainConfig domain) /// ) so the map stays a single /// source of truth. public static Dictionary BuildServiceUrlMap(string apex) + => BuildServiceUrlMap(new DomainConfig(apex)); + + public static Dictionary BuildServiceUrlMap(DomainConfig domain) { var map = new Dictionary(ServiceSubdomains.Length); foreach (var (service, sub) in ServiceSubdomains) { - var host = string.IsNullOrEmpty(sub) ? apex : $"{sub}.{apex}"; - map[service] = $"https://{host}"; + map[service] = domain.Url(sub); } return map; } diff --git a/DorkNet.Server/Services/DomainConfig.cs b/DorkNet.Server/Services/DomainConfig.cs index 6bff828..2b328b3 100644 --- a/DorkNet.Server/Services/DomainConfig.cs +++ b/DorkNet.Server/Services/DomainConfig.cs @@ -11,8 +11,28 @@ namespace DorkNet.Server.Services; public sealed class DomainConfig { public string Apex { get; } - public DomainConfig(string apex) { Apex = apex; } + public string Scheme { get; } + + public DomainConfig(string apex, string scheme = "https") + { + Apex = apex; + Scheme = NormalizeScheme(scheme); + } + public string Sub(string prefix) => $"{prefix}.{Apex}"; + public string Url(string prefix, string path = "") + { + var host = string.IsNullOrEmpty(prefix) ? Apex : Sub(prefix); + if (string.IsNullOrEmpty(path)) return $"{Scheme}://{host}"; + return $"{Scheme}://{host}{(path.StartsWith('/') ? path : "/" + path)}"; + } + public static bool MatchesSubdomain(string host, string prefix) => host.StartsWith(prefix + ".", StringComparison.OrdinalIgnoreCase); + + private static string NormalizeScheme(string? value) + { + var scheme = string.IsNullOrWhiteSpace(value) ? "https" : value.Trim().TrimEnd(':', '/', '\\').ToLowerInvariant(); + return scheme is "http" or "https" ? scheme : "https"; + } } diff --git a/DorkNet.Server/Startup/MiddlewarePipelineExtensions.cs b/DorkNet.Server/Startup/MiddlewarePipelineExtensions.cs index ae90ca2..4313c8a 100644 --- a/DorkNet.Server/Startup/MiddlewarePipelineExtensions.cs +++ b/DorkNet.Server/Startup/MiddlewarePipelineExtensions.cs @@ -70,7 +70,7 @@ public static class MiddlewarePipelineExtensions if (ctx.Request.Host.Host.Equals(domainCfg.Sub("api"), StringComparison.OrdinalIgnoreCase) && (ctx.Request.Path == "/admin" || ctx.Request.Path.StartsWithSegments("/admin"))) { - ctx.Response.Redirect($"https://{domainCfg.Sub("admin")}/", permanent: false); + ctx.Response.Redirect(domainCfg.Url("admin", "/"), permanent: false); return; } await next(); diff --git a/DorkNet.Server/Startup/ServiceCollectionExtensions.cs b/DorkNet.Server/Startup/ServiceCollectionExtensions.cs index ad5692f..4162d59 100644 --- a/DorkNet.Server/Startup/ServiceCollectionExtensions.cs +++ b/DorkNet.Server/Startup/ServiceCollectionExtensions.cs @@ -106,7 +106,8 @@ public static class ServiceCollectionExtensions builder.Configuration["Domain:Apex"] ?? Environment.GetEnvironmentVariable("DORKNET_DOMAIN") ?? "localhost"; - builder.Services.AddSingleton(new DomainConfig(apex)); + var scheme = builder.Configuration["Domain:Scheme"] ?? "https"; + builder.Services.AddSingleton(new DomainConfig(apex, scheme)); builder.Services.Configure(opt => { opt.AllowedHosts = new[] { apex, $"*.{apex}", "localhost", "127.0.0.1" };