From 1055b706d1f71a3acee98e86b7ac01c33f9bd0d8 Mon Sep 17 00:00:00 2001 From: mikeburg Date: Sat, 9 Dec 2023 15:47:12 -0800 Subject: [PATCH 1/3] Clubhouse SSO support. --- app/Http/Controllers/AuthController.php | 6 +- app/Http/Controllers/OAuth2Controller.php | 174 ++++++++++++++++++ .../Controllers/OauthClientController.php | 93 ++++++++++ app/Models/OauthClient.php | 81 ++++++++ app/Models/OauthCode.php | 56 ++++++ app/Policies/OauthClientPolicy.php | 63 +++++++ app/Providers/AppServiceProvider.php | 1 - app/Providers/AuthServiceProvider.php | 3 + app/Providers/RouteServiceProvider.php | 75 +------- config/app.php | 84 ++------- config/auth.php | 5 + config/session.php | 43 +++-- .../2023_12_08_122632_create_oauth_tables.php | 42 +++++ routes/api.php | 13 +- routes/web.php | 18 -- 15 files changed, 580 insertions(+), 177 deletions(-) create mode 100644 app/Http/Controllers/OAuth2Controller.php create mode 100644 app/Http/Controllers/OauthClientController.php create mode 100644 app/Models/OauthClient.php create mode 100644 app/Models/OauthCode.php create mode 100644 app/Policies/OauthClientPolicy.php create mode 100644 database/migrations/2023_12_08_122632_create_oauth_tables.php delete mode 100644 routes/web.php diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php index bc3164f8..a16b9fb4 100644 --- a/app/Http/Controllers/AuthController.php +++ b/app/Http/Controllers/AuthController.php @@ -5,14 +5,18 @@ use App\Mail\ResetPassword; use App\Models\ActionLog; use App\Models\ErrorLog; +use App\Models\OauthClient; +use App\Models\OauthCode; use App\Models\Person; use Carbon\Carbon; use Exception; use GuzzleHttp; use GuzzleHttp\Exception\RequestException; use Illuminate\Auth\Access\AuthorizationException; +use Illuminate\Auth\AuthenticationException; use Illuminate\Http\JsonResponse; use Illuminate\Support\Facades\Auth; +use InvalidArgumentException; use Okta\JwtVerifier\JwtVerifierBuilder; class AuthController extends Controller @@ -113,7 +117,7 @@ private function attemptLogin(Person $person, $actionData): JsonResponse ActionLog::record($person, 'auth-login', 'User login', $actionData); - $token = $this->groundHogDayWrap(fn() => auth()->login($person)); + $token = $this->groundHogDayWrap(fn() => Auth::login($person)); return $this->respondWithToken($token); } diff --git a/app/Http/Controllers/OAuth2Controller.php b/app/Http/Controllers/OAuth2Controller.php new file mode 100644 index 00000000..da16dd30 --- /dev/null +++ b/app/Http/Controllers/OAuth2Controller.php @@ -0,0 +1,174 @@ +json([ + "issuer" => "https://ranger-clubhouse.burningman.org", + "authorization_endpoint" => "http://127.0.0.1:4200/me/oauth2-grant", + "token_endpoint" => "http://docker.for.mac.localhost:8000/auth/oauth2/token", + "userinfo_endpoint" => "http://docker.for.mac.localhost:8000/auth/oauth2/userinfo", + "response_types_supported" => [ + "code", + "token", + "none" + ], + "subject_types_supported" => [ + "public" + ], + "id_token_signing_alg_values_supported" => [ + "RS256" + ], + "scopes_supported" => [ + "openid", + "email", + "profile" + ], + "token_endpoint_auth_methods_supported" => [ + "client_secret_post", + "client_secret_basic" + ], + "claims_supported" => [ + "aud", + "email", + "exp", + "family_name", + "given_name", + "iat", + "iss", + "locale", + "name", + "sub" + ], + "code_challenge_methods_supported" => [ + "plain", + "S256" + ], + "grant_types_supported" => [ + "authorization_code", + "refresh_token", + "urn:ietf:params:oauth:grant-type:device_code", + "urn:ietf:params:oauth:grant-type:jwt-bearer" + ] + ]); + } + + /** + * Create a grant code and build the callback url for the frontend handling the login form. + * Unlike the other methods in this controller, this one has to called by a logged-in user. + * + */ + + public function grantOAuthCode(): JsonResponse + { + $params = request()->validate([ + 'response_type' => 'required|string', + 'redirect_uri' => 'required|string', + 'client_id' => 'required|string|exists:oauth_client,client_id', + 'scope' => 'required|string', + 'state' => 'sometimes|string', + ]); + + + if ($params['response_type'] != 'code') { + throw new InvalidArgumentException('Unsupported response type'); + } + + $client = OauthClient::findForClientId($params['client_id']); + $code = OauthCode::createCodeForClient($client, Auth::user(), $params['scope']); + + $callbackParams = ['code' => $code]; + if (!empty($params['state'])) { + $callbackParams['state'] = $params['state']; + } + + return response()->json([ + 'callback_url' => $params['redirect_uri'] . '?' . http_build_query($callbackParams), + 'client_description' => $client->description, + ]); + } + + /** + * Response to an OAuth2 token request + * + * @return JsonResponse + */ + + public function grantOAuthToken(): JsonResponse + { + $params = request()->validate([ + 'grant_type' => 'required|string', + 'client_id' => 'required|string', + 'client_secret' => 'required|string', + 'code' => 'required|string', + ]); + + if ($params['grant_type'] != 'authorization_code') { + throw new InvalidArgumentException('Grant type not supported.'); + } + + $client = OauthClient::findForClientId($params['client_id']); + if (!$client) { + throw new InvalidArgumentException('Client ID not registered.'); + } + + if ($client->secret != $params['client_secret']) { + throw new InvalidArgumentException('Invalid client secret'); + } + + $oc = OAuthCode::findForClientCode($client, $params['code']); + if (!$oc) { + throw new InvalidArgumentException('Code not found.'); + } + + $person = Person::findOrFail($oc->person_id); + + $claims = []; + if (!empty($oc->scope)) { + foreach (explode(' ', $oc->scope) as $scope) { + switch ($scope) { + case 'email': + $claims['email'] = $person->email; + break; + case 'profile': + $claims['given_name'] = $person->desired_first_name(); + $claims['family_name'] = $person->last_name; + break; + } + } + } + + $token = auth()->claims($claims)->login($person); + return response()->json([ + 'token_type' => 'Bearer', + 'access_token' => $token, + 'expires_in' => (string)now()->addSeconds(config('jwt.ttl')) + ]); + } + + /** + * Return basic user information for oauth2 tokens + * + * @return JsonResponse + */ + + public function oauthUserInfo(): JsonResponse + { + $person = Auth::user(); + + return response()->json([ + 'email' => $person->email, + 'given_name' => $person->desired_first_name(), + 'family_name' => $person->last_name + ]); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/OauthClientController.php b/app/Http/Controllers/OauthClientController.php new file mode 100644 index 00000000..01a3affa --- /dev/null +++ b/app/Http/Controllers/OauthClientController.php @@ -0,0 +1,93 @@ +authorize('index', OauthClient::class); + return $this->success(OauthClient::findAll()); + } + + /*** + * Create an OAuth Client record + * + * @return JsonResponse + * @throws AuthorizationException|ValidationException + */ + + public function store(): JsonResponse + { + $this->authorize('store', OauthClient::class); + $client = new OauthClient; + $this->fromRest($client); + + if ($client->save()) { + return $this->success($client); + } + + return $this->restError($client); + } + + /** + * Display the specified OAuth Client. + * + * @param OauthClient $client + * @return JsonResponse + * @throws AuthorizationException + */ + + public function show(OauthClient $client): JsonResponse + { + $this->authorize('show', $client); + return $this->success($client); + } + + /** + * Update the specified OAuth Client in storage. + * + * @param OauthClient $client + * @return JsonResponse + * @throws AuthorizationException|ValidationException + */ + + public function update(OauthClient $client): JsonResponse + { + $this->authorize('update', $client); + $this->fromRest($client); + + if ($client->save()) { + return $this->success($client); + } + + return $this->restError($client); + } + + /** + * Delete an OAuth Client record + * + * @param OauthClient $client + * @return JsonResponse + * @throws AuthorizationException + */ + + public function destroy(OauthClient $client): JsonResponse + { + $this->authorize('destroy', $client); + $client->delete(); + return $this->restDeleteSuccess(); + } +} diff --git a/app/Models/OauthClient.php b/app/Models/OauthClient.php new file mode 100644 index 00000000..b79d0569 --- /dev/null +++ b/app/Models/OauthClient.php @@ -0,0 +1,81 @@ + 'datetime', + ]; + + protected $fillable = [ + 'client_id', + 'secret', + 'description', + ]; + + protected $rules = [ + 'client_id' => 'required|string', + 'description' => 'required|string', + ]; + + public static function boot(): void + { + parent::boot(); + + self::deleted(function ($model) { + OauthCode::where('oauth_client_id', $model->id)->delete(); + }); + } + + public function oauth_codes(): HasMany + { + return $this->hasMany(OauthCode::class); + } + + public static function findAll(): Collection + { + return self::orderBy('client_id')->get(); + } + + public static function findForClientId(string $clientId): ?self + { + return self::where('client_id', $clientId)->first(); + } + + public function save($options = []): bool + { + if ($this->exists) { + $this->rules['secret'] = 'required|string'; + } else { + $this->secret = Str::random(32); + } + + if (!$this->exists || $this->isDirty('client_id')) { + $this->rules['client_id'] = [ + 'required', + 'string', + Rule::unique('oauth_client')->where(function ($q) { + $q->where('client_id', $this->client_id); + if ($this->exists) { + $q->where('id', '!=', $this->id); + } + }) + ]; + + } + return parent::save($options); + } +} \ No newline at end of file diff --git a/app/Models/OauthCode.php b/app/Models/OauthCode.php new file mode 100644 index 00000000..a4b9e77d --- /dev/null +++ b/app/Models/OauthCode.php @@ -0,0 +1,56 @@ + 'datetime' + ]; + + public static function deleteExpired(): void + { + self::where('created_at', '<=', now()->subSeconds(self::EXPIRE_IN_SECONDS))->delete(); + } + + public function person(): BelongsTo + { + return $this->belongsTo(Person::class); + } + + public function oauth_client(): BelongsTo + { + return $this->belongsTo(OauthClient::class); + } + + public static function findForClientCode(OauthClient $client, string $code): ?OauthCode + { + return self::where('oauth_client_id', $client->id)->where('code', $code)->first(); + } + + public static function createCodeForClient(OauthClient $client, Person $person, string $scope): string + { + $oc = new OauthCode([ + 'oauth_client_id' => $client->id, + 'person_id' => $person->id, + 'code' => Str::random(64), + 'scope' => $scope, + 'created_at' => now(), + ]); + + $oc->save(); + + return $oc->code; + } +} \ No newline at end of file diff --git a/app/Policies/OauthClientPolicy.php b/app/Policies/OauthClientPolicy.php new file mode 100644 index 00000000..6c5fa21e --- /dev/null +++ b/app/Policies/OauthClientPolicy.php @@ -0,0 +1,63 @@ +hasRole(Role::TECH_NINJA)) { + return true; + } + } + + /** + * Determine if the user can view all the records + */ + + public function index(Person $user): false + { + return false; + } + + /** + * Determine if the user can see one record + */ + + public function show(Person $user): false + { + return false; + } + + /** + * Determine whether the user can create a OAuth Client document. + */ + public function store(Person $user): false + { + return false; + } + + /** + * Determine whether the user can update the OAuth Client. + */ + + public function update(Person $user, OauthClient $client): false + { + return false; + } + + /** + * Determine whether the user can delete the OAuth Client. + */ + public function destroy(Person $user, OauthClient $client): false + { + return false; + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 018ded33..05f7b767 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -7,7 +7,6 @@ use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Validator; use Illuminate\Support\ServiceProvider; -use Laravel\Passport\Passport; class AppServiceProvider extends ServiceProvider { diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 20b0a28c..775a0a3e 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -20,6 +20,7 @@ use App\Models\HandleReservation; use App\Models\Help; use App\Models\Motd; +use App\Models\OauthClient; use App\Models\OnlineCourse; use App\Models\Person; use App\Models\PersonAward; @@ -65,6 +66,7 @@ use App\Policies\HandleReservationPolicy; use App\Policies\HelpPolicy; use App\Policies\MotdPolicy; +use App\Policies\OauthClientPolicy; use App\Policies\OnlineCoursePolicy; use App\Policies\PersonAwardPolicy; use App\Policies\PersonEventPolicy; @@ -121,6 +123,7 @@ class AuthServiceProvider extends ServiceProvider HandleReservation::class => HandleReservationPolicy::class, Help::class => HelpPolicy::class, Motd::class => MotdPolicy::class, + OauthClient::class => OauthClientPolicy::class, OnlineCourse::class => OnlineCoursePolicy::class, Person::class => PersonPolicy::class, PersonAward::class => PersonAwardPolicy::class, diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 6a316c91..6634cbc6 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -2,16 +2,10 @@ namespace App\Providers; -use Illuminate\Support\Facades\Route; -use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; - -use App\Models\AccessDocumentDelivery; -use App\Models\Bmid; -use App\Models\Document; use App\Models\Help; use App\Models\PersonEvent; -use App\Models\PersonIntakeNote; -use App\Models\Setting; +use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; +use Illuminate\Support\Facades\Route; class RouteServiceProvider extends ServiceProvider { @@ -29,76 +23,23 @@ class RouteServiceProvider extends ServiceProvider * * @return void */ - public function boot() + public function boot(): void { parent::boot(); - Route::bind('access-document-delivery', function($id) { - return AccessDocumentDelivery::findForRoute($id) ?? abort(404); - }); - - Route::bind('bmid', function($id) { - return Bmid::find($id) ?? abort(404); - }); - Route::bind('help', function ($id) { return Help::findByIdOrSlug($id) ?? abort(404); }); - Route::bind('setting', function ($id) { - return Setting::find($id) ?? abort(404); - }); - Route::bind('person-event', function ($id) { return PersonEvent::findForRoute($id) ?? abort(404); }); - Route::bind('document', function ($id) { - return Document::findIdOrTag($id) ?? abort(404); + $this->routes(function () { + Route::prefix('') + ->middleware('api') + ->namespace($this->namespace) + ->group(base_path('routes/api.php')); }); } - - /** - * Define the routes for the application. - * - * @return void - */ - public function map() - { - $this->mapApiRoutes(); - - //$this->mapWebRoutes(); - } - - /** - * Define the "web" routes for the application. - * - * These routes all receive session state, CSRF protection, etc. - * - * @return void - */ - - /* - protected function mapWebRoutes() - { - Route::middleware('web') - ->namespace($this->namespace) - ->group(base_path('routes/web.php')); - } - */ - - /** - * Define the "api" routes for the application. - * - * These routes are typically stateless. - * - * @return void - */ - protected function mapApiRoutes() - { - Route::prefix('') - ->middleware('api') - ->namespace($this->namespace) - ->group(base_path('routes/api.php')); - } } diff --git a/config/app.php b/config/app.php index b323b90d..293cc24e 100644 --- a/config/app.php +++ b/config/app.php @@ -1,5 +1,8 @@ env('APP_DEBUG', false), + 'debug' => (bool) env('APP_DEBUG', false), /* |-------------------------------------------------------------------------- @@ -54,6 +57,8 @@ 'url' => env('APP_URL', 'http://localhost'), + 'asset_url' => env('ASSET_URL'), + /* |-------------------------------------------------------------------------- | Application Timezone @@ -120,34 +125,7 @@ | */ - 'providers' => [ - - /* - * Laravel Framework Service Providers... - */ - Illuminate\Auth\AuthServiceProvider::class, - Illuminate\Broadcasting\BroadcastServiceProvider::class, - Illuminate\Bus\BusServiceProvider::class, - Illuminate\Cache\CacheServiceProvider::class, - Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, -// Illuminate\Cookie\CookieServiceProvider::class, - Illuminate\Database\DatabaseServiceProvider::class, -// Illuminate\Encryption\EncryptionServiceProvider::class, - Illuminate\Filesystem\FilesystemServiceProvider::class, - Illuminate\Foundation\Providers\FoundationServiceProvider::class, - Illuminate\Hashing\HashServiceProvider::class, - Illuminate\Mail\MailServiceProvider::class, - Illuminate\Notifications\NotificationServiceProvider::class, - Illuminate\Pagination\PaginationServiceProvider::class, - Illuminate\Pipeline\PipelineServiceProvider::class, - Illuminate\Queue\QueueServiceProvider::class, - Illuminate\Redis\RedisServiceProvider::class, -// Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, -// Illuminate\Session\SessionServiceProvider::class, - Illuminate\Translation\TranslationServiceProvider::class, - Illuminate\Validation\ValidationServiceProvider::class, - Illuminate\View\ViewServiceProvider::class, - + 'providers' => ServiceProvider::defaultProviders()->merge([ /* * Package Service Providers... */ @@ -160,10 +138,7 @@ // App\Providers\BroadcastServiceProvider::class, App\Providers\EventServiceProvider::class, App\Providers\RouteServiceProvider::class, - - // Database audit trail - //OwenIt\Auditing\AuditingServiceProvider::class, - ], + ])->toArray(), /* |-------------------------------------------------------------------------- @@ -176,47 +151,10 @@ | */ - 'aliases' => [ - - 'App' => Illuminate\Support\Facades\App::class, - 'Artisan' => Illuminate\Support\Facades\Artisan::class, - 'Auth' => Illuminate\Support\Facades\Auth::class, - 'Blade' => Illuminate\Support\Facades\Blade::class, - 'Broadcast' => Illuminate\Support\Facades\Broadcast::class, - 'Bus' => Illuminate\Support\Facades\Bus::class, - 'Cache' => Illuminate\Support\Facades\Cache::class, - 'Config' => Illuminate\Support\Facades\Config::class, - 'Cookie' => Illuminate\Support\Facades\Cookie::class, - 'Crypt' => Illuminate\Support\Facades\Crypt::class, - 'DB' => Illuminate\Support\Facades\DB::class, - 'Eloquent' => Illuminate\Database\Eloquent\Model::class, - 'Event' => Illuminate\Support\Facades\Event::class, - 'File' => Illuminate\Support\Facades\File::class, - 'Gate' => Illuminate\Support\Facades\Gate::class, - 'Hash' => Illuminate\Support\Facades\Hash::class, - 'Lang' => Illuminate\Support\Facades\Lang::class, - 'Log' => Illuminate\Support\Facades\Log::class, - 'Mail' => Illuminate\Support\Facades\Mail::class, - 'Notification' => Illuminate\Support\Facades\Notification::class, - 'Password' => Illuminate\Support\Facades\Password::class, - 'Queue' => Illuminate\Support\Facades\Queue::class, - 'Redirect' => Illuminate\Support\Facades\Redirect::class, - 'Redis' => Illuminate\Support\Facades\Redis::class, - 'Request' => Illuminate\Support\Facades\Request::class, - 'Response' => Illuminate\Support\Facades\Response::class, - 'Route' => Illuminate\Support\Facades\Route::class, - 'Schema' => Illuminate\Support\Facades\Schema::class, - 'Session' => Illuminate\Support\Facades\Session::class, - 'Storage' => Illuminate\Support\Facades\Storage::class, - 'Str' => Illuminate\Support\Str::class, - 'URL' => Illuminate\Support\Facades\URL::class, - 'Validator' => Illuminate\Support\Facades\Validator::class, - 'View' => Illuminate\Support\Facades\View::class, - - // For email message generation + 'aliases' => Facade::defaultAliases()->merge([ + // 'Example' => App\Facades\Example::class, 'HyperLinkHelper' => App\Helpers\HyperLinkHelper::class, 'PersonPhoto' => App\Models\PersonPhoto::class, - - ], + ])->toArray(), ]; diff --git a/config/auth.php b/config/auth.php index 99c4b661..5d659d15 100644 --- a/config/auth.php +++ b/config/auth.php @@ -41,6 +41,11 @@ 'provider' => 'users', ], + 'passport' => [ + 'driver' => 'passport', + 'provider' => 'users', + ], + 'api' => [ 'driver' => 'jwt', 'provider' => 'users', diff --git a/config/session.php b/config/session.php index 8906ed30..aefd696c 100644 --- a/config/session.php +++ b/config/session.php @@ -14,11 +14,11 @@ | you may specify any of the other wonderful drivers provided here. | | Supported: "file", "cookie", "database", "apc", - | "memcached", "redis", "array" + | "memcached", "redis", "dynamodb", "array" | */ - 'driver' => env('SESSION_DRIVER', 'file'), + 'driver' => env('SESSION_DRIVER', 'array'), /* |-------------------------------------------------------------------------- @@ -72,7 +72,7 @@ | */ - 'connection' => null, + 'connection' => env('SESSION_CONNECTION'), /* |-------------------------------------------------------------------------- @@ -92,13 +92,15 @@ | Session Cache Store |-------------------------------------------------------------------------- | - | When using the "apc" or "memcached" session drivers, you may specify a - | cache store that should be used for these sessions. This value must - | correspond with one of the application's configured cache stores. + | While using one of the framework's cache driven session backends you may + | list a cache store that should be used for these sessions. This value + | must match with one of the application's configured cache "stores". + | + | Affects: "apc", "dynamodb", "memcached", "redis" | */ - 'store' => null, + 'store' => env('SESSION_STORE'), /* |-------------------------------------------------------------------------- @@ -126,7 +128,7 @@ 'cookie' => env( 'SESSION_COOKIE', - Str::slug(env('APP_NAME', 'Rangers Secret Clubhouse'), '_').'_session' + Str::slug(env('APP_NAME', 'clubhouse'), '_').'_session' ), /* @@ -153,7 +155,7 @@ | */ - 'domain' => env('SESSION_DOMAIN', null), + 'domain' => env('SESSION_DOMAIN'), /* |-------------------------------------------------------------------------- @@ -162,11 +164,11 @@ | | By setting this option to true, session cookies will only be sent back | to the server if the browser has a HTTPS connection. This will keep - | the cookie from being sent to you if it can not be done securely. + | the cookie from being sent to you when it can't be done securely. | */ - 'secure' => env('SESSION_SECURE_COOKIE', false), + 'secure' => env('SESSION_SECURE_COOKIE'), /* |-------------------------------------------------------------------------- @@ -188,12 +190,25 @@ | | This option determines how your cookies behave when cross-site requests | take place, and can be used to mitigate CSRF attacks. By default, we - | do not enable this as other CSRF protection services are in place. + | will set this value to "lax" since this is a secure default value. + | + | Supported: "lax", "strict", "none", null + | + */ + + 'same_site' => 'lax', + + /* + |-------------------------------------------------------------------------- + | Partitioned Cookies + |-------------------------------------------------------------------------- | - | Supported: "lax", "strict" + | Setting this value to true will tie the cookie to the top-level site for + | a cross-site context. Partitioned cookies are accepted by the browser + | when flagged "secure" and the Same-Site attribute is set to "none". | */ - 'same_site' => null, + 'partitioned' => false, ]; diff --git a/database/migrations/2023_12_08_122632_create_oauth_tables.php b/database/migrations/2023_12_08_122632_create_oauth_tables.php new file mode 100644 index 00000000..6bcbb3d6 --- /dev/null +++ b/database/migrations/2023_12_08_122632_create_oauth_tables.php @@ -0,0 +1,42 @@ +id(); + $table->string('code')->nullable(false); + $table->string('scope'); + $table->integer('oauth_client_id')->nullable(false); + $table->integer('person_id')->nullable(false); + $table->datetime('created_at')->nullable(false); + $table->unique([ 'oauth_client_id', 'code']); + }); + + Schema::create('oauth_client', function (Blueprint $table) { + $table->id(); + $table->string('client_id')->nullable(false); + $table->string('description')->nullable(false); + $table->string('secret')->nullable(false); + $table->timestamps(); + $table->unique('client_id'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('oauth_code'); + Schema::dropIfExists('oauth_client'); + } +}; diff --git a/routes/api.php b/routes/api.php index 06e9d26d..cbb6ffa6 100644 --- a/routes/api.php +++ b/routes/api.php @@ -20,13 +20,13 @@ Route::group([ 'middleware' => 'api', -], function ($router) { +], function () { Route::get('config/dashboard-period', 'ConfigController@dashboardPeriod'); Route::get('config', 'ConfigController@show'); Route::post('auth/login', 'AuthController@login'); Route::post('auth/reset-password', 'AuthController@resetPassword'); - + Route::match(['GET', 'POST'], 'auth/oauth2/token', 'Oauth2Controller@grantOAuthToken'); Route::post('person/register', 'PersonController@register'); Route::post('error-log/record', 'ErrorLogController@record'); @@ -41,6 +41,8 @@ // Serve up files in exports, photos, and staging Route::get('{file}', 'FileController@serve')->where('file', '(exports|photos|staging)/.*'); } + + Route::get('.well-known/openid-configuration', 'OAuth2Controller@openIdDiscovery'); }); @@ -55,6 +57,9 @@ Route::post('auth/logout', 'AuthController@logout'); Route::post('auth/refresh', 'AuthController@refresh'); + Route::get('auth/oauth2/grant-code', 'OAuth2Controller@grantOAuthCode'); + Route::get('auth/oauth2/userinfo', 'OAuth2Controller@oauthUserInfo'); + Route::post('access-document/bank-access-documents', 'AccessDocumentController@bankAccessDocuments'); Route::post('access-document/bulk-comment', 'AccessDocumentController@bulkComment'); Route::post('access-document/bump-expiration', 'AccessDocumentController@bumpExpiration'); @@ -119,7 +124,7 @@ Route::resource('document', 'DocumentController'); Route::get('callsigns', 'CallsignsController@index'); - + Route::post('certification/people', 'CertificationController@peopleReport'); Route::resource('certification', 'CertificationController'); @@ -198,6 +203,8 @@ Route::post('motd/{motd}/markread', 'MotdController@markRead'); Route::resource('motd', 'MotdController'); + Route::resource('oauth-client', 'OauthClientController'); + Route::get('person/alpha-shirts', 'PersonController@alphaShirts'); Route::get('person/languages', 'PersonController@languagesReport'); Route::get('person/by-location', 'PersonController@peopleByLocation'); diff --git a/routes/web.php b/routes/web.php deleted file mode 100644 index 1490d45a..00000000 --- a/routes/web.php +++ /dev/null @@ -1,18 +0,0 @@ - Date: Sat, 9 Dec 2023 15:55:56 -0800 Subject: [PATCH 2/3] Added oauth code expiry command, and cron job. --- .../Commands/ClubhouseExpireOauthCodes.php | 32 +++++++++++++++++++ app/Console/Kernel.php | 3 ++ 2 files changed, 35 insertions(+) create mode 100644 app/Console/Commands/ClubhouseExpireOauthCodes.php diff --git a/app/Console/Commands/ClubhouseExpireOauthCodes.php b/app/Console/Commands/ClubhouseExpireOauthCodes.php new file mode 100644 index 00000000..472cab01 --- /dev/null +++ b/app/Console/Commands/ClubhouseExpireOauthCodes.php @@ -0,0 +1,32 @@ +command('clubhouse:cleanup-maillog')->dailyAt('03:30')->onOneServer(); + // Cleanup oauth codes + $schedule->command('clubhouse:expire-oauth-codes')->dailyAt('03:00')->onOneServer(); + // Sign out timesheets $schedule->job(new SignOutTimesheetsJob())->cron('0,10,20,30,40,50 * 15-31 8 *')->onOneServer(); $schedule->job(new SignOutTimesheetsJob())->cron('0,10,20,30,40,50 * 1-10 9 *')->onOneServer(); From 1568f709041ae44ddd62a9cb7e6364160828dc4e Mon Sep 17 00:00:00 2001 From: mikeburg Date: Sat, 9 Dec 2023 16:50:02 -0800 Subject: [PATCH 3/3] Added APP_KEY to test_docker. --- bin/test_docker | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bin/test_docker b/bin/test_docker index e69f33a5..db9eeee3 100755 --- a/bin/test_docker +++ b/bin/test_docker @@ -136,6 +136,7 @@ db_init() { --env="RANGER_DB_USER_NAME=${db_user}" \ --env="RANGER_DB_PASSWORD=${db_password}" \ --env="JWT_SECRET=deadbeef" \ + --env="APP_KEY=base64:ko1L+Qbmd3ygR7OXPwy8h8TPa95hONlaAkFAbEO3mL0=" \ --link="${db_container_name}" \ "${api_image_name}" \ php artisan migrate:fresh --seed --force; @@ -176,6 +177,7 @@ start_api_container() { --env="RANGER_DB_USER_NAME=${db_user}" \ --env="RANGER_DB_PASSWORD=${db_password}" \ --env="JWT_SECRET=deadbeef" \ + --env="APP_KEY=base64:ko1L+Qbmd3ygR7OXPwy8h8TPa95hONlaAkFAbEO3mL0=" \ --link="${db_container_name}" \ --publish="${host_api_port}:80" \ "${api_image_name}" \