diff --git a/lnbits/extensions/nostrnip5/README.md b/lnbits/extensions/nostrnip5/README.md deleted file mode 100644 index 2bcbf0548..000000000 --- a/lnbits/extensions/nostrnip5/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# Nostr NIP-05 - -## Allow users to NIP-05 verify themselves at a domain you control - -This extension allows users to sell NIP-05 verification to other nostr users on a domain they control. - -## Usage - -1. Create a Domain by clicking "NEW DOMAIN"\ -2. Fill the options for your DOMAIN - - select the wallet - - select the fiat currency the invoice will be denominated in - - select an amount in fiat to charge users for verification - - enter the domain (or subdomain) you want to provide verification for - - Note, you must own this domain and have access to a web server -3. You can then use share your signup link with your users to allow them to sign up - - -## Installation - -In order for this to work, you need to have ownership of a domain name, and access to a web server that this domain is pointed to. - -Then, you'll need to set up a proxy that points `https://{your_domain}/.well-known/nostr.json` to `https://{your_lnbits}/nostrnip5/api/v1/domain/{domain_id}/nostr.json` - -Example nginx configuration - -``` -## Proxy Server Caching -proxy_cache_path /tmp/nginx_cache keys_zone=nip5_cache:5m levels=1:2 inactive=300s max_size=100m use_temp_path=off; - -location /.well-known/nostr.json { - proxy_pass https://{your_lnbits}/nostrnip5/api/v1/domain/{domain_id}/nostr.json; - proxy_set_header Host {your_lnbits}; - proxy_ssl_server_name on; - - expires 5m; - add_header Cache-Control "public, no-transform"; - - proxy_cache nip5_cache; - proxy_cache_lock on; - proxy_cache_valid 200 300s; - proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504; -} -``` - -Example Caddy configuration - -``` -my.lnbits.instance { - reverse_proxy {your_lnbits} -} - -nip.5.domain { - route /.well-known/nostr.json { - rewrite * /nostrnip5/api/v1/domain/{domain_id}/nostr.json - reverse_proxy {your_lnbits} - } -} -``` \ No newline at end of file diff --git a/lnbits/extensions/nostrnip5/__init__.py b/lnbits/extensions/nostrnip5/__init__.py deleted file mode 100644 index a9cb526d5..000000000 --- a/lnbits/extensions/nostrnip5/__init__.py +++ /dev/null @@ -1,36 +0,0 @@ -import asyncio - -from fastapi import APIRouter -from starlette.staticfiles import StaticFiles - -from lnbits.db import Database -from lnbits.helpers import template_renderer -from lnbits.tasks import catch_everything_and_restart - -db = Database("ext_nostrnip5") - -nostrnip5_static_files = [ - { - "path": "/nostrnip5/static", - "app": StaticFiles(directory="lnbits/extensions/nostrnip5/static"), - "name": "nostrnip5_static", - } -] - -nostrnip5_ext: APIRouter = APIRouter(prefix="/nostrnip5", tags=["nostrnip5"]) - - -def nostrnip5_renderer(): - return template_renderer(["lnbits/extensions/nostrnip5/templates"]) - - -from .tasks import wait_for_paid_invoices - - -def nostrnip5_start(): - loop = asyncio.get_event_loop() - loop.create_task(catch_everything_and_restart(wait_for_paid_invoices)) - - -from .views import * # noqa: F401,F403 -from .views_api import * # noqa: F401,F403 diff --git a/lnbits/extensions/nostrnip5/config.json b/lnbits/extensions/nostrnip5/config.json deleted file mode 100644 index 8621b17ce..000000000 --- a/lnbits/extensions/nostrnip5/config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Nostr NIP-5", - "short_description": "Verify addresses for Nostr NIP-5", - "tile": "/nostrnip5/static/image/nostrnip5.png", - "contributors": ["leesalminen"] -} diff --git a/lnbits/extensions/nostrnip5/crud.py b/lnbits/extensions/nostrnip5/crud.py deleted file mode 100644 index f7ec929c7..000000000 --- a/lnbits/extensions/nostrnip5/crud.py +++ /dev/null @@ -1,211 +0,0 @@ -from typing import List, Optional, Union - -from lnbits.helpers import urlsafe_short_hash - -from . import db -from .models import Address, CreateAddressData, CreateDomainData, Domain, EditDomainData - - -async def get_domain(domain_id: str) -> Optional[Domain]: - row = await db.fetchone( - "SELECT * FROM nostrnip5.domains WHERE id = ?", (domain_id,) - ) - return Domain.from_row(row) if row else None - - -async def get_domain_by_name(domain: str) -> Optional[Domain]: - row = await db.fetchone( - "SELECT * FROM nostrnip5.domains WHERE domain = ?", (domain,) - ) - return Domain.from_row(row) if row else None - - -async def get_domains(wallet_ids: Union[str, List[str]]) -> List[Domain]: - if isinstance(wallet_ids, str): - wallet_ids = [wallet_ids] - - q = ",".join(["?"] * len(wallet_ids)) - rows = await db.fetchall( - f"SELECT * FROM nostrnip5.domains WHERE wallet IN ({q})", (*wallet_ids,) - ) - - return [Domain.from_row(row) for row in rows] - - -async def get_address(domain_id: str, address_id: str) -> Optional[Address]: - row = await db.fetchone( - "SELECT * FROM nostrnip5.addresses WHERE domain_id = ? AND id = ?", - ( - domain_id, - address_id, - ), - ) - return Address.from_row(row) if row else None - - -async def get_address_by_local_part( - domain_id: str, local_part: str -) -> Optional[Address]: - row = await db.fetchone( - "SELECT * FROM nostrnip5.addresses WHERE domain_id = ? AND local_part = ?", - ( - domain_id, - local_part.lower(), - ), - ) - return Address.from_row(row) if row else None - - -async def get_addresses(domain_id: str) -> List[Address]: - rows = await db.fetchall( - "SELECT * FROM nostrnip5.addresses WHERE domain_id = ?", (domain_id,) - ) - - return [Address.from_row(row) for row in rows] - - -async def get_all_addresses(wallet_ids: Union[str, List[str]]) -> List[Address]: - if isinstance(wallet_ids, str): - wallet_ids = [wallet_ids] - - q = ",".join(["?"] * len(wallet_ids)) - rows = await db.fetchall( - f""" - SELECT a.* - FROM nostrnip5.addresses a - JOIN nostrnip5.domains d ON d.id = a.domain_id - WHERE d.wallet IN ({q}) - """, - (*wallet_ids,), - ) - - return [Address.from_row(row) for row in rows] - - -async def activate_address(domain_id: str, address_id: str) -> Address: - await db.execute( - """ - UPDATE nostrnip5.addresses - SET active = true - WHERE domain_id = ? - AND id = ? - """, - ( - domain_id, - address_id, - ), - ) - - address = await get_address(domain_id, address_id) - assert address, "Newly updated address couldn't be retrieved" - return address - - -async def rotate_address(domain_id: str, address_id: str, pubkey: str) -> Address: - await db.execute( - """ - UPDATE nostrnip5.addresses - SET pubkey = ? - WHERE domain_id = ? - AND id = ? - """, - ( - pubkey, - domain_id, - address_id, - ), - ) - - address = await get_address(domain_id, address_id) - assert address, "Newly updated address couldn't be retrieved" - return address - - -async def delete_domain(domain_id) -> bool: - await db.execute( - """ - DELETE FROM nostrnip5.addresses WHERE domain_id = ? - """, - (domain_id,), - ) - - await db.execute( - """ - DELETE FROM nostrnip5.domains WHERE id = ? - """, - (domain_id,), - ) - - return True - - -async def delete_address(address_id): - await db.execute( - """ - DELETE FROM nostrnip5.addresses WHERE id = ? - """, - (address_id,), - ) - - -async def create_address_internal(domain_id: str, data: CreateAddressData) -> Address: - address_id = urlsafe_short_hash() - - await db.execute( - """ - INSERT INTO nostrnip5.addresses (id, domain_id, local_part, pubkey, active) - VALUES (?, ?, ?, ?, ?) - """, - ( - address_id, - domain_id, - data.local_part.lower(), - data.pubkey, - False, - ), - ) - - address = await get_address(domain_id, address_id) - assert address, "Newly created address couldn't be retrieved" - return address - - -async def update_domain_internal(wallet_id: str, data: EditDomainData) -> Domain: - if data.currency != "Satoshis": - amount = data.amount * 100 - else: - amount = data.amount - print(data) - await db.execute( - """ - UPDATE nostrnip5.domains - SET amount = ?, currency = ? - WHERE id = ? - """, - (int(amount), data.currency, data.id), - ) - - domain = await get_domain(data.id) - assert domain, "Domain couldn't be updated" - return domain - - -async def create_domain_internal(wallet_id: str, data: CreateDomainData) -> Domain: - domain_id = urlsafe_short_hash() - - if data.currency != "Satoshis": - amount = data.amount * 100 - else: - amount = data.amount - - await db.execute( - """ - INSERT INTO nostrnip5.domains (id, wallet, currency, amount, domain) - VALUES (?, ?, ?, ?, ?) - """, - (domain_id, wallet_id, data.currency, int(amount), data.domain), - ) - - domain = await get_domain(domain_id) - assert domain, "Newly created domain couldn't be retrieved" - return domain diff --git a/lnbits/extensions/nostrnip5/migrations.py b/lnbits/extensions/nostrnip5/migrations.py deleted file mode 100644 index 7c5a49dd4..000000000 --- a/lnbits/extensions/nostrnip5/migrations.py +++ /dev/null @@ -1,35 +0,0 @@ -async def m001_initial_invoices(db): - - await db.execute( - f""" - CREATE TABLE nostrnip5.domains ( - id TEXT PRIMARY KEY, - wallet TEXT NOT NULL, - - currency TEXT NOT NULL, - amount INTEGER NOT NULL, - - domain TEXT NOT NULL, - - time TIMESTAMP NOT NULL DEFAULT {db.timestamp_now} - ); - """ - ) - - await db.execute( - f""" - CREATE TABLE nostrnip5.addresses ( - id TEXT PRIMARY KEY, - domain_id TEXT NOT NULL, - - local_part TEXT NOT NULL, - pubkey TEXT NOT NULL, - - active BOOLEAN NOT NULL DEFAULT false, - - time TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}, - - FOREIGN KEY(domain_id) REFERENCES {db.references_schema}domains(id) - ); - """ - ) diff --git a/lnbits/extensions/nostrnip5/models.py b/lnbits/extensions/nostrnip5/models.py deleted file mode 100644 index 7e7bf2546..000000000 --- a/lnbits/extensions/nostrnip5/models.py +++ /dev/null @@ -1,58 +0,0 @@ -from sqlite3 import Row - -from fastapi.param_functions import Query -from pydantic import BaseModel - - -class RotateAddressData(BaseModel): - pubkey: str - - -class CreateAddressData(BaseModel): - domain_id: str - local_part: str - pubkey: str - active: bool = False - - -class CreateDomainData(BaseModel): - wallet: str - currency: str - amount: float = Query(..., ge=0.01) - domain: str - - -class EditDomainData(BaseModel): - id: str - currency: str - amount: float = Query(..., ge=0.01) - - @classmethod - def from_row(cls, row: Row) -> "EditDomainData": - return cls(**dict(row)) - - -class Domain(BaseModel): - id: str - wallet: str - currency: str - amount: int - domain: str - time: int - - @classmethod - def from_row(cls, row: Row) -> "Domain": - return cls(**dict(row)) - - -class Address(BaseModel): - id: str - domain_id: str - local_part: str - pubkey: str - active: bool - time: int - - @classmethod - def from_row(cls, row: Row) -> "Address": - return cls(**dict(row)) diff --git a/lnbits/extensions/nostrnip5/static/css/signup.css b/lnbits/extensions/nostrnip5/static/css/signup.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/lnbits/extensions/nostrnip5/static/image/nostrnip5.png b/lnbits/extensions/nostrnip5/static/image/nostrnip5.png deleted file mode 100644 index 91dc47f0c..000000000 Binary files a/lnbits/extensions/nostrnip5/static/image/nostrnip5.png and /dev/null differ diff --git a/lnbits/extensions/nostrnip5/tasks.py b/lnbits/extensions/nostrnip5/tasks.py deleted file mode 100644 index f0d0c965e..000000000 --- a/lnbits/extensions/nostrnip5/tasks.py +++ /dev/null @@ -1,33 +0,0 @@ -import asyncio - -from loguru import logger - -from lnbits.core.models import Payment -from lnbits.tasks import register_invoice_listener - -from .crud import activate_address - - -async def wait_for_paid_invoices(): - invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue) - - while True: - payment = await invoice_queue.get() - await on_invoice_paid(payment) - - -async def on_invoice_paid(payment: Payment) -> None: - if payment.extra.get("tag") != "nostrnip5": - return - - domain_id = payment.extra.get("domain_id") - address_id = payment.extra.get("address_id") - - if domain_id and address_id: - logger.info("Activating NOSTR NIP-05") - logger.info(domain_id) - logger.info(address_id) - await activate_address(domain_id, address_id) - - return diff --git a/lnbits/extensions/nostrnip5/templates/nostrnip5/_api_docs.html b/lnbits/extensions/nostrnip5/templates/nostrnip5/_api_docs.html deleted file mode 100644 index a3f91201f..000000000 --- a/lnbits/extensions/nostrnip5/templates/nostrnip5/_api_docs.html +++ /dev/null @@ -1,238 +0,0 @@ - - -

