چگونه برای افزایش امنیت، ورود دو مرحله ای Google Authenticator را به Laravel اضافه کنیم؟

3 دیدگاه

بی شک لاراول (Laravel) یکی از بهترین فریم ورک های PHP در حال حاضر است که نوشتن پروژه های PHP را تبدیل به یک تفریح کرده است.

یکی از بهترین امکانات لاراول داشتن سیستم کاربری داخلی شامل ورود و ثبت نام و فراموشی رمز و … است که شما را از عذاب نوشتن دوباره آن بی نیاز می کند.

با این حال ، در اوضاع فعلی سیستم های ورود معمول که با ایمیل و کلمه عبور کار می کنند روز به روز نا امن تر می شوند. حملات Brute Force, فرم های تقلبی یا فیشینگ، حملات تزریق SQL یا همان SQL Injection و … مواردی هستند که می توانند امنیت نام کاربری و کلمات عبور کاربران را به خطر بیندازند و باعث کرک شدن رمزها ، پخش شدن و دزدیده شدن آنها شوند. به این موارد استفاده کردن از رمزهای ساده قابل حدس، رمزهای یکسان کاربران که در همه سایت ها استفاده می کنند و شبکه های نا امن Wifi را هم اضافه کنید که مردم را در معرض خطر هک شدن قرار می دهد.

به همین خاطر اعتبارسنجی دو مرحله ای یا همان Two Factor Authentication یکی از روش های قوی امنیتی ورود است که از دو روش همزمان اعتبارسنجی استفاده می کند و بعد اجازه ورود کاربر را می دهد. سایتی که از سیستم ورود دو مرحله ای استفاده می کند، کاربرانش را در مقابل فیشینگ، مهندسی اجتماعی و بروت فورس محافظت می کند و ورودی امن و بدون نگرانی به خاطر رمزهای نا امن فراهم می کند.در واقع حتی اگر رمز کاربر را همه بدانند باز نمی توانند وارد حسابش شوند.

در این مقاله می خواهیم ببینیم که چطور می توانیم سیستم ورود دو مرحله ای را به برنامه Laravel مان اضافه کنیم. ما از Google Authenticator استفاده خواهیم کرد و الگوریتم رمز یکبار مصرف(TOTP) را پیاده سازی خواهیم کرد.

برای استفاده از ورود دو مرحله ای، کاربر سایت شما باید یکی از اپلیکیشن های سازگار با Google Authenticator را نصب کند. بعضی از برنامه های معتبر موجود در این زمینه عبارتند از:

راه اندازی

– نصب لاراول

برای شروع، یک نسخه تازه از لاراول در پوشه laravel-2fa نصب می کنیم

composer create-project --prefer-dist laravel/laravel laravel-2fa

# set proper folder permissions
sudo chmod -R 777 laravel-2fa/storage laravel-2fa/bootstrap/cache

توضیحات بیشتر در مورد نصب لاراول

سپس با روش معمول یا با دستور زیر برنامه را اجرا کنیم:

php artisan serve --port=8000

حالا برنامه لاراول در آدرس http://localhost:8000 باز می شود و شکل زیر را خواهید دید:

 

– اتصال به یک دیتابیس

طبق معمول برای اینکه بتوانیم اطلاعات کاربران را مدیریت کنیم به دیتابیس نیاز داریم.ما از MySQL استفاده می کنیم ولی به لطف ORM لاراول می توانید از هر سیستم دیتابیس دیگری استفاده کنید.

در فایل .env ، خط های زیر را ویرایش کنید:

# .env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

همچنین در همین فایل، خطوط زیر را نیز ویرایش کنید:

# .env
APP_NAME=Laravel 2FA Demo
APP_URL=http://localhost:8000

این کد یه عنوان متفاوت به برنامه لاراول تان می دهد و همچنین لینک اصلی لاراول را تصحیح می کند.شما می توانید بسته به روش باز کردن لاراول آدرس بالا را تغییر دهید ولی آدرس باید همان باشد که لاراول را با آن باز می کنید.

