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:
+54
-311
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user