""" "E-mail it to myself" on the recovery screen (docs/MESHBAY_DESIGN.md ยง3.6). The recovery key never reaches the hub; a `mailto:` link has the person's own mail application send it to their own address. This runs the page's `recoveryMailto` under Node, with the English strings, and reads the link back the way a mail application would. """ import json import shutil import subprocess from urllib.parse import parse_qs, unquote, urlsplit import pytest from spa_source import STATIC pytestmark = pytest.mark.skipif(shutil.which("node") is None, reason="no node") MNEMONIC = "ABCD EFGH JKLM NPQR STUV WXYZ 2345 6789 ABCD EFGH JKLM NPQR STUV" HARNESS = r""" const fs = require('fs'); const [page, locale, email, user, key] = process.argv.slice(1); const src = fs.readFileSync(page, 'utf8'); const start = src.indexOf('export function recoveryMailto('); if (start < 0) throw new Error('recoveryMailto not found'); const end = src.indexOf('\n}\n', start); const fn = src.slice(start, end + 2).replace(/^export /, ''); (async () => { const strings = (await import(locale)).default; const t = (k, params) => { let s = strings[k]; if (s == null) throw new Error('missing string ' + k); for (const [n, v] of Object.entries(params || {})) s = s.split(`{${n}}`).join(v); return s; }; const recoveryMailto = new Function('t', fn + '\nreturn recoveryMailto;')(t); process.stdout.write(JSON.stringify(recoveryMailto(email, user, key))); })(); """ def _link(email: str, user: str = "alice_tester") -> str: out = subprocess.run( ["node", "-e", HARNESS, str(STATIC / "auth-page.js"), (STATIC / "locales" / "en.js").as_uri(), email, user, MNEMONIC], capture_output=True, text=True, check=True) return json.loads(out.stdout) def test_the_link_sends_the_key_to_the_person_and_nowhere_else(): link = _link("alice+mesh@example.org") parts = urlsplit(link) assert parts.scheme == "mailto" assert unquote(parts.path) == "alice+mesh@example.org" assert "@" in parts.path, "an encoded '@' is not decoded by every mail app" query = parse_qs(parts.query) assert set(query) == {"subject", "body"}, "no cc, no bcc, nothing else" body = query["body"][0] assert MNEMONIC in body and "alice_tester" in body assert "\n\n" in body, "line breaks survive the encoding" def test_an_address_cannot_add_recipients_or_headers(): # An address is the person's own, but it is still text put into a URL. link = _link("a@example.org?bcc=x@evil.example&body=") query = parse_qs(urlsplit(link).query) assert set(query) == {"subject", "body"} assert MNEMONIC in query["body"][0]