Commit ac9308db authored by Self Denial's avatar Self Denial
Browse files

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
parent c8f7bb99
...@@ -6,7 +6,7 @@ ...@@ -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 @@ ...@@ -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) => { ...@@ -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 @@ ...@@ -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 @@ ...@@ -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);
}; };
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment