using BoardApp.Services; using BoardApp.Session; using Microsoft.AspNetCore.Mvc; namespace BoardApp.Controllers; [ApiController] public sealed class ApiController : ControllerBase { private readonly LdapAuthService _ldap; private readonly SqlDataSourceService _sql; private readonly UserScenarioService _userScenarios; public ApiController( LdapAuthService ldap, SqlDataSourceService sql, UserScenarioService userScenarios) { _ldap = ldap; _sql = sql; _userScenarios = userScenarios; } private string BasePath => (Request.PathBase.HasValue ? Request.PathBase.Value! : "/board"); public sealed class LoginReq { public string? Username { get; set; } public string? Password { get; set; } public string? Next { get; set; } } [HttpPost("/api/login")] public async Task Login([FromBody] LoginReq req) { var username = (req.Username ?? "").Trim(); var password = req.Password ?? ""; if (string.IsNullOrWhiteSpace(username) || string.IsNullOrEmpty(password)) { return Ok(new { ok = false, error = "缺少用户名或密码" }); } var appCfg = HttpContext.Items["BoardAppConfig"] as BoardApp.Config.AppConfig; // 与 Node 版一致:仅由 app.config.json 的 skipLdapAuthForDebug 控制;生产环境务必设为 false。 var skipAd = appCfg?.SkipLdapAuthForDebug == true; if (!skipAd) { var result = await _ldap.VerifyCredentials(username, password); if (!result.Ok) { var msg = result.Reason switch { LdapAuthService.LdapVerifyFailReason.UserNotFound => "用户不存在或无权访问", LdapAuthService.LdapVerifyFailReason.NotInAllowedGroup => "该账号不在允许访问的域组内", LdapAuthService.LdapVerifyFailReason.ConnectionError => "目录服务不可用,请稍后重试", _ => "用户名或密码错误" }; // 统一 200,避免 401/403 与 IIS/Windows 认证叠加后弹出系统凭据框 return Ok(new { ok = false, error = msg }); } } HttpContext.Session.SetJson("user", new SessionUser { Username = username }); await HttpContext.Session.CommitAsync(); var next = req.Next ?? ""; var safeNext = next.StartsWith(BasePath + "/", StringComparison.Ordinal) && !next.Contains("//", StringComparison.Ordinal) ? next : BasePath + "/"; return Ok(new { ok = true, redirect = safeNext }); } /// /// Windows 集成登录(IIS 开启 Windows Authentication 后生效): /// 自动读取域账号并写入 session。 /// [HttpPost("/api/winauth/login")] public async Task WindowsIntegratedLogin([FromBody] LoginReq req) { var loginPrincipal = await AdAuthenticationService.ResolveWindowsPrincipalAsync(HttpContext, User); if (loginPrincipal?.Identity?.IsAuthenticated != true) { return Ok(new { ok = false, error = "未识别为域用户或未通过 Windows 集成认证,请使用账号密码登录。" }); } var winName = AdAuthenticationService.TryGetWindowsSamAccountName(loginPrincipal); if (string.IsNullOrWhiteSpace(winName)) { return Ok(new { ok = false, error = "无法解析域账号,请使用账号密码登录。" }); } var appCfg = HttpContext.Items["BoardAppConfig"] as BoardApp.Config.AppConfig; var skipAd = appCfg?.SkipLdapAuthForDebug == true; if (!skipAd) { var r = await _ldap.VerifyUserForIntegratedLogin(winName); if (!r.Ok) { var msg = r.Reason switch { LdapAuthService.LdapVerifyFailReason.UserNotFound => "域账号在目录中不存在或无权访问", LdapAuthService.LdapVerifyFailReason.NotInAllowedGroup => "该域账号不在允许访问的组内", LdapAuthService.LdapVerifyFailReason.ConnectionError => "目录服务不可用,请稍后重试", _ => "一键登录失败,请使用账号密码登录" }; return Ok(new { ok = false, error = msg }); } } HttpContext.Session.SetJson("user", new SessionUser { Username = winName }); await HttpContext.Session.CommitAsync(); var next = req.Next ?? ""; var safeNext = next.StartsWith(BasePath + "/", StringComparison.Ordinal) && !next.Contains("//", StringComparison.Ordinal) ? next : BasePath + "/"; return Ok(new { ok = true, redirect = safeNext, username = winName }); } [HttpPost("/api/logout")] public IActionResult Logout() { HttpContext.Session.Clear(); Response.Cookies.Delete("board.sid", new CookieOptions { Path = "/" }); return Ok(new { ok = true }); } [HttpGet("/api/datasrc/query")] public async Task DataSrcQueryGet([FromQuery] string id) { var realId = (id ?? "").Trim(); if (string.IsNullOrWhiteSpace(realId)) { return BadRequest(new { ok = false, error = "缺少参数 id" }); } var dict = Request.Query.ToDictionary(k => k.Key, v => v.Value.ToString()); dict.Remove("id"); try { var p = SqlDataSourceService.CoerceParamsFromRequest(dict); var data = await _sql.ExecuteDataSourceById(realId, p); return Ok(new { ok = true, data }); } catch (Exception ex) { return StatusCode(500, new { ok = false, error = ex.Message }); } } public sealed class DataSrcPostReq { public string? Id { get; set; } public Dictionary? Extra { get; set; } } [HttpPost("/api/datasrc/query")] public async Task DataSrcQueryPost([FromBody] Dictionary body) { var realId = (body.TryGetValue("id", out var v) ? (v?.ToString() ?? "") : "").Trim(); if (string.IsNullOrWhiteSpace(realId)) { return BadRequest(new { ok = false, error = "缺少参数 id" }); } var raw = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var (k, val) in body) { if (string.Equals(k, "id", StringComparison.OrdinalIgnoreCase)) continue; raw[k] = val?.ToString(); } try { var p = SqlDataSourceService.CoerceParamsFromRequest(raw); var data = await _sql.ExecuteDataSourceById(realId, p); return Ok(new { ok = true, data }); } catch (Exception ex) { return StatusCode(500, new { ok = false, error = ex.Message }); } } public sealed class DeleteUserScenarioReq { public string? ScenarioId { get; set; } } [HttpPost("/api/user-scenarios/delete")] public IActionResult DeleteUserScenario([FromBody] DeleteUserScenarioReq req) { var username = HttpContext.Session.GetJson("user")?.Username ?? ""; var scenarioId = (req.ScenarioId ?? "").Trim(); if (scenarioId.Length == 0) { return BadRequest(new { ok = false, error = "缺少 scenarioId" }); } var r = _userScenarios.DeleteUserScenario(username, scenarioId); if (!r.Ok) return BadRequest(new { ok = false, error = r.Error }); return Ok(new { ok = true }); } public sealed class CreateUserScenarioReq { public string? Name { get; set; } public List? Pages { get; set; } } public sealed class PageRefReq { public string? PageId { get; set; } public int Staytime { get; set; } } [HttpPost("/api/user-scenarios/create")] public IActionResult CreateUserScenario([FromBody] CreateUserScenarioReq req) { var username = HttpContext.Session.GetJson("user")?.Username ?? ""; var name = req.Name ?? ""; var pages = (req.Pages ?? new List()) .Select(x => new BoardApp.Config.ScenarioPageRef { PageId = (x.PageId ?? "").Trim(), Staytime = x.Staytime }).ToList(); var r = _userScenarios.CreateUserScenario(username, name, pages); if (!r.Ok) return BadRequest(new { ok = false, error = r.Error }); return Ok(new { ok = true, scenario = r.Scenario }); } }