- - Usage
- - 1. Create a Domain by clicking "NEW DOMAIN"\
- 2. Fill the options for your DOMAIN
- - select the wallet
- - select the fiat currency the invoice will be denominated in
- - select an amount in fiat to charge users for verification
- - enter the domain (or subdomain) you want to provide verification - for
- 3. You can then use share your signup link with your users to allow them - to sign up *Note, you must own this domain and have access to a web - server* -

- Installation
- - In order for this to work, you need to have ownership of a domain name, - and access to a web server that this domain is pointed to. Then, you'll - need to set up a proxy that points - `https://{your_domain}/.well-known/nostr.json` to - `https://{your_lnbits}/nostrnip5/api/v1/domain/{domain_id}/nostr.json` -

- Example nginx configuration -
- - - - - proxy_cache_path /tmp/nginx_cache keys_zone=nip5_cache:5m - levels=1:2 inactive=300s max_size=100m use_temp_path=off;
- - location /.well-known/nostr.json {
-     proxy_pass - https://{your_lnbits}/nostrnip5/api/v1/domain/{domain_id}/nostr.json;
-     proxy_set_header Host {your_lnbits};
-     proxy_ssl_server_name on;

- -     expires 5m;
-     add_header Cache-Control "public, - no-transform";

- -     proxy_cache nip5_cache;
-     proxy_cache_lock on;
-     proxy_cache_valid 200 300s;
-     proxy_cache_use_stale error timeout - invalid_header updating http_500 http_502 http_503 http_504;
- } -
-
-
-
-

