diff --git a/salesflow-saas/backend/app/middleware/internal_api.py b/salesflow-saas/backend/app/middleware/internal_api.py index 3320265f..695b9155 100644 --- a/salesflow-saas/backend/app/middleware/internal_api.py +++ b/salesflow-saas/backend/app/middleware/internal_api.py @@ -1,4 +1,8 @@ -"""Optional bearer token for /api/v1 when DEALIX_INTERNAL_API_TOKEN is set (production hardening).""" +"""Optional bearer token for /api/v1 when DEALIX_INTERNAL_API_TOKEN is set (production hardening). + +Exemption list is broad (public marketing, demo widgets). Before strict production, see +docs/LAUNCH_CHECKLIST.md section 6. +""" from __future__ import annotations diff --git a/salesflow-saas/docs/LAUNCH_CHECKLIST.md b/salesflow-saas/docs/LAUNCH_CHECKLIST.md index 603cdfeb..93d62c09 100644 --- a/salesflow-saas/docs/LAUNCH_CHECKLIST.md +++ b/salesflow-saas/docs/LAUNCH_CHECKLIST.md @@ -21,10 +21,22 @@ `.\scripts\grand_launch_verify.ps1 -HttpCheck -SoftReady` مع `DEALIX_BASE_URL` إذا لم يكن الـ API على `http://127.0.0.1:8000`. +### 2.1 بوابة الجاهزية التجارية (`go-live-gate`) + +- [ ] مع تشغيل الـ API وبعد ضبط `.env` الحقيقي (staging أو إنتاج): استدعِ + `GET /api/v1/autonomous-foundation/integrations/go-live-gate` +- [ ] **200** + `launch_allowed: true` يعني اجتياز الفحوص الحاسمة حسب [`go_live_matrix.py`](../backend/app/services/go_live_matrix.py). **403** مع نفس شكل JSON يعني وجود فحوص معطّلة — راجع `blocked_reasons` و`blocking`. +- [ ] من الطرفية (خادم على 8000): + `curl -sS http://127.0.0.1:8000/api/v1/autonomous-foundation/integrations/go-live-gate` +- [ ] ملخص سريع بدون uvicorn (يحمّل التطبيق داخل العملية، مثل الاختبارات): من جذر `salesflow-saas`: + `py -3 scripts/check_go_live_gate.py` + (للطباعة الكاملة: `py -3 scripts/check_go_live_gate.py --json`) + ## 3. الواجهة (Next.js) -- [ ] ضبط `NEXT_PUBLIC_API_URL` لنقطة نهاية الـ API العامة (انظر `frontend/.env.example`). -- [ ] التأكد من أن الـ backend يضمّن نطاق الواجهة في CORS (`FRONTEND_URL` / `main.py`). +- [ ] ضبط **`NEXT_PUBLIC_API_URL`** لعنوان الـ API الذي يصل إليه المتصفح فعليًا (HTTPS في الإنتاج). مرجع: [`frontend/.env.example`](../frontend/.env.example). +- [ ] **CORS:** في الباكند عرّف **`FRONTEND_URL`** (أصل الواجهة الافتراضي، مثل `https://app.dealix.sa`) في `backend/.env` — يُستخدم في [`main.py`](../backend/app/main.py) مع أصول ثابتة إضافية. لأصول إضافية (معاينة، دومين قديم): **`CORS_EXTRA_ORIGINS`** قائمة مفصولة بفواصل — انظر [`app/config.py`](../backend/app/config.py). +- [ ] تحقق من أن قيمة `NEXT_PUBLIC_API_URL` على الواجهة **تطابق** المخطط والشهادة (لا خلط `http`/`https` أو دومين خاطئ) حتى لا تفشل طلبات `fetch` / `apiFetch`. ## 4. الأسرار والبيئة @@ -34,7 +46,15 @@ ## 5. ما بعد الإطلاق - [ ] مراقبة `/api/v1/health` و `/api/v1/ready`. -- [ ] مراجعة `go-live-gate` عند التكاملات الحقيقية (قد يعيد 403 حتى اكتمال التهيئة — متوقع). +- [ ] إعادة فحص **`go-live-gate`** بعد أي تغيير على أسرار الطرف الثالث (Stripe، البريد، CRM، إلخ). + +## 6. أمان `DEALIX_INTERNAL_API_TOKEN` (إنتاج) + +عند تعيين **`DEALIX_INTERNAL_API_TOKEN`** في الباكند، يُطلب `Authorization: Bearer <التوكن>` على معظم مسارات `/api/v1`، مع **قائمة إعفاءات** واسعة للمسارات العامة والتسويق والديمو — التنفيذ في [`app/middleware/internal_api.py`](../backend/app/middleware/internal_api.py). + +- [ ] **Staging / ديمو:** الإعفاءات الحالية تسمح للواجهة بجلب محتوى عام ولوحات ديمو دون التوكن الداخلي؛ هذا متعمد لتجربة المطوّر. +- [ ] **إنتاج صارم:** راجع ما إذا كانت مسارات الإعفاء (مثل أجزاء من التسويق أو `dealix/generate-leads`) مقبولة لسياسة المنتج؛ يمكن لاحقًا تقييد الإعفاءات حسب `ENVIRONMENT` أو إلزام **`apiFetch` + JWT** لمسارات حساسة بدل الإعفاء. +- [ ] إن لم تُضبط المتغير (فارغ)، الميدلوير لا يفرض التوكن — مناسب للتطوير المحلي فقط. --- diff --git a/salesflow-saas/scripts/check_go_live_gate.py b/salesflow-saas/scripts/check_go_live_gate.py new file mode 100644 index 00000000..38287659 --- /dev/null +++ b/salesflow-saas/scripts/check_go_live_gate.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +""" +Print a short summary of GET /api/v1/autonomous-foundation/integrations/go-live-gate. + +Uses the in-process FastAPI app (same deps as pytest). Does not start uvicorn. + +Run from repo: + cd salesflow-saas && py -3 scripts/check_go_live_gate.py + +Against a running API instead: + curl -sS http://127.0.0.1:8000/api/v1/autonomous-foundation/integrations/go-live-gate | py -3 -m json.tool + +Exit code: 0 always (informational). Use HTTP status / launch_allowed in the JSON when calling from CI. +""" +from __future__ import annotations + +import json +import os +import sys +from pathlib import Path + + +def main() -> int: + saas = Path(__file__).resolve().parent.parent + backend = saas / "backend" + os.environ.setdefault("DATABASE_URL", "sqlite+aiosqlite:///./go_live_gate_cli.db") + os.environ.setdefault("DEALIX_INTERNAL_API_TOKEN", "") + sys.path.insert(0, str(backend)) + os.chdir(backend) + + from fastapi.testclient import TestClient + from app.main import app + + c = TestClient(app) + r = c.get("/api/v1/autonomous-foundation/integrations/go-live-gate") + try: + body = r.json() + except Exception: + print("HTTP", r.status_code, "non-JSON body", r.text[:500]) + return 0 + + la = body.get("launch_allowed") + print(f"HTTP {r.status_code} launch_allowed={la}") + print(f"readiness_percent_total={body.get('readiness_percent_total')}") + br = body.get("blocked_reasons") or [] + if br: + print("blocked_reasons (up to 8):") + for line in br[:8]: + print(f" - {line}") + blocking = body.get("blocking") or [] + print(f"blocking_checks={len(blocking)}") + if "--json" in sys.argv: + print(json.dumps(body, indent=2, ensure_ascii=False)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())