Feature: Customer Account Services

This commit is contained in:
2026-09-15 16:01:28 +03:00
parent 57fc28ca06
commit 8472649905
19 changed files with 875 additions and 13 deletions
@@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* Caps brute-forcing a 6-digit OTP code (1M combinations, 10-minute
* window, previously uncapped) — see Modules\Core\Auth\Services\
* UserOtpService::validate(), which now invalidates the code entirely
* (forcing a fresh generateAndSend()) once otp_attempts reaches its max,
* rather than leaving a live code guessable indefinitely within its
* expiry window.
*/
return new class extends Migration
{
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->unsignedTinyInteger('otp_attempts')->default(0)->after('otp_expires_at');
});
}
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('otp_attempts');
});
}
};
@@ -0,0 +1,41 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* A per-login session registry, independent of the actual session store
* driver (SESSION_DRIVER=redis in this app — no "sessions" table to
* purge by user_id the way the database driver would allow). Each
* successful OTP login (Modules\Core\Auth\Services\UserOtpService::
* validate()) records one row here and stamps the token into the
* Laravel session payload; Modules\Core\Auth\Http\Middleware\
* EnsureSessionNotRevoked checks it on every request. "Logout
* everywhere" (Modules\Core\Auth\Services\UserSessionService::
* revokeOtherSessions()) is then just marking every OTHER row
* revoked_at, no session-store-specific logic anywhere.
*/
return new class extends Migration
{
public function up(): void
{
Schema::create('user_sessions', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('token', 64)->unique();
$table->string('user_agent')->nullable();
$table->string('ip_address', 45)->nullable();
$table->timestamp('last_used_at');
$table->timestamp('revoked_at')->nullable();
$table->timestamps();
$table->index(['user_id', 'revoked_at']);
});
}
public function down(): void
{
Schema::dropIfExists('user_sessions');
}
};