refactor: use Flask Blueprints to organize extensions

- extensions are now blueprints: keep views, templastes and statics in the same folder
- increase app security using `flask-talisman`
- whenever possible use {{ url_for }} for links between pages
- remove references to non-existing JavaScript code
- add missing favicon.ico
This commit is contained in:
Eneko Illarramendi
2020-01-31 21:07:05 +01:00
parent 4beb7be852
commit 9e90aabead
29 changed files with 541 additions and 1115 deletions
+54 -311
View File
@@ -1,41 +1,57 @@
import uuid
import os
import json
import os
import requests
import re
import uuid
from flask import Flask, jsonify, render_template, request, redirect, url_for
from lnurl import Lnurl, LnurlWithdrawResponse, encode
from datetime import datetime
from flask import Flask, jsonify, redirect, render_template, request, url_for
from flask_talisman import Talisman
from lnurl import Lnurl, LnurlWithdrawResponse
from . import bolt11
from .db import Database, ExtDatabase, FauDatabase
from .core import core_app
from .db import open_db, open_ext_db
from .extensions.withdraw import withdraw_ext
from .helpers import megajson
from .settings import LNBITS_PATH, WALLET, DEFAULT_USER_WALLET_NAME, FEE_RESERVE
app = Flask(__name__)
Talisman(app, content_security_policy={
"default-src": [
"'self'",
"'unsafe-eval'",
"'unsafe-inline'",
"cdnjs.cloudflare.com",
"code.ionicframework.com",
"code.jquery.com",
"fonts.googleapis.com",
"fonts.gstatic.com",
"maxcdn.bootstrapcdn.com",
]
})
# filters
app.jinja_env.filters["megajson"] = megajson
# blueprints
app.register_blueprint(core_app)
app.register_blueprint(withdraw_ext, url_prefix="/withdraw")
@app.before_first_request
def init():
with Database() as db:
with open_db() as db:
with open(os.path.join(LNBITS_PATH, "data", "schema.sql")) as schemafile:
for stmt in schemafile.read().split(";\n\n"):
db.execute(stmt, [])
@app.route("/")
def home():
return render_template("index.html")
@app.route("/deletewallet")
def deletewallet():
user_id = request.args.get("usr")
wallet_id = request.args.get("wal")
with Database() as db:
with open_db() as db:
db.execute(
"""
UPDATE wallets AS w
@@ -94,7 +110,7 @@ def lnurlwallet():
data = r.json()
break
with Database() as db:
with open_db() as db:
wallet_id = uuid.uuid4().hex
user_id = uuid.uuid4().hex
wallet_name = DEFAULT_USER_WALLET_NAME
@@ -135,7 +151,7 @@ def wallet():
# just wallet_name: create a user, then generate a wallet_id and create
# nothing: create everything
with Database() as db:
with open_db() as db:
# ensure this user exists
# -------------------------------
@@ -224,15 +240,15 @@ def wallet():
)
@app.route("/v1/invoices", methods=["GET", "POST"])
@app.route("/api/v1/invoices", methods=["GET", "POST"])
def api_invoices():
if request.headers["Content-Type"] != "application/json":
return jsonify({"ERROR": "MUST BE JSON"}), 400
postedjson = request.json
#Form validation
if int(postedjson["value"]) < 0 or not postedjson["memo"].replace(' ','').isalnum():
# Form validation
if int(postedjson["value"]) < 0 or not postedjson["memo"].replace(" ", "").isalnum():
return jsonify({"ERROR": "FORM ERROR"}), 401
if "value" not in postedjson:
@@ -247,7 +263,7 @@ def api_invoices():
if "memo" not in postedjson:
return jsonify({"ERROR": "NO MEMO"}), 400
with Database() as db:
with open_db() as db:
wallet = db.fetchone(
"SELECT id FROM wallets WHERE inkey = ? OR adminkey = ?",
(request.headers["Grpc-Metadata-macaroon"], request.headers["Grpc-Metadata-macaroon"],),
@@ -271,7 +287,7 @@ def api_invoices():
return jsonify({"pay_req": pay_req, "payment_hash": pay_hash}), 200
@app.route("/v1/channels/transactions", methods=["GET", "POST"])
@app.route("/api/v1/channels/transactions", methods=["GET", "POST"])
def api_transactions():
if request.headers["Content-Type"] != "application/json":
@@ -283,7 +299,7 @@ def api_transactions():
if "payment_request" not in data:
return jsonify({"ERROR": "NO PAY REQ"}), 400
with Database() as db:
with open_db() as db:
wallet = db.fetchone("SELECT id FROM wallets WHERE adminkey = ?", (request.headers["Grpc-Metadata-macaroon"],))
if not wallet:
@@ -332,12 +348,12 @@ def api_transactions():
return jsonify({"PAID": "TRUE", "payment_hash": invoice.payment_hash}), 200
@app.route("/v1/invoice/<payhash>", methods=["GET"])
@app.route("/api/v1/invoice/<payhash>", methods=["GET"])
def api_checkinvoice(payhash):
if request.headers["Content-Type"] != "application/json":
return jsonify({"ERROR": "MUST BE JSON"}), 400
with Database() as db:
with open_db() as db:
payment = db.fetchone(
"""
SELECT pending
@@ -362,12 +378,12 @@ def api_checkinvoice(payhash):
return jsonify({"PAID": "TRUE"}), 200
@app.route("/v1/payment/<payhash>", methods=["GET"])
@app.route("/api/v1/payment/<payhash>", methods=["GET"])
def api_checkpayment(payhash):
if request.headers["Content-Type"] != "application/json":
return jsonify({"ERROR": "MUST BE JSON"}), 400
with Database() as db:
with open_db() as db:
payment = db.fetchone(
"""
SELECT pending
@@ -392,9 +408,9 @@ def api_checkpayment(payhash):
return jsonify({"PAID": "TRUE"}), 200
@app.route("/v1/checkpending", methods=["POST"])
@app.route("/api/v1/checkpending", methods=["POST"])
def api_checkpending():
with Database() as db:
with open_db() as db:
for pendingtx in db.fetchall(
"""
SELECT
@@ -427,10 +443,6 @@ def api_checkpending():
return ""
###########EXTENSIONS STUFF - ADD TO LNFAUCET FOLDER IF POSS
# Checks DB to see if the extensions are activated or not activated for the user
@app.route("/extensions")
def extensions():
@@ -441,15 +453,14 @@ def extensions():
if usr:
if not len(usr) > 20:
return redirect(url_for("home"))
with Database() as db:
with open_db() as db:
user_wallets = db.fetchall("SELECT * FROM wallets WHERE user = ?", (usr,))
with ExtDatabase() as Extdd:
user_ext = Extdd.fetchall("SELECT * FROM overview WHERE user = ?", (usr,))
with open_ext_db() as ext_db:
user_ext = ext_db.fetchall("SELECT * FROM overview WHERE user = ?", (usr,))
if not user_ext:
Extdd.execute(
ext_db.execute(
"""
INSERT OR IGNORE INTO overview (user) VALUES (?)
""",
@@ -459,283 +470,15 @@ def extensions():
if lnevents:
if int(lnevents) != user_ext[0][1] and int(lnevents) < 2:
Extdd.execute("UPDATE overview SET lnevents = ? WHERE user = ?", (int(lnevents),usr,))
user_ext = Extdd.fetchall("SELECT * FROM overview WHERE user = ?", (usr,))
ext_db.execute("UPDATE overview SET lnevents = ? WHERE user = ?", (int(lnevents), usr,))
user_ext = ext_db.fetchall("SELECT * FROM overview WHERE user = ?", (usr,))
if lnjoust:
if int(lnjoust) != user_ext[0][2] and int(lnjoust) < 2:
Extdd.execute("UPDATE overview SET lnjoust = ? WHERE user = ?", (int(lnjoust),usr,))
user_ext = Extdd.fetchall("SELECT * FROM overview WHERE user = ?", (usr,))
ext_db.execute("UPDATE overview SET lnjoust = ? WHERE user = ?", (int(lnjoust), usr,))
user_ext = ext_db.fetchall("SELECT * FROM overview WHERE user = ?", (usr,))
if withdraw:
if int(withdraw) != user_ext[0][3] and int(withdraw) < 2:
Extdd.execute("UPDATE overview SET withdraw = ? WHERE user = ?", (int(withdraw),usr,))
user_ext = Extdd.fetchall("SELECT * FROM overview WHERE user = ?", (usr,))
ext_db.execute("UPDATE overview SET withdraw = ? WHERE user = ?", (int(withdraw), usr,))
user_ext = ext_db.fetchall("SELECT * FROM overview WHERE user = ?", (usr,))
return render_template(
"extensions.html", user_wallets=user_wallets, user=usr, user_ext=user_ext
)
# Main withdraw link page
@app.route("/withdraw")
def withdraw():
usr = request.args.get("usr")
fauc = request.args.get("fauc")
if usr:
if not len(usr) > 20:
return redirect(url_for("home"))
#Get all the data
with Database() as db:
user_wallets = db.fetchall("SELECT * FROM wallets WHERE user = ?", (usr,))
with ExtDatabase() as Extdd:
user_ext = Extdd.fetchall("SELECT * FROM overview WHERE user = ?", (usr,))
with FauDatabase() as Faudb:
user_fau = Faudb.fetchall("SELECT * FROM withdraws WHERE usr = ?", (usr,))
#If del is selected by user from withdraw page, the withdraw link is to be deleted
faudel = request.args.get("del")
if faudel:
Faudb.execute("DELETE FROM withdraws WHERE uni = ?", (faudel,))
user_fau = Faudb.fetchall("SELECT * FROM withdraws WHERE usr = ?", (usr,))
return render_template(
"withdraw.html", user_wallets=user_wallets, user=usr, user_ext=user_ext, user_fau=user_fau
)
#Returns encoded LNURL if web url and parameter gieven
@app.route("/v1/lnurlencode/<urlstr>/<parstr>", methods=["GET"])
def api_lnurlencode(urlstr, parstr):
if not urlstr:
return jsonify({"STATUS": "FALSE"}), 200
with FauDatabase() as Faudb:
user_fau = Faudb.fetchall("SELECT * FROM withdraws WHERE uni = ?", (parstr,))
randar = user_fau[0][15].split(",")
# randar = randar[:-1]
print(int(user_fau[0][10])-1)
#If "Unique links" selected get correct rand, if not there is only one rand
if user_fau[0][12] > 0:
rand = randar[user_fau[0][10]-2]
print(rand)
else:
rand = randar[0]
lnurlstr = encode( "https://" + urlstr + "/v1/lnurlfetch/" + urlstr + "/" + parstr + "/" + rand)
return jsonify({"STATUS": "TRUE", "LNURL": lnurlstr}), 200
#Returns LNURL json
@app.route("/v1/lnurlfetch/<urlstr>/<parstr>/<rand>", methods=["GET"])
def api_lnurlfetch(parstr, urlstr, rand):
if not parstr:
return jsonify({"STATUS": "FALSE", "ERROR": "NO WALL ID"}), 200
if not urlstr:
return jsonify({"STATUS": "FALSE", "ERROR": "NO URL"}), 200
with FauDatabase() as Faudb:
user_fau = Faudb.fetchall("SELECT * FROM withdraws WHERE uni = ?", (parstr,))
print(user_fau[0][0])
k1str = uuid.uuid4().hex
Faudb.execute("UPDATE withdraws SET withdrawals = ? WHERE uni = ?", (k1str ,parstr,))
res = LnurlWithdrawResponse(
callback='https://' + urlstr + '/v1/lnurlwithdraw/' + rand + '/',
k1=k1str,
min_withdrawable=user_fau[0][8]*1000,
max_withdrawable=user_fau[0][7]*1000,
default_description="LNURL withdraw",
)
print("res")
return res.json(), 200
#Pays invoice if passed k1 invoice and rand
@app.route("/v1/lnurlwithdraw/<rand>/", methods=["GET"])
def api_lnurlwithdraw(rand):
k1 = request.args.get("k1")
pr = request.args.get("pr")
print(rand)
if not k1:
return jsonify({"STATUS": "FALSE", "ERROR": "NO k1"}), 200
if not pr:
return jsonify({"STATUS": "FALSE", "ERROR": "NO PR"}), 200
with FauDatabase() as Faudb:
user_fau = Faudb.fetchall("SELECT * FROM withdraws WHERE withdrawals = ?", (k1,))
if not user_fau:
return jsonify({"status":"ERROR", "reason":"NO AUTH"}), 400
if user_fau[0][10] <1:
return jsonify({"status":"ERROR", "reason":"withdraw SPENT"}), 400
# Check withdraw time
dt = datetime.now()
seconds = dt.timestamp()
secspast = seconds - user_fau[0][14]
print(secspast)
if secspast < user_fau[0][11]:
return jsonify({"status":"ERROR", "reason":"WAIT " + str(int(user_fau[0][11] - secspast)) + "s"}), 400
randar = user_fau[0][15].split(",")
if rand not in randar:
return jsonify({"status":"ERROR", "reason":"BAD AUTH"}), 400
if len(randar) > 2:
randar.remove(rand)
randstr = ','.join(randar)
print(randstr)
# Update time and increments
upinc = (int(user_fau[0][10]) - 1)
Faudb.execute("UPDATE withdraws SET inc = ?, rand = ?, tmestmp = ? WHERE withdrawals = ?", (upinc, randstr, seconds, k1,))
header = {'Content-Type': 'application/json','Grpc-Metadata-macaroon':str(user_fau[0][4])}
data = {'payment_request': pr}
r = requests.post(url = "https://lnbits.com/v1/channels/transactions", headers=header, data=json.dumps(data))
r_json=r.json()
if "ERROR" in r_json:
return jsonify({"status":"ERROR", "reason":r_json["ERROR"]}), 400
with FauDatabase() as Faudb:
user_fau = Faudb.fetchall("SELECT * FROM withdraws WHERE withdrawals = ?", (k1,))
return jsonify({"status":"OK"}), 200
@app.route("/withdrawmaker", methods=["GET", "POST"])
def withdrawmaker():
data = request.json
amt = data["amt"]
tit = data["tit"]
wal = data["wal"]
minamt = data["minamt"]
maxamt = data["maxamt"]
tme = data["tme"]
uniq = data["uniq"]
usr = data["usr"]
wall = wal.split("-")
#Form validation
if int(amt) < 0 or not tit.replace(' ','').isalnum() or wal == "" or int(minamt) < 0 or int(maxamt) < 0 or int(minamt) > int(maxamt) or int(tme) < 0:
return jsonify({"ERROR": "FORM ERROR"}), 401
#If id that means its a link being edited, delet the record first
if "id" in data:
unid = data["id"].split("-")
uni = unid[1]
print(data["id"])
print(uni)
with FauDatabase() as Faudb:
Faudb.execute("DELETE FROM withdraws WHERE uni = ?", (unid[1],))
else:
uni = uuid.uuid4().hex
# Randomiser for random QR option
rand = ""
if uniq > 0:
for x in range(0,int(amt)):
rand += uuid.uuid4().hex[0:5] + ","
else:
rand = uuid.uuid4().hex[0:5] + ","
with Database() as dbb:
user_wallets = dbb.fetchall("SELECT * FROM wallets WHERE user = ? AND id = ?", (usr,wall[1],))
if not user_wallets:
return jsonify({"ERROR": "NO WALLET USER"}), 401
# Get time
dt = datetime.now()
seconds = dt.timestamp()
print(seconds)
#Add to DB
with FauDatabase() as db:
db.execute(
"INSERT OR IGNORE INTO withdraws (usr, wal, walnme, adm, uni, tit, maxamt, minamt, spent, inc, tme, uniq, withdrawals, tmestmp, rand) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
(
usr,
wall[1],
user_wallets[0][1],
user_wallets[0][3],
uni,
tit,
maxamt,
minamt,
0,
amt,
tme,
uniq,
0,
seconds,
rand,
),
)
#Get updated records
with ExtDatabase() as Extdd:
user_ext = Extdd.fetchall("SELECT * FROM overview WHERE user = ?", (usr,))
if not user_ext:
return jsonify({"ERROR": "NO WALLET USER"}), 401
with FauDatabase() as Faudb:
user_fau = Faudb.fetchall("SELECT * FROM withdraws WHERE usr = ?", (usr,))
if not user_fau:
return jsonify({"ERROR": "NO WALLET USER"}), 401
return render_template(
"withdraw.html", user_wallets=user_wallets, user=usr, user_ext=user_ext, user_fau=user_fau
)
#Simple shareable link
@app.route("/displaywithdraw", methods=["GET", "POST"])
def displaywithdraw():
fauid = request.args.get("id")
with FauDatabase() as Faudb:
user_fau = Faudb.fetchall("SELECT * FROM withdraws WHERE uni = ?", (fauid,))
return render_template(
"displaywithdraw.html", user_fau=user_fau,
)
#Simple printable page of links
@app.route("/printwithdraw/<urlstr>/", methods=["GET", "POST"])
def printwithdraw(urlstr):
fauid = request.args.get("id")
with FauDatabase() as Faudb:
user_fau = Faudb.fetchall("SELECT * FROM withdraws WHERE uni = ?", (fauid,))
randar = user_fau[0][15].split(",")
randar = randar[:-1]
lnurlar = []
print(len(randar))
for d in range(len(randar)):
lnurlar.append( encode("https://"+ urlstr +"/v1/lnurlfetch/" + urlstr + "/" + fauid + "/" + randar[d]))
return render_template(
"printwithdraws.html", lnurlar=lnurlar, user_fau=user_fau[0],
)
return render_template("extensions.html", user_wallets=user_wallets, user=usr, user_ext=user_ext)