forked from open-webui/open-webui
Introduce canvasPixelTest() intended to validate canvas functionality
Browsers and plugins that spoof canvas data produce corrupt images. In attempt to mitigate: * Add canvasPixelTest() to test a single pixel and test the RGB values * Test canvasPixelTest() inside generateInitialsImage() and use default `/user.png` if failure detected * Call canvasPixelTest() directly within settings to avoid setting an invalid image * Use toast.error() with 10 second autoClose
This commit is contained in:
parent
c8f7bb990c
commit
ac9308dbed
3 changed files with 55 additions and 3 deletions
|
@ -6,7 +6,7 @@
|
||||||
import { updateUserProfile } from '$lib/apis/auths';
|
import { updateUserProfile } from '$lib/apis/auths';
|
||||||
|
|
||||||
import UpdatePassword from './Account/UpdatePassword.svelte';
|
import UpdatePassword from './Account/UpdatePassword.svelte';
|
||||||
import { generateInitialsImage } from '$lib/utils';
|
import { generateInitialsImage, canvasPixelTest } from '$lib/utils';
|
||||||
import { copyToClipboard } from '$lib/utils';
|
import { copyToClipboard } from '$lib/utils';
|
||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
@ -148,7 +148,13 @@
|
||||||
<button
|
<button
|
||||||
class=" text-xs text-gray-600"
|
class=" text-xs text-gray-600"
|
||||||
on:click={async () => {
|
on:click={async () => {
|
||||||
profileImageUrl = generateInitialsImage(name);
|
if (canvasPixelTest()) {
|
||||||
|
profileImageUrl = generateInitialsImage(name);
|
||||||
|
} else {
|
||||||
|
toast.error("Canvas pixel test failed, fingerprint evasion likely. Disable fingerprint evasion and try again!", {
|
||||||
|
autoClose: 1000 * 10,
|
||||||
|
});
|
||||||
|
}
|
||||||
}}>{$i18n.t('Use Gravatar')}</button
|
}}>{$i18n.t('Use Gravatar')}</button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -96,12 +96,52 @@ export const getGravatarURL = (email) => {
|
||||||
return `https://www.gravatar.com/avatar/${hash}`;
|
return `https://www.gravatar.com/avatar/${hash}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const canvasPixelTest = () => {
|
||||||
|
// Test a 1x1 pixel to potentially identify browser/plugin fingerprint blocking or spoofing
|
||||||
|
// Inspiration: https://github.com/kkapsner/CanvasBlocker/blob/master/test/detectionTest.js
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
canvas.height = 1;
|
||||||
|
canvas.width = 1;
|
||||||
|
const imageData = new ImageData(canvas.width, canvas.height);
|
||||||
|
const pixelValues = imageData.data;
|
||||||
|
|
||||||
|
// Generate RGB test data
|
||||||
|
for (let i = 0; i < imageData.data.length; i += 1){
|
||||||
|
if (i % 4 !== 3){
|
||||||
|
pixelValues[i] = Math.floor(256 * Math.random());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pixelValues[i] = 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.putImageData(imageData, 0, 0);
|
||||||
|
const p = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
|
||||||
|
|
||||||
|
// Read RGB data and fail if unmatched
|
||||||
|
for (let i = 0; i < p.length; i += 1){
|
||||||
|
if (p[i] !== pixelValues[i]){
|
||||||
|
console.log("canvasPixelTest: Wrong canvas pixel RGB value detected:", p[i], "at:", i, "expected:", pixelValues[i]);
|
||||||
|
console.log("canvasPixelTest: Canvas blocking or spoofing is likely");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
export const generateInitialsImage = (name) => {
|
export const generateInitialsImage = (name) => {
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
canvas.width = 100;
|
canvas.width = 100;
|
||||||
canvas.height = 100;
|
canvas.height = 100;
|
||||||
|
|
||||||
|
if (!canvasPixelTest()) {
|
||||||
|
console.log("generateInitialsImage: failed pixel test, fingerprint evasion is likely. Using default image.");
|
||||||
|
return '/user.png';
|
||||||
|
}
|
||||||
|
|
||||||
ctx.fillStyle = '#F39C12';
|
ctx.fillStyle = '#F39C12';
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
import { WEBUI_NAME, config, user } from '$lib/stores';
|
import { WEBUI_NAME, config, user } from '$lib/stores';
|
||||||
import { onMount, getContext } from 'svelte';
|
import { onMount, getContext } from 'svelte';
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
import { generateInitialsImage } from '$lib/utils';
|
import { generateInitialsImage, canvasPixelTest } from '$lib/utils';
|
||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
|
@ -43,6 +43,12 @@
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!canvasPixelTest()) {
|
||||||
|
toast.error("Canvas pixel test failed, fingerprint evasion likely. Default image used.", {
|
||||||
|
autoClose: 1000 * 10,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await setSessionUser(sessionUser);
|
await setSessionUser(sessionUser);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue