initodwehjowecfuihe

This commit is contained in:
ZOMBIIIIIII
2026-05-29 01:09:06 +03:00
parent 860a22eb4a
commit 1b3fc444fc

View File

@@ -194,11 +194,61 @@ export function deriveSigningKey(
} }
} }
function decodeTimestamp(encoded: string): number { /** Big-endian int from b64url timestamp chunk (без epoch). */
function decodeTimestampRaw(encoded: string): number {
const raw = b64urlDecode(encoded); const raw = b64urlDecode(encoded);
let ts = 0; let ts = 0;
for (const b of raw) ts = ts * 256 + b; for (const b of raw) ts = ts * 256 + b;
return ts + ITSDANGEROUS_EPOCH; return ts;
}
const TIMESTAMP_SKEW_SEC = 60;
/** Ниже — отсекаем legacy raw, чтобы не путать с unix. */
const MIN_PLAUSIBLE_UNIX = 1_577_836_800;
type TimestampCheck = 'ok' | 'future' | 'expired';
function checkIssuedAt(issuedAt: number, maxAgeSec: number): TimestampCheck {
const now = Math.floor(Date.now() / 1000);
if (issuedAt > now + TIMESTAMP_SKEW_SEC) return 'future';
if (now - issuedAt > maxAgeSec) return 'expired';
return 'ok';
}
/**
* itsdangerous 2.x (prod auth): unix в payload.
* Старый URLSafeTimedSerializer / test-jwt-signer: raw + ITSDANGEROUS_EPOCH.
*/
function verifyCsrfTimestamp(
tsStr: string,
maxAgeSec: number,
): { ok: true; mode: 'unix' | 'legacy-epoch' } | { ok: false; reason: string } {
let raw: number;
try {
raw = decodeTimestampRaw(tsStr);
} catch {
return { ok: false, reason: 'Invalid timestamp' };
}
const candidates: { issuedAt: number; mode: 'unix' | 'legacy-epoch' }[] = [
{ issuedAt: raw, mode: 'unix' },
{ issuedAt: raw + ITSDANGEROUS_EPOCH, mode: 'legacy-epoch' },
];
let lastReason = 'Invalid timestamp';
for (const { issuedAt, mode } of candidates) {
if (issuedAt < MIN_PLAUSIBLE_UNIX) continue;
const check = checkIssuedAt(issuedAt, maxAgeSec);
if (check === 'ok') {
if (mode === 'legacy-epoch') {
logger.warn('CSRF timestamp decoded as legacy-epoch (itsdangerous 1.x / test signer)');
}
return { ok: true, mode };
}
lastReason = check === 'future' ? 'Token from the future' : 'Token expired';
}
return { ok: false, reason: lastReason };
} }
export interface CsrfVerifyResult { export interface CsrfVerifyResult {
@@ -262,13 +312,9 @@ function verifyCsrfTokenWithParams(
return { valid: false, reason: 'Signature mismatch' }; return { valid: false, reason: 'Signature mismatch' };
} }
try { const tsResult = verifyCsrfTimestamp(tsStr, cfg.maxAgeSec);
const issuedAt = decodeTimestamp(tsStr); if (!tsResult.ok) {
const now = Math.floor(Date.now() / 1000); return { valid: false, reason: tsResult.reason };
if (issuedAt > now + 60) return { valid: false, reason: 'Token from the future' };
if (now - issuedAt > cfg.maxAgeSec) return { valid: false, reason: 'Token expired' };
} catch {
return { valid: false, reason: 'Invalid timestamp' };
} }
return { valid: true }; return { valid: true };