def test_ChangeEmail_sends_proper_emails_has_password(db, fast_passwords): password = random_hex() new_email = f"{random_hex()}@couchers.org.invalid" user, token = generate_user(hashed_password=hash_password(password)) with account_session(token) as account: account.ChangeEmail( account_pb2.ChangeEmailReq( password=wrappers_pb2.StringValue(value=password), new_email=new_email, )) with session_scope() as session: jobs = (session.execute( select(BackgroundJob).where( BackgroundJob.job_type == BackgroundJobType.send_email)).scalars().all()) assert len(jobs) == 2 payload_for_notification_email = jobs[0].payload payload_for_confirmation_email_new_address = jobs[1].payload unique_string_notification_email_as_bytes = b"You requested that your email on Couchers.org be changed to" unique_string_for_confirmation_email_new_email_address_as_bytes = ( b"You requested that your email be changed to this email address on Couchers.org" ) assert unique_string_notification_email_as_bytes in payload_for_notification_email assert (unique_string_for_confirmation_email_new_email_address_as_bytes in payload_for_confirmation_email_new_address)
def test_ChangeEmail_tokens_two_hour_window(db): def two_hours_one_minute_in_future(): return now() + timedelta(hours=2, minutes=1) def one_minute_ago(): return now() - timedelta(minutes=1) new_email = f"{random_hex()}@couchers.org.invalid" user, token = generate_user(hashed_password=None) with account_session(token) as account: account.ChangeEmail(account_pb2.ChangeEmailReq(new_email=new_email, )) with session_scope() as session: user = session.execute( select(User).where(User.id == user.id)).scalar_one() old_email_token = user.old_email_token new_email_token = user.new_email_token with patch("couchers.servicers.auth.now", one_minute_ago): with auth_api_session() as (auth_api, metadata_interceptor): with pytest.raises(grpc.RpcError) as e: auth_api.ConfirmChangeEmail( auth_pb2.ConfirmChangeEmailReq( change_email_token=old_email_token, )) assert e.value.code() == grpc.StatusCode.NOT_FOUND assert e.value.details() == errors.INVALID_TOKEN with pytest.raises(grpc.RpcError) as e: auth_api.ConfirmChangeEmail( auth_pb2.ConfirmChangeEmailReq( change_email_token=new_email_token, )) assert e.value.code() == grpc.StatusCode.NOT_FOUND assert e.value.details() == errors.INVALID_TOKEN with patch("couchers.servicers.auth.now", two_hours_one_minute_in_future): with auth_api_session() as (auth_api, metadata_interceptor): with pytest.raises(grpc.RpcError) as e: auth_api.ConfirmChangeEmail( auth_pb2.ConfirmChangeEmailReq( change_email_token=old_email_token, )) assert e.value.code() == grpc.StatusCode.NOT_FOUND assert e.value.details() == errors.INVALID_TOKEN with pytest.raises(grpc.RpcError) as e: auth_api.ConfirmChangeEmail( auth_pb2.ConfirmChangeEmailReq( change_email_token=new_email_token, )) assert e.value.code() == grpc.StatusCode.NOT_FOUND assert e.value.details() == errors.INVALID_TOKEN
def test_ChangeEmail_has_password(db, fast_passwords): password = random_hex() new_email = f"{random_hex()}@couchers.org.invalid" user, token = generate_user(hashed_password=hash_password(password)) with account_session(token) as account: account.ChangeEmail( account_pb2.ChangeEmailReq( password=wrappers_pb2.StringValue(value=password), new_email=new_email, )) with session_scope() as session: user_updated = session.execute( select(User).where(User.id == user.id)).scalar_one() assert user_updated.email == user.email assert user_updated.new_email == new_email assert user_updated.old_email_token is None assert not user_updated.old_email_token_created assert not user_updated.old_email_token_expiry assert not user_updated.need_to_confirm_via_old_email assert user_updated.new_email_token is not None assert user_updated.new_email_token_created <= now() assert user_updated.new_email_token_expiry >= now() assert user_updated.need_to_confirm_via_new_email token = user_updated.new_email_token with auth_api_session() as (auth_api, metadata_interceptor): res = auth_api.ConfirmChangeEmail( auth_pb2.ConfirmChangeEmailReq(change_email_token=token, )) assert res.state == auth_pb2.EMAIL_CONFIRMATION_STATE_SUCCESS with session_scope() as session: user = session.execute( select(User).where(User.id == user.id)).scalar_one() assert user.email == new_email assert user.new_email is None assert user.old_email_token is None assert user.old_email_token_created is None assert user.old_email_token_expiry is None assert not user.need_to_confirm_via_old_email assert user.new_email_token is None assert user.new_email_token_created is None assert user.new_email_token_expiry is None assert not user.need_to_confirm_via_new_email
def test_ChangeEmail_wrong_token(db, fast_passwords): password = random_hex() new_email = f"{random_hex()}@couchers.org.invalid" user, token = generate_user(hashed_password=None) with account_session(token) as account: account.ChangeEmail(account_pb2.ChangeEmailReq(new_email=new_email, )) with auth_api_session() as (auth_api, metadata_interceptor): with pytest.raises(grpc.RpcError) as e: res = auth_api.ConfirmChangeEmail( auth_pb2.ConfirmChangeEmailReq( change_email_token="wrongtoken", )) assert e.value.code() == grpc.StatusCode.NOT_FOUND assert e.value.details() == errors.INVALID_TOKEN with session_scope() as session: user_updated = session.execute( select(User).where(User.id == user.id)).scalar_one() assert user_updated.email == user.email
def test_ChangeEmail_no_change(db, fast_passwords): password = random_hex() user, token = generate_user(hashed_password=hash_password(password)) with account_session(token) as account: with pytest.raises(grpc.RpcError) as e: account.ChangeEmail( account_pb2.ChangeEmailReq( password=wrappers_pb2.StringValue(value=password), new_email=user.email, )) assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT assert e.value.details() == errors.INVALID_EMAIL with session_scope() as session: assert (session.execute( select(func.count()).select_from(User).where( User.new_email_token_created <= func.now()).where( User.new_email_token_expiry >= func.now())) ).scalar_one() == 0
def test_ChangeEmail_wrong_email(db, fast_passwords): password = random_hex() new_email = f"{random_hex()}@couchers.org.invalid" user, token = generate_user(hashed_password=hash_password(password)) with account_session(token) as account: with pytest.raises(grpc.RpcError) as e: account.ChangeEmail( account_pb2.ChangeEmailReq( password=wrappers_pb2.StringValue(value="wrong password"), new_email=new_email, )) assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT assert e.value.details() == errors.INVALID_USERNAME_OR_PASSWORD with session_scope() as session: assert (session.execute( select(func.count()).select_from(User).where( User.new_email_token_created <= func.now()).where( User.new_email_token_expiry >= func.now())) ).scalar_one() == 0
def test_ChangeEmail_no_password_confirm_with_new_email_first(db): new_email = f"{random_hex()}@couchers.org.invalid" user, token = generate_user(hashed_password=None) with account_session(token) as account: account.ChangeEmail(account_pb2.ChangeEmailReq(new_email=new_email, )) with session_scope() as session: user_updated = session.execute( select(User).where(User.id == user.id)).scalar_one() assert user_updated.email == user.email assert user_updated.new_email == new_email assert user_updated.old_email_token is not None assert user_updated.old_email_token_created <= now() assert user_updated.old_email_token_expiry >= now() assert user_updated.need_to_confirm_via_old_email assert user_updated.new_email_token is not None assert user_updated.new_email_token_created <= now() assert user_updated.new_email_token_expiry >= now() assert user_updated.need_to_confirm_via_new_email token = user_updated.new_email_token with auth_api_session() as (auth_api, metadata_interceptor): res = auth_api.ConfirmChangeEmail( auth_pb2.ConfirmChangeEmailReq(change_email_token=token, )) assert res.state == auth_pb2.EMAIL_CONFIRMATION_STATE_REQUIRES_CONFIRMATION_FROM_OLD_EMAIL with session_scope() as session: user_updated = session.execute( select(User).where(User.id == user.id)).scalar_one() assert user_updated.email == user.email assert user_updated.new_email == new_email assert user_updated.old_email_token is not None assert user_updated.old_email_token_created <= now() assert user_updated.old_email_token_expiry >= now() assert user_updated.need_to_confirm_via_old_email assert user_updated.new_email_token is None assert user_updated.new_email_token_created is None assert user_updated.new_email_token_expiry is None assert not user_updated.need_to_confirm_via_new_email token = user_updated.old_email_token with auth_api_session() as (auth_api, metadata_interceptor): res = auth_api.ConfirmChangeEmail( auth_pb2.ConfirmChangeEmailReq(change_email_token=token, )) assert res.state == auth_pb2.EMAIL_CONFIRMATION_STATE_SUCCESS with session_scope() as session: user = session.execute( select(User).where(User.id == user.id)).scalar_one() assert user.email == new_email assert user.new_email is None assert user.old_email_token is None assert user.old_email_token_created is None assert user.old_email_token_expiry is None assert not user.need_to_confirm_via_old_email assert user.new_email_token is None assert user.new_email_token_created is None assert user.new_email_token_expiry is None assert not user.need_to_confirm_via_new_email