++ fix
This commit is contained in:
@@ -6,6 +6,7 @@ APP_TIMEZONE=Europe/Rome
|
|||||||
APP_URL=http://localhost:8080
|
APP_URL=http://localhost:8080
|
||||||
|
|
||||||
APP_PORT=8080
|
APP_PORT=8080
|
||||||
|
SEED_DEV_DATA=false
|
||||||
|
|
||||||
DB_CONNECTION=mysql
|
DB_CONNECTION=mysql
|
||||||
DB_HOST=mariadb
|
DB_HOST=mariadb
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ docker compose up -d --build
|
|||||||
|-----------------------|----------------------|------------------------------------------|
|
|-----------------------|----------------------|------------------------------------------|
|
||||||
| `APP_KEY` | (generata) | Chiave AES-256 per cifratura. **Mai condividere** |
|
| `APP_KEY` | (generata) | Chiave AES-256 per cifratura. **Mai condividere** |
|
||||||
| `APP_PORT` | `8080` | Porta host per l'applicazione |
|
| `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_DATABASE` | `termanager2` | Nome database MariaDB |
|
||||||
| `DB_USERNAME` | `termanager2` | Utente database |
|
| `DB_USERNAME` | `termanager2` | Utente database |
|
||||||
| `DB_PASSWORD` | `secret` | Password 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;
|
namespace App\Livewire\Auth;
|
||||||
|
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use App\Models\Setting;
|
||||||
use Illuminate\Support\Facades\RateLimiter;
|
use App\Models\User;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
|
|
||||||
class Login extends Component
|
class Login extends Component
|
||||||
{
|
{
|
||||||
public string $email = '';
|
public function mount()
|
||||||
public string $password = '';
|
|
||||||
public bool $remember = false;
|
|
||||||
|
|
||||||
protected function rules(): array
|
|
||||||
{
|
{
|
||||||
return [
|
if (! Setting::isSetupComplete() || User::count() === 0) {
|
||||||
'email' => 'required|email',
|
return redirect()->route('setup.index');
|
||||||
'password' => 'required|min:6',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
public function render()
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use Livewire\Component;
|
|||||||
use Livewire\WithFileUploads;
|
use Livewire\WithFileUploads;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
|
||||||
class Wizard extends Component
|
class Wizard extends Component
|
||||||
@@ -33,11 +34,11 @@ class Wizard extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
if (Setting::isSetupComplete()) {
|
if (Setting::isSetupComplete() && User::count() > 0) {
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->needsAdmin = User::count() <= 1;
|
$this->needsAdmin = User::count() === 0;
|
||||||
|
|
||||||
$setting = Setting::first();
|
$setting = Setting::first();
|
||||||
if ($setting) {
|
if ($setting) {
|
||||||
@@ -107,10 +108,17 @@ class Wizard extends Component
|
|||||||
'password' => Hash::make($this->admin_password),
|
'password' => Hash::make($this->admin_password),
|
||||||
]);
|
]);
|
||||||
$admin->assignRole('amministratore');
|
$admin->assignRole('amministratore');
|
||||||
|
Auth::login($admin);
|
||||||
|
request()->session()->regenerate();
|
||||||
}
|
}
|
||||||
|
|
||||||
session()->flash('success', 'Setup completato con successo!');
|
session()->flash('success', 'Setup completato con successo!');
|
||||||
return redirect()->route('dashboard');
|
|
||||||
|
if (auth()->check()) {
|
||||||
|
return redirect()->route('dashboard');
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('login');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
|
|||||||
@@ -3,14 +3,16 @@
|
|||||||
namespace Database\Seeders;
|
namespace Database\Seeders;
|
||||||
|
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
|
use Illuminate\Support\Facades\App;
|
||||||
|
|
||||||
class DatabaseSeeder extends Seeder
|
class DatabaseSeeder extends Seeder
|
||||||
{
|
{
|
||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
$this->call([
|
$this->call([RolesAndPermissionsSeeder::class]);
|
||||||
RolesAndPermissionsSeeder::class,
|
|
||||||
DevSeeder::class,
|
if (App::environment('local') && env('SEED_DEV_DATA', false)) {
|
||||||
]);
|
$this->call([DevSeeder::class]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
redis:
|
redis:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "nc -z 127.0.0.1 9000"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 20
|
||||||
|
start_period: 45s
|
||||||
environment:
|
environment:
|
||||||
- PHP_OPCACHE_VALIDATE_TIMESTAMPS=1
|
- PHP_OPCACHE_VALIDATE_TIMESTAMPS=1
|
||||||
|
|
||||||
@@ -32,7 +38,8 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- termanager2
|
- termanager2
|
||||||
depends_on:
|
depends_on:
|
||||||
- app
|
app:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
mariadb:
|
mariadb:
|
||||||
image: mariadb:11
|
image: mariadb:11
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ FROM php:8.3-fpm
|
|||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y \
|
||||||
git \
|
git \
|
||||||
curl \
|
curl \
|
||||||
|
netcat-openbsd \
|
||||||
libpng-dev \
|
libpng-dev \
|
||||||
libjpeg62-turbo-dev \
|
libjpeg62-turbo-dev \
|
||||||
libfreetype6-dev \
|
libfreetype6-dev \
|
||||||
@@ -58,6 +59,7 @@ RUN if [ -f package-lock.json ]; then \
|
|||||||
COPY . .
|
COPY . .
|
||||||
RUN mkdir -p bootstrap/cache storage/framework/cache storage/framework/sessions storage/framework/views storage/logs storage/app
|
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 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 composer dump-autoload --optimize --no-interaction
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
|
|||||||
@@ -37,12 +37,34 @@ upsert_env() {
|
|||||||
fi
|
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
|
# 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
|
if [ ! -f /var/www/html/artisan ]; then
|
||||||
echo "[*] Syncing application code to volume..."
|
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
|
else
|
||||||
echo "[✓] Application code already in volume."
|
echo "[✓] Application code already in volume."
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -5,34 +5,31 @@
|
|||||||
<p class="text-gray-500 text-sm mt-1">Accedi per continuare</p>
|
<p class="text-gray-500 text-sm mt-1">Accedi per continuare</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form wire:submit="login" class="space-y-5">
|
<form method="POST" action="{{ route('login.store') }}" class="space-y-5">
|
||||||
|
@csrf
|
||||||
<div>
|
<div>
|
||||||
<label for="email" class="block text-sm font-medium text-gray-700">Email</label>
|
<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">
|
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
|
@error('email') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label for="password" class="block text-sm font-medium text-gray-700">Password</label>
|
<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">
|
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
|
@error('password') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center">
|
<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">
|
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>
|
<label for="remember" class="ml-2 text-sm text-gray-600">Ricordami</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit"
|
<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">
|
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>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>
|
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use App\Http\Controllers\Auth\LoginController;
|
||||||
use App\Livewire\Home;
|
use App\Livewire\Home;
|
||||||
use App\Livewire\Setup\Wizard;
|
use App\Livewire\Setup\Wizard;
|
||||||
use App\Livewire\Territori\TerritorioIndex;
|
use App\Livewire\Territori\TerritorioIndex;
|
||||||
@@ -33,6 +34,7 @@ use App\Livewire\Privacy;
|
|||||||
*/
|
*/
|
||||||
Route::middleware('guest')->group(function () {
|
Route::middleware('guest')->group(function () {
|
||||||
Route::get('login', App\Livewire\Auth\Login::class)->name('login');
|
Route::get('login', App\Livewire\Auth\Login::class)->name('login');
|
||||||
|
Route::post('login', LoginController::class)->name('login.store');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::post('logout', function () {
|
Route::post('logout', function () {
|
||||||
@@ -48,9 +50,9 @@ Route::post('logout', function () {
|
|||||||
| Setup Wizard (bypasses setup.required middleware via exclusion)
|
| Setup Wizard (bypasses setup.required middleware via exclusion)
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
Route::middleware('auth')->group(function () {
|
Route::get('setup', Wizard::class)
|
||||||
Route::get('setup', Wizard::class)->name('setup.index')->withoutMiddleware(\App\Http\Middleware\SetupRequired::class);
|
->name('setup.index')
|
||||||
});
|
->withoutMiddleware(\App\Http\Middleware\SetupRequired::class);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user