init2222
This commit is contained in:
29
README.md
29
README.md
@@ -15,7 +15,8 @@ vault kv put dev-secrets/crypto/master key=$(openssl rand -hex 32)
|
||||
# 2. CSRF secret в Vault
|
||||
vault kv put dev-secrets/csrf secret_key=$(openssl rand -hex 32) salt=csrf-salt digest=sha256
|
||||
|
||||
# 3. DB schema (миграция идемпотентна — безопасно прогонять на existing БД)
|
||||
# 3. DB schema — APPEND-ONLY / NON-DESTRUCTIVE
|
||||
# Безопасно прогонять на existing БД. См. ниже "Schema is non-destructive".
|
||||
psql -h <db-host> -U postgres_user -d postgres -f cryptowallet-schema.sql
|
||||
|
||||
# 4. bitok public key в Vault (для kid из JWT header)
|
||||
@@ -93,9 +94,33 @@ ssh server@<host> -p 2222 'cd cryptowallet && docker compose up -d --build'
|
||||
- **Relay proxy** whitelist method+path — `/quote` (POST), `/intents/status/v3` (GET), `/execute/{swap|bridge}` (POST). Никаких freeform action'ов
|
||||
- **Bridge UI dropdown** ETH/BSC/SOL only (TRX через Relay убран — плохая ликвидность)
|
||||
|
||||
## Schema is non-destructive
|
||||
|
||||
`cryptowallet-schema.sql` **append-only**. Re-run на боксе с уже настроенной БД = **zero DDL changes**. Если оператор добавил кастомные таблицы / индексы / constraints вручную — они **никогда** не будут перезаписаны или удалены.
|
||||
|
||||
Что делает script:
|
||||
- `CREATE TABLE IF NOT EXISTS users` / `wallets`
|
||||
- `ALTER TABLE users ADD COLUMN <X>` (только если колонки нет — `encrypted_mnemonic`, `erc20`, `passport_data`)
|
||||
- `CREATE UNIQUE INDEX users_email_lower_unique` (если индекса нет)
|
||||
- `CREATE INDEX idx_users_active` / `idx_wallets_*` (если индексов нет)
|
||||
- `ADD CONSTRAINT` × 4 (только если данного constraint name нет)
|
||||
|
||||
Что script **НЕ делает**:
|
||||
- ❌ Никогда не `DROP TABLE`
|
||||
- ❌ Никогда не `DROP CONSTRAINT`
|
||||
- ❌ Никогда не `DROP COLUMN`
|
||||
- ❌ Никогда не перезаписывает существующие constraints / indexes
|
||||
|
||||
Legacy cleanup (audit_log, idempotency_keys, sessions от старых версий) — **manual one-time** операторская задача, не часть этого script'а:
|
||||
```bash
|
||||
psql ... -c "DROP TABLE IF EXISTS audit_log CASCADE;"
|
||||
psql ... -c "DROP TABLE IF EXISTS idempotency_keys CASCADE;"
|
||||
psql ... -c "DROP TABLE IF EXISTS sessions CASCADE;"
|
||||
```
|
||||
|
||||
## Logs
|
||||
|
||||
Файловых логов **нет**. Всё в stdout, подбирается Docker log driver:
|
||||
Файловых логов **нет**. Весь код пишет в `process.stdout` (см. `apps/api/src/lib/logger.ts` и `lib/audit-log.ts`). Docker подбирает stdout через json-file driver и показывает через `docker compose logs`:
|
||||
|
||||
```bash
|
||||
docker compose logs -f api # все логи (structured JSON)
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
-- ╔══════════════════════════════════════════════════════════════════╗
|
||||
-- ║ CryptoWallet API — Production DB schema (idempotent, custodial) ║
|
||||
-- ║ Применять: psql -h <host> -U postgres_user -d postgres -f ... ║
|
||||
-- ║ Безопасно прогонять повторно на existing БД. ║
|
||||
-- ║ CryptoWallet API — Production DB schema ║
|
||||
-- ║ ║
|
||||
-- ║ APPEND-ONLY / NON-DESTRUCTIVE: ║
|
||||
-- ║ Безопасно прогонять повторно. Ничего не DROP'ает, не overwrite. ║
|
||||
-- ║ Если оператор добавил кастомные таблицы / индексы / constraints ║
|
||||
-- ║ вручную — они НЕ будут затронуты. ║
|
||||
-- ║ ║
|
||||
-- ║ Применять: psql -h <host> -U <user> -d <db> -f cryptowallet-schema.sql ║
|
||||
-- ╚══════════════════════════════════════════════════════════════════╝
|
||||
|
||||
-- NOTE: idempotency_keys + audit_log таблицы УДАЛЕНЫ из БД.
|
||||
-- - idempotency_keys → KeyDB (Redis cache), см. apps/api/src/config/redis.ts
|
||||
-- - audit_log → stdout-only (Docker logs / log-aggregator подбирает JSON lines)
|
||||
-- Migration ниже drop'ает их если они существуют от прошлой версии.
|
||||
-- NOTE: idempotency_keys и audit_log таблицы НЕ используются.
|
||||
-- - idempotency_keys → KeyDB (Redis cache) — apps/api/src/config/redis.ts
|
||||
-- - audit_log → stdout JSON-lines — apps/api/src/lib/audit-log.ts
|
||||
-- Скрипт их НЕ дропает (чтобы re-run был non-destructive).
|
||||
-- Если оператор хочет cleanup — manual one-time:
|
||||
-- DROP TABLE IF EXISTS audit_log CASCADE;
|
||||
-- DROP TABLE IF EXISTS idempotency_keys CASCADE;
|
||||
|
||||
-- ── USERS ───────────────────────────────────────────────────────────
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id VARCHAR(26) NOT NULL PRIMARY KEY,
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
@@ -33,7 +42,7 @@ CREATE TABLE IF NOT EXISTS users (
|
||||
encrypted_mnemonic TEXT
|
||||
);
|
||||
|
||||
-- Idempotent ALTERs для existing БД без extension-columns
|
||||
-- Idempotent ALTERs для existing БД у которой нет extension-columns (только ADD если нет колонки)
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'encrypted_mnemonic') THEN
|
||||
@@ -47,15 +56,16 @@ BEGIN
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Sanity check на blob size. Floor 100 (worst-case 12-word всё-3char mnemonic):
|
||||
-- Constraint: blob size check (only ADDs if missing, никогда не DROP).
|
||||
-- Floor 100 (worst-case 12-word 3-char mnemonic = 100 base64 chars).
|
||||
-- Если оператор изменил этот constraint вручную — наш script его НЕ перезатрёт.
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'users_encrypted_mnemonic_size') THEN
|
||||
ALTER TABLE users DROP CONSTRAINT users_encrypted_mnemonic_size;
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'users_encrypted_mnemonic_size') THEN
|
||||
ALTER TABLE users
|
||||
ADD CONSTRAINT users_encrypted_mnemonic_size
|
||||
CHECK (encrypted_mnemonic IS NULL OR (char_length(encrypted_mnemonic) BETWEEN 100 AND 512));
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Case-insensitive email uniqueness (Alice@x.com ≠ alice@x.com → ACCOUNT HIJACKING fix)
|
||||
@@ -91,6 +101,7 @@ END $$;
|
||||
|
||||
-- ── WALLETS ─────────────────────────────────────────────────────────
|
||||
-- ON DELETE RESTRICT: hard-delete user → запрос отвергнут пока есть wallets.
|
||||
-- Это защита от unrecoverable fund loss при GDPR-wipe или admin удалении.
|
||||
CREATE TABLE IF NOT EXISTS wallets (
|
||||
id VARCHAR(26) NOT NULL PRIMARY KEY,
|
||||
user_id VARCHAR(26) NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
||||
@@ -105,21 +116,11 @@ CREATE TABLE IF NOT EXISTS wallets (
|
||||
CREATE INDEX IF NOT EXISTS idx_wallets_user_id ON wallets(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_wallets_address ON wallets(address);
|
||||
|
||||
-- Idempotent FK migration: если raised на старой DB с CASCADE — поменять
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.referential_constraints
|
||||
WHERE constraint_name LIKE 'wallets_user_id_fkey%' AND delete_rule = 'CASCADE'
|
||||
) THEN
|
||||
ALTER TABLE wallets DROP CONSTRAINT IF EXISTS wallets_user_id_fkey;
|
||||
ALTER TABLE wallets ADD CONSTRAINT wallets_user_id_fkey
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE RESTRICT;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ── DROP legacy tables (если existing БД от прошлой версии) ────────
|
||||
-- idempotency_keys → KeyDB cache (apps/api/src/lib/idempotency.ts → Redis)
|
||||
-- audit_log → stdout-only (apps/api/src/lib/audit-log.ts)
|
||||
DROP TABLE IF EXISTS audit_log CASCADE;
|
||||
DROP TABLE IF EXISTS idempotency_keys CASCADE;
|
||||
-- NOTE: если БД старая и wallets.user_id_fkey ON DELETE CASCADE (а нужен RESTRICT
|
||||
-- для защиты от fund loss при delete user), оператор делает manual ОДИН раз:
|
||||
--
|
||||
-- ALTER TABLE wallets DROP CONSTRAINT wallets_user_id_fkey;
|
||||
-- ALTER TABLE wallets ADD CONSTRAINT wallets_user_id_fkey
|
||||
-- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE RESTRICT;
|
||||
--
|
||||
-- Этот script ничего не дропает — re-run полностью non-destructive.
|
||||
|
||||
Reference in New Issue
Block a user