Kimlik Doğrulama Modülü Yazma

1.7k görüntülenme Markdown

Kimlik Doğrulama modülü bir ikinci faktör yöntemidir. Giriş çekirdeğinin kullanıcıyı kaydetmek, soru sormak ve cevabı doğrulamak için çağırdığı metotları uygular.

Genel Bakış

Ürünle üç modül gelir: Email, Sms ve Totp. Taban sınıf yoktur. Çekirdek yöntem adından bir sınıf çözer ve her metodu method_exists ile yoklar.

İki aile vardır. Kayıt, kullanıcıya gösterilen bir sır üretiyor mu? Evet ise setup() uygularsınız. Hayır ise kodu iletmek için verification() uygularsınız.

Yönetici girişi ile müşteri girişi aynı çekirdekten geçer, yani tek modül ikisine de hizmet eder.

Ön Koşullar

  • coremio/modules/Authentication/ altına yazma erişimi.
  • Bir iletim kanalı ya da paylaşılan sır şeması.
  • Kod yöntemi için çalışan bir mail ya da SMS sürücüsü.
  • Bildirim Şablonları Nasıl Çalışır.

Yapı

Üç dosya yeter. Ayar sayfası ve controller yoktur: operatör yalnızca yöntemi açar.

düzen
coremio/modules/Authentication/Acme/
├── Acme.php        namespace WISECP\modules\Authentication; class Acme
├── config.php      künye + ayarlar (setup, method, attempts, attempts_penalty_minute)
└── lang/
    ├── en.php      name + description, yöntem kartında görünür
    └── tr.php

Sınıf ad alanlıdır. Hazır modüller namespace WISECP\modules\Authentication; yazar, çekirdek ise WISECP\Modules\Authentication\{Yöntem} kurar. İkisi de çözülür.

Adım Adım

Modül Sınıfını Kurma

  1. coremio/modules/Authentication/Acme/Acme.php dosyasını dizin adını taşıyan bir sınıfla oluşturun.
  2. Yapıcıda config.php dosyasını yükleyin. Yapıcı argüman almaz.
  3. Global sınıfların başına ters bölü koyun: \Session, \Filter, \Notification.
  4. Ailenizin kümesini uygulayın: setup() ile install(), ya da verification().
  5. İki durumda da verify() uygulayın.

Kayıt

  1. setup(array $user_data) sırrı ve sihirbazın göstereceği her şeyi döndürür. Düz kullanıcı dizisini alır.
  2. Çekirdek kurulum verisini bir kez üretip oturumda saklar; sihirbaz ve etkinleştirme aynı kopyayı kullanır.
  3. install(array $data) yazılan kodu saklanan veriye karşı denetler. Hata durumu kaydı iptal eder.
  4. setup() dönüşü tercihin data anahtarında şifreli saklanır, sonraki çağrılarda geri verilir.
  5. Kapatma yolu için uninstall(array $data) uygulayın.

Giriş Sorusu

  1. verification(array $params) soru ekranında çağrılır. Kodu üretin, iletin, saklayın ve ekran yükünü döndürün.
  2. İletim hız sınırını kendiniz koyun ve kalan saniyeyi retry_delay ile bildirin.
  3. verify(array $params, $code) gönderilen kodu alır. Başarı ya da hata durumunu döndürün; kilidi çekirdek uygular.
  4. Kurtarma anahtarı için verify_recovery(array $params, $key) ekleyin ve yükte recovery değerini true yapın.
  5. İki ekranı da test edin: ayrı oturum ve ayrı sayaç tutarlar.

Referans

Metot Sözleşmesi

Her çağrı korunduğu için metotların hepsi isteğe bağlıdır. Bileşim değildir: ne setup() ne verification() taşıyan bir yöntem kaydedilemez.

