bank: API for auto-created personal accounts (balance zero)
Add GET /intro/auto-account.json on the bank helper (demo-withdraw-api): public registration with generated username/password, balance starts GOA:0, credentials returned once for the client to display. Nginx proxies the new path; install-demo-withdraw-api.sh installs the location. This is separate from the shared explorer demo withdraw.
This commit is contained in:
parent
65629ea57c
commit
f341116371
4 changed files with 267 additions and 14 deletions
|
|
@ -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 @@
|
|||
<div class="more-fold-body" id="more-fold-body" hidden>
|
||||
<div class="more-card">
|
||||
<h2>Own bank account?</h2>
|
||||
<p>Register in the bank UI for your own balance (separate from the shared pool).</p>
|
||||
<p class="own-acct-lead">
|
||||
We can <strong>create a personal bank account for you</strong> automatically
|
||||
(you do not pick a username or password). Balance starts at
|
||||
<strong>GOA:0</strong> — separate from the shared pool.
|
||||
</p>
|
||||
<div class="more-actions" style="margin-bottom:0.75rem">
|
||||
<button type="button" class="cta teal" id="auto-account-btn">
|
||||
Create my bank account →
|
||||
</button>
|
||||
</div>
|
||||
<div class="own-acct-creds" id="auto-account-box" hidden>
|
||||
<p class="own-acct-warn" role="status">
|
||||
<strong>Created for you.</strong>
|
||||
Copy these credentials now — they are not stored for recovery.
|
||||
Login at the bank UI; balance is <strong>GOA:0</strong>.
|
||||
</p>
|
||||
<dl class="own-acct-dl">
|
||||
<div><dt>Username</dt><dd><code id="auto-account-user">—</code></dd></div>
|
||||
<div><dt>Password</dt><dd><code id="auto-account-pass">—</code></dd></div>
|
||||
<div><dt>Balance</dt><dd><code id="auto-account-bal">GOA:0</code></dd></div>
|
||||
</dl>
|
||||
<p class="meta" id="auto-account-status" style="margin:0.5rem 0 0"></p>
|
||||
</div>
|
||||
<div class="more-qr">
|
||||
<div class="qr-taler-wrap">
|
||||
<div class="goa-pay-taler-qr" id="qr-webui-wrap">
|
||||
<div class="goa-pay-taler-qr__inner" id="qr-webui"></div>
|
||||
<img class="goa-pay-taler-qr__logo" id="qr-webui-logo" alt="" />
|
||||
</div>
|
||||
<div class="qr-label">Bank UI · register / login</div>
|
||||
<div class="qr-label">Bank UI · login with your credentials</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="more-actions">
|
||||
<a class="cta-sec" href="/webui/">Open bank UI →</a>
|
||||
<a class="cta-sec" href="/webui/" id="auto-account-webui">Open bank UI →</a>
|
||||
<button type="button" class="cta-sec" id="auto-account-again" hidden style="margin-top:0.45rem">
|
||||
Generate another account
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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/;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"):
|
||||
try:
|
||||
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
|
||||
try:
|
||||
body = mint_withdraw()
|
||||
raw = json.dumps(body).encode()
|
||||
self.send_response(200)
|
||||
self._cors()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue