Editor/ProxyInfoDialog.cs
using Sandbox;
using Editor;
/// <summary>
/// Info popup explaining the Multiplayer Auth Proxy feature.
/// </summary>
public class ProxyInfoDialog : DockWindow
{
private Vector2 _mousePos;
private Rect _closeRect;
public ProxyInfoDialog()
{
Title = "Multiplayer Auth Proxy";
Size = new Vector2( 460, 380 );
}
public static void Open()
{
var dialog = new ProxyInfoDialog();
dialog.Show();
}
protected override void OnPaint()
{
base.OnPaint();
var pad = 20f;
var w = Width - pad * 2;
var y = 20f;
var lineH = 15f;
// Title
Paint.SetDefaultFont( size: 13, weight: 700 );
Paint.SetPen( Color.White );
Paint.DrawText( new Rect( pad, y, w, 22 ), "Multiplayer Auth Proxy", TextFlag.LeftCenter );
y += 32;
// What this does
Paint.SetDefaultFont( size: 9, weight: 600 );
Paint.SetPen( Color.White.WithAlpha( 0.7f ) );
Paint.DrawText( new Rect( pad, y, w, lineH ), "What this does:", TextFlag.LeftCenter );
y += lineH + 2;
Paint.SetDefaultFont( size: 9 );
Paint.SetPen( Color.White.WithAlpha( 0.5f ) );
DrawWrappedText( ref y, pad, w, lineH,
"Steam auth tokens are tied to the Steam session. When running " +
"multiple editor instances on the same machine (same Steam account), " +
"only the primary session can generate valid tokens \u2014 so editor " +
"clients fail to authenticate directly." );
y += 4;
DrawWrappedText( ref y, pad, w, lineH,
"When enabled, non-host clients route API calls through the host " +
"via RPC. The host authenticates with its own valid token and calls " +
"the backend on the client's behalf using a signed proxy request." );
y += 4;
Paint.SetDefaultFont( size: 9 );
Paint.SetPen( Color.White.WithAlpha( 0.45f ) );
DrawWrappedText( ref y, pad, w, lineH,
"Enable for local development and editor testing. In production, " +
"each player uses their own Steam account and can authenticate " +
"directly \u2014 you can safely disable this then." );
y += 12;
// Security notes
Paint.SetPen( Color.White.WithAlpha( 0.08f ) );
Paint.DrawLine( new Vector2( pad, y ), new Vector2( pad + w, y ) );
y += 10;
Paint.SetDefaultFont( size: 10, weight: 600 );
Paint.SetPen( Color.Yellow.WithAlpha( 0.7f ) );
Paint.DrawText( new Rect( pad, y, w, 18 ), "Security Notes", TextFlag.LeftCenter );
y += 24;
Paint.SetDefaultFont( size: 9 );
Paint.SetPen( Color.White.WithAlpha( 0.45f ) );
DrawWrappedText( ref y, pad, w, lineH,
"When proxy is enabled, the host can make API calls on behalf of " +
"any connected player. This is safe for P2P games where the host " +
"is already trusted (they control the game session)." );
y += 4;
DrawWrappedText( ref y, pad, w, lineH,
"The backend verifies both the host's auth token (Facepunch-validated) " +
"and an HMAC signature scoped to your project and endpoint, preventing " +
"cross-server replay attacks." );
y += 12;
// Integration hint
Paint.SetPen( Color.White.WithAlpha( 0.08f ) );
Paint.DrawLine( new Vector2( pad, y ), new Vector2( pad + w, y ) );
y += 10;
Paint.SetDefaultFont( size: 10, weight: 600 );
Paint.SetPen( Color.White.WithAlpha( 0.6f ) );
Paint.DrawText( new Rect( pad, y, w, 18 ), "Integration", TextFlag.LeftCenter );
y += 22;
Paint.SetDefaultFont( size: 9 );
Paint.SetPen( Color.White.WithAlpha( 0.4f ) );
DrawWrappedText( ref y, pad, w, lineH,
"Add NetworkStorageProxyComponent to each player's GameObject via " +
"PlayerSpawner. This component registers the proxy delegates that " +
"route non-host requests through the host via Broadcast RPC." );
y += 16;
// Close button
var btnW = 100f;
var btnH = 30f;
var btnRect = new Rect( ( Width - btnW ) / 2, y, btnW, btnH );
var btnHovered = btnRect.IsInside( _mousePos );
Paint.SetBrush( Color.White.WithAlpha( btnHovered ? 0.12f : 0.05f ) );
Paint.SetPen( Color.White.WithAlpha( btnHovered ? 0.4f : 0.2f ) );
Paint.DrawRect( btnRect, 4 );
Paint.SetDefaultFont( size: 10, weight: 600 );
Paint.SetPen( Color.White.WithAlpha( btnHovered ? 0.9f : 0.6f ) );
Paint.DrawText( btnRect, "Close", TextFlag.Center );
_closeRect = btnRect;
}
private void DrawWrappedText( ref float y, float x, float w, float lineH, string text )
{
var charsPerLine = System.Math.Max( 40, (int)( w / 6.2f ) );
var words = text.Split( ' ' );
var line = "";
foreach ( var word in words )
{
if ( line.Length + word.Length + 1 > charsPerLine && line.Length > 0 )
{
Paint.DrawText( new Rect( x, y, w, lineH ), line, TextFlag.LeftCenter );
y += lineH;
line = word;
}
else
{
line = line.Length > 0 ? $"{line} {word}" : word;
}
}
if ( line.Length > 0 )
{
Paint.DrawText( new Rect( x, y, w, lineH ), line, TextFlag.LeftCenter );
y += lineH;
}
}
protected override void OnMousePress( MouseEvent e )
{
base.OnMousePress( e );
if ( _closeRect.IsInside( e.LocalPosition ) )
Close();
}
protected override void OnMouseMove( MouseEvent e )
{
base.OnMouseMove( e );
_mousePos = e.LocalPosition;
Update();
}
}