diff --git a/DysonNetwork.Sphere/Account/Account.cs b/DysonNetwork.Sphere/Account/Account.cs index 46fdab1..a3e2b4e 100644 --- a/DysonNetwork.Sphere/Account/Account.cs +++ b/DysonNetwork.Sphere/Account/Account.cs @@ -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."); } diff --git a/DysonNetwork.Sphere/Account/AccountCurrentController.cs b/DysonNetwork.Sphere/Account/AccountCurrentController.cs index 966606f..eb0eac3 100644 --- a/DysonNetwork.Sphere/Account/AccountCurrentController.cs +++ b/DysonNetwork.Sphere/Account/AccountCurrentController.cs @@ -357,7 +357,7 @@ public class AccountCurrentController( [HttpPost("factors/{id:guid}/enable")] [Authorize] - public async Task> EnableAuthFactor(Guid id, [FromBody] string code) + public async Task> EnableAuthFactor(Guid id, [FromBody] string? code) { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); diff --git a/DysonNetwork.Sphere/Account/AccountService.cs b/DysonNetwork.Sphere/Account/AccountService.cs index 3381c8b..abd3d8b 100644 --- a/DysonNetwork.Sphere/Account/AccountService.cs +++ b/DysonNetwork.Sphere/Account/AccountService.cs @@ -157,13 +157,16 @@ public class AccountService( return factor; } - public async Task EnableAuthFactor(AccountAuthFactor factor, string code) + public async Task EnableAuthFactor(AccountAuthFactor factor, string? code) { if (factor.EnabledAt is not null) throw new ArgumentException("The factor has been enabled."); - if (!factor.VerifyPassword(code)) - throw new InvalidOperationException( - "Invalid code, you need to enter the correct code to enable the factor." - ); + 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); @@ -186,7 +189,7 @@ public class AccountService( factor.EnabledAt = null; db.Update(factor); await db.SaveChangesAsync(); - + return factor; } @@ -219,7 +222,7 @@ public class AccountService( case AccountAuthFactorType.InAppCode: if (await _GetFactorCode(factor) is not null) throw new InvalidOperationException("A factor code has been sent and in active duration."); - + await nty.SendNotification( account, "auth.verification", @@ -233,7 +236,7 @@ public class AccountService( case AccountAuthFactorType.EmailCode: if (await _GetFactorCode(factor) is not null) throw new InvalidOperationException("A factor code has been sent and in active duration."); - + ArgumentNullException.ThrowIfNull(hint); hint = hint.Replace("@", "").Replace(".", "").Replace("+", "").Replace("%", ""); if (string.IsNullOrWhiteSpace(hint)) @@ -263,8 +266,8 @@ public class AccountService( } await email.SendTemplatedEmailAsync( + account.Nick, contact.Content, - localizer["EmailVerificationTitle"], localizer["VerificationEmail"], new VerificationEmailModel { diff --git a/DysonNetwork.Sphere/Auth/AuthController.cs b/DysonNetwork.Sphere/Auth/AuthController.cs index 1027240..0f1702e 100644 --- a/DysonNetwork.Sphere/Auth/AuthController.cs +++ b/DysonNetwork.Sphere/Auth/AuthController.cs @@ -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,