Sidecar: auth token for the website-facing API
config.rs loads all runtime settings from an external sidecar.toml (path via $UOLINK_CONFIG), with env-var overrides (UOLINK_WEB_TOKEN, UOLINK_WEB_BIND, UOLINK_SHARD_BIND, UOLINK_DB_PATH). Nothing is compiled into the binary. On first run the file is generated with a random 24-byte auth token, so the sidecar is secured out of the box and the operator just copies the token to the website. An axum middleware rejects any request to a non-/health route that does not present the token, as Authorization: Bearer, X-Api-Key, or ?token= (the last so browser WebSocket clients, which cannot set handshake headers, can authenticate). The comparison is constant-time. An empty token disables auth and is only tolerated on a loopback bind; binding to 0.0.0.0 with no token logs a warning. Verified: /health open (200); /history 401 without a token, 401 with a wrong one, 200 with the right one via either Bearer or X-Api-Key; an authed shard query falls through to 503 when no shard is connected; WS rejected (401) with a bad ?token= and upgraded (101) with the right one. sidecar.toml is gitignored (holds the secret); sidecar.toml.example is committed as the reference. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
98
tools/scaffolding/BridgeVendorTestProbe.cs
Normal file
98
tools/scaffolding/BridgeVendorTestProbe.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
using Server.Accounting;
|
||||
using Server.Items;
|
||||
using Server.Mobiles;
|
||||
|
||||
namespace Server.Custom
|
||||
{
|
||||
/// <summary>
|
||||
/// Spawns a reachable, houseless player vendor next to the `tester` character (wttest) and
|
||||
/// gives tester gold, so the PlayerVendorSale core patch can be exercised by a real in-game
|
||||
/// purchase. The buyer must be a non-GM — IsOwner() treats any GameMaster+ as the owner of
|
||||
/// every player vendor, so an Owner-level character can never buy.
|
||||
///
|
||||
/// Owner is a seed_000 character, so buyer (wttest) and owner (seed_000) are different
|
||||
/// accounts — a clean cheat-detection example.
|
||||
///
|
||||
/// Test scaffolding. Never deployed. Spawns a mobile and hands out gold; run only on the
|
||||
/// throwaway seeded world.
|
||||
/// </summary>
|
||||
public static class BridgeVendorTestProbe
|
||||
{
|
||||
private static readonly MethodInfo SetVendorItem = typeof(PlayerVendor).GetMethod(
|
||||
"SetVendorItem",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic,
|
||||
null,
|
||||
new[] { typeof(Item), typeof(int), typeof(string) },
|
||||
null);
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
if (Config.Get("Bridge.VendorTestOnStart", false))
|
||||
EventSink.ServerStarted += () => Timer.DelayCall(TimeSpan.FromSeconds(3.0), Run);
|
||||
}
|
||||
|
||||
private static void Run()
|
||||
{
|
||||
try
|
||||
{
|
||||
var ownerAcct = Accounting.Accounts.GetAccount("seed_000") as Account;
|
||||
var owner = ownerAcct == null ? null : ownerAcct[0];
|
||||
|
||||
var buyerAcct = Accounting.Accounts.GetAccount("wttest") as Account;
|
||||
var buyer = buyerAcct == null ? null : buyerAcct[0] as PlayerMobile;
|
||||
|
||||
if (owner == null || buyer == null)
|
||||
{
|
||||
Console.WriteLine("[VendorTest] need seed_000 owner and wttest/tester; not found");
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove any prior test vendor (e.g. one orphaned on the Internal map).
|
||||
if (PlayerVendor.PlayerVendors != null)
|
||||
{
|
||||
var doomed = new System.Collections.Generic.List<PlayerVendor>();
|
||||
foreach (var v in PlayerVendor.PlayerVendors)
|
||||
if (v != null && !v.Deleted && v.ShopName == "Bridge Test Shop")
|
||||
doomed.Add(v);
|
||||
foreach (var v in doomed)
|
||||
v.Delete();
|
||||
}
|
||||
|
||||
// A confirmed-walkable spot where tester was already standing this session, so the
|
||||
// vendor is on solid ground and reachable. Then force tester's logout location right
|
||||
// next to it, so tester logs in beside the vendor regardless of where it was.
|
||||
var map = Map.Trammel;
|
||||
var loc = new Point3D(3533, 2546, 20); // vendor
|
||||
buyer.LogoutMap = map;
|
||||
buyer.LogoutLocation = new Point3D(3532, 2546, 20); // tester appears here
|
||||
|
||||
var vendor = new PlayerVendor(owner, null)
|
||||
{
|
||||
Name = "test vendor",
|
||||
ShopName = "Bridge Test Shop"
|
||||
};
|
||||
vendor.MoveToWorld(loc, map);
|
||||
|
||||
var blade = new Longsword();
|
||||
vendor.Backpack.DropItem(blade);
|
||||
if (SetVendorItem != null)
|
||||
SetVendorItem.Invoke(vendor, new object[] { blade, 100, "a test blade" });
|
||||
|
||||
// Make sure tester can afford it.
|
||||
if (buyer.Backpack != null)
|
||||
buyer.Backpack.DropItem(new Gold(1000));
|
||||
|
||||
Console.WriteLine(
|
||||
"[VendorTest] spawned '{0}' (owner {1}/seed_000) next to {2}/wttest at {3} on {4}; test blade = 100 gold; gave tester 1000 gold",
|
||||
vendor.ShopName, owner.Name, buyer.Name, loc, map);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("[VendorTest] FAILED: " + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user