Beispiel #1
0
        public void NewUserToken_InvalidUsername_ThrowsSystemException()
        {
            var sut = new GenerateJwtToken(_key, _getUserAuth.Object);

            var testUser = new User();

            Assert.That(() => sut.NewUserToken(testUser), Throws.Exception);
        }
Beispiel #2
0
        public void Authenticate_NonExistingUser_ThrowsNonExistingUserException()
        {
            var randomUsername = TestData.CreateRandomString();
            var randomPassword = TestData.CreateRandomString();

            _getUserAuth.Setup(x => x.Get(randomUsername)).Returns((User)null);

            var sut = new GenerateJwtToken(_key, _getUserAuth.Object);

            Assert.That(() => sut.Authenticate(randomUsername, randomPassword),
                        Throws.Exception.TypeOf <NonExistingUserException>());
        }
Beispiel #3
0
        public void NewUserToken_WhenCalled_ReturnsJwtToken()
        {
            var sut = new GenerateJwtToken(_key, _getUserAuth.Object);

            var testUser = new User
            {
                Username = TestData.CreateRandomString()
            };

            var jwtToken = sut.NewUserToken(testUser);

            Assert.That(jwtToken, Is.TypeOf <string>());
        }
Beispiel #4
0
        public void Authenticate_InvalidPassword_ThrowsUserValidationException()
        {
            var randomUsername = TestData.CreateRandomString();
            var randomPassword = TestData.CreateRandomString();
            var testUser       = new User
            {
                Password = BCrypt.Net.BCrypt.HashPassword(TestData.CreateRandomString()),
                Username = randomUsername
            };

            _getUserAuth.Setup(x => x.Get(randomUsername)).Returns(testUser);

            var sut = new GenerateJwtToken(_key, _getUserAuth.Object);

            Assert.That(() => sut.Authenticate(randomUsername, randomPassword),
                        Throws.Exception.TypeOf <UserValidationException>());
        }
