تخط وانتقل إلى المحتوى الرئيسي

إعداد البيئة و .env

كيف تخزّن بيانات اعتماد DZBuild API بأمان في تطبيقك — من ملفات .env إلى KMS وحتى GitHub Secrets.

بقلم: Support

يغطّي هذا الدليل كيفية حمل بيانات اعتماد DZBuild API بأمان إلى تطبيقك — تطوير محلي، staging، إنتاج. سواء تبني واجهة Node.js، back-office Laravel/Symfony، خط بيانات Python، خدمة Go صغيرة، أو دالة edge serverless، القواعد ذاتها.

قبل أن تبدأ

  • يجب أن يكون مفتاحك مُسجَّلًا في البرنامج التجريبي. فـ API v1 مُقيَّد بالبرنامج التجريبي في الإنتاج. والمفتاح الذي لم تُسجّله DZBuild يُرجع 403 forbidden "API is in pilot mode; key not enrolled" في كل نداء، مهما كان إعدادك صحيحًا.

  • وجّه DZBUILD_API_BASE دائمًا إلى https://api.dzbuild.app/v1. فـ dzbuild.com/api/v1/... مجرد اسم بديل لنفس الـ API: قد تُحجب عليه بعض المسارات، ولا يحصل على ذاكرة القراءة المؤقتة لمدة 30 ثانية. أما تحديد المعدل لكل مفتاح وإعادة تشغيل idempotency فيسريان على المضيفين معًا.

ما تحتاج تخزينه

لتكامل واجهة / back-office نموذجي:

المتغير

مثال

ملاحظات

DZBUILD_API_KEY

dzpk_live_3f9c1b7a4e02d5.<48 hex>

رمز bearer الكامل. عامله ككلمة مرور.

DZBUILD_API_BASE

https://api.dzbuild.app/v1

عنوان القاعدة. استخدم هذا المضيف في الإنتاج، لا dzbuild.com/api/v1 (هو alias فقط).

DZBUILD_WEBHOOK_SECRET

64 محرفًا ست عشريًا صغيرًا

سر لكل webhook يعود من التسجيل. بلا بادئة. انظر أسرار Webhooks لما يمكنه وما لا يمكنه فعله اليوم.

تشريح رمز الـ bearer

التجار يبترون هذا الرمز باستمرار، فيستحق التوضيح. رمز bearer لمفتاح المنصة هو:

dzpk_live_<14 hex>.<48 hex>

73 محرفًا، كلها صغيرة بعد البادئة، وبينها نقطة واحدة. والجزء dzpk_live_… قبل النقطة هو معرّف المفتاح — هوية لا بيان اعتماد. وAuthorization: Bearer يحتاج السلسلة كاملة بمحارفها الـ 73.

وإن أخطأت في ذلك تختلف الطبقتان، وهذا تشخيص مفيد:

  • رمز بلا نقطة إطلاقًا401 unauthorized "Invalid bearer format".

  • رمز جيّد الشكل لكنه مجهول / ملغى ← 401 unauthorized "Invalid or revoked API key".

لـ تدفق المفتاح العام (signups / events من عميل عام):

المتغير

مثال

ملاحظات

DZBUILD_PUBLIC_KEY_ID

dzpub_live_...

آمن للشحن في كود العميل (المعرّف عام، السر لا)

DZBUILD_SIGNING_SECRET

64 محرفًا ست عشريًا صغيرًا، بلا بادئة

خادمي فقط — لتوقيع HMAC لكل جسم طلب

ملفات .env

النمط الأبسط:

# .env (في جذر مشروعك، gitignored)
DZBUILD_API_KEY=dzpk_live_3f9c1b7a4e02d5.4d1c8a90f7b23e6510ac7fd9b48e2c31a05f6d7e8b9c0a1d
DZBUILD_API_BASE=https://api.dzbuild.app/v1
DZBUILD_WEBHOOK_SECRET=fc9b5f0b51b4a93c1d6f8e29b6a2e30c7c2c44a4f2a6c8d8e0e1b9d4f6c1a8b3

# .gitignore
.env
.env.local
.env.*.local

ضع ملف .env.example بقيم placeholder ليعرف المطورون الآخرون ما يضبطون:

# .env.example (ملتزَم بـ git)
DZBUILD_API_KEY=dzpk_live_REPLACE_ME
DZBUILD_API_BASE=https://api.dzbuild.app/v1
DZBUILD_WEBHOOK_SECRET=REPLACE_ME_64_HEX

تحميل .env لكل لغة