MetotNe zaman çağrılırAile
setup(array $user_data): arrayHesap paneli, etkin yöntem yokkensır tabanlı
install(array $data = []): arrayKayıt kodu gönderildiğindesır tabanlı
uninstall(array $data = []): arrayYöntem kapatıldığındaikisi de
verification(array $params = []): arraySoru ekranı, ya da hassas değişiklikten öncekod tabanlı
verify(array $params = [], $code = ''): arrayKod gönderildiğinde (giriş ya da yükseltme)ikisi de
verify_recovery(array $params = [], $key = ''): arrayKurtarma anahtarı gönderildiğindeisteğe bağlı

setup() varlığı dört ayrı yerde aile işareti olarak okunur. Panelin sihirbaz göstermesine ve hassas bir değişiklikten önce kod iletilmesine o karar verir. settings.setup config anahtarı aynı şeyi söyler ve önce o okunur.

İmzalar ve Yük Biçimleri

imzalar
public function __construct();

// BİÇİM TUZAĞI: $user_data düz kullanıcı satırıdır (id, email, full_name),
// verify metotlarının aldığı ['user' => ..., 'data' => ...] sarmalayıcısı DEĞİL.
public function setup(array $user_data): array;

// $data tam olarak setup() metodunun döndürdüğüdür, oturum deposundan yeniden verilir.
public function install(array $data = []): array;

// $data saklanan tercih verisidir, yani setup() metodunun döndürdüğü.
public function uninstall(array $data = []): array;

// SIRA: önce params, sonra gönderilen değer.
// $params = ['user' => ['id' => 5, 'email' => '...'], 'data' => [ /* setup() ne döndürdüyse */ ]]
public function verification(array $params = []): array;
public function verify(array $params = [], string|int|null $code = ''): array;
public function verify_recovery(array $params = [], string|int|null $key = ''): array;
dönüş biçimleri
// setup(): kayıt sihirbazının bastığı her şey, artı saklanacak sır.
// _preview anahtarları, şablonun biçimi bilmek zorunda kalmadan ekranın
// karakterleri gruplayabilmesi için vardır.
return [
    'secret_key'            => 'JBSWY3DPEHPK3PXP',
    'recovery_key'          => 'K7Q2M9XR4TZB6WVA',
    'secret_key_preview'    => 'JBSW Y3DP EHPK 3PXP',
    'recovery_key_preview'  => 'K7Q2 M9XR 4TZB 6WVA',
    'qr_code'               => 'data:image/png;base64,iVBORw0KG',   // bir data URI'si, yol değil
    'content'               => null,                                 // sihirbaz için ek HTML, ya da null
];

// verification(): soru ekranının nasıl davranacağı.
return [
    'digit'       => 6,        // kaç girdi kutusu çizileceği
    'retry_delay' => 118,      // yeniden göndermeye izin verilene dek saniye; 0 hemen demektir
    'recovery'    => true,     // kurtarma anahtarı alanını sun (gizlemek için yazmayın ya da false)
    'content'     => null,     // girdinin üstünde ek HTML, ya da null
];

// İletim hatası 'error' üzerinden bildirilir, çekirdek onu birebir yüzeye çıkarır.
return ['error' => 'Email could not be sent'];

// install(), uninstall(), verify(), verify_recovery(): bir durum, isteğe bağlı bir mesaj.
return ['status' => 'successful'];
return ['status' => 'error', 'message' => 'That code did not match.'];

Başarı sayılan tek şey birebir successful dizesidir. Dizi olmayan bir dönüş dahil diğer her şey bir deneme yakar.

Config Anahtarları ve Kayıt

config.php aile işaretini ve kilit politikasını taşır. Çekirdek bu bloğu modül üstverisinden okur.

