Laravel API с использованием модифицированного ПО промежуточного слоя «MustVerifyApiEmail» и настраиваемого ПО «EnsureApiEmailIsVerified» не может принять запрос пользователя - PullRequest
0 голосов
/ 25 мая 2020

Я использую Laravel 6.x в качестве серверной части с внешним (другим доменом) Vue интерфейсом и не имею функции регистрации пользователя. Я регистрирую пользователей путем импорта групп пользователей с помощью пакета Maatwebsite / Laravel -Excel, который отлично работает.

Таким образом, когда каждый пользователь создается, задание создается путем отправки каждому пользователю ссылки для подтверждения электронной почты , который при первом входе в систему им нужно будет изменить свой пароль, и одновременно их адрес электронной почты будет помечен как проверенный, что также должно работать нормально.

Проблема в том, что с уже созданной фабрикой пользователей, которые заполнено поле email_verified_at - и недавно импортированные пользователи - я не могу войти в систему, так как пользовательское промежуточное ПО EnsureEmailApiIsVerified не имеет доступа к $ request-> user (). Я выяснил, что могу указать защиту аутентификации для api, например $ request-> user ('api'), которая затем может забрать пользователя, но только его токен-носитель (с использованием Laravel Passport) отправляется с запрос.

Это имеет смысл, поскольку иначе система могла бы узнать, кто является пользователем запроса, без какого-либо идентификатора, такого как токен. Но как тогда стандарт 'реализует MustVerifyEmail' в модели User, а последующее стандартное промежуточное ПО EnsureEmailIsVerified на веб-маршрутах выбирает $ request-> user ()? (стандартное и мое пользовательское) промежуточное ПО должно иметь доступ к $ request-> user () или оба не должны иметь доступа.

Теперь мне пришлось изменить и перенести довольно много контроллеров фреймворка в мой каталог App \ Http но я скопировал их почти дословно, просто изменив несколько вещей, чтобы убедиться, что он работает с моими маршрутами API - потому что установка защиты по умолчанию на 'api' вместо 'web' в config / auth. php не использовал его по умолчанию во всех моих контроллерах, как и предполагалось.

Итак, вот шаги, которые я выполнил:

Создал пользовательское промежуточное ПО и прикрепил его ко всей группе промежуточного программного обеспечения api в App \ Http \ Kernel. php

/**
     * The application's route middleware groups.
     *
     * @var array
     */
    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class
        ],

        'api' => [
            'throttle:60,1',
            'bindings',
            'verifiedapi',

        ],
    ];

    /**
     * The application's route middleware.
     *
     * These middleware may be assigned to groups or used individually.
     *
     * @var array
     */
    protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
        'role' => \App\Http\Middleware\HasRole::class,
        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
        'verifiedapi' => \App\Http\Middleware\EnsureApiEmailIsVerified::class,
    ];

Тогда вот это настраиваемое промежуточное ПО EnsureApiEmailIsVerified:

<?php

namespace App\Http\Middleware;

use Closure;
use App\Http\Controllers\API\Auth\MustVerifyApiEmail;

class EnsureApiEmailIsVerified
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if (! $request->user('api') ||
            ($request->user('api') instanceof MustVerifyApiEmail &&
            ! $request->user('api')->hasVerifiedEmail())) {
            return abort(403, 'Your email has not yet been verified.');
        }

        return $next($request);
    }
}

Вы увидите эту ссылку Это экземпляр моего настраиваемого свойства 'MustVerifyApiEmail', который является чертой, которая используется в пользовательской модели, с единственным отклонением от стандартной черты, являющейся publi c функцией 'sendApiEmailVerificationNotification' как таковой:

<?php

namespace App\Http\Controllers\API\Auth;

use App\Notifications\VerifyApiEmail;

trait MustVerifyApiEmail
{
    /**
     * Determine if the user has verified their email address.
     *
     * @return bool
     */
    public function hasVerifiedEmail()
    {
        return ! is_null($this->email_verified_at);
    }

    /**
     * Mark the given user's email as verified.
     *
     * @return bool
     */
    public function markEmailAsVerified()
    {
        return $this->forceFill([
            'email_verified_at' => $this->freshTimestamp(),
        ])->save();
    }

    /**
     * Send the email verification notification.
     *
     * @return void
     */
    public function sendApiEmailVerificationNotification()
    {
        $this->notify(new VerifyApiEmail);
    }

    /**
     * Get the email address that should be used for verification.
     *
     * @return string
     */
    public function getEmailForVerification()
    {
        return $this->email;
    }
}

Этот новый 'sendApiEmailVerificationNotification ()' уведомляет пользователя $ request ('api') с помощью настраиваемого уведомления VerifyApiEmail, как такового:

<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\Lang;
use Illuminate\Support\Facades\Config;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;

class VerifyApiEmail implements ShouldQueue
{
    use Queueable;

    /**
     * The callback that should be used to build the mail message.
     *
     * @var \Closure|null
     */
    public static $toMailCallback;

    /**
     * Create a new notification instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return ['mail'];
    }

    /**
     * Build the mail representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return \Illuminate\Notifications\Messages\MailMessage
     */
    public function toMail($notifiable)
    {
        $verificationUrl = $this->verificationUrl($notifiable);

        if (static::$toMailCallback) {
            return call_user_func(static::$toMailCallback, $notifiable, $verificationUrl);
        }

        return (new MailMessage)
            ->subject(Lang::get('Verify Email Address'))
            ->line(Lang::get('Please click the button below to verify your email address.'))
            ->action(Lang::get('Verify Email Address'), $verificationUrl)
            ->line(Lang::get('If you did not create an account, no further action is required.'));
    }

