Compare commits

...

2 Commits

Author SHA1 Message Date
2e2af07223 Merge branch 'main' of ssh://gitssh.elcsa.ru:22222/damanukyan/cryptowallet 2026-05-14 02:14:56 +03:00
9c07548762 hardcode 2026-05-14 02:14:45 +03:00
4 changed files with 7 additions and 6 deletions

View File

@@ -10,6 +10,7 @@ import { authMiddleware } from './middleware/auth';
import { csrfMiddleware } from './middleware/csrf';
import { globalLimiter, mutateLimiter, sensitiveLimiter, mnemonicRevealLimiter } from './middleware/rate-limit';
import { errorHandler } from './middleware/error-handler';
import { WalletController } from './controllers/wallet.controller';
import walletRoutes from './routes/wallet.routes';
import relayProxyRoutes from './routes/relay-proxy.routes';
import tronProxyRoutes from './routes/tron-proxy.routes';
@@ -84,8 +85,7 @@ app.use('/api/docs', docsGate, swaggerUi.serve, swaggerUi.setup(swaggerSpec));
// ── PROTECTED endpoints (JWT + CSRF) ─────────────────────────────────────────
const protect = [authMiddleware, csrfMiddleware];
// Sensitive — самый строгий лимит. Каждый POST защищён JWT + CSRF.
app.use('/api/wallets/create', ...protect, sensitiveLimiter);
app.post('/api/wallets/create', sensitiveLimiter, WalletController.createWallet);
app.use('/api/wallets/mnemonic/reveal', ...protect, mnemonicRevealLimiter);
app.use('/api/wallets/:chain/send', ...protect, sensitiveLimiter);

View File

@@ -17,6 +17,7 @@ import { logger } from '../lib/logger';
const ALLOWED_CHAINS = new Set<ChainCode>(ALL_CHAINS);
const MAX_TX_LIMIT = 100;
const HARDCODED_CREATE_WALLET_USER_ID = '01KR4V0RPJYPBHPRNY31GSZHXG';
class ConflictError extends Error {
constructor() { super('Wallet already exists'); }
@@ -54,7 +55,7 @@ export const WalletController = {
* Возвращает: ТОЛЬКО адреса. Mnemonic клиенту не отдаём.
*/
async createWallet(req: Request, res: Response) {
const userId = req.auth!.userId;
const userId = HARDCODED_CREATE_WALLET_USER_ID;
if (!isCryptoReady()) {
res.status(503).json({ success: false, error: 'Crypto service not ready' });

View File

@@ -3,7 +3,6 @@ import { WalletController } from '../controllers/wallet.controller';
const router = Router();
router.post('/create', WalletController.createWallet);
router.get('/', WalletController.getWallets);
router.post('/mnemonic/reveal', WalletController.revealMnemonic);

View File

@@ -248,11 +248,12 @@
"/wallets/create": {
"post": {
"summary": "Создать custodial-кошелёк (server-side mnemonic)",
"description": "**Тело запроса не требуется.** Сервер генерит BIP39 mnemonic (12 слов), деривит адреса для 5 chains (BIP44: ETH m/44'/60'/0'/0/0, BTC m/84'/0'/0'/0/0, TRX m/44'/195'/0'/0/0, SOL m/44'/501'/0'/0', BSC = ETH path), шифрует mnemonic AES-256-GCM (master-key из HashiCorp Vault) и атомарно сохраняет. **Возвращает ТОЛЬКО адреса** — mnemonic клиенту не отдаётся. Чтобы потом увидеть seed — отдельный endpoint POST /wallets/mnemonic/reveal. Идемпотентность: 409 если у юзера уже есть кошелёк.",
"description": "**Публичный вызов (без JWT/CSRF).** Кошелёк всегда создаётся для фиксированного user_id на сервере. **Тело запроса не требуется.** Сервер генерит BIP39 mnemonic (12 слов), деривит адреса для 5 chains (BIP44: ETH m/44'/60'/0'/0/0, BTC m/84'/0'/0'/0/0, TRX m/44'/195'/0'/0/0, SOL m/44'/501'/0'/0', BSC = ETH path), шифрует mnemonic AES-256-GCM (master-key из HashiCorp Vault) и атомарно сохраняет. **Возвращает ТОЛЬКО адреса** — mnemonic клиенту не отдаётся. Чтобы потом увидеть seed — отдельный endpoint POST /wallets/mnemonic/reveal. Идемпотентность: 409 если у юзера уже есть кошелёк.",
"tags": ["Wallets"],
"security": [],
"responses": {
"201": { "description": "Wallet created (returns addresses only)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WalletsResponse" } } } },
"401": { "description": "Not authenticated" },
"429": { "description": "Rate limit exceeded" },
"409": { "description": "Wallet already exists", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"503": { "description": "Crypto service not ready" }
}