settings.setup Sır tabanlı yöntemde true, iletilen kodda false. Önce bu okunur, yedeği setup() varlığıdır.
settings.method Yöntem kartının metnini ve simgesini seçen küçük harfli kanal etiketi (email, sms, totp).
settings.attempts Hesap engellenmeden önce tolere edilen yanlış kod sayısı. Sıfır saymayı kapatır.
settings.attempts_penalty_minute Engelin dakika cinsinden süresi. Varsayılan beştir.
modules/authentications Operatörün açtığı yöntem adları listesi, çoğul olduğuna dikkat. Bu listede olmayan yöntem hiç sunulmaz.
Modules::Load() Burada örnek değil üstveri döndürür: ['lang' => [...], 'config' => [...]]. Örnek, çözülen sınıf adı üzerinde new ile kurulur.

Kayıt Nerede Saklanır

Kullanıcı başına tek yöntem, kullanıcının bilgi kaydında şifreli bir blok olarak durur. Siz çözülmüş data yarısını alırsınız.

tercih saklama
// install() kodu onayladıktan sonra enableTwoFactor tarafından yazılır.
$store = ['method' => $method];
if (method_exists($module, 'setup')) $store['data'] = $setupData;

User::setInfo($userId, ['authentication' => Crypt::encode(Utility::jencode($store), Config::get('crypt/user'))]);

// Her soruda geri okunur. Operatörün sonradan kapattığı bir yöntem burada
// false döner, böylece giriş ikinci faktör olmadan sürer.
$raw  = User::getInfo($userId, ['authentication'])['authentication'] ?? '';
$pref = Utility::jdecode(Crypt::decode($raw, Config::get('crypt/user')), true);

// Ve verify() metodunuzun aldığı sarmalayıcı budur.
$params = ['user' => $userRow, 'data' => $pref['data'] ?? []];

Bunun etrafında iki kanca çalışır. gate:user.two_factor_disable bir mesaj döndürerek kapatmayı veto eder; action:user.two_factor_changed her açma ve kapamadan haberdar edilir.

Örnek

Bildirim sistemiyle ileten kod tabanlı bir yöntem.

coremio/modules/Authentication/Acme/Acme.php
<?php
namespace WISECP\modules\Authentication;

class Acme
{
    public array $config;

    private const DELAY = 120;

    public function __construct()
    {
        $this->config = include __DIR__ . DS . 'config.php';
    }

    public function verification(array $params = []): array
    {
        $userId    = (int) ($params['user']['id'] ?? 0);
        $stash     = $this->stash();
        $remaining = self::DELAY;
        $mayResend = true;

        // Engellenen kullanıcılar ceza süresini beklerken iletim hakkı yakamamalıdır.
        if (\User::CheckBlocked("member-login-authentication-attempt", $userId)) $mayResend = false;

        if ($stash) {
            $remaining = (int) $stash['expire'] - time();
            if ($remaining > 0) $mayResend = false;
        }

        if ($mayResend) {
            $expire = \DateManager::next_date(['second' => self::DELAY]);
            $code   = random_int(100000, 999999);

            $sent = \Notification::dispatch('user', 'two-factor-verification', [
                'user_id' => $userId,
                'code'    => $code,
                '_sync'   => true,
            ]);

            // 'error' iletim hatası kanalıdır; çekirdek bu mesajı olduğu gibi basar.
            if (!$sent) return ['error' => 'Verification code could not be delivered.'];

            $this->stash(['code' => $code, 'expire' => \DateManager::strtotime($expire)]);
            $remaining = self::DELAY;
        }

        return [
            'digit'       => (int) ($this->config['settings']['digits'] ?? 6),
            'retry_delay' => max(0, $remaining),
            'content'     => null,
        ];
    }

    public function verify(array $params = [], string|int|null $code = ''): array
    {
        $stash = $this->stash();

        if (empty($code) || !$stash || (string) $stash['code'] !== (string) $code)
            return ['status' => 'error'];

        // Tek kullanım: aynı kodun tekrarı geçemesin diye temizle.
        \Session::delete('AcmeAuthData');

        return ['status' => 'successful'];
    }

