fix(server): 转发头可信代理可配置、登录冷启动非阻塞与启动路径/授权健壮化

Program.cs 的 ForwardedHeaders 改为读 ForwardedHeaders:KnownProxies 显式收紧可信代理
(未配置则维持信任所有,适合同机/内网;配置后按数量收紧 ForwardLimit,解析全失败 fail-closed),
并补 AnyAuthed 授权策略、内容根/前端目录三级探测、OpsAuditStore 与 HttpClientFactory 注册;
AuthController 登录改 MaybeStart(waitForReady:false) 避免 SimpleLite 冷启动阻塞登录;
SimpleLiteLauncher 增强相对 ContentRoot 的文件/目录解析,兼容从 bin 目录运行。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zhaowei.huang
2026-06-08 16:10:34 +08:00
co-authored by Cursor
parent 4ca5a5afeb
commit 655d50e04d
3 changed files with 143 additions and 18 deletions
+52 -5
View File
@@ -61,7 +61,7 @@ public sealed class SimpleLiteLauncher : IDisposable
/// </summary>
/// <param name="launchMode">"WebOnly" 或 "DesktopAndWeb"(大小写不敏感)。</param>
/// <returns>本次调用产生的状态摘要,可写入登录响应或日志。</returns>
public LaunchResult MaybeStart(string launchMode)
public LaunchResult MaybeStart(string launchMode, bool waitForReady = true)
{
var displayMode = NormalizeDisplayMode(launchMode);
@@ -150,7 +150,7 @@ public sealed class SimpleLiteLauncher : IDisposable
var workdir = string.IsNullOrWhiteSpace(_opts.WorkingDirectory)
? Path.GetDirectoryName(resolved) ?? Environment.CurrentDirectory
: Path.GetFullPath(_opts.WorkingDirectory);
: ResolveConfiguredDirectory(_opts.WorkingDirectory) ?? Path.GetDirectoryName(resolved) ?? Environment.CurrentDirectory;
var arguments = BuildArguments(displayMode, _opts.Arguments);
@@ -182,6 +182,17 @@ public sealed class SimpleLiteLauncher : IDisposable
}
}
// M1:登录路径传 waitForReady=false —— 进程拉起后立即返回,不再同步阻塞最长
// ReadinessTimeoutMs 等端口就绪(避免冷启动登录干等十几秒)。前端可轮询
// GET /api/health/simplelite 获知就绪状态。
if (!waitForReady)
{
return new LaunchResult(true, "Starting",
$"projection :{_opts.ProjectionPort} readiness wait skipped (async), displayMode={displayMode}",
DisplayMode: displayMode,
Warning: null);
}
var ready = WaitForProjectionReady();
return new LaunchResult(true, ready ? "Ready" : "StartedButNotReady",
ready ? $"projection :{_opts.ProjectionPort} reachable, displayMode={displayMode}"
@@ -262,7 +273,7 @@ public sealed class SimpleLiteLauncher : IDisposable
public string? ResolveWorkingDirectory()
{
if (!string.IsNullOrWhiteSpace(_opts.WorkingDirectory))
return Path.GetFullPath(_opts.WorkingDirectory);
return ResolveConfiguredDirectory(_opts.WorkingDirectory);
var resolved = ResolveExecutable(_opts.ExecutablePath);
return resolved == null ? null : (Path.GetDirectoryName(resolved) ?? Environment.CurrentDirectory);
}
@@ -518,8 +529,8 @@ public sealed class SimpleLiteLauncher : IDisposable
{
if (!string.IsNullOrWhiteSpace(configured))
{
var p = Path.IsPathRooted(configured) ? configured : Path.GetFullPath(configured, _env.ContentRootPath);
return File.Exists(p) ? p : null;
var p = ResolveConfiguredFile(configured);
if (p != null) return p;
}
var cwd = _env.ContentRootPath;
@@ -562,6 +573,42 @@ public sealed class SimpleLiteLauncher : IDisposable
return null;
}
private string? ResolveConfiguredFile(string configured)
{
foreach (var baseDir in EnumeratePathBases())
{
var p = Path.IsPathRooted(configured)
? configured
: Path.GetFullPath(configured, baseDir);
if (File.Exists(p)) return p;
if (Path.IsPathRooted(configured)) break;
}
return null;
}
private string? ResolveConfiguredDirectory(string configured)
{
foreach (var baseDir in EnumeratePathBases())
{
var p = Path.IsPathRooted(configured)
? configured
: Path.GetFullPath(configured, baseDir);
if (Directory.Exists(p)) return p;
if (Path.IsPathRooted(configured)) break;
}
return null;
}
private IEnumerable<string> EnumeratePathBases()
{
var dir = new DirectoryInfo(_env.ContentRootPath);
while (dir != null)
{
yield return dir.FullName;
dir = dir.Parent;
}
}
private bool WaitForProjectionReady()
{
if (_opts.ReadinessTimeoutMs == 0) return false;