Node.js / Next.js / Express

// next.config.js — Next.js يُحمّل .env و .env.local و .env.production تلقائيًا
// لـ Node عادي:
import 'dotenv/config';
const key = process.env.DZBUILD_API_KEY;

Python

# pip install python-dotenv
from dotenv import load_dotenv
import os
load_dotenv()
key = os.environ['DZBUILD_API_KEY']

PHP / Laravel

// Laravel يُحمّل .env تلقائيًا
$key = env('DZBUILD_API_KEY');// PHP عادي:
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();
$key = $_ENV['DZBUILD_API_KEY'];

Go

// go get github.com/joho/godotenv
godotenv.Load()
key := os.Getenv("DZBUILD_API_KEY")

Flutter / موبايل

لا تشحن مفتاح المنصة في تطبيقك المحمول. بدلًا من ذلك:

  1. ينادي تطبيقك المحمول خادمك الخلفي.

  2. خادمك الخلفي يحمل المفتاح ويُمرّر الطلبات إلى DZBuild.

إن احتجت أن يتحدث العميل المحمول مع DZBuild مباشرة (مثلًا لـ signups)، استخدم تدفق المفتاح العام — يُشحن فقط معرّف المفتاح العام، لا السر. انظر نقاط المفتاح العام.

بيئات الإنتاج

في الإنتاج لا تستعمل ملف .env. استعمل مدير الأسرار الأصلي للمنصة:

Vercel / Netlify / Cloudflare Pages

المشروع → الإعدادات → Environment Variables
   DZBUILD_API_KEY = dzpk_live_...
   DZBUILD_API_BASE = https://api.dzbuild.app/v1
   DZBUILD_WEBHOOK_SECRET = <64-char lowercase hex>

علّم متغيرات الإنتاج في scope "Production". متغيرات staging في "Preview" أو بيئة منفصلة.

Cloudflare Workers

wrangler secret put DZBUILD_API_KEY
# (الصق القيمة عند الطلب)

متاح في الـ worker كـ env.DZBUILD_API_KEY (مع [vars] في wrangler.toml).

AWS Lambda / API Gateway

استعمل AWS Secrets Manager أو Parameter Store:

import boto3, json
secret = json.loads(
    boto3.client('secretsmanager').get_secret_value(SecretId='dzbuild/prod')['SecretString']
)
key = secret['DZBUILD_API_KEY']

لا تخزّن المفتاح في Lambda env vars بنص واضح (يظهر في سجلات CloudTrail). أحل من Secrets Manager.

Docker / Docker Compose

# docker-compose.yml
services:
  app:
    image: yourapp:latest
    env_file:
      - .env.production    # غير ملتزَم
    environment:
      - NODE_ENV=production

لـ Kubernetes استعمل Secret:

apiVersion: v1
kind: Secret
metadata:
  name: dzbuild-creds
type: Opaque
stringData:
  DZBUILD_API_KEY: dzpk_live_...
  DZBUILD_WEBHOOK_SECRET: <64-char lowercase hex>

ثم أحل في النشر:

envFrom:
  - secretRef:
      name: dzbuild-creds

GitHub Actions

# .github/workflows/deploy.yml
env:
  DZBUILD_API_KEY: ${{ secrets.DZBUILD_API_KEY }}

اضبط السر في Repo → Settings → Secrets and variables → Actions.

مفاتيح dev مقابل production

أنشئ دائمًا مفتاحين منفصلين:

  • واحد بالاسم dev أو staging — للاستخدام في .env.local / بيئات dev

  • واحد بالاسم production — يُستعمل فقط في نشر الإنتاج الفعلي

إن تسرّب مفتاح dev، تحرق dev فقط. بيانات الإنتاج تبقى سليمة.

كيف تحصل على المفتاح

لا توجد صفحة "Developer → API Keys" في لوحة تحكم التاجر. وخلال البرنامج التجريبي:

  1. مفتاحك الأول تُصدره DZBuild عند الطلب — راسل الدعم أو مدير حسابك. اطلب مفتاحًا مسمّى باسم البيئة التي سيعيش فيها (myapp-dev، myapp-prod)، واطلب تسجيله في البرنامج التجريبي.

  2. المفاتيح التالية يمكنك إنشاؤها بنفسك انطلاقًا من مفتاح قائم:

bash curl -X POST 'https://api.dzbuild.app/v1/keys' \ -H "Authorization: Bearer $DZBUILD_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"type":"platform","name":"myapp-dev"}' # HTTP 200 → { "data": { "key_id", "bearer_token", "signing_secret", "note" } }