-
-
- - - - - - - GET /nostrnip5/api/v1/domains -
Headers
- {"X-Api-Key": <invoice_key>}
-
Body (application/json)
-
- Returns 200 OK (application/json) -
- [<domain_object>, ...] -
Curl example
- curl -X GET {{ request.base_url }}nostrnip5/api/v1/domains -H - "X-Api-Key: <invoice_key>" - -
-
-
- - - - - GET /nostrnip5/api/v1/addresses -
Headers
- {"X-Api-Key": <invoice_key>}
-
Body (application/json)
-
- Returns 200 OK (application/json) -
- [<address_object>, ...] -
Curl example
- curl -X GET {{ request.base_url }}nostrnip5/api/v1/addresses -H - "X-Api-Key: <invoice_key>" - -
-
-
- - - - - GET - /nostrnip5/api/v1/domain/{domain_id} -
Headers
- {"X-Api-Key": <invoice_key>}
-
Body (application/json)
-
- Returns 200 OK (application/json) -
- {domain_object} -
Curl example
- curl -X GET {{ request.base_url }}nostrnip5/api/v1/domain/{domain_id} - -H "X-Api-Key: <invoice_key>" - -
-
-
- - - - - POST /nostrnip5/api/v1/domain -
Headers
- {"X-Api-Key": <invoice_key>}
-
Body (application/json)
-
- Returns 200 OK (application/json) -
- {domain_object} -
Curl example
- curl -X POST {{ request.base_url }}nostrnip5/api/v1/domain -H - "X-Api-Key: <invoice_key>" - -
-
-
- - - - - POST - /nostrnip5/api/v1/domain/{domain_id}/address -
Headers
- {"X-Api-Key": <invoice_key>}
-
Body (application/json)
-
- Returns 200 OK (application/json) -
- {address_object} -
Curl example
- curl -X POST {{ request.base_url - }}nostrnip5/api/v1/domain/{domain_id}/address -H "X-Api-Key: - <invoice_key>" - -
-
-
- - - - - POST - /invoices/api/v1/invoice/{invoice_id}/payments -
Headers
-
Body (application/json)
-
- Returns 200 OK (application/json) -
- {payment_object} -
Curl example
- curl -X POST {{ request.base_url - }}invoices/api/v1/invoice/{invoice_id}/payments -H "X-Api-Key: - <invoice_key>" - -
-
-
- - - - - GET - /nostrnip5/api/v1/domain/{domain_id}/payments/{payment_hash} -
Headers
-
Body (application/json)
-
- Returns 200 OK (application/json) -
-
Curl example
- curl -X GET {{ request.base_url - }}nostrnip5/api/v1/domain/{domain_id}/payments/{payment_hash} -H - "X-Api-Key: <invoice_key>" - -
-
-
-
diff --git a/lnbits/extensions/nostrnip5/templates/nostrnip5/index.html b/lnbits/extensions/nostrnip5/templates/nostrnip5/index.html deleted file mode 100644 index 8ebaa5027..000000000 --- a/lnbits/extensions/nostrnip5/templates/nostrnip5/index.html +++ /dev/null @@ -1,785 +0,0 @@ -{% extends "base.html" %} {% from "macros.jinja" import window_vars with context -%} {% block page %} -
-
- - - New Domain - New Address - - - - - -
-
-
Domains
-
-
- Export to CSV -
-
- - {% raw %} - - - - {% endraw %} - -
-
- - - -
-
-
Addresses
-
-
- Export to CSV -
-
- - {% raw %} - - - - {% endraw %} - -
-
-
- -
- - -
- {{SITE_TITLE}} Nostr NIP-5 extension -
-

