Beispiel #1
0
        public async Task RecoverUserGivenMalformedTokenReturnsFalse()
        {
            // Arrange
            var builder = new DbContextOptionsBuilder <CoffeeCardContext>()
                          .UseInMemoryDatabase(nameof(RecoverUserGivenMalformedTokenReturnsFalse));

            var databaseSettings = new DatabaseSettings
            {
                SchemaName = "test"
            };
            var environmentSettings = new EnvironmentSettings()
            {
                EnvironmentType = EnvironmentType.Test
            };
            var loginLimiterSettings = new LoginLimiterSettings()
            {
                IsEnabled = true, MaximumLoginAttemptsWithinTimeOut = 5, TimeOutPeriodInMinutes = 5
            };

            var  expectedResult = false;
            bool result;

            // Act
            await using (var context = new CoffeeCardContext(builder.Options, databaseSettings, environmentSettings))
            {
                var accountService = new AccountService(context, environmentSettings, new Mock <ITokenService>().Object,
                                                        new Mock <IEmailService>().Object, new Mock <IHashService>().Object,
                                                        new Mock <IHttpContextAccessor>().Object, new Mock <ILoginLimiter>().Object, loginLimiterSettings);
                result = await accountService.RecoverUserAsync("bogus", "3433");
            }

            // Assert
            Assert.Equal(expectedResult, result);
        }
Beispiel #2
0
        public void LoginAllowsLoginsAfterTimeout()
        {
            // Arrange
            var loginLimiterSettings = new LoginLimiterSettings()
            {
                IsEnabled = true, MaximumLoginAttemptsWithinTimeOut = 5, TimeOutPeriodInMinutes = 1
            };
            var user = new User
            {
                Id       = 1, Name = "test", Email = "*****@*****.**", Programme = new Programme(),
                Password = "******", IsVerified = true
            };

            var loginLimiter = new LoginLimiter(loginLimiterSettings);

            const bool lockedOutExpected         = false;
            const bool loginAllowedAgainExpected = true;

            // Act
            //Triggers the lockout by attempting login 5 times in a row
            loginLimiter.LoginAllowed(user);
            loginLimiter.LoginAllowed(user);
            loginLimiter.LoginAllowed(user);
            loginLimiter.LoginAllowed(user);
            loginLimiter.LoginAllowed(user);

            var lockedOutActual = loginLimiter.LoginAllowed(user); //Checks that you are actually locked after the initial attempts

            Thread.Sleep(61000);
            var loginAllowedAgainActual = loginLimiter.LoginAllowed(user); //Checks that you are allowed to login again after the timeout period

            Assert.Equal(lockedOutExpected, lockedOutActual);
            Assert.Equal(loginAllowedAgainExpected, loginAllowedAgainActual);
        }
Beispiel #3
0
        public async Task RecoverUserGivenValidTokenReturnsTrue()
        {
            // Arrange
            var builder = new DbContextOptionsBuilder <CoffeeCardContext>()
                          .UseInMemoryDatabase(nameof(RecoverUserGivenValidTokenReturnsTrue));

            var databaseSettings = new DatabaseSettings
            {
                SchemaName = "test"
            };
            var environmentSettings = new EnvironmentSettings()
            {
                EnvironmentType = EnvironmentType.Test
            };
            var loginLimiterSettings = new LoginLimiterSettings()
            {
                IsEnabled = true, MaximumLoginAttemptsWithinTimeOut = 5, TimeOutPeriodInMinutes = 5
            };

            var  expectedResult = true;
            bool result;

            var claim  = new Claim(ClaimTypes.Email, "*****@*****.**");
            var claims = new List <Claim> {
                claim
            };
            var validToken = new JwtSecurityToken("analog", "all", claims);

            var tokenService = new Mock <ITokenService>();

            tokenService.Setup(t => t.ReadToken("valid")).Returns(validToken);
            tokenService.Setup(t => t.ValidateToken("valid")).ReturnsAsync(true);

            // Act
            await using (var context = new CoffeeCardContext(builder.Options, databaseSettings, environmentSettings))
            {
                var token      = new Token("valid");
                var userTokens = new List <Token> {
                    token
                };

                var user = new User {
                    Tokens = userTokens, Email = "*****@*****.**", Programme = new Programme()
                };
                context.Add(user);
                context.SaveChanges();

                var accountService = new AccountService(context, environmentSettings, tokenService.Object,
                                                        new Mock <IEmailService>().Object, new Mock <IHashService>().Object,
                                                        new Mock <IHttpContextAccessor>().Object, new Mock <ILoginLimiter>().Object, loginLimiterSettings);

                result = await accountService.RecoverUserAsync("valid", "3433");
            }

            // Assert
            Assert.Equal(expectedResult, result);
        }
