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", "nostrRelay": "Nostr Relay",
"nostrZapNotify": "Nostr Zap Notifications", "nostrZapNotify": "Nostr Zap Notifications",
"useNostr": "Use Nostr datasource", "useNostr": "Use Nostr datasource",
"bitaxeHostname": "BitAxe hostname", "bitaxeHostname": "BitAxe hostname or IP",
"bitaxeEnabled": "Enable BitAxe", "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": { "control": {
"systemInfo": "System info", "systemInfo": "System info",

View file

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

View file

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { isValidNostrRelay, getPubKey, isValidHexPubKey } from '$lib'; import { isValidNostrRelay, getPubKey, isValidHexPubKey, isValidNpub } from '$lib';
import { PUBLIC_BASE_URL } from '$lib/config'; import { PUBLIC_BASE_URL } from '$lib/config';
import { uiSettings } from '$lib/uiSettings'; import { uiSettings } from '$lib/uiSettings';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
@ -52,6 +52,10 @@
const onSave = async (e: Event) => { const onSave = async (e: Event) => {
e.preventDefault(); e.preventDefault();
// const form = e.target as HTMLFormElement;
// const formData = new FormData(form);
let formSettings = $settings; let formSettings = $settings;
delete formSettings['gitRev']; delete formSettings['gitRev'];
@ -84,8 +88,49 @@
validNostrRelay = await isValidNostrRelay($settings.nostrRelay); validNostrRelay = await isValidNostrRelay($settings.nostrRelay);
}; };
const checkValidNostrPubkey = () => { let validBitaxe = false;
$settings.nostrPubKey = getPubKey($settings.nostrPubKey); 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 () => { const onFlBrightnessChange = async () => {
@ -160,6 +205,7 @@
id="timePerScreen" id="timePerScreen"
min={1} min={1}
step="1" step="1"
required
bind:value={$settings.timePerScreen} bind:value={$settings.timePerScreen}
/> />
<InputGroupText>{$_('time.minutes')}</InputGroupText> <InputGroupText>{$_('time.minutes')}</InputGroupText>
@ -177,6 +223,7 @@
id="fullRefreshMin" id="fullRefreshMin"
min={1} min={1}
step="1" step="1"
required
bind:value={$settings.fullRefreshMin} bind:value={$settings.fullRefreshMin}
/> />
<InputGroupText>{$_('time.minutes')}</InputGroupText> <InputGroupText>{$_('time.minutes')}</InputGroupText>
@ -212,6 +259,7 @@
step="1" step="1"
name="tzOffset" name="tzOffset"
id="tzOffset" id="tzOffset"
required
bind:value={$settings.tzOffset} bind:value={$settings.tzOffset}
/> />
<InputGroupText>{$_('time.minutes')}</InputGroupText> <InputGroupText>{$_('time.minutes')}</InputGroupText>
@ -297,17 +345,23 @@
>{$_('section.settings.bitaxeHostname')}</Label >{$_('section.settings.bitaxeHostname')}</Label
> >
<Col md="6"> <Col md="6">
<InputGroup size={$uiSettings.inputSize}>
<Input <Input
type="text" type="text"
bind:value={$settings.bitaxeHostname} bind:value={$settings.bitaxeHostname}
name="bitaxeHostname" name="bitaxeHostname"
valid={validBitaxe}
id="bitaxeHostname" id="bitaxeHostname"
bsSize={$uiSettings.inputSize} required
></Input> ></Input>
<Button type="button" color="success" on:click={testBitaxe}
>{$_('test', { default: 'Test' })}</Button
>
</InputGroup>
</Col> </Col>
</Row> </Row>
{/if} {/if}
{#if 'nostrZapNotify' in $settings} {#if 'nostrZapNotify' in $settings && $settings['nostrZapNotify']}
<Row> <Row>
<Label md={6} for="nostrZapPubkey" size={$uiSettings.inputSize} <Label md={6} for="nostrZapPubkey" size={$uiSettings.inputSize}
>{$_('section.settings.nostrZapPubkey')}</Label >{$_('section.settings.nostrZapPubkey')}</Label
@ -318,10 +372,15 @@
bind:value={$settings.nostrZapPubkey} bind:value={$settings.nostrZapPubkey}
name="nostrZapPubkey" name="nostrZapPubkey"
id="nostrZapPubkey" id="nostrZapPubkey"
on:change={checkValidNostrPubkey} on:change={() => checkValidNostrPubkey('nostrZapPubkey')}
invalid={!isValidHexPubKey($settings.nostrZapPubkey)} invalid={!isValidHexPubKey($settings.nostrZapPubkey)}
bsSize={$uiSettings.inputSize} bsSize={$uiSettings.inputSize}
required
minlength="64"
></Input> ></Input>
{#if !isValidHexPubKey($settings.nostrZapPubkey)}
<FormText>{$_('section.settings.invalidNostrPubkey')}</FormText>
{/if}
</Col> </Col>
</Row> </Row>
{/if} {/if}
@ -336,10 +395,13 @@
bind:value={$settings.nostrPubKey} bind:value={$settings.nostrPubKey}
name="nostrPubKey" name="nostrPubKey"
id="nostrPubKey" id="nostrPubKey"
on:change={checkValidNostrPubkey} on:change={() => checkValidNostrPubkey('nostrPubKey')}
invalid={!isValidHexPubKey($settings.nostrPubKey)} invalid={!isValidHexPubKey($settings.nostrPubKey)}
bsSize={$uiSettings.inputSize} bsSize={$uiSettings.inputSize}
></Input> ></Input>
{#if !isValidHexPubKey($settings.nostrPubKey)}
<FormText>{$_('section.settings.invalidNostrPubkey')}</FormText>
{/if}
</Col> </Col>
</Row> </Row>
{/if} {/if}
@ -357,6 +419,7 @@
id="nostrRelay" id="nostrRelay"
valid={validNostrRelay} valid={validNostrRelay}
bsSize={$uiSettings.inputSize} bsSize={$uiSettings.inputSize}
required
></Input> ></Input>
<Button type="button" color="success" on:click={testNostrRelay} <Button type="button" color="success" on:click={testNostrRelay}
>{$_('test', { default: 'Test' })}</Button >{$_('test', { default: 'Test' })}</Button
@ -378,6 +441,7 @@
id="mempoolInstance" id="mempoolInstance"
disabled={$settings.ownDataSource} disabled={$settings.ownDataSource}
bsSize="sm" bsSize="sm"
required
></Input> ></Input>
<InputGroupText> <InputGroupText>
<Input <Input
@ -404,6 +468,8 @@
name="hostnamePrefix" name="hostnamePrefix"
id="hostnamePrefix" id="hostnamePrefix"
bsSize={$uiSettings.inputSize} bsSize={$uiSettings.inputSize}
required
minlength="1"
></Input> ></Input>
</Col> </Col>
</Row> </Row>
@ -439,6 +505,7 @@
min={1} min={1}
step="1" step="1"
bind:value={$settings.wpTimeout} bind:value={$settings.wpTimeout}
required
/> />
<InputGroupText>{$_('time.seconds')}</InputGroupText> <InputGroupText>{$_('time.seconds')}</InputGroupText>
</InputGroup> </InputGroup>

View file

@ -44,6 +44,13 @@ const settingsJson = {
ip: '192.168.20.231', ip: '192.168.20.231',
txPower: 78, txPower: 78,
gitRev: '25d8b92bcbc8938417c140355ea3ba99ff9eb4b7', gitRev: '25d8b92bcbc8938417c140355ea3ba99ff9eb4b7',
gitTag: '3.1.9',
bitaxeEnabled: false,
bitaxeHostname: 'bitaxe1',
nostrZapNotify: true,
hwRev: 'REV_A_EPD_2_13',
fsRev: '4c5d9616212b27e3f05c35370f0befcf2c5a04b2',
nostrZapPubkey: 'b5127a08cf33616274800a4387881a9f98e04b9c37116e92de5250498635c422',
lastBuildTime: '1700666677', lastBuildTime: '1700666677',
screens: [ screens: [
{ id: 0, name: 'Block Height', enabled: true }, { 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(); 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 }) => { test('screens should be able to change', async ({ page }) => {
await page.goto('/'); await page.goto('/');
await expect(page.getByRole('button', { name: 'Sats per Dollar' })).toBeVisible(); await expect(page.getByRole('button', { name: 'Sats per Dollar' })).toBeVisible();