add passkey & totp
This commit is contained in:
1 parent
70f6b52577
commit
1d06bbd482
13 files changed
+9114
-4
No files matched your search
@@ -19,6 +19,7 @@ public sealed class AuthController(
|
||||
AppDbContext db,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
IAuthSessionService authSessionService,
|
||||
ITwoFactorLoginTicketService twoFactorTickets,
|
||||
IAppCache cache) : ControllerBase
|
||||
{
|
||||
[AllowAnonymous]
|
||||
@@ -137,7 +138,7 @@ public sealed class AuthController(
|
||||
[AllowAnonymous]
|
||||
[EnableRateLimiting("public-auth")]
|
||||
[HttpPost("login")]
|
||||
public async Task<ActionResult<LoginResponse>> Login(
|
||||
public async Task<ActionResult> Login(
|
||||
LoginRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -164,6 +165,11 @@ public sealed class AuthController(
|
||||
});
|
||||
}
|
||||
|
||||
if (user.TwoFactorEnabled)
|
||||
{
|
||||
return Ok(new TwoFactorRequiredResponse(twoFactorTickets.Create(user.Id, request.IsNativeApp)));
|
||||
}
|
||||
|
||||
await userManager.ResetAccessFailedCountAsync(user);
|
||||
user.LastLoginAt = DateTime.UtcNow;
|
||||
await userManager.UpdateAsync(user);
|
||||
@@ -176,6 +182,36 @@ public sealed class AuthController(
|
||||
? AuthenticationClientType.App
|
||||
: AuthenticationClientType.Web,
|
||||
cancellationToken);
|
||||
return Ok(CreateLoginResponse(session));
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[EnableRateLimiting("public-auth")]
|
||||
[HttpPost("login/totp")]
|
||||
public async Task<ActionResult<LoginResponse>> CompleteTotpLogin(
|
||||
TotpLoginRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!twoFactorTickets.TryRead(request.TwoFactorTicket, out var userId, out var isNativeApp))
|
||||
return Unauthorized(LoginProblem());
|
||||
var user = await userManager.FindByIdAsync(userId.ToString("D"));
|
||||
var code = request.Code.Replace(" ", string.Empty);
|
||||
var isValid = user is not null && (await userManager.VerifyTwoFactorTokenAsync(
|
||||
user, TokenOptions.DefaultAuthenticatorProvider, code) ||
|
||||
(await userManager.RedeemTwoFactorRecoveryCodeAsync(user, code)).Succeeded);
|
||||
if (user is null || !user.IsEnabled || await userManager.IsLockedOutAsync(user) || !isValid)
|
||||
{
|
||||
if (user is not null) await userManager.AccessFailedAsync(user);
|
||||
return Unauthorized(LoginProblem());
|
||||
}
|
||||
|
||||
await userManager.ResetAccessFailedCountAsync(user);
|
||||
user.LastLoginAt = DateTime.UtcNow;
|
||||
await userManager.UpdateAsync(user);
|
||||
var roles = await userManager.GetRolesAsync(user);
|
||||
var session = await authSessionService.CreateAsync(user, roles,
|
||||
isNativeApp ? AuthenticationClientType.App : AuthenticationClientType.Web,
|
||||
cancellationToken);
|
||||
return CreateLoginResponse(session);
|
||||
}
|
||||
|
||||
@@ -248,6 +284,12 @@ public sealed class AuthController(
|
||||
Status = status
|
||||
});
|
||||
|
||||
private static ProblemDetails LoginProblem() => new()
|
||||
{
|
||||
Title = "登录失败", Detail = "验证码无效、已过期或账号已停用。",
|
||||
Status = StatusCodes.Status401Unauthorized
|
||||
};
|
||||
|
||||
internal static LoginResponse CreateLoginResponse(AuthSessionResult session) =>
|
||||
new(
|
||||
session.AccessToken,
|
||||
@@ -268,6 +310,15 @@ public sealed record LoginRequest(
|
||||
[Required, MaxLength(100)] string Password,
|
||||
bool IsNativeApp = false);
|
||||
|
||||
public sealed record TotpLoginRequest(
|
||||
[Required, MinLength(20), MaxLength(2048)] string TwoFactorTicket,
|
||||
[Required, MinLength(6), MaxLength(12)] string Code);
|
||||
|
||||
public sealed record TwoFactorRequiredResponse(string TwoFactorTicket)
|
||||
{
|
||||
public bool RequiresTotp => true;
|
||||
}
|
||||
|
||||
public sealed record RefreshTokenRequest(
|
||||
[Required, MinLength(40), MaxLength(200)] string RefreshToken);
|
||||
|
||||
|
||||
Reference in new issue
Block a user