Beispiel #4
0
        public async Task LoginThrowsExceptionWhenLimitIsReached()
        {
            // Arrange
            var builder = new DbContextOptionsBuilder <CoffeeCardContext>()
                          .UseInMemoryDatabase(nameof(LoginThrowsExceptionWhenLimitIsReached));

            var databaseSettings = new DatabaseSettings()
            {
                SchemaName = "test"
            };
            var environmentSettings = new EnvironmentSettings()
            {
                DeploymentUrl = "test", EnvironmentType = EnvironmentType.Test, MinAppVersion = "2.1.0"
            };
            var loginLimiterSettings = new LoginLimiterSettings()
            {
                IsEnabled = true, MaximumLoginAttemptsWithinTimeOut = 1, TimeOutPeriodInMinutes = 1
            };

            var userTokens = new List <Token>();
            var user       = new User
            {
                Id       = 1, Name = "test", Tokens = userTokens, Email = "*****@*****.**", Programme = new Programme(),
                Password = "******", IsVerified = true
            };

            var wrongPass = "******";

            var httpContextAccessor = new Mock <IHttpContextAccessor>();

            httpContextAccessor.Setup(h => h.HttpContext).Returns(new DefaultHttpContext().HttpContext);

            // Act
            await using (var context = new CoffeeCardContext(builder.Options, databaseSettings, environmentSettings))
            {
                context.Add(user);
                context.SaveChanges();

                var accountService = new AccountService(context, environmentSettings, new Mock <ITokenService>().Object,
                                                        new Mock <IEmailService>().Object, new HashService(), httpContextAccessor.Object,
                                                        new LoginLimiter(loginLimiterSettings), loginLimiterSettings);

                //Attempts to login with the wrong credentials
                Assert.Throws <ApiException>(() => accountService.Login(user.Email, wrongPass, "2.1.0"));

                //Attempts to login a sixth time with the same credentials and captures the exception
                var tooManyLoginsException =
                    (ApiException)Record.Exception(() => accountService.Login(user.Email, wrongPass, "2.1.0"));
                // Assert
                var expectedException =
                    new ApiException(
                        $"Amount of failed login attempts exceeds the allowed amount, please wait for {loginLimiterSettings.TimeOutPeriodInMinutes} minutes before trying again",
                        429);
                Assert.Equal(tooManyLoginsException.StatusCode, expectedException.StatusCode);
                Assert.Equal(tooManyLoginsException.Message, expectedException.Message);
            }
        }
Beispiel #5
0
 public AccountService(CoffeeCardContext context, EnvironmentSettings environmentSettings, ITokenService tokenService,
                       IEmailService emailService, IHashService hashService, IHttpContextAccessor httpContextAccessor, ILoginLimiter loginLimiter, LoginLimiterSettings loginLimiterSettings)
 {
     _context              = context;
     _environmentSettings  = environmentSettings;
     _tokenService         = tokenService;
     _emailService         = emailService;
     _hashService          = hashService;
     _httpContextAccessor  = httpContextAccessor;
     _loginLimiter         = loginLimiter;
     _loginLimiterSettings = loginLimiterSettings;
 }