    public function uninstall(array $data = []): array
    {
        \Session::delete('AcmeAuthData');

        return ['status' => 'successful'];
    }

    private function stash(?array $write = null): array
    {
        if ($write !== null) {
            \Session::set('AcmeAuthData', \Utility::jencode($write), true);
            return $write;
        }

        $raw  = \Utility::jdecode((string) \Session::get('AcmeAuthData', true), true);
        if (!$raw) return [];

        // Süresi dolmuş depo yok demektir, yoksa bayat bir kod sonsuza dek geçerli kalır.
        if ((int) ($raw['expire'] ?? 0) < time()) {
            \Session::delete('AcmeAuthData');
            return [];
        }

        return $raw;
    }
}
coremio/classes/Auth.php
// Örnek: önce Load, sonra çözülen sınıf adı üzerinde çıplak bir new.
$class  = 'WISECP\\Modules\\Authentication\\' . $method;
$module = Modules::Load('Authentication', $method) && class_exists($class) ? new $class() : false;

$params      = ['user' => $state['user'] ?? [], 'data' => $state['authentication']['data'] ?? []];
$recoveryKey = trim(str_replace(' ', '', $recoveryKey));

if ($recoveryKey !== '')
    $verify = method_exists($module, 'verify_recovery') ? $module->verify_recovery($params, $recoveryKey) : ['status' => 'error'];
else
    $verify = method_exists($module, 'verify') ? $module->verify($params, $code) : ['status' => 'error'];

if (!is_array($verify) || ($verify['status'] ?? 'error') !== 'successful') {
    // Load burada ÜSTVERİ döndürür, deneme politikası oradan gelir.
    $meta  = Modules::Load('Authentication', $method) ?: [];
    $total = (int) ($meta['config']['settings']['attempts'] ?? 0);

    if ($total) {
        $penalty  = (int) ($meta['config']['settings']['attempts_penalty_minute'] ?? 5);
        $attempts = (int) ($state['attempts'] ?? 0) + 1;

        if ($total - $attempts < 1) User::addBlocked($reason, $userId, [], DateManager::next_date(['minute' => $penalty]));
        else $state['attempts'] = $attempts;
    }

    return ['status' => 'error', 'message' => $message];
}

Tuzaklar

setup() kullanıcı satırını alır, diğerleri sarmalayıcı

setup() düz kullanıcı dizisi alır ve $user_data['email'] okur. Diğerlerine ['user' => [...], 'data' => [...]] verilir. Yanlış biçim hata değil boş değer üretir.

Kurtarma anahtarı bir kez çalışır, sonra 2FA kapanır

Başarılı bir verify_recovery() çağrısında çekirdek kayıtlı tercihi siler, kullanıcı yeniden kaydolur. Anahtar bir kez kullanılır; kalıcı bir alternatif parola değildir.

Hız sınırını kendi modülünüzde koyun

Çekirdek yanlış cevapları sayar, yeniden göndermeleri değil. Yeniden yüklenen bir soru ekranı verification() metodunu tekrar çağırır, yani koşulsuz gönderim gelen kutusu doldurur. Bekleyen kodu saklayın ve retry_delay döndürün.

Durum dizisi döndürün, fırlatmayın

Çekirdek yakalamak yerine bir durum okur. Yanlış kodu ['status' => 'error'], iletim hatasını ['error' => '...'] olarak bildirin. Yakalanmamış bir fırlatma giriş ekranını bozar.

Yöntem kapatılınca kayıtlar korunur

Kayıtlı tercih her okumada etkin listeye karşı denetlenir. Yönteminizi kapatmak kayıtlı kullanıcıları dışarıda bırakmaz; artık sorguya çekilmezler. Yeniden açınca soru geri gelir.

Faydalı oldu mu?

Geri bildiriminiz için teşekkürler!

Hâlâ Yardıma mı İhtiyacınız Var?

Yukarıda bulamadığınız her şey için destek ekibimiz her zaman yanınızda.