Diff -- /var/www/vhosts/3dshawn.com/abforge.3dshawn.com/assets/js/fingerprint.js

O Operator
Diff

/var/www/vhosts/3dshawn.com/abforge.3dshawn.com/assets/js/fingerprint.js

added on local at 2026-07-02 17:01:21

Added
+135
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 578cec2be731
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
1// fingerprint.js -- lightweight passive browser fingerprint.
2//
3// Computes a stable-per-browser SHA-256 hash from:
4// canvas rendering, WebGL renderer/vendor, screen dims, timezone,
5// language, platform, available fonts (probed via canvas measure),
6// hardware concurrency, deviceMemory, and touch capability.
7//
8// Injects the resulting 64-char hex hash into a hidden input named
9// `fp_hash` inside the first login/signup form on the page, so a normal
10// form POST carries it through without any XHR.
11//
12// Absolutely no personally-identifying info. This is a device signature
13// used for the blocklist -- when an admin blocks a fingerprint, that
14// hash is compared against the signup/login attempt's hash.
15(function () {
16 'use strict';
17 if (!window.crypto || !window.crypto.subtle) return; // very old browsers -- skip silently
18 if (document.querySelector('form input[name="fp_hash"]')) return; // already injected
19
20 function safe(fn) { try { return fn(); } catch (e) { return ''; } }
21
22 function canvasHash() {
23 return safe(function () {
24 var c = document.createElement('canvas');
25 c.width = 220; c.height = 40;
26 var ctx = c.getContext('2d');
27 if (!ctx) return '';
28 ctx.textBaseline = 'top';
29 ctx.font = "14px 'Arial'";
30 ctx.fillStyle = '#f60';
31 ctx.fillRect(0, 0, 200, 20);
32 ctx.fillStyle = '#069';
33 ctx.fillText('Cwm fjord bank glyphs vext quiz. \u{1F4A1}', 2, 15);
34 ctx.strokeStyle = 'rgba(102,204,0,0.7)';
35 ctx.beginPath();
36 ctx.arc(30, 20, 12, 0, Math.PI * 2, true);
37 ctx.stroke();
38 return c.toDataURL();
39 });
40 }
41
42 function webglHash() {
43 return safe(function () {
44 var c = document.createElement('canvas');
45 var gl = c.getContext('webgl') || c.getContext('experimental-webgl');
46 if (!gl) return '';
47 var dbg = gl.getExtension('WEBGL_debug_renderer_info');
48 var vendor = dbg ? gl.getParameter(dbg.UNMASKED_VENDOR_WEBGL) : '';
49 var render = dbg ? gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL) : '';
50 return vendor + '||' + render + '||' + gl.getParameter(gl.VERSION);
51 });
52 }
53
54 function fontProbe() {
55 // Detect installed fonts by measuring baseline widths and comparing
56 // to a fallback. Not exhaustive -- just enough to add entropy.
57 return safe(function () {
58 var baseFonts = ['monospace', 'sans-serif', 'serif'];
59 var testFonts = ['Arial','Courier New','Georgia','Helvetica','Impact','Tahoma','Times New Roman','Verdana','Trebuchet MS','Comic Sans MS'];
60 var span = document.createElement('span');
61 span.style.cssText = 'position:absolute;left:-9999px;top:-9999px;font-size:72px';
62 span.textContent = 'mmmmmmmmmmlli';
63 document.body.appendChild(span);
64 var widths = {};
65 baseFonts.forEach(function (bf) {
66 span.style.fontFamily = bf;
67 widths[bf] = { w: span.offsetWidth, h: span.offsetHeight };
68 });
69 var detected = testFonts.filter(function (tf) {
70 return baseFonts.some(function (bf) {
71 span.style.fontFamily = tf + ',' + bf;
72 return span.offsetWidth !== widths[bf].w || span.offsetHeight !== widths[bf].h;
73 });
74 });
75 document.body.removeChild(span);
76 return detected.join(',');
77 });
78 }
79
80 function hexDigest(buf) {
81 var arr = new Uint8Array(buf);
82 var hex = '';
83 for (var i = 0; i < arr.length; i++) {
84 var h = arr[i].toString(16);
85 hex += (h.length < 2 ? '0' + h : h);
86 }
87 return hex;
88 }
89
90 function compute() {
91 var parts = [
92 'v1',
93 canvasHash(),
94 webglHash(),
95 fontProbe(),
96 navigator.userAgent || '',
97 navigator.language || '',
98 navigator.platform || '',
99 (navigator.hardwareConcurrency || 0) + '',
100 (navigator.deviceMemory || 0) + '',
101 ('ontouchstart' in window) + '',
102 (screen.width || 0) + 'x' + (screen.height || 0),
103 (screen.colorDepth || 0) + '',
104 (window.devicePixelRatio || 0) + '',
105 safe(function () { return Intl.DateTimeFormat().resolvedOptions().timeZone; }) || '',
106 (new Date().getTimezoneOffset()) + ''
107 ];
108 var input = parts.join('||');
109 var enc = new TextEncoder().encode(input);
110 return window.crypto.subtle.digest('SHA-256', enc).then(hexDigest);
111 }
112
113 function injectInto(form, hash) {
114 if (!form) return;
115 if (form.querySelector('input[name="fp_hash"]')) return;
116 var hidden = document.createElement('input');
117 hidden.type = 'hidden';
118 hidden.name = 'fp_hash';
119 hidden.value = hash;
120 form.appendChild(hidden);
121 }
122
123 function run() {
124 compute().then(function (hash) {
125 // Attach to every form -- login, signup, and any others on the page.
126 document.querySelectorAll('form').forEach(function (f) { injectInto(f, hash); });
127 }).catch(function () { /* silent -- fingerprint is best-effort */ });
128 }
129
130 if (document.readyState === 'loading') {
131 document.addEventListener('DOMContentLoaded', run);
132 } else {
133 run();
134 }
135})();