← API notifikasi JS Staff

إعداد "تسجيل الدخول باستخدام JS Staff"

يسجّل الموظفون الدخول إلى تطبيقك بحساب JS Staff الخاص بهم. لن يرى تطبيقك كلمة مرورهم أبدًا.

ما الذي تحصل عليه

{
  "user_id": 3,
  "nama": "Sela",
  "email": "sela@contoh.com",
  "peran": "staff",
  "tenant_id": 1,
  "tenant_nama": "Jidanshoppu",
  "tenant_slug": "jidanshoppu"
}

هذا كل شيء. يحمل peran (الدور) القيمة staff أو supervisor أو owner. تطبيقك لا يستطيع قراءة كلمة مرور أو راتب أو حضور أو سلفة أو حساب بنكي لأي شخص.

لماذا إعادة توجيه، وليس API "أرسل البريد + كلمة المرور". واجهة كهذه ستُبطل رمز OTP للجهاز الجديد الذي تعتمد عليه هذه البوابة. جوهر فائدة OTP بأكمله هو أن كلمة المرور وحدها لا تكفي للدخول — فبمجرد وجود باب واحد يقبل كلمة المرور فقط، يمرّ سارق كلمات المرور من ذلك الباب ببساطة.

مع إعادة التوجيه، لا يوجد باب كهذا: يحدث تسجيل الدخول دائمًا في صفحتنا، مع OTP والأجهزة الموثوقة الخاصة به.

التدفق

[1] يضغط الموظف على "الدخول باستخدام JS Staff" في تطبيقك ↓ [2] تُعيد توجيهه إلى https://staff.jidanshoppu.com/sso/authorize.php?... ↓ (يسجّل الدخول هناك + OTP إذا كان جهازًا جديدًا + شاشة الموافقة) [3] يعود إلى sso-callback.php?code=...&state=... ↓ [4] يستبدل خادمك الـ code + الـ secret في /sso/token.php ← من خادم إلى خادم ↓ [5] تحصل على هويته ← تُنشئ جلستك الخاصة

1 · اطلب هاتين القيمتين

من مسؤول منصة JS Staff (يسجّلونه في /super/apps.php):

القيمةمثال
client_idapp_a1b2c3d4e5f6a7b8
client_secretsec_1a2b3c… — يُعرض مرة واحدة فقط

يجب أن تحدّد redirect URI الخاص بك عند التسجيل. القاعدة صارمة:

لماذا هذا الصرامة. هذا هو أهم حاجز في كامل تدفّق SSO. خفّفه قليلًا فقط — بادئة، نطاق فرعي، حرف بدل — ولن يحتاج المهاجم إلا إلى إرسال رابط تفويض لموظفك بعنوان استدعاء خاص به. يسجّل الضحية الدخول في صفحتنا الحقيقية كالمعتاد، ولا شيء يبدو مريبًا، بينما يهبط الرمز على خادم المهاجم.

2 · الإعداد

<?php
// sso-config.php — JANGAN di-commit. Isi dari env var.
const SSO_BASE      = 'https://staff.jidanshoppu.com';
const SSO_CLIENT_ID = 'app_xxxxxxxxxxxxxxxx';
const SSO_SECRET    = 'sec_xxxxxxxxxxxxxxxx';
const SSO_REDIRECT  = 'https://order.jidanshoppu.com/sso-callback.php';

3 · زر تسجيل الدخول

<?php
// sso-mulai.php
require 'sso-config.php';
session_start();

/*
 * `state` WAJIB. Acak, disimpan di sesi, dicocokkan lagi di callback.
 * Tanpa ini, penyerang bisa mengirim link callback berisi kode MILIK DIA ke korban —
 * korban jadi login sebagai akun penyerang, lalu mengetik data ke akun itu tanpa sadar.
 */
$_SESSION['sso_state'] = bin2hex(random_bytes(16));

$url = SSO_BASE . '/sso/authorize.php?' . http_build_query([
    'client_id'    => SSO_CLIENT_ID,
    'redirect_uri' => SSO_REDIRECT,
    'state'        => $_SESSION['sso_state'],
]);

header('Location: ' . $url);
exit;

يكفي زر واحد: <a href="/sso-mulai.php">Masuk pakai JS Staff</a>

4 · رد النداء (Callback)

<?php
// sso-callback.php
require 'sso-config.php';
session_start();

// --- Orangnya menolak di layar persetujuan ---
if (isset($_GET['error'])) {
    exit('Login dibatalkan.');
}

// --- Cocokkan state DULU, sebelum menyentuh code ---
$state = (string)($_GET['state'] ?? '');

if ($state === ''
    || !isset($_SESSION['sso_state'])
    || !hash_equals($_SESSION['sso_state'], $state)) {
    // hash_equals, bukan ===: perbandingan biasa berhenti di huruf pertama yang beda,
    // dan selisih waktunya bisa dipakai menebak nilainya.
    exit('State tidak cocok. Ulangi dari awal.');
}
unset($_SESSION['sso_state']);   // sekali pakai

