From f38492a9209f27cf587946c0359c5ae047d9463c Mon Sep 17 00:00:00 2001 From: benarc Date: Mon, 7 Feb 2022 13:22:43 +0000 Subject: [PATCH 01/14] Added nostr ext --- lnbits/extensions/nostr/README.md | 3 + lnbits/extensions/nostr/__init__.py | 17 + lnbits/extensions/nostr/config.json | 6 + lnbits/extensions/nostr/crud.py | 134 +++++ lnbits/extensions/nostr/migrations.py | 47 ++ lnbits/extensions/nostr/models.py | 34 ++ .../templates/lnurldevice/_api_docs.html | 169 ++++++ .../nostr/templates/lnurldevice/error.html | 34 ++ .../nostr/templates/lnurldevice/index.html | 534 ++++++++++++++++++ .../nostr/templates/lnurldevice/paid.html | 27 + lnbits/extensions/nostr/views.py | 24 + lnbits/extensions/nostr/views_api.py | 48 ++ 12 files changed, 1077 insertions(+) create mode 100644 lnbits/extensions/nostr/README.md create mode 100644 lnbits/extensions/nostr/__init__.py create mode 100644 lnbits/extensions/nostr/config.json create mode 100644 lnbits/extensions/nostr/crud.py create mode 100644 lnbits/extensions/nostr/migrations.py create mode 100644 lnbits/extensions/nostr/models.py create mode 100644 lnbits/extensions/nostr/templates/lnurldevice/_api_docs.html create mode 100644 lnbits/extensions/nostr/templates/lnurldevice/error.html create mode 100644 lnbits/extensions/nostr/templates/lnurldevice/index.html create mode 100644 lnbits/extensions/nostr/templates/lnurldevice/paid.html create mode 100644 lnbits/extensions/nostr/views.py create mode 100644 lnbits/extensions/nostr/views_api.py diff --git a/lnbits/extensions/nostr/README.md b/lnbits/extensions/nostr/README.md new file mode 100644 index 000000000..596cce9de --- /dev/null +++ b/lnbits/extensions/nostr/README.md @@ -0,0 +1,3 @@ +# Nostr + +Opens a Nostr daemon diff --git a/lnbits/extensions/nostr/__init__.py b/lnbits/extensions/nostr/__init__.py new file mode 100644 index 000000000..775960e3d --- /dev/null +++ b/lnbits/extensions/nostr/__init__.py @@ -0,0 +1,17 @@ +from fastapi import APIRouter + +from lnbits.db import Database +from lnbits.helpers import template_renderer + +db = Database("ext_nostr") + +nostr_ext: APIRouter = APIRouter(prefix="/nostr", tags=["nostr"]) + + +def nostr_renderer(): + return template_renderer(["lnbits/extensions/nostr/templates"]) + + +from .lnurl import * # noqa +from .views import * # noqa +from .views_api import * # noqa diff --git a/lnbits/extensions/nostr/config.json b/lnbits/extensions/nostr/config.json new file mode 100644 index 000000000..a32e39a1a --- /dev/null +++ b/lnbits/extensions/nostr/config.json @@ -0,0 +1,6 @@ +{ + "name": "Nostr", + "short_description": "Daemon for Nostr", + "icon": "swap_horizontal_circle", + "contributors": ["arcbtc"] +} diff --git a/lnbits/extensions/nostr/crud.py b/lnbits/extensions/nostr/crud.py new file mode 100644 index 000000000..55e99ec67 --- /dev/null +++ b/lnbits/extensions/nostr/crud.py @@ -0,0 +1,134 @@ +from typing import List, Optional, Union + +from lnbits.helpers import urlsafe_short_hash + +from . import db +from .models import nostrKeys, nostrNotes, nostrRelays, nostrConnections + +###############KEYS################## + +async def create_nostrkeys( + data: nostrKeys +) -> nostrKeys: + nostrkey_id = urlsafe_short_hash() + await db.execute( + """ + INSERT INTO nostr.keys ( + id, + pubkey, + privkey + ) + VALUES (?, ?, ?) + """, + (nostrkey_id, data.pubkey, data.privkey), + ) + return await get_nostrkeys(nostrkey_id) + +async def get_nostrkeys(nostrkey_id: str) -> nostrKeys: + row = await db.fetchone( + "SELECT * FROM nostr.keys WHERE id = ?", + (lnurldevicepayment_id,), + ) + return nostrKeys(**row) if row else None + + +###############NOTES################## + +async def create_nostrnotes( + data: nostrNotes +) -> nostrNotes: + await db.execute( + """ + INSERT INTO nostr.notes ( + id, + pubkey, + created_at, + kind, + tags, + content, + sig + ) + VALUES (?, ?, ?, ?, ?, ?, ?) + """, + (data.id, data.pubkey, data.created_at, data.kind, data.tags, data.content, data.sig), + ) + return await get_nostrnotes(data.id) + +async def get_nostrnotes(nostrnote_id: str) -> nostrNotes: + row = await db.fetchone( + "SELECT * FROM nostr.notes WHERE id = ?", + (nostrnote_id,), + ) + return nostrNotes(**row) if row else None + +###############RELAYS################## + +async def create_nostrrelays( + relay: str +) -> nostrRelays: + nostrrelay_id = urlsafe_short_hash() + await db.execute( + """ + INSERT INTO nostr.relays ( + id, + relay + ) + VALUES (?, ?) + """, + (nostrrelay_id, relay), + ) + return await get_nostrnotes(nostrrelay_id) + +async def get_nostrrelays(nostrrelay_id: str) -> nostrRelays: + row = await db.fetchone( + "SELECT * FROM nostr.relays WHERE id = ?", + (nostrnote_id,), + ) + return nostrRelays(**row) if row else None + + +###############CONNECTIONS################## + +async def create_nostrconnections( + data: nostrNotes +) -> nostrNotes: + nostrkey_id = urlsafe_short_hash() + await db.execute( + """ + INSERT INTO nostr.notes ( + id, + pubkey, + created_at, + kind, + tags, + content, + sig + ) + VALUES (?, ?, ?, ?, ?, ?, ?) + """, + (data.id, data.pubkey, data.created_at, data.kind, data.tags, data.content, data.sig), + ) + return await get_nostrnotes(data.id) + +async def get_nostrnotes(nostrnote_id: str) -> nostrNotes: + row = await db.fetchone( + "SELECT * FROM nostr.notes WHERE id = ?", + (nostrnote_id,), + ) + return nostrNotes(**row) if row else None + + + +async def update_lnurldevicepayment( + lnurldevicepayment_id: str, **kwargs +) -> Optional[lnurldevicepayment]: + q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) + await db.execute( + f"UPDATE lnurldevice.lnurldevicepayment SET {q} WHERE id = ?", + (*kwargs.values(), lnurldevicepayment_id), + ) + row = await db.fetchone( + "SELECT * FROM lnurldevice.lnurldevicepayment WHERE id = ?", + (lnurldevicepayment_id,), + ) + return lnurldevicepayment(**row) if row else None \ No newline at end of file diff --git a/lnbits/extensions/nostr/migrations.py b/lnbits/extensions/nostr/migrations.py new file mode 100644 index 000000000..de32a5787 --- /dev/null +++ b/lnbits/extensions/nostr/migrations.py @@ -0,0 +1,47 @@ +from lnbits.db import Database + +db2 = Database("ext_nostr") + + +async def m001_initial(db): + """ + Initial nostr table. + """ + await db.execute( + f""" + CREATE TABLE nostr.keys ( + pubkey TEXT NOT NULL PRIMARY KEY, + privkey TEXT NOT NULL + ); + """ + ) + await db.execute( + f""" + CREATE TABLE nostr.notes ( + id TEXT NOT NULL PRIMARY KEY, + pubkey TEXT NOT NULL, + created_at TEXT NOT NULL, + kind INT NOT NULL, + tags TEXT NOT NULL, + content TEXT NOT NULL, + sig TEXT NOT NULL, + ); + """ + ) + await db.execute( + f""" + CREATE TABLE nostr.relays ( + id TEXT NOT NULL PRIMARY KEY, + relay TEXT NOT NULL + ); + """ + ) + await db.execute( + f""" + CREATE TABLE nostr.connections ( + id TEXT NOT NULL PRIMARY KEY, + publickey TEXT NOT NULL, + relayid TEXT NOT NULL + ); + """ + ) \ No newline at end of file diff --git a/lnbits/extensions/nostr/models.py b/lnbits/extensions/nostr/models.py new file mode 100644 index 000000000..ba0c87a5e --- /dev/null +++ b/lnbits/extensions/nostr/models.py @@ -0,0 +1,34 @@ +import json +from sqlite3 import Row +from typing import Optional + +from fastapi import Request +from lnurl import Lnurl +from lnurl import encode as lnurl_encode # type: ignore +from lnurl.models import LnurlPaySuccessAction, UrlAction # type: ignore +from lnurl.types import LnurlPayMetadata # type: ignore +from pydantic import BaseModel +from pydantic.main import BaseModel + +class nostrKeys(BaseModel): + id: str + pubkey: str + privkey: str + +class nostrNotes(BaseModel): + id: str + pubkey: str + created_at: str + kind: int + tags: str + content: str + sig: str + +class nostrRelays(BaseModel): + id: str + relay: str + +class nostrConnections(BaseModel): + id: str + pubkey: str + relayid: str \ No newline at end of file diff --git a/lnbits/extensions/nostr/templates/lnurldevice/_api_docs.html b/lnbits/extensions/nostr/templates/lnurldevice/_api_docs.html new file mode 100644 index 000000000..af69b76e3 --- /dev/null +++ b/lnbits/extensions/nostr/templates/lnurldevice/_api_docs.html @@ -0,0 +1,169 @@ + + +

+ Register LNURLDevice devices to receive payments in your LNbits wallet.
+ Build your own here + https://github.com/arcbtc/bitcoinpos
+ + Created by, Ben Arc +

