- setWiFi() now throws detailed errors instead of returning false - Shows specific error messages: "WiFi credentials rejected", timeout etc. - Added logging throughout BLE WiFi configuration flow - Fixed WiFi network deduplication (keeps strongest signal) - Ignore "Operation cancelled" error (normal cleanup behavior) - BatchSetupProgress shows actual error in hint field 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
388 lines
13 KiB
Markdown
388 lines
13 KiB
Markdown
# WellNuo Security & Quality Audit Report
|
||
|
||
**Дата:** 2026-01-22
|
||
**Версия:** 4.0
|
||
**Последнее обновление:** 2026-01-26
|
||
|
||
---
|
||
|
||
## Резюме
|
||
|
||
| Категория | Кол-во | Статус |
|
||
|-----------|--------|--------|
|
||
| ✅ Критичные (VULN-001 — VULN-008) | 6 | **ВЫПОЛНЕНО** — задеплоено 2026-01-26 |
|
||
| 🟢 Быстрые рекомендации | 5 | Можно сделать за 2-3 часа |
|
||
| 🟡 Средние рекомендации | ~15 | 1-3 дня на каждую |
|
||
| 🔴 Большие задачи | 3 | 1-2 недели (после релиза) |
|
||
| ⏸️ Заблокированные | 4 | Ждём legacy backend |
|
||
|
||
---
|
||
|
||
# ✅ ВЫПОЛНЕНО — Критичные уязвимости
|
||
|
||
> Задеплоено 2026-01-26. Code Review Score: 10/10.
|
||
> PRD: `PRD-SECURITY.md`
|
||
|
||
| # | Уязвимость | Что сделано | Файлы |
|
||
|---|------------|-------------|-------|
|
||
| 1 | **VULN-001**: Stripe Webhook | Проверка `STRIPE_WEBHOOK_SECRET` на старте, сервер не запустится без него | `webhook.js`, `index.js` |
|
||
| 2 | **VULN-003**: JWT Secret | Проверка ≥32 символа на старте + обновлён secret до 64 символов | `index.js` |
|
||
| 3 | **VULN-004**: OTP Rate Limit | 5 попыток/15мин на verify, 3/15мин на send | `auth.js` |
|
||
| 4 | **VULN-005**: Input Validation | express-validator на всех POST/PATCH endpoints | `beneficiaries.js`, `stripe.js`, `invitations.js` |
|
||
| 5 | **VULN-007**: Doppler | Инструкция создана | `backend/DOPPLER_SETUP.md` |
|
||
| 6 | **VULN-008**: npm audit | Уязвимости в зависимостях исправлены | `package.json` |
|
||
|
||
---
|
||
|
||
# ⏸️ ЗАБЛОКИРОВАНО — Ждём legacy backend
|
||
|
||
> Эти задачи зависят от изменений на **eluxnetworks.net**.
|
||
> Контакт: команда legacy backend.
|
||
|
||
### Что ждём:
|
||
|
||
**Phase 1 (ETA: mid next week):**
|
||
- `set_deployment` endpoint возвращает `deployment_id` в ответе
|
||
|
||
**Phase 2 (post-MVP):**
|
||
- Header-based auth (`Authorization: Bearer <token>`)
|
||
- `beneficiary_password` становится optional
|
||
- `external_beneficiary_id` поле в таблице deployments
|
||
|
||
### Заблокированные задачи:
|
||
|
||
| Задача | Проблема | Когда разблокируется |
|
||
|--------|----------|---------------------|
|
||
| **FE-003**: Legacy Credentials | Hardcoded `anandk` / `anandk_8` в `api.ts:1444` | Phase 2 |
|
||
| **LEGACY-001**: Deployment ID | `set_deployment` не возвращает ID | Phase 1 |
|
||
| **LEGACY-002**: Plaintext Passwords | Отправляем пароль в открытом виде | Phase 2 |
|
||
| **LEGACY-003**: External ID Mapping | Нет `external_beneficiary_id` в БД | Phase 2 |
|
||
|
||
---
|
||
|
||
# 🟢 БЫСТРЫЕ РЕКОМЕНДАЦИИ (2-3 часа всего)
|
||
|
||
> Простые фиксы, можно сделать перед релизом.
|
||
|
||
### VULN-010: CORS Whitelist
|
||
**Effort:** 30 минут
|
||
**Файл:** `backend/src/index.js`
|
||
|
||
**Проблема:** CORS разрешает запросы с любого домена.
|
||
|
||
**Fix:**
|
||
```javascript
|
||
const corsOptions = {
|
||
origin: ['https://wellnuo.smartlaunchhub.com', 'exp://'],
|
||
credentials: true
|
||
};
|
||
app.use(cors(corsOptions));
|
||
```
|
||
|
||
---
|
||
|
||
### VULN-012: Request Size Limit
|
||
**Effort:** 15 минут
|
||
**Файл:** `backend/src/index.js`
|
||
|
||
**Проблема:** Нет лимита на размер body — можно отправить огромный JSON и положить сервер.
|
||
|
||
**Fix:**
|
||
```javascript
|
||
app.use(express.json({ limit: '1mb' }));
|
||
```
|
||
|
||
---
|
||
|
||
### FE-001: Stripe Key в Environment
|
||
**Effort:** 15 минут
|
||
**Файл:** `services/api.ts` или где используется Stripe
|
||
|
||
**Проблема:** Stripe publishable key захардкожен.
|
||
|
||
**Fix:** Вынести в `EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEY`.
|
||
|
||
---
|
||
|
||
### FE-006: Убрать console.log в Production
|
||
**Effort:** 30 минут
|
||
**Файлы:** `babel.config.js`, `package.json`
|
||
|
||
**Проблема:** Console.log попадают в production build.
|
||
|
||
**Fix:**
|
||
```bash
|
||
npm install babel-plugin-transform-remove-console --save-dev
|
||
```
|
||
|
||
```javascript
|
||
// babel.config.js
|
||
module.exports = {
|
||
presets: ['babel-preset-expo'],
|
||
env: {
|
||
production: {
|
||
plugins: ['transform-remove-console']
|
||
}
|
||
}
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
### FE-007: JWT Expiration Check
|
||
**Effort:** 1 час
|
||
**Файл:** `services/api.ts`
|
||
|
||
**Проблема:** Если токен истёк — пользователь получает непонятные ошибки вместо редиректа на логин.
|
||
|
||
**Fix:**
|
||
```javascript
|
||
function isTokenExpired(token) {
|
||
try {
|
||
const payload = JSON.parse(atob(token.split('.')[1]));
|
||
return payload.exp * 1000 < Date.now();
|
||
} catch {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
// Перед каждым запросом
|
||
if (isTokenExpired(token)) {
|
||
// Редирект на логин
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
# 🟡 СРЕДНИЕ РЕКОМЕНДАЦИИ (1-3 дня каждая)
|
||
|
||
> Полезно, но не критично для релиза.
|
||
|
||
### VULN-006: Логирование (Winston + Sentry)
|
||
**Effort:** 1 день
|
||
**Приоритет:** Высокий после релиза
|
||
|
||
**Что делаем:**
|
||
1. Winston для structured logs
|
||
2. Sentry для алертов об ошибках
|
||
3. Audit Trail таблица для истории действий
|
||
4. Generic error messages в production (не показывать stack trace)
|
||
|
||
**Почему важно:** Сейчас если что-то падает — мы не узнаем. Sentry пришлёт алерт.
|
||
|
||
---
|
||
|
||
### FE-004: SSL Pinning
|
||
**Effort:** 1 день
|
||
**Приоритет:** Средний
|
||
|
||
**Что делаем:**
|
||
1. Удалить `app/(tabs)/bug.tsx` (если есть)
|
||
2. Добавить SSL pinning для API запросов
|
||
3. Проверка URL в WebView
|
||
|
||
**Почему важно:** Защита от man-in-the-middle атак.
|
||
|
||
---
|
||
|
||
### VULN-009: Admin 2FA
|
||
**Effort:** 2-3 дня
|
||
**Приоритет:** Низкий (мало админов)
|
||
|
||
**Что делаем:** TOTP или email code для admin операций.
|
||
|
||
---
|
||
|
||
### VULN-015: CSRF Protection
|
||
**Effort:** 1 день
|
||
**Приоритет:** Низкий (мобильное приложение)
|
||
|
||
**Что делаем:** `csurf` middleware для web версии.
|
||
|
||
---
|
||
|
||
### FE-010: SecureStore вместо AsyncStorage
|
||
**Effort:** 2-3 часа
|
||
**Приоритет:** Средний
|
||
|
||
**Что делаем:** Перенести tokens из AsyncStorage в SecureStore (зашифрованное хранилище).
|
||
|
||
---
|
||
|
||
### FE-014: Deep Links Validation
|
||
**Effort:** 2-3 часа
|
||
**Приоритет:** Средний
|
||
|
||
**Что делаем:** Валидировать параметры deep links перед обработкой.
|
||
|
||
---
|
||
|
||
### FE-019: Client-Side Rate Limiting
|
||
**Effort:** 1-2 часа
|
||
**Приоритет:** Низкий
|
||
|
||
**Что делаем:** Debounce на кнопки отправки форм.
|
||
|
||
---
|
||
|
||
### BUG-001 — BUG-005: Race Conditions & Memory Leaks
|
||
**Effort:** 1 день
|
||
**Приоритет:** Средний
|
||
|
||
| Баг | Файл | Проблема |
|
||
|-----|------|----------|
|
||
| BUG-001 | `AuthContext.tsx:44` | Race condition в useEffect |
|
||
| BUG-002 | `BeneficiaryContext.tsx:59` | Stale closure |
|
||
| BUG-003 | `api.ts:316` | Silent auth failures |
|
||
| BUG-004 | `beneficiaries/[id]/index.tsx:146` | Missing dependencies |
|
||
| BUG-005 | `beneficiaries/[id]/index.tsx:107` | Interval not cleaned |
|
||
|
||
---
|
||
|
||
### Code Quality (SMELL-001 — SMELL-006)
|
||
**Effort:** 2-3 дня
|
||
**Приоритет:** Низкий
|
||
|
||
- Duplicate navigation logic
|
||
- Magic numbers → constants
|
||
- Mixed auth token types
|
||
- Excessive `any` types (40+)
|
||
- Callback hell → async/await
|
||
- Silent fallbacks → add logging
|
||
|
||
---
|
||
|
||
# 🔴 БОЛЬШИЕ ЗАДАЧИ (1-2 недели, после релиза)
|
||
|
||
### VULN-017: Refresh Tokens
|
||
**Effort:** 1 неделя (backend + frontend)
|
||
**Приоритет:** После релиза
|
||
|
||
**Сейчас:** JWT живёт 7 дней, нет refresh.
|
||
|
||
**Рекомендуется:**
|
||
- Access token: 15-60 минут
|
||
- Refresh token: 7 дней
|
||
- Автоматический refresh при истечении
|
||
|
||
**Комментарий:** Работает и так, но refresh tokens — best practice.
|
||
|
||
---
|
||
|
||
### FE-017: Biometric Auth
|
||
**Effort:** 3-5 дней
|
||
**Приоритет:** После релиза
|
||
|
||
**Что делаем:** Face ID / Touch ID для входа в приложение.
|
||
|
||
**Комментарий:** Nice to have, не критично для MVP.
|
||
|
||
---
|
||
|
||
### RLS: Row Level Security
|
||
**Effort:** 1-2 недели
|
||
**Приоритет:** После релиза
|
||
|
||
**Что это:** Защита данных на уровне базы данных. Даже если баг в коде — пользователь увидит только свои данные.
|
||
|
||
**Подробный план:** См. ЧАСТЬ 4 в конце этого документа.
|
||
|
||
**Комментарий:** Серьёзный рефакторинг. Делать когда будет время на полноценное тестирование.
|
||
|
||
---
|
||
|
||
# 📋 App Store Compliance
|
||
|
||
> Требования для публикации в App Store.
|
||
|
||
| Задача | Статус | Комментарий |
|
||
|--------|--------|-------------|
|
||
| **PERMISSION-001**: Camera description | ⚠️ Проверить | Нужно человеческое описание в `app.json` |
|
||
| **PERMISSION-002**: Microphone description | ⚠️ Проверить | Для голосового AI |
|
||
| **DATA-001**: Account deletion | ❌ Нужно | Apple требует возможность удалить аккаунт |
|
||
| **PRIVACY-001**: Privacy Policy URL | ⚠️ Проверить | Должен быть в `app.json` |
|
||
| **PRIVACY-002**: Privacy Manifest (iOS 17+) | ❌ Нужно | `PrivacyInfo.xcprivacy` файл |
|
||
| **CONTENT-001**: AI Chat Consent | ⚠️ Проверить | Согласие перед использованием AI |
|
||
|
||
---
|
||
|
||
# 📊 Приоритеты
|
||
|
||
## Перед релизом (опционально, 2-3 часа):
|
||
1. VULN-010: CORS whitelist
|
||
2. VULN-012: Request size limit
|
||
3. FE-001: Stripe key в env
|
||
4. FE-006: Remove console.log
|
||
5. FE-007: JWT expiration check
|
||
|
||
## Сразу после релиза:
|
||
1. VULN-006: Winston + Sentry (мониторинг)
|
||
2. DATA-001: Account deletion (требование Apple)
|
||
3. BUG-001-005: Race conditions
|
||
|
||
## Когда будет время:
|
||
1. Refresh tokens
|
||
2. Biometric auth
|
||
3. RLS (Row Level Security)
|
||
4. SSL Pinning
|
||
|
||
## Ждём legacy backend:
|
||
1. FE-003: Legacy credentials (Phase 2)
|
||
2. LEGACY-001-003: Integration fixes (Phase 1/2)
|
||
|
||
---
|
||
|
||
# ПРИЛОЖЕНИЕ: RLS (Row Level Security) — План реализации
|
||
|
||
> Подробный план на случай если решим делать.
|
||
|
||
## Что это и зачем
|
||
|
||
**Сейчас:** Проверки доступа в коде backend. Если баг — данные утекут.
|
||
|
||
**С RLS:** Защита на уровне БД. Даже при баге, SQL injection, или прямом доступе — пользователь увидит только свои данные.
|
||
|
||
## Этапы
|
||
|
||
| Этап | Время | Что делаем |
|
||
|------|-------|------------|
|
||
| 1. Подготовка | 2-3 часа | Создать роль `wellnuo_app`, включить RLS |
|
||
| 2. Политики | 4-6 часов | Создать policies для каждой таблицы |
|
||
| 3. Query Builder | 1-2 дня | Переделать под RLS |
|
||
| 4. Routes | 2-3 дня | Обновить все endpoints |
|
||
| 5. Тесты | 1 день | Unit + интеграционные |
|
||
| 6. Деплой | 2-3 часа | Миграция + проверка |
|
||
|
||
**Итого:** 1-2 недели
|
||
|
||
## Пример политики
|
||
|
||
```sql
|
||
-- Пользователь видит только своих beneficiaries
|
||
CREATE POLICY beneficiaries_select ON beneficiaries
|
||
FOR SELECT
|
||
USING (
|
||
id IN (
|
||
SELECT beneficiary_id FROM user_access
|
||
WHERE accessor_id = current_setting('app.current_user_id', true)
|
||
)
|
||
);
|
||
```
|
||
|
||
## Пример использования в коде
|
||
|
||
```javascript
|
||
// До (без RLS)
|
||
const { data } = await supabase.from('beneficiaries').select('*');
|
||
const accessible = data.filter(b => hasAccess(userId, b.id)); // ручная фильтрация
|
||
|
||
// После (с RLS)
|
||
const db = createDb(userId); // привязка к пользователю
|
||
const { data } = await db.from('beneficiaries').select('*'); // RLS фильтрует автоматически
|
||
```
|
||
|
||
---
|
||
|
||
**Файл:** `AUDIT_REPORT.md`
|
||
**Обновлён:** 2026-01-26
|