diff --git a/configs/bank-landing/index.html b/configs/bank-landing/index.html
index 4c1f0e0..d517fa3 100644
--- a/configs/bank-landing/index.html
+++ b/configs/bank-landing/index.html
@@ -448,6 +448,67 @@
transform: scale(0.92);
transform-origin: center top;
}
+ .own-acct-lead {
+ margin: 0 0 0.85rem;
+ color: var(--muted);
+ font-size: 0.95rem;
+ line-height: 1.45;
+ }
+ .own-acct-lead strong { color: #e8c878; }
+ .own-acct-creds {
+ margin: 0 0 0.85rem;
+ padding: 0.75rem 0.85rem;
+ border-radius: 12px;
+ background: rgba(94, 234, 212, 0.08);
+ border: 1px solid rgba(94, 234, 212, 0.35);
+ text-align: left;
+ }
+ .own-acct-warn {
+ margin: 0 0 0.65rem;
+ font-size: 0.86rem;
+ color: #ccfbf1;
+ line-height: 1.4;
+ }
+ .own-acct-warn strong { color: #5eead4; }
+ .own-acct-dl {
+ margin: 0;
+ display: grid;
+ gap: 0.45rem;
+ }
+ .own-acct-dl div {
+ display: grid;
+ grid-template-columns: 5.5rem 1fr;
+ gap: 0.35rem;
+ align-items: baseline;
+ }
+ .own-acct-dl dt {
+ margin: 0;
+ font-size: 0.72rem;
+ font-weight: 700;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--muted);
+ }
+ .own-acct-dl dd {
+ margin: 0;
+ }
+ .own-acct-dl code {
+ font-size: 0.95rem;
+ font-weight: 700;
+ color: #f0d090;
+ word-break: break-all;
+ user-select: all;
+ }
+ #auto-account-btn {
+ border: 0;
+ cursor: pointer;
+ width: 100%;
+ max-width: 22rem;
+ }
+ #auto-account-btn:disabled {
+ opacity: 0.55;
+ cursor: wait;
+ }
.stats {
background: rgba(18, 28, 28, 0.55);
border: 1px solid rgba(62, 207, 191, 0.28);
@@ -763,18 +824,43 @@
Own bank account?
-
Register in the bank UI for your own balance (separate from the shared pool).
+
+ We can create a personal bank account for you automatically
+ (you do not pick a username or password). Balance starts at
+ GOA:0 — separate from the shared pool.
+
+
+
+ Create my bank account →
+
+
+
+
+ Created for you.
+ Copy these credentials now — they are not stored for recovery.
+ Login at the bank UI; balance is GOA:0 .
+
+
+
Username —
+
Password —
+
Balance GOA:0
+
+
+
-
Bank UI · register / login
+
Bank UI · login with your credentials
@@ -1095,6 +1181,71 @@
);
webuiPainted = true;
});
+
+ function createAutoAccount() {
+ var btn = document.getElementById("auto-account-btn");
+ var box = document.getElementById("auto-account-box");
+ var st = document.getElementById("auto-account-status");
+ var again = document.getElementById("auto-account-again");
+ if (btn) {
+ btn.disabled = true;
+ btn.textContent = "Creating account…";
+ }
+ if (st) st.textContent = "";
+ fetch("/intro/auto-account.json", { cache: "no-store" })
+ .then(function (r) {
+ if (!r.ok) throw new Error("HTTP " + r.status);
+ return r.json();
+ })
+ .then(function (d) {
+ if (!d || !d.ok) throw new Error((d && d.error) || "create failed");
+ if (box) box.hidden = false;
+ var u = document.getElementById("auto-account-user");
+ var p = document.getElementById("auto-account-pass");
+ var b = document.getElementById("auto-account-bal");
+ if (u) u.textContent = d.username || "—";
+ if (p) p.textContent = d.password || "—";
+ if (b) b.textContent = d.balance || "GOA:0";
+ if (st) {
+ st.textContent =
+ "Account created for you · save username & password · balance " +
+ (d.balance || "GOA:0");
+ }
+ if (btn) {
+ btn.hidden = true;
+ btn.disabled = false;
+ btn.textContent = "Create my bank account →";
+ }
+ if (again) again.hidden = false;
+ })
+ .catch(function (err) {
+ if (box) box.hidden = false;
+ if (st) {
+ st.textContent =
+ "Could not create account — try again. " +
+ (err && err.message ? err.message : "");
+ }
+ if (btn) {
+ btn.disabled = false;
+ btn.hidden = false;
+ btn.textContent = "Create my bank account →";
+ }
+ });
+ }
+ var btn = document.getElementById("auto-account-btn");
+ var again = document.getElementById("auto-account-again");
+ if (btn) btn.addEventListener("click", createAutoAccount);
+ if (again) {
+ again.addEventListener("click", function () {
+ var b = document.getElementById("auto-account-btn");
+ if (b) {
+ b.hidden = false;
+ b.disabled = false;
+ }
+ again.hidden = true;
+ createAutoAccount();
+ });
+ }
})();
function fmtCest(unix, fallback) {
diff --git a/configs/bank-landing/nginx-landing.conf b/configs/bank-landing/nginx-landing.conf
index 97047f1..251a713 100644
--- a/configs/bank-landing/nginx-landing.conf
+++ b/configs/bank-landing/nginx-landing.conf
@@ -41,6 +41,14 @@ server {
add_header Cache-Control "no-store" always;
add_header Access-Control-Allow-Origin * always;
}
+ # Auto-create personal bank account (credentials returned once; balance GOA:0)
+ location = /intro/auto-account.json {
+ proxy_pass http://127.0.0.1:19096/auto-account.json;
+ proxy_http_version 1.1;
+ proxy_set_header Host $host;
+ add_header Cache-Control "no-store" always;
+ add_header Access-Control-Allow-Origin * always;
+ }
location /intro/ {
alias /var/www/bank-landing/;
}
diff --git a/scripts/taler-bank/demo-withdraw-api.py b/scripts/taler-bank/demo-withdraw-api.py
index 2b175e5..c5ca797 100755
--- a/scripts/taler-bank/demo-withdraw-api.py
+++ b/scripts/taler-bank/demo-withdraw-api.py
@@ -1,10 +1,12 @@
#!/usr/bin/env python3
"""
-HTTP helper for bank landing: mint a fresh demo withdrawal from the shared
-explorer pool so app users get GOA without registering a bank account.
+HTTP helper for bank landing:
+ - GET /demo-withdraw.json — mint shared-pool (explorer) demo withdraw
+ - GET /auto-account.json — create a personal bank account (balance 0)
+ and return one-time credentials for the user to copy
Listens on 127.0.0.1:19096 (only inside bank container / localhost).
-Nginx proxies GET /intro/demo-withdraw.json → this service.
+Nginx proxies /intro/*.json → this service.
Env:
BANK_URL default http://127.0.0.1:9012
@@ -18,7 +20,9 @@ from __future__ import annotations
import json
import os
import re
+import secrets
import ssl
+import string
import time
import urllib.error
import urllib.request
@@ -119,6 +123,71 @@ def mint_withdraw() -> dict:
}
+def _rand_username() -> str:
+ # bank usernames: keep simple [a-z0-9]
+ alphabet = string.ascii_lowercase + string.digits
+ return "guest" + "".join(secrets.choice(alphabet) for _ in range(8))
+
+
+def _rand_password() -> str:
+ # readable enough to type; no ambiguous 0/O/1/l
+ alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789"
+ return "".join(secrets.choice(alphabet) for _ in range(14))
+
+
+def create_personal_account() -> dict:
+ """Public registration: auto username/password, balance starts at 0."""
+ username = _rand_username()
+ password = _rand_password()
+ name = "Guest " + username[5:9].upper()
+ code, body = http_json(
+ "POST",
+ f"{BANK}/accounts",
+ {
+ "username": username,
+ "password": password,
+ "name": name,
+ },
+ )
+ if code not in (200, 201, 204):
+ # retry once on conflict
+ if code in (409, 400):
+ username = _rand_username()
+ code, body = http_json(
+ "POST",
+ f"{BANK}/accounts",
+ {
+ "username": username,
+ "password": password,
+ "name": name,
+ },
+ )
+ if code not in (200, 201, 204):
+ raise RuntimeError(f"register failed HTTP {code}: {body}")
+ payto = ""
+ if isinstance(body, dict):
+ payto = body.get("internal_payto_uri") or body.get("payto_uri") or ""
+ if not payto:
+ payto = f"payto://x-taler-bank/bank.hacktivism.ch/{username}?receiver-name={username}"
+ return {
+ "ok": True,
+ "created_for_you": True,
+ "username": username,
+ "password": password,
+ "name": name,
+ "balance": "GOA:0",
+ "balance_note": "Starts at zero — not the shared community pool.",
+ "payto_uri": payto,
+ "webui": "https://bank.hacktivism.ch/webui/",
+ "hint": (
+ "This bank account was created for you automatically. "
+ "Copy username and password now — we do not store them for later recovery. "
+ "Balance starts at GOA:0."
+ ),
+ "created": time.strftime("%Y-%m-%dT%H:%MZ", time.gmtime()),
+ }
+
+
class Handler(BaseHTTPRequestHandler):
def log_message(self, fmt, *args):
sys_stderr = __import__("sys").stderr
@@ -136,15 +205,25 @@ class Handler(BaseHTTPRequestHandler):
def do_GET(self):
path = self.path.split("?", 1)[0]
- if path not in ("/", "/demo-withdraw.json", "/intro/demo-withdraw.json"):
- self.send_response(404)
- self._cors()
- self.send_header("Content-Type", "application/json")
- self.end_headers()
- self.wfile.write(b'{"ok":false,"error":"not found"}')
- return
try:
- body = mint_withdraw()
+ if path in (
+ "/",
+ "/demo-withdraw.json",
+ "/intro/demo-withdraw.json",
+ ):
+ body = mint_withdraw()
+ elif path in (
+ "/auto-account.json",
+ "/intro/auto-account.json",
+ ):
+ body = create_personal_account()
+ else:
+ self.send_response(404)
+ self._cors()
+ self.send_header("Content-Type", "application/json")
+ self.end_headers()
+ self.wfile.write(b'{"ok":false,"error":"not found"}')
+ return
raw = json.dumps(body).encode()
self.send_response(200)
self._cors()
diff --git a/scripts/taler-bank/install-demo-withdraw-api.sh b/scripts/taler-bank/install-demo-withdraw-api.sh
index 8d34a50..5366486 100755
--- a/scripts/taler-bank/install-demo-withdraw-api.sh
+++ b/scripts/taler-bank/install-demo-withdraw-api.sh
@@ -39,6 +39,21 @@ if "demo-withdraw.json" not in t:
print("nginx location added")
else:
print("nginx already has demo-withdraw")
+if "auto-account.json" not in t:
+ t=p.read_text()
+ block2=""" location = /intro/auto-account.json {
+ proxy_pass http://127.0.0.1:19096/auto-account.json;
+ proxy_http_version 1.1;
+ proxy_set_header Host \$host;
+ add_header Cache-Control "no-store" always;
+ add_header Access-Control-Allow-Origin * always;
+ }
+"""
+ t=t.replace(" location /intro/ {", block2+" location /intro/ {", 1)
+ p.write_text(t)
+ print("nginx auto-account location added")
+else:
+ print("nginx already has auto-account")
PY
nginx -t && nginx -s reload || true
else