remove /api/vault endpoints

This commit is contained in:
ZOMBIIIIIII
2026-05-11 18:36:44 +03:00
parent 64696b334c
commit 8d91dbeb14
10 changed files with 12 additions and 211 deletions

View File

@@ -1,20 +0,0 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"parserOptions": {
"ecmaVersion": 2022,
"sourceType": "module"
},
"rules": {
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
"@typescript-eslint/no-namespace": "off",
"no-console": "off"
},
"ignorePatterns": ["dist/", "node_modules/"]
}

View File

@@ -11,7 +11,6 @@ import { csrfMiddleware } from './middleware/csrf';
import { globalLimiter, mutateLimiter, sensitiveLimiter, mnemonicRevealLimiter } from './middleware/rate-limit';
import { errorHandler } from './middleware/error-handler';
import walletRoutes from './routes/wallet.routes';
import vaultRoutes from './routes/vault.routes';
import relayProxyRoutes from './routes/relay-proxy.routes';
import tronProxyRoutes from './routes/tron-proxy.routes';
import solSwapProxyRoutes from './routes/sol-swap-proxy.routes';
@@ -55,7 +54,6 @@ const protect = [authMiddleware, csrfMiddleware];
app.use('/api/wallets/create', ...protect, sensitiveLimiter);
app.use('/api/wallets/mnemonic/reveal', ...protect, mnemonicRevealLimiter);
app.use('/api/wallets/:chain/send', ...protect, sensitiveLimiter);
app.use('/api/vault', ...protect, sensitiveLimiter, vaultRoutes);
// Mutating (proxy + read endpoints) — повышенный лимит
app.use('/api/wallets', ...protect, mutateLimiter, walletRoutes);

View File

@@ -1,70 +0,0 @@
import { Request, Response } from 'express';
import { UserModel } from '../models/user.model';
import { logger } from '../lib/logger';
const MAX_VAULT_SIZE = 8192; // base64 encrypted blob upper limit
const MAX_SALT_LEN = 128;
/**
* Encrypted vault — opaque blob (зашифрованный mnemonic, AES-GCM на клиенте).
* Сервис хранит как есть; никогда не расшифровывает. Ключ только у клиента
* (PBKDF2(password+pin) или аналог).
*/
export const VaultController = {
/**
* GET /api/vault — вернуть encrypted_vault + vault_salt пользователя.
*/
async getVault(req: Request, res: Response) {
const userId = req.auth!.userId;
try {
const row = await UserModel.getVault(userId);
if (!row || !row.encrypted_vault || !row.vault_salt) {
res.status(404).json({ success: false, error: 'Vault not found' });
return;
}
res.json({
success: true,
data: {
encryptedVault: row.encrypted_vault,
vaultSalt: row.vault_salt,
},
});
} catch (err: any) {
logger.error(`getVault failed for user ${userId}: ${err.stack || err.message}`);
res.status(500).json({ success: false, error: 'Internal error' });
}
},
/**
* PUT /api/vault — сохранить новый encrypted_vault + vault_salt.
* Создаёт user-row если её ещё нет.
*/
async putVault(req: Request, res: Response) {
const userId = req.auth!.userId;
const { encryptedVault, vaultSalt } = req.body ?? {};
if (typeof encryptedVault !== 'string' || encryptedVault.length === 0 || encryptedVault.length > MAX_VAULT_SIZE) {
res.status(400).json({
success: false,
error: `encryptedVault must be a non-empty string (max ${MAX_VAULT_SIZE} chars)`,
});
return;
}
if (typeof vaultSalt !== 'string' || vaultSalt.length === 0 || vaultSalt.length > MAX_SALT_LEN) {
res.status(400).json({
success: false,
error: `vaultSalt must be a non-empty string (max ${MAX_SALT_LEN} chars)`,
});
return;
}
try {
await UserModel.ensureExists(userId);
await UserModel.setVault(userId, encryptedVault, vaultSalt);
res.json({ success: true });
} catch (err: any) {
logger.error(`putVault failed for user ${userId}: ${err.stack || err.message}`);
res.status(500).json({ success: false, error: 'Internal error' });
}
},
};

View File

@@ -61,23 +61,6 @@ export const UserModel = {
return user;
},
async setVault(id: string, encryptedVault: string, vaultSalt: string): Promise<void> {
await db('users')
.where({ id })
.update({
encrypted_vault: encryptedVault,
vault_salt: vaultSalt,
updated_at: db.fn.now(),
});
},
async getVault(id: string): Promise<{ encrypted_vault: string | null; vault_salt: string | null } | undefined> {
return db('users')
.where({ id, is_deleted: false })
.select('encrypted_vault', 'vault_salt')
.first();
},
/**
* Custodial: атомарно записать зашифрованную мнемонику.
* Используется set-once семантика: UPDATE WHERE encrypted_mnemonic IS NULL,

View File

@@ -1,9 +0,0 @@
import { Router } from 'express';
import { VaultController } from '../controllers/vault.controller';
const router = Router();
router.get('/', VaultController.getVault);
router.put('/', VaultController.putVault);
export default router;

View File

@@ -12,7 +12,6 @@
{ "name": "System", "description": "Health & service info" },
{ "name": "Wallets", "description": "User wallet records" },
{ "name": "Wallet Ops", "description": "Per-chain balance / transactions / send" },
{ "name": "Vault", "description": "Encrypted mnemonic blob storage (opaque)" },
{ "name": "BTC", "description": "Bitcoin RPC proxy (Blockstream)" },
{ "name": "TRON", "description": "TRON RPC proxy (TronGrid)" },
{ "name": "Solana", "description": "Solana swap proxy (Jupiter)" },
@@ -144,27 +143,6 @@
"description": "Unsigned tx — формат зависит от chain (kind: btc | tron | evm | solana). Клиент подписывает приватом и broadcast'ит через соответствующий /api/{btc,tron}/broadcast endpoint"
}
}
},
"VaultResponse": {
"type": "object",
"properties": {
"success": { "type": "boolean" },
"data": {
"type": "object",
"properties": {
"encryptedVault": { "type": "string", "description": "AES-GCM encrypted mnemonic, base64" },
"vaultSalt": { "type": "string", "description": "PBKDF2 salt, hex" }
}
}
}
},
"VaultPutRequest": {
"type": "object",
"required": ["encryptedVault", "vaultSalt"],
"properties": {
"encryptedVault": { "type": "string", "maxLength": 8192 },
"vaultSalt": { "type": "string", "maxLength": 128 }
}
}
}
},
@@ -285,29 +263,6 @@
}
},
"/vault": {
"get": {
"summary": "Get user's encrypted mnemonic vault",
"tags": ["Vault"],
"responses": {
"200": { "description": "Encrypted vault blob", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/VaultResponse" } } } },
"404": { "description": "Vault not yet stored" }
}
},
"put": {
"summary": "Save / replace encrypted mnemonic vault",
"description": "Vault — opaque blob (AES-GCM на стороне клиента). Сервер хранит как есть, не расшифровывает.",
"tags": ["Vault"],
"requestBody": {
"required": true,
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/VaultPutRequest" } } }
},
"responses": {
"200": { "description": "Saved", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SuccessEmpty" } } } },
"400": { "description": "Invalid input" }
}
}
},
"/btc/utxos/{address}": {
"get": {