Chnaged nostr to nostradmin

This commit is contained in:
benarc 2022-02-08 10:35:20 +00:00
parent 89c3c1b272
commit 29bd8d9ce9
13 changed files with 421 additions and 160 deletions

View file

@ -8,7 +8,7 @@ PORT=5000
LNBITS_ALLOWED_USERS="" LNBITS_ALLOWED_USERS=""
LNBITS_ADMIN_USERS="" LNBITS_ADMIN_USERS=""
# Extensions only admin can access # Extensions only admin can access
LNBITS_ADMIN_EXTENSIONS="ngrok" LNBITS_ADMIN_EXTENSIONS="nostradmin"
LNBITS_DEFAULT_WALLET_NAME="LNbits wallet" LNBITS_DEFAULT_WALLET_NAME="LNbits wallet"
# Disable extensions for all users, use "all" to disable all extensions # Disable extensions for all users, use "all" to disable all extensions

View file

@ -1,6 +0,0 @@
{
"name": "Nostr",
"short_description": "Daemon for Nostr",
"icon": "swap_horizontal_circle",
"contributors": ["arcbtc"]
}

View file

@ -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

View file

@ -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

View file

@ -3,13 +3,14 @@ from fastapi import APIRouter
from lnbits.db import Database from lnbits.db import Database
from lnbits.helpers import template_renderer 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(): def nostr_renderer():
return template_renderer(["lnbits/extensions/nostr/templates"]) return template_renderer(["lnbits/extensions/nostradmin/templates"])
from .views import * # noqa from .views import * # noqa
from .views_api import * # noqa from .views_api import * # noqa

View file

@ -0,0 +1,6 @@
{
"name": "NostrAdmin",
"short_description": "Admin daemon for Nostr",
"icon": "swap_horizontal_circle",
"contributors": ["arcbtc"]
}

View file

@ -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

View file