+
+ + + + + /lnurldevice/api/v1/lnurlpos +
Headers
+ {"X-Api-Key": <admin_key>}
+
+ Body (application/json) +
+
+ Returns 200 OK (application/json) +
+ [<lnurldevice_object>, ...] +
Curl example
+ curl -X POST {{ request.base_url }}api/v1/lnurldevice -d '{"title": + <string>, "message":<string>, "currency": + <integer>}' -H "Content-type: application/json" -H "X-Api-Key: + {{user.wallets[0].adminkey }}" + +
+
+
+ + + + PUT + /lnurldevice/api/v1/lnurlpos/<lnurldevice_id> +
Headers
+ {"X-Api-Key": <admin_key>}
+
+ Body (application/json) +
+
+ Returns 200 OK (application/json) +
+ [<lnurldevice_object>, ...] +
Curl example
+ curl -X POST {{ request.base_url + }}api/v1/lnurlpos/<lnurldevice_id> -d ''{"title": + <string>, "message":<string>, "currency": + <integer>} -H "Content-type: application/json" -H "X-Api-Key: + {{user.wallets[0].adminkey }}" + +
+
+
+ + + + + GET + /lnurldevice/api/v1/lnurlpos/<lnurldevice_id> +
Headers
+ {"X-Api-Key": <invoice_key>}
+
+ Body (application/json) +
+
+ Returns 200 OK (application/json) +
+ [<lnurldevice_object>, ...] +
Curl example
+ curl -X GET {{ request.base_url + }}api/v1/lnurlpos/<lnurldevice_id> -H "X-Api-Key: {{ + user.wallets[0].inkey }}" + +
+
+
+ + + + GET + /lnurldevice/api/v1/lnurlposs +
Headers
+ {"X-Api-Key": <invoice_key>}
+
+ Body (application/json) +
+
+ Returns 200 OK (application/json) +
+ [<lnurldevice_object>, ...] +
Curl example
+ curl -X GET {{ request.base_url }}api/v1/lnurldevices -H + "X-Api-Key: {{ user.wallets[0].inkey }}" + +
+
+
+ + + + DELETE + /lnurldevice/api/v1/lnurlpos/<lnurldevice_id> +
Headers
+ {"X-Api-Key": <admin_key>}
+
Returns 204 NO CONTENT
+ +
Curl example
+ curl -X DELETE {{ request.base_url + }}api/v1/lnurlpos/<lnurldevice_id> -H "X-Api-Key: {{ + user.wallets[0].adminkey }}" + +
+
+
+
+
diff --git a/lnbits/extensions/nostr/templates/lnurldevice/error.html b/lnbits/extensions/nostr/templates/lnurldevice/error.html new file mode 100644 index 000000000..d8e418329 --- /dev/null +++ b/lnbits/extensions/nostr/templates/lnurldevice/error.html @@ -0,0 +1,34 @@ +{% extends "public.html" %} {% block page %} +
+
+ + +
+

LNURL-pay not paid

+
+ + +
+
+
+
+
+ + {% endblock %} {% block scripts %} + + + + {% endblock %} +
diff --git a/lnbits/extensions/nostr/templates/lnurldevice/index.html b/lnbits/extensions/nostr/templates/lnurldevice/index.html new file mode 100644 index 000000000..b51e25568 --- /dev/null +++ b/lnbits/extensions/nostr/templates/lnurldevice/index.html @@ -0,0 +1,534 @@ +{% extends "base.html" %} {% from "macros.jinja" import window_vars with context +%} {% block page %} +
+
+ + + {% raw %} + New LNURLDevice instance + + + + + + +
+
+
lNURLdevice
+
+ +
+ + + + Export to CSV +
+
+ + + + + {% endraw %} + +
+
+
+ +
+ + +
+ {{SITE_TITLE}} LNURLDevice Extension +
+
+ + + {% include "lnurldevice/_api_docs.html" %} + +
+
+ + + +
LNURLDevice device string
+ {% raw + %}{{location}}/lnurldevice/api/v1/lnurl/{{settingsDialog.data.id}}, + {{settingsDialog.data.key}}, {{settingsDialog.data.currency}}{% endraw + %} Click to copy URL + + +
+ +
+
+
+ + + + + + + + + + + + +
+ Update lnurldevice + Create lnurldevice + Cancel +
+
+
+
+
+{% endblock %} {% block scripts %} {{ window_vars(user) }} + + +{% endblock %} diff --git a/lnbits/extensions/nostr/templates/lnurldevice/paid.html b/lnbits/extensions/nostr/templates/lnurldevice/paid.html new file mode 100644 index 000000000..c185ecce6 --- /dev/null +++ b/lnbits/extensions/nostr/templates/lnurldevice/paid.html @@ -0,0 +1,27 @@ +{% extends "public.html" %} {% block page %} +
+
+ + +
+

{{ pin }}

+
+
+
+
+
+ + {% endblock %} {% block scripts %} + + + + {% endblock %} +
diff --git a/lnbits/extensions/nostr/views.py b/lnbits/extensions/nostr/views.py new file mode 100644 index 000000000..1dfc07da7 --- /dev/null +++ b/lnbits/extensions/nostr/views.py @@ -0,0 +1,24 @@ +from http import HTTPStatus + +from fastapi import Request +from fastapi.param_functions import Query +from fastapi.params import Depends +from fastapi.templating import Jinja2Templates +from starlette.exceptions import HTTPException +from starlette.responses import HTMLResponse + +from lnbits.core.crud import update_payment_status +from lnbits.core.models import User +from lnbits.core.views.api import api_payment +from lnbits.decorators import check_user_exists + +from . import nostr_ext, nostr_renderer + +templates = Jinja2Templates(directory="templates") + + +@nostr_ext.get("/", response_class=HTMLResponse) +async def index(request: Request, user: User = Depends(check_user_exists)): + return nostr_renderer().TemplateResponse( + "nostr/index.html", {"request": request, "user": user.dict()} + ) diff --git a/lnbits/extensions/nostr/views_api.py b/lnbits/extensions/nostr/views_api.py new file mode 100644 index 000000000..a479cad8f --- /dev/null +++ b/lnbits/extensions/nostr/views_api.py @@ -0,0 +1,48 @@ +from http import HTTPStatus + +from fastapi import Request +from fastapi.param_functions import Query +from fastapi.params import Depends +from starlette.exceptions import HTTPException + +from lnbits.core.crud import get_user +from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key +from lnbits.extensions.lnurldevice import lnurldevice_ext +from lnbits.utils.exchange_rates import currencies + +from . import lnurldevice_ext +from .crud import ( + create_lnurldevice, + delete_lnurldevice, + get_lnurldevice, + get_lnurldevices, + update_lnurldevice, +) +from .models import createLnurldevice + + +@nostr_ext.get("/api/v1/lnurlpos") +async def api_check_daemon(wallet: WalletTypeInfo = Depends(get_key_type)): + wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids + try: + return [ + {**lnurldevice.dict()} for lnurldevice in await get_lnurldevices(wallet_ids) + ] + except: + return "" + +@nostr_ext.delete("/api/v1/lnurlpos/{lnurldevice_id}") +async def api_lnurldevice_delete( + wallet: WalletTypeInfo = Depends(require_admin_key), + lnurldevice_id: str = Query(None), +): + lnurldevice = await get_lnurldevice(lnurldevice_id) + + if not lnurldevice: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="Wallet link does not exist." + ) + + await delete_lnurldevice(lnurldevice_id) + + return "", HTTPStatus.NO_CONTENT \ No newline at end of file From da1025a625b75dc842efb2baf7582cfb5682a191 Mon Sep 17 00:00:00 2001 From: benarc Date: Mon, 7 Feb 2022 20:01:01 +0000 Subject: [PATCH 02/14] extension loading --- lnbits/extensions/nostr/__init__.py | 2 - lnbits/extensions/nostr/crud.py | 71 +-- lnbits/extensions/nostr/migrations.py | 2 +- lnbits/extensions/nostr/models.py | 12 +- .../templates/lnurldevice/_api_docs.html | 169 ------ .../nostr/templates/lnurldevice/error.html | 34 -- .../nostr/templates/lnurldevice/index.html | 534 ------------------ .../nostr/templates/lnurldevice/paid.html | 27 - .../nostr/templates/nostr/index.html | 159 ++++++ lnbits/extensions/nostr/views_api.py | 46 +- 10 files changed, 204 insertions(+), 852 deletions(-) delete mode 100644 lnbits/extensions/nostr/templates/lnurldevice/_api_docs.html delete mode 100644 lnbits/extensions/nostr/templates/lnurldevice/error.html delete mode 100644 lnbits/extensions/nostr/templates/lnurldevice/index.html delete mode 100644 lnbits/extensions/nostr/templates/lnurldevice/paid.html create mode 100644 lnbits/extensions/nostr/templates/nostr/index.html diff --git a/lnbits/extensions/nostr/__init__.py b/lnbits/extensions/nostr/__init__.py index 775960e3d..9774a2790 100644 --- a/lnbits/extensions/nostr/__init__.py +++ b/lnbits/extensions/nostr/__init__.py @@ -11,7 +11,5 @@ nostr_ext: APIRouter = APIRouter(prefix="/nostr", tags=["nostr"]) def nostr_renderer(): return template_renderer(["lnbits/extensions/nostr/templates"]) - -from .lnurl import * # noqa from .views import * # noqa from .views_api import * # noqa diff --git a/lnbits/extensions/nostr/crud.py b/lnbits/extensions/nostr/crud.py index 55e99ec67..0b30bc9a7 100644 --- a/lnbits/extensions/nostr/crud.py +++ b/lnbits/extensions/nostr/crud.py @@ -1,33 +1,31 @@ from typing import List, Optional, Union from lnbits.helpers import urlsafe_short_hash - +import shortuuid from . import db -from .models import nostrKeys, nostrNotes, nostrRelays, nostrConnections +from .models import nostrKeys, nostrNotes, nostrCreateRelays, nostrRelays, nostrConnections, nostrCreateConnections ###############KEYS################## async def create_nostrkeys( data: nostrKeys ) -> nostrKeys: - nostrkey_id = urlsafe_short_hash() await db.execute( """ INSERT INTO nostr.keys ( - id, pubkey, privkey ) - VALUES (?, ?, ?) + VALUES (?, ?) """, - (nostrkey_id, data.pubkey, data.privkey), + (data.pubkey, data.privkey), ) return await get_nostrkeys(nostrkey_id) -async def get_nostrkeys(nostrkey_id: str) -> nostrKeys: +async def get_nostrkeys(pubkey: str) -> nostrKeys: row = await db.fetchone( - "SELECT * FROM nostr.keys WHERE id = ?", - (lnurldevicepayment_id,), + "SELECT * FROM nostr.keys WHERE pubkey = ?", + (pubkey,), ) return nostrKeys(**row) if row else None @@ -64,9 +62,12 @@ async def get_nostrnotes(nostrnote_id: str) -> nostrNotes: ###############RELAYS################## async def create_nostrrelays( - relay: str -) -> nostrRelays: - nostrrelay_id = urlsafe_short_hash() + data: nostrCreateRelays +) -> nostrCreateRelays: + nostrrelay_id = shortuuid.uuid(name=relay) + + if await get_nostrrelays(nostrrelay_id): + return "error" await db.execute( """ INSERT INTO nostr.relays ( @@ -75,7 +76,7 @@ async def create_nostrrelays( ) VALUES (?, ?) """, - (nostrrelay_id, relay), + (nostrrelay_id, data.relay), ) return await get_nostrnotes(nostrrelay_id) @@ -90,45 +91,25 @@ async def get_nostrrelays(nostrrelay_id: str) -> nostrRelays: ###############CONNECTIONS################## async def create_nostrconnections( - data: nostrNotes -) -> nostrNotes: - nostrkey_id = urlsafe_short_hash() + data: nostrCreateConnections +) -> nostrCreateConnections: + nostrrelay_id = shortuuid.uuid(name=data.relayid + data.pubkey) await db.execute( """ - INSERT INTO nostr.notes ( + INSERT INTO nostr.connections ( id, pubkey, - created_at, - kind, - tags, - content, - sig + relayid ) - VALUES (?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?) """, - (data.id, data.pubkey, data.created_at, data.kind, data.tags, data.content, data.sig), + (data.id, data.pubkey, data.relayid), ) - return await get_nostrnotes(data.id) + return await get_nostrconnections(data.id) -async def get_nostrnotes(nostrnote_id: str) -> nostrNotes: +async def get_nostrconnections(nostrconnections_id: str) -> nostrConnections: row = await db.fetchone( - "SELECT * FROM nostr.notes WHERE id = ?", - (nostrnote_id,), + "SELECT * FROM nostr.connections WHERE id = ?", + (nostrconnections_id,), ) - return nostrNotes(**row) if row else None - - - -async def update_lnurldevicepayment( - lnurldevicepayment_id: str, **kwargs -) -> Optional[lnurldevicepayment]: - q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) - await db.execute( - f"UPDATE lnurldevice.lnurldevicepayment SET {q} WHERE id = ?", - (*kwargs.values(), lnurldevicepayment_id), - ) - row = await db.fetchone( - "SELECT * FROM lnurldevice.lnurldevicepayment WHERE id = ?", - (lnurldevicepayment_id,), - ) - return lnurldevicepayment(**row) if row else None \ No newline at end of file + return nostrConnections(**row) if row else None \ No newline at end of file diff --git a/lnbits/extensions/nostr/migrations.py b/lnbits/extensions/nostr/migrations.py index de32a5787..0c616110e 100644 --- a/lnbits/extensions/nostr/migrations.py +++ b/lnbits/extensions/nostr/migrations.py @@ -24,7 +24,7 @@ async def m001_initial(db): kind INT NOT NULL, tags TEXT NOT NULL, content TEXT NOT NULL, - sig TEXT NOT NULL, + sig TEXT NOT NULL ); """ ) diff --git a/lnbits/extensions/nostr/models.py b/lnbits/extensions/nostr/models.py index ba0c87a5e..e21e5d76f 100644 --- a/lnbits/extensions/nostr/models.py +++ b/lnbits/extensions/nostr/models.py @@ -3,15 +3,10 @@ from sqlite3 import Row from typing import Optional from fastapi import Request -from lnurl import Lnurl -from lnurl import encode as lnurl_encode # type: ignore -from lnurl.models import LnurlPaySuccessAction, UrlAction # type: ignore -from lnurl.types import LnurlPayMetadata # type: ignore from pydantic import BaseModel from pydantic.main import BaseModel class nostrKeys(BaseModel): - id: str pubkey: str privkey: str @@ -24,6 +19,13 @@ class nostrNotes(BaseModel): content: str sig: str +class nostrCreateRelays(BaseModel): + relay: str + +class nostrCreateConnections(BaseModel): + pubkey: str + relayid: str + class nostrRelays(BaseModel): id: str relay: str diff --git a/lnbits/extensions/nostr/templates/lnurldevice/_api_docs.html b/lnbits/extensions/nostr/templates/lnurldevice/_api_docs.html deleted file mode 100644 index af69b76e3..000000000 --- a/lnbits/extensions/nostr/templates/lnurldevice/_api_docs.html +++ /dev/null @@ -1,169 +0,0 @@ - - -

