Reset wallet keys (#2929)

This commit is contained in:
Tiago Vasconcelos 2025-02-06 13:07:36 +00:00 committed by GitHub
parent 34a959f0bc
commit 432b3a0fe0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 94 additions and 27 deletions

View file

@ -233,4 +233,14 @@
</q-card-section> </q-card-section>
</q-card> </q-card>
</q-expansion-item> </q-expansion-item>
<q-separator></q-separator>
<q-card-section>
<p v-text="$t('reset_wallet_keys_desc')"></p>
<q-btn
unelevated
color="red-10"
@click="resetKeys()"
:label="$t('reset_wallet_keys')"
></q-btn>
</q-card-section>
</q-expansion-item> </q-expansion-item>

View file

@ -1,5 +1,6 @@
from http import HTTPStatus from http import HTTPStatus
from typing import Optional from typing import Optional
from uuid import uuid4
from fastapi import ( from fastapi import (
APIRouter, APIRouter,
@ -8,13 +9,10 @@ from fastapi import (
HTTPException, HTTPException,
) )
from lnbits.core.models import ( from lnbits.core.models import CreateWallet, KeyType, User, Wallet
CreateWallet,
KeyType,
Wallet,
)
from lnbits.decorators import ( from lnbits.decorators import (
WalletTypeInfo, WalletTypeInfo,
check_user_exists,
require_admin_key, require_admin_key,
require_invoice_key, require_invoice_key,
) )
@ -56,6 +54,20 @@ async def api_update_wallet_name(
} }
@wallet_router.put("/reset/{wallet_id}")
async def api_reset_wallet_keys(
wallet_id: str, user: User = Depends(check_user_exists)
) -> Wallet:
wallet = await get_wallet(wallet_id)
if not wallet or wallet.user != user.id:
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Wallet not found")
wallet.adminkey = uuid4().hex
wallet.inkey = uuid4().hex
await update_wallet(wallet)
return wallet
@wallet_router.patch("") @wallet_router.patch("")
async def api_update_wallet( async def api_update_wallet(
name: Optional[str] = Body(None), name: Optional[str] = Body(None),
@ -75,13 +87,17 @@ async def api_update_wallet(
return wallet return wallet
@wallet_router.delete("") @wallet_router.delete("/{wallet_id}")
async def api_delete_wallet( async def api_delete_wallet(
wallet: WalletTypeInfo = Depends(require_admin_key), wallet_id: str, user: User = Depends(check_user_exists)
) -> None: ) -> None:
wallet = await get_wallet(wallet_id)
if not wallet or wallet.user != user.id:
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Wallet not found")
await delete_wallet( await delete_wallet(
user_id=wallet.wallet.user, user_id=wallet.user,
wallet_id=wallet.wallet.id, wallet_id=wallet.id,
) )

File diff suppressed because one or more lines are too long

View file

@ -505,5 +505,8 @@ window.localisation.en = {
payments_balance_chart: 'Balance Chart', payments_balance_chart: 'Balance Chart',
payments_wallets_chart: 'Wallets Chart', payments_wallets_chart: 'Wallets Chart',
payments_balance_in_out_chart: 'Balance In/Out Chart', payments_balance_in_out_chart: 'Balance In/Out Chart',
payments_count_in_out_chart: 'Count In/Out Chart' payments_count_in_out_chart: 'Count In/Out Chart',
reset_wallet_keys: 'Reset Keys',
reset_wallet_keys_desc:
'Reset the API keys for this wallet. This will invalidate the current keys and generate new ones.'
} }

View file

@ -132,15 +132,20 @@ window.LNbits = {
name: name name: name
}) })
}, },
deleteWallet(wallet) { resetWalletKeys(wallet) {
return this.request('delete', '/api/v1/wallet', wallet.adminkey).then( return this.request('put', `/api/v1/wallet/reset/${wallet.id}`).then(
_ => { res => {
let url = new URL(window.location.href) return res.data
url.searchParams.delete('wal')
window.location = url
} }
) )
}, },
deleteWallet(wallet) {
return this.request('delete', `/api/v1/wallet/${wallet.id}`).then(_ => {
let url = new URL(window.location.href)
url.searchParams.delete('wal')
window.location = url
})
},
getPayments(wallet, params) { getPayments(wallet, params) {
return this.request( return this.request(
'get', 'get',

View file

@ -564,6 +564,40 @@ window.WalletPageLogic = {
} }
}) })
}, },
resetKeys() {
LNbits.utils
.confirmDialog('Are you sure you want to reset your API keys?')
.onOk(() => {
LNbits.api
.resetWalletKeys(this.g.wallet)
.then(response => {
const {id, adminkey, inkey} = response
this.g.wallet = {
...this.g.wallet,
inkey,
adminkey
}
const walletIndex = this.g.user.wallets.findIndex(
wallet => wallet.id === id
)
if (walletIndex !== -1) {
this.g.user.wallets[walletIndex] = {
...this.g.user.wallets[walletIndex],
inkey,
adminkey
}
}
Quasar.Notify.create({
timeout: 3500,
type: 'positive',
message: 'API keys reset!'
})
})
.catch(err => {
LNbits.utils.notifyApiError(err)
})
})
},
updateWallet(data) { updateWallet(data) {
LNbits.api LNbits.api
.request('PATCH', '/api/v1/wallet', this.g.wallet.adminkey, data) .request('PATCH', '/api/v1/wallet', this.g.wallet.adminkey, data)

View file

@ -35,12 +35,14 @@ async def test_create_account(client, settings: Settings):
assert "user" in result assert "user" in result
# check POST and DELETE /api/v1/wallet with adminkey: # check POST and DELETE /api/v1/wallet with adminkey and user token:
# create additional wallet and delete it # create additional wallet and delete it
@pytest.mark.anyio @pytest.mark.anyio
async def test_create_wallet_and_delete(client, adminkey_headers_to): async def test_create_wallet_and_delete(
client, adminkey_headers_from, user_headers_from
):
response = await client.post( response = await client.post(
"/api/v1/wallet", json={"name": "test"}, headers=adminkey_headers_to "/api/v1/wallet", json={"name": "test"}, headers=adminkey_headers_from
) )
assert response.status_code == 200 assert response.status_code == 200
result = response.json() result = response.json()
@ -51,20 +53,17 @@ async def test_create_wallet_and_delete(client, adminkey_headers_to):
assert "adminkey" in result assert "adminkey" in result
invalid_response = await client.delete( invalid_response = await client.delete(
"/api/v1/wallet", f"/api/v1/wallet/{result['id']}",
headers={ headers={
"X-Api-Key": result["inkey"], "X-Api-Key": result["adminkey"],
"Content-type": "application/json", "Content-type": "application/json",
}, },
) )
assert invalid_response.status_code == 401 assert invalid_response.status_code == 401
response = await client.delete( response = await client.delete(
"/api/v1/wallet", f"/api/v1/wallet/{result['id']}",
headers={ headers=user_headers_from,
"X-Api-Key": result["adminkey"],
"Content-type": "application/json",
},
) )
assert response.status_code == 200 assert response.status_code == 200