Livewire Сервер вернул «Метод 405 Не разрешен». в производстве, а не проблема на стадии разработки

#laravel-blade #laravel-livewire

Вопрос:

Я работал с приложением, созданным с помощью TALL. На локальной работе все нормально, а на рабочей странице с 3 формами появляется ошибка «Метод 405 не разрешен».

Трассировка ошибок показывает проблему с методом POST вместо метода GET.

Форма представляет собой смесь трех форм. 1 — Тот, у которого профиль 2. Тот, у которого есть пароль 3. Тот, у которого был жетон

Все три формы являются компонентами livewire.

У каждого есть свой бутон, который нужно спасти.

Таким образом, ошибка возникает в любой из трех кнопок сохранения в процессе работы.

wep.php

 Route::group(['prefix' => 'dashboard', 'as' => 'admin.', 'middleware' => ['auth']], function () {
   ...
   Route::get('/profile', Profile::class)->name('profile');
   ... 
}
 

Profile.php

 <?php

namespace AppHttpLivewireAuth;

use AppModelsUser;
use LivewireComponent;

class Profile extends Component
{

    public User $user;

    public function mount() { $this->user = auth()->user(); }

    public function render()
    {
        return view('livewire.auth.profile');
    }
}
 

UpdatePassword.php

 <?php

namespace AppHttpLivewireAuthProfile;

use LivewireComponent;
use AppModelsUser;

class UpdatePassword extends Component
{
    //public User $user;

    public $password;
    public $password_confirmation;

    public function render()
    {
        return view('livewire.auth.profile.update-password');
    }

    protected $rules = [
            'password' => [
                'required',
                'confirmed',
                'min:10',
                'regex:/^.*(?=.{3,})(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[dx])(?=.*[!$#%]).*$/',
            ],
    ];

    public function updated($propertyName)
    {
        $this->validateOnly($propertyName);
    }

    public function save()
    {
        $this->validate();

        auth()->user()->update([
            'password' => bcrypt($this->password)
        ]);

        $this->emitSelf('notify-saved');

        $this->resetForm();
    }

    protected function resetForm()
    {
        $this->password = '';
        $this->password_confirmation = '';
    }

}
 

UpdateProfile.php

 <?php

namespace AppHttpLivewireAuthProfile;

use AppModelsUser;
use LivewireComponent;
use LivewireWithFileUploads;

class UpdateProfile extends Component
{
    use WithFileUploads;

    public User $user;

    public $upload;

    public function render()
    {
        return view('livewire.auth.profile.update-profile');
    }

    protected function rules(): array
    {
        return [
            'user.name' => [
                'string',
                'required',
                'min:5',
            ],
            'user.email' => [
                'email:rfc',
                'required',
                'regex:/^([a-z0-9 _-] )(.[a-z0-9 _-] )*@([a-z0-9-] .) [a-z]{2,6}$/ix',
                'unique:users,email,' . $this->user->id,
            ],
            'upload' => [
                'nullable',
                'image',
                'mimes:jpg,bmp,png',
                'max:200'
            ],
        ];
    }

    public function mount() { $this->user = auth()->user(); }

    public function save()
    {
        $this->validate();

        $this->user->save();

        $this->upload amp;amp; $this->user->update([
            'avatar' => $this->upload->store('/', 'avatars'),
        ]);

        $this->emitSelf('notify-saved');
    }

}
 

UpdateToken.php

 <?php

namespace AppHttpLivewireAuthProfile;

use AppHelpersApiHelpers;
use LivewireComponent;

class UpdateToken extends Component
{
    public string $token;

    public function mount()
    {
        $this->resetState();
    }

    public function updateToken()
    {

        $user = auth()->user();

        ApiHelpers::deleteTokenUserType($user->id, 'auth_token');

        $this->token = auth()->user()->createToken('auth_token')->plainTextToken;
    }

    public function render()
    {
        return view('livewire.auth.profile.update-token');
    }

    protected function resetState()
    {
        $this->token = '';
    }
}
 

profile.blade.php

 <div>
    @livewire('auth.profile.update-profile')
    <!-- Contraseña -->
    <div class="hidden sm:block" aria-hidden="true">
        <div class="py-5">
            <div class="border-t border-gray-200"></div>
        </div>
    </div>

    @livewire('auth.profile.update-password')

    <!-- Token -->
    <div class="hidden sm:block" aria-hidden="true">
        <div class="py-5">
            <div class="border-t border-gray-200"></div>
        </div>
    </div>

    @livewire('auth.profile.update-token')
</div>
 

update-profile.blade.php

 <div>
    <div class="md:grid md:grid-cols-3 md:gap-6 border-gray-300">
        <div class="md:col-span-1">
            <h3 class="text-lg font-medium leading-6 text-gray-900">Perfil</h3>
            <p class="mt-1 text-sm text-gray-500">
                Esta información es privada y sólo tiene efectos administrativos.
            </p>
        </div>
        <div class="mt-5 md:mt-0 md:col-span-2">
            <form wire:submit.prevent="save">
                <div class="shadow sm:rounded-md sm:overflow-hidden">
                    <div class="px-4 py-5 bg-white space-y-6 sm:p-6">
                        <!-- Nombre -->
                        <div class="grid grid-cols-3 gap-6">
                            <div class="col-span-3 sm:col-span-2">
                                <label for="name" class="block text-sm font-medium text-gray-700">
                                    Nombre
                                </label>
                                <div class="mt-1 flex rounded-md shadow-sm">
                                    <input wire:model.defer="user.name" type="text" name="username" id="username" class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-r-md sm:text-sm border-gray-300" placeholder="Nombre y apellidos">
                                </div>
                                <div class="mt-1 relative rounded-md shadow-sm">
                                    @error('user.name')
                                    <div class="mt-1 text-red-500 text-sm">{{ $message }}</div>
                                    @enderror
                                </div>
                            </div>
                        </div>

                        <div class="grid grid-cols-3 gap-6">
                            <div class="col-span-3 sm:col-span-2">
                                <label for="email" class="block text-sm font-medium text-gray-700">
                                    Email
                                </label>
                                <div class="mt-1 flex rounded-md shadow-sm">
                                    <input wire:model.defer="user.email" type="text" name="email" id="email" class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-r-md sm:text-sm border-gray-300" placeholder="Correo electrónico">
                                </div>
                                <div class="mt-1 relative rounded-md shadow-sm">
                                    @error('user.email')
                                    <div class="mt-1 text-red-500 text-sm">{{ $message }}</div>
                                    @enderror
                                </div>
                            </div>
                        </div>

                        <div>
                            <label class="block text-sm font-medium text-gray-700">
                                Foto
                            </label>
                            <div class="mt-1 flex items-center space-x-5">
                                    <span class="inline-block h-12 w-12 rounded-full overflow-hidden bg-gray-100">
                                        @if ($upload)
                                            <img src="{{ $upload->temporaryUrl() }}" alt="Profile Photo">
                                        @else
                                            <img src="{{ auth()->user()->avatarUrl() }}" alt="Profile Photo">
                                        @endif
                                    </span>
                                <input type="file" wire:model="upload" id="photo">
                                <div class="mt-1 relative rounded-md shadow-sm">
                                    @error('user.avatar')
                                    <div class="mt-1 text-red-500 text-sm">{{ $message }}</div>
                                    @enderror
                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="px-4 py-3 bg-gray-50 text-right sm:px-6">
                        <button type="submit" class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
                            Save
                        </button>
                        <span x-data="{ open: false }"
                              x-init="@this.on('notify-saved',
                                    () => {
                                        if (open === false) setTimeout(() => { open = false }, 3500);
                                        open = true;
                                    })"
                              x-show.transition.out.duration.1000ms="open"
                              style="display: none;"
                              class="text-gray-500">¡Guardado!</span>
                    </div>
                </div>
            </form>
        </div>
    </div>
