Adds super user and config setup

Adds super user role to restrict settings changes.

Improves the settings screen to only allow super users to make modifications.

Adds a warning banner if the Castle wallet is not configured.

Changes admin key to inkey for fetching settings.
This fixes an issue where settings weren't accessible.

Adds a validation to require the Castle wallet ID when updating settings.
This commit is contained in:
padreug 2025-10-22 14:45:18 +02:00
parent 29983cedb7
commit 31344607c6
3 changed files with 106 additions and 16 deletions

View file

@ -13,6 +13,8 @@ window.app = Vue.createApp({
currencies: [], currencies: [],
settings: null, settings: null,
isAdmin: false, isAdmin: false,
isSuperUser: false,
castleWalletConfigured: false,
expenseDialog: { expenseDialog: {
show: false, show: false,
description: '', description: '',
@ -104,16 +106,21 @@ window.app = Vue.createApp({
}, },
async loadSettings() { async loadSettings() {
try { try {
// Try with admin key first to check settings
const response = await LNbits.api.request( const response = await LNbits.api.request(
'GET', 'GET',
'/castle/api/v1/settings', '/castle/api/v1/settings',
this.g.user.wallets[0].adminkey this.g.user.wallets[0].inkey
) )
this.settings = response.data this.settings = response.data
this.isAdmin = true this.castleWalletConfigured = !!(this.settings && this.settings.castle_wallet_id)
// Check if user is super user by seeing if they can access admin features
this.isSuperUser = this.g.user.super_user || false
this.isAdmin = this.g.user.admin || this.isSuperUser
} catch (error) { } catch (error) {
// Not admin or settings not available // Settings not available
this.isAdmin = false this.castleWalletConfigured = false
} }
}, },
showSettingsDialog() { showSettingsDialog() {
@ -121,6 +128,14 @@ window.app = Vue.createApp({
this.settingsDialog.show = true this.settingsDialog.show = true
}, },
async submitSettings() { async submitSettings() {
if (!this.settingsDialog.castleWalletId) {
this.$q.notify({
type: 'warning',
message: 'Castle Wallet ID is required'
})
return
}
this.settingsDialog.loading = true this.settingsDialog.loading = true
try { try {
await LNbits.api.request( await LNbits.api.request(

View file

@ -16,15 +16,37 @@
<h5 class="q-my-none">🏰 Castle Accounting</h5> <h5 class="q-my-none">🏰 Castle Accounting</h5>
<p class="q-mb-none">Track expenses, receivables, and balances for the collective</p> <p class="q-mb-none">Track expenses, receivables, and balances for the collective</p>
</div> </div>
<div class="col-auto" v-if="isAdmin"> <div class="col-auto" v-if="isSuperUser">
<q-btn flat round icon="settings" @click="showSettingsDialog"> <q-btn flat round icon="settings" @click="showSettingsDialog">
<q-tooltip>Settings</q-tooltip> <q-tooltip>Settings (Super User Only)</q-tooltip>
</q-btn> </q-btn>
</div> </div>
</div> </div>
</q-card-section> </q-card-section>
</q-card> </q-card>
<!-- Setup Warning -->
<q-banner v-if="!castleWalletConfigured && isSuperUser" class="bg-warning text-white" rounded>
<template v-slot:avatar>
<q-icon name="warning" color="white"></q-icon>
</template>
<div>
<strong>Setup Required:</strong> Castle Wallet ID must be configured before the extension can function.
</div>
<template v-slot:action>
<q-btn flat color="white" label="Configure Now" @click="showSettingsDialog"></q-btn>
</template>
</q-banner>
<q-banner v-if="!castleWalletConfigured && !isSuperUser" class="bg-info text-white" rounded>
<template v-slot:avatar>
<q-icon name="info" color="white"></q-icon>
</template>
<div>
<strong>Setup Required:</strong> This extension requires configuration by the super user before it can be used.
</div>
</q-banner>
<!-- User Balance Card --> <!-- User Balance Card -->
<q-card> <q-card>
<q-card-section> <q-card-section>
@ -66,8 +88,15 @@
<q-card-section> <q-card-section>
<h6 class="q-my-none q-mb-md">Quick Actions</h6> <h6 class="q-my-none q-mb-md">Quick Actions</h6>
<div class="row q-gutter-sm"> <div class="row q-gutter-sm">
<q-btn color="primary" @click="expenseDialog.show = true"> <q-btn
color="primary"
@click="expenseDialog.show = true"
:disable="!castleWalletConfigured"
>
Add Expense Add Expense
<q-tooltip v-if="!castleWalletConfigured">
Castle wallet must be configured first
</q-tooltip>
</q-btn> </q-btn>
<q-btn color="secondary" @click="loadTransactions"> <q-btn color="secondary" @click="loadTransactions">
View Transactions View Transactions
@ -248,12 +277,22 @@
<q-form @submit="submitSettings" class="q-gutter-md"> <q-form @submit="submitSettings" class="q-gutter-md">
<div class="text-h6 q-mb-md">Castle Settings</div> <div class="text-h6 q-mb-md">Castle Settings</div>
<q-banner v-if="!isSuperUser" class="bg-warning text-dark q-mb-md" dense rounded>
<template v-slot:avatar>
<q-icon name="lock" color="orange"></q-icon>
</template>
<div class="text-caption">
<strong>Super User Only:</strong> Only the super user can modify these settings.
</div>
</q-banner>
<q-input <q-input
filled filled
dense dense
v-model.trim="settingsDialog.castleWalletId" v-model.trim="settingsDialog.castleWalletId"
label="Castle Wallet ID *" label="Castle Wallet ID *"
placeholder="The wallet ID that represents the Castle" placeholder="The wallet ID that represents the Castle"
:readonly="!isSuperUser"
></q-input> ></q-input>
<div class="text-caption text-grey"> <div class="text-caption text-grey">
@ -261,10 +300,18 @@
</div> </div>
<div class="row q-mt-lg"> <div class="row q-mt-lg">
<q-btn unelevated color="primary" type="submit" :loading="settingsDialog.loading"> <q-btn
v-if="isSuperUser"
unelevated
color="primary"
type="submit"
:loading="settingsDialog.loading"
>
Save Settings Save Settings
</q-btn> </q-btn>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn> <q-btn v-close-popup flat color="grey" :class="isSuperUser ? 'q-ml-auto' : ''">
{% raw %}{{ isSuperUser ? 'Cancel' : 'Close' }}{% endraw %}
</q-btn>
</div> </div>
</q-form> </q-form>
</q-card> </q-card>

View file

@ -1,8 +1,13 @@
from http import HTTPStatus from http import HTTPStatus
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from lnbits.core.models import WalletTypeInfo from lnbits.core.models import User, WalletTypeInfo
from lnbits.decorators import require_admin_key, require_invoice_key from lnbits.decorators import (
check_super_user,
check_user_exists,
require_admin_key,
require_invoice_key,
)
from lnbits.utils.exchange_rates import allowed_currencies, fiat_amount_as_satoshis from lnbits.utils.exchange_rates import allowed_currencies, fiat_amount_as_satoshis
from .crud import ( from .crud import (
@ -37,6 +42,20 @@ from .services import get_settings, update_settings
castle_api_router = APIRouter() castle_api_router = APIRouter()
# ===== HELPER FUNCTIONS =====
async def check_castle_wallet_configured() -> str:
"""Ensure castle wallet is configured, return wallet_id"""
settings = await get_settings("admin")
if not settings or not settings.castle_wallet_id:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail="Castle wallet not configured. Please contact the super user to configure the Castle wallet in settings.",
)
return settings.castle_wallet_id
# ===== UTILITY ENDPOINTS ===== # ===== UTILITY ENDPOINTS =====
@ -459,18 +478,27 @@ async def api_pay_user(
@castle_api_router.get("/api/v1/settings") @castle_api_router.get("/api/v1/settings")
async def api_get_settings( async def api_get_settings(
wallet: WalletTypeInfo = Depends(require_admin_key), user: User = Depends(check_user_exists),
) -> CastleSettings: ) -> CastleSettings:
"""Get Castle settings (admin only)""" """Get Castle settings"""
user_id = "admin" user_id = "admin"
return await get_settings(user_id) settings = await get_settings(user_id)
# Return empty settings if not configured (so UI can show setup screen)
if not settings:
return CastleSettings()
return settings
@castle_api_router.put("/api/v1/settings") @castle_api_router.put("/api/v1/settings")
async def api_update_settings( async def api_update_settings(
data: CastleSettings, data: CastleSettings,
wallet: WalletTypeInfo = Depends(require_admin_key), user: User = Depends(check_super_user),
) -> CastleSettings: ) -> CastleSettings:
"""Update Castle settings (admin only)""" """Update Castle settings (super user only)"""
if not data.castle_wallet_id:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail="Castle wallet ID is required",
)
user_id = "admin" user_id = "admin"
return await update_settings(user_id, data) return await update_settings(user_id, data)