راه اندازی اعتبارسنجی داخلی لاراول

لاراول به همراه چند کنترلر اعتبارسنجی عرضه می شود و همه view ها و route های مربوطه را نیز خودش می سازد. برای ایجاد سیستم کاربری لاراول تنها کاری که باید بکنید وارد کردن دو دستور زیر است:

php artisan make:auth

# create the database tables needed with
php artisan migrate

دستور اول همه فایل های مورد نیاز view, controller, migration را می سازد و دستور دوم فایل migration مربوط به کاربران را اجرا می کند تا جدول کاربران ساخته شود.

اگر دوباره به برنامه لاراول برگردید این را می بینید. لینک های Login و Register در بالای صفحه اضافه شده اند.

حالا می توانید به آدرس http://localhost:8000/register رفته و ثبت نام کنید.

اضافه کردن ورود دومرحله ای در طی ثبت نام

چیزی که ما می خواهیم به آن برسیم:

  1. وقتی کاربری می خواهد ثبت نام کند ما یک کد محرمانه برای برای برنامه های Authenticator (از این به بعد authenticator می نامیم) که در بالا لیست شد، می سازیم.
  2. در درخواست بعدی، ما از این کد محرمانه برای نمایش یک کد QR به کاربر استفاده می کنیم تا به Google authenticator بدهد.
  3. وقتی کاربر “OK” را زد ما کاربر را با کد محرمانه Google authenticator ثبت نام می کنیم.
در همین رابطه :   متدهای جدید Route در لاراول 5.5 معرفی شدند

بدین ترتیب صفحه نمایش کد QR فقط یکبار قابل دیدن است.این برای حداکثر امنیت است.اگر کاربر خواست ورود دو مرحله ای را دوباره راه اندازی کند، باید این راه را تکرار کند و کد قبلی را از اعتبار ساقط نماید.

برای رسیدن به این، ما یک قدم برای راه اندازی Google authenticator قبل از ثبت نام کامل کاربر در دیتابیس، قرار می دهیم.

برای انجام تغییر در مراحل ثبت نام، باید متد register را در کنترلر RegisterController تعریف کنیم (این فایل را می توانید در app/Http/Controllers/Auth/RegisterController پیدا کنید).

– ایجاد و نمایش کد محرمانه

اول، باید این دو دستور را اجرا کنید تا دو پکیج لاراول نصب شود

composer require pragmarx/google2fa-laravel
composer require bacon/bacon-qr-code

اول از لاراول نسخه 5.4 به پایین استفاده می کنید، باید کد PragmaRX\Google2FALaravel\ServiceProvider::class, را داخل آرایه providers داخل فایل config/app.php وارد کرده و کد 'Google2FA' => PragmaRX\Google2FALaravel\Facade::class, را در همان فایل داخل آرایه aliases وارد کنید.

بعد، فایل config پکیج را با دستور زیر داخل پوشه config کپی می کنیم:

php artisan vendor:publish --provider=PragmaRX\\Google2FALaravel\\ServiceProvider

بعد، کلاس request را بالای فایل کنترلر RegisterController قرار می دهیم.زیرا یم خواهیم از کلاس Request لاراول داخل آن استفاده استفاده کنیم.

// app/Http/Controllers/Auth/RegisterController.php

use Illuminate\Http\Request;

حالا متد register را داخل RegisterController به این صورت اضافه می کنیم:

// app/Http/Controllers/Auth/RegisterController.php

public function register(Request $request)
{
//Validate the incoming request using the already included validator method
$this->validator($request->all())->validate();

// Initialise the 2FA class
$google2fa = app('pragmarx.google2fa');

// Save the registration data in an array
$registration_data = $request->all();

// Add the secret key to the registration data
$registration_data["google2fa_secret"] = $google2fa->generateSecretKey();

// Save the registration data to the user session for just the next request
$request->session()->flash('registration_data', $registration_data);

// Generate the QR image. This is the image the user will scan with their app
// to set up two factor authentication
$QR_Image = $google2fa->getQRCodeInline(
config('app.name'),
$registration_data['email'],
$registration_data['google2fa_secret']
);

// Pass the QR barcode image to our view
return view('google2fa.register', ['QR_Image' => $QR_Image, 'secret' => $registration_data['google2fa_secret']]);
}

