++ fix
This commit is contained in:
@@ -6,6 +6,7 @@ APP_TIMEZONE=Europe/Rome
|
||||
APP_URL=http://localhost:8080
|
||||
|
||||
APP_PORT=8080
|
||||
SEED_DEV_DATA=false
|
||||
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=mariadb
|
||||
|
||||
@@ -139,6 +139,7 @@ docker compose up -d --build
|
||||
|-----------------------|----------------------|------------------------------------------|
|
||||
| `APP_KEY` | (generata) | Chiave AES-256 per cifratura. **Mai condividere** |
|
||||
| `APP_PORT` | `8080` | Porta host per l'applicazione |
|
||||
| `SEED_DEV_DATA` | `false` | Se `true`, `php artisan db:seed` include anche i dati demo |
|
||||
| `DB_DATABASE` | `termanager2` | Nome database MariaDB |
|
||||
| `DB_USERNAME` | `termanager2` | Utente database |
|
||||
| `DB_PASSWORD` | `secret` | Password database |
|
||||
|
||||
55
app/Http/Controllers/Auth/LoginController.php
Normal file
55
app/Http/Controllers/Auth/LoginController.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class LoginController extends Controller
|
||||
{
|
||||
public function __invoke(Request $request): RedirectResponse
|
||||
{
|
||||
if (! Setting::isSetupComplete() || User::count() === 0) {
|
||||
return redirect()->route('setup.index');
|
||||
}
|
||||
|
||||
$credentials = $request->validate([
|
||||
'email' => ['required', 'email'],
|
||||
'password' => ['required', 'string', 'min:6'],
|
||||
'remember' => ['nullable', 'boolean'],
|
||||
]);
|
||||
|
||||
$throttleKey = Str::transliterate(Str::lower($credentials['email']) . '|' . $request->ip());
|
||||
|
||||
if (RateLimiter::tooManyAttempts($throttleKey, 5)) {
|
||||
$seconds = RateLimiter::availableIn($throttleKey);
|
||||
|
||||
return back()
|
||||
->withErrors(['email' => "Troppi tentativi. Riprova tra {$seconds} secondi."])
|
||||
->withInput($request->only('email', 'remember'));
|
||||
}
|
||||
|
||||
if (! Auth::attempt([
|
||||
'email' => $credentials['email'],
|
||||
'password' => $credentials['password'],
|
||||
], $request->boolean('remember'))) {
|
||||
RateLimiter::hit($throttleKey);
|
||||
|
||||
return back()
|
||||
->withErrors(['email' => 'Credenziali non valide.'])
|
||||
->withInput($request->only('email', 'remember'));
|
||||
}
|
||||
|
||||
RateLimiter::clear($throttleKey);
|
||||
$request->session()->regenerate();
|
||||
activity()->causedBy(auth()->user())->log('login');
|
||||
|
||||
return redirect()->intended(route('dashboard'));
|
||||
}
|
||||
}
|
||||
7
app/Http/Controllers/Controller.php
Normal file
7
app/Http/Controllers/Controller.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
}
|
||||
@@ -3,47 +3,16 @@
|
||||
namespace App\Livewire\Auth;
|
||||
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
|
||||
class Login extends Component
|
||||
{
|
||||
public string $email = '';
|
||||
public string $password = '';
|
||||
public bool $remember = false;
|
||||
|
||||
protected function rules(): array
|
||||
public function mount()
|
||||
{
|
||||
return [
|
||||
'email' => 'required|email',
|
||||
'password' => 'required|min:6',
|
||||
];
|
||||
if (! Setting::isSetupComplete() || User::count() === 0) {
|
||||
return redirect()->route('setup.index');
|
||||
}
|
||||
|
||||
public function login()
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
$throttleKey = Str::transliterate(Str::lower($this->email) . '|' . request()->ip());
|
||||
|
||||
if (RateLimiter::tooManyAttempts($throttleKey, 5)) {
|
||||
$seconds = RateLimiter::availableIn($throttleKey);
|
||||
$this->addError('email', "Troppi tentativi. Riprova tra {$seconds} secondi.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Auth::attempt(['email' => $this->email, 'password' => $this->password], $this->remember)) {
|
||||
RateLimiter::hit($throttleKey);
|
||||
$this->addError('email', 'Credenziali non valide.');
|
||||
return;
|
||||
}
|
||||
|
||||
RateLimiter::clear($throttleKey);
|
||||
session()->regenerate();
|
||||
activity()->causedBy(auth()->user())->log('login');
|
||||
|
||||
return redirect()->intended(route('dashboard'));
|
||||
}
|
||||
|
||||
public function render()
|
||||
|
||||
@@ -6,6 +6,7 @@ use Livewire\Component;
|
||||
use Livewire\WithFileUploads;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class Wizard extends Component
|
||||
@@ -33,11 +34,11 @@ class Wizard extends Component
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if (Setting::isSetupComplete()) {
|
||||
if (Setting::isSetupComplete() && User::count() > 0) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
|
||||
$this->needsAdmin = User::count() <= 1;
|
||||
$this->needsAdmin = User::count() === 0;
|
||||
|
||||
$setting = Setting::first();
|
||||
if ($setting) {
|
||||
@@ -107,12 +108,19 @@ class Wizard extends Component
|
||||
'password' => Hash::make($this->admin_password),
|
||||
]);
|
||||
$admin->assignRole('amministratore');
|
||||
Auth::login($admin);
|
||||
request()->session()->regenerate();
|
||||
}
|
||||
|
||||
session()->flash('success', 'Setup completato con successo!');
|
||||
|
||||
if (auth()->check()) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
|
||||
return redirect()->route('login');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.setup.wizard')
|
||||
|
||||
@@ -3,14 +3,16 @@
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
class DatabaseSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$this->call([
|
||||
RolesAndPermissionsSeeder::class,
|
||||
DevSeeder::class,
|
||||
]);
|
||||
$this->call([RolesAndPermissionsSeeder::class]);
|
||||
|
||||
if (App::environment('local') && env('SEED_DEV_DATA', false)) {
|
||||
$this->call([DevSeeder::class]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,12 @@ services:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "nc -z 127.0.0.1 9000"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 20
|
||||
start_period: 45s
|
||||
environment:
|
||||
- PHP_OPCACHE_VALIDATE_TIMESTAMPS=1
|
||||
|
||||
@@ -32,7 +38,8 @@ services:
|
||||
networks:
|
||||
- termanager2
|
||||
depends_on:
|
||||
- app
|
||||
app:
|
||||
condition: service_healthy
|
||||
|
||||
mariadb:
|
||||
image: mariadb:11
|
||||
|
||||
@@ -4,6 +4,7 @@ FROM php:8.3-fpm
|
||||
RUN apt-get update && apt-get install -y \
|
||||
git \
|
||||
curl \
|
||||
netcat-openbsd \
|
||||
libpng-dev \
|
||||
libjpeg62-turbo-dev \
|
||||
libfreetype6-dev \
|
||||
@@ -58,6 +59,7 @@ RUN if [ -f package-lock.json ]; then \
|
||||
COPY . .
|
||||
RUN mkdir -p bootstrap/cache storage/framework/cache storage/framework/sessions storage/framework/views storage/logs storage/app
|
||||
RUN if [ ! -f .env ] && [ -f .env.example ]; then cp .env.example .env; fi
|
||||
RUN date -u +%Y%m%d%H%M%S > .image-build-id
|
||||
RUN composer dump-autoload --optimize --no-interaction
|
||||
RUN npm run build
|
||||
|
||||
|
||||
@@ -37,12 +37,34 @@ upsert_env() {
|
||||
fi
|
||||
}
|
||||
|
||||
sync_app_code() {
|
||||
local env_backup="/tmp/termanager2.env.backup"
|
||||
|
||||
rm -f "$env_backup"
|
||||
|
||||
if [ -f /var/www/html/.env ]; then
|
||||
cp /var/www/html/.env "$env_backup"
|
||||
fi
|
||||
|
||||
cp -a /app-src/. /var/www/html/
|
||||
|
||||
if [ -f "$env_backup" ]; then
|
||||
mv "$env_backup" /var/www/html/.env
|
||||
fi
|
||||
}
|
||||
|
||||
# -----------------------------------------------
|
||||
# 0. Sync application code from image to volume
|
||||
# -----------------------------------------------
|
||||
IMAGE_BUILD_FILE="/app-src/.image-build-id"
|
||||
VOLUME_BUILD_FILE="/var/www/html/.image-build-id"
|
||||
|
||||
if [ ! -f /var/www/html/artisan ]; then
|
||||
echo "[*] Syncing application code to volume..."
|
||||
cp -a /app-src/. /var/www/html/
|
||||
sync_app_code
|
||||
elif [ -f "$IMAGE_BUILD_FILE" ] && { [ ! -f "$VOLUME_BUILD_FILE" ] || ! cmp -s "$IMAGE_BUILD_FILE" "$VOLUME_BUILD_FILE"; }; then
|
||||
echo "[*] New image detected. Syncing updated application code to volume..."
|
||||
sync_app_code
|
||||
else
|
||||
echo "[✓] Application code already in volume."
|
||||
fi
|
||||
|
||||
@@ -5,34 +5,31 @@
|
||||
<p class="text-gray-500 text-sm mt-1">Accedi per continuare</p>
|
||||
</div>
|
||||
|
||||
<form wire:submit="login" class="space-y-5">
|
||||
<form method="POST" action="{{ route('login.store') }}" class="space-y-5">
|
||||
@csrf
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-gray-700">Email</label>
|
||||
<input wire:model="email" type="email" id="email" autocomplete="email"
|
||||
<input name="email" value="{{ old('email') }}" type="email" id="email" autocomplete="email"
|
||||
class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 text-sm px-4 py-2.5">
|
||||
@error('email') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="password" class="block text-sm font-medium text-gray-700">Password</label>
|
||||
<input wire:model="password" type="password" id="password" autocomplete="current-password"
|
||||
<input name="password" type="password" id="password" autocomplete="current-password"
|
||||
class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 text-sm px-4 py-2.5">
|
||||
@error('password') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<input wire:model="remember" type="checkbox" id="remember"
|
||||
<input name="remember" value="1" {{ old('remember') ? 'checked' : '' }} type="checkbox" id="remember"
|
||||
class="h-4 w-4 text-indigo-600 border-gray-300 rounded focus:ring-indigo-500">
|
||||
<label for="remember" class="ml-2 text-sm text-gray-600">Ricordami</label>
|
||||
</div>
|
||||
|
||||
<button type="submit"
|
||||
class="w-full flex justify-center py-2.5 px-4 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition">
|
||||
<span wire:loading.remove>Accedi</span>
|
||||
<span wire:loading class="flex items-center gap-2">
|
||||
<svg class="animate-spin h-4 w-4" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"/><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/></svg>
|
||||
Accesso in corso...
|
||||
</span>
|
||||
<span>Accedi</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Http\Controllers\Auth\LoginController;
|
||||
use App\Livewire\Home;
|
||||
use App\Livewire\Setup\Wizard;
|
||||
use App\Livewire\Territori\TerritorioIndex;
|
||||
@@ -33,6 +34,7 @@ use App\Livewire\Privacy;
|
||||
*/
|
||||
Route::middleware('guest')->group(function () {
|
||||
Route::get('login', App\Livewire\Auth\Login::class)->name('login');
|
||||
Route::post('login', LoginController::class)->name('login.store');
|
||||
});
|
||||
|
||||
Route::post('logout', function () {
|
||||
@@ -48,9 +50,9 @@ Route::post('logout', function () {
|
||||
| Setup Wizard (bypasses setup.required middleware via exclusion)
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
Route::middleware('auth')->group(function () {
|
||||
Route::get('setup', Wizard::class)->name('setup.index')->withoutMiddleware(\App\Http\Middleware\SetupRequired::class);
|
||||
});
|
||||
Route::get('setup', Wizard::class)
|
||||
->name('setup.index')
|
||||
->withoutMiddleware(\App\Http\Middleware\SetupRequired::class);
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user