mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-25 23:18:44 -07:00
Single-origin URL rewriting; configurable SQLite path; default InjectAuthValues=false
- ClientMod: add SingleOriginBaseUrl config + URI rewrite path so the client can talk to one https://<apex>/__dn/<service>/... base instead of demanding wildcard subdomains. ServerHost rewrite still the fallback when SingleOriginBaseUrl is empty. - ClientMod config: InjectAuthValues now defaults to false. The server's PhotonCustomAuthController already fail-opens (Photon:RequireCustomAuth=false), so anonymous bootstrap is the cleaner default and avoids Photon's CustomAuthFailed loop when a WAF returns 403 to Photon's probe. - Server: SQLite path now reads Database:SqlitePath config (Database__SqlitePath env). Falls back to <bin>/data/dorknet.db so existing Docker / standalone deploys are unchanged. Lets the Windows launcher put the db under %APPDATA%\DorkNet. - Server: DomainConfig + ConfigService updates for the single-origin apex. - .gitignore: exclude local-releases.zip.
This commit is contained in:
@@ -64,3 +64,4 @@ DorkNet.Server/site/node_modules/
|
||||
DorkNet.Server/site/dist/
|
||||
DorkNet.Server/site/.vite/
|
||||
DorkNet.Server/site/tsconfig.tsbuildinfo
|
||||
local-releases.zip
|
||||
|
||||
@@ -55,6 +55,7 @@ public class Mod : MelonMod
|
||||
public static class Cfg
|
||||
{
|
||||
public static string ServerHost = "localhost";
|
||||
public static string SingleOriginBaseUrl = "";
|
||||
public static string PhotonAppId = "";
|
||||
public static string PhotonVoiceAppId = "";
|
||||
// Forced PhotonServerSettings.PreferredRegion (with HostType
|
||||
@@ -281,12 +282,13 @@ public class Mod : MelonMod
|
||||
var doc = JsonDocument.Parse(File.ReadAllText(path));
|
||||
var r = doc.RootElement;
|
||||
if (r.TryGetProperty("ServerHost", out var v)) Cfg.ServerHost = v.GetString() ?? Cfg.ServerHost;
|
||||
if (r.TryGetProperty("SingleOriginBaseUrl", out v)) Cfg.SingleOriginBaseUrl = (v.GetString() ?? "").TrimEnd('/');
|
||||
if (r.TryGetProperty("PhotonAppId", out v)) Cfg.PhotonAppId = v.GetString() ?? Cfg.PhotonAppId;
|
||||
if (r.TryGetProperty("PhotonVoiceAppId", out v)) Cfg.PhotonVoiceAppId = v.GetString() ?? Cfg.PhotonVoiceAppId;
|
||||
if (r.TryGetProperty("PhotonCloudRegion", out v)) Cfg.PhotonCloudRegion = v.GetString() ?? Cfg.PhotonCloudRegion;
|
||||
if (r.TryGetProperty("InjectAuthValues", out v)) Cfg.InjectAuthValues = v.GetBoolean();
|
||||
if (r.TryGetProperty("EnableTlsTrustBypass", out v)) Cfg.EnableTlsTrustBypass = v.GetBoolean();
|
||||
Log.Msg($"[config] loaded: ServerHost={Cfg.ServerHost}, PhotonAppId={(string.IsNullOrEmpty(Cfg.PhotonAppId) ? "<unset>" : "<set>")}, " +
|
||||
Log.Msg($"[config] loaded: ServerHost={Cfg.ServerHost}, SingleOrigin={(string.IsNullOrEmpty(Cfg.SingleOriginBaseUrl) ? "<off>" : Cfg.SingleOriginBaseUrl)}, PhotonAppId={(string.IsNullOrEmpty(Cfg.PhotonAppId) ? "<unset>" : "<set>")}, " +
|
||||
$"InjectAuthValues={Cfg.InjectAuthValues}, EnableTlsTrustBypass={Cfg.EnableTlsTrustBypass}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -554,9 +556,22 @@ internal static class UriPatches
|
||||
private static void RewriteString(ref string uriString, string label)
|
||||
{
|
||||
if (string.IsNullOrEmpty(uriString)) return;
|
||||
if (uriString.IndexOf(".rec.net", StringComparison.OrdinalIgnoreCase) < 0) return;
|
||||
|
||||
if (!string.IsNullOrEmpty(Mod.Cfg.SingleOriginBaseUrl) &&
|
||||
Uri.TryCreate(uriString, UriKind.Absolute, out var uri))
|
||||
{
|
||||
var rewrittenUri = RewriteUri(uri);
|
||||
if (rewrittenUri is null) return;
|
||||
var rewrittenText = rewrittenUri.ToString();
|
||||
if (rewrittenText == uriString) return;
|
||||
Mod.Log.Msg($"[{label}] {uriString} → {rewrittenText}");
|
||||
uriString = rewrittenText;
|
||||
return;
|
||||
}
|
||||
|
||||
var host = Mod.Cfg.ServerHost;
|
||||
if (string.IsNullOrEmpty(host)) return;
|
||||
if (uriString.IndexOf(".rec.net", StringComparison.OrdinalIgnoreCase) < 0) return;
|
||||
var rewritten = uriString.Replace(".rec.net", "." + host);
|
||||
if (rewritten == uriString) return;
|
||||
Mod.Log.Msg($"[{label}] {uriString} → {rewritten}");
|
||||
@@ -572,11 +587,33 @@ internal static class UriPatches
|
||||
var service = uri.Host[..^".rec.net".Length];
|
||||
if (string.IsNullOrWhiteSpace(service)) return null;
|
||||
|
||||
if (!string.IsNullOrEmpty(Mod.Cfg.SingleOriginBaseUrl) &&
|
||||
Uri.TryCreate(Mod.Cfg.SingleOriginBaseUrl, UriKind.Absolute, out var baseUri))
|
||||
{
|
||||
var path = CombinePath(baseUri.AbsolutePath, "__dn/" + service, uri.AbsolutePath);
|
||||
return new UriBuilder(baseUri)
|
||||
{
|
||||
Path = path,
|
||||
Query = uri.Query.TrimStart('?'),
|
||||
}.Uri;
|
||||
}
|
||||
|
||||
return new UriBuilder(uri)
|
||||
{
|
||||
Host = service + "." + host,
|
||||
}.Uri;
|
||||
}
|
||||
|
||||
private static string CombinePath(params string[] parts)
|
||||
{
|
||||
var cleaned = new List<string>();
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var p = (part ?? "").Trim('/');
|
||||
if (p.Length > 0) cleaned.Add(p);
|
||||
}
|
||||
return "/" + string.Join("/", cleaned);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class PhotonPatches
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
{
|
||||
"ServerHost": "{HOST}",
|
||||
"SingleOriginBaseUrl": "{SINGLE_ORIGIN_BASE_URL}",
|
||||
"PhotonAppId": "{PHOTON_APPID}",
|
||||
"PhotonVoiceAppId": "{PHOTON_VOICE_APPID}",
|
||||
"PhotonCloudRegion": "{PHOTON_REGION}",
|
||||
"InjectAuthValues": true,
|
||||
"InjectAuthValues": false,
|
||||
"EnableTlsTrustBypass": true
|
||||
}
|
||||
|
||||
@@ -65,7 +65,15 @@ builder.Services.AddDbContext<DorkNetDbContext>(opt =>
|
||||
}
|
||||
else
|
||||
{
|
||||
var dbPath = Path.Combine(AppContext.BaseDirectory, "data", "dorknet.db");
|
||||
// Honor an explicit path the host can set via env var
|
||||
// (Database__SqlitePath) or appsettings (Database:SqlitePath).
|
||||
// The Windows launcher sets this to %APPDATA%\DorkNet\dorknet.db
|
||||
// so server installs don't accumulate state under <bin>\data\.
|
||||
// Existing Docker / standalone deploys that mounted <bin>\data\
|
||||
// keep working because that's still the fallback.
|
||||
var dbPath = builder.Configuration["Database:SqlitePath"];
|
||||
if (string.IsNullOrWhiteSpace(dbPath))
|
||||
dbPath = Path.Combine(AppContext.BaseDirectory, "data", "dorknet.db");
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(dbPath)!);
|
||||
opt.UseSqlite($"Data Source={dbPath}", lite => lite.MigrationsAssembly("DorkNet.Server"));
|
||||
}
|
||||
@@ -94,14 +102,17 @@ var domainScheme =
|
||||
var domainPort =
|
||||
builder.Configuration["Domain:Port"]
|
||||
?? Environment.GetEnvironmentVariable("DORKNET_DOMAIN_PORT");
|
||||
builder.Services.AddSingleton(new DomainConfig(apex, domainScheme, domainPort));
|
||||
var singleOriginBaseUrl =
|
||||
builder.Configuration["Domain:SingleOriginBaseUrl"]
|
||||
?? Environment.GetEnvironmentVariable("DORKNET_SINGLE_ORIGIN_BASE_URL");
|
||||
builder.Services.AddSingleton(new DomainConfig(apex, domainScheme, domainPort, singleOriginBaseUrl));
|
||||
builder.Services.Configure<Microsoft.AspNetCore.HostFiltering.HostFilteringOptions>(opt =>
|
||||
{
|
||||
opt.AllowedHosts = new[] { apex, $"*.{apex}", "localhost", "127.0.0.1" };
|
||||
opt.AllowEmptyHosts = true;
|
||||
opt.IncludeFailureMessage = true;
|
||||
});
|
||||
Console.WriteLine($"[domain] apex={apex}, scheme={domainScheme}, port={domainPort}, allowedHosts=[{apex}, *.{apex}, localhost]");
|
||||
Console.WriteLine($"[domain] apex={apex}, scheme={domainScheme}, port={domainPort}, singleOrigin={singleOriginBaseUrl}, allowedHosts=[{apex}, *.{apex}, localhost]");
|
||||
|
||||
// ── Services ──────────────────────────────────────────────────────────────────
|
||||
builder.Services.AddScoped<PlayerService>();
|
||||
@@ -317,6 +328,30 @@ var app = builder.Build();
|
||||
// hardcoded rec.net/localhost literals.
|
||||
var domainCfg = app.Services.GetRequiredService<DomainConfig>();
|
||||
|
||||
// Easy Launcher single-origin mode. localtunnel gives us one public
|
||||
// hostname, so the launcher name-server emits URLs like
|
||||
// https://abc.loca.lt/__dn/api/...; this middleware maps those path
|
||||
// prefixes back into the same internal host layout used by advanced
|
||||
// wildcard deployments. If Domain:SingleOriginBaseUrl is unset this is
|
||||
// inert, preserving the normal subdomain behavior.
|
||||
app.Use(async (ctx, next) =>
|
||||
{
|
||||
if (domainCfg.SingleOriginEnabled &&
|
||||
TryMapSingleOriginPath(ctx.Request.Path, out var service, out var rest))
|
||||
{
|
||||
ctx.Request.Host = new HostString(service == "www" ? domainCfg.Apex : domainCfg.Sub(service));
|
||||
ctx.Request.Path = string.IsNullOrEmpty(rest) ? "/" : rest;
|
||||
}
|
||||
|
||||
await next();
|
||||
});
|
||||
|
||||
// Keep endpoint selection after the single-origin path rewrite. Without an
|
||||
// explicit routing point here, WebApplication can auto-insert routing before
|
||||
// the rewrite middleware, causing /__dn/api/... requests to stay route misses
|
||||
// even though the trace logs show the rewritten /api/... path.
|
||||
app.UseRouting();
|
||||
|
||||
// Request/response tracing — emits one structured log per non-health request,
|
||||
// with the response body included for any 4xx/5xx so we can see exactly what
|
||||
// the game is asking for. This runs early enough to catch route misses,
|
||||
@@ -1071,6 +1106,22 @@ app.MapHub<DorkNet.Server.Hubs.NotifyHub>("/hub/v1", opts =>
|
||||
|
||||
app.Run();
|
||||
|
||||
static bool TryMapSingleOriginPath(PathString path, out string service, out PathString rest)
|
||||
{
|
||||
service = "";
|
||||
rest = PathString.Empty;
|
||||
if (!path.StartsWithSegments("/__dn", out var tail)) return false;
|
||||
|
||||
var value = tail.Value ?? "";
|
||||
if (value.Length <= 1) return false;
|
||||
var slash = value.IndexOf('/', 1);
|
||||
service = slash < 0 ? value[1..] : value[1..slash];
|
||||
if (string.IsNullOrWhiteSpace(service)) return false;
|
||||
|
||||
rest = slash < 0 ? "/" : value[slash..];
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool IsHealthProbe(HttpRequest request)
|
||||
{
|
||||
return request.Path.Equals("/healthz", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
@@ -23,7 +23,9 @@ public class ConfigService(IConfiguration config, DomainConfig domain)
|
||||
return new RecRoomConfig
|
||||
{
|
||||
MessageOfTheDay = config["Server:MOTD"] ?? "Welcome to the private server!",
|
||||
CdnBaseUri = domain.SubUrl("cdn"),
|
||||
CdnBaseUri = domain.SingleOriginEnabled
|
||||
? $"{domain.SingleOriginBaseUrl}/__dn/cdn"
|
||||
: domain.SubUrl("cdn"),
|
||||
PhotonConfig = photon,
|
||||
ServiceUrls = BuildServiceUrlMap(domain),
|
||||
ConfigTable = new Dictionary<string, string>
|
||||
@@ -110,6 +112,12 @@ public class ConfigService(IConfiguration config, DomainConfig domain)
|
||||
var map = new Dictionary<string, string>(ServiceSubdomains.Length);
|
||||
foreach (var (service, sub) in ServiceSubdomains)
|
||||
{
|
||||
if (domain.SingleOriginEnabled)
|
||||
{
|
||||
var prefix = string.IsNullOrEmpty(sub) ? "www" : sub;
|
||||
map[service] = $"{domain.SingleOriginBaseUrl}/__dn/{prefix}";
|
||||
continue;
|
||||
}
|
||||
var host = string.IsNullOrEmpty(sub) ? domain.Apex : domain.Sub(sub);
|
||||
map[service] = domain.Url(host);
|
||||
}
|
||||
|
||||
@@ -13,12 +13,17 @@ public sealed class DomainConfig
|
||||
public string Apex { get; }
|
||||
public string Scheme { get; }
|
||||
public string? Port { get; }
|
||||
public string? SingleOriginBaseUrl { get; }
|
||||
public bool SingleOriginEnabled => !string.IsNullOrWhiteSpace(SingleOriginBaseUrl);
|
||||
|
||||
public DomainConfig(string apex, string scheme = "https", string? port = null)
|
||||
public DomainConfig(string apex, string scheme = "https", string? port = null, string? singleOriginBaseUrl = null)
|
||||
{
|
||||
Apex = apex;
|
||||
Scheme = string.IsNullOrWhiteSpace(scheme) ? "https" : scheme.TrimEnd(':', '/', '\\');
|
||||
Port = string.IsNullOrWhiteSpace(port) ? null : port.Trim().TrimStart(':');
|
||||
SingleOriginBaseUrl = string.IsNullOrWhiteSpace(singleOriginBaseUrl)
|
||||
? null
|
||||
: singleOriginBaseUrl.Trim().TrimEnd('/');
|
||||
}
|
||||
|
||||
public string Sub(string prefix) => $"{prefix}.{Apex}";
|
||||
|
||||
Reference in New Issue
Block a user