همچنین باید یک view برای نمایش کد QR ایجاد کنیم. داخل متد بالا ما این view را به این صورت درخواست داده ایم : google2fa.register . پس به پوشه resources/views رفته و یک پوشه جدید بنام google2fa ایجاد می کنیم و داخل آن یک فایل جدید ساخته و اسمش را register.blade.php می گذاریم.

بنابراین مسیر کامل فایل این خواهد بود: resources/views/google2fa/register.blade.php

محتویات زیر را در فایل فوق قرار دهید:

// resources/views/google2fa/register.blade.php

@extends('layouts.app')

@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Set up Google Authenticator</div>

<div class="panel-body" style="text-align: center;">
<p>Set up your two factor authentication by scanning the barcode below. Alternatively, you can use the code {{ $secret }}</p>
<div>
<img src="{{ $QR_Image }}">
</div>
<p>You must set up your Google Authenticator app before continuing. You will be unable to login otherwise</p>
<div>
<a href="/complete-registration"><button class="btn-primary">Complete Registration</button></a>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection

حالا بلافاصله بعد از ثبت نام، کاربر را به این صفحه آورده و کد QR را به وی نمایش می دهید در غیر اینصورت نمی تواند کد را اسکن کند.

صفحه شبیه این خواهد بود:

– ثبت نام کاربر

متاسفانه وقتی کاربر بخواهد ادامه بدهد با خطا روبرو می شود، به این خاطر که ما route و action لازم را تعریف نکرده ایم.

قبل از اینکه سراغ اینکار بریم، باید فیلدی به جدول users اضافه کنیم تا کد محرمانه Google authenticator داخل آن ذخیره شود. برای این کار با دستور زیر یک migration جدید می سازیم:

php artisan make:migration add_google2fa_column_to_users --table=users

محتویات فایل میگریشن به این صورت خواهد بود:

// database/migrations/201X_XX_XX_XXXXXX_add_google2fa_column_to_users.php

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class AddGoogle2faColumnToUsers extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
// add a text column in the users table for the google2fa_secret
$table->text('google2fa_secret');
});
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
// drop the column if the migration is rolledback
$table->dropColumn('google2fa_secret');
});
}
}

این فایل میگریشن را برای این ساختیم که فیلد googel2fa_secret را به جدول users اضافه کنیم. برای اتمام این کار باید دستورات آن را با دستور زیر اعمال کنیم:

php artisan migrate

در قدم بعدی ثبت نام، ما به متد register نیاز داریم، بنابراین در فایل RegisterController این کد را پیدا کنید:

// app/Http/Controllers/Auth/RegisterController.php

use RegistersUsers;

و به این صورت تغییر دهید:

// app/Http/Controllers/Auth/RegisterController.php

use RegistersUsers {
// change the name of the name of the trait's method in this class
// so it does not clash with our own register method
register as registration;
}

حالا باید route بنام complete-registration بسازیم.

به فایل routes/web.php رفته و کد زیر را اضافه کنید:

// routes/web.php

Route::get('/complete-registration', 'Auth\RegisterController@completeRegistration');

خب در کد بالا از متدی بنام completeRegistration نام برده شده است که هنوز وجود ندارد. بنابراین در RegisterController دستورات زیر را وارد کنید:

// app/Http/Controllers/Auth/RegisterController.php

public function completeRegistration(Request $request)
{
// add the session data back to the request input
$request->merge(session('registration_data'));

// Call the default laravel authentication
return $this->registration($request);
}

خود لاراول فقط فیلدهای نام، ایمیل و پسورد را در دیتابیس ذخیره می کند.

در همین رابطه :   تصویری : دوست داشتنی ترین لوگوهای گوگل

