revert: non-custodial — client supplies addresses+paths to POST /wallets/create

This commit is contained in:
ZOMBIIIIIII
2026-05-11 19:51:10 +03:00
parent 8d91dbeb14
commit c8bc40af97
20 changed files with 122 additions and 1475 deletions

View File

@@ -2,7 +2,6 @@ import { env, getVaultToken } from '../config/env';
import { vaultAppRoleLogin } from '../config/vault';
import { fetchJwtKeysFromVault, swapKeyMap, getKeyMapSize } from './jwt.service';
import { fetchCsrfConfig, swapCsrfConfig } from './csrf.service';
import { fetchMasterKey, swapMasterKey, masterKeyMatches, isCryptoReady } from './crypto.service';
import { logger } from '../lib/logger';
const DEFAULT_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
@@ -15,7 +14,7 @@ let currentVaultToken: string | null = null;
* При любой ошибке оставляем старые значения в памяти, сервис продолжает работать.
*/
export async function refreshAllKeys(): Promise<void> {
const { addr, roleId, secretId, mount, jwtKidPath, jwtKidsPrefix, csrfPath, cryptoKeyPath } = env.vault;
const { addr, roleId, secretId, mount, jwtKidPath, jwtKidsPrefix, csrfPath } = env.vault;
if (!addr || !roleId || !secretId) {
logger.warn('Vault not configured, skipping key refresh');
@@ -34,14 +33,11 @@ export async function refreshAllKeys(): Promise<void> {
currentVaultToken = fresh;
}
// ── Pre-fetch всех секретов параллельно (НЕ мутируя глобал) ───────────
// ── Pre-fetch обоих секретов параллельно (НЕ мутируя глобал) ───────────
const jwtPromise = fetchJwtKeysFromVault(addr, token, mount, jwtKidPath, jwtKidsPrefix);
const csrfPromise = csrfPath ? fetchCsrfConfig(addr, token, mount, csrfPath) : Promise.resolve(null);
// Master-key: первая загрузка обязательна (custodial без него работать не может),
// последующие тики толерантны (если упало — оставляем старый ключ).
const cryptoPromise = cryptoKeyPath ? fetchMasterKey(addr, token, mount, cryptoKeyPath) : Promise.resolve(null);
const [jwtResult, csrfResult, cryptoResult] = await Promise.allSettled([jwtPromise, csrfPromise, cryptoPromise]);
const [jwtResult, csrfResult] = await Promise.allSettled([jwtPromise, csrfPromise]);
// ── Атомарность: если хоть один обязательный fetch упал — НИЧЕГО не меняем ──
if (jwtResult.status === 'rejected') {
@@ -52,36 +48,16 @@ export async function refreshAllKeys(): Promise<void> {
logger.error(`Key refresh ABORTED — CSRF fetch failed: ${csrfResult.reason?.message || csrfResult.reason}`);
return;
}
// Master-key: если он ещё не загружен — это критическая ошибка (отказ при первом запуске).
// Если уже был — оставляем старый (ротация ключа = ломает всю расшифровку, не делаем on rotation).
if (cryptoKeyPath && !isCryptoReady() && cryptoResult.status === 'rejected') {
logger.error(`Key refresh ABORTED — Crypto master key fetch failed: ${cryptoResult.reason?.message || cryptoResult.reason}`);
return;
}
// ── Atomic swap (синхронные операции, нельзя прервать) ──────────────────
swapKeyMap(jwtResult.value);
if (csrfResult.status === 'fulfilled' && csrfResult.value) {
swapCsrfConfig(csrfResult.value);
}
// Master-key загружаем ТОЛЬКО при первой инициализации (потом не ротируем — иначе сломаем расшифровку).
// Если в Vault положили НОВЫЙ ключ — WARN-log, операторская ошибка.
if (cryptoResult.status === 'fulfilled' && cryptoResult.value) {
if (!isCryptoReady()) {
swapMasterKey(cryptoResult.value);
logger.info('Crypto master key loaded');
} else if (!masterKeyMatches(cryptoResult.value)) {
logger.warn(
'Vault crypto/master key DIFFERS from in-memory key. Service continues with old key. ' +
'Rotating master-key bricks all existing encrypted_mnemonic — revert Vault or plan re-encryption migration.'
);
}
}
logger.info(
`Keys refreshed atomically: JWT keys=${getKeyMapSize()}` +
(csrfPath ? `, CSRF=${csrfResult.status === 'fulfilled' ? 'updated' : 'unchanged'}` : '') +
`, Crypto=${isCryptoReady() ? 'ready' : 'NOT-READY'}`
(csrfPath ? `, CSRF=${csrfResult.status === 'fulfilled' ? 'updated' : 'unchanged'}` : '')
);
}