- Register LNURLDevice devices to receive payments in your LNbits wallet.
- Build your own here - https://github.com/arcbtc/bitcoinpos
- - Created by, Ben Arc -

-
- - - - - /lnurldevice/api/v1/lnurlpos -
Headers
- {"X-Api-Key": <admin_key>}
-
- Body (application/json) -
-
- Returns 200 OK (application/json) -
- [<lnurldevice_object>, ...] -
Curl example
- curl -X POST {{ request.base_url }}api/v1/lnurldevice -d '{"title": - <string>, "message":<string>, "currency": - <integer>}' -H "Content-type: application/json" -H "X-Api-Key: - {{user.wallets[0].adminkey }}" - -
-
-
- - - - PUT - /lnurldevice/api/v1/lnurlpos/<lnurldevice_id> -
Headers
- {"X-Api-Key": <admin_key>}
-
- Body (application/json) -
-
- Returns 200 OK (application/json) -
- [<lnurldevice_object>, ...] -
Curl example
- curl -X POST {{ request.base_url - }}api/v1/lnurlpos/<lnurldevice_id> -d ''{"title": - <string>, "message":<string>, "currency": - <integer>} -H "Content-type: application/json" -H "X-Api-Key: - {{user.wallets[0].adminkey }}" - -
-
-
- - - - - GET - /lnurldevice/api/v1/lnurlpos/<lnurldevice_id> -
Headers
- {"X-Api-Key": <invoice_key>}
-
- Body (application/json) -
-
- Returns 200 OK (application/json) -
- [<lnurldevice_object>, ...] -
Curl example
- curl -X GET {{ request.base_url - }}api/v1/lnurlpos/<lnurldevice_id> -H "X-Api-Key: {{ - user.wallets[0].inkey }}" - -
-
-
- - - - GET - /lnurldevice/api/v1/lnurlposs -
Headers
- {"X-Api-Key": <invoice_key>}
-
- Body (application/json) -
-
- Returns 200 OK (application/json) -
- [<lnurldevice_object>, ...] -
Curl example
- curl -X GET {{ request.base_url }}api/v1/lnurldevices -H - "X-Api-Key: {{ user.wallets[0].inkey }}" - -
-
-
- - - - DELETE - /lnurldevice/api/v1/lnurlpos/<lnurldevice_id> -
Headers
- {"X-Api-Key": <admin_key>}
-
Returns 204 NO CONTENT
- -
Curl example
- curl -X DELETE {{ request.base_url - }}api/v1/lnurlpos/<lnurldevice_id> -H "X-Api-Key: {{ - user.wallets[0].adminkey }}" - -
-
-
-
-
diff --git a/lnbits/extensions/nostr/templates/lnurldevice/error.html b/lnbits/extensions/nostr/templates/lnurldevice/error.html deleted file mode 100644 index d8e418329..000000000 --- a/lnbits/extensions/nostr/templates/lnurldevice/error.html +++ /dev/null @@ -1,34 +0,0 @@ -{% extends "public.html" %} {% block page %} -
-
- - -
-

LNURL-pay not paid

-
- - -
-
-
-
-
- - {% endblock %} {% block scripts %} - - - - {% endblock %} -
diff --git a/lnbits/extensions/nostr/templates/lnurldevice/index.html b/lnbits/extensions/nostr/templates/lnurldevice/index.html deleted file mode 100644 index b51e25568..000000000 --- a/lnbits/extensions/nostr/templates/lnurldevice/index.html +++ /dev/null @@ -1,534 +0,0 @@ -{% extends "base.html" %} {% from "macros.jinja" import window_vars with context -%} {% block page %} -
-
- - - {% raw %} - New LNURLDevice instance - - - - - - -
-
-
lNURLdevice
-
- -
- - - - Export to CSV -
-
- - - - - {% endraw %} - -
-
-
- -
- - -
- {{SITE_TITLE}} LNURLDevice Extension -
-
- - - {% include "lnurldevice/_api_docs.html" %} - -
-
- - - -
LNURLDevice device string
- {% raw - %}{{location}}/lnurldevice/api/v1/lnurl/{{settingsDialog.data.id}}, - {{settingsDialog.data.key}}, {{settingsDialog.data.currency}}{% endraw - %} Click to copy URL - - -
- -
-
-
- - - - - - - - - - - - -
- Update lnurldevice - Create lnurldevice - Cancel -
-
-
-
-
-{% endblock %} {% block scripts %} {{ window_vars(user) }} - - -{% endblock %} diff --git a/lnbits/extensions/nostr/templates/lnurldevice/paid.html b/lnbits/extensions/nostr/templates/lnurldevice/paid.html deleted file mode 100644 index c185ecce6..000000000 --- a/lnbits/extensions/nostr/templates/lnurldevice/paid.html +++ /dev/null @@ -1,27 +0,0 @@ -{% extends "public.html" %} {% block page %} -
-
- - -
-

{{ pin }}

