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:
44
routes/api.php
Normal file
44
routes/api.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\Api\AnalyticsController;
|
||||
use App\Http\Controllers\Api\AppController;
|
||||
use App\Http\Controllers\Api\AuthorizedAppController;
|
||||
use App\Http\Controllers\Api\ClientController;
|
||||
use App\Http\Controllers\Api\PersonalAccessTokenController;
|
||||
use App\Http\Controllers\Api\UserController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
// Usuario del token actual (whoami) + roles.
|
||||
Route::get('/user', [UserController::class, 'me'])->middleware('auth:api');
|
||||
|
||||
// Directorio de usuarios (admin) — self-service para uno mismo, permiso users.* para el resto.
|
||||
// "/users/trashed" tiene que registrarse ANTES de la apiResource: si no, Laravel
|
||||
// intenta bindear "trashed" como {user} de la ruta show y nunca llega acá.
|
||||
Route::get('/users/trashed', [UserController::class, 'trashed']);
|
||||
Route::get('/users/pending', [UserController::class, 'pending']);
|
||||
Route::post('/users/{id}/restore', [UserController::class, 'restore'])->whereNumber('id');
|
||||
Route::delete('/users/{id}/force', [UserController::class, 'forceDelete'])->whereNumber('id');
|
||||
Route::apiResource('users', UserController::class);
|
||||
Route::post('/users/{user}/approve', [UserController::class, 'approve']);
|
||||
Route::get('/users/{user}/app-roles', [UserController::class, 'appRoles']);
|
||||
Route::post('/users/{user}/app-roles', [UserController::class, 'assignAppRole']);
|
||||
Route::delete('/users/{user}/app-roles/{clientId}/{role}', [UserController::class, 'revokeAppRole']);
|
||||
|
||||
// Apps registradas en el sistema (admin, para el selector de roles-por-app).
|
||||
Route::get('/apps', [AppController::class, 'index']);
|
||||
|
||||
// Analíticas de uso: sesiones, logins por app, actividad reciente.
|
||||
Route::get('/analytics/summary', [AnalyticsController::class, 'summary']);
|
||||
|
||||
// Cuenta propia: clientes OAuth, personal access tokens y apps autorizadas.
|
||||
// Todo esto es lo mismo que ofrece la SPA Vue por sesión (routes/web.php), pero
|
||||
// vía Bearer token — para que cualquier cliente (Angular, Flutter, etc.) pueda
|
||||
// construir su propio panel sin depender de la sesión de Laravel.
|
||||
Route::apiResource('clients', ClientController::class)->except(['show'])->names('api.clients');
|
||||
|
||||
Route::get('/personal-access-tokens', [PersonalAccessTokenController::class, 'index']);
|
||||
Route::post('/personal-access-tokens', [PersonalAccessTokenController::class, 'store']);
|
||||
Route::delete('/personal-access-tokens/{tokenId}', [PersonalAccessTokenController::class, 'destroy']);
|
||||
|
||||
Route::get('/authorized-apps', [AuthorizedAppController::class, 'index']);
|
||||
Route::delete('/authorized-apps/{tokenId}', [AuthorizedAppController::class, 'destroy']);
|
||||
8
routes/console.php
Normal file
8
routes/console.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Foundation\Inspiring;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
|
||||
Artisan::command('inspire', function () {
|
||||
$this->comment(Inspiring::quote());
|
||||
})->purpose('Display an inspiring quote');
|
||||
74
routes/web.php
Normal file
74
routes/web.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\Admin\AnalyticsController;
|
||||
use App\Http\Controllers\Admin\AppController;
|
||||
use App\Http\Controllers\Admin\UserController;
|
||||
use App\Http\Controllers\Auth\AuthController;
|
||||
use App\Http\Controllers\OAuth\AuthorizedAppController;
|
||||
use App\Http\Controllers\OAuth\ClientController;
|
||||
use App\Http\Controllers\OAuth\PersonalAccessTokenController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Laravel\Passport\Passport;
|
||||
|
||||
// Selector de idioma (es/en/ca), no requiere sesión.
|
||||
Route::post('/web-api/locale', function () {
|
||||
request()->validate(['locale' => 'required|in:es,en,ca']);
|
||||
session(['locale' => request('locale')]);
|
||||
|
||||
return response()->noContent();
|
||||
});
|
||||
|
||||
// Endpoints de sesión (guard "web") que consume la SPA para loguearse y
|
||||
// administrar el servidor OAuth2 (clientes, personal access tokens, apps autorizadas).
|
||||
Route::prefix('web-api')->group(function () {
|
||||
Route::get('/setup-status', [AuthController::class, 'setupStatus']);
|
||||
Route::post('/register', [AuthController::class, 'register']);
|
||||
Route::post('/login', [AuthController::class, 'login']);
|
||||
|
||||
Route::middleware('auth')->group(function () {
|
||||
Route::post('/logout', [AuthController::class, 'logout']);
|
||||
Route::get('/me', [AuthController::class, 'me']);
|
||||
Route::get('/scopes', fn () => response()->json(Passport::scopes()));
|
||||
|
||||
Route::apiResource('clients', ClientController::class)->except(['show']);
|
||||
|
||||
Route::get('/personal-access-tokens', [PersonalAccessTokenController::class, 'index']);
|
||||
Route::post('/personal-access-tokens', [PersonalAccessTokenController::class, 'store']);
|
||||
Route::delete('/personal-access-tokens/{tokenId}', [PersonalAccessTokenController::class, 'destroy']);
|
||||
|
||||
Route::get('/authorized-apps', [AuthorizedAppController::class, 'index']);
|
||||
Route::delete('/authorized-apps/{tokenId}', [AuthorizedAppController::class, 'destroy']);
|
||||
|
||||
// Panel de administración de usuarios (solo para roles/permisos con "users.*").
|
||||
Route::prefix('admin')->group(function () {
|
||||
Route::get('/users', [UserController::class, 'index'])->middleware('can:users.view');
|
||||
Route::get('/users/trashed', [UserController::class, 'trashed'])->middleware('can:users.view');
|
||||
Route::get('/users/pending', [UserController::class, 'pending'])->middleware('can:users.view');
|
||||
Route::post('/users', [UserController::class, 'store'])->middleware('can:users.create');
|
||||
Route::put('/users/{user}', [UserController::class, 'update'])->middleware('can:users.update');
|
||||
Route::delete('/users/{user}', [UserController::class, 'destroy'])->middleware('can:users.delete');
|
||||
Route::post('/users/{id}/restore', [UserController::class, 'restore'])->middleware('can:users.update')->whereNumber('id');
|
||||
Route::delete('/users/{id}/force', [UserController::class, 'forceDelete'])->middleware('can:users.delete')->whereNumber('id');
|
||||
Route::post('/users/{user}/approve', [UserController::class, 'approve'])->middleware('can:users.update');
|
||||
|
||||
// Roles por app (multi-tenant): a qué app y con qué rol puede entrar cada usuario.
|
||||
Route::get('/apps', [AppController::class, 'index'])->middleware('can:users.view');
|
||||
Route::get('/users/{user}/app-roles', [UserController::class, 'appRoles'])->middleware('can:users.view');
|
||||
Route::post('/users/{user}/app-roles', [UserController::class, 'assignAppRole'])->middleware('can:users.update');
|
||||
Route::delete('/users/{user}/app-roles/{clientId}/{role}', [UserController::class, 'revokeAppRole'])->middleware('can:users.update');
|
||||
|
||||
// Analíticas de uso: sesiones, logins por app, actividad reciente.
|
||||
Route::get('/analytics/summary', [AnalyticsController::class, 'summary'])->middleware('can:users.view');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Laravel/Passport redirige a los invitados no autenticados a route('login'), así que
|
||||
// necesita una ruta fija (no el wildcard de abajo, que exige un parámetro).
|
||||
Route::view('/login', 'app')->name('login');
|
||||
|
||||
// Todo lo demás (home, register, dashboard, /oauth/authorize cuando hay sesión, etc.)
|
||||
// lo resuelve el shell de la SPA Vue (Vue Router decide qué pantalla mostrar).
|
||||
Route::view('/', 'app');
|
||||
Route::get('/{any}', fn () => view('app'))
|
||||
->where('any', '^(?!web-api|api|oauth|storage|login).*$');
|
||||
Reference in New Issue
Block a user