برای اینکه کد محرمانه را در فیلد google2fa_secret ذخیره کند باید متد create را دستکاری کنیم:

 

// app/Http/Controllers/Auth/RegisterController.php

protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
'google2fa_secret' => $data['google2fa_secret'],
]);
}

همچنین باید مدل User را دستکاری کرده و در بخش fillable اسم فیلد جدید را وارد کنیم والا کد محرمانه همیشه خالی ذخیره خواهد شد.همچنین باید آن را مخفی کنیم تا در نتایج دیتابیس وجود نداشته باشد.

اطلاعات بیشتر در مورد fillable را اینجا ببنید

اطلاعات بیشتر در مورد مخفی کردن فیلدها را اینجا ببنید

بنابراین خط های زیر را در فایل app/User.php ویرایش کنید:

// app/User.php

/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'email', 'password', 'google2fa_secret',
];

/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password', 'remember_token', 'google2fa_secret',
];

برای امنیت بیشتر ، کد محرمانه را قبل از ذخیره در دیتابیس کدگذاری می کنیم تا اگر دیتابیس هک شد باز هم کد محرمانه قابل خوانده شدن نباشد.

در فایل بالا، دو متد زیر را اضافه کنید:

// app/User.php

/**
* Ecrypt the user's google_2fa secret.
*
* @param string $value
* @return string
*/
public function setGoogle2faSecretAttribute($value)
{
$this->attributes['google2fa_secret'] = encrypt($value);
}

/**
* Decrypt the user's google_2fa secret.
*
* @param string $value
* @return string
*/
public function getGoogle2faSecretAttribute($value)
{
return decrypt($value);
}

برای اطلاعات بیشتر در مورد متدهای بالا اینجا را ببینید

در نهایت چرخه ثبت نام کاربر کامل شد! کاربر بعد از ثبت نام این صفحه را باید ببیند:

 

اضافه کردن ورود دومرحله ای در طی ورود

همه کارهایی که تا به حال کرده ایم بی فایده است اگر در فرم ورود هم آن را پیاده سازی نکنیم. زیرا فعلا از ورود پیش فرض لاراول استفاده می کنیم که فقط ایمیل و رمز عبور از کاربر می خواهد.

هدف ما این است که کاربران ابتدا کد Google authenticator خود را قبل از ورود به سایت وارد کنند.

بهترین روش انجام این کار استفاده از یک middleware است. خوشبختانه پکیج pragmarx/google2fa-laravel یک middleware برای این کار در نظر گرفته است.

برای استفاده از آن، ابتدا آن را به آرایه routeMiddleware در فایل app/Http/Kernel.php اضافه می کنیم:

// app/Http/Kernel.php

protected $routeMiddleware = [
...
'2fa' => \PragmaRX\Google2FALaravel\Middleware::class,
];

با این روش، می توانیم بوسیله 2fa به Middleware دسترسی داشته باشیم.هم داخل route ها و هم داخل controller ها.

بعد فایل view را اضافه می کنیم جایی که کاربر OTP یا One Time Password را بعد از ورود وارد می کند.به صورت پیش فرض از این view استفاده خواهد شد: resources/views/google2fa/index.blade.php . بنابراین در همین مسیر یک فایل ایجاد می کنیم و کدهای زیر را داخلش قرار می دهیم: // resources/views/google2fa/index.blade.php @extends('layouts.app') @section('content') <div class="container"> <div class="row"> <div class="col-md-8 col-md-offset-2"> <div class="panel panel-default"> <div class="panel-heading">Register</div> <div class="panel-body"> <form class="form-horizontal" method="POST" action="{{ route('2fa') }}"> {{ csrf_field() }} <div class="form-group"> <label for="one_time_password" class="col-md-4 control-label">One Time Password</label> <div class="col-md-6"> <input id="one_time_password" type="number" class="form-control" name="one_time_password" required autofocus> </div> </div> <div class="form-group"> <div class="col-md-6 col-md-offset-4"> <button type="submit" class="btn btn-primary"> Login </button> </div> </div> </form> </div> </div> </div> </div> </div> @endsection

