🐛 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: case AccountAuthFactorType.TimedCode:
var otp = new Totp(Base32Encoding.ToBytes(Secret)); var otp = new Totp(Base32Encoding.ToBytes(Secret));
return otp.VerifyTotp(DateTime.UtcNow, password, out _, new VerificationWindow(previous: 5, future: 5)); return otp.VerifyTotp(DateTime.UtcNow, password, out _, new VerificationWindow(previous: 5, future: 5));
case AccountAuthFactorType.EmailCode:
case AccountAuthFactorType.InAppCode:
default: default:
throw new InvalidOperationException("Unsupported verification type, use CheckDeliveredCode instead."); throw new InvalidOperationException("Unsupported verification type, use CheckDeliveredCode instead.");
} }

View File

@ -357,7 +357,7 @@ public class AccountCurrentController(
[HttpPost("factors/{id:guid}/enable")] [HttpPost("factors/{id:guid}/enable")]
[Authorize] [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(); if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();

View File

@ -157,13 +157,16 @@ public class AccountService(
return factor; 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.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( throw new InvalidOperationException(
"Invalid code, you need to enter the correct code to enable the factor." "Invalid code, you need to enter the correct code to enable the factor."
); );
}
factor.EnabledAt = SystemClock.Instance.GetCurrentInstant(); factor.EnabledAt = SystemClock.Instance.GetCurrentInstant();
db.Update(factor); db.Update(factor);
@ -263,8 +266,8 @@ public class AccountService(
} }
await email.SendTemplatedEmailAsync<VerificationEmail, VerificationEmailModel>( await email.SendTemplatedEmailAsync<VerificationEmail, VerificationEmailModel>(
account.Nick,
contact.Content, contact.Content,
localizer["EmailVerificationTitle"],
localizer["VerificationEmail"], localizer["VerificationEmail"],
new VerificationEmailModel new VerificationEmailModel
{ {

View File

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