Beispiel #5
0
        public void Authenticate_ValidInput_ReturnsJwtToken()
        {
            var randomUsername = TestData.CreateRandomString();
            var randomPassword = TestData.CreateRandomString();
            var testUser       = new User
            {
                Password = BCrypt.Net.BCrypt.HashPassword(randomPassword),
                Username = randomUsername
            };

            _getUserAuth.Setup(x => x.Get(randomUsername)).Returns(testUser);

            var sut = new GenerateJwtToken(_key, _getUserAuth.Object);

            var jwtToken = sut.Authenticate(randomUsername, randomPassword);

            Assert.That(jwtToken, Is.TypeOf <string>());
        }
        public async Task <AuthenticationResponse> Handle(AuthenticateUserCommand request, CancellationToken cancellationToken)
        {
            // Validate our input
            AuthenticateUserValidator validator        = new AuthenticateUserValidator();
            ValidationResult          validationResult = validator.Validate(request);

            if (!validationResult.IsValid)
            {
                return(new AuthenticationResponse(validationResult.Errors)
                {
                    Message = "Please include both a username/email and a password"
                });
            }


            #region Setup our caching client and key

            IDatabase cache         = _redisContext.ConnectionMultiplexer.GetDatabase();
            var       attemptsKey   = Common.Constants.CachingKeys.LoginAttempts(request.UserNameOrEmail);
            var       attemptsCount = 0;

            #endregion

            #region Check lockouts and login attempts (Redis Cache)

            // Check attempts count on this username/email
            var attemptsCacheValue = cache.StringGet(attemptsKey);

            if (attemptsCacheValue.HasValue)
            {
                attemptsCount = Convert.ToInt32(attemptsCacheValue);
                if (attemptsCount >= _coreConfiguration.Logins.MaxAttemptsBeforeLockout)
                {
                    return(new AuthenticationResponse {
                        Message = "Too many attempts!"
                    });
                }
            }

            #endregion


            //=========================================================================
            // DETERMINE AUTHENTICATION TYPE AND QUERY THE DOCUMENT STORE
            //=========================================================================

            #region get user from document store

            var authenticationType  = "NameKey";
            var authenticationQuery = Common.Transformations.NameKey.Transform(request.UserNameOrEmail);

            if (request.UserNameOrEmail.Contains("@") && request.UserNameOrEmail.Contains("."))
            {
                authenticationType  = "Email";
                authenticationQuery = request.UserNameOrEmail.ToLower().Trim();
            }

            // Create the query
            string sqlQuery = $"SELECT * FROM Documents d WHERE d.{ authenticationType } ='{ authenticationQuery }'";
            var    sqlSpec  = new SqlQuerySpec {
                QueryText = sqlQuery
            };

            // Generate collection uri
            Uri collectionUri = UriFactory.CreateDocumentCollectionUri(_documentContext.Settings.Database, _documentContext.Settings.Collection);

            // Generate FeedOptions/ParitionKey
            var feedOptions = new FeedOptions
            {
                PartitionKey = new PartitionKey(Common.Constants.DocumentType.User())
            };

            // Run query against the document store
            var getResult = _documentContext.Client.CreateDocumentQuery <UserDocumentModel>(
                collectionUri,
                sqlSpec,
                feedOptions
                );

            var userDocumentModel = getResult.AsEnumerable().FirstOrDefault();

            if (userDocumentModel == null)
            {
                #region Update login attempts (Redis Cache)

                cache.StringSet(attemptsKey, (attemptsCount + 1), TimeSpan.FromHours(_coreConfiguration.Logins.LockoutTimespanHours), When.Always, CommandFlags.FireAndForget);

                #endregion

                return(new AuthenticationResponse {
                    Message = "Incorrect credentials"
                });
            }

            #endregion

            //=========================================================================
            // DETERMINE IF THE PASSWORD IS CORRECT
            //=========================================================================
            var authenticationGranted = Common.Encryption.PasswordHashing.ValidatePassword(request.Password, userDocumentModel.PasswordHash, userDocumentModel.PasswordSalt);

            if (!authenticationGranted)
            {
                #region Update login attempts (Redis Cache)

                cache.StringSet(attemptsKey, (attemptsCount + 1), TimeSpan.FromHours(_coreConfiguration.Logins.LockoutTimespanHours), When.Always, CommandFlags.FireAndForget);

                #endregion

                return(new AuthenticationResponse {
                    Message = "Incorrect credentials"
                });
            }
            else
            {
                #region Clear login attempts (Redis Cache)

                if (attemptsCount > 0)
                {
                    cache.KeyDelete(attemptsKey, CommandFlags.FireAndForget);
                }

                #endregion

                //=========================================================================
                //
                //      BUILD THE JWT TOKEN, REFRESH TOKEN AND RETURN RESULTS
                //
                //=========================================================================


                var jwtTokenString = GenerateJwtToken.GenerateJwtTokenString(
                    _coreConfiguration,
                    userDocumentModel.Id,
                    userDocumentModel.UserName,
                    userDocumentModel.NameKey,
                    userDocumentModel.Email,
                    userDocumentModel.FirstName,
                    userDocumentModel.LastName,
                    userDocumentModel.Roles
                    );

                // Generate refresh token
                var refreshToken = string.Empty;
                try
                {
                    refreshToken = await _mediator.Send(new GenerateRefreshTokenCommand { UserId = userDocumentModel.Id });
                }
                catch (Exception ex)
                {
                    Log.Error("There was an error generating a refresh token for {loginString} during authentication {@ex}", request.UserNameOrEmail, ex);
                }



                //=========================================================================
                // UPDATE LAST LOGIN DATETIME ON USER
                //=========================================================================

                #region Update Last Login DateTime

                // Update field:
                userDocumentModel.LastLoginDate = DateTime.UtcNow;

                var documentUri = UriFactory.CreateDocumentUri(
                    _documentContext.Settings.Database,
                    _documentContext.Settings.Collection,
                    userDocumentModel.Id);

                ResourceResponse <Document> result;

                try
                {
                    // Save the document to document store using the IDocumentContext dependency
                    result = await _documentContext.Client.ReplaceDocumentAsync(
                        documentUri,
                        userDocumentModel,
                        new RequestOptions { PartitionKey = new PartitionKey(Common.Constants.DocumentType.User().ToString()) }
                        );
                }
                catch (Exception ex)
                {
                    // throw DocumentStoreException (if a custom exception type is desired)
                    // ... Will be caught, logged and handled by the ExceptionHandlerMiddleware

                    // Pass exception up the chain:
                    throw ex;

                    // ...Or pass along as inner exception:
                    //throw new Exception("An error occured trying to use the document store", ex);
                }
                finally
                {
                    // Close any open connections, etc...
                }

                #endregion

                //=========================================================================
                // LOG THE ACTIVITY
                //=========================================================================
                var user = AutoMapper.Mapper.Map <Core.Domain.Entities.User>(userDocumentModel);
                Log.Information("User authenticated {@user}", user);

                return(new AuthenticationResponse {
                    isSuccess = true, JwtToken = jwtTokenString, RefreshToken = refreshToken, User = user, Message = "Authentication succeeded"
                });
            }
        }
        public async Task <AuthenticationResponse> Handle(AuthenticateRefreshTokenCommand request, CancellationToken cancellationToken)
        {
            // Validate our input
            AuthenticateRefreshTokenValidator validator = new AuthenticateRefreshTokenValidator();
            ValidationResult validationResult           = validator.Validate(request);

            if (!validationResult.IsValid)
            {
                return(new AuthenticationResponse(validationResult.Errors)
                {
                    Message = "Validation issues"
                });
            }



            //======================================================
            // QUERY THE DOCUMENT STORE FOR BOTH THE REFRESH TOKEN
            //======================================================

            #region get request token from document store

            // Create the query
            string sqlQuery = $"SELECT * FROM Documents d WHERE d.id ='{ request.RefreshToken }'";
            var    sqlSpec  = new SqlQuerySpec {
                QueryText = sqlQuery
            };

            // Generate collection uri
            Uri collectionUri = UriFactory.CreateDocumentCollectionUri(_documentContext.Settings.Database, _documentContext.Settings.Collection);

            // Generate FeedOptions/ParitionKey
            var feedOptions = new FeedOptions
            {
                PartitionKey = new PartitionKey(Common.Constants.DocumentType.RefreshToken())
            };

            // Run query against the document store
            var getResult = _documentContext.Client.CreateDocumentQuery <RefreshTokenDocumentModel>(
                collectionUri,
                sqlSpec,
                feedOptions
                );

            var refreshDocumentModel = getResult.AsEnumerable().FirstOrDefault();

            #endregion

            //=========================================================================
            // DETERMINE IF THE TOKEN EXISTS AND IS NOT EXPIRED
            //=========================================================================

            if (refreshDocumentModel == null)
            {
                return(new AuthenticationResponse {
                    Message = "Invalid Token"
                });
            }
            else if (refreshDocumentModel.CreatedDate.AddHours(_coreConfiguration.JSONWebTokens.RefreshTokenExpirationHours) <= DateTime.UtcNow)
            {
                // Delete the expired refresh token and return as expired
                await _mediator.Send(new DeleteRefreshTokenCommand { Id = request.RefreshToken });

                return(new AuthenticationResponse {
                    Message = "Expired Token"
                });
            }
            else
            {
                //=========================================================================
                // TOKEN IS VALID, BUILD OUR AUTHENTICATION RESPONSE
                //=========================================================================

                // Get the user
                var user = await _mediator.Send(new GetUserByIdQuery { Id = refreshDocumentModel.UserId });

                //=========================================================================
                //
                //      BUILD THE JWT TOKEN, REFRESH TOKEN AND RETURN RESULTS
                //
                //=========================================================================

                if (user == null)
                {
                    return(new AuthenticationResponse {
                        Message = "Invalid Token"
                    });
                }


                var jwtTokenString = GenerateJwtToken.GenerateJwtTokenString(
                    _coreConfiguration,
                    user.Id.ToString(),
                    user.UserName,
                    user.NameKey,
                    user.Email,
                    user.FirstName,
                    user.LastName,
                    user.Roles
                    );


                var refreshToken = string.Empty;

                try
                {
                    // Generate new refresh token
                    refreshToken = await _mediator.Send(new GenerateRefreshTokenCommand { UserId = user.Id.ToString() });

                    // Delete this refresh token
                    await _mediator.Send(new DeleteRefreshTokenCommand { Id = request.RefreshToken });
                }
                catch (Exception ex)
                {
                    Log.Error("There was an error generating a followup refresh token for user:{userId} during authentication {@ex}", user.Id.ToString(), ex);
                }

                return(new AuthenticationResponse {
                    isSuccess = true, JwtToken = jwtTokenString, RefreshToken = refreshToken, User = user, Message = "Authentication succeeded"
                });
            }
        }