initodwehjowecfuihe
This commit is contained in:
@@ -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);
|
||||
let ts = 0;
|
||||
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 {
|
||||
@@ -262,13 +312,9 @@ function verifyCsrfTokenWithParams(
|
||||
return { valid: false, reason: 'Signature mismatch' };
|
||||
}
|
||||
|
||||
try {
|
||||
const issuedAt = decodeTimestamp(tsStr);
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
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' };
|
||||
const tsResult = verifyCsrfTimestamp(tsStr, cfg.maxAgeSec);
|
||||
if (!tsResult.ok) {
|
||||
return { valid: false, reason: tsResult.reason };
|
||||
}
|
||||
|
||||
return { valid: true };
|
||||
|
||||
Reference in New Issue
Block a user