🐛 Fixes and overhaul the auth experience

This commit is contained in:
LittleSheep 2025-06-07 12:14:42 +08:00
parent 2f051d0615
commit 0e78f7f7d2
4 changed files with 23 additions and 14 deletions

View File

@ -141,6 +141,8 @@ public class AccountAuthFactor : ModelBase
case AccountAuthFactorType.TimedCode:
var otp = new Totp(Base32Encoding.ToBytes(Secret));
return otp.VerifyTotp(DateTime.UtcNow, password, out _, new VerificationWindow(previous: 5, future: 5));
case AccountAuthFactorType.EmailCode:
case AccountAuthFactorType.InAppCode:
default:
throw new InvalidOperationException("Unsupported verification type, use CheckDeliveredCode instead.");
}

View File

@ -357,7 +357,7 @@ public class AccountCurrentController(
[HttpPost("factors/{id:guid}/enable")]
[Authorize]
public async Task<ActionResult<AccountAuthFactor>> EnableAuthFactor(Guid id, [FromBody] string code)
public async Task<ActionResult<AccountAuthFactor>> EnableAuthFactor(Guid id, [FromBody] string? code)
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();

View File

@ -157,13 +157,16 @@ public class AccountService(
return factor;
}
public async Task<AccountAuthFactor> EnableAuthFactor(AccountAuthFactor factor, string code)
public async Task<AccountAuthFactor> EnableAuthFactor(AccountAuthFactor factor, string? code)
{
if (factor.EnabledAt is not null) throw new ArgumentException("The factor has been enabled.");
if (!factor.VerifyPassword(code))
if (factor.Type is AccountAuthFactorType.Password or AccountAuthFactorType.TimedCode)
{
if (code is null || !factor.VerifyPassword(code))
throw new InvalidOperationException(
"Invalid code, you need to enter the correct code to enable the factor."
);
}
factor.EnabledAt = SystemClock.Instance.GetCurrentInstant();
db.Update(factor);
@ -263,8 +266,8 @@ public class AccountService(
}
await email.SendTemplatedEmailAsync<VerificationEmail, VerificationEmailModel>(
account.Nick,
contact.Content,
localizer["EmailVerificationTitle"],
localizer["VerificationEmail"],
new VerificationEmailModel
{

View File

@ -53,7 +53,7 @@ public class AuthController(
var challenge = new Challenge
{
ExpiredAt = Instant.FromDateTimeUtc(DateTime.UtcNow.AddHours(1)),
StepTotal = 1,
StepTotal = 3,
Platform = request.Platform,
Audiences = request.Audiences,
Scopes = request.Scopes,
@ -80,10 +80,11 @@ public class AuthController(
var challenge = await db.AuthChallenges
.Include(e => e.Account)
.Include(e => e.Account.AuthFactors)
.Where(e => e.Id == id).FirstOrDefaultAsync();
.Where(e => e.Id == id)
.FirstOrDefaultAsync();
return challenge is null
? NotFound("Auth challenge was not found.")
: challenge.Account.AuthFactors.ToList();
: challenge.Account.AuthFactors.Where(e => e.EnabledAt != null).ToList();
}
[HttpPost("challenge/{id:guid}/factors/{factorId:guid}")]
@ -131,6 +132,8 @@ public class AuthController(
var factor = await db.AccountAuthFactors.FindAsync(request.FactorId);
if (factor is null) return NotFound("Auth factor was not found.");
if (factor.EnabledAt is null) return BadRequest("Auth factor is not enabled.");
if (factor.Trustworthy <= 0) return BadRequest("Auth factor is not trustworthy.");
if (challenge.StepRemain == 0) return challenge;
if (challenge.ExpiredAt.HasValue && challenge.ExpiredAt.Value < Instant.FromDateTimeUtc(DateTime.UtcNow))
@ -140,7 +143,8 @@ public class AuthController(
{
if (await accounts.VerifyFactorCode(factor, request.Password))
{
challenge.StepRemain--;
challenge.StepRemain -= factor.Trustworthy;
challenge.StepRemain = Math.Max(0, challenge.StepRemain);
challenge.BlacklistFactors.Add(factor.Id);
db.Update(challenge);
als.CreateActionLogFromRequest(ActionLogType.ChallengeSuccess,