inithilyhb

This commit is contained in:
ZOMBIIIIIII
2026-05-28 23:48:09 +03:00
parent 31aba0b681
commit b2ab5f0421

View File

@@ -8,6 +8,8 @@ import { logger } from '../lib/logger';
* *
* Token format: <b64url_payload>.<b64url_timestamp>.<b64url_signature> * Token format: <b64url_payload>.<b64url_timestamp>.<b64url_signature>
* *
* Digests: sha1 (20-byte sig, legacy Flask-WTF), sha256 (32), sha512 (64, itsdangerous 2.x default).
*
* Default algorithm (itsdangerous ≥ 2.0): * Default algorithm (itsdangerous ≥ 2.0):
* - digest: SHA-512 (HMAC) * - digest: SHA-512 (HMAC)
* - salt: "itsdangerous.Signer" (or app-specific, e.g. "csrf-token") * - salt: "itsdangerous.Signer" (or app-specific, e.g. "csrf-token")
@@ -155,7 +157,20 @@ export function generateCsrfToken(): { token: string; maxAgeSec: number } {
}; };
} }
function verifyCsrfTokenWithConfig(cfg: CsrfConfig, token: string): CsrfVerifyResult { type CsrfDigest = 'sha1' | 'sha256' | 'sha512';
/** HMAC output length → digest (auth-service legacy часто sha1 → sigLen=20). */
const DIGEST_BY_SIG_LEN: Record<number, CsrfDigest> = {
20: 'sha1',
32: 'sha256',
64: 'sha512',
};
function verifyCsrfTokenWithDigest(
cfg: CsrfConfig,
digest: CsrfDigest,
token: string,
): CsrfVerifyResult {
if (!token || typeof token !== 'string') return { valid: false, reason: 'Empty token' }; if (!token || typeof token !== 'string') return { valid: false, reason: 'Empty token' };
const lastDot = token.lastIndexOf('.'); const lastDot = token.lastIndexOf('.');
@@ -169,8 +184,8 @@ function verifyCsrfTokenWithConfig(cfg: CsrfConfig, token: string): CsrfVerifyRe
const tsStr = payloadTs.slice(prevDot + 1); const tsStr = payloadTs.slice(prevDot + 1);
const derived = deriveKey(cfg.secret, cfg.salt, cfg.digest); const derived = deriveKey(cfg.secret, cfg.salt, digest);
const expectedSig = crypto.createHmac(cfg.digest, derived).update(payloadTs).digest(); const expectedSig = crypto.createHmac(digest, derived).update(payloadTs).digest();
let actualSig: Buffer; let actualSig: Buffer;
try { try {
@@ -203,28 +218,47 @@ function verifyCsrfTokenWithConfig(cfg: CsrfConfig, token: string): CsrfVerifyRe
return { valid: true }; return { valid: true };
} }
function digestsToTry(primary: CsrfVerifyResult): CsrfDigest[] {
const order: CsrfDigest[] = [];
const add = (d: CsrfDigest) => {
if (!order.includes(d)) order.push(d);
};
add(current!.digest);
if (primary.actualSigLen !== undefined) {
const inferred = DIGEST_BY_SIG_LEN[primary.actualSigLen];
if (inferred) add(inferred);
}
add('sha1');
add('sha256');
add('sha512');
return order;
}
/** /**
* Verify CSRF token. If Vault digest differs from auth-service (sha256 vs sha512), * Verify CSRF token. Fallback по длине подписи: sha1 (20) / sha256 (32) / sha512 (64).
* retry once with the alternate digest — типичный случай Flask-WTF / itsdangerous 2.x.
*/ */
export function verifyCsrfToken(token: string): CsrfVerifyResult { export function verifyCsrfToken(token: string): CsrfVerifyResult {
if (!current) return { valid: false, reason: 'CSRF secret not loaded' }; if (!current) return { valid: false, reason: 'CSRF secret not loaded' };
const primary = verifyCsrfTokenWithConfig(current, token); const primaryDigest = current.digest;
const primary = verifyCsrfTokenWithDigest(current, primaryDigest, token);
if (primary.valid) return primary; if (primary.valid) return primary;
if (primary.reason !== 'Signature length mismatch') { if (primary.reason !== 'Signature length mismatch' && primary.reason !== 'Signature mismatch') {
return primary; return primary;
} }
const altDigest: 'sha256' | 'sha512' = current.digest === 'sha256' ? 'sha512' : 'sha256'; for (const digest of digestsToTry(primary)) {
const fallback = verifyCsrfTokenWithConfig({ ...current, digest: altDigest }, token); if (digest === primaryDigest) continue;
if (fallback.valid) { const attempt = verifyCsrfTokenWithDigest(current, digest, token);
logger.warn( if (attempt.valid) {
`CSRF verified with fallback digest ${altDigest} (Vault digest=${current.digest}). ` + logger.warn(
'Align auth-service URLSafeTimedSerializer digest_method with Vault `digest` field.', `CSRF verified with fallback digest ${digest} (Vault digest=${primaryDigest}, ` +
); `sigLen=${primary.actualSigLen ?? '?'}). Align auth digest_method with Vault.`,
return { valid: true }; );
return { valid: true };
}
} }
return primary; return primary;