مفاتيح API
المفتاح هو هويتك وصلاحيتك ونطاقك في آنٍ واحد. هذا القسم يشرح شكله، ومن يصدره، وماذا يحدث حين يتسرّب.
شكل المفتاح
ثلاثة أجزاء يفصلها شرطة سفلية. الجزء الأوسط معرّف المفتاح — وهو ما تستخدمه للإلغاء وما يظهر في السجلات؛ الجزء الأخير هو السر ولا يُعرض إلا مرة واحدة.
gurb_a1b2c3d4e5f6_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
└──┘ └──────────┘ └─────────────────────────────────────────┘
│ │ └─ secret — shown ONCE, never again
│ └─ key id — revoke by this; safe in logs
└─ prefix. gurb_sa_ instead means a PLATFORM key| الجزء | معناه |
|---|---|
gurb_ | community key |
gurb_sa_ | platform (super-admin) key |
a1b2c3d4e5f6 | 12 hex — the key id |
xxxx… | 43 chars — the secret |
نوعان، لا يتبادلان
مفتاح المجتمع مثبّت على مجتمع واحد ولا يمكن توجيهه لغيره. مفتاح المنصة لا مجتمع له إطلاقاً — ينشئ المجتمعات ويصدر المفاتيح. كل عميل يرفض مفتاح الآخر عند الإنشاء لا عند أول نداء.
createGurbClient({ apiKey: 'gurb_sa_...' })
// → That is a super-admin key, not a community API key. Use GurbAdminClient…
createGurbAdminClient({ adminKey: 'gurb_...' })
// → That is a community API key, not a super-admin key. Use GurbClient…النطاقات
المفتاح يحمل read أو read وwrite. الافتراضي read وحده، وهذا مقصود: أغلب التكاملات تقرأ فقط، ومفتاح للقراءة يتسرّب حادثة تُغلق. اطلب write حين يكتب التكامل فعلاً.
// Read-only — the default, and the right choice for most integrations
await admin.apiKeys.create({ communityId, name: 'zapier-prod' })
// Explicitly asking for write. One extra argument, and it should feel
// like a decision.
await admin.apiKeys.create({ communityId, name: 'sync-worker', scopes: ['read', 'write'] })النطاق يُفحص قبل الصلاحية
من يصدر المفتاح، ولحساب من يعمل
مفتاح المنصة لا يصدره إلا مشرف عام. فبدون تحديد صاحب، المفتاح يعمل لحساب من أصدره — أي مشرف المنصة — وكل مجتمع تنشئه به يصير ملكه لا ملكك. هذي أكثر مفاجأة مكلفة في التكامل، ولها فحص بنداء واحد.
const me = await admin.me()
if (me.actsForIssuer) {
// Everything you create will belong to the platform admin, not to you.
throw new Error('This key names no subject — ask for one that does.')
}أين تحفظه
في متغيّر بيئة. لا في مستودع، ولا في متغيّر قالب، ولا في استجابة JSON يراها متصفّح. الحزمة ترفض العمل في متصفّح أصلاً، وترفض أن يُطبع مفتاح في صفحة — لكن هذي حواجز أخيرة لا بديل عن الحفظ الصحيح.
# .env — never committed
GURB_API_KEY=gurb_a1b2c3d4e5f6_...
# ✗ never
const gurb = createGurbClient({ apiKey: 'gurb_a1b2c3d4e5f6_...' }) // in source
res.json({ apiKey }) // to a browserالتدوير والإلغاء
الإلغاء فوري ونهائي ولا رجعة فيه — بالتصميم، ليكون لسؤال «هل هذا المفتاح حيّ؟» جواب واحد لا يعتمد على وقت السؤال. للتدوير بلا انقطاع: أصدر الجديد، انشره، تحقق أنه يعمل، ثم ألغِ القديم. بهذا الترتيب.
// 1. mint the replacement
const next = await admin.apiKeys.create({ communityId, name: 'zapier-prod-2' })
// 2. deploy it, 3. confirm it works, THEN:
await admin.apiKeys.revoke(oldKeyId) // immediate, permanent, no undoإذا تسرّب مفتاح
لا يوجد مسار لاسترجاع السر
غياب هذا المسار متعمّد: لو أمكن جلب السر مرة ثانية، لصار وصول القراءة إلى لوحتك مكافئاً لحيازة كل مفتاح أصدرته. سلّمه لصاحبه الآن، أو أتلفه وأصدر غيره.
const key = await admin.apiKeys.create({ communityId, name: 'zapier' })
console.log(key.key) // gurb_a1b2… — present here and nowhere else, ever
await admin.apiKeys.list() // summaries only: id, name, scopes, lastUsedAt