سپس به یک route نیاز داریم تا ثبت کد OTP را هندل کند. middleware خودش کد OTP را چک می کند.بنابراین فقط به یک route نیاز داریم که پشت middleware منتظر بنشیند و کاربر را به آدرس اصلی ببرد.

پس کد زیر را به routes/web.php اضافه کنید:

// routes/web.php

Route::post('/2fa', function () {
return redirect(URL()->previous());
})->name('2fa')->middleware('2fa');

حالا یک route داریم که دسترسی به آدرس http://localhost:8000/2fa را هندل می کند و کاربر را به صفحه قبلی اش منتقل می کند.

حالا می توانیم از این Middleware در هر جای برنامه که نیاز باشد برای محدود کردن دسترسی استفاده کنیم.

اطلاعات بیشتر در مورد middleware های لاراول را اینجا ببنید

برای مثال، در کنترلر HomeController می توانیم به این صورت تغییرش دهیم:

// app/Http/Controllers/HomeController.php

/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth');
}

به

// app/Http/Controllers/HomeController.php

/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware(['auth', '2fa']);
}

بنابراین بعد از ورود، وقتی کاربر به /home منتقل می شود (پیش فرض لاراول این است) ، باید رمز یکبار مصرف را از Google Authenticator وارد کند.

در واقع آنها فرم زیر را خواهند دید:

وقتی رمز یکبار مصرف را وارد کرد، به صورت کامل وارد می شود.

همین! با با موفقیت ورود دو مرحله را به Laravel با استفاده از Google Authenticator اضافه کردیم.

جایزه : موارد خاص

– اعتبارسنجی دوباره توسط خود کاربر

شاید کاربر احساس کند کسی به راز او پی برده است و می تواند رمزهای یکبار مصرف را ایجاد کند، بنابراین می خواهد کد محرمانه جدیدی تولید کند.

در همین رابطه :   آموزش تبدیل SQL خام به Laravel Query Builder با Orator

مطمئنا شما نمی خواهید ساعت 3 صبح به شما زنگ بزند یا برای مشکلات احتمالی شما را سرزنش کند. بنابراین چه بهتر که لینکی در اختیارش بگذاریم تا هر وقت خواست باز-اعتبارسنجی کند.

برای این، یک route جدید تعریف می کنیم.

در فایل routes/web.php این را اضافه کنید:

// routes/web.php

Route::get('/re-authenticate', 'HomeController@reauthenticate');

و در HomeController متد reauthenticate را اضافه کنید:

// app/Http/Controllers/HomeController.php

public function reauthenticate(Request $request)
{
// get the logged in user
$user = \Auth::user();

// initialise the 2FA class
$google2fa = app('pragmarx.google2fa');

// generate a new secret key for the user
$user->google2fa_secret = $google2fa->generateSecretKey();

// save the user
$user->save();

// generate the QR image
$QR_Image = $google2fa->getQRCodeInline(
config('app.name'),
$user->email,
$user->google2fa_secret
);

// Pass the QR barcode image to our view.
return view('google2fa.register', ['QR_Image' => $QR_Image,
'secret' => $user->google2fa_secret,
'reauthenticating' => true
]);
}

اگر توجه کرده باشید، از همان view قبلی نمایش کد QR استفاده می کنیم. پس بزارید تعدادی شرط به آن اضافه کنیم و پیام هایی که مخصوص ثبت نام هستند را اینجا مخفی کنیم.

فایل resources/views/google2fa/register.blade.php را به این صورت ویرایش کنید:

//resources/views/google2fa/register.blade.php

@extends('layouts.app')

@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Set up Google Authenticator</div>

<div class="panel-body" style="text-align: center;">
<p>Set up your two factor authentication by scanning the barcode below. Alternatively, you can use the code {{ $secret }}</p>
<div>
<img src="{{ $QR_Image }}">
</div>
@if (!@$reauthenticating) {{-- add this line --}}
<p>You must set up your Google Authenticator app before continuing. You will be unable to login otherwise</p>
<div>
<a href="/complete-registration"><button class="btn-primary">Complete Registration</button></a>
</div>
@endif {{-- and this line --}}
</div>
</div>
</div>
</div>
</div>
@endsection
– اعتبارسنجی دوباره توسط مدیر سیستم (شما)