Beispiel #6
0
        public async Task LoginLimiterNotCalledWhenDisabled()
        {
            // Arrange
            var builder = new DbContextOptionsBuilder <CoffeeCardContext>()
                          .UseInMemoryDatabase(nameof(LoginLimiterNotCalledWhenDisabled));

            var databaseSettings = new DatabaseSettings()
            {
                SchemaName = "test"
            };
            var environmentSettings = new EnvironmentSettings()
            {
                DeploymentUrl = "test", EnvironmentType = EnvironmentType.Test, MinAppVersion = "2.1.0"
            };
            var loginLimiterSettings = new LoginLimiterSettings()
            {
                IsEnabled = false, MaximumLoginAttemptsWithinTimeOut = 5, TimeOutPeriodInMinutes = 5
            };

            var userTokens = new List <Token>();
            var user       = new User
            {
                Id       = 1, Name = "test", Tokens = userTokens, Email = "*****@*****.**", Programme = new Programme(),
                Password = "******", IsVerified = true
            };

            var wrongPass = "******";

            var httpContextAccessor = new Mock <IHttpContextAccessor>();

            httpContextAccessor.Setup(h => h.HttpContext).Returns(new DefaultHttpContext().HttpContext);

            var loginLimiter = new Mock <ILoginLimiter>();

            loginLimiter.Setup(l => l.LoginAllowed(user)).Returns(true);

            // Act
            await using (var context = new CoffeeCardContext(builder.Options, databaseSettings, environmentSettings))
            {
                context.Add(user);
                context.SaveChanges();

                var accountService = new AccountService(context, environmentSettings, new Mock <ITokenService>().Object,
                                                        new Mock <IEmailService>().Object, new HashService(), httpContextAccessor.Object,
                                                        loginLimiter.Object, loginLimiterSettings);

                //Attempts to login
                Assert.Throws <ApiException>(() => accountService.Login(user.Email, wrongPass, "2.1.0"));

                // Assert
                loginLimiter.Verify(l => l.LoginAllowed(user), Times.Never);
            }
        }
Beispiel #7
0
        public async Task LoginGivenValidCredentialsReturnsToken()
        {
            // Arrange
            var builder = new DbContextOptionsBuilder <CoffeeCardContext>()
                          .UseInMemoryDatabase(nameof(LoginGivenValidCredentialsReturnsToken));

            var databaseSettings = new DatabaseSettings()
            {
                SchemaName = "test"
            };
            var environmentSettings = new EnvironmentSettings()
            {
                DeploymentUrl = "test", EnvironmentType = EnvironmentType.Test, MinAppVersion = "2.1.0"
            };
            var loginLimiterSettings = new LoginLimiterSettings()
            {
                IsEnabled = true, MaximumLoginAttemptsWithinTimeOut = 5, TimeOutPeriodInMinutes = 5
            };

            var userTokens = new List <Token>();
            var user       = new User
            {
                Id       = 1, Name = "test", Tokens = userTokens, Email = "*****@*****.**", Programme = new Programme(),
                Password = "******", IsVerified = true
            };

            var hasher = new Mock <IHashService>();

            hasher.Setup(h => h.Hash(user.Password)).Returns(user.Password);

            var    expectedToken = "valid";
            string actualToken;

            var tokenService = new Mock <ITokenService>();

            tokenService.Setup(t => t.GenerateToken(It.IsAny <IEnumerable <Claim> >())).Returns(expectedToken);

            // Act
            await using (var context = new CoffeeCardContext(builder.Options, databaseSettings, environmentSettings))
            {
                context.Add(user);
                context.SaveChanges();

                var accountService = new AccountService(context, environmentSettings, tokenService.Object,
                                                        new Mock <IEmailService>().Object, hasher.Object,
                                                        new Mock <IHttpContextAccessor>().Object, new LoginLimiter(loginLimiterSettings), loginLimiterSettings);

                actualToken = accountService.Login(user.Email, user.Password, "2.1.0");
            }

            // Assert
            Assert.Equal(expectedToken, actualToken);
        }
Beispiel #8
0
        public async Task LoginSucceedsIfEmailIsVerified()
        {
            // Arrange
            var builder = new DbContextOptionsBuilder <CoffeeCardContext>()
                          .UseInMemoryDatabase(nameof(LoginSucceedsIfEmailIsVerified));

            var databaseSettings = new DatabaseSettings()
            {
                SchemaName = "test"
            };
            var environmentSettings = new EnvironmentSettings()
            {
                DeploymentUrl = "test", EnvironmentType = EnvironmentType.Test, MinAppVersion = "2.1.0"
            };
            var loginLimiterSettings = new LoginLimiterSettings()
            {
                IsEnabled = true, MaximumLoginAttemptsWithinTimeOut = 1, TimeOutPeriodInMinutes = 1
            };

            var userTokens = new List <Token>();
            var somePass   = "******";
            var user       = new User
            {
                Id       = 1, Name = "test", Tokens = userTokens, Email = "*****@*****.**", Programme = new Programme(),
                Password = somePass, IsVerified = true
            };
            var httpContextAccessor = new Mock <IHttpContextAccessor>();

            httpContextAccessor.Setup(h => h.HttpContext).Returns(new DefaultHttpContext().HttpContext);

            var hashService = new Mock <IHashService>();

            hashService.Setup(m => m.Hash(It.IsAny <string>())).Returns(somePass);

            await using var context = new CoffeeCardContext(builder.Options, databaseSettings, environmentSettings);
            context.Add(user);
            await context.SaveChangesAsync();

            // Act
            var accountService = new AccountService(context, environmentSettings, new Mock <ITokenService>().Object,
                                                    new Mock <IEmailService>().Object, hashService.Object, httpContextAccessor.Object,
                                                    new LoginLimiter(loginLimiterSettings), loginLimiterSettings);

            // Login
            var result = accountService.Login(user.Email, somePass, "2.1.0");

            // Assert we did not fail in the above call. This test does not test the result
            Assert.Null(result);
        }
