def test_login_token_expired(dummy_request, dummy_user): email_address = dummy_user.email_address _, user_id = save(dummy_user) token = '123456' token_hash, token_salt = hash_plaintext(token) recovery_token = RecoveryToken( for_user_id=user_id, token_hash=token_hash, token_salt=token_salt, expires_on=datetime.datetime.utcnow() - datetime.timedelta(hours=1) ) _, recovery_token_id = save(recovery_token) data = { 'email_address': email_address, 'token': token } auth_manager = AuthWithRecoveryTokenManager(dummy_request) with pytest.raises(HTTPBadRequest) as bad_request: auth_manager.login(data) unaltered_recovery_token = get_recovery_token_by_id(recovery_token_id) assert not unaltered_recovery_token.used assert bad_request.value.json == { 'message': { 'token': [auth_manager.invalid_credentials_error] } }
def test_login_token_incorrect(dummy_request, dummy_user): email_address = dummy_user.email_address _, user_id = save(dummy_user) token_hash, token_salt = hash_plaintext('123456') recovery_token = RecoveryToken( for_user_id=user_id, token_hash=token_hash, token_salt=token_salt ) _, recovery_token_id = save(recovery_token) data = { 'email_address': email_address, 'token': 'wrong1' } auth_manager = AuthWithRecoveryTokenManager(dummy_request) with pytest.raises(HTTPBadRequest) as bad_request: auth_manager.login(data) unaltered_recovery_token = get_recovery_token_by_id(recovery_token_id) assert not unaltered_recovery_token.used assert bad_request.value.json == { 'message': { 'token': [auth_manager.invalid_credentials_error] } }
def test_locked_after_too_many_attempts(dummy_request): data = {'email_address': '{{cookiecutter.test_email_address}}', 'token': 'wrong1'} auth_manager = AuthWithRecoveryTokenManager(dummy_request) for _ in range(0, 3): try: auth_manager.login(data) except HTTPBadRequest: pass with pytest.raises(HTTPBadRequest) as bad_request: auth_manager.login(data) assert "This account is locked" in bad_request.value.json.get('message')
def test_login_user_not_found(dummy_request): data = { 'email_address': '{{cookiecutter.alternative_test_email_address}}', 'token': '123456' } auth_manager = AuthWithRecoveryTokenManager(dummy_request) with pytest.raises(HTTPBadRequest) as bad_request: auth_manager.login(data) assert bad_request.value.json == { 'message': { 'token': [auth_manager.invalid_credentials_error] } }
def test_logout_success(dummy_request, dummy_user): user, user_id = save(dummy_user) token = '123456' token_hash, token_salt = hash_plaintext(token) recovery_token = RecoveryToken( for_user_id=user_id, token_hash=token_hash, token_salt=token_salt ) save(recovery_token) auth_manager = AuthWithRecoveryTokenManager(dummy_request) auth_manager.login({ 'email_address': user.email_address, 'token': token }) auth_manager.logout() assert 'auth_tkt=;' in dummy_request.response.headers['Set-Cookie']
class AccountRecoveryHandler(LoginHandler): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.auth_manager = AuthWithRecoveryTokenManager(self.request) @validate(AccountRecoverySchema()) @view_config( path_hints=['/auth/recover-account'], request_schema_class=AccountRecoverySchema, permission='recovery.request_token', tags=['authentication', 'account recovery'], request_method='POST', public_hint=True ) def request_account_recovery_token(self, request_data): response = HTTPCreated() token = token_hex(NUMBER_OF_TOKEN_BYTES) email_address = request_data['email_address'] self._prevent_user_enumeration() try: recipient = get_one_user_by_email_address(email_address) self._invalidate_any_current_recovery_token(recipient) self._save_recovery_token(recipient, token) SendGridClient().send_account_recovery_email(email_address, token) except NoResultFound: # To avoid user enumeration we don't indicate failure. pass raise response @staticmethod def _prevent_user_enumeration(): time.sleep(random.randint( MIN_TIME_PADDING_IN_DECISECONDS, MAX_TIME_PADDING_IN_DECISECONDS ) / 10) @staticmethod def _invalidate_any_current_recovery_token(user): try: user.active_recovery_token.invalidate() except AttributeError: pass @staticmethod def _save_recovery_token(for_user: User, token: str): token_hash, token_salt = hash_plaintext(token) recovery_token = RecoveryToken( token_hash=token_hash, token_salt=token_salt, for_user=for_user ) save(recovery_token) @validate(AccountRecoveryLoginSchema()) @view_config( path_hints=['/auth/recover-account/login'], request_schema_class=AccountRecoveryLoginSchema, permission='recovery.login', request_method='POST', successful_response_code=200, tags=['authentication', 'account recovery'], name='login', public_hint=True ) def login(self, login_data): self.auth_manager.login(login_data) raise self.request.response