$code = (string)($_GET['code'] ?? '');
if ($code === '') {
    exit('Kode tidak ada.');
}

// --- Tukar kode jadi identitas (SERVER ke server) ---
$ch = curl_init(SSO_BASE . '/sso/token.php');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 10,
    CURLOPT_POSTFIELDS     => http_build_query([
        'client_id'     => SSO_CLIENT_ID,
        'client_secret' => SSO_SECRET,
        'code'          => $code,
        'redirect_uri'  => SSO_REDIRECT,   // harus sama dengan langkah 3
    ]),
]);

$body = curl_exec($ch);
$kode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($kode !== 200) {
    // Jangan tampilkan $body apa adanya ke pengguna — itu pesan untuk kamu, bukan dia.
    error_log('SSO gagal: ' . $kode . ' ' . $body);
    exit('Login gagal. Coba lagi.');
}

$data = json_decode((string)$body, true);
if (!is_array($data) || empty($data['ok'])) {
    exit('Login gagal. Coba lagi.');
}

$u = $data['user'];

// --- Mulai dari sini urusan kamu ---
session_regenerate_id(true);   // cegah session fixation

$_SESSION['jss_user_id']   = $u['user_id'];
$_SESSION['jss_tenant_id'] = $u['tenant_id'];
$_SESSION['nama']          = $u['nama'];
$_SESSION['peran']         = $u['peran'];

header('Location: /app.php');
exit;
يجب أن تكون هذه الخطوة من الخادم، لا من المتصفح. إن مرور client_secret عبر المتصفح يعادل مشاركة سرّ مع الجميع.

استبدل الرمز فورًا. عمره 60 ثانية ويُستخدم مرة واحدة — فهو يمرّ عبر عنوان المتصفح، والعناوين تنتهي في السجل، وسجلات الخادم، وترويسة Referer.

5 · الربط بحساب على جانبك

user_id فريد عالميًا، وtenant_id يحدّد الشركة. كلاهما ثابت — لا يتغيّران أبدًا حتى لو تغيّرت الأسماء.

لا تُطابق عبر البريد الإلكتروني. يمكن للمالك تغيير البريد الإلكتروني، والمطابقة عبر البريد تعني أن الحساب يمكن أن ينتقل بصمت بمجرد تغيير البريد. احفظ jss_user_id.
ALTER TABLE staff ADD COLUMN jss_user_id   INTEGER NULL;
ALTER TABLE staff ADD COLUMN jss_tenant_id INTEGER NULL;
CREATE UNIQUE INDEX uq_staff_jss ON staff (jss_user_id);

التدفّق في المرة الأولى:

  1. ابحث عن الحساب بـ jss_user_id = :user_id ← وُجد؟ سجّل الدخول.
  2. لم يُوجد؟ طابِق tenant_slug مع متجر لديك، أنشئ حساب الموظف، واحفظ jss_user_id الخاص به.
  3. tenant_slug غير معروف؟ ارفضه. لا تُنشئ متجرًا جديدًا بصمت من بيانات SSO — يمكن لأي شخص لديه حساب JS Staff أن يستحدث ذلك.

ما هو مطلوب وما هو غير ضروري

مطلوب

غير ضروري

إذا فشل

العرضالسبب غالبًا
صفحة "عنوان العودة غير مطابق" يختلف redirect_uri عمّا تم تسجيله. تحقّق من الشرطة المائلة الزائدة، وhttp مقابل https، وسلسلة الاستعلام.
صفحة "تطبيق غير معروف" client_id خاطئ، أو تم إلغاء التطبيق من قِبل مسؤول المنصة.
401 عند /sso/token.php انتهت صلاحية الرمز (>60 ثانية)، أو استُخدم بالفعل، أو client_secret خاطئ، أو redirect_uri مختلف عن الخطوة 3.
يعود بـ ?error=access_denied ضغط الموظف على رفض. ليس خللًا.

يستخدم 401 عمدًا الرسالة نفسها لكل الأسباب — فتمييز "رمز خاطئ" عن "رمز مُستخدَم من قبل" يمنح المهاجم وسيلة لرسم خريطة بالرموز التي وُجدت يومًا، ولا يفيد أي شخص تكاملُه صحيح. تحقّق من الأمور الأربعة أعلاه واحدًا تلو الآخر.

الاختبار

  1. اطلب من مسؤول المنصة تسجيل تطبيق ثانٍ مخصّص للاختبار.
  2. سجّل الدخول بحساب موظف حقيقي.
  3. يجب أن تظهر شاشة الموافقة مرة واحدة. في المرة الثانية يجب أن تمرّ مباشرة.
  4. ألغِ الإذن من ملفي الشخصي ← التطبيقات المسموح لها باستخدام حسابك ← يجب أن تظهر شاشة الموافقة مرة أخرى.