يرث المفتاح الجديد فئة تحديد المعدل وعَلَم البرنامج التجريبي من المفتاح المنادي حرفيًا.

الصلاحيات غير قابلة للاختيار. فـ POST /v1/keys لا يقبل إلا type و name. ومفتاح المنصة يحصل دائمًا على المجموعة الافتراضية الكاملة — store:read و store:write و products:read و products:write و orders:read و orders:write و customers:read و landing_pages:read و landing_pages:write و webhooks:read و webhooks:write و usage:read. أما المفتاح العام فيحصل دائمًا على signups:write و events:write. والفصل بين dev و prod يأتي من استعمال مفتاحين مختلفين، لا من تضييق الصلاحيات.

أسرار Webhooks

عند تسجيل webhook تتضمن الاستجابة secret:

curl -X POST 'https://api.dzbuild.app/v1/webhooks' \
  -H "Authorization: Bearer $DZBUILD_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "url":    "https://yourapp.com/webhook",
    "events": ["order.created", "order.confirmed"]
  }'

الاستجابة — HTTP 200، وثلاثة حقول فقط:

{
  "data": {
    "id":     42,
    "secret": "fc9b5f0b51b4a93c1d6f8e29b6a2e30c7c2c44a4f2a6c8d8e0e1b9d4f6c1a8b3",
    "note":   "Save the secret now — it is not retrievable after this response."
  },
  "meta": { "request_id": "...", "api_version": "v1" }
}

الـ secret سلسلة مجرّدة من 64 محرفًا ست عشريًا صغيرًا — لا توجد بادئة dzwh_sec_. وهو يظهر مرة واحدة؛ احفظه فورًا في مدير أسرارك. إن فقدته، احذف الـ webhook وسجّل واحدًا جديدًا.

⚠️ تنبيه — لا يمكنك بعدُ التحقق من تسليمات API v1 بهذا السر

لا يوقّع API v1 التسليمات بالسر secret الخاص بكل webhook أعلاه، لذا فأي شيفرة HMAC مكتوبة بالاعتماد على DZBUILD_WEBHOOK_SECRET سترفض كل تسليم حقيقي. احفظ السر للمستقبل، لكن وثّق تسليمات v1 بوسائل أخرى — وأعد قراءة السجل عبر الـ API قبل التصرّف بناءً عليه.

الشرح الكامل، مع شيفرة تحقق تعمل فعلًا (لإضافة Webhooks للتاجر)، موجود في التحقق من التواقيع — مصدر واحد للحقيقة، بأربع لغات.

تدفق المفتاح العام (signups / events)

يوجد تدفق المفتاح العام للنقاط محدودة النطاق (تتبع signups، تتبع events) حيث لا تريد إدخال مفتاح منصة في الحلقة. ومعرّف المفتاح العام وحده غير سرّي — أما سر التوقيع فيبقى على خادمك الخلفي.

⚠️ تنبيه — لا تنادِ /v1/signups مباشرة من متصفح

ثلاثة أمور تُفشل النداء المباشر من المتصفح اليوم:

  • لا يوجد CORS. الطلب التمهيدي (preflight) ينجح، لكن استجابة الـ POST الفعلية لا تحمل Access-Control-Allow-Origin، فيُهملها المتصفح.

  • Idempotency-Key إلزامية في كل POST، ومن السهل نسيانها في كود العميل.

  • crypto.randomUUID() ليس nonce صالحًا. فالـ nonce يجب أن يكون 32 محرفًا ست عشريًا صغيرًا بالضبط؛ أما UUID بـ 36 محرفًا وشرطات فيُرجع 401 unauthorized "Invalid nonce format".

مرّر عبر خادمك الخلفي بدلًا من ذلك — بالنمط أدناه.

// المتصفح: تحدّث مع نقطتك أنت، لا مع api.dzbuild.app
await fetch('/api/track-signup', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ email, external_user_id: externalId })
});