شاید این بار کاربر نمی تواند وارد سایت ما شود. ممکن است تلفن اش دزدیده شده باشد، یا اشتباهی اطلاعات ورودش را پاک کرده باشد و ….

پس شما باید بتوانید یک کد محرمانه جدید برایش بسازید.

بهترین راهی که برای این پیدا کردم ایجاد دستور artisan هست که کد محرمانه کاربر رو بروز کنه و کد جدید رو در خط فرمان به ما نشون بده. بعد ما می توانیم این کد رو به کاربر بفرستیم تا در اپ وارد کند.

برای ایجاد دستور artisan، این دستور را اجرا کنید:

php artisan make:command ReAuthenticate

حالا می توانید کدهای دستور را در فایل app/Console/Commands/ReAuthenticate.php ویرایش کنید:

// app/Console/Commands/ReAuthenticate.php

<?php

namespace App\Console\Commands;

use App\User;
use Illuminate\Console\Command;

class ReAuthenticate extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = '2fa:reauthenticate {--email= : The email of the user to reauthenticate} {--force : run without asking for confirmation}';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Regenerate the secret key for a user\'s two factor authentication';

/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}

/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
// retrieve the email from the option
$email = $this->option('email');

// if no email was passed to the option, prompt the user to enter the email
if (!$email) $email = $this->ask('what is the user\'s email?');

// retrieve the user with the specified email
$user = User::where('email', $email)->first();

if (!$user) {
// show an error and exist if the user does not exist
$this->error('No user with that email.');
return;
}

// Print a warning
$this->info('A new secret will be generated for '.$user->email);
$this->info('This action will invalidate the previous secret key.');

// ask for confirmation if not forced
if (!$this->option('force') && !$this->confirm('Do you wish to continue?')) return;

// initialise the 2FA class
$google2fa = app('pragmarx.google2fa');

// generate a new secret key for the user
$user->google2fa_secret = $google2fa->generateSecretKey();

// save the user
$user->save();

// show the new secret key
$this->info('A new secret has been generated for '.$user->email);
$this->info('The new secret is: '.$user->google2fa_secret);
}
}

کامنت هایی در کد بالا هست که توضیحاتی در مورد بخش ها می دهد.

اطلاعات بیشتر در مورد دستورات Artisan لاراول را اینجا ببینید

برای استفاده از دستور جدیدمان، به ترمینال رفته و این دستور را اجرا کنید:

php artisan 2fa:reauthenticate

دستور ما ایمیل کاربر را از شما خواهد خواست و یکبار دیگر تاییدش را می خواهد.

همچنین می توانید ایمیل کاربر را به همراه دستور با پارامتر --email اعمال کنید:

php artisan 2fa:reauthenticate --email [email protected]

اگر می خواهد دوباره تایید ایمیل را نپرسد از پارامتر --force استفاده کنید:

php artisan 2fa:reauthenticate --force

کد محرمانه جدید کاربر ایجاد شده و در کنسول نمایش داده خواهد شد و می توانید آن را کپی کرده و برای کاربر بفرستید.

در پایان

در این مقاله سعی کردیم نحوه اضافه کردن ورود دو مرحله به لاراول به کمک Google Authenticator را آموزش دهیم.

همچنین اگر می خواهید یک نسخه لاراول 5.5 که همه کدهای این مقاله در آن پیاده سازی شده باشد دانلود کنید، می توانید به این مخزن گیت هاب سری بزنید.

ترجمه این مقاله حدود 2.5 ساعت زمان برده است به امید اینکه برای شما مفید باشه و استفاده کنید. خوشحال میشیم نظرات با اهمیت شما رو بشنویم 🙂

منبع

دسته بندی : Laravel

3 نظر

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *