Tenant eklentiyi kurduğunda, Restomenum tarayıcıyı senin connectUrl'ine tek-kullanımlık bir code ile yönlendirir. Bu code'u sunucu tarafında credential'a çevirirsin (bir sonraki adım). Bu, eklentinin etkinleşmesi için zorunlu kapıdır.
Tenant: "Eklentiyi kur / Bağlan"
│
▼
Restomenum ──redirect──► https://<senin-origin>/connect
?code=<tek-kullanımlık>
&environment=sandbox|production ◄── API kökünü BU belirler
&state=<csrf>
│
▼
Senin /connect (sunucu):
1) state SENİN ürettiğin biri mi? → evet: CSRF doğrula · hayır (platform-issued): atla
2) code'u Token exchange ile credential'a çevir (adım 2)
3) credential'ı tenant başına sakla
4) kendi paneline yönlendirRestomenum, connectUrl'ine tarayıcıyı GET ile şu query parametreleriyle yönlendirir:
| Alan | Tip | Zorunlu | Açıklama |
|---|---|---|---|
| code | string | ✓ | Tek-kullanımlık, kısa ömürlü yetkilendirme kodu. Token exchange'de credential'a çevrilir; ikinci kullanımda invalid_grant. |
| environment | "sandbox" | "production" | ✓ | Kurulumun ortamı. Token takasını bu ortamın köküne gönder ve kurulumu bu ortamla sakla. Bkz. Ortamlar. |
| state | string | – | CSRF state. Değeri opak kabul et — formatı platform dayatmaz ve üreteni kurulumu kimin başlattığına bağlıdır (aşağıdaki uyarı). |
environment'ı yok sayma. Tek bir ortama sabitlenmiş kod, production kurulumunda token takasını yanlış API köküne gönderir ve kurulum çalışmaz. Kökü daima gelen değerden seç: sandbox → https://sandbox.plugins.restomenum.app, production → https://plugins.restomenum.app.Başka bir parametreye güvenme: tenant kimliği (tenantId) ve verilen yetkiler (scopes) URL'den değil, token exchange yanıtından gelir — sorgu dizisi tarayıcıdan geçtiği için otoriter değildir.
state'i her zaman sen üretmezsin. Kurulumu marketplace başlattığında state'i platform üretir ve sana opak gelir — senin CSRF deponda karşılığı yoktur. Bu durumda koşulsuz bir verifyState(state) kontrolü her kurulumu 400 ile öldürür.| Kurulumu başlatan | state'i üreten | Sen ne yaparsın |
|---|---|---|
| Marketplace (tenant "Kur" der) | Platform | Kendi CSRF kontrolünü atla — değeri doğrulayamazsın |
| Sen (kendi sitendeki "Restomenum'a ekle") | Sen | Üret + oturuma yaz → dönüşte timing-safe karşılaştır + tek-kullan |
İkisini ayırmanın pratik yolu: kendi state'ini seçtiğin bir önekle üret (örn. myapp1.) ve yalnız o öneki taşıyanı doğrula. Önek senin seçimindir; platform bir format dayatmaz.
CSRF kontrolünü atlamak akışı savunmasız bırakmaz: code tek-kullanımlık ve kısa ömürlüdür, credential'a çevrilmesi sunucu-sunucu bir çağrı gerektirir ve client_secret tarayıcıya hiç gitmez.
// /connect — OAuth Connect redirect hedefi (Node + Express)
// Tenant eklentiyi kurunca tarayıcı buraya ?code=..&environment=..&state=.. ile gelir.
// client_secret SADECE burada (sunucuda) kullanılır; tarayıcıya ASLA gitmez.
import express from 'express';
const app = express();
const CLIENT_ID = process.env.RESTOMENUM_CLIENT_ID; // = pluginId (UUID) — slug DEĞİL
const CLIENT_SECRET = process.env.RESTOMENUM_CLIENT_SECRET; // portalda üretilen cs_...
// API kökü ORTAMA göre seçilir — tek bir köke SABİTLEME (production kurulumu yanlış köke gider).
const BASES = {
sandbox: 'https://sandbox.plugins.restomenum.app',
production: 'https://plugins.restomenum.app',
};
// Kendi ürettiğin state'leri tanımak için SENİN seçtiğin önek (platform bir format dayatmaz).
const MY_STATE_PREFIX = 'myapp1.';
app.get('/connect', async (req, res) => {
const { code, environment, state } = req.query;
if (!code) return res.status(400).send('missing code');
// 0) ORTAM: kurulumun hangi ortama ait olduğunu SORGUDAN öğrenirsin ("sandbox" | "production").
// Kökü buradan seç ve kurulumu bu ortamla sakla — AYNI tenantId iki ortamda birden kurulu
// olabilir ve her kurulumun apiKey/webhookSecret'ı AYRIDIR.
const env = environment === 'production' ? 'production' : 'sandbox';
const TOKEN_URL = `${BASES[env]}/plugin-api/oauth/token`;
// 1) CSRF state — ⚠️ state'i HER ZAMAN SEN üretmezsin:
// • Kurulumu MARKETPLACE başlatırsa state'i PLATFORM üretir ve sana OPAK gelir; senin CSRF
// deponda karşılığı YOKTUR → KOŞULSUZ doğrularsan her kurulum 400 ile ölür.
// • Kurulumu SEN başlatırsan (kendi sitendeki "Restomenum'a ekle" akışı) state SENİNDİR.
// Bu yüzden yalnız KENDİ önekini taşıyan state'i doğrula:
if (typeof state === 'string' && state.startsWith(MY_STATE_PREFIX)) {
if (!consumeState(state)) return res.status(400).send('invalid state'); // timing-safe + tek-kullan
}
// Önek yoksa (platform-issued) kendi CSRF kontrolünü ATLA — akışın güvenliği tek-kullanımlık,
// kısa ömürlü code'a ve sunucu-sunucu exchange'e dayanır (client_secret tarayıcıya hiç gitmez).
// 2) code → credential exchange (server-to-server)
const r = await fetch(TOKEN_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'authorization_code',
code,
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
}),
});
if (!r.ok) return res.status(502).send('exchange failed');
// 2b) ZARF: BAŞARI yanıtı DAİMA zarflıdır — alanlar "data" İÇİNDEDİR, kökte değil.
// Düz gövde VARSAYMA: kökten okuyan kod alanları undefined görür ve canlıda patlar.
// ("?? body" geriye uyum için zararsız; başarı yolunda ikinci dala düşmez.)
const body = await r.json();
const { apiKey, webhookSecret, tenantId, scopes, environment } = body.data ?? body;
// 3) tenant + ORTAM başına SAKLA (apiKey: Callback API çağrıları, webhookSecret: imza doğrulama).
// Anahtar yalnız tenantId OLMAMALI — ikinci ortamın kurulumu birincinin üzerine yazar.
// Yanıttaki "environment" KOŞULSUZ gelir ve /connect'e gelen "env" ile AYNI kaynaktan türer
// → sapmamalı. Saparsa bu bir PLATFORM hatasıdır: sandbox credential'ıyla production trafigi
// calisiyormus gibi gorunur ve ancak gercek bir tahsilat bozulunca fark edilir → kaydetme, patla.
if (environment !== env) throw new Error('ortam uyusmazligi (platform hatasi)');
await saveInstallCredentials({ tenantId, environment, apiKey, webhookSecret, scopes });
res.redirect('/panel?connected=1'); // kendi paneline dön
});state'i koşullu doğrula: yalnız senin ürettiğin (kendi önekini taşıyan) state CSRF kontrolünden geçer; marketplace kurulumunda gelen platform state'i opaktır ve doğrulanmaz.code'u Token exchange ile değiştir.client_secret yalnız sunucuda kullanılır; tarayıcıya/iframe'e asla gönderme. codetek kullanımlıktır ve kısa ömürlüdür.