Merge pull request #2940 from appwrite/fix/fingerprint-server-time-sync

fix: sync fingerprint timestamp with server clock
This commit is contained in:
Damodar Lohani
2026-03-29 13:02:22 +05:45
committed by GitHub
2 changed files with 33 additions and 2 deletions
+24 -1
View File
@@ -3,6 +3,29 @@ import { env } from '$env/dynamic/public';
const SECRET = env.PUBLIC_CONSOLE_FINGERPRINT_KEY ?? '';
const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
/** Cached server timestamp and the local time it was fetched at, for interpolation. */
let serverTimeCache: { serverSecs: number; fetchedAtMs: number } | null = null;
/**
* Cache the server's clock so fingerprint timestamps always align with the
* backend's clock, regardless of local clock drift.
*
* @param serverTimeSecs - the server's unix timestamp in seconds
* (e.g. parsed from a response Date header)
*/
export function syncServerTime(serverTimeSecs: number): void {
if (serverTimeCache) return;
serverTimeCache = { serverSecs: serverTimeSecs, fetchedAtMs: Date.now() };
}
function getServerTimestamp(): number {
if (!serverTimeCache) {
return Math.floor(Date.now() / 1000);
}
const elapsedSecs = Math.floor((Date.now() - serverTimeCache.fetchedAtMs) / 1000);
return serverTimeCache.serverSecs + elapsedSecs;
}
async function sha256(message: string): Promise<string> {
if (!crypto?.subtle) {
console.warn('crypto.subtle unavailable, fingerprinting disabled');
@@ -204,7 +227,7 @@ export async function generateFingerprintToken(): Promise<string> {
const signals: BrowserSignals = {
...staticSignals,
timestamp: Math.floor(Date.now() / 1000)
timestamp: getServerTimestamp()
};
const payload = JSON.stringify(signals);
+9 -1
View File
@@ -6,6 +6,7 @@ import { Platform, Query } from '@appwrite.io/console';
import { makePlansMap } from '$lib/helpers/billing';
import { plansInfo as plansInfoStore } from '$lib/stores/billing';
import { normalizeConsoleVariables } from '$lib/helpers/domains';
import { syncServerTime } from '$lib/helpers/fingerprint';
export const load: LayoutLoad = async ({ depends, parent }) => {
const { organizations, plansInfo } = await parent();
@@ -28,7 +29,14 @@ export const load: LayoutLoad = async ({ depends, parent }) => {
plansArrayPromise,
fetch(`${endpoint}/health/version`, {
headers: { 'X-Appwrite-Project': project as string }
}).then((response) => response.json() as { version?: string }),
}).then((response) => {
const dateHeader = response.headers.get('Date');
const parsed = dateHeader ? new Date(dateHeader).getTime() : NaN;
if (Number.isFinite(parsed)) {
syncServerTime(Math.floor(parsed / 1000));
}
return response.json() as { version?: string };
}),
sdk.forConsole.console.variables()
]);