// خادمك الخلفي (Node): يحمل سر التوقيع، يوقّع ويُمرّر.
import crypto from 'node:crypto';const KEY_ID = process.env.DZBUILD_PUBLIC_KEY_ID;
const SECRET = process.env.DZBUILD_SIGNING_SECRET;export async function trackSignup({ email, external_user_id }) {
  const nonce = crypto.randomBytes(16).toString('hex');   // 32 محرفًا ست عشريًا صغيرًا
  const ts    = Math.floor(Date.now() / 1000).toString();
  const body  = JSON.stringify({ email, external_user_id, source: 'web', nonce });
  const hash  = crypto.createHash('sha256').update(body).digest('hex');
  const sig   = crypto.createHmac('sha256', SECRET)
    .update(`${KEY_ID}\n${nonce}\n${ts}\n${hash}`).digest('hex');  await fetch('https://api.dzbuild.app/v1/signups', {
    method: 'POST',
    headers: {
      'Authorization':   `DZ-Public ${KEY_ID}`,
      'X-DZ-Timestamp':  ts,
      'X-DZ-Nonce':      nonce,
      'X-DZ-Signature':  sig,
      'Idempotency-Key': nonce,
      'Content-Type':    'application/json'
    },
    body
  });
}

سر التوقيع لا يغادر خادمك أبدًا. معرّف المفتاح العام يمكن لأي أحد فحصه — وهذا بقصد. انظر الاشتراكات للعقد الكامل.

نفق dev محلي للـ webhooks

تحتاج webhooks DZBuild URL HTTPS عام. للاختبار محليًا استعمل tunnel:

# ngrok
ngrok http 3000# Cloudflare Tunnel
cloudflared tunnel --url http://localhost:3000# tailscale serve
tailscale serve https / http://localhost:3000

سجّل URL الـ tunnel كهدف webhook. حدّثه كلما أُعيد تشغيل الـ tunnel (ngrok مجاني يغير URL في كل مرة؛ ادفع 8$ شهريًا لـ subdomain ثابت).

سياسة التدوير

دوّر المفاتيح:

  • ربع سنوي لمفاتيح الإنتاج (ضع تذكير في التقويم)

  • فورًا إن احتمل أن مفتاحًا تسرّب (التزم بـ git، صورة شاشة، أُرسل في chat)

  • عند مغادرة عضو فريق إن كان لديه وصول لمدير الأسرار

إجراء التدوير:

  1. أنشئ مفتاحًا جديدًا بـ POST /v1/keys (انظر كيف تحصل على المفتاح). سيرث فئة المفتاح القديم وعَلَم البرنامج التجريبي.

  2. حدّث مدير الأسرار / متغيرات البيئة إلى المفتاح الجديد.

  3. انشر. تحقّق من حركة المفتاح الجديد عبر GET /v1/usage.

  4. بعد تأكيد 24 ساعة من حركة نظيفة على الجديد، ألغِ القديم: DELETE /v1/keys/{old_key_id}.

أخطاء شائعة

الخطأ

ما يحدث

الإصلاح

التزام .env في git

المفتاح صار في تاريخ git إلى الأبد؛ دوّره فورًا

git filter-repo لا يُصلح forks/clones؛ اعتبره متسربًا

استخدام مفتاح المنصة في كود متصفح

العملاء يقرؤون network tab → يرونه ويسرقونه

انقله إلى back-end / serverless؛ دوّره

Hardcode dzpk_live_... في source

نفس الأمر

استعمل env vars؛ دوّر

نفس المفتاح لـ dev وprod

خطأ dev يضرب بيانات prod

أنشئ مفتاحين؛ لا تشارك أبدًا

فقدان سر webhook

لا يمكن التحقق من التسليمات

احذف الـ webhook، سجّل غيره

تسجيل مفتاح API في سجلات التطبيق

مدققون / شاحنو سجلات / كل من له وصول للسجل يراه

redact secrets في إعدادات logger؛ دوّر

قائمة سريعة قبل الإطلاق

  • [ ] .env في .gitignore (ولم يُلتزَم سهوًا)

  • [ ] مفتاح الإنتاج اسمه prod ويُستعمل فقط في prod

  • [ ] مفتاح الـ dev اسمه dev ويُستعمل فقط في dev/staging

  • [ ] أسرار webhooks في مدير أسرار، ليس في الكود

  • [ ] رأس Authorization على كل نداء API من خادمك الخلفي

  • [ ] كود المتصفح لا يرى dzpk_live_* (فقط dzpub_live_* إن استعملت تدفق المفتاح العام)

  • [ ] نقطة webhook لديك على رابط غير قابل للتخمين، وتُعيد قراءة السجلات عبر الـ API قبل التصرّف (تواقيع API v1 غير قابلة للتحقق بعد)

  • [ ] لديك تذكير تدوير مفاتيح في تقويمك

  • [ ] لديك logging لا يلتقط أجسام الطلبات الكاملة (قد تتضمن مفاتيح API في رؤوس العميل)

هل أجاب هذا عن سؤالك؟