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
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue