From ad9e35a2689f11669ef91b504aec6b7175096bd5 Mon Sep 17 00:00:00 2001
From: Djuri Baars <dsbaars@users.noreply.github.com>
Date: Tue, 3 Sep 2024 01:07:23 +0200
Subject: [PATCH] Add WebUI authentication

---
 package.json               |  1 +
 src/lib/locales/de.json    |  5 ++-
 src/lib/locales/en.json    |  6 ++-
 src/lib/locales/es.json    |  5 ++-
 src/lib/locales/nl.json    |  5 ++-
 src/lib/style/app.scss     |  6 +++
 src/routes/Settings.svelte | 83 +++++++++++++++++++++++++++++++++-----
 yarn.lock                  |  5 +++
 8 files changed, 102 insertions(+), 14 deletions(-)

diff --git a/package.json b/package.json
index 6ea1f5a..e736c40 100644
--- a/package.json
+++ b/package.json
@@ -51,6 +51,7 @@
 		"@noble/secp256k1": "^2.1.0",
 		"@playwright/test": "^1.40.0",
 		"bootstrap": "^5.3.2",
+		"bootstrap-icons": "^1.11.3",
 		"nostr-tools": "^2.7.1",
 		"patch-package": "^8.0.0",
 		"svelte-i18n": "^4.0.0",
diff --git a/src/lib/locales/de.json b/src/lib/locales/de.json
index e34c1f6..9ece840 100644
--- a/src/lib/locales/de.json
+++ b/src/lib/locales/de.json
@@ -38,7 +38,10 @@
 			"luxLightToggle": "Automatisches Umschalten des Frontlichts bei Lux",
 			"wpTimeout": "WiFi-Konfigurationsportal timeout",
 			"useNostr": "Nostr-Datenquelle verwenden",
