#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"]