@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.
- package/addon/components/modals/create-registry-credentials.hbs +5 -0
- package/addon/components/registry-admin-config.hbs +16 -0
- package/addon/components/registry-admin-config.js +36 -0
- package/addon/controllers/developers/credentials.js +106 -0
- package/addon/engine.js +8 -2
- package/addon/routes/developers/credentials.js +8 -1
- package/addon/templates/application.hbs +1 -0
- package/addon/templates/developers/credentials.hbs +13 -1
- package/addon/templates/developers/payments/index.hbs +14 -13
- package/addon/templates/installed.hbs +3 -3
- package/app/components/modals/create-registry-credentials.js +1 -0
- package/app/components/registry-admin-config.js +1 -0
- package/app/controllers/developers/credentials.js +1 -0
- package/composer.json +2 -2
- package/extension.json +1 -1
- package/package.json +5 -5
- package/server/config/registry-bridge.php +2 -1
- package/server/migrations/2024_07_18_151000_add_auth_token_column_to_registry_users_table.php +28 -0
- package/server/src/Http/Controllers/Internal/v1/RegistryAuthController.php +163 -24
- package/server/src/Http/Controllers/Internal/v1/RegistryExtensionBundleController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/RegistryExtensionController.php +59 -1
- package/server/src/Http/Filter/RegistryExtensionFilter.php +1 -1
- package/server/src/Http/Requests/RegistryAuthRequest.php +3 -1
- package/server/src/Http/Resources/RegistryUser.php +0 -12
- package/server/src/Models/RegistryExtension.php +42 -3
- package/server/src/Models/RegistryExtensionBundle.php +1 -1
- package/server/src/Models/RegistryUser.php +111 -1
- package/server/src/Providers/RegistryBridgeServiceProvider.php +16 -31
- package/server/src/Support/Bridge.php +123 -0
- package/server/src/Support/Utils.php +136 -1
- 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: '
|
|
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: '
|
|
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
|
-
|
|
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
|
-
{{#
|
|
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
|
-
{{/
|
|
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.
|
|
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.
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fleetbase/registry-bridge-engine",
|
|
3
|
-
"version": "0.0.
|
|
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.
|
|
44
|
-
"@fortawesome/ember-fontawesome": "^0.
|
|
45
|
-
"@fortawesome/fontawesome-svg-core": "
|
|
46
|
-
"@fortawesome/free-solid-svg-icons": "
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
133
|
-
$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
|
-
//
|
|
136
|
-
|
|
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
|
-
//
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
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;
|