-			"flDisable": "Displaybeleuchtung deaktivieren"
+			"flDisable": "Displaybeleuchtung deaktivieren",
+			"httpAuthUser": "WebUI-Benutzername",
+			"httpAuthPass": "WebUI-Passwort",
+			"httpAuthText": "Schützt nur die WebUI mit einem Passwort, nicht API-Aufrufe."
 		},
 		"control": {
 			"systemInfo": "Systeminfo",
diff --git a/src/lib/locales/en.json b/src/lib/locales/en.json
index 188c794..2d85180 100644
--- a/src/lib/locales/en.json
+++ b/src/lib/locales/en.json
@@ -47,7 +47,11 @@
 			"nostrZapPubkey": "Nostr Zap pubkey",
 			"invalidNostrPubkey": "Invalid Nostr pubkey, note that your pubkey does NOT start with npub.",
 			"convertingValidNpub": "Converting valid npub to pubkey",
-			"flDisable": "Disable frontlight"
+			"flDisable": "Disable frontlight",
+			"httpAuthEnabled": "Require authentication for WebUI",
+			"httpAuthUser": "WebUI Username",
+			"httpAuthPass": "WebUI Password",
+			"httpAuthText": "Only password-protects WebUI, not API-calls."
 		},
 		"control": {
 			"systemInfo": "System info",
diff --git a/src/lib/locales/es.json b/src/lib/locales/es.json
index 6bf8260..af5cc35 100644
--- a/src/lib/locales/es.json
+++ b/src/lib/locales/es.json
@@ -37,7 +37,10 @@
 			"luxLightToggle": "Cambio automático de luz frontal en lux",
 			"wpTimeout": "Portal de configuración WiFi timeout",
 			"useNostr": "Utilice la fuente de datos Nostr",
-			"flDisable": "Desactivar luz de la pantalla"
+			"flDisable": "Desactivar luz de la pantalla",
+			"httpAuthUser": "Nombre de usuario WebUI",
+			"httpAuthPass": "Contraseña WebUI",
+			"httpAuthText": "Solo la WebUI está protegida con contraseña, no las llamadas API."
 		},
 		"control": {
 			"turnOff": "Apagar",
diff --git a/src/lib/locales/nl.json b/src/lib/locales/nl.json
index e984c98..eab8ea7 100644
--- a/src/lib/locales/nl.json
+++ b/src/lib/locales/nl.json
@@ -38,7 +38,10 @@
 			"luxLightToggle": "Schakelen displaylicht op lux",
 			"wpTimeout": "WiFi-config-portal timeout",
 			"useNostr": "Gebruik Nostr-gegevensbron",
-			"flDisable": "Schakel Displaylicht uit"
+			"flDisable": "Schakel Displaylicht uit",
+			"httpAuthUser": "WebUI-gebruikersnaam",
+			"httpAuthPass": "WebUI-wachtwoord",
+			"httpAuthText": "Beveiligd enkel WebUI, niet de API."
 		},
 		"control": {
 			"systemInfo": "Systeeminformatie",
diff --git a/src/lib/style/app.scss b/src/lib/style/app.scss
index c8b6e52..59cc5ac 100644
--- a/src/lib/style/app.scss
+++ b/src/lib/style/app.scss
@@ -7,6 +7,12 @@
 @import '@fontsource/oswald/latin-400.css';
 @import './satsymbol';
 
+$bootstrap-icons-font: 'bootstrap-icons' !default;
+$bootstrap-icons-font-dir: 'bootstrap-icons/font//fonts' !default;
+$bootstrap-icons-font-file: '#{$bootstrap-icons-font-dir}/#{$bootstrap-icons-font}' !default;
+
+@import 'bootstrap-icons/font/bootstrap-icons.scss';
+
 $color-mode-type: media-query;
 $font-family-base: 'Ubuntu';
 $font-size-base: 0.9rem;
diff --git a/src/routes/Settings.svelte b/src/routes/Settings.svelte
index bd0141a..e41424c 100644
--- a/src/routes/Settings.svelte
+++ b/src/routes/Settings.svelte
@@ -14,6 +14,7 @@
 		Col,
 		Form,
 		FormText,
+		Icon,
 		Input,
 		InputGroup,
 		InputGroupText,
@@ -62,18 +63,32 @@
 		delete formSettings['ip'];
 		delete formSettings['lastBuildTime'];
 
+		let headers = new Headers({
+			'Content-Type': 'application/json'
+		});
+
+		//if ($settings.httpAuthEnabled) {
+		//	headers.set('Authorization', 'Basic ' + btoa($settings.httpAuthUser + ":" + $settings.httpAuthPass));
+		//}
+
 		await fetch(`${PUBLIC_BASE_URL}/api/json/settings`, {
 			method: 'PATCH',
-			headers: {
-				'Content-Type': 'application/json'
-			},
+			headers: headers,
+			credentials: 'same-origin',
 			body: JSON.stringify(formSettings)
 		})
-			.then(() => {
-				dispatch('showToast', {
-					color: 'success',
-					text: $_('section.settings.settingsSaved')
-				});
+			.then((data) => {
+				if (data.status == 200) {
+					dispatch('showToast', {
+						color: 'success',
+						text: $_('section.settings.settingsSaved')
+					});
+				} else {
+					dispatch('showToast', {
+						color: 'danger',
+						text: `${data.status}: ${data.statusText}`
+					});
+				}
 			})
 			.catch(() => {
 				dispatch('showToast', {
@@ -142,6 +157,8 @@
 		});
 	};
 
+	let showPassword = false;
+
 	// You can also add more props if needed
 	export let xs = 12;
 	export let sm = xs;
@@ -457,6 +474,43 @@
 						<FormText>{$_('section.settings.mempoolInstanceHelpText')}</FormText>
 					</Col>
 				</Row>
+				{#if $settings.httpAuthEnabled}
+					<Row>
+						<Label md={6} for="httpAuthUser" size="sm">{$_('section.settings.httpAuthUser')}</Label>
+						<Col md="6">
+							<Input
+								type="text"
+								bind:value={$settings.httpAuthUser}
+								name="httpAuthUser"
+								id="httpAuthUser"
+								bsSize="sm"
+								required
+							></Input>
+						</Col>
+					</Row>
+					<Row>
+						<Label md={6} for="httpAuthPass" size="sm">{$_('section.settings.httpAuthPass')}</Label>
+						<Col md="6">
+							<InputGroup size={$uiSettings.inputSize}>
+								<Input
+									type={showPassword ? 'text' : 'password'}
+									bind:value={$settings.httpAuthPass}
+									name="httpAuthPass"
+									id="httpAuthPass"
+									bsSize="sm"
+									required
+								></Input>
+								<Button
+									type="button"
+									on:click={() => (showPassword = !showPassword)}
+									color={showPassword ? 'success' : 'danger'}
+									><Icon name={showPassword ? 'eye-slash' : 'eye'}></Icon></Button
+								>
+							</InputGroup>
+							<FormText>{$_('section.settings.httpAuthText')}</FormText>
+						</Col>
+					</Row>
+				{/if}
 				<Row>
 					<Label md={6} for="hostnamePrefix" size={$uiSettings.inputSize}
 						>{$_('section.settings.hostnamePrefix')}</Label
@@ -482,7 +536,7 @@
 							type="select"
 							bind:value={$settings.txPower}
 							name="select"
-							id="fgColor"
+							id="wifiTxPower"
 							bsSize={$uiSettings.inputSize}
 							class={$uiSettings.selectClass}
 						>
@@ -501,7 +555,7 @@
 						<InputGroup size={$uiSettings.inputSize}>
 							<Input
 								type="number"
-								id="minSecPriceUpd"
+								id="wpTimeout"
 								min={1}
 								step="1"
 								bind:value={$settings.wpTimeout}
@@ -685,6 +739,15 @@
 							label="{$_('section.settings.enableMdns')} ({$_('restartRequired')})"
 						/>
 					</Col>
+					<Col md="6" xl="12" xxl="6">
+						<Input
+							id="httpAuthEnabled"
+							bind:checked={$settings.httpAuthEnabled}
+							type="switch"
+							bsSize={$uiSettings.inputSize}
+							label="{$_('section.settings.httpAuthEnabled')} ({$_('restartRequired')})"
+						/>
+					</Col>
 				</Row>
 
 				<Row>
diff --git a/yarn.lock b/yarn.lock
index cd0d480..59b3537 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1758,6 +1758,11 @@ bl@^4.0.3:
     inherits "^2.0.4"
     readable-stream "^3.4.0"
 
+bootstrap-icons@^1.11.3:
+  version "1.11.3"
+  resolved "https://registry.yarnpkg.com/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz#03f9cb754ec005c52f9ee616e2e84a82cab3084b"
+  integrity sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==
+
 bootstrap@^5.3.2:
   version "5.3.3"
   resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.3.tgz#de35e1a765c897ac940021900fcbb831602bac38"