mirror of
https://github.com/haexhub/haex-hub.git
synced 2025-12-16 22:20:51 +01:00
Update extension system and database migrations
Changes: - Added CLAUDE.md with project instructions - Updated extension manifest bindings (TypeScript) - Regenerated database migrations (consolidated into single migration) - Updated haex schema with table name handling - Enhanced extension manager and manifest handling in Rust - Updated extension store in frontend - Updated vault.db 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
13
CLAUDE.md
Normal file
13
CLAUDE.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Verwandte Projekte
|
||||||
|
|
||||||
|
## SDK
|
||||||
|
|
||||||
|
- /home/haex/Projekte/haexhub-sdk
|
||||||
|
|
||||||
|
## Erweiterung HaexPass (Password Manager)
|
||||||
|
|
||||||
|
- /home/haex/Projekte/haex-pass
|
||||||
|
|
||||||
|
# Codingstyle
|
||||||
|
|
||||||
|
- alle asynchronen Funktionen bitte mit Async prependen
|
||||||
@ -1,3 +1,3 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
export type ExtensionInfoResponse = { id: string, publicKey: string, name: string, version: string, author: string | null, enabled: boolean, description: string | null, homepage: string | null, icon: string | null, devServerUrl: string | null, };
|
export type ExtensionInfoResponse = { id: string, publicKey: string, name: string, version: string, author: string | null, enabled: boolean, description: string | null, homepage: string | null, icon: string | null, entry: string | null, singleInstance: boolean | null, devServerUrl: string | null, };
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { ExtensionPermissions } from "./ExtensionPermissions";
|
import type { ExtensionPermissions } from "./ExtensionPermissions";
|
||||||
|
|
||||||
export type ExtensionManifest = { name: string, version: string, author: string | null, entry: string, icon: string | null, public_key: string, signature: string, permissions: ExtensionPermissions, homepage: string | null, description: string | null, };
|
export type ExtensionManifest = { name: string, version: string, author: string | null, entry: string | null, icon: string | null, public_key: string, signature: string, permissions: ExtensionPermissions, homepage: string | null, description: string | null, single_instance: boolean | null, };
|
||||||
|
|||||||
@ -60,11 +60,12 @@ CREATE TABLE `haex_extensions` (
|
|||||||
`version` text NOT NULL,
|
`version` text NOT NULL,
|
||||||
`author` text,
|
`author` text,
|
||||||
`description` text,
|
`description` text,
|
||||||
`entry` text DEFAULT 'index.html' NOT NULL,
|
`entry` text DEFAULT 'index.html',
|
||||||
`homepage` text,
|
`homepage` text,
|
||||||
`enabled` integer DEFAULT true,
|
`enabled` integer DEFAULT true,
|
||||||
`icon` text,
|
`icon` text,
|
||||||
`signature` text NOT NULL,
|
`signature` text NOT NULL,
|
||||||
|
`single_instance` integer DEFAULT false,
|
||||||
`haex_timestamp` text
|
`haex_timestamp` text
|
||||||
);
|
);
|
||||||
--> statement-breakpoint
|
--> statement-breakpoint
|
||||||
@ -94,6 +95,7 @@ CREATE TABLE `haex_settings` (
|
|||||||
CREATE UNIQUE INDEX `haex_settings_key_type_value_unique` ON `haex_settings` (`key`,`type`,`value`);--> statement-breakpoint
|
CREATE UNIQUE INDEX `haex_settings_key_type_value_unique` ON `haex_settings` (`key`,`type`,`value`);--> statement-breakpoint
|
||||||
CREATE TABLE `haex_workspaces` (
|
CREATE TABLE `haex_workspaces` (
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`device_id` text NOT NULL,
|
||||||
`name` text NOT NULL,
|
`name` text NOT NULL,
|
||||||
`position` integer DEFAULT 0 NOT NULL,
|
`position` integer DEFAULT 0 NOT NULL,
|
||||||
`haex_timestamp` text
|
`haex_timestamp` text
|
||||||
@ -1 +0,0 @@
|
|||||||
ALTER TABLE `haex_workspaces` ADD `device_id` text NOT NULL;
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"dialect": "sqlite",
|
"dialect": "sqlite",
|
||||||
"id": "bcdd9ad3-a87a-4a43-9eba-673f94b10287",
|
"id": "8dc25226-70f9-4d2e-89d4-f3a6b2bdf58d",
|
||||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
"tables": {
|
"tables": {
|
||||||
"haex_crdt_configs": {
|
"haex_crdt_configs": {
|
||||||
@ -411,7 +411,7 @@
|
|||||||
"name": "entry",
|
"name": "entry",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": false,
|
||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"default": "'index.html'"
|
"default": "'index.html'"
|
||||||
},
|
},
|
||||||
@ -444,6 +444,14 @@
|
|||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"single_instance": {
|
||||||
|
"name": "single_instance",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
"haex_timestamp": {
|
"haex_timestamp": {
|
||||||
"name": "haex_timestamp",
|
"name": "haex_timestamp",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@ -619,6 +627,13 @@
|
|||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"device_id": {
|
||||||
|
"name": "device_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"name": "name",
|
"name": "name",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
|||||||
@ -1,677 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "6",
|
|
||||||
"dialect": "sqlite",
|
|
||||||
"id": "27735348-b9c5-4bc6-9cc5-dd707ad689b9",
|
|
||||||
"prevId": "bcdd9ad3-a87a-4a43-9eba-673f94b10287",
|
|
||||||
"tables": {
|
|
||||||
"haex_crdt_configs": {
|
|
||||||
"name": "haex_crdt_configs",
|
|
||||||
"columns": {
|
|
||||||
"key": {
|
|
||||||
"name": "key",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": true,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"value": {
|
|
||||||
"name": "value",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {},
|
|
||||||
"foreignKeys": {},
|
|
||||||
"compositePrimaryKeys": {},
|
|
||||||
"uniqueConstraints": {},
|
|
||||||
"checkConstraints": {}
|
|
||||||
},
|
|
||||||
"haex_crdt_logs": {
|
|
||||||
"name": "haex_crdt_logs",
|
|
||||||
"columns": {
|
|
||||||
"id": {
|
|
||||||
"name": "id",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": true,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"haex_timestamp": {
|
|
||||||
"name": "haex_timestamp",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"table_name": {
|
|
||||||
"name": "table_name",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"row_pks": {
|
|
||||||
"name": "row_pks",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"op_type": {
|
|
||||||
"name": "op_type",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"column_name": {
|
|
||||||
"name": "column_name",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"new_value": {
|
|
||||||
"name": "new_value",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"old_value": {
|
|
||||||
"name": "old_value",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {
|
|
||||||
"idx_haex_timestamp": {
|
|
||||||
"name": "idx_haex_timestamp",
|
|
||||||
"columns": [
|
|
||||||
"haex_timestamp"
|
|
||||||
],
|
|
||||||
"isUnique": false
|
|
||||||
},
|
|
||||||
"idx_table_row": {
|
|
||||||
"name": "idx_table_row",
|
|
||||||
"columns": [
|
|
||||||
"table_name",
|
|
||||||
"row_pks"
|
|
||||||
],
|
|
||||||
"isUnique": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"foreignKeys": {},
|
|
||||||
"compositePrimaryKeys": {},
|
|
||||||
"uniqueConstraints": {},
|
|
||||||
"checkConstraints": {}
|
|
||||||
},
|
|
||||||
"haex_crdt_snapshots": {
|
|
||||||
"name": "haex_crdt_snapshots",
|
|
||||||
"columns": {
|
|
||||||
"snapshot_id": {
|
|
||||||
"name": "snapshot_id",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": true,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"created": {
|
|
||||||
"name": "created",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"epoch_hlc": {
|
|
||||||
"name": "epoch_hlc",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"location_url": {
|
|
||||||
"name": "location_url",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"file_size_bytes": {
|
|
||||||
"name": "file_size_bytes",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {},
|
|
||||||
"foreignKeys": {},
|
|
||||||
"compositePrimaryKeys": {},
|
|
||||||
"uniqueConstraints": {},
|
|
||||||
"checkConstraints": {}
|
|
||||||
},
|
|
||||||
"haex_desktop_items": {
|
|
||||||
"name": "haex_desktop_items",
|
|
||||||
"columns": {
|
|
||||||
"id": {
|
|
||||||
"name": "id",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": true,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"workspace_id": {
|
|
||||||
"name": "workspace_id",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"item_type": {
|
|
||||||
"name": "item_type",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"extension_id": {
|
|
||||||
"name": "extension_id",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"system_window_id": {
|
|
||||||
"name": "system_window_id",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"position_x": {
|
|
||||||
"name": "position_x",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": 0
|
|
||||||
},
|
|
||||||
"position_y": {
|
|
||||||
"name": "position_y",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": 0
|
|
||||||
},
|
|
||||||
"haex_timestamp": {
|
|
||||||
"name": "haex_timestamp",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {},
|
|
||||||
"foreignKeys": {
|
|
||||||
"haex_desktop_items_workspace_id_haex_workspaces_id_fk": {
|
|
||||||
"name": "haex_desktop_items_workspace_id_haex_workspaces_id_fk",
|
|
||||||
"tableFrom": "haex_desktop_items",
|
|
||||||
"tableTo": "haex_workspaces",
|
|
||||||
"columnsFrom": [
|
|
||||||
"workspace_id"
|
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
|
||||||
"onUpdate": "no action"
|
|
||||||
},
|
|
||||||
"haex_desktop_items_extension_id_haex_extensions_id_fk": {
|
|
||||||
"name": "haex_desktop_items_extension_id_haex_extensions_id_fk",
|
|
||||||
"tableFrom": "haex_desktop_items",
|
|
||||||
"tableTo": "haex_extensions",
|
|
||||||
"columnsFrom": [
|
|
||||||
"extension_id"
|
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
|
||||||
"onUpdate": "no action"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"compositePrimaryKeys": {},
|
|
||||||
"uniqueConstraints": {},
|
|
||||||
"checkConstraints": {
|
|
||||||
"item_reference": {
|
|
||||||
"name": "item_reference",
|
|
||||||
"value": "(\"haex_desktop_items\".\"item_type\" = 'extension' AND \"haex_desktop_items\".\"extension_id\" IS NOT NULL AND \"haex_desktop_items\".\"system_window_id\" IS NULL) OR (\"haex_desktop_items\".\"item_type\" = 'system' AND \"haex_desktop_items\".\"system_window_id\" IS NOT NULL AND \"haex_desktop_items\".\"extension_id\" IS NULL) OR (\"haex_desktop_items\".\"item_type\" = 'file' AND \"haex_desktop_items\".\"system_window_id\" IS NOT NULL AND \"haex_desktop_items\".\"extension_id\" IS NULL) OR (\"haex_desktop_items\".\"item_type\" = 'folder' AND \"haex_desktop_items\".\"system_window_id\" IS NOT NULL AND \"haex_desktop_items\".\"extension_id\" IS NULL)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"haex_extension_permissions": {
|
|
||||||
"name": "haex_extension_permissions",
|
|
||||||
"columns": {
|
|
||||||
"id": {
|
|
||||||
"name": "id",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": true,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"extension_id": {
|
|
||||||
"name": "extension_id",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"resource_type": {
|
|
||||||
"name": "resource_type",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"action": {
|
|
||||||
"name": "action",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"name": "target",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"constraints": {
|
|
||||||
"name": "constraints",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"name": "status",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": "'denied'"
|
|
||||||
},
|
|
||||||
"created_at": {
|
|
||||||
"name": "created_at",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": "(CURRENT_TIMESTAMP)"
|
|
||||||
},
|
|
||||||
"updated_at": {
|
|
||||||
"name": "updated_at",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"haex_timestamp": {
|
|
||||||
"name": "haex_timestamp",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {
|
|
||||||
"haex_extension_permissions_extension_id_resource_type_action_target_unique": {
|
|
||||||
"name": "haex_extension_permissions_extension_id_resource_type_action_target_unique",
|
|
||||||
"columns": [
|
|
||||||
"extension_id",
|
|
||||||
"resource_type",
|
|
||||||
"action",
|
|
||||||
"target"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"foreignKeys": {
|
|
||||||
"haex_extension_permissions_extension_id_haex_extensions_id_fk": {
|
|
||||||
"name": "haex_extension_permissions_extension_id_haex_extensions_id_fk",
|
|
||||||
"tableFrom": "haex_extension_permissions",
|
|
||||||
"tableTo": "haex_extensions",
|
|
||||||
"columnsFrom": [
|
|
||||||
"extension_id"
|
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
|
||||||
"onUpdate": "no action"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"compositePrimaryKeys": {},
|
|
||||||
"uniqueConstraints": {},
|
|
||||||
"checkConstraints": {}
|
|
||||||
},
|
|
||||||
"haex_extensions": {
|
|
||||||
"name": "haex_extensions",
|
|
||||||
"columns": {
|
|
||||||
"id": {
|
|
||||||
"name": "id",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": true,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"public_key": {
|
|
||||||
"name": "public_key",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"name": "name",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"version": {
|
|
||||||
"name": "version",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"author": {
|
|
||||||
"name": "author",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"name": "description",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"entry": {
|
|
||||||
"name": "entry",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": "'index.html'"
|
|
||||||
},
|
|
||||||
"homepage": {
|
|
||||||
"name": "homepage",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"enabled": {
|
|
||||||
"name": "enabled",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": true
|
|
||||||
},
|
|
||||||
"icon": {
|
|
||||||
"name": "icon",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"signature": {
|
|
||||||
"name": "signature",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"haex_timestamp": {
|
|
||||||
"name": "haex_timestamp",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {
|
|
||||||
"haex_extensions_public_key_name_unique": {
|
|
||||||
"name": "haex_extensions_public_key_name_unique",
|
|
||||||
"columns": [
|
|
||||||
"public_key",
|
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"foreignKeys": {},
|
|
||||||
"compositePrimaryKeys": {},
|
|
||||||
"uniqueConstraints": {},
|
|
||||||
"checkConstraints": {}
|
|
||||||
},
|
|
||||||
"haex_notifications": {
|
|
||||||
"name": "haex_notifications",
|
|
||||||
"columns": {
|
|
||||||
"id": {
|
|
||||||
"name": "id",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": true,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"alt": {
|
|
||||||
"name": "alt",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"date": {
|
|
||||||
"name": "date",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"icon": {
|
|
||||||
"name": "icon",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"image": {
|
|
||||||
"name": "image",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"read": {
|
|
||||||
"name": "read",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"source": {
|
|
||||||
"name": "source",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"text": {
|
|
||||||
"name": "text",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"title": {
|
|
||||||
"name": "title",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
"name": "type",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"haex_timestamp": {
|
|
||||||
"name": "haex_timestamp",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {},
|
|
||||||
"foreignKeys": {},
|
|
||||||
"compositePrimaryKeys": {},
|
|
||||||
"uniqueConstraints": {},
|
|
||||||
"checkConstraints": {}
|
|
||||||
},
|
|
||||||
"haex_settings": {
|
|
||||||
"name": "haex_settings",
|
|
||||||
"columns": {
|
|
||||||
"id": {
|
|
||||||
"name": "id",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": true,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"key": {
|
|
||||||
"name": "key",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
"name": "type",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"value": {
|
|
||||||
"name": "value",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"haex_timestamp": {
|
|
||||||
"name": "haex_timestamp",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {
|
|
||||||
"haex_settings_key_type_value_unique": {
|
|
||||||
"name": "haex_settings_key_type_value_unique",
|
|
||||||
"columns": [
|
|
||||||
"key",
|
|
||||||
"type",
|
|
||||||
"value"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"foreignKeys": {},
|
|
||||||
"compositePrimaryKeys": {},
|
|
||||||
"uniqueConstraints": {},
|
|
||||||
"checkConstraints": {}
|
|
||||||
},
|
|
||||||
"haex_workspaces": {
|
|
||||||
"name": "haex_workspaces",
|
|
||||||
"columns": {
|
|
||||||
"id": {
|
|
||||||
"name": "id",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": true,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"device_id": {
|
|
||||||
"name": "device_id",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"name": "name",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"position": {
|
|
||||||
"name": "position",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": 0
|
|
||||||
},
|
|
||||||
"haex_timestamp": {
|
|
||||||
"name": "haex_timestamp",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {
|
|
||||||
"haex_workspaces_position_unique": {
|
|
||||||
"name": "haex_workspaces_position_unique",
|
|
||||||
"columns": [
|
|
||||||
"position"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"foreignKeys": {},
|
|
||||||
"compositePrimaryKeys": {},
|
|
||||||
"uniqueConstraints": {},
|
|
||||||
"checkConstraints": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"views": {},
|
|
||||||
"enums": {},
|
|
||||||
"_meta": {
|
|
||||||
"schemas": {},
|
|
||||||
"tables": {},
|
|
||||||
"columns": {}
|
|
||||||
},
|
|
||||||
"internal": {
|
|
||||||
"indexes": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -5,15 +5,8 @@
|
|||||||
{
|
{
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1761430560028,
|
"when": 1761821821609,
|
||||||
"tag": "0000_secret_ender_wiggin",
|
"tag": "0000_dashing_night_nurse",
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 1,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1761581351395,
|
|
||||||
"tag": "0001_late_the_renegades",
|
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -47,11 +47,12 @@ export const haexExtensions = sqliteTable(
|
|||||||
version: text().notNull(),
|
version: text().notNull(),
|
||||||
author: text(),
|
author: text(),
|
||||||
description: text(),
|
description: text(),
|
||||||
entry: text().notNull().default('index.html'),
|
entry: text().default('index.html'),
|
||||||
homepage: text(),
|
homepage: text(),
|
||||||
enabled: integer({ mode: 'boolean' }).default(true),
|
enabled: integer({ mode: 'boolean' }).default(true),
|
||||||
icon: text(),
|
icon: text(),
|
||||||
signature: text().notNull(),
|
signature: text().notNull(),
|
||||||
|
single_instance: integer({ mode: 'boolean' }).default(false),
|
||||||
}),
|
}),
|
||||||
(table) => [
|
(table) => [
|
||||||
// UNIQUE constraint: Pro Developer (public_key) kann nur eine Extension mit diesem Namen existieren
|
// UNIQUE constraint: Pro Developer (public_key) kann nur eine Extension mit diesem Namen existieren
|
||||||
|
|||||||
Binary file not shown.
@ -66,6 +66,91 @@ impl ExtensionManager {
|
|||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper function to validate path and check for path traversal
|
||||||
|
/// Returns the cleaned path if valid, or None if invalid/not found
|
||||||
|
/// If require_exists is true, returns None if path doesn't exist
|
||||||
|
pub fn validate_path_in_directory(
|
||||||
|
base_dir: &PathBuf,
|
||||||
|
relative_path: &str,
|
||||||
|
require_exists: bool,
|
||||||
|
) -> Result<Option<PathBuf>, ExtensionError> {
|
||||||
|
// Check for path traversal patterns
|
||||||
|
if relative_path.contains("..") {
|
||||||
|
return Err(ExtensionError::SecurityViolation {
|
||||||
|
reason: format!("Path traversal attempt: {}", relative_path),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean the path (same logic as in protocol.rs)
|
||||||
|
let clean_path = relative_path
|
||||||
|
.replace('\\', "/")
|
||||||
|
.trim_start_matches('/')
|
||||||
|
.split('/')
|
||||||
|
.filter(|&part| !part.is_empty() && part != "." && part != "..")
|
||||||
|
.collect::<PathBuf>();
|
||||||
|
|
||||||
|
let full_path = base_dir.join(&clean_path);
|
||||||
|
|
||||||
|
// Check if file/directory exists (if required)
|
||||||
|
if require_exists && !full_path.exists() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify path is within base directory
|
||||||
|
let canonical_base = base_dir
|
||||||
|
.canonicalize()
|
||||||
|
.map_err(|e| ExtensionError::Filesystem { source: e })?;
|
||||||
|
|
||||||
|
if let Ok(canonical_path) = full_path.canonicalize() {
|
||||||
|
if !canonical_path.starts_with(&canonical_base) {
|
||||||
|
return Err(ExtensionError::SecurityViolation {
|
||||||
|
reason: format!("Path outside base directory: {}", relative_path),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(Some(canonical_path))
|
||||||
|
} else {
|
||||||
|
// Path doesn't exist yet - still validate it would be within base
|
||||||
|
if full_path.starts_with(&canonical_base) {
|
||||||
|
Ok(Some(full_path))
|
||||||
|
} else {
|
||||||
|
Err(ExtensionError::SecurityViolation {
|
||||||
|
reason: format!("Path outside base directory: {}", relative_path),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates icon path and falls back to favicon.ico if not specified
|
||||||
|
fn validate_and_resolve_icon_path(
|
||||||
|
extension_dir: &PathBuf,
|
||||||
|
haextension_dir: &str,
|
||||||
|
icon_path: Option<&str>,
|
||||||
|
) -> Result<Option<String>, ExtensionError> {
|
||||||
|
// If icon is specified in manifest, validate it
|
||||||
|
if let Some(icon) = icon_path {
|
||||||
|
if let Some(clean_path) = Self::validate_path_in_directory(extension_dir, icon, true)? {
|
||||||
|
return Ok(Some(clean_path.to_string_lossy().to_string()));
|
||||||
|
} else {
|
||||||
|
eprintln!("WARNING: Icon path specified in manifest not found: {}", icon);
|
||||||
|
// Continue to fallback logic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback 1: Check haextension/favicon.ico
|
||||||
|
let haextension_favicon = format!("{}/favicon.ico", haextension_dir);
|
||||||
|
if let Some(clean_path) = Self::validate_path_in_directory(extension_dir, &haextension_favicon, true)? {
|
||||||
|
return Ok(Some(clean_path.to_string_lossy().to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback 2: Check public/favicon.ico
|
||||||
|
if let Some(clean_path) = Self::validate_path_in_directory(extension_dir, "public/favicon.ico", true)? {
|
||||||
|
return Ok(Some(clean_path.to_string_lossy().to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// No icon found
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
/// Extrahiert eine Extension-ZIP-Datei und validiert das Manifest
|
/// Extrahiert eine Extension-ZIP-Datei und validiert das Manifest
|
||||||
fn extract_and_validate_extension(
|
fn extract_and_validate_extension(
|
||||||
bytes: Vec<u8>,
|
bytes: Vec<u8>,
|
||||||
@ -108,42 +193,17 @@ impl ExtensionManager {
|
|||||||
.unwrap_or("haextension")
|
.unwrap_or("haextension")
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
// Security: Validate that haextension_dir doesn't contain ".." for path traversal
|
|
||||||
if dir.contains("..") {
|
|
||||||
return Err(ExtensionError::ManifestError {
|
|
||||||
reason: "Invalid haextension_dir: path traversal with '..' not allowed".to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
dir
|
dir
|
||||||
} else {
|
} else {
|
||||||
"haextension".to_string()
|
"haextension".to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build the manifest path
|
// Validate manifest path using helper function
|
||||||
let manifest_path = temp.join(&haextension_dir).join("manifest.json");
|
let manifest_relative_path = format!("{}/manifest.json", haextension_dir);
|
||||||
|
let manifest_path = Self::validate_path_in_directory(&temp, &manifest_relative_path, true)?
|
||||||
// Ensure the resolved path is still within temp directory (safety check against path traversal)
|
.ok_or_else(|| ExtensionError::ManifestError {
|
||||||
let canonical_temp = temp.canonicalize()
|
|
||||||
.map_err(|e| ExtensionError::Filesystem { source: e })?;
|
|
||||||
|
|
||||||
// Only check if manifest_path parent exists to avoid errors
|
|
||||||
if let Some(parent) = manifest_path.parent() {
|
|
||||||
if let Ok(canonical_manifest_dir) = parent.canonicalize() {
|
|
||||||
if !canonical_manifest_dir.starts_with(&canonical_temp) {
|
|
||||||
return Err(ExtensionError::ManifestError {
|
|
||||||
reason: "Security violation: manifest path outside extension directory".to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if manifest exists
|
|
||||||
if !manifest_path.exists() {
|
|
||||||
return Err(ExtensionError::ManifestError {
|
|
||||||
reason: format!("manifest.json not found at {}/manifest.json", haextension_dir),
|
reason: format!("manifest.json not found at {}/manifest.json", haextension_dir),
|
||||||
});
|
})?;
|
||||||
}
|
|
||||||
|
|
||||||
let actual_dir = temp.clone();
|
let actual_dir = temp.clone();
|
||||||
let manifest_content =
|
let manifest_content =
|
||||||
@ -151,7 +211,11 @@ impl ExtensionManager {
|
|||||||
reason: format!("Cannot read manifest: {}", e),
|
reason: format!("Cannot read manifest: {}", e),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let manifest: ExtensionManifest = serde_json::from_str(&manifest_content)?;
|
let mut manifest: ExtensionManifest = serde_json::from_str(&manifest_content)?;
|
||||||
|
|
||||||
|
// Validate and resolve icon path with fallback logic
|
||||||
|
let validated_icon = Self::validate_and_resolve_icon_path(&actual_dir, &haextension_dir, manifest.icon.as_deref())?;
|
||||||
|
manifest.icon = validated_icon;
|
||||||
|
|
||||||
let content_hash = ExtensionCrypto::hash_directory(&actual_dir, &manifest_path).map_err(|e| {
|
let content_hash = ExtensionCrypto::hash_directory(&actual_dir, &manifest_path).map_err(|e| {
|
||||||
ExtensionError::SignatureVerificationFailed {
|
ExtensionError::SignatureVerificationFailed {
|
||||||
@ -525,7 +589,7 @@ impl ExtensionManager {
|
|||||||
|
|
||||||
// 1. Extension-Eintrag erstellen mit generierter UUID
|
// 1. Extension-Eintrag erstellen mit generierter UUID
|
||||||
let insert_ext_sql = format!(
|
let insert_ext_sql = format!(
|
||||||
"INSERT INTO {} (id, name, version, author, entry, icon, public_key, signature, homepage, description, enabled) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
"INSERT INTO {} (id, name, version, author, entry, icon, public_key, signature, homepage, description, enabled, single_instance) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
TABLE_EXTENSIONS
|
TABLE_EXTENSIONS
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -545,6 +609,7 @@ impl ExtensionManager {
|
|||||||
extracted.manifest.homepage,
|
extracted.manifest.homepage,
|
||||||
extracted.manifest.description,
|
extracted.manifest.description,
|
||||||
true, // enabled
|
true, // enabled
|
||||||
|
extracted.manifest.single_instance.unwrap_or(false),
|
||||||
],
|
],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@ -623,7 +688,7 @@ impl ExtensionManager {
|
|||||||
// Lade alle Daten aus der Datenbank
|
// Lade alle Daten aus der Datenbank
|
||||||
let extensions = with_connection(&state.db, |conn| {
|
let extensions = with_connection(&state.db, |conn| {
|
||||||
let sql = format!(
|
let sql = format!(
|
||||||
"SELECT id, name, version, author, entry, icon, public_key, signature, homepage, description, enabled FROM {}",
|
"SELECT id, name, version, author, entry, icon, public_key, signature, homepage, description, enabled, single_instance FROM {}",
|
||||||
TABLE_EXTENSIONS
|
TABLE_EXTENSIONS
|
||||||
);
|
);
|
||||||
eprintln!("DEBUG: SQL Query before transformation: {}", sql);
|
eprintln!("DEBUG: SQL Query before transformation: {}", sql);
|
||||||
@ -655,13 +720,16 @@ impl ExtensionManager {
|
|||||||
})?
|
})?
|
||||||
.to_string(),
|
.to_string(),
|
||||||
author: row[3].as_str().map(String::from),
|
author: row[3].as_str().map(String::from),
|
||||||
entry: row[4].as_str().unwrap_or("index.html").to_string(),
|
entry: row[4].as_str().map(String::from),
|
||||||
icon: row[5].as_str().map(String::from),
|
icon: row[5].as_str().map(String::from),
|
||||||
public_key: row[6].as_str().unwrap_or("").to_string(),
|
public_key: row[6].as_str().unwrap_or("").to_string(),
|
||||||
signature: row[7].as_str().unwrap_or("").to_string(),
|
signature: row[7].as_str().unwrap_or("").to_string(),
|
||||||
permissions: ExtensionPermissions::default(),
|
permissions: ExtensionPermissions::default(),
|
||||||
homepage: row[8].as_str().map(String::from),
|
homepage: row[8].as_str().map(String::from),
|
||||||
description: row[9].as_str().map(String::from),
|
description: row[9].as_str().map(String::from),
|
||||||
|
single_instance: row[11]
|
||||||
|
.as_bool()
|
||||||
|
.or_else(|| row[11].as_i64().map(|v| v != 0)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let enabled = row[10]
|
let enabled = row[10]
|
||||||
@ -722,33 +790,12 @@ impl ExtensionManager {
|
|||||||
Ok(config_content) => {
|
Ok(config_content) => {
|
||||||
match serde_json::from_str::<serde_json::Value>(&config_content) {
|
match serde_json::from_str::<serde_json::Value>(&config_content) {
|
||||||
Ok(config) => {
|
Ok(config) => {
|
||||||
let dir = config
|
config
|
||||||
.get("dev")
|
.get("dev")
|
||||||
.and_then(|dev| dev.get("haextension_dir"))
|
.and_then(|dev| dev.get("haextension_dir"))
|
||||||
.and_then(|dir| dir.as_str())
|
.and_then(|dir| dir.as_str())
|
||||||
.unwrap_or("haextension")
|
.unwrap_or("haextension")
|
||||||
.to_string();
|
.to_string()
|
||||||
|
|
||||||
// Security: Validate that haextension_dir doesn't contain ".."
|
|
||||||
if dir.contains("..") {
|
|
||||||
eprintln!(
|
|
||||||
"DEBUG: Invalid haextension_dir for: {}, contains '..'",
|
|
||||||
extension_id
|
|
||||||
);
|
|
||||||
self.missing_extensions
|
|
||||||
.lock()
|
|
||||||
.map_err(|e| ExtensionError::MutexPoisoned {
|
|
||||||
reason: e.to_string(),
|
|
||||||
})?
|
|
||||||
.push(MissingExtension {
|
|
||||||
id: extension_id.clone(),
|
|
||||||
public_key: extension_data.manifest.public_key.clone(),
|
|
||||||
name: extension_data.manifest.name.clone(),
|
|
||||||
version: extension_data.manifest.version.clone(),
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
dir
|
|
||||||
}
|
}
|
||||||
Err(_) => "haextension".to_string(),
|
Err(_) => "haextension".to_string(),
|
||||||
}
|
}
|
||||||
@ -759,12 +806,14 @@ impl ExtensionManager {
|
|||||||
"haextension".to_string()
|
"haextension".to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if manifest.json exists in the haextension_dir
|
// Validate manifest.json path using helper function
|
||||||
let manifest_path = extension_path.join(&haextension_dir).join("manifest.json");
|
let manifest_relative_path = format!("{}/manifest.json", haextension_dir);
|
||||||
if !manifest_path.exists() {
|
if Self::validate_path_in_directory(&extension_path, &manifest_relative_path, true)?
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"DEBUG: manifest.json missing for: {} at {:?}",
|
"DEBUG: manifest.json missing or invalid for: {} at {}/manifest.json",
|
||||||
extension_id, manifest_path
|
extension_id, haextension_dir
|
||||||
);
|
);
|
||||||
self.missing_extensions
|
self.missing_extensions
|
||||||
.lock()
|
.lock()
|
||||||
|
|||||||
@ -57,13 +57,20 @@ pub struct ExtensionManifest {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
pub version: String,
|
pub version: String,
|
||||||
pub author: Option<String>,
|
pub author: Option<String>,
|
||||||
pub entry: String,
|
#[serde(default = "default_entry_value")]
|
||||||
|
pub entry: Option<String>,
|
||||||
pub icon: Option<String>,
|
pub icon: Option<String>,
|
||||||
pub public_key: String,
|
pub public_key: String,
|
||||||
pub signature: String,
|
pub signature: String,
|
||||||
pub permissions: ExtensionPermissions,
|
pub permissions: ExtensionPermissions,
|
||||||
pub homepage: Option<String>,
|
pub homepage: Option<String>,
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub single_instance: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_entry_value() -> Option<String> {
|
||||||
|
Some("index.html".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExtensionManifest {
|
impl ExtensionManifest {
|
||||||
@ -172,6 +179,8 @@ pub struct ExtensionInfoResponse {
|
|||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
pub homepage: Option<String>,
|
pub homepage: Option<String>,
|
||||||
pub icon: Option<String>,
|
pub icon: Option<String>,
|
||||||
|
pub entry: Option<String>,
|
||||||
|
pub single_instance: Option<bool>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub dev_server_url: Option<String>,
|
pub dev_server_url: Option<String>,
|
||||||
}
|
}
|
||||||
@ -197,6 +206,8 @@ impl ExtensionInfoResponse {
|
|||||||
description: extension.manifest.description.clone(),
|
description: extension.manifest.description.clone(),
|
||||||
homepage: extension.manifest.homepage.clone(),
|
homepage: extension.manifest.homepage.clone(),
|
||||||
icon: extension.manifest.icon.clone(),
|
icon: extension.manifest.icon.clone(),
|
||||||
|
entry: extension.manifest.entry.clone(),
|
||||||
|
single_instance: extension.manifest.single_instance,
|
||||||
dev_server_url,
|
dev_server_url,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/// src-tauri/src/extension/mod.rs
|
/// src-tauri/src/extension/mod.rs
|
||||||
use crate::{
|
use crate::{
|
||||||
extension::{
|
extension::{
|
||||||
core::{EditablePermissions, ExtensionInfoResponse, ExtensionPreview},
|
core::{manager::ExtensionManager, EditablePermissions, ExtensionInfoResponse, ExtensionPreview},
|
||||||
error::ExtensionError,
|
error::ExtensionError,
|
||||||
},
|
},
|
||||||
AppState,
|
AppState,
|
||||||
@ -320,20 +320,19 @@ pub async fn load_dev_extension(
|
|||||||
}
|
}
|
||||||
eprintln!("✅ Dev server is reachable");
|
eprintln!("✅ Dev server is reachable");
|
||||||
|
|
||||||
// 2. Build path to manifest: <extension_path>/<haextension_dir>/manifest.json
|
// 2. Validate and build path to manifest: <extension_path>/<haextension_dir>/manifest.json
|
||||||
let manifest_path = extension_path_buf
|
let manifest_relative_path = format!("{}/manifest.json", haextension_dir);
|
||||||
.join(&haextension_dir)
|
let manifest_path = ExtensionManager::validate_path_in_directory(
|
||||||
.join("manifest.json");
|
&extension_path_buf,
|
||||||
|
&manifest_relative_path,
|
||||||
// Check if manifest exists
|
true,
|
||||||
if !manifest_path.exists() {
|
)?
|
||||||
return Err(ExtensionError::ManifestError {
|
.ok_or_else(|| ExtensionError::ManifestError {
|
||||||
reason: format!(
|
reason: format!(
|
||||||
"Manifest not found at: {}. Make sure you run 'npx @haexhub/sdk init' first.",
|
"Manifest not found at: {}/manifest.json. Make sure you run 'npx @haexhub/sdk init' first.",
|
||||||
manifest_path.display()
|
haextension_dir
|
||||||
),
|
),
|
||||||
});
|
})?;
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Read and parse manifest
|
// 3. Read and parse manifest
|
||||||
let manifest_content =
|
let manifest_content =
|
||||||
|
|||||||
@ -50,7 +50,7 @@ export const useExtensionsStore = defineStore('extensionsStore', () => {
|
|||||||
currentExtension.value.publicKey,
|
currentExtension.value.publicKey,
|
||||||
currentExtension.value.name,
|
currentExtension.value.name,
|
||||||
currentExtension.value.version,
|
currentExtension.value.version,
|
||||||
'index.html',
|
currentExtension.value.entry ?? 'index.html',
|
||||||
currentExtension.value.devServerUrl ?? undefined,
|
currentExtension.value.devServerUrl ?? undefined,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user