Refactor client configuration access: Remove direct access to sensitive admin config and local client-limits endpoint. Implement fetching of client limits via a secure public API. Update registration form to reflect changes and enhance user experience.

This commit is contained in:
padreug 2025-06-27 23:21:36 +02:00
parent 16db140bb6
commit 340dc22c20
4 changed files with 134 additions and 104 deletions

View file

@ -514,4 +514,8 @@ async def get_client_by_user_id(user_id: str) -> Optional[dict]:
) )
return dict(client) if client else None return dict(client) if client else None
except Exception: except Exception:
return None return None
# Removed get_active_lamassu_config - client should not access sensitive admin config
# Client limits are now fetched via secure public API endpoint

View file

@ -7,7 +7,6 @@ window.app = Vue.createApp({
// Registration state // Registration state
isRegistered: false, isRegistered: false,
registrationChecked: false, registrationChecked: false,
showRegistrationDialog: false,
registrationForm: { registrationForm: {
selectedWallet: null, selectedWallet: null,
dca_mode: 'flow', dca_mode: 'flow',
@ -15,6 +14,12 @@ window.app = Vue.createApp({
username: '' username: ''
}, },
// Admin configuration
adminConfig: {
max_daily_limit_gtq: 2000,
currency: 'GTQ'
},
// Dashboard state // Dashboard state
dashboardData: null, dashboardData: null,
transactions: [], transactions: [],
@ -72,6 +77,23 @@ window.app = Vue.createApp({
}, },
methods: { methods: {
// Configuration Methods
async loadClientLimits() {
try {
const { data } = await LNbits.api.request(
'GET',
'/satmachineadmin/api/v1/dca/client-limits'
// No authentication required - public endpoint with safe data only
)
this.adminConfig = data
console.log('Client limits loaded:', this.adminConfig)
} catch (error) {
console.error('Error loading client limits:', error)
// Keep default values if client limits fail to load
}
},
// Registration Methods // Registration Methods
async checkRegistrationStatus() { async checkRegistrationStatus() {
try { try {
@ -85,9 +107,8 @@ window.app = Vue.createApp({
this.registrationChecked = true this.registrationChecked = true
if (!this.isRegistered) { if (!this.isRegistered) {
this.showRegistrationDialog = true // Fetch current user info to get the username
// Pre-fill username and default wallet if available await this.loadCurrentUser()
this.registrationForm.username = this.g.user.username || ''
this.registrationForm.selectedWallet = this.g.user.wallets[0]?.id || null this.registrationForm.selectedWallet = this.g.user.wallets[0]?.id || null
} }
@ -99,13 +120,29 @@ window.app = Vue.createApp({
} }
}, },
async loadCurrentUser() {
try {
const { data } = await LNbits.api.getAuthenticatedUser()
// Set username from API response with priority: display_name > username > email > fallback
const username = data.extra?.display_name || data.username || data.email
this.registrationForm.username = (username !== null && username !== undefined && username !== '')
? username
: `user_${this.g.user.id.substring(0, 8)}`
} catch (error) {
console.error('Error loading current user:', error)
// Fallback to generated username
this.registrationForm.username = `user_${this.g.user.id.substring(0, 8)}`
}
},
async registerClient() { async registerClient() {
try { try {
// Prepare registration data similar to the admin test client creation // Prepare registration data using the form's username (already loaded from API)
const registrationData = { const registrationData = {
dca_mode: this.registrationForm.dca_mode, dca_mode: this.registrationForm.dca_mode,
fixed_mode_daily_limit: this.registrationForm.fixed_mode_daily_limit, fixed_mode_daily_limit: this.registrationForm.fixed_mode_daily_limit,
username: this.registrationForm.username || this.g.user.username || `user_${this.g.user.id.substring(0, 8)}` username: this.registrationForm.username || `user_${this.g.user.id.substring(0, 8)}`
} }
// Find the selected wallet object to get the adminkey // Find the selected wallet object to get the adminkey
@ -122,7 +159,6 @@ window.app = Vue.createApp({
) )
this.isRegistered = true this.isRegistered = true
this.showRegistrationDialog = false
this.$q.notify({ this.$q.notify({
type: 'positive', type: 'positive',
@ -754,7 +790,10 @@ window.app = Vue.createApp({
try { try {
this.loading = true this.loading = true
// Check registration status first // Load client limits first
await this.loadClientLimits()
// Check registration status
await this.checkRegistrationStatus() await this.checkRegistrationStatus()
// Only load dashboard data if registered // Only load dashboard data if registered

View file

@ -21,19 +21,87 @@
<!-- Error State --> <!-- Error State -->
<q-card v-if="error && !loading" class="bg-negative text-white"> <q-card v-if="error && !loading" class="bg-negative text-white">
<q-card-section> <q-card-section>
<q-icon name="error" class="q-mr-sm" /> <q-icon name="error" class="q-mr-sm"></q-icon>
${error} ${error}
</q-card-section> </q-card-section>
</q-card> </q-card>
<!-- Not Registered State --> <!-- Registration Form Card -->
<q-card v-if="registrationChecked && !isRegistered && !loading" class="bg-orange-1"> <q-card v-if="registrationChecked && !isRegistered && !loading" class="q-mb-md">
<q-card-section class="text-center"> <q-card-section>
<q-icon name="account_circle" size="3em" color="orange" /> <div class="text-center q-mb-lg">
<div class="text-h6 q-mt-md text-orange-8">Welcome to Bitcoin DCA!</div> <div>
<div class="text-body2 text-grey-7 q-mt-sm"> <q-icon name="account_circle" size="4em" color="orange"></q-icon>
Please complete your registration to start your Dollar Cost Averaging journey. </div>
<div class="text-h5 q-mt-md text-orange-8">Welcome to DCA!</div>
<div class="text-body2 text-grey-7">Let's set up your Bitcoin Dollar Cost Averaging account</div>
</div> </div>
<q-form @submit="registerClient" class="q-gutter-md">
<q-select
filled
dense
emit-value
v-model="registrationForm.selectedWallet"
:options="walletOptions"
label="DCA Wallet *"
hint="Choose which wallet will receive your Bitcoin DCA purchases"
></q-select>
<q-select
filled
dense
emit-value
v-model="registrationForm.dca_mode"
:options="[
{ label: 'Flow Mode (Recommended)', value: 'flow' },
{ label: 'Fixed Mode', value: 'fixed' }
]"
option-label="label"
option-value="value"
label="DCA Strategy *"
hint="Choose how your Bitcoin purchases will be distributed"
></q-select>
<q-input
v-if="registrationForm.dca_mode === 'fixed'"
filled
dense
type="number"
v-model.number="registrationForm.fixed_mode_daily_limit"
label="Daily Limit (GTQ)"
placeholder="Enter daily purchase limit"
:hint="`Maximum amount to purchase per day (Admin limit: ${adminConfig.max_daily_limit_gtq} GTQ)`"
:rules="[
val => registrationForm.dca_mode !== 'fixed' || (val && val > 0) || 'Daily limit is required for fixed mode',
val => registrationForm.dca_mode !== 'fixed' || val <= adminConfig.max_daily_limit_gtq || `Daily limit cannot exceed ${adminConfig.max_daily_limit_gtq} GTQ (admin maximum)`
]"
></q-input>
<q-banner class="bg-blue-1 text-blue-9 q-mt-md">
<template v-slot:avatar>
<q-icon name="info" color="blue"></q-icon>
</template>
<div class="text-caption">
<strong>Flow Mode:</strong> Your Bitcoin purchases come at 0% fee when people cash ou at the machine.<br>
<strong>Fixed Mode:</strong> Set a daily limit for consistent Bitcoin accumulation.
</div>
</q-banner>
<div class="row q-mt-lg">
<q-btn
unelevated
color="primary"
type="submit"
:disable="!registrationForm.selectedWallet || !registrationForm.dca_mode"
class="full-width"
size="lg"
>
<q-icon name="flash_on" class="q-mr-sm"></q-icon>
Start My DCA Journey 🚀
</q-btn>
</div>
</q-form>
</q-card-section> </q-card-section>
</q-card> </q-card>
@ -179,7 +247,7 @@
<div v-if="dashboardData.pending_fiat_deposits > 0" class="q-mt-md"> <div v-if="dashboardData.pending_fiat_deposits > 0" class="q-mt-md">
<q-banner rounded class="bg-orange-1 text-orange-9"> <q-banner rounded class="bg-orange-1 text-orange-9">
<template v-slot:avatar> <template v-slot:avatar>
<q-icon name="schedule" color="orange" size="md" /> <q-icon name="schedule" color="orange" size="md"></q-icon>
</template> </template>
<div class="text-subtitle2"> <div class="text-subtitle2">
<strong>${formatCurrency(dashboardData.pending_fiat_deposits)}</strong> ready to DCA <strong>${formatCurrency(dashboardData.pending_fiat_deposits)}</strong> ready to DCA
@ -363,7 +431,7 @@
<template v-slot:no-data="{ message }"> <template v-slot:no-data="{ message }">
<div class="full-width row flex-center q-pa-lg"> <div class="full-width row flex-center q-pa-lg">
<div class="text-center"> <div class="text-center">
<q-icon name="rocket_launch" size="3em" class="text-orange-5 q-mb-md" /> <q-icon name="rocket_launch" size="3em" class="text-orange-5 q-mb-md"></q-icon>
<div class="text-h6 text-grey-7 q-mb-sm">${message}</div> <div class="text-h6 text-grey-7 q-mb-sm">${message}</div>
<div class="text-caption text-grey-5"> <div class="text-caption text-grey-5">
Visit your nearest Lamassu ATM to begin stacking sats automatically Visit your nearest Lamassu ATM to begin stacking sats automatically
@ -556,90 +624,5 @@
</q-card> </q-card>
</div> </div>
<!-- Registration Dialog -->
<q-dialog v-model="showRegistrationDialog" persistent position="top">
<q-card class="q-pa-lg" style="width: 500px; max-width: 90vw">
<div class="text-center q-mb-lg">
<div>
<q-icon name="account_circle" size="4em" color="orange" />
</div>
<div class="text-h5 q-mt-md text-orange-8">Welcome to DCA!</div>
<div class="text-body2 text-grey-7">Let's set up your Bitcoin Dollar Cost Averaging account</div>
</div>
<q-form @submit="registerClient" class="q-gutter-md">
<q-input
filled
dense
v-model.trim="registrationForm.username"
label="Username (Optional)"
placeholder="Enter a friendly name"
hint="How you'd like to be identified in the system"
></q-input>
<q-select
filled
dense
emit-value
v-model="registrationForm.selectedWallet"
:options="walletOptions"
label="DCA Wallet *"
hint="Choose which wallet will receive your Bitcoin DCA purchases"
></q-select>
<q-select
filled
dense
emit-value
v-model="registrationForm.dca_mode"
:options="[
{ label: 'Flow Mode (Recommended)', value: 'flow' },
{ label: 'Fixed Mode', value: 'fixed' }
]"
option-label="label"
option-value="value"
label="DCA Strategy *"
hint="Choose how your Bitcoin purchases will be distributed"
></q-select>
<q-input
v-if="registrationForm.dca_mode === 'fixed'"
filled
dense
type="number"
v-model.number="registrationForm.fixed_mode_daily_limit"
label="Daily Limit (GTQ)"
placeholder="Enter daily purchase limit in centavos"
hint="Maximum amount to purchase per day (in centavos: 1 GTQ = 100 centavos)"
:rules="[
val => registrationForm.dca_mode !== 'fixed' || (val && val > 0) || 'Daily limit is required for fixed mode'
]"
></q-input>
<q-banner class="bg-blue-1 text-blue-9 q-mt-md">
<template v-slot:avatar>
<q-icon name="info" color="blue" />
</template>
<div class="text-caption">
<strong>Flow Mode:</strong> Your Bitcoin purchases come at 0% fee when people cash ou at the machine.<br>
<strong>Fixed Mode:</strong> Set a daily limit for consistent Bitcoin accumulation.
</div>
</q-banner>
<div class="row q-mt-lg">
<q-btn
unelevated
color="primary"
type="submit"
:disable="!registrationForm.selectedWallet || !registrationForm.dca_mode"
class="full-width"
size="lg"
>
🚀 Start My DCA Journey
</q-btn>
</div>
</q-form>
</q-card>
</q-dialog>
</div> </div>
{% endblock %} {% endblock %}

View file

@ -214,3 +214,7 @@ async def api_export_transactions(
) )
else: else:
return {"transactions": transactions} return {"transactions": transactions}
# Removed local client-limits endpoint
# Client should call admin extension's public endpoint directly