Skip to content

مصادقة SSO وOAuth

اضبط تسجيل الدخول عبر Google وGitHub وHuggingFace وOIDC العام في Potato. قيّد الوصول حسب نطاق البريد الإلكتروني أو منظمة GitHub، وشغّل SSO إلى جانب تسجيل الدخول المحلي بكلمة مرور.

جديد في الإصدار v2.3.0

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

يُضبط SSO في موضع واحد. اضبط authentication.method على oauth، ثم أدرج مزودًا واحدًا أو أكثر تحت authentication.providers. يحصل كل مزود على زر خاص به في صفحة تسجيل الدخول.

يتضمن Potato بيانات وصفية مدمجة لنقاط النهاية لثلاثة مزودين هي google وgithub وhuggingface. أي اسم مزود آخر يُعامل على أنه OIDC عام ويحتاج إلى discovery_url.

البدء السريع

إعداد أدنى لـ Google:

yaml
authentication:
  method: "oauth"
  providers:
    google:
      client_id: ${GOOGLE_CLIENT_ID}
      client_secret: ${GOOGLE_CLIENT_SECRET}
  user_identity_field: "email"
 
secret_key: ${POTATO_SECRET_KEY}

اضبط الأسرار في متغيرات البيئة بدلًا من الملف:

bash
export GOOGLE_CLIENT_ID="your-client-id.apps.googleusercontent.com"
export GOOGLE_CLIENT_SECRET="your-client-secret"
export POTATO_SECRET_KEY="$(python3 -c 'import secrets; print(secrets.token_hex(32))')"

secret_key مفتاح من المستوى الأعلى وليس جزءًا من الكتلة authentication. اضبطه، أو اضبط POTATO_SECRET_KEY. إذا لم يوجد أي منهما يولّد Potato مفتاحًا عشوائيًا، ما يعني أن كل إعادة تشغيل تُبطل جميع الجلسات، ويسجّل تحذيرًا عند ضبط OAuth من دونه.

يوفّر Potato مسارين لـ OAuth. يبدأ /auth/login/<provider> عملية المصادقة، ويستقبل /auth/callback/<provider> الاستجابة. مسار الاستدعاء (callback) هو عنوان URL الذي تسجّله لدى المزود، فهو في حالة Google https://your-domain.com/auth/callback/google. لا يوجد مفتاح إعداد باسم redirect_uri، فالمسار مشتق من اسم المزود.

Google

أنشئ بيانات اعتماد OAuth 2.0 من النوع "Web application" في Google Cloud Console، وأضف https://your-domain.com/auth/callback/google بوصفه URI إعادة توجيه مصرحًا به. للتطوير المحلي، يُقبل http://localhost:8000/auth/callback/google.

yaml
authentication:
  method: "oauth"
  providers:
    google:
      client_id: ${GOOGLE_CLIENT_ID}
      client_secret: ${GOOGLE_CLIENT_SECRET}
      allowed_domain: "umich.edu"

يأخذ allowed_domain سلسلة نطاق واحدة. لا يستطيع تسجيل الدخول إلا الحسابات التي يقع بريدها الإلكتروني ضمن ذلك النطاق.

GitHub

سجّل تطبيق OAuth من GitHub Settings > Developer settings > OAuth Apps، مع ضبط عنوان الاستدعاء على https://your-domain.com/auth/callback/github.

yaml
authentication:
  method: "oauth"
  providers:
    github:
      client_id: ${GITHUB_CLIENT_ID}
      client_secret: ${GITHUB_CLIENT_SECRET}
      allowed_org: "my-research-lab"

يأخذ allowed_org اسم منظمة واحدًا. عند ضبطه يطلب Potato النطاق read:org تلقائيًا، فلا تحتاج إلى إدراج النطاقات بنفسك. يجب أن تكون عضوية المعلق في المنظمة عامة كي يراها الفحص.

HuggingFace

أنشئ تطبيق OAuth في huggingface.co/settings/applications.

yaml
authentication:
  method: "oauth"
  providers:
    huggingface:
      client_id: ${HF_CLIENT_ID}
      client_secret: ${HF_CLIENT_SECRET}
  user_identity_field: "username"

يتصل مزود HuggingFace بنقطة نهاية اكتشاف OIDC الخاصة بـ HuggingFace، ويطلب openid profile email افتراضيًا.

OIDC العام

أي اسم مزود غير google وgithub وhuggingface يُعامل على أنه OIDC عام ويتطلب discovery_url. امنحه display_name كي يكون زر تسجيل الدخول مقروءًا.

