public async Task SaveStackUsagesAsync(bool sendNotifications = true, CancellationToken cancellationToken = default) { string occurrenceSetCacheKey = GetStackOccurrenceSetCacheKey(); var stackUsageSet = await _cache.GetListAsync <(string OrganizationId, string ProjectId, string StackId)>(occurrenceSetCacheKey).AnyContext(); if (!stackUsageSet.HasValue) { return; } foreach (var(organizationId, projectId, stackId) in stackUsageSet.Value) { if (cancellationToken.IsCancellationRequested) { break; } var removeFromSetTask = _cache.ListRemoveAsync(occurrenceSetCacheKey, (organizationId, projectId, stackId)); string countCacheKey = GetStackOccurrenceCountCacheKey(stackId); var countTask = _cache.GetAsync <long>(countCacheKey, 0); string minDateCacheKey = GetStackOccurrenceMinDateCacheKey(stackId); var minDateTask = _cache.GetUnixTimeMillisecondsAsync(minDateCacheKey, SystemClock.UtcNow); string maxDateCacheKey = GetStackOccurrenceMaxDateCacheKey(stackId); var maxDateTask = _cache.GetUnixTimeMillisecondsAsync(maxDateCacheKey, SystemClock.UtcNow); await Task.WhenAll( removeFromSetTask, countTask, minDateTask, maxDateTask ).AnyContext(); int occurrenceCount = (int)countTask.Result; if (occurrenceCount <= 0) { await _cache.RemoveAllAsync(new[] { minDateCacheKey, maxDateCacheKey }).AnyContext(); continue; } await Task.WhenAll( _cache.RemoveAllAsync(new[] { minDateCacheKey, maxDateCacheKey }), _cache.DecrementAsync(countCacheKey, occurrenceCount, _expireTimeout) ).AnyContext(); var occurrenceMinDate = minDateTask.Result; var occurrenceMaxDate = maxDateTask.Result; bool shouldRetry = false; try { if (!await _stackRepository.IncrementEventCounterAsync(organizationId, projectId, stackId, occurrenceMinDate, occurrenceMaxDate, occurrenceCount, sendNotifications).AnyContext()) { shouldRetry = true; await IncrementStackUsageAsync(organizationId, projectId, stackId, occurrenceMinDate, occurrenceMaxDate, occurrenceCount).AnyContext(); } else { _logger.LogTrace("Increment event count {OccurrenceCount} for organization:{OrganizationId} project:{ProjectId} stack:{StackId} with Min Date:{OccurrenceMinDate} Max Date:{OccurrenceMaxDate}", occurrenceCount, organizationId, projectId, stackId, occurrenceMinDate, occurrenceMaxDate); } } catch (Exception ex) { _logger.LogError(ex, "Error incrementing event count for organization: {OrganizationId} project:{ProjectId} stack:{StackId}", organizationId, projectId, stackId); if (!shouldRetry) { await IncrementStackUsageAsync(organizationId, projectId, stackId, occurrenceMinDate, occurrenceMaxDate, occurrenceCount).AnyContext(); } } } }
public async Task <IActionResult> LoginAsync([FromBody] LoginModel model) { string email = model?.Email?.Trim().ToLowerInvariant(); using (_logger.BeginScope(new ExceptionlessState().Tag("Login").Identity(email).SetHttpContext(HttpContext))) { if (String.IsNullOrEmpty(email)) { _logger.LogError("Login failed: Email Address is required."); return(BadRequest("Email Address is required.")); } if (String.IsNullOrWhiteSpace(model.Password)) { _logger.LogError("Login failed for {EmailAddress}: Password is required.", email); return(BadRequest("Password is required.")); } // Only allow 5 password attempts per 15 minute period. string userLoginAttemptsCacheKey = $"user:{email}:attempts"; long userLoginAttempts = await _cache.IncrementAsync(userLoginAttemptsCacheKey, 1, SystemClock.UtcNow.Ceiling(TimeSpan.FromMinutes(15))); // Only allow 15 login attempts per 15 minute period by a single ip. string ipLoginAttemptsCacheKey = $"ip:{Request.GetClientIpAddress()}:attempts"; long ipLoginAttempts = await _cache.IncrementAsync(ipLoginAttemptsCacheKey, 1, SystemClock.UtcNow.Ceiling(TimeSpan.FromMinutes(15))); if (userLoginAttempts > 5) { _logger.LogError("Login denied for {EmailAddress} for the {UserLoginAttempts} time.", email, userLoginAttempts); return(Unauthorized()); } if (ipLoginAttempts > 15) { _logger.LogError("Login denied for {EmailAddress} for the {IPLoginAttempts} time.", Request.GetClientIpAddress(), ipLoginAttempts); return(Unauthorized()); } User user; try { user = await _userRepository.GetByEmailAddressAsync(email); } catch (Exception ex) { _logger.LogCritical(ex, "Login failed for {EmailAddress}: {Message}", email, ex.Message); return(Unauthorized()); } if (user == null) { _logger.LogError("Login failed for {EmailAddress}: User not found.", email); return(Unauthorized()); } if (!user.IsActive) { _logger.LogError("Login failed for {EmailAddress}: The user is inactive.", user.EmailAddress); return(Unauthorized()); } if (!Settings.Current.EnableActiveDirectoryAuth) { if (String.IsNullOrEmpty(user.Salt)) { _logger.LogError("Login failed for {EmailAddress}: The user has no salt defined.", user.EmailAddress); return(Unauthorized()); } if (!user.IsCorrectPassword(model.Password)) { _logger.LogError("Login failed for {EmailAddress}: Invalid Password.", user.EmailAddress); return(Unauthorized()); } if (!PasswordMeetsRequirements(model.Password)) { _logger.LogError("Login denied for {EmailAddress} for invalid password.", email); return(StatusCode(423, "Password requirements have changed. Password needs to be reset to meet the new requirements.")); } } else { if (!IsValidActiveDirectoryLogin(email, model.Password)) { _logger.LogError("Domain login failed for {EmailAddress}: Invalid Password or Account.", user.EmailAddress); return(Unauthorized()); } } if (!String.IsNullOrEmpty(model.InviteToken)) { await AddInvitedUserToOrganizationAsync(model.InviteToken, user); } await _cache.RemoveAsync(userLoginAttemptsCacheKey); await _cache.DecrementAsync(ipLoginAttemptsCacheKey, 1, SystemClock.UtcNow.Ceiling(TimeSpan.FromMinutes(15))); _logger.LogInformation("{EmailAddress} logged in.", user.EmailAddress); return(Ok(new TokenResult { Token = await GetOrCreateAccessTokenAsync(user) })); } }