mirror of
https://github.com/lnbits/lnbits.git
synced 2025-06-20 05:42:18 +02:00
feat: paywall extension
This commit is contained in:
parent
fd4dc6c48f
commit
403385c205
@ -11,10 +11,10 @@ def create_paywall(*, wallet_id: str, url: str, memo: str, amount: int) -> Paywa
|
|||||||
paywall_id = urlsafe_short_hash()
|
paywall_id = urlsafe_short_hash()
|
||||||
db.execute(
|
db.execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO paywalls (id, wallet, url, memo, amount)
|
INSERT INTO paywalls (id, wallet, secret, url, memo, amount)
|
||||||
VALUES (?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
""",
|
""",
|
||||||
(paywall_id, wallet_id, url, memo, amount),
|
(paywall_id, wallet_id, urlsafe_short_hash(), url, memo, amount),
|
||||||
)
|
)
|
||||||
|
|
||||||
return get_paywall(paywall_id)
|
return get_paywall(paywall_id)
|
||||||
|
@ -9,6 +9,7 @@ def m001_initial(db):
|
|||||||
CREATE TABLE IF NOT EXISTS paywalls (
|
CREATE TABLE IF NOT EXISTS paywalls (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
wallet TEXT NOT NULL,
|
wallet TEXT NOT NULL,
|
||||||
|
secret TEXT NOT NULL,
|
||||||
url TEXT NOT NULL,
|
url TEXT NOT NULL,
|
||||||
memo TEXT NOT NULL,
|
memo TEXT NOT NULL,
|
||||||
amount INTEGER NOT NULL,
|
amount INTEGER NOT NULL,
|
||||||
@ -18,5 +19,5 @@ def m001_initial(db):
|
|||||||
|
|
||||||
|
|
||||||
def migrate():
|
def migrate():
|
||||||
with open_ext_db("tpos") as db:
|
with open_ext_db("paywall") as db:
|
||||||
m001_initial(db)
|
m001_initial(db)
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
|
from hashlib import sha256
|
||||||
from typing import NamedTuple
|
from typing import NamedTuple
|
||||||
|
|
||||||
|
|
||||||
class Paywall(NamedTuple):
|
class Paywall(NamedTuple):
|
||||||
id: str
|
id: str
|
||||||
wallet: str
|
wallet: str
|
||||||
|
secret: str
|
||||||
url: str
|
url: str
|
||||||
memo: str
|
memo: str
|
||||||
amount: int
|
amount: int
|
||||||
time: int
|
time: int
|
||||||
|
|
||||||
|
def key_for(self, fingerprint: str) -> str:
|
||||||
|
return sha256(f"{self.secret}{fingerprint}".encode("utf-8")).hexdigest()
|
||||||
|
4
lnbits/extensions/paywall/static/vendor/fingerprintjs2@2.1.0/fingerprint2.min.js
vendored
Normal file
4
lnbits/extensions/paywall/static/vendor/fingerprintjs2@2.1.0/fingerprint2.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -4,13 +4,6 @@
|
|||||||
label="API info"
|
label="API info"
|
||||||
:content-inset-level="0.5"
|
:content-inset-level="0.5"
|
||||||
>
|
>
|
||||||
<q-expansion-item group="api" dense expand-separator label="Create a paywall">
|
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</q-expansion-item>
|
|
||||||
<q-expansion-item group="api" dense expand-separator label="List paywalls">
|
<q-expansion-item group="api" dense expand-separator label="List paywalls">
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
@ -18,7 +11,14 @@
|
|||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-expansion-item>
|
</q-expansion-item>
|
||||||
<q-expansion-item group="api" dense expand-separator label="Delete a paywall">
|
<q-expansion-item group="api" dense expand-separator label="Create a paywall">
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</q-expansion-item>
|
||||||
|
<q-expansion-item group="api" dense expand-separator label="Delete a paywall" class="q-pb-md">
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
|
|
||||||
|
114
lnbits/extensions/paywall/templates/paywall/display.html
Normal file
114
lnbits/extensions/paywall/templates/paywall/display.html
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
{% extends "public.html" %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block page %}
|
||||||
|
<div class="row q-col-gutter-md justify-center">
|
||||||
|
<div class="col-12 col-sm-6 col-md-5 col-lg-4">
|
||||||
|
<q-card class="q-pa-lg">
|
||||||
|
<q-card-section class="q-pa-none">
|
||||||
|
<q-responsive v-if="pr" :ratio="1" class="q-mx-xl q-mb-md">
|
||||||
|
<qrcode v-if="pr" :value="pr" :options="{width: 800}" class="rounded-borders"></qrcode>
|
||||||
|
</q-responsive>
|
||||||
|
<div v-if="redirectUrl">
|
||||||
|
<p>You can access the URL behind this paywall:<br>
|
||||||
|
<strong>{% raw %}{{ redirectUrl }}{% endraw %}</strong></p>
|
||||||
|
<div class="row q-mt-lg">
|
||||||
|
<q-btn outline color="grey" type="a" :href="redirectUrl">Open URL</q-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-6 col-md-5 col-lg-4 q-gutter-y-md">
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
<h6 class="text-subtitle1 q-mb-sm q-mt-none">LNbits paywall</h6>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script src="{{ url_for('paywall.static', filename='vendor/fingerprintjs2@2.1.0/fingerprint2.min.js') }}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
|
||||||
|
<script>
|
||||||
|
Vue.component(VueQrcode.name, VueQrcode);
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
el: '#vue',
|
||||||
|
mixins: [windowMixin],
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
pr: null,
|
||||||
|
fingerprint: {
|
||||||
|
hash: null,
|
||||||
|
isValid: false
|
||||||
|
},
|
||||||
|
redirectUrl: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getInvoice: function () {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
axios.get(
|
||||||
|
'/paywall/api/v1/paywalls/{{ paywall.id }}/invoice'
|
||||||
|
).then(function (response) {
|
||||||
|
self.pr = response.data.payment_request;
|
||||||
|
|
||||||
|
dismissMsg = self.$q.notify({
|
||||||
|
timeout: 0,
|
||||||
|
message: 'Waiting for payment...'
|
||||||
|
});
|
||||||
|
|
||||||
|
paymentChecker = setInterval(function () {
|
||||||
|
axios.post(
|
||||||
|
'/paywall/api/v1/paywalls/{{ paywall.id }}/check_invoice',
|
||||||
|
{checking_id: response.data.checking_id, fingerprint: self.fingerprint.hash}
|
||||||
|
).then(function (res) {
|
||||||
|
if (res.data.paid) {
|
||||||
|
clearInterval(paymentChecker);
|
||||||
|
dismissMsg();
|
||||||
|
self.redirectUrl = res.data.url;
|
||||||
|
self.$q.localStorage.set('lnbits.paywall.{{ paywall.id }}', res.data.key);
|
||||||
|
|
||||||
|
self.$q.notify({
|
||||||
|
type: 'positive',
|
||||||
|
message: 'Payment received!',
|
||||||
|
icon: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created: function () {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
Fingerprint2.get(function (components) {
|
||||||
|
self.fingerprint.hash = Fingerprint2.x64hash128(JSON.stringify(components));
|
||||||
|
|
||||||
|
var key = self.$q.localStorage.getItem('lnbits.paywall.{{ paywall.id }}');
|
||||||
|
|
||||||
|
if (key) {
|
||||||
|
axios.post(
|
||||||
|
'/paywall/api/v1/paywalls/{{ paywall.id }}/check_access',
|
||||||
|
{key: key, fingerprint: self.fingerprint.hash}
|
||||||
|
).then(function (response) {
|
||||||
|
if (response.data.valid) {
|
||||||
|
self.fingerprint.isValid = true;
|
||||||
|
self.redirectUrl = response.data.url;
|
||||||
|
} else {
|
||||||
|
self.getInvoice();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self.getInvoice();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
@ -8,7 +8,7 @@
|
|||||||
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
|
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<q-btn unelevated color="deep-purple" @click="paywallDialog.show = true">New Paywall</q-btn>
|
<q-btn unelevated color="deep-purple" @click="formDialog.show = true">New paywall</q-btn>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
|
||||||
@ -44,7 +44,7 @@
|
|||||||
<template v-slot:body="props">
|
<template v-slot:body="props">
|
||||||
<q-tr :props="props">
|
<q-tr :props="props">
|
||||||
<q-td auto-width>
|
<q-td auto-width>
|
||||||
<q-btn unelevated dense size="xs" icon="vpn_lock" :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'" type="a" :href="props.row.wall" target="_blank"></q-btn>
|
<q-btn unelevated dense size="xs" icon="launch" :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'" type="a" :href="props.row.displayUrl" target="_blank"></q-btn>
|
||||||
<q-btn unelevated dense size="xs" icon="link" :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'" type="a" :href="props.row.url" target="_blank"></q-btn>
|
<q-btn unelevated dense size="xs" icon="link" :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'" type="a" :href="props.row.url" target="_blank"></q-btn>
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td
|
<q-td
|
||||||
@ -79,27 +79,27 @@
|
|||||||
</q-card>
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<q-dialog v-model="paywallDialog.show" position="top">
|
<q-dialog v-model="formDialog.show" position="top">
|
||||||
<q-card class="q-pa-lg q-pt-xl" style="width: 500px">
|
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
||||||
<q-form @submit="createPaywall" class="q-gutter-md">
|
<q-form @submit="createPaywall" class="q-gutter-md">
|
||||||
<q-select filled dense emit-value v-model="paywallDialog.data.wallet" :options="g.user.walletOptions" label="Wallet *">
|
<q-select filled dense emit-value v-model="formDialog.data.wallet" :options="g.user.walletOptions" label="Wallet *">
|
||||||
</q-select>
|
</q-select>
|
||||||
<q-input filled dense
|
<q-input filled dense
|
||||||
v-model.trim="paywallDialog.data.url"
|
v-model.trim="formDialog.data.url"
|
||||||
type="url"
|
type="url"
|
||||||
label="Target URL *"></q-input>
|
label="Target URL *"></q-input>
|
||||||
<q-input filled dense
|
<q-input filled dense
|
||||||
v-model.number="paywallDialog.data.amount"
|
v-model.number="formDialog.data.amount"
|
||||||
type="number"
|
type="number"
|
||||||
label="Amount *"></q-input>
|
label="Amount (sat) *"></q-input>
|
||||||
<q-input filled dense
|
<q-input filled dense
|
||||||
v-model.trim="paywallDialog.data.memo"
|
v-model.trim="formDialog.data.memo"
|
||||||
label="Memo"
|
label="Memo"
|
||||||
placeholder="LNbits invoice"></q-input>
|
placeholder="LNbits invoice"></q-input>
|
||||||
<div class="row q-mt-lg">
|
<div class="row q-mt-lg">
|
||||||
<q-btn unelevated
|
<q-btn unelevated
|
||||||
color="deep-purple"
|
color="deep-purple"
|
||||||
:disable="paywallDialog.data.amount == null || paywallDialog.data.amount < 0 || paywallDialog.data.url == null"
|
:disable="formDialog.data.amount == null || formDialog.data.amount < 0 || formDialog.data.url == null"
|
||||||
type="submit">Create paywall</q-btn>
|
type="submit">Create paywall</q-btn>
|
||||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
|
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
|
||||||
</div>
|
</div>
|
||||||
@ -115,7 +115,7 @@
|
|||||||
var mapPaywall = function (obj) {
|
var mapPaywall = function (obj) {
|
||||||
obj.date = Quasar.utils.date.formatDate(new Date(obj.time * 1000), 'YYYY-MM-DD HH:mm');
|
obj.date = Quasar.utils.date.formatDate(new Date(obj.time * 1000), 'YYYY-MM-DD HH:mm');
|
||||||
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.amount);
|
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.amount);
|
||||||
obj.wall = ['/paywall/', obj.id].join('');
|
obj.displayUrl = ['/paywall/', obj.id].join('');
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +141,7 @@
|
|||||||
rowsPerPage: 10
|
rowsPerPage: 10
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
paywallDialog: {
|
formDialog: {
|
||||||
show: false,
|
show: false,
|
||||||
data: {}
|
data: {}
|
||||||
}
|
}
|
||||||
@ -163,23 +163,21 @@
|
|||||||
},
|
},
|
||||||
createPaywall: function () {
|
createPaywall: function () {
|
||||||
var data = {
|
var data = {
|
||||||
url: this.paywallDialog.data.url,
|
url: this.formDialog.data.url,
|
||||||
memo: this.paywallDialog.data.memo,
|
memo: this.formDialog.data.memo,
|
||||||
amount: this.paywallDialog.data.amount
|
amount: this.formDialog.data.amount
|
||||||
};
|
};
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
console.log(this.paywallDialog.data.wallet);
|
|
||||||
|
|
||||||
LNbits.api.request(
|
LNbits.api.request(
|
||||||
'POST',
|
'POST',
|
||||||
'/paywall/api/v1/paywalls',
|
'/paywall/api/v1/paywalls',
|
||||||
_.findWhere(this.g.user.wallets, {id: this.paywallDialog.data.wallet}).inkey,
|
_.findWhere(this.g.user.wallets, {id: this.formDialog.data.wallet}).inkey,
|
||||||
data
|
data
|
||||||
).then(function (response) {
|
).then(function (response) {
|
||||||
self.paywalls.push(mapPaywall(response.data));
|
self.paywalls.push(mapPaywall(response.data));
|
||||||
self.paywallDialog.show = false;
|
self.formDialog.show = false;
|
||||||
self.paywallDialog.data = {};
|
self.formDialog.data = {};
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
LNbits.utils.notifyApiError(error);
|
LNbits.utils.notifyApiError(error);
|
||||||
});
|
});
|
||||||
@ -189,7 +187,7 @@
|
|||||||
var paywall = _.findWhere(this.paywalls, {id: paywallId});
|
var paywall = _.findWhere(this.paywalls, {id: paywallId});
|
||||||
|
|
||||||
this.$q.dialog({
|
this.$q.dialog({
|
||||||
message: 'Are you sure you want to delete this Paywall link?',
|
message: 'Are you sure you want to delete this paywall link?',
|
||||||
ok: {
|
ok: {
|
||||||
flat: true,
|
flat: true,
|
||||||
color: 'orange'
|
color: 'orange'
|
||||||
|
@ -1 +0,0 @@
|
|||||||
{{ paywall.url }}
|
|
@ -1,9 +1,9 @@
|
|||||||
from flask import g, abort, render_template
|
from flask import g, abort, render_template
|
||||||
|
|
||||||
from lnbits.decorators import check_user_exists, validate_uuids
|
from lnbits.decorators import check_user_exists, validate_uuids
|
||||||
from lnbits.extensions.paywall import paywall_ext
|
|
||||||
from lnbits.helpers import Status
|
from lnbits.helpers import Status
|
||||||
|
|
||||||
|
from lnbits.extensions.paywall import paywall_ext
|
||||||
from .crud import get_paywall
|
from .crud import get_paywall
|
||||||
|
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ def index():
|
|||||||
|
|
||||||
|
|
||||||
@paywall_ext.route("/<paywall_id>")
|
@paywall_ext.route("/<paywall_id>")
|
||||||
def wall(paywall_id):
|
def display(paywall_id):
|
||||||
paywall = get_paywall(paywall_id) or abort(Status.NOT_FOUND, "Paywall does not exist.")
|
paywall = get_paywall(paywall_id) or abort(Status.NOT_FOUND, "Paywall does not exist.")
|
||||||
|
|
||||||
return render_template("paywall/wall.html", paywall=paywall)
|
return render_template("paywall/display.html", paywall=paywall)
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
from flask import g, jsonify, request
|
from flask import g, jsonify, request
|
||||||
|
|
||||||
from lnbits.core.crud import get_user
|
from lnbits.core.crud import get_user, get_wallet
|
||||||
|
from lnbits.core.services import create_invoice
|
||||||
from lnbits.decorators import api_check_wallet_key, api_validate_post_request
|
from lnbits.decorators import api_check_wallet_key, api_validate_post_request
|
||||||
from lnbits.helpers import Status
|
from lnbits.helpers import Status
|
||||||
|
from lnbits.settings import WALLET
|
||||||
|
|
||||||
from lnbits.extensions.paywall import paywall_ext
|
from lnbits.extensions.paywall import paywall_ext
|
||||||
from .crud import create_paywall, get_paywall, get_paywalls, delete_paywall
|
from .crud import create_paywall, get_paywall, get_paywalls, delete_paywall
|
||||||
@ -21,11 +23,13 @@ def api_paywalls():
|
|||||||
|
|
||||||
@paywall_ext.route("/api/v1/paywalls", methods=["POST"])
|
@paywall_ext.route("/api/v1/paywalls", methods=["POST"])
|
||||||
@api_check_wallet_key("invoice")
|
@api_check_wallet_key("invoice")
|
||||||
@api_validate_post_request(schema={
|
@api_validate_post_request(
|
||||||
"url": {"type": "string", "empty": False, "required": True},
|
schema={
|
||||||
"memo": {"type": "string", "empty": False, "required": True},
|
"url": {"type": "string", "empty": False, "required": True},
|
||||||
"amount": {"type": "integer", "min": 0, "required": True},
|
"memo": {"type": "string", "empty": False, "required": True},
|
||||||
})
|
"amount": {"type": "integer", "min": 0, "required": True},
|
||||||
|
}
|
||||||
|
)
|
||||||
def api_paywall_create():
|
def api_paywall_create():
|
||||||
paywall = create_paywall(wallet_id=g.wallet.id, **g.data)
|
paywall = create_paywall(wallet_id=g.wallet.id, **g.data)
|
||||||
|
|
||||||
@ -46,3 +50,64 @@ def api_paywall_delete(paywall_id):
|
|||||||
delete_paywall(paywall_id)
|
delete_paywall(paywall_id)
|
||||||
|
|
||||||
return "", Status.NO_CONTENT
|
return "", Status.NO_CONTENT
|
||||||
|
|
||||||
|
|
||||||
|
@paywall_ext.route("/api/v1/paywalls/<paywall_id>/invoice", methods=["GET"])
|
||||||
|
def api_paywall_get_invoice(paywall_id):
|
||||||
|
paywall = get_paywall(paywall_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
checking_id, payment_request = create_invoice(
|
||||||
|
wallet_id=paywall.wallet, amount=paywall.amount, memo=paywall.memo
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"message": str(e)}), Status.INTERNAL_SERVER_ERROR
|
||||||
|
|
||||||
|
return jsonify({"checking_id": checking_id, "payment_request": payment_request}), Status.OK
|
||||||
|
|
||||||
|
|
||||||
|
@paywall_ext.route("/api/v1/paywalls/<paywall_id>/check_invoice", methods=["POST"])
|
||||||
|
@api_validate_post_request(
|
||||||
|
schema={
|
||||||
|
"checking_id": {"type": "string", "empty": False, "required": True},
|
||||||
|
"fingerprint": {"type": "string", "empty": False, "required": True},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def api_paywal_check_invoice(paywall_id):
|
||||||
|
paywall = get_paywall(paywall_id)
|
||||||
|
|
||||||
|
if not paywall:
|
||||||
|
return jsonify({"message": "Paywall does not exist."}), Status.NOT_FOUND
|
||||||
|
|
||||||
|
try:
|
||||||
|
is_paid = not WALLET.get_invoice_status(g.data["checking_id"]).pending
|
||||||
|
except Exception:
|
||||||
|
return jsonify({"paid": False}), Status.OK
|
||||||
|
|
||||||
|
if is_paid:
|
||||||
|
wallet = get_wallet(paywall.wallet)
|
||||||
|
payment = wallet.get_payment(g.data["checking_id"])
|
||||||
|
payment.set_pending(False)
|
||||||
|
|
||||||
|
return jsonify({"paid": True, "key": paywall.key_for(g.data["fingerprint"]), "url": paywall.url}), Status.OK
|
||||||
|
|
||||||
|
return jsonify({"paid": False}), Status.OK
|
||||||
|
|
||||||
|
|
||||||
|
@paywall_ext.route("/api/v1/paywalls/<paywall_id>/check_access", methods=["POST"])
|
||||||
|
@api_validate_post_request(
|
||||||
|
schema={
|
||||||
|
"key": {"type": "string", "empty": False, "required": True},
|
||||||
|
"fingerprint": {"type": "string", "empty": False, "required": True},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def api_fingerprint_check(paywall_id):
|
||||||
|
paywall = get_paywall(paywall_id)
|
||||||
|
|
||||||
|
if not paywall:
|
||||||
|
return jsonify({"message": "Paywall does not exist."}), Status.NOT_FOUND
|
||||||
|
|
||||||
|
if paywall.key_for(g.data["fingerprint"]) != g.data["key"]:
|
||||||
|
return jsonify({"valid": False}), Status.OK
|
||||||
|
|
||||||
|
return jsonify({"valid": True, "url": paywall.url}), Status.OK
|
||||||
|
Loading…
x
Reference in New Issue
Block a user