@fleetbase/registry-bridge-engine 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/addon/components/modals/create-registry-credentials.hbs +5 -0
  2. package/addon/components/registry-admin-config.hbs +16 -0
  3. package/addon/components/registry-admin-config.js +36 -0
  4. package/addon/controllers/developers/credentials.js +106 -0
  5. package/addon/engine.js +8 -2
  6. package/addon/routes/developers/credentials.js +8 -1
  7. package/addon/templates/application.hbs +1 -0
  8. package/addon/templates/developers/credentials.hbs +13 -1
  9. package/addon/templates/developers/payments/index.hbs +14 -13
  10. package/addon/templates/installed.hbs +3 -3
  11. package/app/components/modals/create-registry-credentials.js +1 -0
  12. package/app/components/registry-admin-config.js +1 -0
  13. package/app/controllers/developers/credentials.js +1 -0
  14. package/composer.json +2 -2
  15. package/extension.json +1 -1
  16. package/package.json +5 -5
  17. package/server/config/registry-bridge.php +2 -1
  18. package/server/migrations/2024_07_18_151000_add_auth_token_column_to_registry_users_table.php +28 -0
  19. package/server/src/Http/Controllers/Internal/v1/RegistryAuthController.php +163 -24
  20. package/server/src/Http/Controllers/Internal/v1/RegistryExtensionBundleController.php +1 -1
  21. package/server/src/Http/Controllers/Internal/v1/RegistryExtensionController.php +59 -1
  22. package/server/src/Http/Filter/RegistryExtensionFilter.php +1 -1
  23. package/server/src/Http/Requests/RegistryAuthRequest.php +3 -1
  24. package/server/src/Http/Resources/RegistryUser.php +0 -12
  25. package/server/src/Models/RegistryExtension.php +42 -3
  26. package/server/src/Models/RegistryExtensionBundle.php +1 -1
  27. package/server/src/Models/RegistryUser.php +111 -1
  28. package/server/src/Providers/RegistryBridgeServiceProvider.php +16 -31
  29. package/server/src/Support/Bridge.php +123 -0
  30. package/server/src/Support/Utils.php +136 -1
  31. package/server/src/routes.php +45 -31
@@ -3,11 +3,12 @@
3
3
  namespace Fleetbase\RegistryBridge\Http\Controllers\Internal\v1;
4
4
 
5
5
  use Fleetbase\Exceptions\FleetbaseRequestValidationException;
6
+ use Fleetbase\Models\Setting;
6
7
  use Fleetbase\RegistryBridge\Http\Controllers\RegistryBridgeController;
7
8
  use Fleetbase\RegistryBridge\Http\Requests\CreateRegistryExtensionRequest;
8
9
  use Fleetbase\RegistryBridge\Http\Requests\RegistryExtensionActionRequest;
9
10
  use Fleetbase\RegistryBridge\Models\RegistryExtension;
10
- use Fleetbase\Support\Utils;
11
+ use Fleetbase\RegistryBridge\Support\Utils;
11
12
  use Illuminate\Http\Request;
12
13
  use Illuminate\Support\Facades\Storage;
13
14
  use Illuminate\Support\Facades\Validator;
@@ -254,4 +255,61 @@ class RegistryExtensionController extends RegistryBridgeController
254
255
 
255
256
  return response()->error('Failed to download extension bundle');
256
257
  }