- Allow users to NIP-05 verify themselves at a domain you - control -

-
- - - {% include "nostrnip5/_api_docs.html" %} - -
-
- - - - - - - - - -
- Create Domain - Cancel -
-
-
-
- - - - - - -
- Update Amount - Cancel -
-
-
-
- - - - - - - - -
- Create Address - Cancel -
-
-
-
-
-{% endblock %} {% block scripts %} {{ window_vars(user) }} - -{% endblock %} diff --git a/lnbits/extensions/nostrnip5/templates/nostrnip5/rotate.html b/lnbits/extensions/nostrnip5/templates/nostrnip5/rotate.html deleted file mode 100644 index c9011507b..000000000 --- a/lnbits/extensions/nostrnip5/templates/nostrnip5/rotate.html +++ /dev/null @@ -1,88 +0,0 @@ -{% extends "public.html" %} {% block toolbar_title %} Rotate Keys For {{ -domain.domain }} {% endblock %} {% from "macros.jinja" import window_vars with -context %} {% block page %} - -
- - -

- You can use this page to change the public key associated with your - NIP-5 identity. -

-

- Your current NIP-5 identity is {{ address.local_part }}@{{ domain.domain - }} with nostr public key {{ address.pubkey }}. -

- -

Input your new pubkey below to update it.