    /**
     * Get the verification URL for the given notifiable.
     *
     * @param  mixed  $notifiable
     * @return string
     */
    protected function verificationUrl($notifiable)
    {
        return URL::temporarySignedRoute(
            'verification.api.verify',
            Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)),
            [
                'id' => $notifiable->getKey(),
                'hash' => sha1($notifiable->getEmailForVerification()),
            ]
        );
    }

    /**
     * Set a callback that should be used when building the notification mail message.
     *
     * @param  \Closure  $callback
     * @return void
     */
    public static function toMailUsing($callback)
    {
        static::$toMailCallback = $callback;
    }
}

Новые маршруты api следующие:

Route::namespace('API\Auth')->group(function () {
    Route::post('login', 'PassportController@login');
    Route::post('refresh', 'PassportController@refresh');
    Route::post('logout', 'PassportController@logout');
    Route::get('email/verify/{id}/{hash}', 'VerificationApiController@verify')->name('verification.api.verify');
    Route::get('email/resend', 'VerificationApiController@resend')->name('api.verification.resend');
});

И VerificationApiController выглядит следующим образом:

<?php

namespace App\Http\Controllers\API\Auth;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Auth\Events\Verified;
use Illuminate\Auth\Access\AuthorizationException;

class VerificationApiController extends Controller
{

    /**
     * Show the email verification notice.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function show(Request $request)
    {
        //
    }

    /**
     * Mark the authenticated user's email address as verified.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     *
     * @throws \Illuminate\Auth\Access\AuthorizationException
     */
    public function verify(Request $request)
    {
        if (! hash_equals((string) $request->route('id'), (string) $request->user('api')->getKey())) {
            throw new AuthorizationException;
        }

        if (! hash_equals((string) $request->route('hash'), sha1($request->user('api')->getEmailForVerification()))) {
            throw new AuthorizationException;
        }

        if ($request->user('api')->hasVerifiedEmail()) {
            return response()->json(['error' => 'Email already verified'], 422);
        }

        if ($request->user('api')->markEmailAsVerified()) {
            event(new Verified($request->user('api')));
        }

        return response()->json(['success' => 'Email verified!']);
    }

    /**
     * Resend the email verification notification.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function resend(Request $request)
    {
        $request->user('api')->sendApiEmailVerificationNotification();

        return response()->json(['success' => 'Email verification has been resent!']);
    }
}

Я также заметил, что в пользовательской модели он расширяет User as Authenticatable, который затем использует стандартный MustVerifyEmail '- поэтому я вынес это из фреймворка как ну и изменил использование на новый MustVerifyApiEmail - вот так:

<?php

namespace App\Http\Controllers\API\Auth;

use Illuminate\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Foundation\Auth\Access\Authorizable;
use App\Http\Controllers\API\Auth\MustVerifyApiEmail;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;

class User extends Model implements
    AuthenticatableContract,
    AuthorizableContract,
    CanResetPasswordContract
{
    use Authenticatable, Authorizable, CanResetPassword, MustVerifyApiEmail;
}

Моя модель пользователя тогда выглядит так вверху:

<?php

namespace App;

use Illuminate\Support\Str;
use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use App\Http\Controllers\API\Auth\User as Authenticatable;
use App\Http\Controllers\API\Auth\MustVerifyApiEmailInterface;

class User extends Authenticatable implements MustVerifyApiEmailInterface
{
    use HasApiTokens, Notifiable;
...

Как видите, это довольно много настройки - но все это должно работать теоретически, и я не получаю ошибок, которые можно было бы использовать. Вот ошибки, которые я получаю:

Когда я вхожу в систему с пользователем, электронная почта которого проверена или даже не проверена, я получаю сообщение об ошибке, что электронная почта пользователя не была проверена - но только потому, что он не выбирает вверх по $ request-> user ('api'). Когда я пытаюсь выбросить ошибку в самом промежуточном программном обеспечении перед возвратом запроса, сбрасывающего $ request-> user ('api'), он дает мне значение null

Итак, мой вопрос в стандартном промежуточном программном обеспечении «проверено» => \ Illuminate \ Auth \ Middleware \ EnsureEmailIsVerified :: class, - как это подбирает $ request-> user ()?

Я что-то упускаю или я поступаю неправильно? Кажется, что когда пользователь входит в систему, он не регистрирует их, а затем запускает промежуточное программное обеспечение - поэтому нет $ request-> user ('api') - возможно, потому что я использую Passport, но я бы подумал, что то, что должно случается, что промежуточное ПО должно запускаться после аутентификации пользователя, тогда оно будет иметь доступ к $ request-> user ('api')

ЛЮБЫЕ УКАЗАНИЯ БУДУТ ОЧЕНЬ ПРИНЦИПИРОВАНЫ!

API. php

Route::prefix('email')->middleware(['auth:api'])->group(function () {
  Route::get('resend', 'VerificationController@resend')->name('verification.resend');
  Route::get('verify/{id}/{hash}', 'VerificationController@verify')->name('verification.verify');
});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...