@ -9,7 +9,7 @@ async def m001_initial(db):
""" """
await db.execute( await db.execute(
f""" f"""
CREATE TABLE nostr.keys ( CREATE TABLE nostradmin.keys (
pubkey TEXT NOT NULL PRIMARY KEY, pubkey TEXT NOT NULL PRIMARY KEY,
privkey TEXT NOT NULL privkey TEXT NOT NULL
); );
@ -17,7 +17,7 @@ async def m001_initial(db):
) )
await db.execute( await db.execute(
f""" f"""
CREATE TABLE nostr.notes ( CREATE TABLE nostradmin.notes (
id TEXT NOT NULL PRIMARY KEY, id TEXT NOT NULL PRIMARY KEY,
pubkey TEXT NOT NULL, pubkey TEXT NOT NULL,
created_at TEXT NOT NULL, created_at TEXT NOT NULL,
@ -30,7 +30,7 @@ async def m001_initial(db):
) )
await db.execute( await db.execute(
f""" f"""
CREATE TABLE nostr.relays ( CREATE TABLE nostradmin.relays (
id TEXT NOT NULL PRIMARY KEY, id TEXT NOT NULL PRIMARY KEY,
relay TEXT NOT NULL relay TEXT NOT NULL
); );
@ -38,10 +38,32 @@ async def m001_initial(db):
) )
await db.execute( await db.execute(
f""" 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, id TEXT NOT NULL PRIMARY KEY,
publickey TEXT NOT NULL, publickey TEXT NOT NULL,
relayid TEXT NOT NULL relayid TEXT NOT NULL
); );
""" """
) )

View file

@ -6,6 +6,7 @@ from fastapi import Request
from pydantic import BaseModel from pydantic import BaseModel
from pydantic.main import BaseModel from pydantic.main import BaseModel
class nostrKeys(BaseModel): class nostrKeys(BaseModel):
pubkey: str pubkey: str
privkey: str privkey: str
@ -30,7 +31,18 @@ class nostrRelays(BaseModel):
id: str id: str
relay: 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): class nostrConnections(BaseModel):
id: str id: str
pubkey: str pubkey: str
relayid: str relayid: str

View file

@ -71,12 +71,77 @@
</q-table> </q-table>
</q-card-section> </q-card-section>
</q-card> </q-card>
<q-card>
<q-tabs
v-model="listSelection"
dense
class="text-grey"
active-color="primary"
indicator-color="primary"
align="justify"
narrow-indicator
>
<q-tab name="denylist" label="Deny List" />
<q-tab name="allowlist" label="Allow List" />
</q-tabs>
<q-separator />
<q-tab-panels v-model="tab" animated>
<q-tab-panel name="denylist">
<div class="text-h6">
Deny List (denys use of relays in this list)
</div>
<q-form class="q-gutter-md q-y-md" @submit="setDenyList">
<div class="row">
<div class="col q-mx-lg">
<q-input v-model="denyList" dense filled autogrow />
</div>
<div
class="col q-mx-lg items-align flex items-center justify-center"
>
<q-btn unelevated color="primary" type="submit">
Update Deny List
</q-btn>
<q-btn @click="loadShop" flat color="grey" class="q-ml-auto"
>Reset</q-btn
>
</div>
</div>
</q-form>
</q-tab-panel>
<q-tab-panel name="allowlist">
<div class="text-h6">
Allow List (denys any relays not in this list)
</div>
<q-form class="q-gutter-md q-y-md" @submit="setAllowList">
<div class="row">
<div class="col q-mx-lg">
<q-input v-model="allowList" dense filled autogrow />
</div>
<div
class="col q-mx-lg items-align flex items-center justify-center"
>
<q-btn unelevated color="primary" type="submit">
Update Allow List
</q-btn>
<q-btn @click="loadShop" flat color="grey" class="q-ml-auto"
>Reset</q-btn
>
</div>
</div>
</q-form>
</q-tab-panel>
</q-tab-panels>
</q-card>
</div> </div>
<div class="col-12 col-md-5 q-gutter-y-md"> <div class="col-12 col-md-5 q-gutter-y-md">
<q-card> <q-card>
<q-card-section> <q-card-section>
<h6 class="text-subtitle1 q-my-none">{{SITE_TITLE}} Nostr Extension</h6> <h6 class="text-subtitle1 q-my-none">{{SITE_TITLE}} Nostr Extension</h6>
<p>Only Admin users can manage this extension</p>
<q-card-section>Okay </q-card-section> <q-card-section>Okay </q-card-section>
</q-card-section> </q-card-section>
@ -109,6 +174,9 @@
mixins: [windowMixin], mixins: [windowMixin],
data: function () { data: function () {
return { return {
listSelection: 'denylist',
allowList: [],
denyList: [],
nostrTable: { nostrTable: {
columns: [ columns: [
{ {
@ -136,12 +204,66 @@
LNbits.api LNbits.api
.request( .request(
'GET', 'GET',
'/nostr/api/v1/relays', '/nostradmin/api/v1/relays',
self.g.user.wallets[0].adminkey self.g.user.wallets[0].adminkey
) )
.then(function (response) { .then(function (response) {
if (response.data) { 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) { .catch(function (error) {
@ -151,8 +273,8 @@
}, },
created: function () { created: function () {
var self = this var self = this
var getrelays = this.getrelays this.getrelays()
getrelays() this.getLists()
} }
}) })
</script> </script>

View file

@ -20,5 +20,5 @@ templates = Jinja2Templates(directory="templates")
@nostr_ext.get("/", response_class=HTMLResponse) @nostr_ext.get("/", response_class=HTMLResponse)
async def index(request: Request, user: User = Depends(check_user_exists)): async def index(request: Request, user: User = Depends(check_user_exists)):
return nostr_renderer().TemplateResponse( return nostr_renderer().TemplateResponse(
"nostr/index.html", {"request": request, "user": user.dict()} "nostradmin/index.html", {"request": request, "user": user.dict()}
) )

View file

@ -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)