258
+
259
+ /**
260
+ * Retrieve the current registry configuration.
261
+ *
262
+ * This method fetches the current registry host and token from the configuration
263
+ * settings or environment variables and returns them in a JSON response.
264
+ *
265
+ * @return \Illuminate\Http\JsonResponse a JSON response containing the registry host and token
266
+ */
267
+ public function getConfig()
268
+ {
269
+ $registryHost = config('registry-bridge.registry.host', env('REGISTRY_HOST', 'https://registry.fleetbase.io'));
270
+ $registryToken = config('registry-bridge.registry.token', env('REGISTRY_TOKEN'));
271
+
272
+ return response()->json([
273
+ 'host' => $registryHost,
274
+ 'token' => $registryToken,
275
+ ]);
276
+ }
277
+
278
+ /**
279
+ * Save the registry configuration.
280
+ *
281
+ * This method updates the registry host and token based on the provided request input.
282
+ * If no input is provided, it uses the current configuration values or environment variables.
283
+ * The updated configuration is then saved in the settings and returned in a JSON response.
284
+ *
285
+ * @param Request $request the incoming HTTP request containing the new host and token
286
+ *
287
+ * @return \Illuminate\Http\JsonResponse a JSON response containing the updated registry host and token
288
+ */
289
+ public function saveConfig(Request $request)
290
+ {
291
+ $currentRegistryHost = config('registry-bridge.registry.host', env('REGISTRY_HOST', 'https://registry.fleetbase.io'));
292
+ $currentRegistryToken = config('registry-bridge.registry.token', env('REGISTRY_TOKEN'));
293
+ $registryHost = $request->input('host', $currentRegistryHost);
294
+ $registryToken = $request->input('token', $currentRegistryToken);
295
+
296
+ // Save values in settings and config
297
+ if ($registryHost) {
298
+ Setting::configure('registry-bridge.registry.host', $registryHost);
299
+ config(['registry-bridge.registry.host' => $registryHost]);
300
+ }
301
+
302
+ if ($registryToken) {
303
+ Setting::configure('registry-bridge.registry.token', $registryToken);
304
+ config(['registry-bridge.registry.token' => $registryToken]);
305
+ }
306
+
307
+ // Reboot registry auth
308
+ Utils::bootRegistryAuth(true);
309
+
310
+ return response()->json([
311
+ 'host' => $registryHost,
312
+ 'token' => $registryToken,
313
+ ]);
314
+ }
257
315
  }
@@ -3,7 +3,7 @@
3
3
  namespace Fleetbase\RegistryBridge\Http\Filter;
4
4
 
5
5
  use Fleetbase\Http\Filter\Filter;
6
- use Fleetbase\Support\Utils;
6
+ use Fleetbase\RegistryBridge\Support\Utils;
7
7
  use Illuminate\Support\Str;
8
8
 
9
9
  class RegistryExtensionFilter extends Filter
@@ -5,6 +5,7 @@ namespace Fleetbase\RegistryBridge\Http\Requests;
5
5
  use Fleetbase\Http\Requests\FleetbaseRequest;
6
6
  use Fleetbase\Models\User;
7
7
  use Illuminate\Support\Facades\Validator;
8
+ use Illuminate\Validation\Rule;
8
9
 