yaml
authentication:
  method: "oauth"
  providers:
    oidc:
      display_name: "University SSO"
      discovery_url: "https://sso.university.edu/.well-known/openid-configuration"
      client_id: ${OIDC_CLIENT_ID}
      client_secret: ${OIDC_CLIENT_SECRET}
      scopes:
        - "openid"
        - "email"
        - "profile"

يتبع عنوان الاستدعاء الذي تسجّله لدى المزود مفتاحَ المزود، فالكتلة المسماة oidc تستخدم https://your-domain.com/auth/callback/oidc.

عناوين الاكتشاف لمزودين شائعين:

المزودعنوان الاكتشاف
Oktahttps://your-org.okta.com/.well-known/openid-configuration
Azure ADhttps://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration
Auth0https://your-domain.auth0.com/.well-known/openid-configuration
Keycloakhttps://keycloak.example.com/realms/{realm}/.well-known/openid-configuration

القيمة الافتراضية لـ scopes هي ["openid", "email", "profile"].

عدة مزودين

يقبل providers أي عدد تحتاجه من الإدخالات، ويعرض كل منها زر تسجيل دخول خاصًا به.

yaml
authentication:
  method: "oauth"
  providers:
    google:
      client_id: ${GOOGLE_CLIENT_ID}
      client_secret: ${GOOGLE_CLIENT_SECRET}
    github:
      client_id: ${GITHUB_CLIENT_ID}
      client_secret: ${GITHUB_CLIENT_SECRET}
    oidc:
      display_name: "University SSO"
      discovery_url: "https://sso.university.edu/.well-known/openid-configuration"
      client_id: ${OIDC_CLIENT_ID}
      client_secret: ${OIDC_CLIENT_SECRET}

مع user_identity_field: "email"، من يسجّل الدخول عبر Google ثم لاحقًا عبر مزود الجامعة بالعنوان نفسه يكون مستخدم Potato نفسه، ويعود إلى المهام المسندة إليه نفسها.

الهوية والتسجيل التلقائي

يحدد user_identity_field أي claim من المزود يصبح اسم المستخدم في Potato. يقبل email أو username أو sub أو name، وقيمته الافتراضية email. لهذه القيمة أهمية لأن مجلدات مخرجات المعلقين تُسمّى باسم المستخدم، لذا فإن تغييرها في منتصف الدراسة يفصل المعلق عن عمله السابق.

يتحكم auto_register في ما إذا كان أول تسجيل دخول ناجح ينشئ حساب Potato. قيمته الافتراضية true.

yaml
authentication:
  method: "oauth"
  providers:
    google:
      client_id: ${GOOGLE_CLIENT_ID}
      client_secret: ${GOOGLE_CLIENT_SECRET}
      allowed_domain: "umich.edu"
  user_identity_field: "email"
  auto_register: true

يتم التحكم في الوصول عبر المزود لا عبر قائمة عناوين في الإعداد. لتقييد من يمكنه تسجيل الدخول، استخدم allowed_domain مع Google، أو allowed_org مع GitHub، أو أدوات التحكم في المجموعات وإسناد التطبيقات لدى مزود OIDC الخاص بك.

الوضع المختلط

يضيف allow_local_login نموذج اسم المستخدم وكلمة المرور أسفل أزرار SSO. قيمته الافتراضية false، لذا يحل SSO محل تسجيل الدخول المحلي ما لم تفعّله.

yaml
require_password: true
 
user_config:
  allow_all_users: true
 
authentication:
  method: "oauth"
  providers:
    google:
      client_id: ${GOOGLE_CLIENT_ID}
      client_secret: ${GOOGLE_CLIENT_SECRET}
  allow_local_login: true

في أثناء الترحيل يعمل الاثنان معًا لفترة. انقل المعلقين، ثم احذف allow_local_login بعد أن يسجّل الجميع الدخول عبر المزود. يغطي الوضع المختلط أيضًا الدراسات التي تجمع بين معلقين من المؤسسة ومعلقين خارجيين ليس لديهم حساب في مؤسستك.

تستخدم الحسابات المحلية في الوضع المختلط تجزئة PBKDF2 المملّحة نفسها التي يستخدمها أي حساب آخر بكلمة مرور. راجع إدارة كلمات المرور لمعرفة إجراءات إعادة التعيين.

الجلسات

مدة الجلسة مفتاح من المستوى الأعلى، بالأيام:

yaml
session_lifetime_days: 7

