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<IActionResult> 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 });
|
}
|
|
/// <summary>
|
/// Windows 集成登录(IIS 开启 Windows Authentication 后生效):
|
/// 自动读取域账号并写入 session。
|
/// </summary>
|
[HttpPost("/api/winauth/login")]
|
public async Task<IActionResult> 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<IActionResult> 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<string, object?>? Extra { get; set; }
|
}
|
|
[HttpPost("/api/datasrc/query")]
|
public async Task<IActionResult> DataSrcQueryPost([FromBody] Dictionary<string, object?> 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<string, string?>(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<SessionUser>("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<PageRefReq>? 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<SessionUser>("user")?.Username ?? "";
|
var name = req.Name ?? "";
|
var pages = (req.Pages ?? new List<PageRefReq>())
|
.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 });
|
}
|
}
|
|