mirror of
https://github.com/lnbits/lnbits.git
synced 2025-04-21 22:14:57 +02:00
Admin users can credit accounts
This commit is contained in:
parent
f5069f9699
commit
dbab181759
@ -11,6 +11,7 @@ from decimal import Decimal
|
||||
import embit
|
||||
import secp256k1
|
||||
|
||||
|
||||
class Route(NamedTuple):
|
||||
pubkey: str
|
||||
short_channel_id: str
|
||||
@ -120,8 +121,7 @@ def decode(pr: str) -> Invoice:
|
||||
|
||||
|
||||
def encode(options):
|
||||
""" Convert options into LnAddr and pass it to the encoder
|
||||
"""
|
||||
"""Convert options into LnAddr and pass it to the encoder"""
|
||||
addr = LnAddr()
|
||||
addr.currency = options.currency
|
||||
addr.fallback = options.fallback if options.fallback else None
|
||||
@ -268,11 +268,10 @@ class LnAddr(object):
|
||||
|
||||
|
||||
def shorten_amount(amount):
|
||||
""" Given an amount in bitcoin, shorten it
|
||||
"""
|
||||
"""Given an amount in bitcoin, shorten it"""
|
||||
# Convert to pico initially
|
||||
amount = int(amount * 10**12)
|
||||
units = ['p', 'n', 'u', 'm', '']
|
||||
amount = int(amount * 10 ** 12)
|
||||
units = ["p", "n", "u", "m", ""]
|
||||
for unit in units:
|
||||
if amount % 1000 == 0:
|
||||
amount //= 1000
|
||||
@ -280,6 +279,7 @@ def shorten_amount(amount):
|
||||
break
|
||||
return str(amount) + unit
|
||||
|
||||
|
||||
def _unshorten_amount(amount: str) -> int:
|
||||
"""Given a shortened amount, return millisatoshis"""
|
||||
# BOLT #11:
|
||||
@ -313,21 +313,31 @@ def _pull_tagged(stream):
|
||||
def is_p2pkh(currency, prefix):
|
||||
return prefix == base58_prefix_map[currency][0]
|
||||
|
||||
|
||||
def is_p2sh(currency, prefix):
|
||||
return prefix == base58_prefix_map[currency][1]
|
||||
|
||||
|
||||
# Tagged field containing BitArray
|
||||
def tagged(char, l):
|
||||
# Tagged fields need to be zero-padded to 5 bits.
|
||||
while l.len % 5 != 0:
|
||||
l.append('0b0')
|
||||
return bitstring.pack("uint:5, uint:5, uint:5",
|
||||
CHARSET.find(char),
|
||||
(l.len / 5) / 32, (l.len / 5) % 32) + l
|
||||
l.append("0b0")
|
||||
return (
|
||||
bitstring.pack(
|
||||
"uint:5, uint:5, uint:5",
|
||||
CHARSET.find(char),
|
||||
(l.len / 5) / 32,
|
||||
(l.len / 5) % 32,
|
||||
)
|
||||
+ l
|
||||
)
|
||||
|
||||
|
||||
def tagged_bytes(char, l):
|
||||
return tagged(char, bitstring.BitArray(l))
|
||||
|
||||
|
||||
def _trim_to_bytes(barr):
|
||||
# Adds a byte if necessary.
|
||||
b = barr.tobytes()
|
||||
@ -338,9 +348,9 @@ def _trim_to_bytes(barr):
|
||||
|
||||
def _readable_scid(short_channel_id: int) -> str:
|
||||
return "{blockheight}x{transactionindex}x{outputindex}".format(
|
||||
blockheight=((short_channel_id >> 40) & 0xFFFFFF),
|
||||
transactionindex=((short_channel_id >> 16) & 0xFFFFFF),
|
||||
outputindex=(short_channel_id & 0xFFFF),
|
||||
blockheight=((short_channel_id >> 40) & 0xffffff),
|
||||
transactionindex=((short_channel_id >> 16) & 0xffffff),
|
||||
outputindex=(short_channel_id & 0xffff),
|
||||
)
|
||||
|
||||
|
||||
@ -350,10 +360,11 @@ def _u5_to_bitarray(arr: List[int]) -> bitstring.BitArray:
|
||||
ret += bitstring.pack("uint:5", a)
|
||||
return ret
|
||||
|
||||
|
||||
def bitarray_to_u5(barr):
|
||||
assert barr.len % 5 == 0
|
||||
ret = []
|
||||
s = bitstring.ConstBitStream(barr)
|
||||
while s.pos != s.len:
|
||||
ret.append(s.read(5).uint)
|
||||
return ret
|
||||
return ret
|
||||
|
@ -11,7 +11,6 @@ from lnbits.settings import DEFAULT_WALLET_NAME
|
||||
from . import db
|
||||
from .models import User, Wallet, Payment, BalanceCheck
|
||||
|
||||
|
||||
# accounts
|
||||
# --------
|
||||
|
||||
@ -278,7 +277,9 @@ async def get_payments(
|
||||
return [Payment.from_row(row) for row in rows]
|
||||
|
||||
|
||||
async def delete_expired_invoices(conn: Optional[Connection] = None,) -> None:
|
||||
async def delete_expired_invoices(
|
||||
conn: Optional[Connection] = None,
|
||||
) -> None:
|
||||
# first we delete all invoices older than one month
|
||||
await (conn or db).execute(
|
||||
f"""
|
||||
|
@ -57,6 +57,7 @@ class User(BaseModel):
|
||||
extensions: List[str] = []
|
||||
wallets: List[Wallet] = []
|
||||
password: Optional[str] = None
|
||||
admin: bool = False
|
||||
|
||||
@property
|
||||
def wallet_ids(self) -> List[str]:
|
||||
|
@ -249,6 +249,29 @@ new Vue({
|
||||
this.parse.data.paymentChecker = null
|
||||
this.parse.camera.show = false
|
||||
},
|
||||
updateBalance: function(scopeValue){
|
||||
LNbits.api
|
||||
.request(
|
||||
'PUT',
|
||||
'/api/v1/wallet/balance/' + scopeValue,
|
||||
this.g.wallet.inkey
|
||||
)
|
||||
.catch(err => {
|
||||
LNbits.utils.notifyApiError(err)
|
||||
})
|
||||
.then(response => {
|
||||
let data = response.data
|
||||
if (data.status === 'ERROR') {
|
||||
this.$q.notify({
|
||||
timeout: 5000,
|
||||
type: 'warning',
|
||||
message: `Failed to update.`,
|
||||
})
|
||||
return
|
||||
}
|
||||
this.balance = this.balance + data.balance
|
||||
})
|
||||
},
|
||||
closeReceiveDialog: function () {
|
||||
setTimeout(() => {
|
||||
clearInterval(this.receive.paymentChecker)
|
||||
|
@ -15,7 +15,31 @@
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<h3 class="q-my-none">
|
||||
<strong>{% raw %}{{ formattedBalance }}{% endraw %}</strong> sat
|
||||
<strong>{% raw %}{{ formattedBalance }}{% endraw %} </strong>
|
||||
sat
|
||||
<q-btn
|
||||
v-if="'{{user.admin}}' == 'True'"
|
||||
flat
|
||||
round
|
||||
color="primary"
|
||||
icon="add"
|
||||
size="md"
|
||||
>
|
||||
<q-popup-edit class="bg-accent text-white" v-slot="scope">
|
||||
<q-input
|
||||
label="Amount to credit account"
|
||||
v-model="scope.value"
|
||||
dense
|
||||
autofocus
|
||||
type="number"
|
||||
@keyup.enter="updateBalance(scope.value)"
|
||||
>
|
||||
<template v-slot:append>
|
||||
<q-icon name="edit" />
|
||||
</template>
|
||||
</q-input>
|
||||
</q-popup-edit>
|
||||
</q-btn>
|
||||
</h3>
|
||||
</q-card-section>
|
||||
<div class="row q-pb-md q-px-md q-col-gutter-md">
|
||||
|
@ -38,6 +38,9 @@ from ..crud import (
|
||||
get_standalone_payment,
|
||||
save_balance_check,
|
||||
update_wallet,
|
||||
create_payment,
|
||||
get_wallet,
|
||||
update_payment_status,
|
||||
)
|
||||
from ..services import (
|
||||
InvoiceFailure,
|
||||
@ -48,6 +51,8 @@ from ..services import (
|
||||
perform_lnurlauth,
|
||||
)
|
||||
from ..tasks import api_invoice_listeners
|
||||
from lnbits.settings import LNBITS_ADMIN_USERS
|
||||
from lnbits.helpers import urlsafe_short_hash
|
||||
|
||||
|
||||
@core_app.get("/api/v1/wallet")
|
||||
@ -62,6 +67,35 @@ async def api_wallet(wallet: WalletTypeInfo = Depends(get_key_type)):
|
||||
return {"name": wallet.wallet.name, "balance": wallet.wallet.balance_msat}
|
||||
|
||||
|
||||
@core_app.put("/api/v1/wallet/balance/{amount}")
|
||||
async def api_update_balance(
|
||||
amount: int, wallet: WalletTypeInfo = Depends(get_key_type)
|
||||
):
|
||||
if LNBITS_ADMIN_USERS and wallet.wallet.user not in LNBITS_ADMIN_USERS:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.FORBIDDEN, detail="Not an admin user"
|
||||
)
|
||||
|
||||
payHash = urlsafe_short_hash()
|
||||
await create_payment(
|
||||
wallet_id=wallet.wallet.id,
|
||||
checking_id=payHash,
|
||||
payment_request="selfPay",
|
||||
payment_hash=payHash,
|
||||
amount=amount*1000,
|
||||
memo="selfPay",
|
||||
fee=0,
|
||||
)
|
||||
await update_payment_status(checking_id=payHash, pending=False)
|
||||
updatedWallet = await get_wallet(wallet.wallet.id)
|
||||
|
||||
return {
|
||||
"id": wallet.wallet.id,
|
||||
"name": wallet.wallet.name,
|
||||
"balance": wallet.wallet.balance_msat + amount,
|
||||
}
|
||||
|
||||
|
||||
@core_app.put("/api/v1/wallet/{new_name}")
|
||||
async def api_update_wallet(
|
||||
new_name: str, wallet: WalletTypeInfo = Depends(WalletAdminKeyChecker())
|
||||
|
@ -14,7 +14,12 @@ from lnbits.core import db
|
||||
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_SITE_TITLE, SERVICE_FEE
|
||||
from lnbits.settings import (
|
||||
LNBITS_ALLOWED_USERS,
|
||||
LNBITS_ADMIN_USERS,
|
||||
LNBITS_SITE_TITLE,
|
||||
SERVICE_FEE,
|
||||
)
|
||||
|
||||
from ..crud import (
|
||||
create_account,
|
||||
@ -113,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]
|
||||
|
@ -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
|
||||
from lnbits.settings import LNBITS_ALLOWED_USERS, LNBITS_ADMIN_USERS
|
||||
|
||||
|
||||
class KeyChecker(SecurityBase):
|
||||
@ -204,4 +204,7 @@ async def check_user_exists(usr: UUID4) -> User:
|
||||
status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
|
||||
)
|
||||
|
||||
if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS:
|
||||
g().user.admin = True
|
||||
|
||||
return g().user
|
||||
|
@ -160,7 +160,7 @@ async def set_address_renewed(address_id: str, duration: int):
|
||||
|
||||
|
||||
async def check_address_available(username: str, domain: str):
|
||||
row, = await db.fetchone(
|
||||
(row,) = await db.fetchone(
|
||||
"SELECT COUNT(username) FROM lnaddress.address WHERE username = ? AND domain = ?",
|
||||
(username, domain),
|
||||
)
|
||||
|
@ -108,7 +108,9 @@ async def lndhub_payinvoice(
|
||||
|
||||
|
||||
@lndhub_ext.get("/ext/balance")
|
||||
async def lndhub_balance(wallet: WalletTypeInfo = Depends(check_wallet),):
|
||||
async def lndhub_balance(
|
||||
wallet: WalletTypeInfo = Depends(check_wallet),
|
||||
):
|
||||
return {"BTC": {"AvailableBalance": wallet.wallet.balance}}
|
||||
|
||||
|
||||
|
@ -8,7 +8,9 @@ from .models import createLnurldevice, lnurldevicepayment, lnurldevices
|
||||
###############lnurldeviceS##########################
|
||||
|
||||
|
||||
async def create_lnurldevice(data: createLnurldevice,) -> lnurldevices:
|
||||
async def create_lnurldevice(
|
||||
data: createLnurldevice,
|
||||
) -> lnurldevices:
|
||||
lnurldevice_id = urlsafe_short_hash()
|
||||
lnurldevice_key = urlsafe_short_hash()
|
||||
await db.execute(
|
||||
|
@ -8,8 +8,8 @@ def hotp(key, counter, digits=6, digest="sha1"):
|
||||
key = base64.b32decode(key.upper() + "=" * ((8 - len(key)) % 8))
|
||||
counter = struct.pack(">Q", counter)
|
||||
mac = hmac.new(key, counter, digest).digest()
|
||||
offset = mac[-1] & 0x0F
|
||||
binary = struct.unpack(">L", mac[offset : offset + 4])[0] & 0x7FFFFFFF
|
||||
offset = mac[-1] & 0x0f
|
||||
binary = struct.unpack(">L", mac[offset : offset + 4])[0] & 0x7fffffff
|
||||
return str(binary)[-digits:].zfill(digits)
|
||||
|
||||
|
||||
|
@ -28,6 +28,7 @@ LNBITS_DATABASE_URL = env.str("LNBITS_DATABASE_URL", default=None)
|
||||
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_DISABLED_EXTENSIONS: List[str] = env.list(
|
||||
"LNBITS_DISABLED_EXTENSIONS", default=[], subcast=str
|
||||
)
|
||||
|
@ -37,6 +37,7 @@ class FakeWallet(Wallet):
|
||||
"The FakeWallet backend is for using LNbits as a centralised, stand-alone payment system."
|
||||
)
|
||||
return StatusResponse(None, float("inf"))
|
||||
|
||||
async def create_invoice(
|
||||
self,
|
||||
amount: int,
|
||||
@ -54,7 +55,9 @@ class FakeWallet(Wallet):
|
||||
self.memo = memo
|
||||
self.description = memo
|
||||
letters = string.ascii_lowercase
|
||||
randomHash = hashlib.sha256(str(random.getrandbits(256)).encode('utf-8')).hexdigest()
|
||||
randomHash = hashlib.sha256(
|
||||
str(random.getrandbits(256)).encode("utf-8")
|
||||
).hexdigest()
|
||||
self.paymenthash = randomHash
|
||||
payment_request = encode(self)
|
||||
print(payment_request)
|
||||
@ -62,7 +65,6 @@ class FakeWallet(Wallet):
|
||||
|
||||
return InvoiceResponse(True, checking_id, payment_request)
|
||||
|
||||
|
||||
async def pay_invoice(self, bolt11: str) -> PaymentResponse:
|
||||
invoice = decode(bolt11)
|
||||
return PaymentResponse(True, invoice.payment_hash, 0)
|
||||
|
Loading…
x
Reference in New Issue
Block a user