bank: account QR encodes payto (was wrong webui homepage)

Was broken: after Create my bank account the QR encoded only
https://bank.hacktivism.ch/webui/, not the new account. Also payto
wrongly forced :443 while libeufin issues host without port.

Now: prefer bank internal_payto_uri; QR + blue link = payto:// of
the account; webui stays a separate login button.
This commit is contained in:
Hernâni Marques 2026-07-10 23:12:00 +02:00
parent 2fb060f79a
commit d391b42c52
No known key found for this signature in database
3 changed files with 62 additions and 40 deletions

View file

@ -39,30 +39,38 @@ LANDING = Path(os.environ.get("LANDING_DIR", "/var/www/bank-landing"))
LISTEN = ("127.0.0.1", int(os.environ.get("DEMO_WITHDRAW_PORT", "19096")))
def bank_payto_authority() -> str:
"""Host:port for payto://x-taler-bank/… (port required; public HTTPS → :443)."""
def bank_payto_host() -> str:
"""Hostname for payto://x-taler-bank/HOST/account (libeufin style: no :443)."""
override = os.environ.get("BANK_PAYTO_HOST", "").strip()
if override:
# allow full host or host:port if operator sets it
return override
u = urllib.parse.urlparse(
BANK_PUBLIC if "://" in BANK_PUBLIC else f"https://{BANK_PUBLIC}"
)
host = u.hostname or "bank.hacktivism.ch"
if u.port:
port = u.port
elif (u.scheme or "https") == "https":
port = 443
else:
port = 80
return f"{host}:{port}"
return u.hostname or "bank.hacktivism.ch"
def make_account_payto(username: str) -> str:
"""Valid x-taler-bank payto with explicit port + encoded receiver-name."""
auth = bank_payto_authority()
def make_account_payto(username: str, bank_payto: str = "") -> str:
"""Account payto for QR/link.
Prefer libeufin internal_payto_uri when present (authoritative).
Fallback matches bank registration: host without default HTTPS port.
"""
rn = urllib.parse.quote(username, safe="")
# path account name is bank-safe [a-z0-9-]; keep unencoded
return f"payto://x-taler-bank/{auth}/{username}?receiver-name={rn}"
if isinstance(bank_payto, str) and bank_payto.startswith("payto://x-taler-bank/"):
# Keep bank-issued host/account; ensure receiver-name is encoded
try:
# payto://x-taler-bank/HOST/ACCOUNT?…
rest = bank_payto[len("payto://x-taler-bank/") :]
host_acct, _, _qs = rest.partition("?")
host, _, acct = host_acct.partition("/")
if host and acct:
return f"payto://x-taler-bank/{host}/{acct}?receiver-name={rn}"
except Exception:
pass
host = bank_payto_host()
return f"payto://x-taler-bank/{host}/{username}?receiver-name={rn}"
def public_webui_url() -> str:
@ -240,8 +248,13 @@ def create_personal_account() -> dict:
)
if code not in (200, 201, 204):
raise RuntimeError(f"register failed HTTP {code}: {body}")
# Always emit a correct public payto (libeufin often omits :443 → invalid for HTTPS)
payto = make_account_payto(username)
bank_payto = ""
if isinstance(body, dict):
bank_payto = (
body.get("internal_payto_uri") or body.get("payto_uri") or ""
)
# Prefer bank-issued payto (no fake :443). QR encodes this account payto.
payto = make_account_payto(username, bank_payto)
webui = public_webui_url()
return {
"ok": True,
@ -253,12 +266,14 @@ def create_personal_account() -> dict:
"balance": "GOA:0",
"balance_note": "Starts at zero — not the shared community pool.",
"payto_uri": payto,
"qr_payload": payto,
"webui": webui,
"account_url": webui,
"hint": (
"This bank account was created for you automatically "
f"({username}). "
"Copy username and password now — we do not store them for later recovery."
"Copy username and password now — we do not store them for later recovery. "
"QR / blue link is the account payto:// (not the bank UI homepage)."
),
"created": time.strftime("%Y-%m-%dT%H:%MZ", time.gmtime()),
"created_human": time.strftime("%Y-%m-%d %H:%M %Z", time.localtime()),

View file

@ -189,29 +189,31 @@ except Exception as e:
if not d.get("ok"):
print("ok!=true")
sys.exit(1)
payto = d.get("payto_uri") or ""
payto = d.get("payto_uri") or d.get("qr_payload") or ""
webui = d.get("webui") or d.get("account_url") or ""
# payto://x-taler-bank/host:port/account?...
# payto://x-taler-bank/HOST[/port]/account?receiver-name=…
# Match libeufin: host without :443 is correct for x-taler-bank payto
m = re.match(r"^payto://x-taler-bank/([^/?#]+)/([^/?#]+)(\?.*)?$", payto)
if not m:
print("payto shape invalid:", payto[:120])
sys.exit(1)
auth, acct = m.group(1), m.group(2)
if ":" not in auth:
print("payto missing port (need host:port):", auth)
if not auth or not acct:
print("payto host/account empty")
sys.exit(1)
host, port_s = auth.rsplit(":", 1)
if not host or not port_s.isdigit():
print("payto host:port invalid:", auth)
if "receiver-name=" not in (m.group(3) or ""):
print("payto missing receiver-name")
sys.exit(1)
if not acct:
print("payto account empty")
# QR must encode payto (not only webui homepage)
qr = d.get("qr_payload") or payto
if not qr.startswith("payto://"):
print("qr_payload must be payto:// account URI:", (qr or "")[:80])
sys.exit(1)
u = urlparse(webui)
if u.scheme not in ("http", "https") or not u.netloc or "webui" not in (u.path or ""):
print("webui not absolute https …/webui/:", webui[:120])
sys.exit(1)
print(f"user={d.get('username','')} payto={auth}/{acct} webui={webui}")
print(f"user={d.get('username','')} payto={auth}/{acct} qr=payto webui={webui}")
sys.exit(0)
PY
then