@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
@@ -0,0 +1,5 @@
1
+ <Modal::Default @modalIsOpened={{@modalIsOpened}} @options={{@options}} @confirm={{@onConfirm}} @decline={{@onDecline}}>
2
+ <div class="modal-body-container">
3
+ <InputGroup @name="Password" @helpText="You must authenticate using your password." @placeholder="Enter your password" @value={{@options.password}} @type="password" />
4
+ </div>
5
+ </Modal::Default>
@@ -0,0 +1,16 @@
1
+ <ContentPanel @title="Registry Configuration" @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
2
+ <InputGroup @name="Registry Host" @value={{this.registryHost}} disabled={{or this.getConfigValues.isRunning this.saveConfigValues.isRunning}} />
3
+ <InputGroup @name="Registry Token" @value={{this.registryToken}} disabled={{or this.getConfigValues.isRunning this.saveConfigValues.isRunning}} />
4
+ </ContentPanel>
5
+ <EmberWormhole @to="next-view-section-subheader-actions">
6
+ <Button
7
+ @type="primary"
8
+ @size="sm"
9
+ @icon="save"
10
+ @text="Save Changes"
11
+ @onClick={{perform this.saveConfigValues}}
12
+ @disabled={{or this.getConfigValues.isRunning this.saveConfigValues.isRunning}}
13
+ @isLoading={{this.saveConfigValues.isRunning}}
14
+ />
15
+ </EmberWormhole>
16
+ <Spacer @height="400px" />
@@ -0,0 +1,36 @@
1
+ import Component from '@glimmer/component';
2
+ import { inject as service } from '@ember/service';
3
+ import { tracked } from '@glimmer/tracking';
4
+ import { task } from 'ember-concurrency';
5
+
6
+ export default class RegistryAdminConfigComponent extends Component {
7
+ @service fetch;
8
+ @service notifications;
9
+ @tracked registryHost;
10
+ @tracked registryToken;
11
+
12
+ constructor() {
13
+ super(...arguments);
14
+ this.getConfigValues.perform();
15
+ }
16
+
17
+ @task *getConfigValues() {
18
+ try {
19
+ const { host, token } = yield this.fetch.get('registry-extensions/config', {}, { namespace: '~registry/v1' });
20
+ this.registryHost = host;
21
+ this.registryToken = token;
22
+ } catch (error) {
23
+ this.notifications.serverError(error);
24
+ }
25
+ }
26
+
27
+ @task *saveConfigValues() {
28
+ try {
29
+ const { host, token } = yield this.fetch.post('registry-extensions/config', { host: this.registryHost, token: this.registryToken }, { namespace: '~registry/v1' });
30
+ this.registryHost = host;
31
+ this.registryToken = token;
32
+ } catch (error) {
33
+ this.notifications.serverError(error);
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,106 @@
1
+ import Controller from '@ember/controller';
2
+ import { action } from '@ember/object';
3
+ import { inject as service } from '@ember/service';
4
+
5
+ export default class DevelopersCredentialsController extends Controller {
6
+ @service modalsManager;
7
+ @service notifications;
8
+ @service hostRouter;
9
+ @service fetch;
10
+
11
+ columns = [
12
+ {
13
+ label: 'Owner',
14
+ valuePath: 'user.name',
15
+ width: '15%',
16
+ },
17
+ {
18
+ label: 'Fleetbase Token',
19
+ valuePath: 'token',
20
+ cellComponent: 'click-to-copy',
21
+ width: '20%',
22
+ },
23
+ {
24
+ label: 'Registry Token',
25
+ valuePath: 'registry_token',
26
+ cellComponent: 'click-to-reveal',
27
+ cellComponentArgs: {
28
+ clickToCopy: true,
29
+ },
30
+ width: '25%',
31
+ },
32
+ {
33
+ label: 'Expiry',
34
+ valuePath: 'expires_at',
35
+ width: '15%',
36
+ },
37
+ {
38
+ label: 'Created',
39
+ valuePath: 'created_at',
40
+ width: '15%',
41
+ },
42
+ {
43
+ label: '',
44
+ cellComponent: 'table/cell/dropdown',
45
+ ddButtonText: false,
46
+ ddButtonIcon: 'ellipsis-h',
47
+ ddButtonIconPrefix: 'fas',
48
+ ddMenuLabel: 'Credential Actions',
49
+ cellClassNames: 'overflow-visible',
50
+ wrapperClass: 'flex items-center justify-end mx-2',
51
+ width: '10%',
52
+ align: 'right',
53
+ actions: [
54
+ {
55
+ label: 'Delete Credentials',
56
+ fn: this.deleteCredentials,
57
+ className: 'text-red-700 hover:text-red-800',
58
+ },
59
+ ],
60
+ },
61
+ ];
62
+
63
+ @action deleteCredentials(credentials) {
64
+ this.modalsManager.confirm({
65
+ title: 'Delete extension registry credentials?',
66
+ body: 'Are you sure you wish to delete these credentials? Once deleted any service or user using these credentials will loose access to the registry.',
67
+ confirm: async (modal) => {
68
+ modal.startLoading();
69
+
70
+ try {
71
+ await this.fetch.delete(`auth/registry-tokens/${credentials.uuid}`, {}, { namespace: '~registry/v1' });
72
+ this.notifications.success('Registry credentials deleted.');
73
+ return this.hostRouter.refresh();
74
+ } catch (error) {
75
+ this.notifications.serverError(error);
76
+ }
77
+ },
78
+ });
79
+ }
80
+
81
+ @action createCredentials() {
82
+ this.modalsManager.show('modals/create-registry-credentials', {
83
+ title: 'Create new registry credentials',
84
+ acceptButtonText: 'Create',
85
+ acceptButtonIcon: 'check',
86
+ password: null,
87
+ confirm: async (modal) => {
88
+ modal.startLoading();
89
+
90
+ const password = modal.getOption('password');
91
+ if (!password) {
92
+ this.notifications.warning('Password cannot be empty');
93
+ return modal.stopLoading();
94
+ }
95
+
96
+ try {
97
+ await this.fetch.post('auth/registry-tokens', { password }, { namespace: '~registry/v1' });
98
+ this.notifications.success('Registry credentials created.');
99
+ return this.hostRouter.refresh();
100
+ } catch (error) {
101
+ this.notifications.serverError(error);
102
+ }
103
+ },
104
+ });
105
+ }
106
+ }
package/addon/engine.js CHANGED
@@ -3,6 +3,7 @@ import loadInitializers from 'ember-load-initializers';
3
3
  import Resolver from 'ember-resolver';
4
4
  import config from './config/environment';
5
5
  import services from '@fleetbase/ember-core/exports/services';
6
+ import RegistryAdminConfigComponent from './components/registry-admin-config';
6
7
  import ExtensionReviewerControlComponent from './components/extension-reviewer-control';
7
8
  import ExtensionPendingPublishViewerComponent from './components/extension-pending-publish-viewer';
8
9
 
@@ -24,12 +25,17 @@ export default class RegistryBridgeEngine extends Engine {
24
25
  'Extensions Registry',
25
26
  [
26
27
  {
27
- title: 'Extensions Awaiting Review',
28
+ title: 'Registry Config',
29
+ icon: 'gear',
30
+ component: RegistryAdminConfigComponent,
31
+ },
32
+ {
33
+ title: 'Awaiting Review',
28
34
  icon: 'gavel',
29
35
  component: ExtensionReviewerControlComponent,
30
36
  },
31
37
  {
32
- title: 'Extensions Pending Publish',
38
+ title: 'Pending Publish',
33
39
  icon: 'rocket',
34
40
  component: ExtensionPendingPublishViewerComponent,
35
41
  },
@@ -1,3 +1,10 @@
1
1
  import Route from '@ember/routing/route';
2
+ import { inject as service } from '@ember/service';
2
3
 
3
- export default class DevelopersCredentialsRoute extends Route {}
4
+ export default class DevelopersCredentialsRoute extends Route {
5
+ @service fetch;
6
+
7
+ model() {
8
+ return this.fetch.get('auth/registry-tokens', {}, { namespace: '~registry/v1' });
9
+ }
10
+ }
@@ -17,6 +17,7 @@
17
17
  <Layout::Sidebar::Item @route="console.extensions.developers.extensions" @icon="box-archive">Extensions</Layout::Sidebar::Item>
18
18
  <Layout::Sidebar::Item @route="console.extensions.developers.analytics" @icon="chart-simple">Analytics</Layout::Sidebar::Item>
19
19
  <Layout::Sidebar::Item @route="console.extensions.developers.payments" @icon="cash-register">Payments</Layout::Sidebar::Item>
20
+ <Layout::Sidebar::Item @route="console.extensions.developers.credentials" @icon="key">Credentials</Layout::Sidebar::Item>
20
21
  </Layout::Sidebar::Panel>
21
22
  <Spacer @height="200px" />
22
23
  </EmberWormhole>
@@ -1 +1,13 @@
1
- {{outlet}}
1
+ <Layout::Section::Header @title="Credentials">
2
+ <Button @type="primary" @icon="plus" @iconPrefix="fas" @text="Create new credentials" class="mr-2" @onClick={{this.createCredentials}} />
3
+ </Layout::Section::Header>
4
+
5
+ <Layout::Section::Body class="overflow-y-scroll h-full">
6
+ <Table
7
+ @rows={{@model}}
8
+ @columns={{this.columns}}
9
+ @selectable={{false}}
10
+ @canSelectAll={{false}}
11
+ @pagination={{false}}
12
+ />
13
+ </Layout::Section::Body>
@@ -6,7 +6,19 @@
6
6
  </Layout::Section::Header>
7
7
 
8
8
  <Layout::Section::Body class="overflow-y-scroll h-full">
9
- {{#unless this.hasStripeConnectAccount}}
9
+ {{#if this.hasStripeConnectAccount}}
10
+ <Table
11
+ @rows={{@model.data}}
12
+ @columns={{this.columns}}
13
+ @selectable={{false}}
14
+ @canSelectAll={{false}}
15
+ @onSetup={{fn (mut this.table)}}
16
+ @pagination={{true}}
17
+ @paginationMeta={{@model.meta}}
18
+ @page={{this.page}}
19
+ @onPageChange={{fn (mut this.page)}}
20
+ />
21
+ {{else}}
10
22
  <div class="container">
11
23
  <div class="max-w-3xl mx-auto mt-4">
12
24
  <div class="content">
@@ -18,16 +30,5 @@
18
30
  </div>
19
31
  </div>
20
32
  </div>
21
- {{/unless}}
22
- <Table
23
- @rows={{@model.data}}
24
- @columns={{this.columns}}
25
- @selectable={{false}}
26
- @canSelectAll={{false}}
27
- @onSetup={{fn (mut this.table)}}
28
- @pagination={{true}}
29
- @paginationMeta={{@model.meta}}
30
- @page={{this.page}}
31
- @onPageChange={{fn (mut this.page)}}
32
- />
33
+ {{/if}}
33
34
  </Layout::Section::Body>
@@ -13,15 +13,15 @@
13
13
  <div class="font-semibold text-sm block">{{extension.name}}</div>
14
14
  <div class="text-xs">{{n-a extension.description}}</div>
15
15
  </div>
16
- <div class="pt-1 space-y-2">
16
+ <div class="flex flex-col flex-1 pt-1 space-y-2">
17
17
  <Button
18
18
  @type="default"
19
19
  @text={{t "registry-bridge.common.about-extension" extensionName=extension.name}}
20
20
  @icon="circle-info"
21
21
  @onClick={{fn this.about extension}}
22
- class="w-full"
22
+ class="w-full btn-block"
23
23
  />
24
- <Button @type="danger" @text={{t "registry-bridge.common.uninstall"}} @icon="trash" @onClick={{fn this.uninstall extension}} class="w-full" />
24
+ <Button @type="danger" @text={{t "registry-bridge.common.uninstall"}} @icon="trash" @onClick={{fn this.uninstall extension}} class="w-full btn-block" />
25
25
  </div>
26
26
  </div>
27
27
  </div>
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/registry-bridge-engine/components/modals/create-registry-credentials';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/registry-bridge-engine/components/registry-admin-config';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/registry-bridge-engine/controllers/developers/credentials';
package/composer.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fleetbase/registry-bridge",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Internal Bridge between Fleetbase API and Extensions Registry",
5
5
  "keywords": [
6
6
  "fleetbase-extension",
@@ -20,7 +20,7 @@
20
20
  ],
21
21
  "require": {
22
22
  "php": "^8.0",
23
- "fleetbase/core-api": "^1.4.28",
23
+ "fleetbase/core-api": "^1.4.30",
24
24
  "laravel/cashier": "^15.2.1",
25
25
  "php-http/guzzle7-adapter": "^1.0",
26
26
  "psr/http-factory-implementation": "*",
package/extension.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "Registry Bridge",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Internal Bridge between Fleetbase API and Extensions Registry",
5
5
  "repository": "https://github.com/fleetbase/registry-bridge",
6
6
  "license": "AGPL-3.0-or-later",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetbase/registry-bridge-engine",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Internal Bridge between Fleetbase API and Extensions Registry",
5
5
  "fleetbase": {
6
6
  "route": "extensions"
@@ -40,10 +40,10 @@
40
40
  "dependencies": {
41
41
  "@babel/core": "^7.23.2",
42
42
  "@fleetbase/ember-core": "^0.2.13",
43
- "@fleetbase/ember-ui": "^0.2.18",
44
- "@fortawesome/ember-fontawesome": "^0.4.1",
45
- "@fortawesome/fontawesome-svg-core": "^6.5.2",
46
- "@fortawesome/free-solid-svg-icons": "^6.5.2",
43
+ "@fleetbase/ember-ui": "^0.2.19",
44
+ "@fortawesome/ember-fontawesome": "^2.0.0",
45
+ "@fortawesome/fontawesome-svg-core": "6.4.0",
46
+ "@fortawesome/free-solid-svg-icons": "6.4.0",
47
47
  "@stripe/connect-js": "^3.3.10",
48
48
  "ember-auto-import": "^2.6.3",
49
49
  "ember-cli-babel": "^8.2.0",
@@ -26,7 +26,8 @@ return [
26
26
  'webhook_secret' => env('STRIPE_WEBHOOK_SECRET'),
27
27
  ],
28
28
  'extensions' => [
29
- 'preinstalled' => Utils::castBoolean(env('REGISTRY_PREINSTALLED_EXTENSIONS', false))
29
+ 'preinstalled' => Utils::castBoolean(env('REGISTRY_PREINSTALLED_EXTENSIONS', false)),
30
+ 'protected_prefixes' => explode(',', env('REGISTRY_PROTECTED_PREFIXES', '@fleetbase,fleetbase,@flb,@fleetbase-extension,@flb-extension'))
30
31
  ],
31
32
  'facilitator_fee' => env('REGISTRY_FACILITATOR_FEE', 10)
32
33
  ];
@@ -0,0 +1,28 @@
1
+ <?php
2
+
3
+ use Illuminate\Database\Migrations\Migration;
4
+ use Illuminate\Database\Schema\Blueprint;
5
+ use Illuminate\Support\Facades\Schema;
6
+
7
+ return new class extends Migration
8
+ {
9
+ /**
10
+ * Run the migrations.
11
+ */
12
+ public function up(): void
13
+ {
14
+ Schema::table('registry_users', function (Blueprint $table) {
15
+ $table->string('registry_token')->nullable()->after('token');
16
+ });
17
+ }
18
+
19
+ /**
20
+ * Reverse the migrations.
21
+ */
22
+ public function down(): void
23
+ {
24
+ Schema::table('registry_users', function (Blueprint $table) {
25
+ $table->dropColumn('registry_token');
26
+ });
27
+ }
28
+ };
@@ -12,12 +12,55 @@ use Fleetbase\RegistryBridge\Models\RegistryExtension;
12
12
  use Fleetbase\RegistryBridge\Models\RegistryUser;
13
13
  use Fleetbase\RegistryBridge\Support\Bridge;
14
14
  use Fleetbase\Support\Auth;
15
+ use Illuminate\Http\Request;
16
+ use Illuminate\Support\Str;
15
17
 
16
18
  class RegistryAuthController extends Controller
17
19
  {
18
- public function test()
20
+ /**
21
+ * Handle Composer authentication and return a list of unauthorized packages.
22
+ *
23
+ * This function authenticates the user based on the provided registry token.
24
+ * If the token is valid, it returns a list of packages that the user is not authorized to access.
25
+ *
26
+ * @param Request $request the incoming HTTP request containing the registry token
27
+ *
28
+ * @return \Illuminate\Http\JsonResponse a JSON response containing the status and a list of unauthorized packages
29
+ */
30
+ public function composerAuthentication(Request $request)
19
31
  {
20
- dd(Bridge::get('~/flb/extensions'));
32
+ $registryToken = $request->input('registryToken');
33
+ if (!$registryToken) {
34
+ return response()->error('No registry token provided for authentication.', 401);
35
+ }
36
+
37
+ // Get registry user via token
38
+ $registryUser = RegistryUser::where('registry_token', $registryToken)->first();
39
+ if (!$registryUser) {
40
+ return response()->error('Invalid registry token provided for authentication.', 401);
41
+ }
42
+
43
+ // Fetch unauthorized extensions
44
+ $unauthorizedExtensions = RegistryExtension::where('payment_required', true)
45
+ ->whereDoesntHave('purchases', function ($query) use ($registryUser) {
46
+ $query->where('company_uuid', $registryUser->company_uuid);
47
+ })
48
+ ->whereHas('currentBundle')
49
+ ->with('currentBundle')
50
+ ->get();
51
+
52
+ // Map to unathorized to package names
53
+ $unauthorizedExtensionNames = $unauthorizedExtensions->map(function ($registryExtension) {
54
+ $composerJson = $registryExtension->currentBundle->meta['composer.json'] ?? [];
55
+
56
+ return $composerJson['name'] ?? null;
57
+ })->filter()->values();
58
+
59
+ // Done
60
+ return response()->json([
61
+ 'status' => 'ok',
62
+ 'unauthorizedPackages' => $unauthorizedExtensionNames,
63
+ ]);
21
64
  }
22
65
 
23
66
  /**
@@ -129,15 +172,29 @@ class RegistryAuthController extends Controller
129
172
  */
130
173
  public function checkAccess(RegistryAuthRequest $request)
131
174
  {
132
- // Get identity
133
- $identity = $request->input('identity');
175
+ $packageName = $request->input('package');
176
+ $identity = $request->input('identity');
177
+ $protectedPackage = Str::startsWith($packageName, config('registry-bridge.extensions.protected_prefixes'));
134
178
 
135
- // Find user by email or username
136
- $user = User::where('email', $identity)->orWhere('username', $identity)->first();
179
+ // If no identity and not a protected package allow access
180
+ if (!$identity && !$protectedPackage) {
181
+ return response()->json(['allowed' => true]);
182
+ }
137
183
 
138
- // If user is not admin respond with error
139
- if (!$user->isAdmin()) {
140
- return response()->error('User is not allowed access to the registry.', 401);
184
+ // Get registry user via identity
185
+ $registryUser = RegistryUser::findFromUsername($identity);
186
+
187
+ // If registry user is admin allow access
188
+ if ($registryUser->is_admin) {
189
+ return response()->json(['allowed' => true]);
190
+ }
191
+
192
+ // Check if package is protected, if so verify user has access to package
193
+ if ($protectedPackage) {
194
+ $extension = RegistryExtension::findByPackageName($packageName);
195
+ if ($extension && $extension->doesntHaveAccess($registryUser)) {
196
+ return response()->error('This package requires payment to access.', 401);
197
+ }
141
198
  }
142
199
 
143
200
  // For now only admin users can access registry
@@ -171,15 +228,16 @@ class RegistryAuthController extends Controller
171
228
  $force = $request->boolean('force');
172
229
  $password = $request->input('password');
173
230
 
231
+ // Find user by email or username
232
+ $registryUser = RegistryUser::findFromUsername($identity);
233
+ if (!$registryUser) {
234
+ return response()->error('Attempting to publish extension with invalid user.', 401);
235
+ }
236
+
174
237
  // If force publish bypass checks, authenticate by user login
175
238
  if ($force === true) {
176
- // Find user by email or username
177
- $user = User::where(function ($query) use ($identity) {
178
- $query->where('email', $identity)->orWhere('phone', $identity)->orWhere('username', $identity);
179
- })->first();
180
-
181
239
  // Authenticate user with password
182
- if (Auth::isInvalidPassword($password, $user->password)) {
240
+ if (Auth::isInvalidPassword($password, $registryUser->user->password)) {
183
241
  return response()->error('Invalid credentials, unable to force publish.', 401);
184
242
  }
185
243
 
@@ -197,16 +255,10 @@ class RegistryAuthController extends Controller
197
255
  return response()->error('Attempting to publish extension which has no record.', 401);
198
256
  }
199
257
 
200
- // Find user by email or username
201
- $user = User::where(function ($query) use ($identity) {
202
- $query->where('email', $identity)->orWhere('phone', $identity)->orWhere('username', $identity);
203
- })->first();
204
- if (!$user) {
205
- return response()->error('Attempting to publish extension with invalid user.', 401);
206
- }
207
-
208
258
  // If user is not admin respond with error
209
- if (!$user->isAdmin()) {
259
+ // For now only admin is allowed to publish to registry
260
+ // This may change in the future with approval/reject flow
261
+ if ($registryUser->isNotAdmin()) {
210
262
  return response()->error('User is not allowed publish to the registry.', 401);
211
263
  }
212
264
 
@@ -227,4 +279,91 @@ class RegistryAuthController extends Controller
227
279
  // Passed all checks
228
280
  return response()->json(['allowed' => true]);
229
281
  }
282
+
283
+ /**
284
+ * Creates a registry user by authenticating with the provided password.
285
+ *
286
+ * This method retrieves the current authenticated user and checks the provided password.
287
+ * If the password is valid, it logs in to the npm registry using the user's credentials,
288
+ * retrieves the authentication token, and associates it with the user. The registry token
289
+ * is stored in the database for the user's current session.
290
+ *
291
+ * @param Request $request the incoming HTTP request containing the user's password
292
+ *
293
+ * @return \Illuminate\Http\JsonResponse the JSON response containing the created RegistryUser or an error message
294
+ */
295
+ public function createRegistryUser(Request $request)
296
+ {
297
+ $password = $request->input('password');
298
+ if (!$password) {
299
+ return response()->error('Password is required.');
300
+ }
301
+
302
+ // Get current user
303
+ $user = Auth::getUserFromSession();
304
+ if (!$user) {
305
+ return response()->error('No user authenticated.');
306
+ }
307
+
308
+ // Authenticate user with password
309
+ if (Auth::isInvalidPassword($password, $user->password)) {
310
+ return response()->error('Invalid credentials.', 401);
311
+ }
312
+
313
+ // Create registry user
314
+ try {
315
+ $registryUser = Bridge::loginWithUser($user, $password);
316
+ } catch (\Throwable $e) {
317
+ return response()->json($e->getMessage());
318
+ }
319
+
320
+ return response()->json($registryUser);
321
+ }
322
+
323
+ /**
324
+ * Retrieves all registry tokens for the current company.
325
+ *
326
+ * This method queries the `RegistryUser` model to get all registry tokens
327
+ * associated with the current company's UUID from the session. It also includes
328
+ * user details for each registry token and returns the data as a JSON response.
329
+ *
330
+ * @return \Illuminate\Http\JsonResponse the JSON response containing a list of registry tokens with user details
331
+ */
332
+ public function getRegistryTokens()
333
+ {
334
+ $registryUsers = RegistryUser::select(
335
+ ['uuid', 'user_uuid', 'company_uuid', 'token', 'registry_token', 'expires_at', 'created_at']
336
+ )->where('company_uuid', session('company'))->with(
337
+ [
338
+ 'user' => function ($query) {
339
+ $query->select(['uuid', 'company_uuid', 'name', 'email']);
340
+ },
341
+ ]
342
+ )->get();
343
+
344
+ return response()->json($registryUsers);
345
+ }
346
+
347
+ /**
348
+ * Deletes a specific registry token by its UUID.
349
+ *
350
+ * This method deletes a registry token identified by its UUID. If the registry token
351
+ * does not exist, it returns an error response. If successful, it returns a JSON response
352
+ * with a status indicating the deletion was successful.
353
+ *
354
+ * @param string $id the UUID of the registry token to be deleted
355
+ *
356
+ * @return \Illuminate\Http\JsonResponse the JSON response indicating the status of the deletion
357
+ */
358
+ public function deleteRegistryToken(string $id)
359
+ {
360
+ $registryUser = RegistryUser::where('uuid', $id)->first();
361
+ if (!$registryUser) {
362
+ return response()->error('Registry token does not exist.');
363
+ }
364
+
365
+ $registryUser->delete();
366
+
367
+ return response()->json(['status' => 'ok']);
368
+ }
230
369
  }
@@ -8,7 +8,7 @@ use Fleetbase\RegistryBridge\Http\Controllers\RegistryBridgeController;
8
8
  use Fleetbase\RegistryBridge\Http\Requests\CreateRegistryExtensionBundleRequest;
9
9
  use Fleetbase\RegistryBridge\Http\Requests\RegistryExtensionActionRequest;
10
10
  use Fleetbase\RegistryBridge\Models\RegistryExtensionBundle;
11
- use Fleetbase\Support\Utils;
11
+ use Fleetbase\RegistryBridge\Support\Utils;
12
12
  use Illuminate\Http\Request;
13
13
  use Illuminate\Support\Facades\Storage;
14
14
  use Illuminate\Support\Facades\Validator;