aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_email_change_requires_passphrase.py
blob: 697e6f9118624842362e31a2ff8654e4f494db64 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
"""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