Beispiel #9
0
        public async Task LoginFailsIfEmailIsNotVerified()
        {
            // Arrange
            var builder = new DbContextOptionsBuilder <CoffeeCardContext>()
                          .UseInMemoryDatabase(nameof(LoginFailsIfEmailIsNotVerified));

            var databaseSettings = new DatabaseSettings()
            {
                SchemaName = "test"
            };
            var environmentSettings = new EnvironmentSettings()
            {
                DeploymentUrl = "test", EnvironmentType = EnvironmentType.Test, MinAppVersion = "2.1.0"
            };
            var loginLimiterSettings = new LoginLimiterSettings()
            {
                IsEnabled = true, MaximumLoginAttemptsWithinTimeOut = 1, TimeOutPeriodInMinutes = 1
            };

            var userTokens = new List <Token>();
            var user       = new User
            {
                Id       = 1, Name = "test", Tokens = userTokens, Email = "*****@*****.**", Programme = new Programme(),
                Password = "******", IsVerified = false
            };
            var somePass = "******";

            var httpContextAccessor = new Mock <IHttpContextAccessor>();

            httpContextAccessor.Setup(h => h.HttpContext).Returns(new DefaultHttpContext().HttpContext);

            await using var context = new CoffeeCardContext(builder.Options, databaseSettings, environmentSettings);
            context.Add(user);
            await context.SaveChangesAsync();

            // Act
            var accountService = new AccountService(context, environmentSettings, new Mock <ITokenService>().Object,
                                                    new Mock <IEmailService>().Object, new HashService(), httpContextAccessor.Object,
                                                    new LoginLimiter(loginLimiterSettings), loginLimiterSettings);

            // Login
            var exception = (ApiException)Record.Exception(() => accountService.Login(user.Email, somePass, "2.1.0"));

            // Assert
            var expectedException = new ApiException("E-mail has not been verified", 403);

            Assert.Equal(exception.StatusCode, expectedException.StatusCode);
            Assert.Equal(exception.Message, expectedException.Message);
        }
Beispiel #10
0
        public async Task LoginWithUnknownUserThrowsApiException()
        {
            // Arrange
            var builder = new DbContextOptionsBuilder <CoffeeCardContext>()
                          .UseInMemoryDatabase(nameof(LoginWithUnknownUserThrowsApiException));

            var databaseSettings = new DatabaseSettings()
            {
                SchemaName = "test"
            };
            var environmentSettings = new EnvironmentSettings()
            {
                DeploymentUrl = "test", EnvironmentType = EnvironmentType.Test, MinAppVersion = "2.1.0"
            };
            var loginLimiterSettings = new LoginLimiterSettings()
            {
                IsEnabled = true, MaximumLoginAttemptsWithinTimeOut = 1, TimeOutPeriodInMinutes = 1
            };


            var httpContextAccessor = new Mock <IHttpContextAccessor>();

            httpContextAccessor.Setup(h => h.HttpContext).Returns(new DefaultHttpContext().HttpContext);

            await using var context = new CoffeeCardContext(builder.Options, databaseSettings, environmentSettings);
            await context.SaveChangesAsync();

            // Act
            var accountService = new AccountService(context, environmentSettings, new Mock <ITokenService>().Object,
                                                    new Mock <IEmailService>().Object, new Mock <IHashService>().Object, httpContextAccessor.Object,
                                                    new LoginLimiter(loginLimiterSettings), loginLimiterSettings);

            // Login
            var exception = (ApiException)Record.Exception(() => accountService.Login("unknown email", "somePass", "2.1.0"));

            // Assert
            var expectedException = new ApiException("The username or password does not match", 401);

            Assert.Equal(exception.StatusCode, expectedException.StatusCode);
            Assert.Equal(exception.Message, expectedException.Message);
        }
