This commit is contained in:
2026-09-13 04:05:59 +02:00
parent 90860b9279
commit 340c825019
48 changed files with 2727 additions and 532 deletions
+141 -17
View File
@@ -84,6 +84,9 @@
this.rawSeed = null;
this.isSessionReady = false;
this.hwFingerprint = null;
try {
this.hwFingerprint = localStorage.getItem('f0ck_anon_hw_fp') || null;
} catch (e) {}
}
/**
@@ -92,6 +95,13 @@
*/
async getHardwareFingerprint() {
if (this.hwFingerprint) return this.hwFingerprint;
try {
const cached = localStorage.getItem('f0ck_anon_hw_fp');
if (cached) {
this.hwFingerprint = cached;
return this.hwFingerprint;
}
} catch (e) {}
try {
let glVendor = '';
@@ -117,16 +127,67 @@
}
} catch (e) {}
// WebGPU adapter architecture (modern GPUs)
let gpuArch = '';
try {
if (navigator.gpu) {
const adapter = await navigator.gpu.requestAdapter();
if (adapter && adapter.info) {
gpuArch = [adapter.info.architecture, adapter.info.vendor, adapter.info.description].filter(Boolean).join(':');
}
}
} catch (e) {}
// CPU & Memory
const concurrency = navigator.hardwareConcurrency || 0;
const memory = navigator.deviceMemory || 0;
const platform = navigator.platform || '';
// Display, Gamut & Dynamic Range
const screenInfo = [
window.screen ? window.screen.width : 0,
window.screen ? window.screen.height : 0,
window.screen ? window.screen.availWidth : 0,
window.screen ? window.screen.availHeight : 0,
window.screen ? window.screen.colorDepth : 0,
window.screen ? window.screen.pixelDepth : 0,
window.devicePixelRatio || 1
window.devicePixelRatio || 1,
window.matchMedia && window.matchMedia('(color-gamut: p3)').matches ? 'p3' : (window.matchMedia && window.matchMedia('(color-gamut: rec2020)').matches ? 'rec2020' : 'srgb'),
window.matchMedia && window.matchMedia('(dynamic-range: high)').matches ? 'hdr' : 'sdr'
].join('x');
// Input & Peripheral Hardware (touch, mouse, stylus)
const touchPoints = (typeof navigator !== 'undefined' && 'maxTouchPoints' in navigator) ? navigator.maxTouchPoints : 0;
const pointerType = (window.matchMedia && window.matchMedia('(pointer: fine)').matches) ? 'fine' : ((window.matchMedia && window.matchMedia('(pointer: coarse)').matches) ? 'coarse' : 'none');
const hoverType = (window.matchMedia && window.matchMedia('(hover: hover)').matches) ? 'hover' : 'none';
const inputHardware = `${touchPoints}:${pointerType}:${hoverType}`;
// 2D Canvas Font Rasterizer & Subpixel Geometry
let canvasFp = '';
try {
const c2d = document.createElement('canvas');
c2d.width = 240;
c2d.height = 60;
const ctx = c2d.getContext('2d');
if (ctx) {
ctx.textBaseline = 'alphabetic';
ctx.fillStyle = '#f60';
ctx.fillRect(10, 5, 60, 20);
ctx.fillStyle = '#069';
ctx.font = '14pt Arial, sans-serif';
ctx.fillText('f0ck.dev <canvas> 😃', 4, 35);
ctx.fillStyle = 'rgba(102, 204, 0, 0.7)';
ctx.font = '16pt Times, serif';
ctx.fillText('f0ck.dev <canvas> 😃', 20, 48);
const imgData = ctx.getImageData(0, 0, 240, 60).data;
let sum = 0;
for (let i = 0; i < imgData.length; i += 4) {
sum = (sum * 31 + imgData[i] + imgData[i+1] + imgData[i+2] + imgData[i+3]) >>> 0;
}
canvasFp = sum.toString(16);
}
} catch (e) {}
// Web Audio DSP Floating-Point Math
let audioFp = '';
try {
const AudioContext = window.OfflineAudioContext || window.webkitOfflineAudioContext;
@@ -159,14 +220,26 @@
}
} catch (e) {}
// FPU Math Microarchitecture Precision
const mathPrecision = [
Math.tan(-1e300).toString().substring(0, 10),
Math.sinh(1).toString().substring(0, 10),
Math.acos(0.123456789).toString().substring(0, 10)
].join(';');
const rawHardware = [
glVendor,
glRenderer,
glLimits,
gpuArch,
concurrency,
memory,
platform,
screenInfo,
audioFp
inputHardware,
canvasFp,
audioFp,
mathPrecision
].join('~~~');
const encoder = new TextEncoder();
@@ -174,6 +247,9 @@
const hashBuf = await window.crypto.subtle.digest('SHA-256', data);
const hashHex = bytesToHex(new Uint8Array(hashBuf));
this.hwFingerprint = `HW:${hashHex}`;
try {
localStorage.setItem('f0ck_anon_hw_fp', this.hwFingerprint);
} catch (e) {}
return this.hwFingerprint;
} catch (err) {
console.warn('[ANON_SSH] Failed to compute hardware fingerprint:', err);
@@ -483,7 +559,7 @@
localStorage.setItem(STORAGE_KEY_PUB, this.pubkey);
localStorage.setItem(STORAGE_KEY_FP, this.fingerprint);
await this.ensureSession(true);
await this.ensureSession(true, isExplicitLogin);
return this.getIdentity();
}
@@ -514,7 +590,11 @@
/**
* Authenticate to backend and establish/refresh anonymous session
*/
async ensureSession(force = false) {
async ensureSession(force = false, isExplicitLogin = false) {
if (window.location.pathname === '/banned') {
return;
}
// If user is already logged in as a real registered user, don't overwrite session!
if (window.f0ckSession && window.f0ckSession.user && !window.f0ckSession.is_anon) {
return;
@@ -550,8 +630,15 @@
reason: data.reason,
expires: data.expires
});
if (window.location.pathname !== '/banned') {
window.location.href = data.redirect || '/banned';
this.clearStoredIdentity();
this.updateNavUI(false);
// ONLY redirect to /banned if user was explicitly trying to login!
if (isExplicitLogin) {
const anonModal = document.getElementById('anon-ssh-modal');
if (anonModal) anonModal.style.display = 'none';
if (window.location.pathname !== '/banned') {
window.location.href = data.redirect || '/banned';
}
}
return;
}
@@ -594,6 +681,9 @@
setTombstone(tombstone) {
try {
localStorage.setItem('f0ck_anon_tombstone', JSON.stringify(tombstone));
if (tombstone && tombstone.banned) {
document.cookie = `f0ck_banned=${encodeURIComponent(JSON.stringify(tombstone))}; Path=/; Max-Age=31536000; SameSite=Lax`;
}
} catch (e) {}
}
@@ -653,6 +743,7 @@
localStorage.removeItem(STORAGE_KEY_PRIV);
localStorage.removeItem(STORAGE_KEY_PUB);
localStorage.removeItem(STORAGE_KEY_FP);
document.cookie = 'f0ck_banned=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax';
this.cryptoKey = null;
this.rawSeed = null;
this.rawPub = null;
@@ -669,11 +760,27 @@
if (window.f0ckSession && window.f0ckSession.enable_anonymous_access === false) {
return;
}
// If client has a ban tombstone, redirect to /banned directly on login attempt
const tombstone = this.getTombstone();
if (tombstone && tombstone.banned) {
const anonModal = document.getElementById('anon-ssh-modal');
if (anonModal) anonModal.style.display = 'none';
if (window.location.pathname !== '/banned') {
window.location.href = '/banned';
}
return;
}
try {
if (!this.pubkey) {
await this.generateIdentity();
await this.generateIdentity(true);
} else {
await this.ensureSession(true, true);
}
await this.ensureSession(true);
if (!this.isSessionReady) return;
// Sync any guest favorites saved in localStorage to this anon account
if (window.f0ckGuestFavs && typeof window.f0ckGuestFavs.importToAccount === 'function') {
await window.f0ckGuestFavs.importToAccount();
@@ -698,12 +805,16 @@
this.clearStoredIdentity();
await fetch('/api/v2/anon/logout', { method: 'POST', credentials: 'same-origin' }).catch(() => {});
} finally {
window.location.href = '/';
this.updateNavUI(false);
if (typeof window.showToastNotification === 'function') {
window.showToastNotification('Logged out from anonymous session');
}
window.location.reload();
}
}
/**
* Update visitor navbar UI elements
* Update navigation bar elements based on anonymous authentication state
*/
updateNavUI(isAnon) {
const icon = document.getElementById('nav-visitor-icon');
@@ -765,8 +876,12 @@
* Initialize on page startup
*/
async init() {
if (window.location.pathname === '/banned') {
return;
}
if (window.f0ckSession && window.f0ckSession.enable_anonymous_access === false) {
if (window.f0ckSession.is_anon) {
if (window.f0ckSession.is_anon && window.f0ckSession.logged_in) {
await fetch('/api/v2/anon/logout', { method: 'POST', credentials: 'same-origin' }).catch(() => {});
window.location.reload();
return;
@@ -780,6 +895,15 @@
return;
}
// If client has a ban tombstone, do NOT attempt background session handshake on page load/reload!
const tombstone = this.getTombstone();
if (tombstone && tombstone.banned) {
this.clearStoredIdentity();
this.updateNavUI(false);
this.attachUIListeners();
return;
}
const storedPriv = localStorage.getItem(STORAGE_KEY_PRIV);
const storedPub = localStorage.getItem(STORAGE_KEY_PUB);
const storedFp = localStorage.getItem(STORAGE_KEY_FP);
@@ -808,12 +932,12 @@
// User previously logged in as anonymous with this key
// If backend session is already active, avoid redundant session churn
if (window.f0ckSession && window.f0ckSession.is_anon) {
if (window.f0ckSession && window.f0ckSession.is_anon && window.f0ckSession.logged_in) {
this.isSessionReady = true;
this.updateNavUI(true);
} else {
await this.ensureSession();
this.updateNavUI(true);
// Unauthenticated guest state: do NOT auto-login as anonymous on page load!
this.updateNavUI(false);
}
} catch (err) {
console.warn('[ANON_SSH] Failed to restore stored key:', err);
@@ -822,7 +946,7 @@
}
} else {
// Clean guest state! Do NOT auto-generate or establish session!
if (window.f0ckSession && window.f0ckSession.is_anon) {
if (window.f0ckSession && window.f0ckSession.is_anon && window.f0ckSession.logged_in) {
// Stale backend anon session cookie without corresponding local key: clear cookie
await fetch('/api/v2/anon/logout', { method: 'POST', credentials: 'same-origin' }).catch(() => {});
window.location.reload();
@@ -853,7 +977,7 @@
}
const logoutTarget = e.target.closest('#nav-anon-logout-btn, a[href="/logout"]');
if (logoutTarget && ((window.f0ckSession && window.f0ckSession.is_anon) || this.pubkey)) {
if (logoutTarget && ((window.f0ckSession && window.f0ckSession.is_anon && window.f0ckSession.logged_in) || this.pubkey)) {
e.preventDefault();
this.logoutAnonymous();
return;