验证码优化
This commit is contained in:
1 parent
f489a3553c
commit
4a3bb84aa4
17 files changed
+155
-429
No files matched your search
@@ -63,7 +63,7 @@ public sealed class ArchivesController(AppDbContext db, ICurrentUserDataScope sc
|
|||||||
}
|
}
|
||||||
|
|
||||||
public sealed record ArchiveBatchRequest(
|
public sealed record ArchiveBatchRequest(
|
||||||
[property: Required, MinLength(1), MaxLength(100)] Guid[] Ids,
|
[param: Required, MinLength(1), MaxLength(100)] Guid[] Ids,
|
||||||
bool Archived,
|
bool Archived,
|
||||||
[property: MaxLength(500)] string? Reason = null);
|
[param: MaxLength(500)] string? Reason = null);
|
||||||
|
|
||||||
@@ -4,6 +4,7 @@ using Jiaowu.Api.Domain.Academic;
|
|||||||
using Jiaowu.Api.Domain.Identity;
|
using Jiaowu.Api.Domain.Identity;
|
||||||
using Jiaowu.Api.Infrastructure.Auth;
|
using Jiaowu.Api.Infrastructure.Auth;
|
||||||
using Jiaowu.Api.Infrastructure.Caching;
|
using Jiaowu.Api.Infrastructure.Caching;
|
||||||
|
using Lazy.Captcha.Core;
|
||||||
using Jiaowu.Api.Infrastructure.Persistence;
|
using Jiaowu.Api.Infrastructure.Persistence;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
@@ -20,7 +21,7 @@ public sealed class AuthController(
|
|||||||
UserManager<ApplicationUser> userManager,
|
UserManager<ApplicationUser> userManager,
|
||||||
IAuthSessionService authSessionService,
|
IAuthSessionService authSessionService,
|
||||||
ITwoFactorLoginTicketService twoFactorTickets,
|
ITwoFactorLoginTicketService twoFactorTickets,
|
||||||
ILoginCaptchaService loginCaptcha,
|
ICaptcha captcha,
|
||||||
IAppCache cache) : ControllerBase
|
IAppCache cache) : ControllerBase
|
||||||
{
|
{
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
@@ -139,30 +140,13 @@ public sealed class AuthController(
|
|||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[EnableRateLimiting("public-auth")]
|
[EnableRateLimiting("public-auth")]
|
||||||
[HttpPost("login/captcha")]
|
[HttpPost("login/captcha")]
|
||||||
public async Task<ActionResult<LoginCaptchaChallenge>> CreateLoginCaptcha(
|
public ActionResult<LazyLoginCaptchaResponse> CreateLoginCaptcha()
|
||||||
LoginCaptchaCreateRequest request,
|
|
||||||
CancellationToken cancellationToken) =>
|
|
||||||
Ok(await loginCaptcha.CreateAsync(request.DeviceId, HttpContext, cancellationToken));
|
|
||||||
|
|
||||||
[AllowAnonymous]
|
|
||||||
[EnableRateLimiting("public-auth")]
|
|
||||||
[HttpPost("login/captcha/verify")]
|
|
||||||
public async Task<ActionResult<LoginCaptchaProofResponse>> VerifyLoginCaptcha(
|
|
||||||
LoginCaptchaVerifyRequest request,
|
|
||||||
CancellationToken cancellationToken)
|
|
||||||
{
|
{
|
||||||
var proof = await loginCaptcha.VerifyAsync(
|
var captchaId = Guid.NewGuid().ToString("N");
|
||||||
new LoginCaptchaVerification(
|
var generated = captcha.Generate(captchaId, expirySeconds: 120);
|
||||||
request.ChallengeId,
|
return Ok(new LazyLoginCaptchaResponse(
|
||||||
request.DeviceId,
|
captchaId,
|
||||||
request.Text,
|
$"data:image/gif;base64,{Convert.ToBase64String(generated.Bytes)}"));
|
||||||
request.SliderX,
|
|
||||||
request.Clicks),
|
|
||||||
HttpContext,
|
|
||||||
cancellationToken);
|
|
||||||
return proof is null
|
|
||||||
? Unauthorized(LoginCaptchaProblem())
|
|
||||||
: Ok(new LoginCaptchaProofResponse(proof));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
@@ -172,11 +156,7 @@ public sealed class AuthController(
|
|||||||
LoginRequest request,
|
LoginRequest request,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (!await loginCaptcha.ConsumeProofAsync(
|
if (!captcha.Validate(request.CaptchaId, request.CaptchaCode))
|
||||||
request.CaptchaTicket,
|
|
||||||
request.DeviceId,
|
|
||||||
HttpContext,
|
|
||||||
cancellationToken))
|
|
||||||
return Unauthorized(LoginCaptchaProblem());
|
return Unauthorized(LoginCaptchaProblem());
|
||||||
var user = await userManager.FindByNameAsync(request.UserName);
|
var user = await userManager.FindByNameAsync(request.UserName);
|
||||||
if (user is null || !user.IsEnabled)
|
if (user is null || !user.IsEnabled)
|
||||||
@@ -350,20 +330,11 @@ public sealed class AuthController(
|
|||||||
public sealed record LoginRequest(
|
public sealed record LoginRequest(
|
||||||
[Required, MaxLength(100)] string UserName,
|
[Required, MaxLength(100)] string UserName,
|
||||||
[Required, MaxLength(100)] string Password,
|
[Required, MaxLength(100)] string Password,
|
||||||
[Required, MinLength(32), MaxLength(128)] string CaptchaTicket,
|
[Required, MinLength(16), MaxLength(64)] string CaptchaId,
|
||||||
[MaxLength(128)] string? DeviceId = null,
|
[Required, MinLength(3), MaxLength(16)] string CaptchaCode,
|
||||||
bool IsNativeApp = false);
|
bool IsNativeApp = false);
|
||||||
|
|
||||||
public sealed record LoginCaptchaCreateRequest([MaxLength(128)] string? DeviceId = null);
|
public sealed record LazyLoginCaptchaResponse(string CaptchaId, string ImageData);
|
||||||
|
|
||||||
public sealed record LoginCaptchaVerifyRequest(
|
|
||||||
[Required, MinLength(32), MaxLength(64)] string ChallengeId,
|
|
||||||
[MaxLength(128)] string? DeviceId,
|
|
||||||
[MaxLength(16)] string? Text,
|
|
||||||
[Range(0, 240)] int? SliderX,
|
|
||||||
[MaxLength(2)] IReadOnlyList<CaptchaPoint>? Clicks);
|
|
||||||
|
|
||||||
public sealed record LoginCaptchaProofResponse(string CaptchaTicket);
|
|
||||||
|
|
||||||
public sealed record TotpLoginRequest(
|
public sealed record TotpLoginRequest(
|
||||||
[Required, MinLength(20), MaxLength(2048)] string TwoFactorTicket,
|
[Required, MinLength(20), MaxLength(2048)] string TwoFactorTicket,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ using Microsoft.AspNetCore.RateLimiting;
|
|||||||
using Microsoft.AspNetCore.WebUtilities;
|
using Microsoft.AspNetCore.WebUtilities;
|
||||||
using Microsoft.Extensions.Caching.Distributed;
|
using Microsoft.Extensions.Caching.Distributed;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
using Lazy.Captcha.Core;
|
||||||
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||||
|
|
||||||
namespace Jiaowu.Api.Controllers;
|
namespace Jiaowu.Api.Controllers;
|
||||||
@@ -22,7 +23,7 @@ public sealed class SsoController(
|
|||||||
UserManager<ApplicationUser> userManager,
|
UserManager<ApplicationUser> userManager,
|
||||||
IAuthSessionService authSessionService,
|
IAuthSessionService authSessionService,
|
||||||
IDistributedCache cache,
|
IDistributedCache cache,
|
||||||
ILoginCaptchaService loginCaptcha,
|
ICaptcha captcha,
|
||||||
IOptions<SsoOptions> options,
|
IOptions<SsoOptions> options,
|
||||||
ILogger<SsoController> logger) : ControllerBase
|
ILogger<SsoController> logger) : ControllerBase
|
||||||
{
|
{
|
||||||
@@ -47,8 +48,8 @@ public sealed class SsoController(
|
|||||||
[FromQuery] string? bindingIntent = null,
|
[FromQuery] string? bindingIntent = null,
|
||||||
[FromQuery] bool nativeApp = false,
|
[FromQuery] bool nativeApp = false,
|
||||||
[FromQuery] string? nativeState = null,
|
[FromQuery] string? nativeState = null,
|
||||||
[FromQuery] string? captchaTicket = null,
|
[FromQuery] string? captchaId = null,
|
||||||
[FromQuery] string? deviceId = null,
|
[FromQuery] string? captchaCode = null,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (!_options.Enabled)
|
if (!_options.Enabled)
|
||||||
@@ -56,8 +57,9 @@ public sealed class SsoController(
|
|||||||
|
|
||||||
var safeReturnUrl = NormalizeReturnUrl(returnUrl);
|
var safeReturnUrl = NormalizeReturnUrl(returnUrl);
|
||||||
if (string.IsNullOrWhiteSpace(bindingIntent) &&
|
if (string.IsNullOrWhiteSpace(bindingIntent) &&
|
||||||
(string.IsNullOrWhiteSpace(captchaTicket) ||
|
(string.IsNullOrWhiteSpace(captchaId) ||
|
||||||
!await loginCaptcha.ConsumeProofAsync(captchaTicket, deviceId, HttpContext, cancellationToken)))
|
string.IsNullOrWhiteSpace(captchaCode) ||
|
||||||
|
!captcha.Validate(captchaId, captchaCode)))
|
||||||
{
|
{
|
||||||
return Unauthorized(AuthController.LoginCaptchaProblem());
|
return Unauthorized(AuthController.LoginCaptchaProblem());
|
||||||
}
|
}
|
||||||
@@ -417,8 +419,8 @@ public sealed class SsoController(
|
|||||||
bindingIntent = intentCode,
|
bindingIntent = intentCode,
|
||||||
nativeApp,
|
nativeApp,
|
||||||
nativeState,
|
nativeState,
|
||||||
captchaTicket = (string?)null,
|
captchaId = (string?)null,
|
||||||
deviceId = (string?)null
|
captchaCode = (string?)null
|
||||||
})!;
|
})!;
|
||||||
return new SsoBindingStartResponse(loginUrl);
|
return new SsoBindingStartResponse(loginUrl);
|
||||||
}
|
}
|
||||||
|
|||||||
Loaded 3 of 17 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user