- - - - -
- Rotate Keys -
-
-
-
-{% endblock %} {% block scripts %} - -{% endblock %} diff --git a/lnbits/extensions/nostrnip5/templates/nostrnip5/signup.html b/lnbits/extensions/nostrnip5/templates/nostrnip5/signup.html deleted file mode 100644 index 152948171..000000000 --- a/lnbits/extensions/nostrnip5/templates/nostrnip5/signup.html +++ /dev/null @@ -1,209 +0,0 @@ -{% extends "public.html" %} {% block toolbar_title %} Verify NIP-5 For {{ -domain.domain }} {% endblock %} {% from "macros.jinja" import window_vars with -context %} {% block page %} - -
- - {% raw %} -

- Success! Your username is now active at {{ successData.local_part }}@{{ - domain }}. Please add this to your nostr profile accordingly. If you ever - need to rotate your keys, you can still keep your identity! -

- -

Important!

-

- Bookmark this link: - {{ base_url }}nostrnip5/rotate/{{ domain_id }}/{{ - successData.address_id }} -

-

- In case you ever need to change your pubkey, you can still keep this NIP-5 - identity. Just come back to the above linked page to change the pubkey - associated to your identity. -

- {% endraw %} -
- - -

- You can use this page to get NIP-5 verified on the nostr protocol under - the {{ domain.domain }} domain. -