</div>
 

update-password.blade.php

 <div>
    <div class="md:grid md:grid-cols-3 md:gap-6 border-gray-300">
        <div class="md:col-span-1">
            <h3 class="text-lg font-medium leading-6 text-gray-900">Contraseña</h3>
            <p class="mt-1 text-sm text-gray-500">
                Puede cambiar su contraseña en este formulario
            </p>
        </div>
        <div class="mt-5 md:mt-0 md:col-span-2">
            <form wire:submit.prevent="save">
                <div class="shadow sm:rounded-md sm:overflow-hidden">
                    <div class="px-4 py-5 bg-white space-y-6 sm:p-6">

                        <div class="grid grid-cols-3 gap-6">
                            <div class="col-span-3 sm:col-span-2">
                                <label for="password" class="block text-sm font-medium text-gray-700">
                                    Contraseña
                                </label>
                                <div class="mt-1 flex rounded-md shadow-sm">
                                    <input wire:model.defer="password" type="password" name="password" id="password" class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-r-md sm:text-sm border-gray-300" placeholder="Nueva contraseña">
                                </div>
                                <div class="mt-1 relative rounded-md shadow-sm">
                                    @error('password')
                                    <div class="mt-1 text-red-500 text-sm">{{ $message }}</div>
                                    @enderror
                                </div>
                            </div>
                        </div>

                        <div class="grid grid-cols-3 gap-6">
                            <div class="col-span-3 sm:col-span-2">
                                <label for="password_confirmation" class="block text-sm font-medium text-gray-700">
                                    Confirma la contraseña
                                </label>
                                <div class="mt-1 flex rounded-md shadow-sm">
                                    <input wire:model.defer="password_confirmation" type="password" name="password_confirmation" id="password_confirmation" class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-r-md sm:text-sm border-gray-300">
                                </div>
                                <div class="mt-1 relative rounded-md shadow-sm">
                                    @error('password_confirmation')
                                    <div class="mt-1 text-red-500 text-sm">{{ $message }}</div>
                                    @enderror
                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="px-4 py-3 bg-gray-50 text-right sm:px-6">
                        <button type="submit" class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
                            Cambiar
                        </button>
                        <span x-data="{ open: false }"
                              x-init="@this.on('notify-saved',
                                    () => {
                                        if (open === false) setTimeout(() => { open = false }, 3500);
                                        open = true;
                                    })"
                              x-show.transition.out.duration.1000ms="open"
                              style="display: none;"
                              class="text-gray-500">¡Contraseña cambiada!
                        </span>
                    </div>
                </div>
            </form>
        </div>
    </div>
