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:
2026-07-26 01:02:41 +02:00
commit c2b5291a74
174 changed files with 29318 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Support\AnalyticsSummary;
use Illuminate\Http\Request;
class AnalyticsController extends Controller
{
public function summary(Request $request)
{
$days = min(max((int) $request->integer('days', 30), 7), 90);
return response()->json(AnalyticsSummary::build($days));
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Laravel\Passport\Client;
class AppController extends Controller
{
/**
* Todas las apps (clientes OAuth) registradas en el sistema, sin importar
* quién las creó — para que el admin elija a cuál asignarle un rol por-app.
*/
public function index()
{
return response()->json(
Client::query()->where('revoked', false)->orderBy('name')->get(['id', 'name'])
);
}
}

View File

@@ -0,0 +1,198 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rule;
use Spatie\Permission\Models\Role;
class UserController extends Controller
{
public function index()
{
return response()->json(
User::query()->whereNotNull('approved_at')->orderBy('name')->get()->map($this->present(...))
);
}
/**
* Usuarios borrados (soft delete) — la "papelera".
*/
public function trashed()
{
return response()->json(
User::onlyTrashed()->orderByDesc('deleted_at')->get()->map($this->present(...))
);
}
/**
* Usuarios que se autorregistraron y todavía no aprobó ningún admin.
*/
public function pending()
{
return response()->json(
User::query()->whereNull('approved_at')->orderBy('created_at')->get()->map($this->present(...))
);
}
public function approve(User $user)
{
$user->update(['approved_at' => now()]);
return response()->json($this->present($user));
}
public function store(Request $request)
{
$data = $request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users', 'email')],
'password' => ['required', 'string', 'min:8'],
'role' => ['required', Rule::in(Role::pluck('name'))],
]);
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
'is_active' => true,
'approved_at' => now(), // lo da de alta un admin: ya está aprobado.
]);
$user->assignRole($data['role']);
return response()->json($this->present($user), 201);
}
public function update(Request $request, User $user)
{
$data = $request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users', 'email')->ignore($user->id)],
'password' => ['nullable', 'string', 'min:8'],
'is_active' => ['boolean'],
'role' => ['required', Rule::in(Role::pluck('name'))],
]);
if ($user->id === $request->user()->id && $data['role'] !== 'admin' && $user->hasRole('admin')) {
abort(422, __('messages.cannot_remove_own_admin_role'));
}
$user->update([
'name' => $data['name'],
'email' => $data['email'],
'is_active' => $data['is_active'] ?? $user->is_active,
...(isset($data['password']) ? ['password' => Hash::make($data['password'])] : []),
]);
$user->syncRoles([$data['role']]);
return response()->json($this->present($user->fresh()));
}
/**
* Soft delete: el usuario deja de aparecer en el sistema pero queda
* recuperable desde la papelera.
*/
public function destroy(Request $request, User $user)
{
abort_if($user->id === $request->user()->id, 422, __('messages.cannot_delete_self'));
if ($user->hasRole('admin') && User::role('admin')->count() <= 1) {
abort(422, __('messages.cannot_delete_last_admin'));
}
$user->delete();
return response()->noContent();
}
public function restore(int $id)
{
$user = User::onlyTrashed()->findOrFail($id);
$user->restore();
return response()->json($this->present($user));
}
/**
* Borrado definitivo — solo tiene sentido sobre alguien que ya está en la papelera.
*/
public function forceDelete(Request $request, int $id)
{
$user = User::onlyTrashed()->findOrFail($id);
$user->forceDelete();
return response()->noContent();
}
/**
* Roles que este usuario tiene asignados en cada app (cliente OAuth), agrupados.
* Es un sistema aparte del rol global de spatie: vive en app_user_roles,
* no en las tablas de spatie (evita mezclar "quién administra este panel"
* con "qué rol tiene este usuario dentro de la app de un tercero").
*/
public function appRoles(User $user)
{
$rows = DB::table('app_user_roles')
->join('oauth_clients', 'oauth_clients.id', '=', 'app_user_roles.client_id')
->where('app_user_roles.user_id', $user->id)
->get(['oauth_clients.id as client_id', 'oauth_clients.name as client_name', 'app_user_roles.role']);
return response()->json(
$rows->groupBy('client_id')->map(fn ($group) => [
'client_id' => $group->first()->client_id,
'client_name' => $group->first()->client_name,
'roles' => $group->pluck('role'),
])->values()
);
}
public function assignAppRole(Request $request, User $user)
{
$data = $request->validate([
'client_id' => ['required', 'exists:oauth_clients,id'],
'role' => ['required', Rule::in(Role::pluck('name'))],
]);
DB::table('app_user_roles')->insertOrIgnore([
'user_id' => $user->id,
'client_id' => $data['client_id'],
'role' => $data['role'],
'created_at' => now(),
'updated_at' => now(),
]);
return response()->json($data, 201);
}
public function revokeAppRole(User $user, string $clientId, string $role)
{
DB::table('app_user_roles')
->where('user_id', $user->id)
->where('client_id', $clientId)
->where('role', $role)
->delete();
return response()->noContent();
}
private function present(User $user): array
{
return [
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
'is_active' => $user->is_active,
'roles' => $user->getRoleNames(),
'created_at' => $user->created_at,
'deleted_at' => $user->deleted_at,
'approved_at' => $user->approved_at,
];
}
}