mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-14 02:15:57 -04:00
Tweak HTTPS required message, tweak crypto.js error handling (#1181)
This commit is contained in:
@@ -30,6 +30,22 @@
|
||||
<p class="text-lg text-gray-600 dark:text-gray-300 mb-8">
|
||||
@Localizer["TaglineText"]
|
||||
</p>
|
||||
@if (_isHttpWarning)
|
||||
{
|
||||
<div class="bg-orange-100 border-l-4 border-orange-500 text-orange-700 p-4 mb-6" role="alert">
|
||||
<div class="flex">
|
||||
<div class="py-1">
|
||||
<svg class="fill-current h-6 w-6 text-orange-500 mr-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
|
||||
<path d="M2.93 17.07A10 10 0 1 1 17.07 2.93 10 10 0 0 1 2.93 17.07zm12.73-1.41A8 8 0 1 0 4.34 4.34a8 8 0 0 0 11.32 11.32zM9 11V9h2v6H9v-4zm0-6h2v2H9V5z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-bold">@Localizer["HttpsWarningTitle"]</p>
|
||||
<p class="text-sm">@Localizer["HttpsWarningMessage"]</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="space-y-4">
|
||||
@if (Config.PublicRegistrationEnabled)
|
||||
{
|
||||
@@ -50,6 +66,7 @@
|
||||
|
||||
@code {
|
||||
private IStringLocalizer Localizer => LocalizerFactory.Create("Pages.Auth.Start", "AliasVault.Client");
|
||||
private bool _isHttpWarning = false;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnInitializedAsync()
|
||||
@@ -62,5 +79,20 @@
|
||||
// Already authenticated, redirect to home page.
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
|
||||
CheckHttpProtocol();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
private void CheckHttpProtocol()
|
||||
{
|
||||
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 = uri.Scheme == "http" && !isLocalhost;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,31 +4,7 @@
|
||||
@using Microsoft.Extensions.Localization
|
||||
@implements IDisposable
|
||||
|
||||
@if (_isHttpWarning)
|
||||
{
|
||||
<div class="fixed bottom-0 left-0 right-0 z-50 bg-orange-500 text-white px-4 py-3">
|
||||
<div class="container mx-auto">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-3">
|
||||
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
<div>
|
||||
<p class="font-medium">@Localizer["HttpsWarningTitle"]</p>
|
||||
<p class="text-sm">@Localizer["HttpsWarningMessage"]</p>
|
||||
</div>
|
||||
</div>
|
||||
<button @onclick="DismissHttpWarning" class="text-white hover:text-gray-200 ml-4">
|
||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<footer class="relative -z-10 lg:fixed bottom-0 left-0 right-0 dark:bg-gray-900 @(ShowBorder ? "border-t border-gray-200 dark:border-gray-700" : "") @(_isHttpWarning ? "mb-20" : "")">
|
||||
<footer class="relative -z-10 lg:fixed bottom-0 left-0 right-0 dark:bg-gray-900 @(ShowBorder ? "border-t border-gray-200 dark:border-gray-700" : "")">
|
||||
<div class="container mx-auto px-4 py-4">
|
||||
<div class="flex flex-col lg:flex-row justify-between items-center">
|
||||
<p class="text-sm text-center text-gray-500 mb-4 lg:mb-0">
|
||||
@@ -62,14 +38,11 @@
|
||||
];
|
||||
|
||||
private string _randomQuote = string.Empty;
|
||||
private bool _isHttpWarning = false;
|
||||
private bool _httpWarningDismissed = false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
NavigationManager.LocationChanged -= RefreshQuote;
|
||||
NavigationManager.LocationChanged -= CheckHttpProtocol;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dismisses the HTTP warning.
|
||||
/// </summary>
|
||||
private void DismissHttpWarning()
|
||||
{
|
||||
_httpWarningDismissed = true;
|
||||
_isHttpWarning = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,12 +78,4 @@
|
||||
<value>Tip: Use the g+l (go lock) keyboard shortcut to lock the vault.</value>
|
||||
<comment>Tip about keyboard shortcut for locking vault</comment>
|
||||
</data>
|
||||
<data name="HttpsWarningTitle" xml:space="preserve">
|
||||
<value>HTTPS Required</value>
|
||||
<comment>Title for HTTPS warning banner</comment>
|
||||
</data>
|
||||
<data name="HttpsWarningMessage" xml:space="preserve">
|
||||
<value>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.</value>
|
||||
<comment>Message explaining why HTTPS is required</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -74,4 +74,12 @@
|
||||
<value>Log in with existing account</value>
|
||||
<comment>Button text for logging in with existing account</comment>
|
||||
</data>
|
||||
<data name="HttpsWarningTitle" xml:space="preserve">
|
||||
<value>HTTPS Required</value>
|
||||
<comment>Title for HTTPS warning banner</comment>
|
||||
</data>
|
||||
<data name="HttpsWarningMessage" xml:space="preserve">
|
||||
<value>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.</value>
|
||||
<comment>Message explaining why HTTPS is required</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<string>), decrypt: (function(*, *): Promise<string>), decryptBytes: (function(*, *): Promise<Uint8Array>)}}
|
||||
*/
|
||||
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<string>} 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<Uint8Array>} 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);
|
||||
|
||||
Reference in New Issue
Block a user