public ActionResult <MilvanethProtocol> AccountRecoveryEmail(MilvanethProtocol data) { if (!(data?.Data is RecoveryEmail email) || !email.Check()) { return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.DATA_INVALID_INPUT, ReportTime = _time.SafeNow, } }); } try { var user = _context.AccountData.Include(x => x.PrivilegeLevelNavigation).SingleOrDefault(x => x.AccountName == email.Username); if (user == null) { return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.DATA_NO_SUCH_USER, ReportTime = _time.SafeNow, } }); } _auth.EnsureAccount(user, new PrivilegeConfig { AccountOperation = true }, GlobalOperation.ACCOUNT_RECOVERY_EMAIL, 0, "Recovery account via account/recovery with email", _accessor.GetIp()); if (user.Email == null) { return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.DATA_NO_EMAIL_RECORDED, ReportTime = _time.SafeNow, } }); } var record = _context.EmailVerifyCode.OrderByDescending(x => x.SendTime).FirstOrDefault(); if (string.IsNullOrEmpty(email.Code)) { if (record != null && record.SendTime.AddSeconds(GlobalConfig.ACCOUNT_VERIFY_CODE_COOLDOWN) > _time.UtcNow) { return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.RATE_LIMIT, ReportTime = _time.SafeNow, } }); } if (user.Email.Equals(email.Email, StringComparison.InvariantCultureIgnoreCase)) { string code; if (record != null && record.ValidTo > _time.UtcNow.AddSeconds(3 * GlobalConfig.ACCOUNT_VERIFY_CODE_COOLDOWN)) { code = record.Code; record.SendTime = _time.UtcNow; _context.EmailVerifyCode.Update(record); } else { using (var cryptoRng = new RNGCryptoServiceProvider()) { var seed = new byte[5]; cryptoRng.GetBytes(seed); code = Helper.ToCode(seed); } _context.EmailVerifyCode.Add(new EmailVerifyCode { AccountId = user.AccountId, Email = user.Email, FailedRetry = 0, ValidTo = _time.UtcNow.AddSeconds(GlobalConfig.ACCOUNT_VERIFY_CODE_LIFE_TIME), Code = code, SendTime = _time.UtcNow }); } _context.SaveChanges(); _mail.SendCode(user.Email, user.DisplayName, code); } else { var rand = new Random(); // prevent timing attack Thread.Sleep(1000 + rand.Next(3000)); } return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.OK_SUCCESS, ReportTime = _time.SafeNow, } }); } if (record == null || record.ValidTo < _time.UtcNow || record.FailedRetry >= 5) { return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.DATA_INVALID_INPUT, ReportTime = _time.SafeNow, } }); } if (record.Code != email.Code.ToUpperInvariant()) { record.FailedRetry += 1; _context.EmailVerifyCode.Update(record); _context.SaveChanges(); return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.DATA_INVALID_CAPTCHA, ReportTime = _time.SafeNow, } }); } var recovery = _api.Sign(_changeToken, 1, user, _time.UtcNow, _time.UtcNow.AddSeconds(GlobalConfig.TOKEN_ACCOUNT_LIFE_TIME)); return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.OK_SUCCESS, ReportTime = _time.SafeNow, AuthToken = recovery.Key, } }); } catch (Exception e) { Log.Error(e, "Error in ACCOUNT/RECOVERYMAIL"); return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.OP_INVALID, ReportTime = _time.SafeNow, } }); } }