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>
This commit is contained in:
170
database/seeders/DatabaseSeeder.php
Normal file
170
database/seeders/DatabaseSeeder.php
Normal file
@@ -0,0 +1,170 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
}
|
||||
27
database/seeders/RolesAndPermissionsSeeder.php
Normal file
27
database/seeders/RolesAndPermissionsSeeder.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
/**
|
||||
* Roles y permisos base (admin/user + permisos "users.*"). Se ejecuta desde
|
||||
* DatabaseSeeder y también en runtime cuando se registra el primer usuario
|
||||
* de una instalación (ver AuthController::register) — así el flujo de
|
||||
* "primer admin" funciona aunque nunca se haya corrido "db:seed" en producción.
|
||||
*/
|
||||
class RolesAndPermissionsSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
foreach (['users.view', 'users.create', 'users.update', 'users.delete'] as $permission) {
|
||||
Permission::findOrCreate($permission);
|
||||
}
|
||||
|
||||
$admin = Role::findOrCreate('admin');
|
||||
$admin->syncPermissions(Permission::all());
|
||||
Role::findOrCreate('user');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user