chore: initial project import
This commit is contained in:
commit
26620707a0
347 files changed
+56172
No files matched your search
@@ -0,0 +1,80 @@
|
||||
using Fleet.Core;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Monitor.Backend.Application;
|
||||
using Monitor.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Api;
|
||||
|
||||
/// <summary>登录、退出、当前身份。登录接口限流,失败一律返回 401,不区分“用户不存在”和“密码错误”。</summary>
|
||||
public static class AuthEndpoints
|
||||
{
|
||||
private const string Tag = "admin-auth";
|
||||
|
||||
/// <summary>登录不需要认证:挂在没有守卫的分组上。</summary>
|
||||
public static void MapLogin(RouteGroupBuilder open)
|
||||
{
|
||||
open.MapPost("/auth/login", LoginAsync)
|
||||
.Api("auth_login", "Sign in with a username and password", Tag)
|
||||
.Returns<LoginResponse>().ProducesProblems(400, 401, 429);
|
||||
}
|
||||
|
||||
public static void MapSession(RouteGroupBuilder guarded)
|
||||
{
|
||||
guarded.MapPost("/auth/logout", LogoutAsync)
|
||||
.Api("auth_logout", "Sign out and clear the session cookie", Tag)
|
||||
.Returns<object>(StatusCodes.Status204NoContent).ProducesProblems(401);
|
||||
guarded.MapGet("/auth/me", Me)
|
||||
.Api("auth_me", "The current identity", Tag)
|
||||
.Returns<MeDto>().ProducesProblems(401);
|
||||
}
|
||||
|
||||
private static async Task<IResult> LoginAsync(HttpContext http, LoginRequest request, AuthService auth, LoginRateLimiter limiter, BackendOptions options)
|
||||
{
|
||||
string source = http.Connection.RemoteIpAddress?.ToString() ?? "unknown";
|
||||
if (!limiter.TryAcquire(source, out int retryAfter))
|
||||
{
|
||||
return Problems.RetryAfter(http, new FleetError(MonitorErrors.TooManyRequests, "too many sign-in attempts"), retryAfter);
|
||||
}
|
||||
|
||||
Result<LoginResult> result = await auth.LoginAsync(request.Username ?? "", request.Password ?? "", http.RequestAborted);
|
||||
if (!result.TryGet(out LoginResult? login, out FleetError? error))
|
||||
{
|
||||
return Problems.From(http, error);
|
||||
}
|
||||
|
||||
http.Response.Cookies.Append(
|
||||
PrincipalResolver.SessionCookie,
|
||||
login.SessionToken,
|
||||
new CookieOptions
|
||||
{
|
||||
HttpOnly = true,
|
||||
Secure = options.SecureCookies,
|
||||
SameSite = SameSiteMode.Strict,
|
||||
Path = "/",
|
||||
Expires = DateTimeOffset.FromUnixTimeMilliseconds(login.ExpiresAtMs),
|
||||
});
|
||||
http.Response.Headers.CacheControl = "no-store";
|
||||
return TypedResults.Json(new LoginResponse(login.UserId, login.Username, login.CsrfToken, Mappers.Time(login.ExpiresAtMs)), ApiJsonContext.Default.LoginResponse);
|
||||
}
|
||||
|
||||
private static async Task<IResult> LogoutAsync(HttpContext http, AuthService auth)
|
||||
{
|
||||
if (http.Request.Cookies.TryGetValue(PrincipalResolver.SessionCookie, out string? session))
|
||||
{
|
||||
await auth.LogoutAsync(session, http.RequestAborted);
|
||||
}
|
||||
|
||||
http.Response.Cookies.Delete(PrincipalResolver.SessionCookie, new CookieOptions { Path = "/" });
|
||||
return TypedResults.NoContent();
|
||||
}
|
||||
|
||||
private static Microsoft.AspNetCore.Http.HttpResults.JsonHttpResult<MeDto> Me(HttpContext http)
|
||||
{
|
||||
Principal principal = http.GetPrincipal();
|
||||
return TypedResults.Json(
|
||||
new MeDto(principal.Kind.ToString().ToLowerInvariant(), principal.Scope.ToString().ToLowerInvariant(), principal.UserId, principal.Username, principal.CsrfToken),
|
||||
ApiJsonContext.Default.MeDto);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user