from datetime import datetime, timedelta from time import sleep from flask_jwt_extended import create_access_token, create_refresh_token from .conftest import MOCK_USER_1, assert_200, assert_401, assert_404, assert_400, assert_message, assert_response_json_equal, assert_405, assert_in, assert_true, assert_false, \ assert_equal, assert_402, assert_not_in, assert_error_type, assert_406 from app.model import Roles, User from app_common.const import NOT_UNIQUE, BAD_USER_ROLES, MALFORMED_DATA, USER_NOT_ACTIVE_MESSAGE def test_login(client): url = '/api/auth/ajax/login' # we should get a 200 with valid user login data but no refresh token by default resp = client.post(url, data={'user_id': MOCK_USER_1.user_id, 'password': MOCK_USER_1.password}) assert_200(resp) assert_in('access_token', resp.json) assert_not_in('refresh_token', resp.json) # if we set remember to false we shouldn't get a refresh token resp = client.post(url, data={'user_id': MOCK_USER_1.user_id, 'password': MOCK_USER_1.password, 'remember': False}) assert_200(resp) assert_in('access_token', resp.json) assert_not_in('refresh_token', resp.json) # if we set remember to true, we should get a refresh token resp = client.post(url, data={'user_id': MOCK_USER_1.user_id, 'password': MOCK_USER_1.password, 'remember': True}) assert_200(resp) assert_in('access_token', resp.json) assert_in('refresh_token', resp.json) # if the user is already logged in, we should get an error headers = {'Auth-Token': f'Bearer {resp.json.get("access_token")}'} resp = client.post(url, data={'user_id': MOCK_USER_1.user_id, 'password': MOCK_USER_1.password}, headers=headers) assert_401(resp) # we should get a 401 and a message if the password is incorrect resp = client.post(url, data={'user_id': MOCK_USER_1.user_id, 'password': 'bad-password'}) assert_401(resp) assert_message(resp, 'Incorrect Password') # we should get a 404 if the user does not exist resp = client.post(url, data={'user_id': 'not-a-user', 'password': 'bad-password'}) assert_404(resp) # if malformed data is passed, we should get an error resp = client.post(url, data={'this': 'is', 'bad': 'data'}) assert_400(resp) assert_error_type(resp, MALFORMED_DATA) # trying to post with a non-active user should produce a 406 message and have the correct message user = User.objects(user_id=MOCK_USER_1.user_id).first() user.active = False user.save() assert_false(User.objects(user_id=MOCK_USER_1.user_id).first().active) # this should be the case for requests with headers, though that should be impossible without accidentally modifying the database resp = client.post(url, data={'user_id': MOCK_USER_1.user_id, 'password': MOCK_USER_1.password}, headers=headers) assert_406(resp) assert_message(resp, USER_NOT_ACTIVE_MESSAGE) # this is the test case that should actually happen resp = client.post(url, data={'user_id': MOCK_USER_1.user_id, 'password': MOCK_USER_1.password}) assert_406(resp) assert_message(resp, USER_NOT_ACTIVE_MESSAGE) def test_register(client, mocker): # we want to mock sending the confirmation email. mocker.patch('app.auth.email.send_email_to_user') url = '/api/auth/ajax/register' data = { 'user_id': 'foo', 'email': 'foo@bar.com', 'first_name': 'foo', 'last_name': 'bar', 'date_of_birth': datetime.utcnow().strftime('%b %d, %Y'), 'password': '123', 'phone_number': '222-333-4444', 'roles': Roles.APPLICANT.value } resp = client.post(url, data=data) assert_200(resp) # if the user is already logged in, we should get an error headers = {'Auth-Token': f'Bearer {create_access_token(MOCK_USER_1)}'} resp = client.post(url, data=data, headers=headers) assert_401(resp) # we should get an error trying to register the same user... resp = client.post(url, data=data) assert_400(resp) assert_response_json_equal(resp, {'fields': ['user_id', 'email'], 'error_type': NOT_UNIQUE}, exclude_paths=["root['message']"]) # we should only see the duplicate fields in the response data['user_id'] = 'bar' resp = client.post(url, data=data) assert_400(resp) assert_response_json_equal(resp, {'fields': ['email'], 'error_type': NOT_UNIQUE}, exclude_paths=["root['message']"]) # now we should only see the phone number as bad data['email'] = 'foo@baz.com' resp = client.post(url, data=data) assert_200(resp) # sending an invalid username should give an error data['user_id'] = 'Invalid user ID' data['email'] = 'new@unique.com' resp = client.post(url, data=data) assert_400(resp) assert_error_type(resp, MALFORMED_DATA) # sending a bad role should produce a 400 error data['user_id'] = 'valid_user_id' data['roles'] = 'bad,roles' resp = client.post(url, data=data) assert_400(resp) assert_error_type(resp, BAD_USER_ROLES) # if malformed data is passed, we should get an error data = { 'this': 'is', 'not': 'valid', 'data': 'to_pass' } resp = client.post(url, data=data) assert_400(resp) assert_error_type(resp, MALFORMED_DATA) def test_update_password(client, mocker): url = '/api/auth/ajax/update_password' access_token = create_access_token(MOCK_USER_1) headers = {'Auth-Token': f'Bearer {access_token}'} data = {'old_password': MOCK_USER_1.password, 'new_password': 'foo'} resp = client.post(url, data=data, headers=headers) assert_200(resp) # if the password is incorrect, we should get a 401 error data['old_password'] = 'wrong' resp = client.post(url, data=data, headers=headers) assert_401(resp) # if there is no access token given, we should get a 401 resp = client.post(url, data=data) assert_401(resp) # if the access token is revoked, we should get a 401 mocker.patch('app.auth._is_token_revoked', return_value=False) resp = client.post(url, data=data, headers=headers) assert_401(resp) def test_refresh_token(client): url = '/api/auth/ajax/refresh_access_token' refresh_token = create_refresh_token(MOCK_USER_1) headers = {'Auth-Token': f'Bearer {refresh_token}'} # if the refresh token is valid, we should get and a new access token that works resp = client.get(url, headers=headers) assert_200(resp) assert_in('access_token', resp.json) access_token = resp.json['access_token'] # the new access token should be valid resp = client.post('/api/auth/ajax/test_access_token', headers={'Auth-Token': f'Bearer {access_token}'}) assert_200(resp) def test_expired_access_token(client): url = '/api/auth/ajax/test_access_token' access_token = create_access_token(MOCK_USER_1, expires_delta=timedelta(seconds=1)) headers = {'Auth-Token': f'Bearer {access_token}'} resp = client.post(url, headers=headers) assert_200(resp) # sleep for 2 seconds to let the token expire sleep(2) resp = client.post(url, headers=headers) assert_405(resp) def test_logout(client): url = '/api/auth/ajax/logout' access_token = create_access_token(MOCK_USER_1) headers = {'Auth-Token': f'Bearer {access_token}'} # If we hit the logout endpoint, we should get a 200 resp = client.post(url, headers=headers) assert_200(resp) # the access token should now be blacklisted and we should get a 401 error resp = client.post(url, headers=headers) assert_401(resp) # if no authentication header is given, we should get a 401 resp = client.post(url) assert_401(resp) def test_password_reset(client, mocker): url = '/api/auth/ajax/request_password_reset' mock_send_email = mocker.patch('app.auth.api.send_password_reset_email') def get_reset_url(): # the call count for sending the password reset email should go up by 1 previous_mocked_count = mock_send_email.call_count response = client.post(url, data={'user_id': MOCK_USER_1.user_id}) assert_200(response) assert mock_send_email.call_count == previous_mocked_count + 1 token = mock_send_email.call_args.args[1] return f'/api/auth/ajax/reset_password?token={token}' # sending a user ID should cause a call to the mocked function reset_url = get_reset_url() resp = client.post(reset_url, data={'new_password': 'foobar'}) assert_200(resp) # make sure the new user password is what is expected user = User.objects(user_id=MOCK_USER_1.user_id).first() assert user.check_password('foobar') # Trying to reuse the token should fail resp = client.post(reset_url, data={'new_password': 'foobar1'}) assert_401(resp) # if we sleep for over 1 second (the test config has a 1 second timeout) the we should get a 401 for a fresh token reset_url = get_reset_url() sleep(2) resp = client.post(reset_url, data={'new_password': 'foobar1'}) assert_401(resp) # if the password is being set to the same, we should get a 400 reset_url = get_reset_url() resp = client.post(reset_url, data={'new_password': 'foobar'}) assert_400(resp) assert_in('password_reset_token', resp.json) assert_error_type(resp, MALFORMED_DATA) # using the new password reset token should work reset_url = f'/api/auth/ajax/reset_password?token={resp.json["password_reset_token"]}' resp = client.post(reset_url, data={'new_password': 'foobar1'}) assert_200(resp) def test_activate(client): base_url = 'api/auth/ajax/activate' user = User.objects(user_id=MOCK_USER_1.user_id).first() user.active = False user.save() # make sure the user isn't active assert_false(user.active) token = user.get_activation_token(secret_key=client.application.config['SECRET_KEY']) url = f'{base_url}?token={token}' resp = client.post(url) assert_200(resp) # the user should now be active user = User.objects(user_id=MOCK_USER_1.user_id).first() assert_true(user.active) # if the user is already active, we should get a 401 error resp = client.post(url) assert_402(resp) user.active = False user.save() # if the token expires, we should get a 401 error token = user.get_activation_token(expire_secs=1, secret_key=client.application.config['SECRET_KEY']) sleep(2) url = f'{base_url}?token={token}' resp = client.post(url) assert_401(resp) # the user should not have been activated user = User.objects(user_id=MOCK_USER_1.user_id).first() assert_false(user.active) def test_send_confirm_email(client, mocker): mock_send_email = mocker.patch('app.auth.api.send_activate_account_email') prev_call_count = mock_send_email.call_count url = '/api/auth/ajax/send_confirm_email' user = User.objects(user_id=MOCK_USER_1.user_id).first() user.active = False user.save() assert_false(user.active) resp = client.post(url, data={'email': MOCK_USER_1.email}) assert_200(resp) assert_equal(mock_send_email.call_count, prev_call_count + 1) prev_call_count = mock_send_email.call_count # the token we got should allow us to activate the correct user account token = mock_send_email.call_args.args[1] user = user.from_activation_token(token, secret_key=client.application.config['SECRET_KEY']) assert_equal(user.user_id, MOCK_USER_1.user_id) # if no user can be found we should get a 404 and no email should be sent resp = client.post(url, data={'email': 'bad@email.com'}) assert_404(resp) assert_equal(prev_call_count, mock_send_email.call_count) def test_send_forgot_user_id_email(client, mocker): mock_send_email = mocker.patch('app.auth.api.send_forgot_user_id_email') prev_call_count = mock_send_email.call_count url = '/api/auth/ajax/forgot_user_id' user = User.objects(user_id=MOCK_USER_1.user_id).first() data = {'email': user.email} resp = client.post(url, data=data) assert_200(resp) # an email should have been sent assert_equal(mock_send_email.call_count, prev_call_count + 1) prev_call_count = mock_send_email.call_count # the email should have been sent to the correct user's email address user = mock_send_email.call_args.args[0] assert_equal(user.email, MOCK_USER_1.email) # providing a bad user email should result in a 404 data['email'] = 'DNE@fake.com' resp = client.post(url, data=data) assert_404(resp) assert_equal(mock_send_email.call_count, prev_call_count) # providing a non-email address value should provide a 400 error data['email'] = 'not an email address' resp = client.post(url, data=data) assert_400(resp)