Improved input validation, added tests

This commit is contained in:
Djuri Baars 2024-08-31 19:54:43 +02:00
parent cb9bfa4499
commit e21b9895a7
4 changed files with 120 additions and 16 deletions

View file

@ -42,9 +42,11 @@
"nostrRelay": "Nostr Relay",
"nostrZapNotify": "Nostr Zap Notifications",
"useNostr": "Use Nostr datasource",
"bitaxeHostname": "BitAxe hostname",
"bitaxeHostname": "BitAxe hostname or IP",
"bitaxeEnabled": "Enable BitAxe",
"nostrZapPubkey": "Nostr Zap pubkey"
"nostrZapPubkey": "Nostr Zap pubkey",
"invalidNostrPubkey": "Invalid Nostr pubkey, note that your pubkey does NOT start with npub.",
"convertingValidNpub": "Converting valid npub to pubkey"
},
"control": {
"systemInfo": "System info",

View file

@ -133,7 +133,7 @@
<div class="">
<Toast
isOpen={toastIsOpen}
class="me-1 bg-{toastColor}"
class="me-1 bg-{toastColor} text-bg-{toastColor}"
autohide
on:close={() => (toastIsOpen = false)}
>

View file

@ -1,5 +1,5 @@
<script lang="ts">
import { isValidNostrRelay, getPubKey, isValidHexPubKey } from '$lib';
import { isValidNostrRelay, getPubKey, isValidHexPubKey, isValidNpub } from '$lib';
import { PUBLIC_BASE_URL } from '$lib/config';
import { uiSettings } from '$lib/uiSettings';
import { createEventDispatcher } from 'svelte';
@ -52,6 +52,10 @@
const onSave = async (e: Event) => {
e.preventDefault();
// const form = e.target as HTMLFormElement;
// const formData = new FormData(form);
let formSettings = $settings;
delete formSettings['gitRev'];
@ -84,8 +88,49 @@
validNostrRelay = await isValidNostrRelay($settings.nostrRelay);
};
const checkValidNostrPubkey = () => {
$settings.nostrPubKey = getPubKey($settings.nostrPubKey);
let validBitaxe = false;
const testBitaxe = async () => {
try {
const response = await fetch(`http://${$settings.bitaxeHostname}/api/system/info`);
if (!response.ok) {
dispatch('showToast', {
color: 'danger',
text: `Failed to connect to BitAxe HTTP error! status: ${response.status}`
});
validBitaxe = false;
throw new Error();
}
const systemInfo = await response.json();
dispatch('showToast', {
color: 'success',
text: `Connected to BitAxe ${systemInfo.ASICModel} (Board version ${systemInfo.boardVersion}) running firmware ${systemInfo.version}.\r\nCurrent hashrate ${Math.round(systemInfo.hashRate)} GH/s`
});
validBitaxe = true;
} catch (error) {
if (error instanceof TypeError && error.message.includes('Failed to fetch')) {
dispatch('showToast', {
color: 'danger',
text: `Failed to connect to BitAxe, make sure you are connected to the same network.`
});
}
console.error('Failed to fetch Bitaxe system info:', error);
validBitaxe = false;
}
};
const checkValidNostrPubkey = (key) => {
if (isValidNpub($settings[key])) {
dispatch('showToast', {
color: 'info',
text: $_('section.settings.convertingValidNpub')
});
}
let ret = getPubKey($settings[key]);
if (ret) $settings[key] = ret;
};
const onFlBrightnessChange = async () => {
@ -160,6 +205,7 @@
id="timePerScreen"
min={1}
step="1"
required
bind:value={$settings.timePerScreen}
/>
<InputGroupText>{$_('time.minutes')}</InputGroupText>
@ -177,6 +223,7 @@
id="fullRefreshMin"
min={1}
step="1"
required
bind:value={$settings.fullRefreshMin}
/>
<InputGroupText>{$_('time.minutes')}</InputGroupText>
@ -212,6 +259,7 @@
step="1"
name="tzOffset"
id="tzOffset"
required
bind:value={$settings.tzOffset}
/>
<InputGroupText>{$_('time.minutes')}</InputGroupText>
@ -297,17 +345,23 @@
>{$_('section.settings.bitaxeHostname')}</Label
>
<Col md="6">
<InputGroup size={$uiSettings.inputSize}>
<Input
type="text"
bind:value={$settings.bitaxeHostname}
name="bitaxeHostname"
valid={validBitaxe}
id="bitaxeHostname"
bsSize={$uiSettings.inputSize}
required
></Input>
<Button type="button" color="success" on:click={testBitaxe}
>{$_('test', { default: 'Test' })}</Button
>
</InputGroup>
</Col>
</Row>
{/if}
{#if 'nostrZapNotify' in $settings}
{#if 'nostrZapNotify' in $settings && $settings['nostrZapNotify']}
<Row>
<Label md={6} for="nostrZapPubkey" size={$uiSettings.inputSize}
>{$_('section.settings.nostrZapPubkey')}</Label
@ -318,10 +372,15 @@
bind:value={$settings.nostrZapPubkey}
name="nostrZapPubkey"
id="nostrZapPubkey"
on:change={checkValidNostrPubkey}
on:change={() => checkValidNostrPubkey('nostrZapPubkey')}
invalid={!isValidHexPubKey($settings.nostrZapPubkey)}
bsSize={$uiSettings.inputSize}
required
minlength="64"
></Input>
{#if !isValidHexPubKey($settings.nostrZapPubkey)}
<FormText>{$_('section.settings.invalidNostrPubkey')}</FormText>
{/if}
</Col>
</Row>
{/if}
@ -336,10 +395,13 @@
bind:value={$settings.nostrPubKey}
name="nostrPubKey"
id="nostrPubKey"
on:change={checkValidNostrPubkey}
on:change={() => checkValidNostrPubkey('nostrPubKey')}
invalid={!isValidHexPubKey($settings.nostrPubKey)}
bsSize={$uiSettings.inputSize}
></Input>
{#if !isValidHexPubKey($settings.nostrPubKey)}
<FormText>{$_('section.settings.invalidNostrPubkey')}</FormText>
{/if}
</Col>
</Row>
{/if}
@ -357,6 +419,7 @@
id="nostrRelay"
valid={validNostrRelay}
bsSize={$uiSettings.inputSize}
required
></Input>
<Button type="button" color="success" on:click={testNostrRelay}
>{$_('test', { default: 'Test' })}</Button
@ -378,6 +441,7 @@
id="mempoolInstance"
disabled={$settings.ownDataSource}
bsSize="sm"
required
></Input>
<InputGroupText>
<Input
@ -404,6 +468,8 @@
name="hostnamePrefix"
id="hostnamePrefix"
bsSize={$uiSettings.inputSize}
required
minlength="1"
></Input>
</Col>
</Row>
@ -439,6 +505,7 @@
min={1}
step="1"
bind:value={$settings.wpTimeout}
required
/>
<InputGroupText>{$_('time.seconds')}</InputGroupText>
</InputGroup>

View file

@ -44,6 +44,13 @@ const settingsJson = {
ip: '192.168.20.231',
txPower: 78,
gitRev: '25d8b92bcbc8938417c140355ea3ba99ff9eb4b7',
gitTag: '3.1.9',
bitaxeEnabled: false,
bitaxeHostname: 'bitaxe1',
nostrZapNotify: true,
hwRev: 'REV_A_EPD_2_13',
fsRev: '4c5d9616212b27e3f05c35370f0befcf2c5a04b2',
nostrZapPubkey: 'b5127a08cf33616274800a4387881a9f98e04b9c37116e92de5250498635c422',
lastBuildTime: '1700666677',
screens: [
{ id: 0, name: 'Block Height', enabled: true },
@ -187,6 +194,34 @@ test('info message when fetch eur price is enabled', async ({ page }) => {
await expect(page.getByText('the WS Price connection will show')).toBeVisible();
});
test('npub values will be converted to hex pubkeys', async ({ page }) => {
await page.goto('/');
for (const field of ['#nostrZapPubkey']) {
for (const val of ['npub1k5f85zx0xdskyayqpfpc0zq6n7vwqjuuxugkayk72fgynp34cs3qfcvqg2']) {
await page.fill(field, val);
await page.getByLabel('Nostr Relay').click();
const resultValue = await page.$eval(field, (input: HTMLInputElement) => input.value);
expect(resultValue).toBe('b5127a08cf33616274800a4387881a9f98e04b9c37116e92de5250498635c422');
}
}
});
test('empty nostr relay field is not accepted', async ({ page }) => {
await page.goto('/');
const nostrRelayField = page.getByLabel('Nostr Relay');
nostrRelayField.fill('');
await page.getByRole('button', { name: 'Save' }).click();
const validationMessage = await nostrRelayField.evaluate((el) => el.validationMessage);
expect(validationMessage).toContain('Please fill out this field');
});
test('screens should be able to change', async ({ page }) => {
await page.goto('/');
await expect(page.getByRole('button', { name: 'Sats per Dollar' })).toBeVisible();