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>
*
* Digests: sha1 (20-byte sig, legacy Flask-WTF), sha256 (32), sha512 (64, itsdangerous 2.x default).
*
* Default algorithm (itsdangerous ≥ 2.0):
* - digest: SHA-512 (HMAC)
* - 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' };
const lastDot = token.lastIndexOf('.');
@@ -169,8 +184,8 @@ function verifyCsrfTokenWithConfig(cfg: CsrfConfig, token: string): CsrfVerifyRe
const tsStr = payloadTs.slice(prevDot + 1);
const derived = deriveKey(cfg.secret, cfg.salt, cfg.digest);
const expectedSig = crypto.createHmac(cfg.digest, derived).update(payloadTs).digest();
const derived = deriveKey(cfg.secret, cfg.salt, digest);
const expectedSig = crypto.createHmac(digest, derived).update(payloadTs).digest();
let actualSig: Buffer;
try {
@@ -203,29 +218,48 @@ function verifyCsrfTokenWithConfig(cfg: CsrfConfig, token: string): CsrfVerifyRe
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),
* retry once with the alternate digest — типичный случай Flask-WTF / itsdangerous 2.x.
* Verify CSRF token. Fallback по длине подписи: sha1 (20) / sha256 (32) / sha512 (64).
*/
export function verifyCsrfToken(token: string): CsrfVerifyResult {
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.reason !== 'Signature length mismatch') {
if (primary.reason !== 'Signature length mismatch' && primary.reason !== 'Signature mismatch') {
return primary;
}
const altDigest: 'sha256' | 'sha512' = current.digest === 'sha256' ? 'sha512' : 'sha256';
const fallback = verifyCsrfTokenWithConfig({ ...current, digest: altDigest }, token);
if (fallback.valid) {
for (const digest of digestsToTry(primary)) {
if (digest === primaryDigest) continue;
const attempt = verifyCsrfTokenWithDigest(current, digest, token);
if (attempt.valid) {
logger.warn(
`CSRF verified with fallback digest ${altDigest} (Vault digest=${current.digest}). ` +
'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 primary;
}