Beispiel #1
0
    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)
                }));
            }
        }