From 466d2c74e364aeccddc79543c7b85c4d7c5381f2 Mon Sep 17 00:00:00 2001 From: padreug Date: Sun, 22 Jun 2025 11:13:41 +0200 Subject: [PATCH] Refactor DCA API endpoints to require admin key for access: Updated wallet dependency in multiple DCA-related endpoints to use `require_admin_key` instead of `require_invoice_key`, enhancing security and access control. Cleaned up code formatting for improved readability. Update DCA API calls to use admin key: Changed references from `inkey` to `adminkey` in multiple DCA-related API requests to ensure proper access control and security compliance. --- static/js/index.js | 14 ++--- views_api.py | 150 ++++++++++++++++++++++++++------------------- 2 files changed, 94 insertions(+), 70 deletions(-) diff --git a/static/js/index.js b/static/js/index.js index c5d105b..47a6368 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -166,7 +166,7 @@ window.app = Vue.createApp({ const {data} = await LNbits.api.request( 'GET', '/satmachineadmin/api/v1/dca/config', - this.g.user.wallets[0].inkey + this.g.user.wallets[0].adminkey ) this.lamassuConfig = data @@ -254,7 +254,7 @@ window.app = Vue.createApp({ const { data } = await LNbits.api.request( 'GET', '/satmachineadmin/api/v1/dca/clients', - this.g.user.wallets[0].inkey + this.g.user.wallets[0].adminkey ) // Fetch balance data for each client @@ -264,7 +264,7 @@ window.app = Vue.createApp({ const { data: balance } = await LNbits.api.request( 'GET', `/satmachineadmin/api/v1/dca/clients/${client.id}/balance`, - this.g.user.wallets[0].inkey + this.g.user.wallets[0].adminkey ) return { ...client, @@ -356,7 +356,7 @@ window.app = Vue.createApp({ const { data: balance } = await LNbits.api.request( 'GET', `/satmachineadmin/api/v1/dca/clients/${client.id}/balance`, - this.g.user.wallets[0].inkey + this.g.user.wallets[0].adminkey ) this.clientDetailsDialog.data = client this.clientDetailsDialog.balance = balance @@ -372,7 +372,7 @@ window.app = Vue.createApp({ const { data } = await LNbits.api.request( 'GET', '/satmachineadmin/api/v1/dca/deposits', - this.g.user.wallets[0].inkey + this.g.user.wallets[0].adminkey ) this.deposits = data } catch (error) { @@ -617,7 +617,7 @@ window.app = Vue.createApp({ const { data } = await LNbits.api.request( 'GET', '/satmachineadmin/api/v1/dca/transactions', - this.g.user.wallets[0].inkey + this.g.user.wallets[0].adminkey ) this.lamassuTransactions = data } catch (error) { @@ -630,7 +630,7 @@ window.app = Vue.createApp({ const { data: distributions } = await LNbits.api.request( 'GET', `/satmachineadmin/api/v1/dca/transactions/${transaction.id}/distributions`, - this.g.user.wallets[0].inkey + this.g.user.wallets[0].adminkey ) this.distributionDialog.transaction = transaction diff --git a/views_api.py b/views_api.py index 3225239..119a300 100644 --- a/views_api.py +++ b/views_api.py @@ -7,7 +7,7 @@ from fastapi import APIRouter, Depends, Request from lnbits.core.crud import get_user from lnbits.core.models import WalletTypeInfo from lnbits.core.services import create_invoice -from lnbits.decorators import require_admin_key, require_invoice_key +from lnbits.decorators import require_admin_key from starlette.exceptions import HTTPException from .crud import ( @@ -32,15 +32,21 @@ from .crud import ( delete_lamassu_config, # Lamassu transaction CRUD operations get_all_lamassu_transactions, - get_lamassu_transaction + get_lamassu_transaction, ) from .models import ( # DCA models - CreateDcaClientData, DcaClient, UpdateDcaClientData, - CreateDepositData, DcaDeposit, UpdateDepositStatusData, + CreateDcaClientData, + DcaClient, + UpdateDcaClientData, + CreateDepositData, + DcaDeposit, + UpdateDepositStatusData, ClientBalanceSummary, - CreateLamassuConfigData, LamassuConfig, UpdateLamassuConfigData, - StoredLamassuTransaction + CreateLamassuConfigData, + LamassuConfig, + UpdateLamassuConfigData, + StoredLamassuTransaction, ) satmachineadmin_api_router = APIRouter() @@ -52,9 +58,10 @@ satmachineadmin_api_router = APIRouter() # DCA Client Endpoints + @satmachineadmin_api_router.get("/api/v1/dca/clients") async def api_get_dca_clients( - wallet: WalletTypeInfo = Depends(require_invoice_key), + wallet: WalletTypeInfo = Depends(require_admin_key), ) -> list[DcaClient]: """Get all DCA clients""" return await get_dca_clients() @@ -63,7 +70,7 @@ async def api_get_dca_clients( @satmachineadmin_api_router.get("/api/v1/dca/clients/{client_id}") async def api_get_dca_client( client_id: str, - wallet: WalletTypeInfo = Depends(require_invoice_key), + wallet: WalletTypeInfo = Depends(require_admin_key), ) -> DcaClient: """Get a specific DCA client""" client = await get_dca_client(client_id) @@ -77,6 +84,7 @@ async def api_get_dca_client( # Note: Client creation/update/delete will be handled by the DCA client extension # Admin extension only reads existing clients and manages their deposits + # TEMPORARY: Test client creation endpoint (remove in production) @satmachineadmin_api_router.post("/api/v1/dca/clients", status_code=HTTPStatus.CREATED) async def api_create_test_dca_client( @@ -90,7 +98,7 @@ async def api_create_test_dca_client( @satmachineadmin_api_router.get("/api/v1/dca/clients/{client_id}/balance") async def api_get_client_balance( client_id: str, - wallet: WalletTypeInfo = Depends(require_invoice_key), + wallet: WalletTypeInfo = Depends(require_admin_key), ) -> ClientBalanceSummary: """Get client balance summary""" client = await get_dca_client(client_id) @@ -98,15 +106,16 @@ async def api_get_client_balance( raise HTTPException( status_code=HTTPStatus.NOT_FOUND, detail="DCA client not found." ) - + return await get_client_balance_summary(client_id) # DCA Deposit Endpoints + @satmachineadmin_api_router.get("/api/v1/dca/deposits") async def api_get_deposits( - wallet: WalletTypeInfo = Depends(require_invoice_key), + wallet: WalletTypeInfo = Depends(require_admin_key), ) -> list[DcaDeposit]: """Get all deposits""" return await get_all_deposits() @@ -115,7 +124,7 @@ async def api_get_deposits( @satmachineadmin_api_router.get("/api/v1/dca/deposits/{deposit_id}") async def api_get_deposit( deposit_id: str, - wallet: WalletTypeInfo = Depends(require_invoice_key), + wallet: WalletTypeInfo = Depends(require_admin_key), ) -> DcaDeposit: """Get a specific deposit""" deposit = await get_deposit(deposit_id) @@ -138,7 +147,7 @@ async def api_create_deposit( raise HTTPException( status_code=HTTPStatus.NOT_FOUND, detail="DCA client not found." ) - + return await create_deposit(data) @@ -154,17 +163,19 @@ async def api_update_deposit_status( raise HTTPException( status_code=HTTPStatus.NOT_FOUND, detail="Deposit not found." ) - + updated_deposit = await update_deposit_status(deposit_id, data) if not updated_deposit: raise HTTPException( - status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Failed to update deposit." + status_code=HTTPStatus.INTERNAL_SERVER_ERROR, + detail="Failed to update deposit.", ) return updated_deposit # Transaction Polling Endpoints + @satmachineadmin_api_router.post("/api/v1/dca/test-connection") async def api_test_database_connection( wallet: WalletTypeInfo = Depends(require_admin_key), @@ -172,11 +183,11 @@ async def api_test_database_connection( """Test connection to Lamassu database with detailed reporting""" try: from .transaction_processor import transaction_processor - + # Use the detailed test method result = await transaction_processor.test_connection_detailed() return result - + except Exception as e: return { "success": False, @@ -184,7 +195,7 @@ async def api_test_database_connection( "steps": [f"❌ Unexpected error: {str(e)}"], "ssh_tunnel_used": False, "ssh_tunnel_success": False, - "database_connection_success": False + "database_connection_success": False, } @@ -196,41 +207,41 @@ async def api_manual_poll( try: from .transaction_processor import transaction_processor from .crud import update_poll_start_time, update_poll_success_time - + # Get database configuration db_config = await transaction_processor.connect_to_lamassu_db() if not db_config: raise HTTPException( status_code=HTTPStatus.SERVICE_UNAVAILABLE, - detail="Could not get Lamassu database configuration" + detail="Could not get Lamassu database configuration", ) - + config_id = db_config["config_id"] - + # Record manual poll start time await update_poll_start_time(config_id) - + # Fetch and process transactions via SSH new_transactions = await transaction_processor.fetch_new_transactions(db_config) - + transactions_processed = 0 for transaction in new_transactions: await transaction_processor.process_transaction(transaction) transactions_processed += 1 - + # Record successful manual poll completion await update_poll_success_time(config_id) - + return { "success": True, "transactions_processed": transactions_processed, - "message": f"Processed {transactions_processed} new transactions since last poll" + "message": f"Processed {transactions_processed} new transactions since last poll", } - + except Exception as e: raise HTTPException( status_code=HTTPStatus.INTERNAL_SERVER_ERROR, - detail=f"Error during manual poll: {str(e)}" + detail=f"Error during manual poll: {str(e)}", ) @@ -246,7 +257,7 @@ async def api_test_transaction( from .transaction_processor import transaction_processor import uuid from datetime import datetime, timezone - + # Create a mock transaction that mimics Lamassu database structure mock_transaction = { "transaction_id": str(uuid.uuid4())[:8], # Short ID for testing @@ -258,12 +269,12 @@ async def api_test_transaction( "crypto_code": "BTC", "fiat_code": "GTQ", "device_id": "test_device", - "status": "confirmed" + "status": "confirmed", } - + # Process the mock transaction through the complete DCA flow await transaction_processor.process_transaction(mock_transaction) - + # Calculate commission for response if commission_percentage > 0: effective_commission = commission_percentage * (100 - discount) / 100 @@ -272,7 +283,7 @@ async def api_test_transaction( else: base_crypto_atoms = crypto_atoms commission_amount_sats = 0 - + return { "success": True, "message": "Test transaction processed successfully", @@ -281,24 +292,28 @@ async def api_test_transaction( "total_amount_sats": crypto_atoms, "base_amount_sats": base_crypto_atoms, "commission_amount_sats": commission_amount_sats, - "commission_percentage": commission_percentage * 100, # Show as percentage - "effective_commission": effective_commission * 100 if commission_percentage > 0 else 0, - "discount": discount - } + "commission_percentage": commission_percentage + * 100, # Show as percentage + "effective_commission": effective_commission * 100 + if commission_percentage > 0 + else 0, + "discount": discount, + }, } - + except Exception as e: raise HTTPException( status_code=HTTPStatus.INTERNAL_SERVER_ERROR, - detail=f"Error processing test transaction: {str(e)}" + detail=f"Error processing test transaction: {str(e)}", ) # Lamassu Transaction Endpoints + @satmachineadmin_api_router.get("/api/v1/dca/transactions") async def api_get_lamassu_transactions( - wallet: WalletTypeInfo = Depends(require_invoice_key), + wallet: WalletTypeInfo = Depends(require_admin_key), ) -> list[StoredLamassuTransaction]: """Get all processed Lamassu transactions""" return await get_all_lamassu_transactions() @@ -307,7 +322,7 @@ async def api_get_lamassu_transactions( @satmachineadmin_api_router.get("/api/v1/dca/transactions/{transaction_id}") async def api_get_lamassu_transaction( transaction_id: str, - wallet: WalletTypeInfo = Depends(require_invoice_key), + wallet: WalletTypeInfo = Depends(require_admin_key), ) -> StoredLamassuTransaction: """Get a specific Lamassu transaction with details""" transaction = await get_lamassu_transaction(transaction_id) @@ -318,10 +333,12 @@ async def api_get_lamassu_transaction( return transaction -@satmachineadmin_api_router.get("/api/v1/dca/transactions/{transaction_id}/distributions") +@satmachineadmin_api_router.get( + "/api/v1/dca/transactions/{transaction_id}/distributions" +) async def api_get_transaction_distributions( transaction_id: str, - wallet: WalletTypeInfo = Depends(require_invoice_key), + wallet: WalletTypeInfo = Depends(require_admin_key), ) -> list[dict]: """Get distribution details for a specific Lamassu transaction""" # Get the stored transaction @@ -330,35 +347,41 @@ async def api_get_transaction_distributions( raise HTTPException( status_code=HTTPStatus.NOT_FOUND, detail="Lamassu transaction not found." ) - + # Get all DCA payments for this Lamassu transaction from .crud import get_payments_by_lamassu_transaction, get_dca_client - payments = await get_payments_by_lamassu_transaction(transaction.lamassu_transaction_id) - + + payments = await get_payments_by_lamassu_transaction( + transaction.lamassu_transaction_id + ) + # Enhance payments with client information distributions = [] for payment in payments: client = await get_dca_client(payment.client_id) - distributions.append({ - "payment_id": payment.id, - "client_id": payment.client_id, - "client_username": client.username if client else None, - "client_user_id": client.user_id if client else None, - "amount_sats": payment.amount_sats, - "amount_fiat": payment.amount_fiat, - "exchange_rate": payment.exchange_rate, - "status": payment.status, - "created_at": payment.created_at - }) - + distributions.append( + { + "payment_id": payment.id, + "client_id": payment.client_id, + "client_username": client.username if client else None, + "client_user_id": client.user_id if client else None, + "amount_sats": payment.amount_sats, + "amount_fiat": payment.amount_fiat, + "exchange_rate": payment.exchange_rate, + "status": payment.status, + "created_at": payment.created_at, + } + ) + return distributions # Lamassu Configuration Endpoints + @satmachineadmin_api_router.get("/api/v1/dca/config") async def api_get_lamassu_config( - wallet: WalletTypeInfo = Depends(require_invoice_key), + wallet: WalletTypeInfo = Depends(require_admin_key), ) -> Optional[LamassuConfig]: """Get active Lamassu database configuration""" return await get_active_lamassu_config() @@ -385,11 +408,12 @@ async def api_update_lamassu_config( raise HTTPException( status_code=HTTPStatus.NOT_FOUND, detail="Configuration not found." ) - + updated_config = await update_lamassu_config(config_id, data) if not updated_config: raise HTTPException( - status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Failed to update configuration." + status_code=HTTPStatus.INTERNAL_SERVER_ERROR, + detail="Failed to update configuration.", ) return updated_config @@ -405,6 +429,6 @@ async def api_delete_lamassu_config( raise HTTPException( status_code=HTTPStatus.NOT_FOUND, detail="Configuration not found." ) - + await delete_lamassu_config(config_id) return {"message": "Configuration deleted successfully"}