-

- The current price is {% if domain.currency != "Satoshis" %} - {{ "{:0,.2f}".format(domain.amount / 100) }} {{ domain.currency }} - {% else %} - {{ "{}".format(domain.amount) }} {{ domain.currency }} - {% endif %} for an account (if you do not own the domain, the service - provider can disable at any time). -

- -

After submitting payment, your address will be

- - - - - -

and will be tied to this nostr pubkey

- - - - -
- Create Address -
-
-
- - - - - - - - -
- Copy Invoice -
-
-
-{% endblock %} {% block scripts %} - -{% endblock %} diff --git a/lnbits/extensions/nostrnip5/views.py b/lnbits/extensions/nostrnip5/views.py deleted file mode 100644 index 40f498c1d..000000000 --- a/lnbits/extensions/nostrnip5/views.py +++ /dev/null @@ -1,67 +0,0 @@ -from http import HTTPStatus - -from fastapi import Depends, Request -from fastapi.templating import Jinja2Templates -from starlette.exceptions import HTTPException -from starlette.responses import HTMLResponse - -from lnbits.core.models import User -from lnbits.decorators import check_user_exists - -from . import nostrnip5_ext, nostrnip5_renderer -from .crud import get_address, get_domain - -templates = Jinja2Templates(directory="templates") - - -@nostrnip5_ext.get("/", response_class=HTMLResponse) -async def index(request: Request, user: User = Depends(check_user_exists)): - return nostrnip5_renderer().TemplateResponse( - "nostrnip5/index.html", {"request": request, "user": user.dict()} - ) - - -@nostrnip5_ext.get("/signup/{domain_id}", response_class=HTMLResponse) -async def signup(request: Request, domain_id: str): - domain = await get_domain(domain_id) - - if not domain: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Domain does not exist." - ) - - return nostrnip5_renderer().TemplateResponse( - "nostrnip5/signup.html", - { - "request": request, - "domain_id": domain_id, - "domain": domain, - }, - ) - - -@nostrnip5_ext.get("/rotate/{domain_id}/{address_id}", response_class=HTMLResponse) -async def rotate(request: Request, domain_id: str, address_id: str): - domain = await get_domain(domain_id) - address = await get_address(domain_id, address_id) - - if not domain: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Domain does not exist." - ) - - if not address: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Address does not exist." - ) - - return nostrnip5_renderer().TemplateResponse( - "nostrnip5/rotate.html", - { - "request": request, - "domain_id": domain_id, - "domain": domain, - "address_id": address_id, - "address": address, - }, - ) diff --git a/lnbits/extensions/nostrnip5/views_api.py b/lnbits/extensions/nostrnip5/views_api.py deleted file mode 100644 index aa5dc887b..000000000 --- a/lnbits/extensions/nostrnip5/views_api.py +++ /dev/null @@ -1,284 +0,0 @@ -import re -from http import HTTPStatus - -from bech32 import bech32_decode, convertbits -from fastapi import Depends, Query, Response -from loguru import logger -from starlette.exceptions import HTTPException - -from lnbits.core.crud import get_user -from lnbits.core.services import create_invoice -from lnbits.core.views.api import api_payment -from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key -from lnbits.utils.exchange_rates import fiat_amount_as_satoshis - -from . import nostrnip5_ext -from .crud import ( - activate_address, - create_address_internal, - create_domain_internal, - delete_address, - delete_domain, - get_address_by_local_part, - get_addresses, - get_all_addresses, - get_domain, - get_domain_by_name, - get_domains, - rotate_address, - update_domain_internal, -) -from .models import ( - CreateAddressData, - CreateDomainData, - EditDomainData, - RotateAddressData, -) - - -@nostrnip5_ext.get("/api/v1/domains", status_code=HTTPStatus.OK) -async def api_domains( - all_wallets: bool = Query(None), wallet: WalletTypeInfo = Depends(get_key_type) -): - wallet_ids = [wallet.wallet.id] - if all_wallets: - user = await get_user(wallet.wallet.user) - if not user: - return [] - wallet_ids = user.wallet_ids - - return [domain.dict() for domain in await get_domains(wallet_ids)] - - -@nostrnip5_ext.get("/api/v1/addresses", status_code=HTTPStatus.OK) -async def api_addresses( - all_wallets: bool = Query(None), wallet: WalletTypeInfo = Depends(get_key_type) -): - wallet_ids = [wallet.wallet.id] - if all_wallets: - user = await get_user(wallet.wallet.user) - if not user: - return [] - wallet_ids = user.wallet_ids - - return [address.dict() for address in await get_all_addresses(wallet_ids)] - - -@nostrnip5_ext.get( - "/api/v1/domain/{domain_id}", - status_code=HTTPStatus.OK, - dependencies=[Depends(get_key_type)], -) -async def api_invoice(domain_id: str): - domain = await get_domain(domain_id) - if not domain: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Domain does not exist." - ) - - return domain - - -@nostrnip5_ext.post("/api/v1/domain", status_code=HTTPStatus.CREATED) -async def api_domain_create( - data: CreateDomainData, wallet: WalletTypeInfo = Depends(get_key_type) -): - exists = await get_domain_by_name(data.domain) - logger.error(exists) - if exists: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Domain already exists." - ) - - domain = await create_domain_internal(wallet_id=wallet.wallet.id, data=data) - - return domain - - -@nostrnip5_ext.put("/api/v1/domain", status_code=HTTPStatus.OK) -async def api_domain_update( - data: EditDomainData, wallet: WalletTypeInfo = Depends(get_key_type) -): - - domain = await update_domain_internal(wallet_id=wallet.wallet.id, data=data) - - return domain - - -@nostrnip5_ext.delete("/api/v1/domain/{domain_id}", status_code=HTTPStatus.CREATED) -async def api_domain_delete( - domain_id: str, - wallet: WalletTypeInfo = Depends(require_admin_key), -): - await delete_domain(domain_id) - - return True - - -@nostrnip5_ext.delete("/api/v1/address/{address_id}", status_code=HTTPStatus.CREATED) -async def api_address_delete( - address_id: str, - wallet: WalletTypeInfo = Depends(require_admin_key), -): - await delete_address(address_id) - - return True - - -@nostrnip5_ext.post( - "/api/v1/domain/{domain_id}/address/{address_id}/activate", - status_code=HTTPStatus.OK, - dependencies=[Depends(require_admin_key)], -) -async def api_address_activate( - domain_id: str, - address_id: str, -): - await activate_address(domain_id, address_id) - - return True - - -@nostrnip5_ext.post( - "/api/v1/domain/{domain_id}/address/{address_id}/rotate", - status_code=HTTPStatus.OK, -) -async def api_address_rotate( - domain_id: str, - address_id: str, - post_data: RotateAddressData, -): - - if post_data.pubkey.startswith("npub"): - _, data = bech32_decode(post_data.pubkey) - if data: - decoded_data = convertbits(data, 5, 8, False) - if decoded_data: - post_data.pubkey = bytes(decoded_data).hex() - - if len(bytes.fromhex(post_data.pubkey)) != 32: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Pubkey must be in hex format." - ) - - await rotate_address(domain_id, address_id, post_data.pubkey) - - return True - - -@nostrnip5_ext.post( - "/api/v1/domain/{domain_id}/address", status_code=HTTPStatus.CREATED -) -async def api_address_create( - post_data: CreateAddressData, - domain_id: str, -): - domain = await get_domain(domain_id) - - if not domain: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Domain does not exist." - ) - - if post_data.local_part == "_": - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="You're sneaky, nice try." - ) - - regex = re.compile(r"^[a-z0-9_.]+$") - if not re.fullmatch(regex, post_data.local_part.lower()): - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, - detail="Only a-z, 0-9 and .-_ are allowed characters, case insensitive.", - ) - - exists = await get_address_by_local_part(domain_id, post_data.local_part) - - if exists: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Local part already exists." - ) - - if post_data and post_data.pubkey.startswith("npub"): - _, data = bech32_decode(post_data.pubkey) - if data: - decoded_data = convertbits(data, 5, 8, False) - if decoded_data: - post_data.pubkey = bytes(decoded_data).hex() - - if len(bytes.fromhex(post_data.pubkey)) != 32: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Pubkey must be in hex format." - ) - - address = await create_address_internal(domain_id=domain_id, data=post_data) - if domain.currency == "Satoshis": - price_in_sats = domain.amount - else: - price_in_sats = await fiat_amount_as_satoshis( - domain.amount / 100, domain.currency - ) - - try: - payment_hash, payment_request = await create_invoice( - wallet_id=domain.wallet, - amount=price_in_sats, - memo=f"Payment for NIP-05 for {address.local_part}@{domain.domain}", - extra={ - "tag": "nostrnip5", - "domain_id": domain_id, - "address_id": address.id, - }, - ) - except Exception as e: - raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e)) - - return { - "payment_hash": payment_hash, - "payment_request": payment_request, - "address_id": address.id, - } - - -@nostrnip5_ext.get( - "/api/v1/domain/{domain_id}/payments/{payment_hash}", status_code=HTTPStatus.OK -) -async def api_nostrnip5_check_payment(domain_id: str, payment_hash: str): - domain = await get_domain(domain_id) - if not domain: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Domain does not exist." - ) - try: - status = await api_payment(payment_hash) - - except Exception as exc: - logger.error(exc) - return {"paid": False} - return status - - -@nostrnip5_ext.get("/api/v1/domain/{domain_id}/nostr.json", status_code=HTTPStatus.OK) -async def api_get_nostr_json( - response: Response, domain_id: str, name: str = Query(None) -): - addresses = [address.dict() for address in await get_addresses(domain_id)] - output = {} - - for address in addresses: - local_part = address.get("local_part") - if not local_part: - continue - - if address.get("active") is False: - continue - - if name and name.lower() != local_part.lower(): - continue - - output[local_part.lower()] = address.get("pubkey") - - response.headers["Access-Control-Allow-Origin"] = "*" - response.headers["Access-Control-Allow-Methods"] = "GET,OPTIONS" - - return {"names": output}