@@ -62,14 +38,11 @@
];
private string _randomQuote = string.Empty;
- private bool _isHttpWarning = false;
- private bool _httpWarningDismissed = false;
///
public void Dispose()
{
NavigationManager.LocationChanged -= RefreshQuote;
- NavigationManager.LocationChanged -= CheckHttpProtocol;
}
///
@@ -79,8 +52,6 @@
{
_randomQuote = Quotes[Random.Shared.Next(Quotes.Length)];
NavigationManager.LocationChanged += RefreshQuote;
- NavigationManager.LocationChanged += CheckHttpProtocol;
- CheckHttpProtocol(null, null);
}
}
@@ -92,28 +63,4 @@
_randomQuote = Quotes[Random.Shared.Next(Quotes.Length)];
StateHasChanged();
}
-
- ///
- /// Checks if the current URL is using HTTP and shows warning if needed.
- /// Only shows warning for non-localhost hostnames since browsers allow crypto operations on localhost via HTTP.
- ///
- private void CheckHttpProtocol(object? sender, LocationChangedEventArgs? e)
- {
- var uri = new Uri(NavigationManager.Uri);
- var isLocalhost = uri.Host.Equals("localhost", StringComparison.OrdinalIgnoreCase) ||
- uri.Host.Equals("127.0.0.1", StringComparison.OrdinalIgnoreCase) ||
- uri.Host.Equals("::1", StringComparison.OrdinalIgnoreCase);
- _isHttpWarning = !_httpWarningDismissed && uri.Scheme == "http" && !isLocalhost;
- StateHasChanged();
- }
-
- ///
- /// Dismisses the HTTP warning.
- ///
- private void DismissHttpWarning()
- {
- _httpWarningDismissed = true;
- _isHttpWarning = false;
- StateHasChanged();
- }
}
diff --git a/apps/server/AliasVault.Client/Resources/Layout/Footer.en.resx b/apps/server/AliasVault.Client/Resources/Layout/Footer.en.resx
index 7619df498..bc0918479 100644
--- a/apps/server/AliasVault.Client/Resources/Layout/Footer.en.resx
+++ b/apps/server/AliasVault.Client/Resources/Layout/Footer.en.resx
@@ -78,12 +78,4 @@
Tip: Use the g+l (go lock) keyboard shortcut to lock the vault.
Tip about keyboard shortcut for locking vault
-
- HTTPS Required
- Title for HTTPS warning banner
-
-
- Browsers only allow secure crypto operations via HTTPS, except for localhost. Login/registration won't work over HTTP with the current hostname. Please switch to HTTPS.
- Message explaining why HTTPS is required
-
\ No newline at end of file
diff --git a/apps/server/AliasVault.Client/Resources/Pages/Auth/Start.en.resx b/apps/server/AliasVault.Client/Resources/Pages/Auth/Start.en.resx
index c73f047fc..7e188fe8f 100644
--- a/apps/server/AliasVault.Client/Resources/Pages/Auth/Start.en.resx
+++ b/apps/server/AliasVault.Client/Resources/Pages/Auth/Start.en.resx
@@ -74,4 +74,12 @@
Log in with existing account
Button text for logging in with existing account
+
+ HTTPS Required
+ Title for HTTPS warning banner
+
+
+ Browsers only allow secure crypto operations via HTTPS, except for localhost. Login/registration won't work over HTTP with the current hostname. Please switch to HTTPS.
+ Message explaining why HTTPS is required
+
\ No newline at end of file
diff --git a/apps/server/AliasVault.Client/wwwroot/css/tailwind.css b/apps/server/AliasVault.Client/wwwroot/css/tailwind.css
index 79adb97aa..bf4057d68 100644
--- a/apps/server/AliasVault.Client/wwwroot/css/tailwind.css
+++ b/apps/server/AliasVault.Client/wwwroot/css/tailwind.css
@@ -866,14 +866,6 @@ video {
margin-top: 2rem;
}
-.mb-20 {
- margin-bottom: 5rem;
-}
-
-.ml-4 {
- margin-left: 1rem;
-}
-
.block {
display: block;
}
@@ -1533,6 +1525,11 @@ video {
border-color: rgb(254 215 170 / var(--tw-border-opacity));
}
+.border-orange-500 {
+ --tw-border-opacity: 1;
+ border-color: rgb(249 115 22 / var(--tw-border-opacity));
+}
+
.border-primary-100 {
--tw-border-opacity: 1;
border-color: rgb(253 222 133 / var(--tw-border-opacity));
@@ -1673,6 +1670,11 @@ video {
background-color: rgb(238 242 255 / var(--tw-bg-opacity));
}
+.bg-orange-100 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 237 213 / var(--tw-bg-opacity));
+}
+
.bg-orange-50 {
--tw-bg-opacity: 1;
background-color: rgb(255 247 237 / var(--tw-bg-opacity));
@@ -1779,6 +1781,10 @@ video {
--tw-gradient-to: #d68338 var(--tw-gradient-to-position);
}
+.fill-current {
+ fill: currentColor;
+}
+
.fill-primary-600 {
fill: #d68338;
}
@@ -2154,6 +2160,16 @@ video {
color: rgb(67 56 202 / var(--tw-text-opacity));
}
+.text-orange-500 {
+ --tw-text-opacity: 1;
+ color: rgb(249 115 22 / var(--tw-text-opacity));
+}
+
+.text-orange-700 {
+ --tw-text-opacity: 1;
+ color: rgb(194 65 12 / var(--tw-text-opacity));
+}
+
.text-orange-800 {
--tw-text-opacity: 1;
color: rgb(154 52 18 / var(--tw-text-opacity));
@@ -2558,11 +2574,6 @@ video {
color: rgb(255 255 255 / var(--tw-text-opacity));
}
-.hover\:text-gray-200:hover {
- --tw-text-opacity: 1;
- color: rgb(229 231 235 / var(--tw-text-opacity));
-}
-
.hover\:underline:hover {
text-decoration-line: underline;
}
diff --git a/apps/server/AliasVault.Client/wwwroot/js/crypto.js b/apps/server/AliasVault.Client/wwwroot/js/crypto.js
index a834ce34a..7610ba8af 100644
--- a/apps/server/AliasVault.Client/wwwroot/js/crypto.js
+++ b/apps/server/AliasVault.Client/wwwroot/js/crypto.js
@@ -1,9 +1,34 @@
+/**
+ * Custom error class for crypto availability issues
+ */
+class CryptoNotAvailableError extends Error {
+ constructor(message) {
+ super(message);
+ this.name = 'CryptoNotAvailableError';
+ // Prevent stack trace from being captured
+ this.stack = '';
+ }
+}
+
+/**
+ * Check if crypto API is available and throw user-friendly error if not.
+ */
+function checkCryptoAvailable() {
+ if (!window.crypto || !window.crypto.subtle) {
+ const error = new CryptoNotAvailableError("Cryptographic operations are not available. Please ensure you are accessing AliasVault over HTTPS, as this is required for security features to work properly.");
+ console.error(error.message);
+ throw error;
+ }
+}
+
/**
* AES (symmetric) encryption and decryption functions.
* @type {{encrypt: (function(*, *): Promise), decrypt: (function(*, *): Promise), decryptBytes: (function(*, *): Promise)}}
*/
window.cryptoInterop = {
encrypt: async function (plaintext, base64Key) {
+ checkCryptoAvailable();
+
const key = await window.crypto.subtle.importKey(
"raw",
Uint8Array.from(atob(base64Key), c => c.charCodeAt(0)),
@@ -36,6 +61,8 @@ window.cryptoInterop = {
);
},
decrypt: async function (base64Ciphertext, base64Key) {
+ checkCryptoAvailable();
+
const key = await window.crypto.subtle.importKey(
"raw",
Uint8Array.from(atob(base64Key), c => c.charCodeAt(0)),
@@ -61,6 +88,8 @@ window.cryptoInterop = {
return decoder.decode(decrypted);
},
decryptBytes: async function (base64Ciphertext, base64Key) {
+ checkCryptoAvailable();
+
const key = await window.crypto.subtle.importKey(
"raw",
Uint8Array.from(atob(base64Key), c => c.charCodeAt(0)),
@@ -96,6 +125,8 @@ window.rsaInterop = {
* @returns {Promise<{publicKey: string, privateKey: string}>} A promise that resolves to an object containing the public and private keys as JWK strings.
*/
generateRsaKeyPair : async function() {
+ checkCryptoAvailable();
+
const keyPair = await window.crypto.subtle.generateKey(
{
name: "RSA-OAEP",
@@ -122,6 +153,8 @@ window.rsaInterop = {
* @returns {Promise} A promise that resolves to the encrypted data as a base64-encoded string.
*/
encryptWithPublicKey : async function(plaintext, publicKey) {
+ checkCryptoAvailable();
+
const publicKeyObj = await window.crypto.subtle.importKey(
"jwk",
JSON.parse(publicKey),
@@ -151,6 +184,8 @@ window.rsaInterop = {
* @returns {Promise} A promise that resolves to the decrypted data as a Uint8Array.
*/
decryptWithPrivateKey: async function(ciphertext, privateKey) {
+ checkCryptoAvailable();
+
try {
// Parse the private key
let parsedPrivateKey = JSON.parse(privateKey);