"""Changing the address on file requires the passphrase, not merely a token. A member hands its hub access token to every node it connects to (the MNP handshake), so a node operator holds a live bearer token for that member. `PATCH /v1/users/me {email}` used to need only that token, and the confirmation code goes to the *new* address — so an operator could point the account's e-mail at their own inbox, confirm it, and then use the passphrase-reset path to take the account over. The passphrase (as the derived auth_key, which is all the hub ever sees) is now required, exactly as for a passphrase change or an account deletion. """ import pytest async def _account(client, username="mail_pass_test", auth_key="k" * 44): await client.post("/v1/users/register", json={ "username": username, "email": f"{username}@test.local", "auth_key": auth_key}) r = await client.post("/v1/users/login", json={ "username": username, "auth_key": auth_key}) return r.json()["access_token"] @pytest.mark.asyncio async def test_email_change_without_passphrase_is_refused(client): tok = await _account(client) r = await client.patch("/v1/users/me", headers={"Authorization": f"Bearer {tok}"}, json={"email": "attacker@evil.invalid"}) assert r.status_code == 403 @pytest.mark.asyncio async def test_email_change_with_wrong_passphrase_is_refused(client): tok = await _account(client) r = await client.patch("/v1/users/me", headers={"Authorization": f"Bearer {tok}"}, json={"email": "attacker@evil.invalid", "auth_key": "z" * 44}) assert r.status_code == 403 @pytest.mark.asyncio async def test_email_change_with_correct_passphrase_proceeds(client): tok = await _account(client, username="mail_ok_test", auth_key="k" * 44) r = await client.patch("/v1/users/me", headers={"Authorization": f"Bearer {tok}"}, json={"email": "new@real.invalid", "auth_key": "k" * 44}) assert r.status_code == 200 assert r.json().get("email_verification_required") is True @pytest.mark.asyncio async def test_profile_patch_without_email_needs_no_passphrase(client): """Regression: a PATCH that does not change the address is unaffected.""" tok = await _account(client, username="mail_noop_test") r = await client.patch("/v1/users/me", headers={"Authorization": f"Bearer {tok}"}, json={}) assert r.status_code == 200