</div>
 

update-token.php

 <div>
    <div class="md:grid md:grid-cols-3 md:gap-6 border-gray-300">
        <div class="md:col-span-1">
            <h3 class="text-lg font-medium leading-6 text-gray-900">Token API</h3>
            <p class="mt-1 text-sm text-gray-500">
                El token solo se muestra una vez generado, por seguridad.
                <br />Copielo y guardelo en un lugar seguro.
                <br />El anterior se elimina del sistema, dejan de ser operativo.
            </p>
        </div>
        <div class="mt-5 md:mt-0 md:col-span-2">
            <form wire:submit.prevent="updateToken">
                <div class="shadow sm:rounded-md sm:overflow-hidden">
                    <div class="px-4 py-5 bg-white space-y-6 sm:p-6">
                        <div class="grid grid-cols-3 gap-6">
                            <div class="col-span-3 sm:col-span-2">
                                <label for="token" class="block text-sm font-medium text-gray-700">
                                    Token
                                </label>
                                <div class="mt-1 flex rounded-md shadow-sm">
                                    <input wire:model="token" type="text" name="token" id="token"  disabled class="disabled:opacity-50 focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-r-md sm:text-sm border-gray-300" placeholder="Haz click en el botón para regenerar el token">
                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="px-4 py-3 bg-gray-50 text-right sm:px-6">
                        <button type="submit" class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
                            Regenerar
                        </button>
                    </div>
                </div>
            </form>
        </div>
    </div>
</div>
 

Комментарии:

1. Вы только начинаете проект или есть похожий код, который работает?

2. Это проект, который работает на местном уровне, но имеет проблему в одной форме, в производстве @gnasher729

Ответ №1:

Непростительная ошибка.

Как системный администратор, я забыл, что вы должны просматривать журналы. И среди прочего журнал mod_security.

 --9ac02665-F--
HTTP/1.1 405 Method Not Allowed
allow: POST
Cache-Control: no-cache, private
date: Fri, 12 Nov 2021 15:18:31 GMT
Connection: close
Content-Type: text/html; charset=UTF-8
Server: Apache

--9ac02665-H--
Message: Warning. Matched phrase ".profile" at REQUEST_FILENAME. [file "/etc/apache2/conf.d/modsec_vendor_configs/OWASP3/rules/REQUEST-930-APPLICATION-ATTACK-LFI.conf"] [line "124"] [id "930130"] [msg "Restricted File Access Attempt"] [data "Matched Data: .profile found within REQUEST_FILENAME: /livewire/message/auth.profile.update-profile"] [severity "CRITICAL"] [ver "OWASP_CRS/3.3.2"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-lfi"] [tag "paranoia-level/1"] [tag "OWASP_CRS"] [tag "capec/1000/255/153/126"] [tag "PCI/6.5.4"]