Beispiel #11
0
        public void LoginLockoutsTwice()
        {
            // Arrange
            var loginLimiterSettings = new LoginLimiterSettings()
            {
                IsEnabled = true, MaximumLoginAttemptsWithinTimeOut = 5, TimeOutPeriodInMinutes = 1
            };
            var user = new User
            {
                Id       = 1, Name = "test", Email = "*****@*****.**", Programme = new Programme(),
                Password = "******", IsVerified = true
            };

            var loginLimiter = new LoginLimiter(loginLimiterSettings);

            // Act
            //Triggers the lockout by attempting login 5 times in a row
            loginLimiter.LoginAllowed(user);
            loginLimiter.LoginAllowed(user);
            loginLimiter.LoginAllowed(user);
            loginLimiter.LoginAllowed(user);
            loginLimiter.LoginAllowed(user);
            var actualFirstLockoutResult = loginLimiter.LoginAllowed(user); //Checks that you are actually locked after the first series of attempts

            Thread.Sleep(60001);                                            //Passes the lockout time

            //Triggers the lockout again by another 5 attempted logins in a row
            loginLimiter.LoginAllowed(user);
            loginLimiter.LoginAllowed(user);
            loginLimiter.LoginAllowed(user);
            loginLimiter.LoginAllowed(user);
            loginLimiter.LoginAllowed(user);

            var actualLogoutResult = loginLimiter.LoginAllowed(user); //Checks that you are actually locked after the second series of attempts

            Assert.False(actualFirstLockoutResult);
            Assert.False(actualLogoutResult);
        }
Beispiel #12
0
        public async Task RecoverUserGivenValidTokenUpdatesPasswordAndResetsUsersTokens()
        {
            // Arrange
            var builder = new DbContextOptionsBuilder <CoffeeCardContext>()
                          .UseInMemoryDatabase(nameof(RecoverUserGivenValidTokenUpdatesPasswordAndResetsUsersTokens));

            var databaseSettings = new DatabaseSettings
            {
                SchemaName = "test"
            };
            var environmentSettings = new EnvironmentSettings()
            {
                EnvironmentType = EnvironmentType.Test
            };
            var loginLimiterSettings = new LoginLimiterSettings()
            {
                IsEnabled = true, MaximumLoginAttemptsWithinTimeOut = 5, TimeOutPeriodInMinutes = 5
            };

            var claim  = new Claim(ClaimTypes.Email, "*****@*****.**");
            var claims = new List <Claim> {
                claim
            };
            var validToken = new JwtSecurityToken("analog", "all", claims);

            var tokenService = new Mock <ITokenService>();

            tokenService.Setup(t => t.ReadToken("valid")).Returns(validToken);
            tokenService.Setup(t => t.ValidateToken("valid")).ReturnsAsync(true);

            var    userPass = "******";
            string newUserPass;
            ICollection <Token> newUserTokens;

            // Act
            await using (var context = new CoffeeCardContext(builder.Options, databaseSettings, environmentSettings))
            {
                var token      = new Token("valid");
                var userTokens = new List <Token> {
                    token
                };

                var user = new User
                {
                    Tokens = userTokens, Email = "*****@*****.**", Programme = new Programme(), Password = userPass
                };
                context.Add(user);
                context.SaveChanges();

                var accountService = new AccountService(context, environmentSettings, tokenService.Object,
                                                        new Mock <IEmailService>().Object, new Mock <IHashService>().Object,
                                                        new Mock <IHttpContextAccessor>().Object, new Mock <ILoginLimiter>().Object, loginLimiterSettings);

                await accountService.RecoverUserAsync("valid", "3433");

                var updatedUser = context.Users.FirstOrDefault(u => u.Email == user.Email);
                newUserPass   = updatedUser.Password;
                newUserTokens = updatedUser.Tokens;
            }

            var expectedTokenCount = 0;

            // Assert
            Assert.NotEqual(newUserPass, userPass);
            Assert.Equal(newUserTokens.Count, expectedTokenCount);
        }