-
-
-
-
-
- - {% endblock %} {% block scripts %} - - - - {% endblock %} -
diff --git a/lnbits/extensions/nostr/templates/nostr/index.html b/lnbits/extensions/nostr/templates/nostr/index.html new file mode 100644 index 000000000..f17d0243b --- /dev/null +++ b/lnbits/extensions/nostr/templates/nostr/index.html @@ -0,0 +1,159 @@ +{% extends "base.html" %} {% from "macros.jinja" import window_vars with context +%} {% block page %} +
+
+ + +
+
+
Nostr
+
+ +
+ + + + Export to CSV +
+
+ + {% raw %} + + + + {% endraw %} + +
+
+
+ +
+ + +
{{SITE_TITLE}} Nostr Extension
+ + Okay +
+
+
+
+{% endblock %} {% block scripts %} {{ window_vars(user) }} + + +{% endblock %} diff --git a/lnbits/extensions/nostr/views_api.py b/lnbits/extensions/nostr/views_api.py index a479cad8f..9e7ccfffa 100644 --- a/lnbits/extensions/nostr/views_api.py +++ b/lnbits/extensions/nostr/views_api.py @@ -7,42 +7,18 @@ from starlette.exceptions import HTTPException from lnbits.core.crud import get_user from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key -from lnbits.extensions.lnurldevice import lnurldevice_ext +from lnbits.extensions.nostr import nostr_ext from lnbits.utils.exchange_rates import currencies -from . import lnurldevice_ext +from . import nostr_ext from .crud import ( - create_lnurldevice, - delete_lnurldevice, - get_lnurldevice, - get_lnurldevices, - update_lnurldevice, + create_nostrkeys, + get_nostrkeys, + create_nostrnotes, + get_nostrnotes, + create_nostrrelays, + get_nostrrelays, + create_nostrconnections, + get_nostrconnections, ) -from .models import createLnurldevice - - -@nostr_ext.get("/api/v1/lnurlpos") -async def api_check_daemon(wallet: WalletTypeInfo = Depends(get_key_type)): - wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids - try: - return [ - {**lnurldevice.dict()} for lnurldevice in await get_lnurldevices(wallet_ids) - ] - except: - return "" - -@nostr_ext.delete("/api/v1/lnurlpos/{lnurldevice_id}") -async def api_lnurldevice_delete( - wallet: WalletTypeInfo = Depends(require_admin_key), - lnurldevice_id: str = Query(None), -): - lnurldevice = await get_lnurldevice(lnurldevice_id) - - if not lnurldevice: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Wallet link does not exist." - ) - - await delete_lnurldevice(lnurldevice_id) - - return "", HTTPStatus.NO_CONTENT \ No newline at end of file +from .models import nostrKeys \ No newline at end of file From 4b11342f0c55e8917cf978b9279c0708195759df Mon Sep 17 00:00:00 2001 From: benarc Date: Mon, 7 Feb 2022 20:43:47 +0000 Subject: [PATCH 03/14] works --- lnbits/decorators.py | 13 ++++++++++--- lnbits/helpers.py | 4 ++++ lnbits/settings.py | 1 + lnbits/static/js/base.js | 34 ++++++++++++++++++++++++++-------- 4 files changed, 41 insertions(+), 11 deletions(-) diff --git a/lnbits/decorators.py b/lnbits/decorators.py index fc92594ed..9eee1afae 100644 --- a/lnbits/decorators.py +++ b/lnbits/decorators.py @@ -13,7 +13,7 @@ from starlette.requests import Request from lnbits.core.crud import get_user, get_wallet_for_key from lnbits.core.models import User, Wallet from lnbits.requestvars import g -from lnbits.settings import LNBITS_ALLOWED_USERS, LNBITS_ADMIN_USERS +from lnbits.settings import LNBITS_ALLOWED_USERS, LNBITS_ADMIN_USERS, LNBITS_ADMIN_EXTENSIONS class KeyChecker(SecurityBase): @@ -122,6 +122,7 @@ async def get_key_type( # 0: admin # 1: invoice # 2: invalid + pathname = r['path'].split('/')[1] if not api_key_header and not api_key_query: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST) @@ -131,7 +132,10 @@ async def get_key_type( try: checker = WalletAdminKeyChecker(api_key=token) await checker.__call__(r) - return WalletTypeInfo(0, checker.wallet) + wallet = WalletTypeInfo(0, checker.wallet) + if (LNBITS_ADMIN_USERS and wallet.wallet.user not in LNBITS_ADMIN_USERS) and (LNBITS_ADMIN_EXTENSIONS and pathname in LNBITS_ADMIN_EXTENSIONS): + raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized.") + return wallet except HTTPException as e: if e.status_code == HTTPStatus.BAD_REQUEST: raise @@ -143,7 +147,10 @@ async def get_key_type( try: checker = WalletInvoiceKeyChecker(api_key=token) await checker.__call__(r) - return WalletTypeInfo(1, checker.wallet) + wallet = WalletTypeInfo(0, checker.wallet) + if (LNBITS_ADMIN_USERS and wallet.wallet.user not in LNBITS_ADMIN_USERS) and (LNBITS_ADMIN_EXTENSIONS and pathname in LNBITS_ADMIN_EXTENSIONS): + raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized.") + return wallet except HTTPException as e: if e.status_code == HTTPStatus.BAD_REQUEST: raise diff --git a/lnbits/helpers.py b/lnbits/helpers.py index dfd9d53de..a1c88389d 100644 --- a/lnbits/helpers.py +++ b/lnbits/helpers.py @@ -15,6 +15,7 @@ import lnbits.settings as settings class Extension(NamedTuple): code: str is_valid: bool + is_admin_only: bool name: Optional[str] = None short_description: Optional[str] = None icon: Optional[str] = None @@ -25,6 +26,7 @@ class Extension(NamedTuple): class ExtensionManager: def __init__(self): self._disabled: List[str] = settings.LNBITS_DISABLED_EXTENSIONS + self._admin_only: List[str] = [x.strip(' ') for x in settings.LNBITS_ADMIN_EXTENSIONS] self._extension_folders: List[str] = [ x[1] for x in os.walk(os.path.join(settings.LNBITS_PATH, "extensions")) ][0] @@ -47,6 +49,7 @@ class ExtensionManager: ) as json_file: config = json.load(json_file) is_valid = True + is_admin_only = True if extension in self._admin_only else False except Exception: config = {} is_valid = False @@ -55,6 +58,7 @@ class ExtensionManager: Extension( extension, is_valid, + is_admin_only, config.get("name"), config.get("short_description"), config.get("icon"), diff --git a/lnbits/settings.py b/lnbits/settings.py index b204e28f1..26699bc02 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -29,6 +29,7 @@ LNBITS_ALLOWED_USERS: List[str] = env.list( "LNBITS_ALLOWED_USERS", default=[], subcast=str ) LNBITS_ADMIN_USERS: List[str] = env.list("LNBITS_ADMIN_USERS", default=[], subcast=str) +LNBITS_ADMIN_EXTENSIONS: List[str] = env.list("LNBITS_ADMIN_EXTENSIONS", default=[], subcast=str) LNBITS_DISABLED_EXTENSIONS: List[str] = env.list( "LNBITS_DISABLED_EXTENSIONS", default=[], subcast=str ) diff --git a/lnbits/static/js/base.js b/lnbits/static/js/base.js index 13f683882..d49eec16f 100644 --- a/lnbits/static/js/base.js +++ b/lnbits/static/js/base.js @@ -111,7 +111,7 @@ window.LNbits = { '/wallet?' + (userId ? 'usr=' + userId + '&' : '') + 'nme=' + walletName }, updateWallet: function (walletName, userId, walletId) { - window.location.href = `/wallet?usr=${userId}&wal=${walletId}&nme=${walletName}` + window.location.href = `/wallet?usr=${userId}&wal=${walletId}&nme=${walletName}` }, deleteWallet: function (walletId, userId) { window.location.href = '/deletewallet?usr=' + userId + '&wal=' + walletId @@ -123,6 +123,7 @@ window.LNbits = { [ 'code', 'isValid', + 'isAdminOnly', 'name', 'shortDescription', 'icon', @@ -135,7 +136,12 @@ window.LNbits = { return obj }, user: function (data) { - var obj = {id: data.id, email: data.email, extensions: data.extensions, wallets: data.wallets} + var obj = { + id: data.id, + email: data.email, + extensions: data.extensions, + wallets: data.wallets + } var mapWallet = this.wallet obj.wallets = obj.wallets .map(function (obj) { @@ -153,16 +159,23 @@ window.LNbits = { return obj }, wallet: function (data) { - newWallet = {id: data.id, name: data.name, adminkey: data.adminkey, inkey: data.inkey} + newWallet = { + id: data.id, + name: data.name, + adminkey: data.adminkey, + inkey: data.inkey + } newWallet.msat = data.balance_msat newWallet.sat = Math.round(data.balance_msat / 1000) - newWallet.fsat = new Intl.NumberFormat(window.LOCALE).format(newWallet.sat) + newWallet.fsat = new Intl.NumberFormat(window.LOCALE).format( + newWallet.sat + ) newWallet.url = ['/wallet?usr=', data.user, '&wal=', data.id].join('') return newWallet }, payment: function (data) { obj = { - checking_id:data.id, + checking_id: data.id, pending: data.pending, amount: data.amount, fee: data.fee, @@ -174,8 +187,8 @@ window.LNbits = { extra: data.extra, wallet_id: data.wallet_id, webhook: data.webhook, - webhook_status: data.webhook_status, - } + webhook_status: data.webhook_status + } obj.date = Quasar.utils.date.formatDate( new Date(obj.time * 1000), @@ -225,7 +238,8 @@ window.LNbits = { Quasar.plugins.Notify.create({ timeout: 5000, type: types[error.response.status] || 'warning', - message: error.response.data.message || error.response.data.detail || null, + message: + error.response.data.message || error.response.data.detail || null, caption: [error.response.status, ' ', error.response.statusText] .join('') @@ -368,6 +382,10 @@ window.windowMixin = { .filter(function (obj) { return !obj.hidden }) + .filter(function (obj) { + if (window.user.admin) return obj + return !obj.isAdminOnly + }) .map(function (obj) { if (user) { obj.isEnabled = user.extensions.indexOf(obj.code) !== -1 From 6bc1474c196fd534104223c8ad00d0c1726435c8 Mon Sep 17 00:00:00 2001 From: benarc Date: Mon, 7 Feb 2022 20:48:48 +0000 Subject: [PATCH 04/14] Organised .env --- .env.example | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index 1c478cd99..060748a97 100644 --- a/.env.example +++ b/.env.example @@ -5,11 +5,15 @@ QUART_DEBUG=true HOST=127.0.0.1 PORT=5000 - LNBITS_ALLOWED_USERS="" LNBITS_ADMIN_USERS="" +# Extensions only admin can access +LNBITS_ADMIN_EXTENSIONS="ngrok" LNBITS_DEFAULT_WALLET_NAME="LNbits wallet" +# Disable extensions for all users, use "all" to disable all extensions +LNBITS_DISABLED_EXTENSIONS="amilk" + # Database: to use SQLite, specify LNBITS_DATA_FOLDER # to use PostgreSQL, specify LNBITS_DATABASE_URL=postgres://... # to use CockroachDB, specify LNBITS_DATABASE_URL=cockroachdb://... @@ -18,8 +22,6 @@ LNBITS_DEFAULT_WALLET_NAME="LNbits wallet" LNBITS_DATA_FOLDER="./data" # LNBITS_DATABASE_URL="postgres://user:password@host:port/databasename" -# disable selected extensions, or use "all" to disable all extensions -LNBITS_DISABLED_EXTENSIONS="amilk,ngrok" LNBITS_FORCE_HTTPS=true LNBITS_SERVICE_FEE="0.0" From 2c853e7a521cc710f9e1457775d9df683bfce34d Mon Sep 17 00:00:00 2001 From: benarc Date: Mon, 7 Feb 2022 21:28:48 +0000 Subject: [PATCH 05/14] slight bug --- lnbits/helpers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lnbits/helpers.py b/lnbits/helpers.py index a1c88389d..d26163759 100644 --- a/lnbits/helpers.py +++ b/lnbits/helpers.py @@ -53,6 +53,7 @@ class ExtensionManager: except Exception: config = {} is_valid = False + is_admin_only = False output.append( Extension( From 29bd8d9ce963b4a9876120fbe4354121680ca32e Mon Sep 17 00:00:00 2001 From: benarc Date: Tue, 8 Feb 2022 10:35:20 +0000 Subject: [PATCH 06/14] Chnaged nostr to nostradmin --- .env.example | 2 +- lnbits/extensions/nostr/config.json | 6 - lnbits/extensions/nostr/crud.py | 115 ------------- lnbits/extensions/nostr/views_api.py | 24 --- .../{nostr => nostradmin}/README.md | 0 .../{nostr => nostradmin}/__init__.py | 7 +- lnbits/extensions/nostradmin/config.json | 6 + lnbits/extensions/nostradmin/crud.py | 156 ++++++++++++++++++ .../{nostr => nostradmin}/migrations.py | 32 +++- .../{nostr => nostradmin}/models.py | 14 +- .../templates/nostradmin}/index.html | 130 ++++++++++++++- .../extensions/{nostr => nostradmin}/views.py | 2 +- lnbits/extensions/nostradmin/views_api.py | 87 ++++++++++ 13 files changed, 421 insertions(+), 160 deletions(-) delete mode 100644 lnbits/extensions/nostr/config.json delete mode 100644 lnbits/extensions/nostr/crud.py delete mode 100644 lnbits/extensions/nostr/views_api.py rename lnbits/extensions/{nostr => nostradmin}/README.md (100%) rename lnbits/extensions/{nostr => nostradmin}/__init__.py (51%) create mode 100644 lnbits/extensions/nostradmin/config.json create mode 100644 lnbits/extensions/nostradmin/crud.py rename lnbits/extensions/{nostr => nostradmin}/migrations.py (54%) rename lnbits/extensions/{nostr => nostradmin}/models.py (73%) rename lnbits/extensions/{nostr/templates/nostr => nostradmin/templates/nostradmin}/index.html (50%) rename lnbits/extensions/{nostr => nostradmin}/views.py (90%) create mode 100644 lnbits/extensions/nostradmin/views_api.py diff --git a/.env.example b/.env.example index 060748a97..0d5b497e0 100644 --- a/.env.example +++ b/.env.example @@ -8,7 +8,7 @@ PORT=5000 LNBITS_ALLOWED_USERS="" LNBITS_ADMIN_USERS="" # Extensions only admin can access -LNBITS_ADMIN_EXTENSIONS="ngrok" +LNBITS_ADMIN_EXTENSIONS="nostradmin" LNBITS_DEFAULT_WALLET_NAME="LNbits wallet" # Disable extensions for all users, use "all" to disable all extensions diff --git a/lnbits/extensions/nostr/config.json b/lnbits/extensions/nostr/config.json deleted file mode 100644 index a32e39a1a..000000000 --- a/lnbits/extensions/nostr/config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Nostr", - "short_description": "Daemon for Nostr", - "icon": "swap_horizontal_circle", - "contributors": ["arcbtc"] -} diff --git a/lnbits/extensions/nostr/crud.py b/lnbits/extensions/nostr/crud.py deleted file mode 100644 index 0b30bc9a7..000000000 --- a/lnbits/extensions/nostr/crud.py +++ /dev/null @@ -1,115 +0,0 @@ -from typing import List, Optional, Union - -from lnbits.helpers import urlsafe_short_hash -import shortuuid -from . import db -from .models import nostrKeys, nostrNotes, nostrCreateRelays, nostrRelays, nostrConnections, nostrCreateConnections - -###############KEYS################## - -async def create_nostrkeys( - data: nostrKeys -) -> nostrKeys: - await db.execute( - """ - INSERT INTO nostr.keys ( - pubkey, - privkey - ) - VALUES (?, ?) - """, - (data.pubkey, data.privkey), - ) - return await get_nostrkeys(nostrkey_id) - -async def get_nostrkeys(pubkey: str) -> nostrKeys: - row = await db.fetchone( - "SELECT * FROM nostr.keys WHERE pubkey = ?", - (pubkey,), - ) - return nostrKeys(**row) if row else None - - -###############NOTES################## - -async def create_nostrnotes( - data: nostrNotes -) -> nostrNotes: - await db.execute( - """ - INSERT INTO nostr.notes ( - id, - pubkey, - created_at, - kind, - tags, - content, - sig - ) - VALUES (?, ?, ?, ?, ?, ?, ?) - """, - (data.id, data.pubkey, data.created_at, data.kind, data.tags, data.content, data.sig), - ) - return await get_nostrnotes(data.id) - -async def get_nostrnotes(nostrnote_id: str) -> nostrNotes: - row = await db.fetchone( - "SELECT * FROM nostr.notes WHERE id = ?", - (nostrnote_id,), - ) - return nostrNotes(**row) if row else None - -###############RELAYS################## - -async def create_nostrrelays( - data: nostrCreateRelays -) -> nostrCreateRelays: - nostrrelay_id = shortuuid.uuid(name=relay) - - if await get_nostrrelays(nostrrelay_id): - return "error" - await db.execute( - """ - INSERT INTO nostr.relays ( - id, - relay - ) - VALUES (?, ?) - """, - (nostrrelay_id, data.relay), - ) - return await get_nostrnotes(nostrrelay_id) - -async def get_nostrrelays(nostrrelay_id: str) -> nostrRelays: - row = await db.fetchone( - "SELECT * FROM nostr.relays WHERE id = ?", - (nostrnote_id,), - ) - return nostrRelays(**row) if row else None - - -###############CONNECTIONS################## - -async def create_nostrconnections( - data: nostrCreateConnections -) -> nostrCreateConnections: - nostrrelay_id = shortuuid.uuid(name=data.relayid + data.pubkey) - await db.execute( - """ - INSERT INTO nostr.connections ( - id, - pubkey, - relayid - ) - VALUES (?, ?, ?) - """, - (data.id, data.pubkey, data.relayid), - ) - return await get_nostrconnections(data.id) - -async def get_nostrconnections(nostrconnections_id: str) -> nostrConnections: - row = await db.fetchone( - "SELECT * FROM nostr.connections WHERE id = ?", - (nostrconnections_id,), - ) - return nostrConnections(**row) if row else None \ No newline at end of file diff --git a/lnbits/extensions/nostr/views_api.py b/lnbits/extensions/nostr/views_api.py deleted file mode 100644 index 9e7ccfffa..000000000 --- a/lnbits/extensions/nostr/views_api.py +++ /dev/null @@ -1,24 +0,0 @@ -from http import HTTPStatus - -from fastapi import Request -from fastapi.param_functions import Query -from fastapi.params import Depends -from starlette.exceptions import HTTPException - -from lnbits.core.crud import get_user -from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key -from lnbits.extensions.nostr import nostr_ext -from lnbits.utils.exchange_rates import currencies - -from . import nostr_ext -from .crud import ( - create_nostrkeys, - get_nostrkeys, - create_nostrnotes, - get_nostrnotes, - create_nostrrelays, - get_nostrrelays, - create_nostrconnections, - get_nostrconnections, -) -from .models import nostrKeys \ No newline at end of file diff --git a/lnbits/extensions/nostr/README.md b/lnbits/extensions/nostradmin/README.md similarity index 100% rename from lnbits/extensions/nostr/README.md rename to lnbits/extensions/nostradmin/README.md diff --git a/lnbits/extensions/nostr/__init__.py b/lnbits/extensions/nostradmin/__init__.py similarity index 51% rename from lnbits/extensions/nostr/__init__.py rename to lnbits/extensions/nostradmin/__init__.py index 9774a2790..7034ca46b 100644 --- a/lnbits/extensions/nostr/__init__.py +++ b/lnbits/extensions/nostradmin/__init__.py @@ -3,13 +3,14 @@ from fastapi import APIRouter from lnbits.db import Database from lnbits.helpers import template_renderer -db = Database("ext_nostr") +db = Database("ext_nostradmin") -nostr_ext: APIRouter = APIRouter(prefix="/nostr", tags=["nostr"]) +nostr_ext: APIRouter = APIRouter(prefix="/nostradmin", tags=["nostradmin"]) def nostr_renderer(): - return template_renderer(["lnbits/extensions/nostr/templates"]) + return template_renderer(["lnbits/extensions/nostradmin/templates"]) + from .views import * # noqa from .views_api import * # noqa diff --git a/lnbits/extensions/nostradmin/config.json b/lnbits/extensions/nostradmin/config.json new file mode 100644 index 000000000..2c4f76d35 --- /dev/null +++ b/lnbits/extensions/nostradmin/config.json @@ -0,0 +1,6 @@ +{ + "name": "NostrAdmin", + "short_description": "Admin daemon for Nostr", + "icon": "swap_horizontal_circle", + "contributors": ["arcbtc"] +} diff --git a/lnbits/extensions/nostradmin/crud.py b/lnbits/extensions/nostradmin/crud.py new file mode 100644 index 000000000..08908e856 --- /dev/null +++ b/lnbits/extensions/nostradmin/crud.py @@ -0,0 +1,156 @@ +from typing import List, Optional, Union + +from lnbits.helpers import urlsafe_short_hash +import shortuuid +from . import db +from .models import ( + nostrKeys, + nostrNotes, + nostrCreateRelays, + nostrRelays, + nostrConnections, + nostrCreateConnections, + nostrRelayList, +) + +###############KEYS################## + + +async def create_nostrkeys(data: nostrKeys) -> nostrKeys: + await db.execute( + """ + INSERT INTO nostradmin.keys ( + pubkey, + privkey + ) + VALUES (?, ?) + """, + (data.pubkey, data.privkey), + ) + return await get_nostrkeys(nostrkey_id) + + +async def get_nostrkeys(pubkey: str) -> nostrKeys: + row = await db.fetchone("SELECT * FROM nostradmin.keys WHERE pubkey = ?", (pubkey,)) + return nostrKeys(**row) if row else None + + +###############NOTES################## + + +async def create_nostrnotes(data: nostrNotes) -> nostrNotes: + await db.execute( + """ + INSERT INTO nostradmin.notes ( + id, + pubkey, + created_at, + kind, + tags, + content, + sig + ) + VALUES (?, ?, ?, ?, ?, ?, ?) + """, + ( + data.id, + data.pubkey, + data.created_at, + data.kind, + data.tags, + data.content, + data.sig, + ), + ) + return await get_nostrnotes(data.id) + + +async def get_nostrnotes(nostrnote_id: str) -> nostrNotes: + row = await db.fetchone("SELECT * FROM nostradmin.notes WHERE id = ?", (nostrnote_id,)) + return nostrNotes(**row) if row else None + + +###############RELAYS################## + + +async def create_nostrrelays(data: nostrCreateRelays) -> nostrCreateRelays: + nostrrelay_id = shortuuid.uuid(name=data.relay) + + if await get_nostrrelay(nostrrelay_id): + return "error" + await db.execute( + """ + INSERT INTO nostradmin.relays ( + id, + relay + ) + VALUES (?, ?) + """, + (nostrrelay_id, data.relay), + ) + return await get_nostrrelay(nostrrelay_id) + + +async def get_nostrrelays() -> nostrRelays: + rows = await db.fetchall("SELECT * FROM nostradmin.relays") + return [nostrRelays(**row) for row in rows] + + +async def get_nostrrelay(nostrrelay_id: str) -> nostrRelays: + row = await db.fetchone("SELECT * FROM nostradmin.relays WHERE id = ?", (nostrrelay_id,)) + return nostrRelays(**row) if row else None + + +async def update_nostrrelayallowlist(allowlist: str) -> nostrRelayList: + await db.execute( + """ + UPDATE nostradmin.relaylist SET + allowlist = ? + WHERE id = ? + """, + (allowlist, 1), + ) + return await get_nostrrelaylist() + +async def update_nostrrelaydenylist(denylist: str) -> nostrRelayList: + await db.execute( + """ + UPDATE nostradmin.relaylist SET + denylist = ? + WHERE id = ? + """, + (denylist, 1), + ) + return await get_nostrrelaylist() + +async def get_nostrrelaylist() -> nostrRelayList: + row = await db.fetchone("SELECT * FROM nostradmin.relaylist WHERE id = ?", (1,)) + return nostrRelayList(**row) if row else None + + +###############CONNECTIONS################## + + +async def create_nostrconnections( + data: nostrCreateConnections +) -> nostrCreateConnections: + nostrrelay_id = shortuuid.uuid(name=data.relayid + data.pubkey) + await db.execute( + """ + INSERT INTO nostradmin.connections ( + id, + pubkey, + relayid + ) + VALUES (?, ?, ?) + """, + (data.id, data.pubkey, data.relayid), + ) + return await get_nostrconnections(data.id) + + +async def get_nostrconnections(nostrconnections_id: str) -> nostrConnections: + row = await db.fetchone( + "SELECT * FROM nostradmin.connections WHERE id = ?", (nostrconnections_id,) + ) + return nostrConnections(**row) if row else None diff --git a/lnbits/extensions/nostr/migrations.py b/lnbits/extensions/nostradmin/migrations.py similarity index 54% rename from lnbits/extensions/nostr/migrations.py rename to lnbits/extensions/nostradmin/migrations.py index 0c616110e..09b28117d 100644 --- a/lnbits/extensions/nostr/migrations.py +++ b/lnbits/extensions/nostradmin/migrations.py @@ -9,7 +9,7 @@ async def m001_initial(db): """ await db.execute( f""" - CREATE TABLE nostr.keys ( + CREATE TABLE nostradmin.keys ( pubkey TEXT NOT NULL PRIMARY KEY, privkey TEXT NOT NULL ); @@ -17,7 +17,7 @@ async def m001_initial(db): ) await db.execute( f""" - CREATE TABLE nostr.notes ( + CREATE TABLE nostradmin.notes ( id TEXT NOT NULL PRIMARY KEY, pubkey TEXT NOT NULL, created_at TEXT NOT NULL, @@ -30,7 +30,7 @@ async def m001_initial(db): ) await db.execute( f""" - CREATE TABLE nostr.relays ( + CREATE TABLE nostradmin.relays ( id TEXT NOT NULL PRIMARY KEY, relay TEXT NOT NULL ); @@ -38,10 +38,32 @@ async def m001_initial(db): ) await db.execute( f""" - CREATE TABLE nostr.connections ( + CREATE TABLE nostradmin.relaylists ( + id TEXT NOT NULL PRIMARY KEY DEFAULT 1, + allowlist TEXT NOT NULL, + denylist TEXT NOT NULL + ); + """ + ) + try: + await db.execute( + """ + INSERT INTO nostradmin.relaylist ( + id, + denylist + ) + VALUES (?, ?,) + """, + (1, "\n".join(["wss://zucks-meta-relay.com", "wss://nostradmin.cia.gov"])), + ) + except: + return + await db.execute( + f""" + CREATE TABLE nostradmin.connections ( id TEXT NOT NULL PRIMARY KEY, publickey TEXT NOT NULL, relayid TEXT NOT NULL ); """ - ) \ No newline at end of file + ) diff --git a/lnbits/extensions/nostr/models.py b/lnbits/extensions/nostradmin/models.py similarity index 73% rename from lnbits/extensions/nostr/models.py rename to lnbits/extensions/nostradmin/models.py index e21e5d76f..fd89f5155 100644 --- a/lnbits/extensions/nostr/models.py +++ b/lnbits/extensions/nostradmin/models.py @@ -6,6 +6,7 @@ from fastapi import Request from pydantic import BaseModel from pydantic.main import BaseModel + class nostrKeys(BaseModel): pubkey: str privkey: str @@ -30,7 +31,18 @@ class nostrRelays(BaseModel): id: str relay: str +class nostrRelayList(BaseModel): + id: str + allowlist: str + denylist: str + +class nostrRelayDenyList(BaseModel): + denylist: str + +class nostrRelayAllowList(BaseModel): + allowlist: str + class nostrConnections(BaseModel): id: str pubkey: str - relayid: str \ No newline at end of file + relayid: str diff --git a/lnbits/extensions/nostr/templates/nostr/index.html b/lnbits/extensions/nostradmin/templates/nostradmin/index.html similarity index 50% rename from lnbits/extensions/nostr/templates/nostr/index.html rename to lnbits/extensions/nostradmin/templates/nostradmin/index.html index f17d0243b..adab98e2f 100644 --- a/lnbits/extensions/nostr/templates/nostr/index.html +++ b/lnbits/extensions/nostradmin/templates/nostradmin/index.html @@ -71,12 +71,77 @@ + + + + + + + + + + +
+ Deny List (denys use of relays in this list) +
+ +
+
+ +
+
+ + Update Deny List + + Reset +
+
+
+
+ + +
+ Allow List (denys any relays not in this list) +
+ +
+
+ +
+
+ + Update Allow List + + Reset +
+
+
+
+
+
{{SITE_TITLE}} Nostr Extension
+

Only Admin users can manage this extension

Okay
@@ -109,6 +174,9 @@ mixins: [windowMixin], data: function () { return { + listSelection: 'denylist', + allowList: [], + denyList: [], nostrTable: { columns: [ { @@ -136,12 +204,66 @@ LNbits.api .request( 'GET', - '/nostr/api/v1/relays', + '/nostradmin/api/v1/relays', self.g.user.wallets[0].adminkey ) .then(function (response) { if (response.data) { - self.lnurldeviceLinks = response.data.map(maplnurldevice) + self.nostrrelayLinks = response.data.map(maprelays) + } + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) + }, + setAllowList: function () { + var self = this + LNbits.api + .request( + 'POST', + '/nostradmin/api/v1/allowlist', + self.g.user.wallets[0].adminkey, + self.allowList + ) + .then(function (response) { + if (response.data) { + self.allowList = response.data + } + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) + }, + setDenyList: function () { + var self = this + LNbits.api + .request( + 'POST', + '/nostradmin/api/v1/denylist', + self.g.user.wallets[0].adminkey, + self.allowList + ) + .then(function (response) { + if (response.data) { + self.denyList = response.data + } + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) + }, + getLists: function () { + var self = this + LNbits.api + .request( + 'GET', + '/nostradmin/api/v1/relaylist', + self.g.user.wallets[0].adminkey + ) + .then(function (response) { + if (response.data) { + self.denyList = response.data.denylist + self.allowList = response.data.allowlist } }) .catch(function (error) { @@ -151,8 +273,8 @@ }, created: function () { var self = this - var getrelays = this.getrelays - getrelays() + this.getrelays() + this.getLists() } }) diff --git a/lnbits/extensions/nostr/views.py b/lnbits/extensions/nostradmin/views.py similarity index 90% rename from lnbits/extensions/nostr/views.py rename to lnbits/extensions/nostradmin/views.py index 1dfc07da7..5609c2185 100644 --- a/lnbits/extensions/nostr/views.py +++ b/lnbits/extensions/nostradmin/views.py @@ -20,5 +20,5 @@ templates = Jinja2Templates(directory="templates") @nostr_ext.get("/", response_class=HTMLResponse) async def index(request: Request, user: User = Depends(check_user_exists)): return nostr_renderer().TemplateResponse( - "nostr/index.html", {"request": request, "user": user.dict()} + "nostradmin/index.html", {"request": request, "user": user.dict()} ) diff --git a/lnbits/extensions/nostradmin/views_api.py b/lnbits/extensions/nostradmin/views_api.py new file mode 100644 index 000000000..da6e140ba --- /dev/null +++ b/lnbits/extensions/nostradmin/views_api.py @@ -0,0 +1,87 @@ +from http import HTTPStatus +import asyncio +from fastapi import Request +from fastapi.param_functions import Query +from fastapi.params import Depends +from starlette.exceptions import HTTPException + +from lnbits.core.crud import get_user +from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key +from lnbits.extensions.nostr import nostr_ext +from lnbits.utils.exchange_rates import currencies + +from . import nostr_ext +from lnbits.settings import LNBITS_ADMIN_USERS +from .crud import ( + create_nostrkeys, + get_nostrkeys, + create_nostrnotes, + get_nostrnotes, + create_nostrrelays, + get_nostrrelays, + get_nostrrelaylist, + update_nostrrelayallowlist, + update_nostrrelaydenylist, + create_nostrconnections, + get_nostrconnections, +) +from .models import nostrKeys, nostrCreateRelays, nostrRelayAllowList, nostrRelayDenyList + + +# while True: +async def nostr_subscribe(): + return + # for the relays: + # async with websockets.connect("ws://localhost:8765") as websocket: + # for the public keys: + # await websocket.send("subscribe to events") + # await websocket.recv() + + +websocket_queue = asyncio.Queue(1000) + + +async def internal_invoice_listener(): + while True: + checking_id = await internal_invoice_queue.get() + asyncio.create_task(invoice_callback_dispatcher(checking_id)) + + +@nostr_ext.get("/api/v1/relays") +async def api_relays_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)): + + relays = await get_nostrrelays() + if not relays: + await create_nostrrelays(nostrCreateRelays(relay="wss://relayer.fiatjaf.com")) + await create_nostrrelays( + nostrCreateRelays(relay="wss://nostr-pub.wellorder.net") + ) + relays = await get_nostrrelays() + try: + return [{**relays.dict()} for relays in await relays] + except: + None + +@nostr_ext.get("/api/v1/relaylist") +async def api_relaylist(wallet: WalletTypeInfo = Depends(get_key_type)): + if wallet.wallet.user not in LNBITS_ADMIN_USERS: + raise HTTPException( + status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized." + ) + return await get_nostrrelaylist() + +@nostr_ext.post("/api/v1/allowlist") +async def api_relaysallowed(data: nostrRelayAllowList, wallet: WalletTypeInfo = Depends(get_key_type)): + if wallet.wallet.user not in LNBITS_ADMIN_USERS: + raise HTTPException( + status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized." + ) + return await update_nostrrelayallowlist(data) + +@nostr_ext.post("/api/v1/denylist") +async def api_relaysdenyed(data: nostrRelayDenyList, wallet: WalletTypeInfo = Depends(get_key_type)): + if wallet.wallet.user not in LNBITS_ADMIN_USERS: + raise HTTPException( + status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized." + ) + return await update_nostrrelaydenylist(data) \ No newline at end of file From 2083ecbc27b00ad4aca605425a183b8122e7d995 Mon Sep 17 00:00:00 2001 From: benarc Date: Tue, 8 Feb 2022 13:13:36 +0000 Subject: [PATCH 07/14] models decided --- lnbits/extensions/nostradmin/__init__.py | 2 +- lnbits/extensions/nostradmin/crud.py | 22 ++---- lnbits/extensions/nostradmin/migrations.py | 41 ++++++----- lnbits/extensions/nostradmin/models.py | 33 +++++---- .../templates/nostradmin/index.html | 71 +++++++++++-------- lnbits/extensions/nostradmin/views.py | 5 +- lnbits/extensions/nostradmin/views_api.py | 33 +++------ 7 files changed, 100 insertions(+), 107 deletions(-) diff --git a/lnbits/extensions/nostradmin/__init__.py b/lnbits/extensions/nostradmin/__init__.py index 7034ca46b..797542c49 100644 --- a/lnbits/extensions/nostradmin/__init__.py +++ b/lnbits/extensions/nostradmin/__init__.py @@ -5,7 +5,7 @@ from lnbits.helpers import template_renderer db = Database("ext_nostradmin") -nostr_ext: APIRouter = APIRouter(prefix="/nostradmin", tags=["nostradmin"]) +nostradmin_ext: APIRouter = APIRouter(prefix="/nostradmin", tags=["nostradmin"]) def nostr_renderer(): diff --git a/lnbits/extensions/nostradmin/crud.py b/lnbits/extensions/nostradmin/crud.py index 08908e856..1e1bf8106 100644 --- a/lnbits/extensions/nostradmin/crud.py +++ b/lnbits/extensions/nostradmin/crud.py @@ -12,6 +12,7 @@ from .models import ( nostrCreateConnections, nostrRelayList, ) +from .models import nostrKeys, nostrCreateRelays, nostrRelaySetList ###############KEYS################## @@ -100,31 +101,20 @@ async def get_nostrrelay(nostrrelay_id: str) -> nostrRelays: row = await db.fetchone("SELECT * FROM nostradmin.relays WHERE id = ?", (nostrrelay_id,)) return nostrRelays(**row) if row else None - -async def update_nostrrelayallowlist(allowlist: str) -> nostrRelayList: +async def update_nostrrelaysetlist(data: nostrRelaySetList) -> nostrRelayList: await db.execute( """ - UPDATE nostradmin.relaylist SET + UPDATE nostradmin.relaylists SET + denylist = ?, allowlist = ? WHERE id = ? """, - (allowlist, 1), - ) - return await get_nostrrelaylist() - -async def update_nostrrelaydenylist(denylist: str) -> nostrRelayList: - await db.execute( - """ - UPDATE nostradmin.relaylist SET - denylist = ? - WHERE id = ? - """, - (denylist, 1), + (data.denylist, data.allowlist, 1), ) return await get_nostrrelaylist() async def get_nostrrelaylist() -> nostrRelayList: - row = await db.fetchone("SELECT * FROM nostradmin.relaylist WHERE id = ?", (1,)) + row = await db.fetchone("SELECT * FROM nostradmin.relaylists WHERE id = ?", (1,)) return nostrRelayList(**row) if row else None diff --git a/lnbits/extensions/nostradmin/migrations.py b/lnbits/extensions/nostradmin/migrations.py index 09b28117d..590f72ea7 100644 --- a/lnbits/extensions/nostradmin/migrations.py +++ b/lnbits/extensions/nostradmin/migrations.py @@ -1,11 +1,8 @@ from lnbits.db import Database -db2 = Database("ext_nostr") - - async def m001_initial(db): """ - Initial nostr table. + Initial nostradmin table. """ await db.execute( f""" @@ -40,24 +37,23 @@ async def m001_initial(db): f""" CREATE TABLE nostradmin.relaylists ( id TEXT NOT NULL PRIMARY KEY DEFAULT 1, - allowlist TEXT NOT NULL, - denylist TEXT NOT NULL + allowlist TEXT, + denylist TEXT ); """ ) - try: - await db.execute( - """ - INSERT INTO nostradmin.relaylist ( - id, - denylist - ) - VALUES (?, ?,) - """, - (1, "\n".join(["wss://zucks-meta-relay.com", "wss://nostradmin.cia.gov"])), + + await db.execute( + """ + INSERT INTO nostradmin.relaylists ( + id, + denylist ) - except: - return + VALUES (?, ?) + """, + ("1", "wss://zucks-meta-relay.com\nwss://nostr.cia.gov",), + ) + await db.execute( f""" CREATE TABLE nostradmin.connections ( @@ -67,3 +63,12 @@ async def m001_initial(db): ); """ ) + await db.execute( + f""" + CREATE TABLE nostradmin.subscribed ( + id TEXT NOT NULL PRIMARY KEY, + userPubkey TEXT NOT NULL, + subscribedPubkey TEXT NOT NULL + ); + """ + ) \ No newline at end of file diff --git a/lnbits/extensions/nostradmin/models.py b/lnbits/extensions/nostradmin/models.py index fd89f5155..1968567f3 100644 --- a/lnbits/extensions/nostradmin/models.py +++ b/lnbits/extensions/nostradmin/models.py @@ -5,7 +5,7 @@ from typing import Optional from fastapi import Request from pydantic import BaseModel from pydantic.main import BaseModel - +from fastapi.param_functions import Query class nostrKeys(BaseModel): pubkey: str @@ -21,28 +21,31 @@ class nostrNotes(BaseModel): sig: str class nostrCreateRelays(BaseModel): - relay: str + relay: str = Query(None) class nostrCreateConnections(BaseModel): - pubkey: str - relayid: str + pubkey: str = Query(None) + relayid: str = Query(None) class nostrRelays(BaseModel): - id: str - relay: str + id: Optional[str] + relay: Optional[str] class nostrRelayList(BaseModel): id: str - allowlist: str - denylist: str + allowlist: Optional[str] + denylist: Optional[str] -class nostrRelayDenyList(BaseModel): - denylist: str - -class nostrRelayAllowList(BaseModel): - allowlist: str +class nostrRelaySetList(BaseModel): + allowlist: Optional[str] + denylist: Optional[str] class nostrConnections(BaseModel): id: str - pubkey: str - relayid: str + pubkey: Optional[str] + relayid: Optional[str] + +class nostrSubscriptions(BaseModel): + id: str + userPubkey: Optional[str] + subscribedPubkey: Optional[str] \ No newline at end of file diff --git a/lnbits/extensions/nostradmin/templates/nostradmin/index.html b/lnbits/extensions/nostradmin/templates/nostradmin/index.html index adab98e2f..27decdc80 100644 --- a/lnbits/extensions/nostradmin/templates/nostradmin/index.html +++ b/lnbits/extensions/nostradmin/templates/nostradmin/index.html @@ -6,7 +6,7 @@
-
Nostr
+
NOSTR RELAYS ONLINE
@@ -29,7 +29,7 @@ - - + + - + - + -
- Deny List (denys use of relays in this list) -
- +
Relays in this list will NOT be used
+
- +
Update Deny List - Reset
-
- Allow List (denys any relays not in this list) -
- +
ONLY relays in this list will be used
+
- +
Update Allow List - Reset
@@ -175,8 +175,12 @@ data: function () { return { listSelection: 'denylist', - allowList: [], - denyList: [], + setList: { + allowlist: '', + denylist: '' + }, + nostrLinks: [], + filter: '', nostrTable: { columns: [ { @@ -234,18 +238,20 @@ LNbits.utils.notifyApiError(error) }) }, - setDenyList: function () { + setRelayList: function () { var self = this + console.log(self.setList) LNbits.api .request( 'POST', - '/nostradmin/api/v1/denylist', + '/nostradmin/api/v1/setlist', self.g.user.wallets[0].adminkey, - self.allowList + self.setList ) .then(function (response) { if (response.data) { - self.denyList = response.data + console.log(response.data) + // self.denyList = response.data } }) .catch(function (error) { @@ -262,13 +268,18 @@ ) .then(function (response) { if (response.data) { - self.denyList = response.data.denylist - self.allowList = response.data.allowlist + console.log(response.data) + self.setList.denylist = response.data.denylist + self.setList.allowlist = response.data.allowlist } }) .catch(function (error) { LNbits.utils.notifyApiError(error) }) + }, + exportlnurldeviceCSV: function () { + var self = this + LNbits.utils.exportCSV(self.nostrTable.columns, this.nostrLinks) } }, created: function () { diff --git a/lnbits/extensions/nostradmin/views.py b/lnbits/extensions/nostradmin/views.py index 5609c2185..51297320b 100644 --- a/lnbits/extensions/nostradmin/views.py +++ b/lnbits/extensions/nostradmin/views.py @@ -6,18 +6,19 @@ from fastapi.params import Depends from fastapi.templating import Jinja2Templates from starlette.exceptions import HTTPException from starlette.responses import HTMLResponse +from . import nostradmin_ext, nostr_renderer from lnbits.core.crud import update_payment_status from lnbits.core.models import User from lnbits.core.views.api import api_payment from lnbits.decorators import check_user_exists -from . import nostr_ext, nostr_renderer + templates = Jinja2Templates(directory="templates") -@nostr_ext.get("/", response_class=HTMLResponse) +@nostradmin_ext.get("/", response_class=HTMLResponse) async def index(request: Request, user: User = Depends(check_user_exists)): return nostr_renderer().TemplateResponse( "nostradmin/index.html", {"request": request, "user": user.dict()} diff --git a/lnbits/extensions/nostradmin/views_api.py b/lnbits/extensions/nostradmin/views_api.py index da6e140ba..d79315ac8 100644 --- a/lnbits/extensions/nostradmin/views_api.py +++ b/lnbits/extensions/nostradmin/views_api.py @@ -7,11 +7,10 @@ from starlette.exceptions import HTTPException from lnbits.core.crud import get_user from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key -from lnbits.extensions.nostr import nostr_ext from lnbits.utils.exchange_rates import currencies -from . import nostr_ext from lnbits.settings import LNBITS_ADMIN_USERS +from . import nostradmin_ext from .crud import ( create_nostrkeys, get_nostrkeys, @@ -20,13 +19,11 @@ from .crud import ( create_nostrrelays, get_nostrrelays, get_nostrrelaylist, - update_nostrrelayallowlist, - update_nostrrelaydenylist, + update_nostrrelaysetlist, create_nostrconnections, get_nostrconnections, ) -from .models import nostrKeys, nostrCreateRelays, nostrRelayAllowList, nostrRelayDenyList - +from .models import nostrKeys, nostrCreateRelays, nostrRelaySetList # while True: async def nostr_subscribe(): @@ -41,13 +38,7 @@ async def nostr_subscribe(): websocket_queue = asyncio.Queue(1000) -async def internal_invoice_listener(): - while True: - checking_id = await internal_invoice_queue.get() - asyncio.create_task(invoice_callback_dispatcher(checking_id)) - - -@nostr_ext.get("/api/v1/relays") +@nostradmin_ext.get("/api/v1/relays") async def api_relays_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)): relays = await get_nostrrelays() @@ -62,7 +53,7 @@ async def api_relays_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)): except: None -@nostr_ext.get("/api/v1/relaylist") +@nostradmin_ext.get("/api/v1/relaylist") async def api_relaylist(wallet: WalletTypeInfo = Depends(get_key_type)): if wallet.wallet.user not in LNBITS_ADMIN_USERS: raise HTTPException( @@ -70,18 +61,10 @@ async def api_relaylist(wallet: WalletTypeInfo = Depends(get_key_type)): ) return await get_nostrrelaylist() -@nostr_ext.post("/api/v1/allowlist") -async def api_relaysallowed(data: nostrRelayAllowList, wallet: WalletTypeInfo = Depends(get_key_type)): +@nostradmin_ext.post("/api/v1/setlist") +async def api_relayssetlist(data: nostrRelaySetList, wallet: WalletTypeInfo = Depends(get_key_type)): if wallet.wallet.user not in LNBITS_ADMIN_USERS: raise HTTPException( status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized." ) - return await update_nostrrelayallowlist(data) - -@nostr_ext.post("/api/v1/denylist") -async def api_relaysdenyed(data: nostrRelayDenyList, wallet: WalletTypeInfo = Depends(get_key_type)): - if wallet.wallet.user not in LNBITS_ADMIN_USERS: - raise HTTPException( - status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized." - ) - return await update_nostrrelaydenylist(data) \ No newline at end of file + return await update_nostrrelaysetlist(data) \ No newline at end of file From 21c316293b4c8986854ade1421e4119fc6e4e5bb Mon Sep 17 00:00:00 2001 From: benarc Date: Tue, 8 Feb 2022 13:52:19 +0000 Subject: [PATCH 08/14] Started planning websocket daemon --- lnbits/extensions/nostradmin/views.py | 70 ++++++++++++++++++++++- lnbits/extensions/nostradmin/views_api.py | 12 ---- 2 files changed, 68 insertions(+), 14 deletions(-) diff --git a/lnbits/extensions/nostradmin/views.py b/lnbits/extensions/nostradmin/views.py index 51297320b..235ec7e98 100644 --- a/lnbits/extensions/nostradmin/views.py +++ b/lnbits/extensions/nostradmin/views.py @@ -1,5 +1,6 @@ from http import HTTPStatus - +import asyncio +import asyncio from fastapi import Request from fastapi.param_functions import Query from fastapi.params import Depends @@ -7,13 +8,14 @@ from fastapi.templating import Jinja2Templates from starlette.exceptions import HTTPException from starlette.responses import HTMLResponse from . import nostradmin_ext, nostr_renderer +from fastapi import Request, WebSocket, WebSocketDisconnect from lnbits.core.crud import update_payment_status from lnbits.core.models import User from lnbits.core.views.api import api_payment from lnbits.decorators import check_user_exists - +from .crud import get_nostrkeys templates = Jinja2Templates(directory="templates") @@ -23,3 +25,67 @@ async def index(request: Request, user: User = Depends(check_user_exists)): return nostr_renderer().TemplateResponse( "nostradmin/index.html", {"request": request, "user": user.dict()} ) + +##################################################################### +#################### NOSTR WEBSOCKET THREAD ######################### +##### THE QUEUE LOOP THREAD THING THAT LISTENS TO BUNCH OF ########## +### WEBSOCKET CONNECTIONS, STORING DATA IN DB/PUSHING TO FRONTEND ### +################### VIA updater() FUNCTION ########################## +##################################################################### + +websocket_queue = asyncio.Queue(1000) + +# while True: +async def nostr_subscribe(): + return + # for the relays: + # async with websockets.connect("ws://localhost:8765") as websocket: + # for the public keys: + # await websocket.send("subscribe to events") + # await websocket.recv() + +##################################################################### +################### LNBITS WEBSOCKET ROUTES ######################### +#### HERE IS WHERE LNBITS FRONTEND CAN RECEIVE AND SEND MESSAGES #### +##################################################################### + +class ConnectionManager: + def __init__(self): + self.active_connections: List[WebSocket] = [] + + async def connect(self, websocket: WebSocket, nostr_id: str): + await websocket.accept() + websocket.id = nostr_id + self.active_connections.append(websocket) + + def disconnect(self, websocket: WebSocket): + self.active_connections.remove(websocket) + + async def send_personal_message(self, message: str, nostr_id: str): + for connection in self.active_connections: + if connection.id == nostr_id: + await connection.send_text(message) + + async def broadcast(self, message: str): + for connection in self.active_connections: + await connection.send_text(message) + + +manager = ConnectionManager() + + +@nostradmin_ext.websocket("/nostradmin/ws/{nostr_id}", name="copilot.websocket_by_id") +async def websocket_endpoint(websocket: WebSocket, copilot_id: str): + await manager.connect(websocket, nostr_id) + try: + while True: + data = await websocket.receive_text() + except WebSocketDisconnect: + manager.disconnect(websocket) + + +async def updater(nostr_id, message): + copilot = await get_copilot(nostr_id) + if not copilot: + return + await manager.send_personal_message(f"{message}", nostr_id) \ No newline at end of file diff --git a/lnbits/extensions/nostradmin/views_api.py b/lnbits/extensions/nostradmin/views_api.py index d79315ac8..ad8bcf179 100644 --- a/lnbits/extensions/nostradmin/views_api.py +++ b/lnbits/extensions/nostradmin/views_api.py @@ -25,18 +25,6 @@ from .crud import ( ) from .models import nostrKeys, nostrCreateRelays, nostrRelaySetList -# while True: -async def nostr_subscribe(): - return - # for the relays: - # async with websockets.connect("ws://localhost:8765") as websocket: - # for the public keys: - # await websocket.send("subscribe to events") - # await websocket.recv() - - -websocket_queue = asyncio.Queue(1000) - @nostradmin_ext.get("/api/v1/relays") async def api_relays_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)): From 0a5495f185a87a2aee8a2efd78b7dff0ad5d2384 Mon Sep 17 00:00:00 2001 From: benarc Date: Tue, 8 Feb 2022 14:02:50 +0000 Subject: [PATCH 09/14] typo --- lnbits/extensions/nostradmin/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/nostradmin/views.py b/lnbits/extensions/nostradmin/views.py index 235ec7e98..f00c43a31 100644 --- a/lnbits/extensions/nostradmin/views.py +++ b/lnbits/extensions/nostradmin/views.py @@ -74,7 +74,7 @@ class ConnectionManager: manager = ConnectionManager() -@nostradmin_ext.websocket("/nostradmin/ws/{nostr_id}", name="copilot.websocket_by_id") +@nostradmin_ext.websocket("/nostradmin/ws/{nostr_id}", name="nostr_id.websocket_by_id") async def websocket_endpoint(websocket: WebSocket, copilot_id: str): await manager.connect(websocket, nostr_id) try: From 4c18eb0b34b733f2b02f96dbb6141dd44d8fab0f Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Wed, 9 Feb 2022 10:57:24 +0000 Subject: [PATCH 10/14] fix disapearing admin extension --- lnbits/core/crud.py | 14 ++++++++------ lnbits/core/views/generic.py | 4 +--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lnbits/core/crud.py b/lnbits/core/crud.py index ad2d9f2cc..3066b124d 100644 --- a/lnbits/core/crud.py +++ b/lnbits/core/crud.py @@ -1,15 +1,15 @@ -import json import datetime -from uuid import uuid4 -from typing import List, Optional, Dict, Any +import json +from typing import Any, Dict, List, Optional from urllib.parse import urlparse +from uuid import uuid4 from lnbits import bolt11 -from lnbits.db import Connection, POSTGRES, COCKROACH -from lnbits.settings import DEFAULT_WALLET_NAME +from lnbits.db import COCKROACH, POSTGRES, Connection +from lnbits.settings import DEFAULT_WALLET_NAME, LNBITS_ADMIN_USERS from . import db -from .models import User, Wallet, Payment, BalanceCheck +from .models import BalanceCheck, Payment, User, Wallet # accounts # -------- @@ -53,6 +53,7 @@ async def get_user(user_id: str, conn: Optional[Connection] = None) -> Optional[ """, (user_id,), ) + else: return None @@ -61,6 +62,7 @@ async def get_user(user_id: str, conn: Optional[Connection] = None) -> Optional[ email=user["email"], extensions=[e[0] for e in extensions], wallets=[Wallet(**w) for w in wallets], + admin=LNBITS_ADMIN_USERS and user["id"] in LNBITS_ADMIN_USERS ) diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py index d917ffab8..d2fd8f412 100644 --- a/lnbits/core/views/generic.py +++ b/lnbits/core/views/generic.py @@ -15,8 +15,8 @@ from lnbits.core.models import User from lnbits.decorators import check_user_exists from lnbits.helpers import template_renderer, url_for from lnbits.settings import ( - LNBITS_ALLOWED_USERS, LNBITS_ADMIN_USERS, + LNBITS_ALLOWED_USERS, LNBITS_SITE_TITLE, SERVICE_FEE, ) @@ -118,8 +118,6 @@ async def wallet( return template_renderer().TemplateResponse( "error.html", {"request": request, "err": "User not authorized."} ) - if LNBITS_ADMIN_USERS and user_id in LNBITS_ADMIN_USERS: - user.admin = True if not wallet_id: if user.wallets and not wallet_name: wallet = user.wallets[0] From d1d62583c4a47e73594c5876632faf825d440f3e Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Wed, 9 Feb 2022 11:16:13 +0000 Subject: [PATCH 11/14] fix for whitespace in admin users list --- lnbits/core/crud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/core/crud.py b/lnbits/core/crud.py index 3066b124d..e1539212e 100644 --- a/lnbits/core/crud.py +++ b/lnbits/core/crud.py @@ -62,7 +62,7 @@ async def get_user(user_id: str, conn: Optional[Connection] = None) -> Optional[ email=user["email"], extensions=[e[0] for e in extensions], wallets=[Wallet(**w) for w in wallets], - admin=LNBITS_ADMIN_USERS and user["id"] in LNBITS_ADMIN_USERS + admin=LNBITS_ADMIN_USERS and user["id"] in [x.strip() for x in LNBITS_ADMIN_USERS] ) From da6a2e772b5624125cafe28409407949a78718bf Mon Sep 17 00:00:00 2001 From: Arc <33088785+arcbtc@users.noreply.github.com> Date: Thu, 10 Feb 2022 09:56:02 +0000 Subject: [PATCH 12/14] Revert "fix disapearing admin extension" --- lnbits/core/crud.py | 14 ++++++-------- lnbits/core/views/generic.py | 4 +++- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lnbits/core/crud.py b/lnbits/core/crud.py index e1539212e..ad2d9f2cc 100644 --- a/lnbits/core/crud.py +++ b/lnbits/core/crud.py @@ -1,15 +1,15 @@ -import datetime import json -from typing import Any, Dict, List, Optional -from urllib.parse import urlparse +import datetime from uuid import uuid4 +from typing import List, Optional, Dict, Any +from urllib.parse import urlparse from lnbits import bolt11 -from lnbits.db import COCKROACH, POSTGRES, Connection -from lnbits.settings import DEFAULT_WALLET_NAME, LNBITS_ADMIN_USERS +from lnbits.db import Connection, POSTGRES, COCKROACH +from lnbits.settings import DEFAULT_WALLET_NAME from . import db -from .models import BalanceCheck, Payment, User, Wallet +from .models import User, Wallet, Payment, BalanceCheck # accounts # -------- @@ -53,7 +53,6 @@ async def get_user(user_id: str, conn: Optional[Connection] = None) -> Optional[ """, (user_id,), ) - else: return None @@ -62,7 +61,6 @@ async def get_user(user_id: str, conn: Optional[Connection] = None) -> Optional[ email=user["email"], extensions=[e[0] for e in extensions], wallets=[Wallet(**w) for w in wallets], - admin=LNBITS_ADMIN_USERS and user["id"] in [x.strip() for x in LNBITS_ADMIN_USERS] ) diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py index d2fd8f412..d917ffab8 100644 --- a/lnbits/core/views/generic.py +++ b/lnbits/core/views/generic.py @@ -15,8 +15,8 @@ from lnbits.core.models import User from lnbits.decorators import check_user_exists from lnbits.helpers import template_renderer, url_for from lnbits.settings import ( - LNBITS_ADMIN_USERS, LNBITS_ALLOWED_USERS, + LNBITS_ADMIN_USERS, LNBITS_SITE_TITLE, SERVICE_FEE, ) @@ -118,6 +118,8 @@ async def wallet( return template_renderer().TemplateResponse( "error.html", {"request": request, "err": "User not authorized."} ) + if LNBITS_ADMIN_USERS and user_id in LNBITS_ADMIN_USERS: + user.admin = True if not wallet_id: if user.wallets and not wallet_name: wallet = user.wallets[0] From 6917813e2af357138bebb2df31d00be6148b7553 Mon Sep 17 00:00:00 2001 From: benarc Date: Thu, 10 Feb 2022 09:58:50 +0000 Subject: [PATCH 13/14] Working, looking good --- lnbits/extensions/nostradmin/models.py | 1 + .../templates/nostradmin/index.html | 39 ++++++++++++------- lnbits/extensions/nostradmin/views.py | 21 +++++++--- lnbits/extensions/nostradmin/views_api.py | 17 +++++--- 4 files changed, 53 insertions(+), 25 deletions(-) diff --git a/lnbits/extensions/nostradmin/models.py b/lnbits/extensions/nostradmin/models.py index 1968567f3..dc99b083c 100644 --- a/lnbits/extensions/nostradmin/models.py +++ b/lnbits/extensions/nostradmin/models.py @@ -30,6 +30,7 @@ class nostrCreateConnections(BaseModel): class nostrRelays(BaseModel): id: Optional[str] relay: Optional[str] + status: Optional[bool] = False class nostrRelayList(BaseModel): id: str diff --git a/lnbits/extensions/nostradmin/templates/nostradmin/index.html b/lnbits/extensions/nostradmin/templates/nostradmin/index.html index 27decdc80..57552bf28 100644 --- a/lnbits/extensions/nostradmin/templates/nostradmin/index.html +++ b/lnbits/extensions/nostradmin/templates/nostradmin/index.html @@ -29,7 +29,7 @@ - - -
-
{{ col.value }}
+
+
+ {{ col.value }} +
+
{{ col.value }}
+
@@ -143,7 +145,7 @@
{{SITE_TITLE}} Nostr Extension

Only Admin users can manage this extension

- Okay +
@@ -153,7 +155,7 @@