المفتاح secret_key الثابت هو ما يجعل الجلسات تبقى بعد إعادة التشغيل.

وصول المسؤول

تُصادَق نقاط نهاية الإدارة بمفتاح API لا عبر كتلة OAuth:

yaml
admin_api_key: ${POTATO_ADMIN_API_KEY}

يقرأ Potato المفتاح admin_api_key من الإعداد أولًا، ثم من متغير البيئة POTATO_ADMIN_API_KEY. إذا لم يُضبط أي منهما، يولّد مفتاحًا ويكتبه في admin_api_key.txt داخل مجلد المهمة. يقدّمه المستدعون في الترويسة X-API-Key.

HTTPS

يشترط مزودو OAuth عناوين URI لإعادة التوجيه تستخدم HTTPS في بيئة الإنتاج. يُستثنى localhost، لذا يعمل التطوير عبر HTTP من دون شهادة.

خلف وكيل عكسي، مرّر المخطط الأصلي، وإلا فسيستخدم عنوان الاستدعاء الذي يبنيه Potato البروتوكول http وسيرفضه المزود:

nginx
location / {
    proxy_pass http://localhost:8000;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $host;
}

مرجع الإعداد

المفتاحالنوعالقيمة الافتراضيةالوصف
authentication.methodstringin_memoryin_memory أو database أو clerk أو oauth
authentication.providersdictمطلوب مع oauthإدخال واحد لكل مزود، مفتاحه اسم المزود
authentication.user_identity_fieldstringemailemail أو username أو sub أو name
authentication.allow_local_loginboolfalseيعرض نموذج كلمة المرور إلى جانب أزرار SSO
authentication.auto_registerbooltrueينشئ حساب Potato عند أول تسجيل دخول
secret_keystringعشوائيمن المستوى الأعلى. يلزم قيمة ثابتة لجلسات دائمة
session_lifetime_daysint7مدة الجلسة، من المستوى الأعلى
admin_api_keystringمُولَّدمفتاح من المستوى الأعلى لنقاط نهاية الإدارة

المفاتيح الخاصة بكل مزود:

المفتاحالنوعمطلوبالوصف
client_idstringنعممن المزود
client_secretstringنعممن المزود
discovery_urlstringمع OIDC العامنقطة نهاية اكتشاف OIDC
display_namestringلانص زر تسجيل الدخول
allowed_domainstringلايقيّد الوصول بنطاق بريد إلكتروني واحد
allowed_orgstringلايقيّد الوصول بمنظمة GitHub واحدة
scopeslistلاالقيمة الافتراضية ["openid", "email", "profile"]

مثال كامل

yaml
annotation_task_name: "Annotation Project"
task_dir: "."
 
authentication:
  method: "oauth"
  providers:
    google:
      client_id: ${GOOGLE_CLIENT_ID}
      client_secret: ${GOOGLE_CLIENT_SECRET}
      allowed_domain: "umich.edu"
  user_identity_field: "email"
  auto_register: true
 
secret_key: ${POTATO_SECRET_KEY}
session_lifetime_days: 7
admin_api_key: ${POTATO_ADMIN_API_KEY}
 
data_files:
  - "data/instances.jsonl"
 
item_properties:
  id_key: id
  text_key: text
 
annotation_schemes:
  - annotation_type: radio
    name: sentiment
    description: "What is the sentiment of this text?"
    labels: [Positive, Neutral, Negative]
 
output_annotation_dir: "output/"
export_annotation_format: "jsonl"

استكشاف الأخطاء وإصلاحها

يرفض المزود URI إعادة التوجيه. يجب أن يطابق URI المسجّل لدى المزود /auth/callback/<provider> تمامًا. يدخل المخطط وأي مسار لاحق في المطابقة، لذا فإن الوكيل الذي لا يمرّر X-Forwarded-Proto ينتج عنوان استدعاء http يرفضه المزود.

يُسجَّل خروج الجميع بعد إعادة التشغيل. لم يُضبط secret_key، فولّد Potato مفتاحًا جديدًا عند الإقلاع.

يرفض تقييد منظمة GitHub عضوًا فيها. عضويته في المنظمة خاصة. إما أن يجعلها عامة، وإما أن تحذف allowed_org وتتحكم في الوصول بطريقة أخرى.

يفشل التحقق برسالة "providers is required". يحتاج method: "oauth" إلى إدخال واحد على الأقل تحت providers. قيمة الطريقة هي oauth، وليست اسم مزود.


قراءة إضافية

لمزيد من تفاصيل التنفيذ، راجع التوثيق المصدري.