9
10
  /**
10
11
  * Request class to handle the addition of new registry users.
@@ -40,7 +41,8 @@ class RegistryAuthRequest extends FleetbaseRequest
40
41
  });
41
42
 
42
43
  return [
43
- 'identity' => ['required', 'valid_identity'],
44
+ 'identity' => [Rule::requiredIf($this->isNotFilled('package')), 'valid_identity'],
45
+ 'package' => ['nullable'],
44
46
  ];
45
47
  }
46
48
  }
@@ -3,7 +3,6 @@
3
3
  namespace Fleetbase\RegistryBridge\Http\Resources;
4
4
 
5
5
  use Fleetbase\Http\Resources\FleetbaseResource;
6
- use Fleetbase\Models\Group;
7
6
 
8
7
  class RegistryUser extends FleetbaseResource
9
8
  {
@@ -26,15 +25,4 @@ class RegistryUser extends FleetbaseResource
26
25
  'created_at' => $this->created_at,
27
26
  ];
28
27
  }
29
-
30
- public function groups(): array
31
- {
32
- return collect(['$all', '$authenticated', ...data_get($this->user, 'groups', [])])->map(function ($group) {
33
- if ($group instanceof Group) {
34
- return $group->public_id;
35
- }
36
-
37
- return $group;
38
- })->toArray();
39
- }
40
28
  }
@@ -10,7 +10,6 @@ use Fleetbase\Models\File;
10
10
  use Fleetbase\Models\Model;
11
11
  use Fleetbase\Models\User;
12
12
  use Fleetbase\RegistryBridge\Support\Utils;
13
- use Fleetbase\Support\Utils as SupportUtils;
14
13
  use Fleetbase\Traits\HasApiModelBehavior;
15
14
  use Fleetbase\Traits\HasMetaAttributes;
16
15
  use Fleetbase\Traits\HasPublicId;
@@ -627,7 +626,7 @@ class RegistryExtension extends Model
627
626
 
628
627
  // Calculate the fee fleetbase takes for faciliation of extension
629
628
  $totalAmount = $price->unit_amount;
630
- $facilitatorFee = SupportUtils::calculatePercentage(config('registry-bridge.facilitator_fee', 10), $totalAmount);
629
+ $facilitatorFee = Utils::calculatePercentage(config('registry-bridge.facilitator_fee', 10), $totalAmount);
631
630
 
632
631
  // Get the stripe client to create the checkout session
633
632
  $stripe = Utils::getStripeClient();
@@ -642,7 +641,7 @@ class RegistryExtension extends Model
642
641
  ],
643
642
  ],
644
643
  'mode' => 'payment',
645
- 'return_url' => SupportUtils::consoleUrl($returnUri) . '?extension_id=' . $this->uuid . '&checkout_session_id={CHECKOUT_SESSION_ID}',
644
+ 'return_url' => Utils::consoleUrl($returnUri) . '?extension_id=' . $this->uuid . '&checkout_session_id={CHECKOUT_SESSION_ID}',
646
645
  'payment_intent_data' => [
647
646
  'application_fee_amount' => $facilitatorFee,
648
647
  'transfer_data' => [
@@ -653,4 +652,44 @@ class RegistryExtension extends Model
653
652
 
654
653
  return $checkoutSession;
655
654
  }
655
+
656
+ /**
657
+ * Determine if the registry user has access to the registry extension.
658
+ *
659
+ * This method checks if the extension requires payment. If it does not,
660
+ * access is granted. If payment is required, it checks if the user's company
661
+ * has made a purchase of the extension.
662
+ *
663
+ * @param RegistryUser $registryUser the registry user to check access for
664
+ *
665
+ * @return bool true if the user has access, false otherwise
666
+ */
667
+ public function hasAccess(RegistryUser $registryUser): bool
668
+ {
669
+ if (!$this->payment_required) {
670
+ return true;
671
+ }
672
+
673
+ return $this->purchases()->where('company_uuid', $registryUser->company_uuid)->exists();
674
+ }
675
+
676
+ /**
677
+ * Determine if the registry user does not have access to the registry extension.
678
+ *
679
+ * This method checks if the extension requires payment. If it does not,
680
+ * access is always denied. If payment is required, it checks if the user's
681
+ * company has not made a purchase of the extension.
682
+ *
683
+ * @param RegistryUser $registryUser the registry user to check access for
684
+ *
685
+ * @return bool true if the user does not have access, false otherwise
686
+ */
687
+ public function doesntHaveAccess(RegistryUser $registryUser): bool
688
+ {
689
+ if (!$this->payment_required) {
690
+ return false;
691
+ }
692
+
693
+ return $this->purchases()->where('company_uuid', $registryUser->company_uuid)->doesntExist();
694
+ }
656
695
  }
@@ -8,8 +8,8 @@ use Fleetbase\Models\File;
8
8
  use Fleetbase\Models\Model;
9
9
  use Fleetbase\Models\User;
10
10
  use Fleetbase\RegistryBridge\Exceptions\InstallFailedException;
11
+ use Fleetbase\RegistryBridge\Support\Utils;
11
12
  use Fleetbase\Support\SocketCluster\SocketClusterService;
12
- use Fleetbase\Support\Utils;
13
13
  use Fleetbase\Traits\HasApiModelBehavior;
14
14
  use Fleetbase\Traits\HasMetaAttributes;
15
15
  use Fleetbase\Traits\HasPublicId;
@@ -41,6 +41,7 @@ class RegistryUser extends Model
41
41
  'company_uuid',
42
42
  'user_uuid',
43
43
  'token',
44
+ 'registry_token',
44
45
  'scope',
45
46
  'expires_at',
46
47
  'last_used_at',
@@ -65,7 +66,7 @@ class RegistryUser extends Model
65
66
  *
66
67
  * @var array
67
68
  */
68
- protected $appends = [];
69
+ protected $appends = ['is_admin'];
69
70
 
