diff --git a/composer.json b/composer.json index 2154314..65e7ca2 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "fleetbase/core-api", - "version": "1.5.14", + "version": "1.5.15", "description": "Core Framework and Resources for Fleetbase API", "keywords": [ "fleetbase", diff --git a/migrations/2024_10_17_075756_add_access_token_id_to_log_tables.php b/migrations/2024_10_17_075756_add_access_token_id_to_log_tables.php new file mode 100644 index 0000000..225a8f5 --- /dev/null +++ b/migrations/2024_10_17_075756_add_access_token_id_to_log_tables.php @@ -0,0 +1,54 @@ +foreignId('access_token_id')->nullable()->after('api_credential_uuid')->references(['id'])->on('personal_access_tokens')->onUpdate('CASCADE')->onDelete('CASCADE'); + }); + + Schema::table('webhook_request_logs', function (Blueprint $table) { + $table->foreignId('access_token_id')->nullable()->after('api_credential_uuid')->references(['id'])->on('personal_access_tokens')->onUpdate('CASCADE')->onDelete('CASCADE'); + }); + + Schema::table('api_request_logs', function (Blueprint $table) { + $table->foreignId('access_token_id')->nullable()->after('api_credential_uuid')->references(['id'])->on('personal_access_tokens')->onUpdate('CASCADE')->onDelete('CASCADE'); + }); + + Schema::enableForeignKeyConstraints(); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::disableForeignKeyConstraints(); + + Schema::table('api_events', function (Blueprint $table) { + $table->dropForeign(['access_token_id']); + $table->dropColumn('access_token_id'); + }); + + Schema::table('webhook_request_logs', function (Blueprint $table) { + $table->dropForeign(['access_token_id']); + $table->dropColumn('access_token_id'); + }); + + Schema::table('api_request_logs', function (Blueprint $table) { + $table->dropForeign(['access_token_id']); + $table->dropColumn('access_token_id'); + }); + + Schema::enableForeignKeyConstraints(); + } +}; diff --git a/src/Auth/Schemas/IAM.php b/src/Auth/Schemas/IAM.php index d8f9a56..a998868 100644 --- a/src/Auth/Schemas/IAM.php +++ b/src/Auth/Schemas/IAM.php @@ -34,7 +34,7 @@ class IAM ], [ 'name' => 'user', - 'actions' => ['deactivate', 'activate', 'verify', 'export'], + 'actions' => ['deactivate', 'activate', 'verify', 'export', 'change-password-for'], ], [ 'name' => 'role', diff --git a/src/Console/Commands/Recovery.php b/src/Console/Commands/Recovery.php index 25ea592..7d63d37 100644 --- a/src/Console/Commands/Recovery.php +++ b/src/Console/Commands/Recovery.php @@ -2,11 +2,13 @@ namespace Fleetbase\Console\Commands; +use Fleetbase\Mail\UserCredentialsMail; use Fleetbase\Models\Company; use Fleetbase\Models\Role; use Fleetbase\Models\User; use Illuminate\Console\Command; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Mail; use Illuminate\Support\Str; /** @@ -73,6 +75,8 @@ public function handle() 'Set Role for User', 'Assign User to Company', 'Assign Owner to Company', + 'Reset User Password', + 'Set User as System Admin', ]; $action = $this->choice('Which recovery option would you like to perform?', $actions); @@ -141,7 +145,7 @@ public function setRoleForUser(?User $user = null, ?Company $company = null) } $roleName = $this->anticipate('Input the role you wish to set for this user', function ($input) { - $results = Role::where(DB::raw('lower(name)'), 'like', '%' . str_replace('.', '%', str_replace(',', '%', $input)) . '%')->get(); + $results = Role::where(DB::raw('lower(name)'), 'like', '%' . str_replace('.', '%', str_replace(',', '%', $input)) . '%')->whereNull('company_uuid')->get(); return $results->map(function ($role) { return $role->name; @@ -161,6 +165,105 @@ public function setRoleForUser(?User $user = null, ?Company $company = null) $this->info('Done'); } + /** + * Promotes a user to a system administrator. + * + * This method assigns system administrator privileges to the specified user, granting them full access + * to the system, including sensitive configurations and secrets. If no user is provided, the method + * will prompt the administrator to select a user. A warning is displayed to inform about the implications + * of this action, and confirmation is required before proceeding. + * + * @param User|null $user The user to be promoted to system administrator. If null, the method will prompt for a user. + * + * @return void + * + * @throws \Exception if an error occurs while setting the user type + */ + public function setUserAsSystemAdmin(?User $user = null) + { + $user = $user ? $user : $this->promptForUser(); + if (!$user) { + return $this->error('No user selected or found to make system admin.'); + } + + // User name output + $usernameOutput = $user->name . ' (' . $user->email . ')'; + + $this->warn('WARNING: By making a user a system administrator they will gain complete system access rights, including sensitive configurations and secrets. Run this command at your own risk.'); + $confirm = $this->confirm('Are you sure you want to make ' . $usernameOutput . ' a system administrator?'); + + if ($confirm) { + try { + $user->setType('admin'); + $this->info('User ' . $usernameOutput . ' is now a system administrator.'); + } catch (\Throwable $e) { + $this->error($e->getMessage()); + } + } + + $this->info('Done'); + } + + /** + * Resets the password of a specified user. + * + * This method allows an administrator to reset a user's password. If no user is provided, the method + * will prompt to select one. It ensures that the new password is confirmed correctly and provides + * options to retry the reset if the passwords do not match. Additionally, it offers the option to + * send the new password to the user's email address. + * + * @param User|null $user The user whose password is to be reset. If null, the method will prompt for a user. + * + * @return void + * + * @throws \Exception if an error occurs while changing the user's password or sending the email + */ + public function resetUserPassword(?User $user = null) + { + $user = $user ? $user : $this->promptForUser(); + if (!$user) { + return $this->error('No user selected or found to reset password for.'); + } + + // User name output + $usernameOutput = $user->name . ' (' . $user->email . ')'; + + // Inform + $this->info('Running password reset for user ' . $usernameOutput); + + // Prompt for user password + $password = $this->secret('Enter the a new password'); + $confirmPassword = $this->secret('Confirm the new password'); + + // Validate + if ($password !== $confirmPassword) { + $this->error('Passwords do not match.'); + $retry = $this->confirm('Would you like to continue password reset for the user ' . $usernameOutput . '?'); + if ($retry) { + return $this->resetUserPassword($user); + } + + return; + } + + $confirm = $this->confirm('Are you sure you want to reset the password'); + $sendUserPassword = $this->confirm('Would you also like to send the users new password to their email (' . $user->email . ')?'); + + if ($confirm) { + try { + $user->changePassword($password); + if ($sendUserPassword) { + Mail::to($user)->send(new UserCredentialsMail($password, $user)); + } + $this->info('User ' . $usernameOutput . ' password was changed.'); + } catch (\Throwable $e) { + $this->error($e->getMessage()); + } + } + + $this->info('Done'); + } + /** * Assigns a user to a company with a specified role. * @@ -203,7 +306,7 @@ public function assignUserToCompany(?User $user = null, ?Company $company = null } $roleName = $this->anticipate('Input the role you wish to set for this user', function ($input) { - $results = Role::where(DB::raw('lower(name)'), 'like', '%' . str_replace('.', '%', str_replace(',', '%', $input)) . '%')->get(); + $results = Role::where(DB::raw('lower(name)'), 'like', '%' . str_replace('.', '%', str_replace(',', '%', $input)) . '%')->whereNull('company_uuid')->get(); return $results->map(function ($role) { return $role->name; diff --git a/src/Events/ResourceLifecycleEvent.php b/src/Events/ResourceLifecycleEvent.php index 3331687..23354fb 100644 --- a/src/Events/ResourceLifecycleEvent.php +++ b/src/Events/ResourceLifecycleEvent.php @@ -197,7 +197,7 @@ protected function addRelationshipSpecificChannels($model, &$channels) { $relationships = ['driverAssigned', 'customer', 'facilitator', 'vendor']; foreach ($relationships as $relationship) { - if ($model && isset($model->{Str::slug($relationship) . '_uuid'})) { + if ($model && isset($model->{Str::snake($relationship) . '_uuid'})) { $channels[] = new Channel($relationship . '.' . $model->{$relationship . '_uuid'}); } if ($model && isset($model->{$relationship})) { diff --git a/src/Http/Controllers/Internal/v1/AuthController.php b/src/Http/Controllers/Internal/v1/AuthController.php index d519bb2..9d4b460 100644 --- a/src/Http/Controllers/Internal/v1/AuthController.php +++ b/src/Http/Controllers/Internal/v1/AuthController.php @@ -11,6 +11,7 @@ use Fleetbase\Http\Requests\SignUpRequest; use Fleetbase\Http\Requests\SwitchOrganizationRequest; use Fleetbase\Http\Resources\Organization; +use Fleetbase\Mail\UserCredentialsMail; use Fleetbase\Models\Company; use Fleetbase\Models\CompanyUser; use Fleetbase\Models\Invite; @@ -23,6 +24,7 @@ use Fleetbase\Twilio\Support\Laravel\Facade as Twilio; use Illuminate\Http\Request; use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Redis; use Illuminate\Support\Str; use Laravel\Sanctum\PersonalAccessToken; @@ -625,4 +627,53 @@ public function services() return response()->json(array_unique($services)); } + + /** + * Change a user password. + * + * @return \Illuminate\Http\Response + */ + public function changeUserPassword(Request $request) + { + $user = Auth::getUserFromSession($request); + if (!$user) { + return response()->error('Not authorized to change user password.', 401); + } + + $canChangePassword = $user->isAdmin() || $user->hasRole('Administrator') || $user->hasPermissionTo('iam change-password-for user'); + if (!$canChangePassword) { + return response()->error('Not authorized to change user password.', 401); + } + + // Get request input + $userId = $request->input('user'); + $password = $request->input('password'); + $confirmPassword = $request->input('password_confirmation'); + $sendCredentials = $request->boolean('send_credentials'); + + if (!$userId) { + return response()->error('No user specified to change password for.'); + } + + if ($password !== $confirmPassword) { + return response()->error('Passwords do not match.'); + } + + $targetUser = User::where('uuid', $userId)->whereHas('anyCompanyUser', function ($query) { + $query->where('company_uuid', session('company')); + })->first(); + if (!$targetUser) { + return response()->error('User not found to change password for.'); + } + + // Change password + $targetUser->changePassword($password); + + // Send credentials to customer if opted + if ($sendCredentials) { + Mail::to($targetUser)->send(new UserCredentialsMail($password, $targetUser)); + } + + return response()->json(['status' => 'ok']); + } } diff --git a/src/Http/Controllers/Internal/v1/RoleController.php b/src/Http/Controllers/Internal/v1/RoleController.php index b405307..57b4b6b 100644 --- a/src/Http/Controllers/Internal/v1/RoleController.php +++ b/src/Http/Controllers/Internal/v1/RoleController.php @@ -7,6 +7,7 @@ use Fleetbase\Models\Permission; use Fleetbase\Models\Policy; use Illuminate\Http\Request; +use Illuminate\Support\Str; class RoleController extends FleetbaseController { @@ -31,6 +32,12 @@ class RoleController extends FleetbaseController */ public function createRecord(Request $request) { + // Disable ability to create any Administrator role + $roleName = strtolower($request->input('role.name', '')); + if ($roleName === 'administrator' || Str::startsWith($roleName, 'admin')) { + return response()->error('Creating a role with name "Administrator" or a role name that starts with "Admin" is prohibited, as the name is system reserved.'); + } + try { $record = $this->model->createRecordFromRequest($request, null, function ($request, &$role) { // Sync Permissions diff --git a/src/Http/Middleware/SetupFleetbaseSession.php b/src/Http/Middleware/SetupFleetbaseSession.php index e022918..e75b740 100644 --- a/src/Http/Middleware/SetupFleetbaseSession.php +++ b/src/Http/Middleware/SetupFleetbaseSession.php @@ -3,6 +3,7 @@ namespace Fleetbase\Http\Middleware; use Fleetbase\Support\Auth; +use Laravel\Sanctum\PersonalAccessToken; class SetupFleetbaseSession { @@ -13,9 +14,17 @@ class SetupFleetbaseSession */ public function handle($request, \Closure $next) { - Auth::setSession($request->user()); + $user = $request->user(); + Auth::setSession($user); Auth::setSandboxSession($request); + if (method_exists($user, 'currentAccessToken')) { + $personalAccessToken = $user->currentAccessToken(); + if ($personalAccessToken && $personalAccessToken instanceof PersonalAccessToken) { + Auth::setApiKey($personalAccessToken); + } + } + return $next($request); } } diff --git a/src/Jobs/LogApiRequest.php b/src/Jobs/LogApiRequest.php index c1cff22..1e14fdb 100644 --- a/src/Jobs/LogApiRequest.php +++ b/src/Jobs/LogApiRequest.php @@ -15,6 +15,7 @@ use Illuminate\Queue\SerializesModels; use Illuminate\Support\Arr; use Illuminate\Support\Str; +use Laravel\Sanctum\PersonalAccessToken; use Spatie\ResponseCache\Facades\ResponseCache; class LogApiRequest implements ShouldQueue @@ -81,11 +82,17 @@ public static function getPayload(Request $request, $response): array $related[] = Utils::get($content, 'id'); } + // Get api credential from session + $apiCredential = session('api_credential'); + // Validate api credential, if not uuid then it could be internal - if (ApiCredential::where('uuid', session('api_credential'))->exists()) { - // Need to add a `api_credentail_type` field and morph -- in later versions - // As could be `PersonalAccessToken` `ApiCredential` and eventually `NavigatorAppToken` - $payload['api_credential_uuid'] = session('api_credential'); + if ($apiCredential && Str::isUuid($apiCredential) && ApiCredential::where('uuid', session('api_credential'))->exists()) { + $payload['api_credential_uuid'] = $apiCredential; + } + + // Check if it was a personal access token which made the request + if ($apiCredential && PersonalAccessToken::where('id', $apiCredential)->exists()) { + $payload['access_token_id'] = $apiCredential; } // Get request duration diff --git a/src/Listeners/LogFailedWebhook.php b/src/Listeners/LogFailedWebhook.php index dc51e35..b165826 100644 --- a/src/Listeners/LogFailedWebhook.php +++ b/src/Listeners/LogFailedWebhook.php @@ -2,9 +2,11 @@ namespace Fleetbase\Listeners; +use Fleetbase\Models\ApiCredential; use Fleetbase\Models\WebhookRequestLog; use Fleetbase\Webhook\Events\WebhookCallFailedEvent; use Illuminate\Support\Str; +use Laravel\Sanctum\PersonalAccessToken; class LogFailedWebhook { @@ -27,11 +29,13 @@ public function handle(WebhookCallFailedEvent $event) // Get API credential $apiCredentialUuid = data_get($event, 'meta.api_credential_uuid'); + // Get API Access Token + $accessTokenId = data_get($event, 'meta.access_token_id'); + // Prepare insert array $data = [ '_key' => data_get($event, 'meta.api_key'), 'company_uuid' => data_get($event, 'meta.company_uuid'), - 'api_credential_uuid' => data_get($event, 'meta.api_credential_uuid'), 'webhook_uuid' => data_get($event, 'meta.webhook_uuid'), 'api_event_uuid' => data_get($event, 'meta.api_event_uuid'), 'method' => $event->httpVerb, @@ -47,11 +51,16 @@ public function handle(WebhookCallFailedEvent $event) 'sent_at' => data_get($event, 'meta.sent_at'), ]; - // Set api credential uuid - if ($apiCredentialUuid && Str::isUuid($apiCredentialUuid)) { + // Validate api credential, if not uuid then it could be internal + if ($apiCredentialUuid && Str::isUuid($apiCredentialUuid) && ApiCredential::where('uuid', $apiCredentialUuid)->exists()) { $data['api_credential_uuid'] = $apiCredentialUuid; } + // Check if it was a personal access token which made the request + if ($accessTokenId && PersonalAccessToken::where('id', $accessTokenId)->exists()) { + $data['access_token_id'] = $accessTokenId; + } + // Log webhook callback event WebhookRequestLog::on($connection)->create($data); } diff --git a/src/Listeners/LogFinalWebhookAttempt.php b/src/Listeners/LogFinalWebhookAttempt.php index 6a3e51f..3ac6965 100644 --- a/src/Listeners/LogFinalWebhookAttempt.php +++ b/src/Listeners/LogFinalWebhookAttempt.php @@ -2,9 +2,11 @@ namespace Fleetbase\Listeners; +use Fleetbase\Models\ApiCredential; use Fleetbase\Models\WebhookRequestLog; use Fleetbase\Webhook\Events\FinalWebhookCallFailedEvent; use Illuminate\Support\Str; +use Laravel\Sanctum\PersonalAccessToken; class LogFinalWebhookAttempt { @@ -29,6 +31,9 @@ public function handle(FinalWebhookCallFailedEvent $event) // Get API credential $apiCredentialUuid = data_get($event, 'meta.api_credential_uuid'); + // Get API Access Token + $accessTokenId = data_get($event, 'meta.access_token_id'); + // Prepare insert array $data = [ '_key' => data_get($event, 'meta.api_key'), @@ -48,11 +53,16 @@ public function handle(FinalWebhookCallFailedEvent $event) 'sent_at' => data_get($event, 'meta.sent_at'), ]; - // Set api credential uuid - if ($apiCredentialUuid && Str::isUuid($apiCredentialUuid)) { + // Validate api credential, if not uuid then it could be internal + if ($apiCredentialUuid && Str::isUuid($apiCredentialUuid) && ApiCredential::where('uuid', $apiCredentialUuid)->exists()) { $data['api_credential_uuid'] = $apiCredentialUuid; } + // Check if it was a personal access token which made the request + if ($accessTokenId && PersonalAccessToken::where('id', $accessTokenId)->exists()) { + $data['access_token_id'] = $accessTokenId; + } + // log webhook event WebhookRequestLog::on($connection)->create($data); } diff --git a/src/Listeners/LogSuccessfulWebhook.php b/src/Listeners/LogSuccessfulWebhook.php index 0183fc7..67406d7 100644 --- a/src/Listeners/LogSuccessfulWebhook.php +++ b/src/Listeners/LogSuccessfulWebhook.php @@ -2,9 +2,11 @@ namespace Fleetbase\Listeners; +use Fleetbase\Models\ApiCredential; use Fleetbase\Models\WebhookRequestLog; use Fleetbase\Webhook\Events\WebhookCallSucceededEvent; use Illuminate\Support\Str; +use Laravel\Sanctum\PersonalAccessToken; class LogSuccessfulWebhook { @@ -27,6 +29,9 @@ public function handle(WebhookCallSucceededEvent $event) // Get API credential $apiCredentialUuid = data_get($event, 'meta.api_credential_uuid'); + // Get API Access Token + $accessTokenId = data_get($event, 'meta.access_token_id'); + // Prepare insert array $data = [ '_key' => data_get($event, 'meta.api_key'), @@ -46,11 +51,16 @@ public function handle(WebhookCallSucceededEvent $event) 'sent_at' => data_get($event, 'meta.sent_at'), ]; - // Set api credential uuid - if ($apiCredentialUuid && Str::isUuid($apiCredentialUuid)) { + // Validate api credential, if not uuid then it could be internal + if ($apiCredentialUuid && Str::isUuid($apiCredentialUuid) && ApiCredential::where('uuid', $apiCredentialUuid)->exists()) { $data['api_credential_uuid'] = $apiCredentialUuid; } + // Check if it was a personal access token which made the request + if ($accessTokenId && PersonalAccessToken::where('id', $accessTokenId)->exists()) { + $data['access_token_id'] = $accessTokenId; + } + // Log webhook callback event WebhookRequestLog::on($connection)->create($data); } diff --git a/src/Listeners/SendResourceLifecycleWebhook.php b/src/Listeners/SendResourceLifecycleWebhook.php index 0f53baf..f9e4e8d 100644 --- a/src/Listeners/SendResourceLifecycleWebhook.php +++ b/src/Listeners/SendResourceLifecycleWebhook.php @@ -3,6 +3,7 @@ namespace Fleetbase\Listeners; use Fleetbase\Events\ResourceLifecycleEvent; +use Fleetbase\Models\ApiCredential; use Fleetbase\Models\ApiEvent; use Fleetbase\Models\User; use Fleetbase\Models\WebhookEndpoint; @@ -12,6 +13,8 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Database\QueryException; use Illuminate\Support\Carbon; +use Illuminate\Support\Str; +use Laravel\Sanctum\PersonalAccessToken; class SendResourceLifecycleWebhook implements ShouldQueue { @@ -34,17 +37,29 @@ public function handle($event) $apiEnvironment = session()->get('api_environment', $event->apiEnvironment ?? 'live'); $isSandbox = session()->get('is_sandbox', $event->isSandbox); + // Prepare event + $eventData = [ + 'company_uuid' => $companyId, + 'event' => $event->broadcastAs(), + 'source' => $apiCredentialId ? 'api' : 'console', + 'data' => $event->getEventData(), + 'method' => $event->requestMethod, + 'description' => $this->getHumanReadableEventDescription($event), + ]; + + // Validate api credential, if not uuid then it could be internal + if (session('api_credential') && ApiCredential::where('uuid', session('api_credential'))->exists()) { + $eventData['api_credential_uuid'] = session('api_credential'); + } + + // Check if it was a personal access token which made the request + if (session('api_credential') && PersonalAccessToken::where('id', session('api_credential'))->exists()) { + $eventData['access_token_id'] = session('api_credential'); + } + try { // log the api event - $apiEvent = ApiEvent::create([ - 'company_uuid' => $companyId, - 'api_credential_uuid' => $apiCredentialId, - 'event' => $event->broadcastAs(), - 'source' => $apiCredentialId ? 'api' : 'console', - 'data' => $event->getEventData(), - 'method' => $event->requestMethod, - 'description' => $this->getHumanReadableEventDescription($event), - ]); + $apiEvent = ApiEvent::create($eventData); } catch (QueryException $e) { return; } @@ -72,7 +87,8 @@ public function handle($event) ->meta([ 'is_sandbox' => $isSandbox, 'api_key' => $apiKey, - 'api_credential_uuid' => $apiCredentialId, + 'api_credential_uuid' => data_get($apiEvent, 'api_credential_uuid'), + 'access_token_id' => data_get($apiEvent, 'access_token_id'), 'company_uuid' => $webhook->company_uuid, 'api_event_uuid' => $apiEvent->uuid, 'webhook_uuid' => $webhook->uuid, @@ -87,11 +103,10 @@ public function handle($event) $response = $exception->getResponse(); $request = $exception->getRequest(); - // log webhook error in logs - WebhookRequestLog::on($connection)->create([ + // Prepare log data + $webhookRequestLogData = [ 'company_uuid' => $webhook->company_uuid, 'webhook_uuid' => $webhook->uuid, - 'api_credential_uuid' => $apiCredentialId, 'api_event_uuid' => $apiEvent->uuid, 'method' => $request->getMethod(), 'status_code' => $exception->getStatusCode(), @@ -106,7 +121,20 @@ public function handle($event) 'exception' => get_class($exception), ], 'sent_at' => $durationStart, - ]); + ]; + + // Validate api credential, if not uuid then it could be internal + if (session('api_credential') && Str::isUuid(session('api_credential')) && ApiCredential::where('uuid', session('api_credential'))->exists()) { + $webhookRequestLogData['api_credential_uuid'] = session('api_credential'); + } + + // Check if it was a personal access token which made the request + if (session('api_credential') && PersonalAccessToken::where('id', session('api_credential'))->exists()) { + $webhookRequestLogData['access_token_id'] = session('api_credential'); + } + + // log webhook error in logs + WebhookRequestLog::on($connection)->create($webhookRequestLogData); } } } diff --git a/src/Mail/UserCredentialsMail.php b/src/Mail/UserCredentialsMail.php new file mode 100644 index 0000000..b1d1ee8 --- /dev/null +++ b/src/Mail/UserCredentialsMail.php @@ -0,0 +1,64 @@ +plaintextPassword = $plaintextPassword; + $this->user = $user; + } + + /** + * Get the message content definition. + */ + public function envelope(): Envelope + { + $this->user->loadMissing('company'); + + return new Envelope( + subject: 'Your login credentials for ' . $this->user->company_name . ' on ' . config('app.name'), + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + return new Content( + markdown: 'fleetbase::mail.user-credentials', + with: [ + 'user' => $this->user, + 'plaintextPassword' => $this->plaintextPassword, + 'currentHour' => now()->hour, + ] + ); + } +} diff --git a/src/Models/ApiEvent.php b/src/Models/ApiEvent.php index 98c736c..260820a 100644 --- a/src/Models/ApiEvent.php +++ b/src/Models/ApiEvent.php @@ -43,7 +43,7 @@ class ApiEvent extends Model * * @var array */ - protected $fillable = ['_key', 'company_uuid', 'api_credential_uuid', 'event', 'source', 'data', 'description', 'method']; + protected $fillable = ['_key', 'company_uuid', 'api_credential_uuid', 'access_token_id', 'event', 'source', 'data', 'description', 'method']; /** * The attributes that should be cast to native types. diff --git a/src/Models/ApiRequestLog.php b/src/Models/ApiRequestLog.php index 84b7719..b2b1b45 100644 --- a/src/Models/ApiRequestLog.php +++ b/src/Models/ApiRequestLog.php @@ -40,6 +40,7 @@ class ApiRequestLog extends Model '_key', 'company_uuid', 'api_credential_uuid', + 'access_token_id', 'public_id', 'method', 'path', diff --git a/src/Models/WebhookRequestLog.php b/src/Models/WebhookRequestLog.php index 7d0ffc8..e5861cd 100644 --- a/src/Models/WebhookRequestLog.php +++ b/src/Models/WebhookRequestLog.php @@ -5,6 +5,7 @@ use Fleetbase\Casts\Json; use Fleetbase\Traits\Filterable; use Fleetbase\Traits\HasApiModelBehavior; +use Fleetbase\Traits\HasPublicId; use Fleetbase\Traits\HasUuid; use Fleetbase\Traits\Searchable; @@ -12,6 +13,7 @@ class WebhookRequestLog extends Model { use HasUuid; use HasApiModelBehavior; + use HasPublicId; use Searchable; use Filterable; @@ -27,7 +29,7 @@ class WebhookRequestLog extends Model * * @var string */ - protected $publicIdType = 'webhook'; + protected $publicIdType = 'webhook_req'; /** * The attributes that are mass assignable. @@ -40,6 +42,7 @@ class WebhookRequestLog extends Model 'company_uuid', 'webhook_uuid', 'api_credential_uuid', + 'access_token_id', 'api_event_uuid', 'method', 'status_code', diff --git a/src/Support/Auth.php b/src/Support/Auth.php index b912d37..e403625 100644 --- a/src/Support/Auth.php +++ b/src/Support/Auth.php @@ -88,11 +88,9 @@ public static function setSession($user = null, $login = false): bool /** * Set session variables for api credentials being used. * - * @param ApiCredential $apiCredential - * * @return bool */ - public static function setApiKey($apiCredential) + public static function setApiKey(ApiCredential|PersonalAccessToken $apiCredential) { // If sanctum token indicate in session if ($apiCredential instanceof PersonalAccessToken) { diff --git a/src/routes.php b/src/routes.php index 4e68cf5..4115471 100644 --- a/src/routes.php +++ b/src/routes.php @@ -140,6 +140,12 @@ function ($router) { $router->group( ['middleware' => ['fleetbase.protected']], function ($router) { + $router->group( + ['prefix' => 'auth'], + function ($router) { + $router->post('change-user-password', 'AuthController@changeUserPassword'); + } + ); $router->fleetbaseRoutes( 'api-credentials', function ($router, $controller) { diff --git a/views/mail/user-credentials.blade.php b/views/mail/user-credentials.blade.php new file mode 100644 index 0000000..2f5a53e --- /dev/null +++ b/views/mail/user-credentials.blade.php @@ -0,0 +1,20 @@ + +

+@if($currentHour < 12) + Good Morning, {{ $user->name }}! +@elseif($currentHour < 18) + Good Afternoon, {{ $user->name }}! +@else + Good Evening, {{ $user->name }}! +@endif +

+ +Your login credentials: +
+
+Your Email: {{ $user->email }} +
+Your Password: {{ $plaintextPassword }} +
+Console URL: {{ \Fleetbase\Support\Utils::consoleUrl() }} +