Files
oauth/database/seeders/DatabaseSeeder.php
Josep Piguillem c2b5291a74 Initial commit: OAuth IAM Laravel application
Laravel-based OAuth/IAM service with Passport, Vue 3 frontend, and
Docker Compose setup for local and production deployment.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-26 01:02:41 +02:00

171 lines
7.0 KiB
PHP

<?php
namespace Database\Seeders;
use App\Models\LoginEvent;
use App\Models\User;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Laravel\Passport\Client;
use Laravel\Passport\ClientRepository;
class DatabaseSeeder extends Seeder
{
use WithoutModelEvents;
public function run(ClientRepository $clients): void
{
$this->call(RolesAndPermissionsSeeder::class);
$adminUser = User::firstOrCreate(
['email' => 'admin@example.com'],
['name' => 'Admin', 'password' => Hash::make('password'), 'approved_at' => now()]
);
$adminUser->syncRoles(['admin']);
// Usuarios extra solo para que las analíticas (sección "usuarios más activos")
// tengan algo interesante que mostrar de entrada.
$extraUsers = collect([
['name' => 'Carla Gómez', 'email' => 'carla@example.com'],
['name' => 'Martín Ruiz', 'email' => 'martin@example.com'],
['name' => 'Bea Fernández', 'email' => 'bea@example.com'],
])->map(function (array $data) {
$u = User::firstOrCreate(['email' => $data['email']], ['name' => $data['name'], 'password' => Hash::make('password'), 'approved_at' => now()]);
$u->syncRoles(['user']);
return $u;
});
// Usuario de ejemplo que se autorregistró y todavía nadie aprobó, para
// que la pestaña "Pendientes" del panel de usuarios no arranque vacía.
User::firstOrCreate(
['email' => 'nuevo@example.com'],
['name' => 'Nuevo Registro', 'password' => Hash::make('password'), 'approved_at' => null]
)->assignRole('user');
// Cliente requerido para poder emitir Personal Access Tokens (grant "personal_access").
if (! Client::where('personal_access_client', true)->exists()) {
$clients->createPersonalAccessGrantClient('Personal Access Client');
}
// Cliente de demo para el flujo Authorization Code + PKCE (público, sin secret).
$pkceClient = $adminUser->oauthApps()->firstOrCreate(
['name' => 'Demo SPA (Authorization Code + PKCE)'],
[
'secret' => null,
'redirect_uris' => ['http://localhost:8000/callback'],
'grant_types' => ['authorization_code', 'refresh_token'],
'revoked' => false,
]
);
// Cliente de demo para Client Credentials (machine-to-machine).
$ccClient = $adminUser->oauthApps()->firstOrCreate(
['name' => 'Demo Service (Client Credentials)'],
[
'secret' => Str::random(40),
'redirect_uris' => [],
'grant_types' => ['client_credentials'],
'revoked' => false,
]
);
// Cliente de demo para Password Grant.
$pwClient = $adminUser->oauthApps()->firstOrCreate(
['name' => 'Demo First-Party App (Password Grant)'],
[
'secret' => Str::random(40),
'redirect_uris' => [],
'grant_types' => ['password', 'refresh_token'],
'revoked' => false,
]
);
// Cliente confidencial para probar el botón "Authorize" de Swagger UI
// (usa /api/oauth2-callback, que ya registra L5-Swagger).
$swaggerClient = $adminUser->oauthApps()->firstOrCreate(
['name' => 'Swagger UI'],
[
'secret' => Str::random(40),
'redirect_uris' => [rtrim(config('app.url'), '/').'/api/oauth2-callback'],
'grant_types' => ['authorization_code', 'client_credentials', 'refresh_token'],
'revoked' => false,
]
);
// Rol por-app de ejemplo: carla@example.com es "admin" dentro de la app
// "Demo SPA", aunque globalmente sea un usuario normal. Muestra que el
// rol puede variar según qué app pregunte por el usuario.
DB::table('app_user_roles')->insertOrIgnore([
'user_id' => $extraUsers->first()->id,
'client_id' => $pkceClient->id,
'role' => 'admin',
'created_at' => now(),
'updated_at' => now(),
]);
if (! LoginEvent::query()->exists()) {
$this->seedLoginEvents(
$extraUsers->push($adminUser),
collect([$pkceClient, $ccClient, $pwClient, $swaggerClient]),
);
}
$this->command?->info('Admin: admin@example.com / password');
$this->command?->info('Client Credentials -> client_id: '.$ccClient->id.' secret: '.($ccClient->plainSecret ?? '(ya existía, ver DB)'));
$this->command?->info('Password Grant -> client_id: '.$pwClient->id.' secret: '.($pwClient->plainSecret ?? '(ya existía, ver DB)'));
$this->command?->info('Swagger UI -> client_id: '.$swaggerClient->id.' secret: '.($swaggerClient->plainSecret ?? '(ya existía, ver DB)'));
}
/**
* Historial sintético de logins (últimos 30 días) para que la pantalla de
* analíticas tenga algo real que graficar apenas se levanta la app.
*
* @param \Illuminate\Support\Collection<int, User> $users
* @param \Illuminate\Support\Collection<int, Client> $clients
*/
private function seedLoginEvents($users, $clients): void
{
$rows = [];
$now = now();
for ($daysAgo = 29; $daysAgo >= 0; $daysAgo--) {
$dayStart = $now->copy()->subDays($daysAgo)->startOfDay();
$maxMinute = $daysAgo === 0 ? $now->diffInMinutes($dayStart) : 1439;
$isWeekend = $dayStart->isWeekend();
$oauthLogins = random_int($isWeekend ? 1 : 4, $isWeekend ? 6 : 14);
$sessionLogins = random_int($isWeekend ? 0 : 1, $isWeekend ? 2 : 5);
for ($i = 0; $i < $oauthLogins; $i++) {
$rows[] = [
'user_id' => $users->random()->id,
'client_id' => $clients->random()->id,
'type' => 'oauth',
'ip_address' => '10.0.'.random_int(0, 9).'.'.random_int(2, 254),
'user_agent' => 'Mozilla/5.0 (demo seed)',
'created_at' => $dayStart->copy()->addMinutes(random_int(0, max(0, $maxMinute))),
];
}
for ($i = 0; $i < $sessionLogins; $i++) {
$rows[] = [
'user_id' => $users->random()->id,
'client_id' => null,
'type' => 'session',
'ip_address' => '10.0.'.random_int(0, 9).'.'.random_int(2, 254),
'user_agent' => 'Mozilla/5.0 (demo seed)',
'created_at' => $dayStart->copy()->addMinutes(random_int(0, max(0, $maxMinute))),
];
}
}
foreach (array_chunk($rows, 200) as $chunk) {
LoginEvent::insert($chunk);
}
}
}