70
71
  /**
71
72
  * The attributes excluded from the model's JSON form.
@@ -114,6 +115,54 @@ class RegistryUser extends Model
114
115
  return $this->belongsTo(User::class);
115
116
  }
116
117
 
118
+ /**
119
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
120
+ */
121
+ public function extensions()
122
+ {
123
+ return $this->hasMany(RegistryExtension::class, 'company_uuid', 'company_uuid');
124
+ }
125
+
126
+ /**
127
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
128
+ */
129
+ public function purchases()
130
+ {
131
+ return $this->hasMany(RegistryExtensionPurchase::class, 'company_uuid', 'company_uuid');
132
+ }
133
+
134
+ /**
135
+ * Undocumented function.
136
+ */
137
+ public function getIsAdminAttribute(): bool
138
+ {
139
+ return $this->user->is_admin === true;
140
+ }
141
+
142
+ /**
143
+ * Determine if the user is an admin.
144
+ *
145
+ * This method checks if the `is_admin` attribute of the user is set to true.
146
+ *
147
+ * @return bool true if the user is an admin, false otherwise
148
+ */
149
+ public function isAdmin(): bool
150
+ {
151
+ return $this->is_admin === true;
152
+ }
153
+
154
+ /**
155
+ * Determine if the user is not an admin.
156
+ *
157
+ * This method checks if the `is_admin` attribute of the user is set to false.
158
+ *
159
+ * @return bool true if the user is not an admin, false otherwise
160
+ */
161
+ public function isNotAdmin(): bool
162
+ {
163
+ return $this->is_admin === false;
164
+ }
165
+
117
166
  /**
118
167
  * Generates a unique token for authenticating with the registry.
119
168
  *
@@ -137,4 +186,65 @@ class RegistryUser extends Model
137
186
 
138
187
  return $token;
139
188
  }
189
+
190
+ /**
191
+ * Find a registry user by their username.
192
+ *
193
+ * This method joins the users table with the registry users table to find a
194
+ * registry user by their email or username. If a matching user is found, the
195
+ * corresponding registry user is returned.
196
+ *
197
+ * @param string $username the username to search for
198
+ *
199
+ * @return RegistryUser|null the found registry user, or null if no user is found
200
+ */
201
+ public static function findFromUsername(string $username): ?RegistryUser
202
+ {
203
+ return static::select('registry_users.*')
204
+ ->join('users', function ($join) use ($username) {
205
+ $join->on('users.uuid', '=', 'registry_users.user_uuid')
206
+ ->on('users.company_uuid', '=', 'registry_users.company_uuid')
207
+ ->where(function ($query) use ($username) {
208
+ $query->where('users.email', $username)
209
+ ->orWhere('users.username', $username);
210
+ });
211
+ })
212
+ ->first();
213
+ }
214
+
215
+ /**
216
+ * Determine if the registry user can access a specific package.
217
+ *
218
+ * This method checks if the package name exists in the list of purchased
219
+ * extensions for the user. It verifies if the package name matches either
220
+ * the `package.json` or `composer.json` name in the metadata of the current bundle
221
+ * of any purchased extension.
222
+ *
223
+ * @param string $packageName the name of the package to check access for
224
+ *
225
+ * @return bool true if the user can access the package, false otherwise
226
+ */
227
+ public function canAccessPackage(string $packageName): bool
228
+ {
229
+ return $this->purchases()->whereHas('extension', function ($query) use ($packageName) {
230
+ $query->whereHas('currentBundle', function ($query) use ($packageName) {
231
+ $query->where('meta->package.json->name', $packageName)->orWhere('meta->composer.json->name', $packageName);
232
+ });
233
+ })->exists();
234
+ }
235
+
236
+ /**
237
+ * Retrieves the user's access groups.
238
+ *
239
+ * This method returns an array of groups that the user belongs to, including
240
+ * the default groups (`$all` and `$authenticated`) and the names of the purchased
241
+ * extension groups. The purchased extension groups are obtained by calling the
242
+ * `getPurchasedExtensionGroups` method.
243
+ *
244
+ * @return array an array of the user's access groups, including default and purchased extension groups
245
+ */
246
+ public function groups(): array
247
+ {
248
+ return [$this->public_id];
249
+ }
140
250
  }
@@ -2,7 +2,9 @@
2
2
 
3
3
  namespace Fleetbase\RegistryBridge\Providers;
4
4
 
5
+ use Fleetbase\Models\Setting;
5
6
  use Fleetbase\Providers\CoreServiceProvider;
7
+ use Fleetbase\RegistryBridge\Support\Utils;
6
8
 
