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:
parent
2fb060f79a
commit
d391b42c52
3 changed files with 62 additions and 40 deletions
|
|
@ -1348,8 +1348,8 @@ tw balance</pre>
|
||||||
<div class="goa-pay-taler-qr__inner" id="qr-auto-acct"></div>
|
<div class="goa-pay-taler-qr__inner" id="qr-auto-acct"></div>
|
||||||
<img class="goa-pay-taler-qr__logo" id="qr-auto-acct-logo" alt="" />
|
<img class="goa-pay-taler-qr__logo" id="qr-auto-acct-logo" alt="" />
|
||||||
</div>
|
</div>
|
||||||
<div class="qr-label" id="qr-auto-acct-label">Your account · bank UI login</div>
|
<div class="qr-label" id="qr-auto-acct-label">Your account · payto</div>
|
||||||
<a class="meta-uri" id="qr-auto-acct-payload" href="https://bank.hacktivism.ch/webui/" target="_blank" rel="noopener noreferrer">https://bank.hacktivism.ch/webui/</a>
|
<a class="meta-uri" id="qr-auto-acct-payload" href="#">—</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1776,8 +1776,8 @@ tw balance</pre>
|
||||||
webuiA.setAttribute("target", "_blank");
|
webuiA.setAttribute("target", "_blank");
|
||||||
webuiA.setAttribute("rel", "noopener noreferrer");
|
webuiA.setAttribute("rel", "noopener noreferrer");
|
||||||
}
|
}
|
||||||
/* payto must include host:port (e.g. bank.hacktivism.ch:443) */
|
/* Account payto (libeufin style) — this is what the account QR encodes */
|
||||||
var payto = d.payto_uri || "";
|
var payto = d.payto_uri || d.qr_payload || "";
|
||||||
if (payA) {
|
if (payA) {
|
||||||
if (payto && payto.indexOf("payto://") === 0) {
|
if (payto && payto.indexOf("payto://") === 0) {
|
||||||
payA.href = payto;
|
payA.href = payto;
|
||||||
|
|
@ -1793,26 +1793,31 @@ tw balance</pre>
|
||||||
st.textContent =
|
st.textContent =
|
||||||
"Account created for you · " +
|
"Account created for you · " +
|
||||||
(d.username || d.name || "") +
|
(d.username || d.name || "") +
|
||||||
" · save username & password";
|
" · save username & password · QR = payto account";
|
||||||
}
|
}
|
||||||
/* Fancy QR_Taler: encode bank UI URL (login with credentials above) */
|
/* QR encodes payto:// of this account (not the generic webui homepage) */
|
||||||
var qrBlock = document.getElementById("auto-account-qr-block");
|
var qrBlock = document.getElementById("auto-account-qr-block");
|
||||||
var qrLabel = document.getElementById("qr-auto-acct-label");
|
var qrLabel = document.getElementById("qr-auto-acct-label");
|
||||||
|
var qrPayload = payto || webui;
|
||||||
if (qrBlock) qrBlock.hidden = false;
|
if (qrBlock) qrBlock.hidden = false;
|
||||||
if (qrLabel) {
|
if (qrLabel) {
|
||||||
qrLabel.textContent =
|
qrLabel.textContent =
|
||||||
"QR_Taler · open bank UI · login as " + (d.username || "you");
|
"Account payto · " + (d.username || "you");
|
||||||
}
|
}
|
||||||
var autoPayload = document.getElementById("qr-auto-acct-payload");
|
var autoPayload = document.getElementById("qr-auto-acct-payload");
|
||||||
if (autoPayload) {
|
if (autoPayload) {
|
||||||
autoPayload.href = webui;
|
autoPayload.href = qrPayload;
|
||||||
autoPayload.textContent = webui;
|
autoPayload.textContent = qrPayload;
|
||||||
autoPayload.setAttribute("target", "_blank");
|
if (qrPayload.indexOf("http") === 0) {
|
||||||
autoPayload.setAttribute("rel", "noopener noreferrer");
|
autoPayload.setAttribute("target", "_blank");
|
||||||
|
autoPayload.setAttribute("rel", "noopener noreferrer");
|
||||||
|
} else {
|
||||||
|
autoPayload.removeAttribute("target");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
makeQr(
|
makeQr(
|
||||||
document.getElementById("qr-auto-acct"),
|
document.getElementById("qr-auto-acct"),
|
||||||
webui,
|
qrPayload,
|
||||||
document.getElementById("qr-auto-acct-logo")
|
document.getElementById("qr-auto-acct-logo")
|
||||||
);
|
);
|
||||||
if (btn) {
|
if (btn) {
|
||||||
|
|
|
||||||
|
|
@ -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")))
|
LISTEN = ("127.0.0.1", int(os.environ.get("DEMO_WITHDRAW_PORT", "19096")))
|
||||||
|
|
||||||
|
|
||||||
def bank_payto_authority() -> str:
|
def bank_payto_host() -> str:
|
||||||
"""Host:port for payto://x-taler-bank/… (port required; public HTTPS → :443)."""
|
"""Hostname for payto://x-taler-bank/HOST/account (libeufin style: no :443)."""
|
||||||
override = os.environ.get("BANK_PAYTO_HOST", "").strip()
|
override = os.environ.get("BANK_PAYTO_HOST", "").strip()
|
||||||
if override:
|
if override:
|
||||||
|
# allow full host or host:port if operator sets it
|
||||||
return override
|
return override
|
||||||
u = urllib.parse.urlparse(
|
u = urllib.parse.urlparse(
|
||||||
BANK_PUBLIC if "://" in BANK_PUBLIC else f"https://{BANK_PUBLIC}"
|
BANK_PUBLIC if "://" in BANK_PUBLIC else f"https://{BANK_PUBLIC}"
|
||||||
)
|
)
|
||||||
host = u.hostname or "bank.hacktivism.ch"
|
return 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}"
|
|
||||||
|
|
||||||
|
|
||||||
def make_account_payto(username: str) -> str:
|
def make_account_payto(username: str, bank_payto: str = "") -> str:
|
||||||
"""Valid x-taler-bank payto with explicit port + encoded receiver-name."""
|
"""Account payto for QR/link.
|
||||||
auth = bank_payto_authority()
|
|
||||||
|
Prefer libeufin internal_payto_uri when present (authoritative).
|
||||||
|
Fallback matches bank registration: host without default HTTPS port.
|
||||||
|
"""
|
||||||
rn = urllib.parse.quote(username, safe="")
|
rn = urllib.parse.quote(username, safe="")
|
||||||
# path account name is bank-safe [a-z0-9-]; keep unencoded
|
if isinstance(bank_payto, str) and bank_payto.startswith("payto://x-taler-bank/"):
|
||||||
return f"payto://x-taler-bank/{auth}/{username}?receiver-name={rn}"
|
# 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:
|
def public_webui_url() -> str:
|
||||||
|
|
@ -240,8 +248,13 @@ def create_personal_account() -> dict:
|
||||||
)
|
)
|
||||||
if code not in (200, 201, 204):
|
if code not in (200, 201, 204):
|
||||||
raise RuntimeError(f"register failed HTTP {code}: {body}")
|
raise RuntimeError(f"register failed HTTP {code}: {body}")
|
||||||
# Always emit a correct public payto (libeufin often omits :443 → invalid for HTTPS)
|
bank_payto = ""
|
||||||
payto = make_account_payto(username)
|
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()
|
webui = public_webui_url()
|
||||||
return {
|
return {
|
||||||
"ok": True,
|
"ok": True,
|
||||||
|
|
@ -253,12 +266,14 @@ def create_personal_account() -> dict:
|
||||||
"balance": "GOA:0",
|
"balance": "GOA:0",
|
||||||
"balance_note": "Starts at zero — not the shared community pool.",
|
"balance_note": "Starts at zero — not the shared community pool.",
|
||||||
"payto_uri": payto,
|
"payto_uri": payto,
|
||||||
|
"qr_payload": payto,
|
||||||
"webui": webui,
|
"webui": webui,
|
||||||
"account_url": webui,
|
"account_url": webui,
|
||||||
"hint": (
|
"hint": (
|
||||||
"This bank account was created for you automatically "
|
"This bank account was created for you automatically "
|
||||||
f"({username}). "
|
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": time.strftime("%Y-%m-%dT%H:%MZ", time.gmtime()),
|
||||||
"created_human": time.strftime("%Y-%m-%d %H:%M %Z", time.localtime()),
|
"created_human": time.strftime("%Y-%m-%d %H:%M %Z", time.localtime()),
|
||||||
|
|
|
||||||
|
|
@ -189,29 +189,31 @@ except Exception as e:
|
||||||
if not d.get("ok"):
|
if not d.get("ok"):
|
||||||
print("ok!=true")
|
print("ok!=true")
|
||||||
sys.exit(1)
|
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 ""
|
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)
|
m = re.match(r"^payto://x-taler-bank/([^/?#]+)/([^/?#]+)(\?.*)?$", payto)
|
||||||
if not m:
|
if not m:
|
||||||
print("payto shape invalid:", payto[:120])
|
print("payto shape invalid:", payto[:120])
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
auth, acct = m.group(1), m.group(2)
|
auth, acct = m.group(1), m.group(2)
|
||||||
if ":" not in auth:
|
if not auth or not acct:
|
||||||
print("payto missing port (need host:port):", auth)
|
print("payto host/account empty")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
host, port_s = auth.rsplit(":", 1)
|
if "receiver-name=" not in (m.group(3) or ""):
|
||||||
if not host or not port_s.isdigit():
|
print("payto missing receiver-name")
|
||||||
print("payto host:port invalid:", auth)
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if not acct:
|
# QR must encode payto (not only webui homepage)
|
||||||
print("payto account empty")
|
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)
|
sys.exit(1)
|
||||||
u = urlparse(webui)
|
u = urlparse(webui)
|
||||||
if u.scheme not in ("http", "https") or not u.netloc or "webui" not in (u.path or ""):
|
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])
|
print("webui not absolute https …/webui/:", webui[:120])
|
||||||
sys.exit(1)
|
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)
|
sys.exit(0)
|
||||||
PY
|
PY
|
||||||
then
|
then
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue