mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 23:18:56 -07:00
125 lines
9.3 KiB
C#
125 lines
9.3 KiB
C#
using Xunit;
|
|
|
|
namespace RadicadeServer.Tests;
|
|
|
|
public class NameserverRedirectTests
|
|
{
|
|
[Theory]
|
|
[InlineData("wss://notifications.rec.net/hub/v1?id=a", "http://127.0.0.1:3031", "ws://127.0.0.1:3031/notify/hub/v1?id=a")]
|
|
[InlineData("ws://notify.rec.net/hub/v1", "https://private.example", "wss://private.example/notify/hub/v1")]
|
|
[InlineData("wss://notifications.radicade.internal/notify/hub/v1", "https://private.example", "wss://private.example/notify/hub/v1")]
|
|
// The socket the client actually opens now: the SignalR transport submits it through the same
|
|
// funnel as the REST calls, and a socket URL the funnel leaves alone is a host with no DNS
|
|
// record - the hub never connects and the boot dies on it. The nameserver advertises the
|
|
// service under its full name, so this host is the one that has to come back to /notify.
|
|
[InlineData("wss://notifications.radicade.app/hub/v1?&id=q041FbQsdxYHA8PQ2oC8dg", "https://ns.radicade.app", "wss://ns.radicade.app/notify/hub/v1?&id=q041FbQsdxYHA8PQ2oC8dg")]
|
|
[InlineData("wss://other.example/hub/v1", "https://private.example", "wss://other.example/hub/v1")]
|
|
public void SocketRoutingUsesConfiguredServerTransport(string input, string server, string expected)
|
|
=> Assert.Equal(expected, NameserverRedirect.RewriteWebSocket(input, server));
|
|
|
|
[Theory]
|
|
[InlineData("https://ns.rec.net", "https://ns.radicade.app/ns")]
|
|
[InlineData("https://ns.rec.net/nameserver?build=20230301", "https://ns.radicade.app/nameserver?build=20230301")]
|
|
[InlineData("http://NS.REC.NET/ns", "https://ns.radicade.app/ns")]
|
|
[InlineData("https://ns.radicade.internal/ns", "https://ns.radicade.app/ns")]
|
|
public void StockClientBootstrapsWithoutPatchedMetadata(string input, string expected)
|
|
=> Assert.Equal(expected, NameserverRedirect.Rewrite(input, "https://ns.radicade.app/"));
|
|
|
|
// The stock client resolves the image host itself, so a patch that only rewrote the
|
|
// bootstrap host left every avatar, photo and thumbnail pointed at the real RecNet.
|
|
// img.rec.net serves bare image names; this server serves them under /images.
|
|
[Theory]
|
|
[InlineData("https://img.rec.net/abc123.png", "https://ns.radicade.app/images/abc123.png")]
|
|
[InlineData("https://img.rec.net/abc123.png?sig=p1", "https://ns.radicade.app/images/abc123.png?sig=p1")]
|
|
[InlineData("https://IMG.REC.NET/DefaultProfileImage.jpg", "https://ns.radicade.app/images/DefaultProfileImage.jpg")]
|
|
[InlineData("https://images.rec.net/abc123.png", "https://ns.radicade.app/images/abc123.png")]
|
|
// A loader that already adds the /images or /img segment must not have it doubled.
|
|
[InlineData("https://img.rec.net/images/abc123.png", "https://ns.radicade.app/images/abc123.png")]
|
|
[InlineData("https://img.rec.net/img/abc123.png", "https://ns.radicade.app/images/abc123.png")]
|
|
public void ImageHostRequestsReachTheServersImageRoute(string input, string expected)
|
|
=> Assert.Equal(expected, NameserverRedirect.Rewrite(input, "https://ns.radicade.app"));
|
|
|
|
// Room saves and their Unity asset bundles download from the CDN host by blob name;
|
|
// the same traffic is served here at the root, so the path passes through unchanged.
|
|
[Theory]
|
|
[InlineData("https://cdn.rec.net/room/9c1f.bin", "https://ns.radicade.app/room/9c1f.bin")]
|
|
[InlineData("https://cdn.rec.net/unity/12345/0/1", "https://ns.radicade.app/unity/12345/0/1")]
|
|
[InlineData("https://data.rec.net/9c1f.bin", "https://ns.radicade.app/data/9c1f.bin")]
|
|
[InlineData("https://storage.rec.net/upload", "https://ns.radicade.app/storage/upload")]
|
|
[InlineData("https://notify.rec.net/hub/v1/negotiate", "https://ns.radicade.app/notify/hub/v1/negotiate")]
|
|
[InlineData("https://api.rec.net/api/config/v2", "https://ns.radicade.app/api/config/v2")]
|
|
[InlineData("https://accounts.rec.net/account/me", "https://ns.radicade.app/accounts/account/me")]
|
|
[InlineData("https://rooms.rec.net/rooms/2?include=all", "https://ns.radicade.app/rooms/rooms/2?include=all")]
|
|
[InlineData("https://matchmaking.rec.net/player/login", "https://ns.radicade.app/matchmaking/player/login")]
|
|
[InlineData("https://auth.rec.net/connect/token", "https://ns.radicade.app/auth/connect/token")]
|
|
[InlineData("https://chat.rec.net/thread?maxCount=30", "https://ns.radicade.app/chat/thread?maxCount=30")]
|
|
public void ServiceHostRequestsKeepTheirPathOnTheConfiguredServer(string input, string expected)
|
|
=> Assert.Equal(expected, NameserverRedirect.Rewrite(input, "https://ns.radicade.app"));
|
|
|
|
// The nameserver names each service on its own host now, and the client builds every call
|
|
// as that base plus its own path literal - so the host has to come back to exactly the
|
|
// route the path-prefixed base used to produce. api is the one bare base, and cdn keeps
|
|
// the /cdn segment its advertised base has always carried.
|
|
[Theory]
|
|
[InlineData("https://auth.radicade.app/connect/token", "https://ns.radicade.app/auth/connect/token")]
|
|
[InlineData("https://api.radicade.app/api/config/v2", "https://ns.radicade.app/api/config/v2")]
|
|
[InlineData("https://accounts.radicade.app/account/me", "https://ns.radicade.app/accounts/account/me")]
|
|
[InlineData("https://rooms.radicade.app/rooms/2?include=all", "https://ns.radicade.app/rooms/rooms/2?include=all")]
|
|
[InlineData("https://matchmaking.radicade.app/player/login", "https://ns.radicade.app/matchmaking/player/login")]
|
|
[InlineData("https://chat.radicade.app/thread?maxCount=30", "https://ns.radicade.app/chat/thread?maxCount=30")]
|
|
[InlineData("https://images.radicade.app/abc123.png", "https://ns.radicade.app/images/abc123.png")]
|
|
// A loader that already adds the /images or /img segment must not have it doubled.
|
|
[InlineData("https://images.radicade.app/img/abc123.png", "https://ns.radicade.app/images/abc123.png")]
|
|
[InlineData("https://cdn.radicade.app/room/9c1f.bin", "https://ns.radicade.app/cdn/room/9c1f.bin")]
|
|
[InlineData("https://data.radicade.app/9c1f.bin", "https://ns.radicade.app/data/9c1f.bin")]
|
|
// The notify service is the one advertised name that is not its own route: the host is
|
|
// notifications.radicade.app, the route it has to land on is /notify.
|
|
[InlineData("https://notifications.radicade.app/hub/v1/negotiate", "https://ns.radicade.app/notify/hub/v1/negotiate")]
|
|
[InlineData("https://www.radicade.app/creator", "https://ns.radicade.app/www/creator")]
|
|
[InlineData("https://stringscdn.radicade.app/strings.json", "https://ns.radicade.app/stringscdn/strings.json")]
|
|
[InlineData("https://studiocdn.radicade.app/roomunityasset/x", "https://ns.radicade.app/studiocdn/roomunityasset/x")]
|
|
[InlineData("https://thorn.radicade.app/endpoint", "https://ns.radicade.app/thorn/endpoint")]
|
|
[InlineData("https://videos.radicade.app/blob.mp4", "https://ns.radicade.app/videos/blob.mp4")]
|
|
public void NameserverServiceHostsRedirectOntoTheirOwnRoute(string input, string expected)
|
|
=> Assert.Equal(expected, NameserverRedirect.Rewrite(input, "https://ns.radicade.app"));
|
|
|
|
[Theory]
|
|
[InlineData("https://ns.radicade.app/ns")]
|
|
[InlineData("https://example.com/?url=https://ns.rec.net")]
|
|
[InlineData("https://ns.rec.net.example.com/ns")]
|
|
[InlineData("https://api.steampowered.com/")]
|
|
// Only the listed hosts are rewritten: a lookalike domain is someone else's traffic.
|
|
[InlineData("https://img.rec.net.evil.example/abc.png")]
|
|
[InlineData("https://myimg.rec.net/abc.png")]
|
|
// The service domain is not a wildcard either - the apex is the site, not a service, and
|
|
// a host that merely ends in it is someone else's.
|
|
[InlineData("https://auth.radicade.app.evil.example/connect/token")]
|
|
[InlineData("https://myauth.radicade.app/connect/token")]
|
|
[InlineData("https://radicade.app/ns")]
|
|
// The notify service's old short name was retired in favour of notifications.radicade.app,
|
|
// and the rename was clean: nothing maps this host any more, so a request still naming it
|
|
// is left exactly as it is - there is no second spelling of the service to drift from.
|
|
[InlineData("https://notify.radicade.app/hub/v1/negotiate")]
|
|
public void LeavesOtherRequestsAndAlreadyPatchedClientsUntouched(string input)
|
|
=> Assert.Equal(input, NameserverRedirect.Rewrite(input, "https://ns.radicade.app"));
|
|
|
|
[Fact]
|
|
public void PatchedPublicServerRequestsFollowLocalConfiguration()
|
|
{
|
|
Assert.Equal("http://127.0.0.1:3031/ns", NameserverRedirect.Rewrite("https://ns.radicade.app/ns", "http://127.0.0.1:3031"));
|
|
Assert.Equal("http://127.0.0.1:3031/api/config/v2?build=20230301", NameserverRedirect.Rewrite("https://ns.radicade.app/api/config/v2?build=20230301", "http://127.0.0.1:3031"));
|
|
Assert.Equal("https://ns.radicade.app.example.com/ns", NameserverRedirect.Rewrite("https://ns.radicade.app.example.com/ns", "http://127.0.0.1:3031"));
|
|
}
|
|
|
|
[Fact]
|
|
public void RedirectedServiceHostsFollowLocalConfigurationToo()
|
|
{
|
|
Assert.Equal("http://127.0.0.1:3031/images/abc.png", NameserverRedirect.Rewrite("https://img.rec.net/abc.png", "http://127.0.0.1:3031"));
|
|
Assert.Equal("http://127.0.0.1:3031/room/9c1f.bin", NameserverRedirect.Rewrite("https://cdn.rec.net/room/9c1f.bin", "http://127.0.0.1:3031"));
|
|
}
|
|
|
|
[Fact]
|
|
public void RejectsMalformedServerConfiguration()
|
|
=> Assert.Throws<ArgumentException>(() => NameserverRedirect.Rewrite("https://ns.rec.net", "localhost:3030"));
|
|
}
|