7
9
  if (!class_exists(CoreServiceProvider::class)) {
8
10
  throw new \Exception('Registry Bridge cannot be loaded without `fleetbase/core-api` installed!');
@@ -37,7 +39,6 @@ class RegistryBridgeServiceProvider extends CoreServiceProvider
37
39
  */
38
40
  public $middleware = [
39
41
  'fleetbase.registry' => [
40
- 'throttle:60,1',
41
42
  \Illuminate\Session\Middleware\StartSession::class,
42
43
  \Fleetbase\Http\Middleware\AuthenticateOnceWithBasicAuth::class,
43
44
  \Illuminate\Routing\Middleware\SubstituteBindings::class,
@@ -71,47 +72,31 @@ class RegistryBridgeServiceProvider extends CoreServiceProvider
71
72
  */
72
73
  public function boot()
73
74
  {
74
- static::bootRegistryAuth();
75
+ Utils::bootRegistryAuth();
75
76
  $this->registerCommands();
76
77
  $this->registerMiddleware();
77
78
  $this->registerExpansionsFrom(__DIR__ . '/../Expansions');
78
79
  $this->loadRoutesFrom(__DIR__ . '/../routes.php');
79
80
  $this->loadMigrationsFrom(__DIR__ . '/../../migrations');
80
81
  $this->mergeConfigFrom(__DIR__ . '/../../config/registry-bridge.php', 'registry-bridge');
82
+ $this->mergeConfigFromSettings();
81
83
  }
82
84
 
83
- /**
84
- * Initializes and sets up the npm registry authentication configuration.
85
- *
86
- * This method constructs the registry authentication string from configuration settings,
87
- * checks for the existence of an npmrc file in the user's home directory, and creates it
88
- * with the registry authentication string if it doesn't already exist.
89
- *
90
- * The registry configuration and token are pulled from the application's configuration files.
91
- * It ensures the path to the .npmrc file is correctly formed regardless of trailing slashes
92
- * in the HOME directory path or the registry host configuration.
93
- *
94
- * @param bool $reset - Overwrites existing file, "resetting" the .npmrc
95
- *
96
- * @return void
97
- */
98
- public static function bootRegistryAuth(bool $reset = false)
85
+ public function mergeConfigFromSettings()
99
86
  {
100
- $homeDirectory = rtrim(getenv('HOME'), '/');
101
- $authPath = $homeDirectory . '/.npmrc';
102
- $authString = '//' . str_replace(['http://', 'https://'], '', rtrim(config('registry-bridge.registry.host'), '/')) . '/:_authToken="' . config('registry-bridge.registry.token') . '"' . PHP_EOL;
103
- if (!file_exists($authPath) || $reset === true) {
104
- file_put_contents($authPath, $authString);
87
+ if (Setting::doesntHaveConnection()) {
88
+ return;
89
+ }
90
+
91
+ $registryHost = Setting::getByKey('registry-bridge.registry.host');
92
+ $registryToken = Setting::getByKey('registry-bridge.registry.token');
93
+
94
+ if ($registryHost) {
95
+ config(['registry-bridge.registry.host' => $registryHost->value]);
105
96
  }
106
97
 
107
- $consolePath = rtrim(config('fleetbase.console.path'), '/');
108
- $registryPath = $consolePath . '/.npmrc';
109
- $registryString = implode(PHP_EOL, [
110
- 'registry=https://registry.npmjs.org/',
111
- '@fleetbase:registry=' . rtrim(config('registry-bridge.registry.host'), '/') . '/',
112
- ]) . PHP_EOL;
113
- if (!file_exists($registryPath) || $reset === true) {
114
- file_put_contents($registryPath, $registryString);
98
+ if ($registryToken) {
99
+ config(['registry-bridge.registry.token' => $registryToken->value]);
115
100
  }
116
101
  }
117
102
  }
@@ -2,7 +2,14 @@
2
2
 
3
3
  namespace Fleetbase\RegistryBridge\Support;
4
4
 
5
+ use Fleetbase\Models\User;
6
+ use Fleetbase\RegistryBridge\Models\RegistryUser;
7
+ use Fleetbase\Support\Auth;
5
8
  use Illuminate\Support\Facades\Http;
9
+ use Illuminate\Support\Facades\Storage;
10
+ use Illuminate\Support\Str;
11
+ use Symfony\Component\Process\Exception\ProcessFailedException;
12
+ use Symfony\Component\Process\Process;
6
13
 
7
14
  class Bridge
8
15
  {
@@ -50,4 +57,120 @@ class Bridge
50
57
  {
51
58
  return Http::withOptions($options)->post(static::createUrl($uri), $parameters);
52
59
  }
60
+
61
+ /**
62
+ * Logs in to the npm registry using the provided user and retrieves the authentication token.
63
+ *
64
+ * This method uses the npm-cli-login tool to authenticate with the npm registry using the
65
+ * provided user's credentials, retrieves the authentication token from the .npmrc file,
66
+ * and associates it with the currently authenticated user in the application. The registry
67
+ * token is stored in the database for the user's current session.
68
+ *
69
+ * @param User $user the fleetbase user model containing the username and email
70
+ * @param string $password the npm password for the user
71
+ *
72
+ * @return RegistryUser the RegistryUser model containing the registry token and associated data
73
+ *
74
+ * @throws \Exception If there is no active session, the .npmrc file is not found, the auth token is not found, or the npm login fails.
75
+ */
76
+ public static function loginWithUser(User $user, string $password): RegistryUser
77
+ {
78
+ return static::login($user->username, $password, $user->email);
79
+ }
80
+
81
+ /**
82
+ * Logs in to the fleetbase registry and retrieves the authentication token.
83
+ *
84
+ * This method uses the npm-cli-login tool to authenticate with the fleetbase registry,
85
+ * retrieves the authentication token from the .npmrc file, and associates it with
86
+ * the currently authenticated user in the application. The registry token is stored
87
+ * in the database for the user's current session.
88
+ *
89
+ * @param string $username the npm username
90
+ * @param string $password the npm password
91
+ * @param string $email the npm email
92
+ *
93
+ * @return RegistryUser the RegistryUser model containing the registry token and associated data
94
+ *
95
+ * @throws \Exception If there is no active session, the .npmrc file is not found, the auth token is not found, or the npm login fails.
96
+ */
97
+ public static function login(string $username, string $password, string $email): RegistryUser
98
+ {
99
+ // Session is required
100
+ if (session()->missing(['company', 'user'])) {
101
+ throw new \Exception('No active session to create registry token for.');
102
+ }
103
+
104
+ // Get registry
105
+ $registry = static::createUrl();
106
+
107
+ // Set .npmrc path
108
+ $npmrcPath = 'tmp/.npmrc-' . session('user');
109
+
110
+ // Create .npmrc file
111
+ Storage::disk('local')->put($npmrcPath, '');
112
+
113
+ // Prepare command
114
+ $process = new Process([
115
+ 'npm-cli-login',
116
+ '-u', $username,
117
+ '-p', $password,
118
+ '-e', $email,
119
+ '-r', $registry,
120
+ '-s', 'false',
121
+ '--config-path', storage_path('app/' . $npmrcPath),
122
+ ]);
123
+
124
+ // Set timeout
125
+ $process->setTimeout(60);
126
+
127
+ try {
128
+ // Run the process
129
+ $process->mustRun();
130
+
131
+ // Check if .npmrc file exists
132
+ if (!Storage::drive('local')->exists($npmrcPath)) {
133
+ throw new \Exception('.npmrc file not found');
134
+ }
135
+
136
+ // Remove protocol from registry URL for matching
137
+ $registryHost = preg_replace('/^https?:\/\//', '', $registry);
138
+
139
+ // Read the .npmrc file to get the auth token
140
+ $npmrcContent = Storage::drive('local')->get($npmrcPath);
141
+ $lines = explode("\n", $npmrcContent);
142
+ $authToken = null;
143
+
144
+ foreach ($lines as $line) {
145
+ $line = trim($line);
146
+ if (Str::contains($line, $registryHost) && Str::contains($line, '_authToken=')) {
147
+ $parts = explode('_authToken=', $line);
148
+ if (count($parts) === 2) {
149
+ $authToken = trim($parts[1], ' "');
150
+ break;
151
+ }
152
+ }
153
+ }
154
+
155
+ // Delete .npmrc file
156
+ Storage::drive('local')->delete($npmrcPath);
157
+
158
+ if ($authToken) {
159
+ // Get current authenticated user
160
+ $user = Auth::getUserFromSession();
161
+
162
+ // Create or update registry user for current session
163
+ $registryUser = RegistryUser::updateOrCreate(
164
+ ['company_uuid' => session('company'), 'user_uuid' => $user->uuid],
165
+ ['registry_token' => $authToken, 'scope' => '*', 'expires_at' => now()->addYear(), 'name' => $user->public_id . ' developer token']
166
+ );
167
+
168
+ return $registryUser;
169
+ }
170
+
171
+ throw new \Exception('Auth token not found in .npmrc');
172
+ } catch (ProcessFailedException $exception) {
173
+ throw new \Exception('npm login failed: ' . $exception->getMessage());
174
+ }
175
+ }
53
176
  }