Skip to content

Conversation

@evereq
Copy link
Member

@evereq evereq commented Dec 24, 2025

PR

Please note: we will close your PR without comment if you do not check the boxes above and provide ALL requested information.



Summary by cubic

Modernized CI/CD and introduced a full plugin marketplace backend with subscriptions, billing, and access control. Also improved desktop plugin lifecycle, updated Angular, and standardized app build outputs.

  • New Features

    • Plugin marketplace tables and domain: subscriptions, billing, access guards, tenant ACLs, pagination, and soft delete.
    • Desktop plugins: installationId tracking, offline DB migration, safer activation/deactivation, and a streaming CDN downloader with size/timeouts checks.
    • Electron apps: per-arch builds (x64/arm64) with artifact names including arch; added Angular/Nebular developer instructions; clarified downloads in README.
  • Dependencies

    • Node.js upgraded to 24.12.0 across CI/Docker; NODE_OPTIONS reduced for Docker; engines set to >=22.
    • Angular dependencies bumped to 19.2.17 across apps.
    • GitHub Actions: added timeouts, switched runners to ubicloud-standard-8, and removed the Windows workflow; CircleCI node orb updated.
    • Expanded cspell dictionary.

Written for commit d4c3405. Summary will update automatically on new commits.

evereq and others added 23 commits December 20, 2025 14:24
Updated the downloads section to clarify that all releases and pre-releases are available from specified pages.
* feat(plugin-subscription-access): add access control and user assignment

Implement a comprehensive system for managing plugin subscription access,
including hierarchical subscriptions, user assignment, and revocation.
This introduces:
- Parent-child subscription relationships in PluginSubscription entity,
enabling organization/tenant-level subscriptions to manage individual
user access.
- Commands, handlers, DTOs, and API endpoints for assigning and revoking
plugin access to users.
- A dedicated PluginSubscriptionAccessService for centralized validation
of user access, assignment permissions, and subscription details.
- A PluginSubscriptionAccessGuard for protecting plugin routes based on
subscription status.
- Refinements to the subscription purchase logic to automatically set
USER scope for free plans and handle initial statuses (ACTIVE, PENDING, TRIAL).

* feat(plugins-access): implement plugin subscription access management

Introduce a robust system for managing and controlling access to plugins based on subscriptions. This includes:
- New Akita state management (store, query, facade) to track plugin and user-specific access.
- Dedicated service for API interactions (check, assign, revoke access, bulk checks).
- UI dialogs (PluginUserAssignmentDialogComponent, PluginUserRevocationDialogComponent) for managing user assignments and revocations.
- An Angular guard (PluginAccessGuard) to protect plugin routes and prompt for subscription if needed.
- Updates to the plugin marketplace item to display access levels and enable user assignment actions.
- Extend plugin subscription plans to support various access scopes (user, organization, tenant, global).
- Add component for displaying plugin subscription hierarchy.

* feat: Implement subscription management for plugin installations

- Refactored plugin installation logic to handle subscription-based plugins.
- Introduced strategies for free and subscription-based plugin installations.
- Added validation chain for installation prerequisites, including access and subscription checks.
- Created a facade for managing installation processes and strategies.
- Implemented access guard to verify user permissions for plugin installations.
- Enhanced UI components to reflect subscription requirements during installation.
- Added validators for plugin status and subscription requirements.
- Improved error handling and user notifications for subscription-related actions.

* feat(plugin-tenant): add PluginTenantService for managing plugin-tenant relationships

* refactor(plugin-marketplace): remove analytics tracking from various actions

* feat(plugin-subscription): add ability to upgrade and downgrade plans

Introduce new functionality for users to change their active plugin
subscription plans. This includes new Redux actions and effects for
managing the upgrade and downgrade processes, alongside new API
endpoints to handle these requests on the backend.
The plugin subscription selection dialog has been enhanced to:
* Detect and display the user's current active subscription plan.
* Visually distinguish the current plan, making it unselectable.
* Dynamically update the primary action button to reflect "Upgrade
Plan", "Downgrade Plan", or "Current Plan" based on the selected
plan and the user's existing subscription status.
* Implement client-side logic to determine the appropriate plan
change type (upgrade or downgrade) based on plan hierarchy.

* feat(plugin-installation): streamline plugin removal by simplifying parameters

* feat(plugin-marketplace): implement lazy loaded detail tabs

Refactor plugin marketplace item detail page to use NbRouteTabset with lazy-loaded child routes.
This improves initial load performance by only loading tab content when activated.
New tabs introduced:
* Settings: Configure plugin-specific options (permission-gated).
* User Management: Assign and unassign users to the plugin (permission-gated).
Existing "Overview" and "Source Code" tab content has been extracted into dedicated lazy-loaded components to align with this new structure.
Tabs are dynamically rendered based on user permissions for configuration and assignment.

* feat(plugin-user-management): implement user assignment facade and dialogs

Introduce PluginUserAssignmentFacade to centralize business logic and
provide a simplified API for managing plugin user assignments. This
enhances testability, maintainability, and improves reactive data flow.
Refactor PluginUserAssignmentEffects to include:
- Comprehensive loading state management for all assignment operations.
- Translateable success and error notifications using ToastrNotificationService.
- Direct store updates on success (add, remove, set assignments).
- Flexible loading of assignments for a plugin or specific installation.
Add showAssignmentDialog, hideAssignmentDialog, showRevocationDialog, and
hideRevocationDialog effects in PluginSubscriptionAccessEffects to
orchestrate UI dialogs for user assignment and revocation.
Update UserManagementTabComponent to consume the new
PluginUserAssignmentFacade via a consolidated view model. This reduces
component complexity, simplifies template logic, and ensures proper
handling of loading and error states in the UI.

* feat(plugin-installation): return structured response for install and uninstall

The `create` and `remove` endpoints for plugin installation and
uninstallation now return a structured JSON response. Previously,
these methods returned `void`, providing no body on success.
This change enhances client feedback by including a descriptive
message and the HTTP status code in the response body.

* feat(plugins-registry): return paginated results for plugin user assignments

Update query handlers and service methods related to plugin user assignments
to return `IPagination<PluginUserAssignment>` instead of a plain array.
This standardizes list retrieval operations by providing pagination metadata.
Also, correct the instantiation of `TypeOrmPluginUserAssignmentRepository`
to properly inject and utilize the underlying TypeORM repository.
BREAKING CHANGE: Plugin user assignment query handlers and service methods
now return an IPagination object instead of a plain array. Consumers
expecting an array will need to adapt to the new return structure (e.g.,
accessing `result.items`).

* feat(plugins): add infinite scroll and pagination for user assignments

This commit introduces infinite scroll functionality to the plugin user assignment lists, significantly enhancing the user experience and performance when dealing with a large number of assigned users.
Key changes include:
- A new `InfiniteScrollDirective` in `desktop-ui-lib` to detect scroll events and emit `scrolled` events.
- Implementation of `take` and `skip` based pagination across plugin user assignment APIs (frontend and backend) and state management (Akita store, query, facade, effects). This forms the foundation for efficient data loading.
- Refactoring of plugin user assignment, unassignment, and access check mechanisms to utilize a subscription-based access control model. This involves changes to service methods and API endpoints, promoting a more scalable and consistent approach to managing plugin access.
The combined changes allow for lazy loading of user assignments, reducing initial load times and memory footprint.

* refactor(plugin-user-assignment): update user interface and improve state selectors

* feat(plugin-user-management): implement available users state management and infinite scroll

Introduces a dedicated Akita state management module (`_available-users` store, query, effects) for handling the list of users available for plugin assignment. This refactors the user fetching, filtering, and selection logic, improving separation of concerns and maintainability.
The `PluginUserManagementComponent` has been refactored to utilize a new `UserManagementFacade`, which orchestrates interactions between the available users and plugin user assignment state.
Key changes include:
- Added `AvailableUsersActions`, `AvailableUsersEffects`, `AvailableUsersQuery`, `AvailableUsersStore` for robust state management.
- Created `UserManagementFacade` to provide a high-level, component-agnostic API for user management.
- Refactored `PluginUserManagementComponent` to delegate all business logic to the `UserManagementFacade`.
- Implemented infinite scroll for the "Available Users" list, enhancing UX for large user bases.
- Updated `plugins.module` to include new state management providers and the `InfiniteScrollDirective`.
- Adjusted `UserManagementTabComponent` to clear and reload assignments after unassignment for improved data consistency.

* refactor(plugin-user-management): reorder initialization methods in ngOnInit for better clarity

* chore(plugin-marketplace): update store config names

Rename store configuration names to use snake_case instead of
kebab-case. This change ensures consistent naming conventions
across the application's state management modules. Additionally,
the 'plugin-user-assignment' store name has been pluralized to
'plugin_user_assignments' for improved semantic clarity.

* fix(plugin-marketplace): correct total count for available users

Remove client-side pagination logic and derive the total count
for available users directly from the API response. This ensures
that the displayed total accurately reflects the backend's
pagination and overall count, preventing data inconsistencies.
Also add a debug log to `loadAssignmentsSuccess` for better visibility.

* refactor: streamline effects in plugin subscription access and user assignment

- Updated effects in `plugin-subscription-access.effects.ts` for better readability and consistency.
- Added dispatch options to effects where necessary.
- Removed unused imports in `plugin-subscription.effect.ts`.
- Refactored loading and assignment effects in `plugin-user-assignment.effects.ts` for clarity and efficiency.
- Improved error handling messages for user assignment actions.
- Cleaned up module imports in `plugins.module.ts` for better organization.

* feat(i18n): enhance plugin user management with new loading states and action messages

* feat(plugins): enhance plugin subscription access and activation flow

Introduce `IPluginAccess` contract and `PluginScope` enum to standardize plugin access details across the platform.
Implement server-side validation for plugin activation and deactivation, ensuring that actions are authorized before local execution. This improves security and adherence to subscription rules.
Refine `PluginSubscriptionAccessService.getSubscriptionDetails` to provide comprehensive access information, including:
- Determining if a plugin requires a subscription.
- Calculating `canAssign` and `canActivate` permissions based on subscription ownership and user assignments.
- Validating subscription parent relationships.
Consolidate `PluginScope` enum definition into the `@gauzy/contracts` package for better shared model management.

* feat(plugin-installation): implement multi-step workflow with installation ID

Introduce a unique installation ID and a comprehensive multi-step workflow
for managing plugin installations. This enhancement provides a robust mechanism
to track plugins across the desktop application and the server-side registry,
improving reliability and traceability of plugin lifecycle events.
Key changes include:
- Add `installationId` column to the `plugins` database table via new migration.
- Update plugin metadata interfaces, services, and the `plugin-manager` to
support storing and managing the `installationId`.
- Refactor the desktop UI's plugin installation process into distinct
steps: download, server-side registration, local completion, and activation.
- Utilize a Command Pattern for each installation step in the UI, enabling
better separation of concerns, explicit error handling, and potential for
rollback mechanisms.
- Server-side plugin installation now returns the generated `installationId`.
- New IPC channel and Electron service method `completeInstallation` added
to sync the `installationId` locally after server registration.
- Update plugin activation, deactivation, and uninstallation logic across
desktop-lib and desktop-ui-lib to explicitly use the `installationId`
for server communication.

* feat(plugins): enhance plugin lifecycle authorization and flow

This commit introduces several improvements to the plugin management
lifecycle, primarily focusing on enhanced security, authorization,
and a more robust client-side installation experience.
Key changes include:
- Enforce employee-specific authorization for plugin installation,
activation, deactivation, and uninstallation, ensuring actions are
tied to the installing employee.
- The `ActivatePluginCommand` now explicitly includes `pluginId`
alongside `installationId` for more precise control over activation.
- Client-side plugin installation completion now utilizes an event-driven
progress observable, improving monitoring and responsiveness.
- The `RequestContext.currentOrganizationId()` helper can now retrieve
the organization ID from the `organization-id` request header.
- The legacy `PluginInstallHandler` and `PluginUninstallHandler` have
been removed.
- The `PluginSubscriptionAccessGuard` now performs stricter validation
for missing plugin IDs.

* fix(plugin-marketplace): reset installing flag after plugin installation

Ensure the 'installing' flag in the plugin installation store is
explicitly set to false when an installation process finalizes.
Previously, only `completingInstallation` was reset, which could
lead to an incorrect UI state where a plugin appeared to be
stuck in an 'installing' state after completion or failure.

* feat(i18n): add strings for categories, plugin management, and user assignment

Add comprehensive internationalization strings to support new and enhanced
UI features. This includes:
*   New success, error, and info messages for managing plugin categories
(CRUD, load operations).
*   Messages for successful assignment/unassignment of users to plugins,
including bulk actions, and related error states.
*   Expanded plugin lifecycle messages covering subscription, download, and
server installation progress.
*   New confirmation and error messages for plugin source deletion and
restoration.
*   Improved parameter naming for plugin version toasts (from `number` to
`version`).
*   Additional generic error messages for plugin forms and data.

* refactor(plugins): streamline billing and subscription entities

Removed direct billing, pricing, invoice, and payment retry-related fields from
PluginBilling and PluginSubscription entities.
Shifted responsibility for subscription pricing, type, and billing period
to PluginSubscriptionPlan, referenced by a new planId on PluginSubscription.
Introduced computed properties (isOverdue, isPending, daysUntilDue) on
PluginBilling for derived status based on existing data.
Reimplemented nextBillingDate and isBillingDue as computed properties
on PluginSubscription, deriving their state from associated PluginBilling
records rather than stored fields.
Updated related DTOs and interfaces (IPluginBillingCreateInput,
IPluginSubscriptionCreateInput, IPluginSubscriptionPurchaseInput, etc.) to
reflect the entity simplifications and removal of redundant fields.
This refactor reduces data redundancy, improves data consistency by
centralizing billing plan details, and better encapsulates runtime state
through computed properties.

* refactor(plugins): simplify user assignments and refine setting management

Remove the dedicated `PluginUserAssignment` entity and its corresponding interfaces,
consolidating user assignment logic within the plugin subscription domain.
Update plugin user assignment handlers and services to utilize `subscriptionId`
instead of `pluginInstallationId` for clarity and consistency with the new structure.
Rework the `PluginSetting` entity:
- Store setting `value` as plain `text` instead of `jsonb`, simplifying data handling.
- Introduce a unique index on `pluginId`, `key`, and `pluginTenantId` to prevent
duplicate configurations.
- Add an `updatedById` field to track the user who last modified a setting.
- Enhance validation rules and `ApiProperty` decorations for existing fields.
- Add `ColumnIndex` decorations for improved query performance.

* refactor(plugins): simplify user assignment and subscription management

Removed `PluginUserAssignment` entity, its repositories, and related models.
Refactored plugin user assignment commands, queries, and handlers to use
`pluginId` directly instead of `pluginInstallationId`. Replaced complex
assignment logic in `PluginUserAssignmentService` with placeholder
implementations, indicating a future re-design.
Updated plugin subscription logic to utilize a `planId` for managing
subscription details (type, billing period, trial days) instead of direct
fields. Streamlined `PluginBillingService` by removing internal total amount
calculation and retry logic.
Restructured plugin user assignment API controllers and DTOs to align with
the new, simplified model, removing `installationId` from endpoints and DTOs.
This change is a preparatory step for a more robust and flexible plugin
management system.

* feat(plugins/billing): implement comprehensive plugin billing system

Introduce a new billing system for plugin subscriptions using a CQRS and
event-driven architecture. This includes:
*   **Billing Commands:** Commands for creating billing records and processing
payments.
*   **Command Handlers:** Logic to handle billing creation and payment
processing, integrating with a new `PluginBillingFactory`.
*   **Billing Events:** Domain events for billing lifecycle states (created,
paid, failed, overdue).
*   **Event Handlers:** React to billing events to update subscription metadata,
change status (e.g., suspend on multiple failures), and trigger
notifications (future implementation).
*   **PluginBillingFactory:** Encapsulates business logic for creating various
types of billing records (initial, renewal, upgrade).
The `PluginSubscriptionService` is updated to utilize this new billing flow for
purchasing, renewing, and upgrading subscriptions, promoting a cleaner
separation of concerns and a more robust billing lifecycle management.
It also introduces new helper methods to determine subscription scope and
initial status based on plan type.

* feat(billing): formalize subscription billing calculations

Refactor subscription billing logic in PluginBillingFactory to extract complex
calculations into a new private method, `calculateBillingAmount`.
This centralizes the application of base price, discounts, and setup fees,
ensuring consistency and clarity. The `createForRenewal` and `createInitialBilling`
methods now accept the full plan object, simplifying their signatures and
enabling richer metadata capture.
Billing records are enhanced with detailed metadata including `basePrice`,
`discountAmount`, `setupFee`, `effectivePrice`, and a comprehensive breakdown
string, improving traceability and auditability of charges.
Explicitly clarifies that discounts and setup fees apply only to initial billing.
Extensive inline documentation has been added to the factory to outline these rules.

* feat(plugin-subscription): add subscriber for entity lifecycle management

Implement a TypeORM subscriber for `PluginSubscription` entities.
This subscriber's `afterLoad` hook automatically updates the in-memory
subscription status to `EXPIRED` if its `endDate` has passed, ensuring
the entity's state is consistent upon loading. It also logs the current
state of the subscription for debugging and monitoring.
Additionally, refactor `PluginSubscription` entity to use the
`PluginBillingStatus` enum for billing status checks, enhancing type
safety and readability.

* fix(plugin-system): always deactivate plugin before deletion

Ensure plugins are always cleanly shut down before their files and metadata are
removed, preventing potential resource leaks or orphaned processes.
Previously, deactivation was conditional on the plugin's `isActivate` metadata
flag, which could lead to improper cleanup if the metadata was out of sync or
the plugin was in an inconsistent state.

* refactor(plugin-subscriptions): consolidate domain logic and access

Transition the plugin subscription management to a rich domain model by encapsulating business rules and state changes directly within the `PluginSubscription` entity.
Introduce `PluginSubscriptionAccessService` to centralize complex access validation and permissions, leveraging Strategy and Factory patterns for modularity. This service now orchestrates access checks across different subscription scopes (user, organization, tenant).
Update the `PluginSubscriptionService` to delegate business logic to the `PluginSubscription` entity's new domain methods for operations like cancellation, renewal, upgrade, downgrade, and trial extension.
Remove the `PluginUserAssignmentService` as its responsibilities are now integrated into the centralized subscription access architecture.
Adjust consuming components, including `PluginAccessGuard`, `PluginInstallationAccessGuard`, `PluginSubscriptionGuard`, and `CheckPluginAccessQueryHandler`, to utilize the new `PluginSubscriptionAccessService` for consistent and robust access control.
This refactor improves maintainability, testability, and adherence to domain-driven design principles. Additionally, trial subscriptions are now correctly identified as active during access checks.

* feat(plugins-registry): add plugin user assignment service

Introduce a lightweight stub for the PluginUserAssignmentService. This
service provides an interface for managing user assignments to plugins,
including methods to assign, unassign, and check user access.
The current implementation is minimal, designed primarily to satisfy
imports and provide a consistent API for command and query handlers.
Full business logic and persistence will be implemented in a subsequent
iteration.

* feat(plugin-registry): implement plugin queries and handlers

- Add GetPluginQuery for fetching a plugin by ID.
- Implement CheckPluginAccessQueryHandler to validate plugin access.
- Create GetPluginQueryHandler to retrieve plugin details.
- Introduce ListPluginsQuery and ListPluginsQueryHandler for paginated plugin listing.
- Add SearchPluginsQuery and SearchPluginsQueryHandler for advanced plugin searching.
- Establish a new index for plugin queries and handlers.
- Refactor plugin subscription strategies for creating and updating subscription plans.
- Implement PluginSubscriptionGuard to ensure valid subscriptions before plugin installation.
- Update shared DTOs and models for better organization and access.
- Enhance infrastructure with new controllers, guards, and storage strategies.

* refactor: reorganize imports and exports

This commit refactors the import and export statements across
multiple files within the plugin registry package.  The goal is
to improve code organization, reduce redundancy, and clarify
dependencies.
Specifically, it addresses the following:
-   Consolidates exports within the `index.ts` files of the
`application`, `domain`, `infrastructure`, and `shared`
directories. This makes it easier to understand the public API
of each module.
-   Reorganizes imports to use relative paths for internal modules and
absolute paths for external dependencies.
-   Refactors the structure to leverage the index.ts for exporting,
reducing the lines of code.
This change doesn't introduce any functional changes but aims to
improve maintainability and readability.

* feat: Add validation for plugin settings and subscription plans

This commit enhances the plugin registry by:
- Adding validation rules for plugin setting creation and updates.
- Implementing validation and defaults for plugin subscription plans.
- Ensuring date fields in plugin subscriptions are properly converted.
- Adding default activation status for subscription plans and subscriptions.
- Allowing stringified JSON for validation rules.

* refactor: Remove unused imports and reorder imports

Removes unused imports and reorders the imports to improve
code readability and maintainability across multiple files.

* refactor: Add business logic methods to entities

Adds business logic methods to `Plugin`, `PluginVersion`,
`PluginSource`, and `PluginInstallation` entities. Also adds static
methods and interfaces to the models.  These methods encapsulate
plugin-related operations such as status checks, version
comparisons, and download counts.

* refactor: use Plugin.create to instantiate plugin

Refactor the plugin instantiation to use `Plugin.create` method
instead of `Object.assign`. This aligns with the project's
intended object creation pattern.

* feat: implement plugin tenant management

This commit introduces a new feature for managing plugin tenants,
allowing for tenant-specific configurations and access controls.
The changes include:
- Added commands and queries for creating, updating, deleting,
enabling, disabling, and approving plugin tenants.
- Implemented command handlers and query handlers to process the
respective commands and queries.
- Added a controller for managing plugin tenants through API endpoints.
- Introduced DTOs for data transfer and validation.
- Updated the plugin model to remove the GLOBAL plugin scope.
- Exported plugin tenant modules.

* fix: apply fixes and improvements across plugins registry

This commit addresses several issues within the plugins registry:
- Removes redundant error message for GLOBAL plugin scope.
- Fixes module import for plugin tenants.
- Improves logic for determining subscription plan operation type.
- Ensures planId is correctly passed during subscription plan updates.
- Adds repository url to CreatePluginDTO and boolean transformation for requiresSubscription

* refactor(plugins): introduce dedicated DTO and remove client-side category counting

Introduce a new `PluginQueryOptions` DTO to provide strongly-typed
query parameters for fetching a single plugin. This replaces the
generic `BaseQueryDTO` in the `PluginController` and `GetPluginQuery`,
improving type safety and API documentation.
In the desktop UI, the client-side logic for calculating plugin category
counts (`updateCategoryCounts`) has been removed from the marketplace
filter component. This simplifies the frontend, as category counts are
now handled and delivered by the backend.

* refactor(plugins): simplify PluginSubscriptionService by removing unused methods and dependencies

* feat(subscription): add computed properties and helper methods to IPluginSubscriptionPlan interface

* refactor(plugin-subscription): move business logic into domain models

Refactor the command handlers for subscription actions (cancel, upgrade, downgrade, renew) to use a richer domain model approach.
Business logic and validation rules, previously handled within the service layer, are now encapsulated as methods directly on the subscription domain entity. Command handlers are now responsible for fetching the entity, invoking its business methods, and persisting the result.
This change aligns with Domain-Driven Design (DDD) principles, making the application layer thinner and centralizing business rules within the domain. This improves maintainability, testability, and clarity of the subscription lifecycle logic.
Additionally, the IPluginTenant interface has been extended with business logic method signatures to support this pattern more broadly.

* refactor(plugin-subscription): refactor trial extension command handler

Relocate the business logic for extending a trial subscription from the `PluginSubscriptionService` directly into the `ExtendTrialSubscriptionCommandHandler`.
This refactoring aligns with CQRS best practices by making the command handler more self-contained and responsible for its specific unit of work. The handler now fetches the subscription using the correct security context, validates the action using new domain entity methods (`canExtendTrial`, `extendTrial`), and persists the result.
This approach improves clarity, testability, and adherence to domain-driven design principles by placing business rules within the domain model itself.
As part of this cleanup, the implementation for the deprecated `PurchasePluginSubscriptionCommandHandler` has been removed.

* feat(plugin-subscription): implement plugin subscription purchase logic

This commit implements the complete logic for the
PurchasePluginSubscriptionCommandHandler. It handles the creation of
plugin subscriptions based on different scenarios, including free, trial,
and paid plans.
The handler now:
- Validates required input like plugin and tenant IDs.
- Ensures a `PluginTenant` relationship exists, creating one if needed.
- Differentiates subscription creation based on the plan type:
- Free plans result in an immediate active subscription.
- Plans with a trial period create a trial subscription.
- Paid plans create a pending subscription with a calculated end date.
- Defaults to a free subscription if no plan is specified.
- Sets additional properties like auto-renew and metadata from the DTO.

* feat(plugin-tenant): support scope on creation and set defaults

The `PluginTenantService.findOrCreate` method is updated to accept an
optional `scope` parameter. This allows callers, such as the plugin
purchase handler, to specify the installation scope (tenant,
organization, or user).
When a new `PluginTenant` relationship is created, it is now populated
with sensible defaults, including `requiresApproval: true` and
`autoInstall: false`. The `approvedById` is set to the current user
initiating the action to ensure a secure initial state.
BREAKING CHANGE: The default scope for a newly created PluginTenant
now defaults to `USER` if not explicitly provided. Previously, the scope
was inferred as `TENANT` or `ORGANIZATION` based on the presence of an
`organizationId`.

* refactor(plugin-subscription): overhaul plugin subscription and access control service

This commit completely refactors the plugin subscription and access control logic to follow Domain-Driven Design (DDD) and SOLID principles. The previous implementation was becoming difficult to maintain and extend.
Key changes include:
- Re-architected `PluginSubscriptionAccessService` to act as a true domain service.
- Introduced the Specification pattern for validating access rules (e.g., plugin enabled, subscription active, quota limits), encapsulating business logic into small, reusable components.
- Implemented a composite `AccessValidator` to orchestrate validation specifications.
- Refactored subscription finders to use a `SubscriptionFinderRegistry` with a clear Chain of Responsibility pattern (User > Organization > Tenant).
- Integrated `PluginTenant` service to manage tenant-specific configurations like installation/user quotas, approvals, and access lists.
- Rewrote multiple CQRS query handlers for subscriptions to use explicit TypeORM queries instead of high-level service methods, improving clarity and error handling. They now return empty results instead of throwing exceptions on failure.
- Simplified the `CreatePluginSubscriptionPlanCommand` to only require the DTO and the acting user's ID.
BREAKING CHANGE: The `GetPluginSubscriptionsByPluginIdQuery` and `GetPluginSubscriptionsBySubscriberIdQuery` handlers now return a paginated object `IPagination<IPluginSubscription>` instead of an array `IPluginSubscription[]`.
BREAKING CHANGE: The `CreatePluginSubscriptionPlanCommand` constructor signature has been changed, removing `tenantId` and `organizationId`.

* fix(plugin-marketplace): prevent runtime errors from non-array subscription data

The plugin marketplace could encounter runtime errors if API responses or
the internal state for subscriptions and plans were not arrays. This could
happen during initial load or with inconsistent API payloads, leading to
crashes.
This commit introduces defensive checks across the data flow:
- `PluginSubscriptionService` now normalizes API responses to ensure
methods always return an array, handling various payload structures.
- `PluginSubscriptionStore` and `PluginSubscriptionQuery` are updated to
safely handle non-array state, defaulting to an empty array for
operations like map, filter, and find.
This makes the subscription management feature more robust and resilient to
unexpected data shapes.

* feat(plugin-registry): implement plugin user access and assignment logic

This commit introduces the full implementation for the plugin user assignment feature, replacing the previous stub and placeholder logic. It provides a complete system for managing which users have access to specific plugins within a tenant and organization.
Key changes include:
- The `PluginUserAssignmentService` is fully implemented. It now manages user assignments by interacting with the `PluginTenantService` and `RoleService` to enforce access control based on user lists, roles, and quotas.
- All query handlers related to user assignments are implemented, providing real data for endpoints that list plugin assignments for a user or user assignments for a plugin.
- Pagination (`skip`, `take`) is added to all relevant query handlers and controller endpoints for fetching user assignment lists.
- Controllers are updated to use `RequestContext` to derive the current tenant and organization, ensuring operations are correctly scoped.
- `PluginSubscriptionService` now includes `createChildSubscriptions` and `revokeChildSubscriptions` methods to manage individual user subscriptions tied to a parent tenant/organization subscription.
- The `ProcessBillingCommandHandler` is simplified to use the `PluginBillingService` directly.

* feat(plugin-tenant): add pagination to get plugin-tenants queries

Adds support for pagination to the queries that retrieve plugin-tenant
relationships by either plugin ID or tenant ID.
The `GetPluginTenantsByPluginQuery` and `GetPluginTenantsByTenantQuery`
now accept optional `skip` and `take` parameters. The corresponding
controller endpoints have been updated to accept these as query
parameters, allowing clients to paginate through large result sets.
The query handlers and service methods are updated to handle these
new parameters and return a paginated response.
BREAKING CHANGE: The `/by-plugin/:pluginId` and `/by-tenant/:tenantId` endpoints now return a paginated response object of type `IPagination<IPluginTenant>` instead of a simple array.

* feat(plugin-tenant): apply plan limitations during creation

Refactor the `PluginTenantService.findOrCreate` method to accept a single
input object instead of multiple arguments. This improves readability
and makes the method easier to extend.
When purchasing a new plugin subscription, if the selected plan includes
limitations such as maximum users or installations, these values are now
passed during PluginTenant creation and stored accordingly.
Additionally, the user initiating the creation of the PluginTenant
relationship is now automatically granted access to the plugin.

* feat(plugin): set default isActive to true during plugin creation

* fix(registry): correct plugin approval and subscription checks

The `isApproved` method on the PluginTenant entity is updated to also check for the presence of `approvedById`. This makes the approval check more robust, ensuring a plugin is considered approved if it has an approver, even if the `approvedAt` timestamp is missing.
The logic for determining if a plugin requires a subscription has been refined. A new `isSubscriptionRequired` method is introduced in the plan service, which checks for active paid plans (price > 0) or if the plugin is explicitly flagged as requiring a subscription. This replaces a less accurate check and prevents free plugins from being misidentified.

* feat(plugin-subscription): implement contextual upgrade and downgrade flow

Introduce a comprehensive, state-managed subscription management flow that provides a contextual experience for users with existing subscriptions.
This change refactors the subscription selection process to be aware of the user's current plan, enabling intelligent upgrade, downgrade, and new subscription actions.
Key additions:
- A new `PlanComparisonService` encapsulates the business logic for determining action types (upgrade, downgrade), calculating prorations, and validating plan changes.
- Akita state is expanded to track the plan comparison process, confirmation steps, and current subscription/plans on a per-plugin basis.
- A new `SubscriptionConfirmationComponent` provides a clear summary of changes before the user commits.
- The subscription selection UI is significantly enhanced with:
- Skeleton loaders and improved error states.
- Visual badges and styling for upgrade/downgrade actions.
- Improved accessibility and keyboard shortcuts.

* feat(plugins): introduce subscription scope and auto-renew

This commit refactors the plugin subscription system to introduce subscription scopes (User, Organization, Tenant) and an auto-renew option. This provides more flexible subscription models and improves system robustness.
The subscription creation API payload is updated to include `scope` and `autoRenew`, while moving `billingPeriod` into the metadata object.
Frontend changes:
- Add a read-only section to display the plan's scope in the subscription dialog.
- Add an auto-renew checkbox to the subscription form.
- Automatically determine and set the subscription scope based on the selected plan type.
Backend changes:
- Add a unique database constraint (`pluginId`, `subscriberId`) to the `PluginSubscription` entity to prevent multiple subscriptions for the same user and plugin.
- Update subscription creation logic to handle pre-existing cancelled or expired subscriptions by deleting them before creating a new one.
- Refactor the `PluginSubscriptionPlan` entity with a builder pattern and static factory methods for more consistent plan creation.

* style(plugin-marketplace): enhance subscription plan selection UI

Overhauls the visual design of the plugin subscription selection screen to create a more modern, polished, and engaging user experience.
This update refines the styling for both the plan selection cards and the subscription preview component.
Key changes include:
- Redesigned `PlanCardComponent` with enhanced hover, focus, and selection states, incorporating subtle scaling, improved shadows, and smoother animations.
- Updated `SubscriptionPreviewComponent` with a cleaner layout, improved typography, and distinct styling for summary items.
- Replaced the feature list with a responsive grid of styled tags for better readability.
- Added responsive styles for improved usability on smaller screens.
- Implemented dark theme support for visual consistency.

* feat(subscription): add plan comparison and formatting services

- Implemented PlanComparisonService to handle logic for comparing subscription plans, including actions for new subscriptions, upgrades, downgrades, and current plans.
- Added methods for calculating proration amounts, checking upgrade/downgrade eligibility, and generating user-friendly action descriptions.
- Introduced PlanFormatterService for formatting subscription plan data for display, including transformation to view models and formatting prices and billing periods.
- Enhanced user experience with clear action descriptions and button variants based on plan actions.

* feat(subscription): enhance plan card comparison and update module exports

* refactor(plugins): introduce shared components and services for subscription management

This commit modularizes the plugin subscription management UI by extracting shared logic and presentation into reusable components and services. This improves code reuse, maintainability, and adherence to SOLID principles.
New shared modules have been created:
- **Shared Components:**
- `SubscriptionPlanCardComponent`: A reusable presentational component for displaying subscription plan details.
- `SubscriptionBillingFormComponent`: A component encapsulating the subscription billing form logic and UI.
- `SubscriptionStatusBadgeComponent`: A component for consistently displaying subscription status badges.
- **Shared Services:**
- `SubscriptionPlanService`: Centralizes business logic for plan comparison (upgrade/downgrade), price formatting, and savings calculations.
- `SubscriptionFormService`: Manages the creation and validation logic for the subscription form.
- `SubscriptionStatusService`: Provides helpers for mapping subscription statuses to UI elements (badges, icons).
The `PluginSubscriptionManagerComponent` has been significantly refactored to consume these new shared modules, removing duplicated logic and mock data. It is now connected to the `PluginSubscriptionFacade` to handle real subscription state changes.
Additionally, `PluginSubscriptionPlanSelectionComponent` and `PluginSubscriptionHierarchyComponent` are updated to leverage the new shared services, simplifying their implementations.

* feat: Refactor subscription plugin installation strategy and dialog routing

- Updated SubscriptionPluginInstallationStrategy to utilize SubscriptionDialogRouterService for managing subscription dialogs.
- Enhanced SubscriptionBillingFormComponent with improved UI elements and accessibility features.
- Introduced SubscriptionDialogRouterService to handle routing to appropriate subscription dialogs based on user status.
- Improved PluginAccessGuard to utilize the new dialog routing service for better user experience.
- Updated styles in subscription billing form for a more modern look and feel.
- Added animations and improved error handling in various components.
- Removed deprecated SubscriptionPlanCardComponent in favor of new plan card implementation.

* refactor(subscription): streamline subscription update input and improve upgrade/downgrade methods

* refactor: update plugin subscription plan data structure and comparison logic

* refactor(plugin-marketplace): streamline state management and improve loading states

- Updated PluginSourceEffects and PluginVersionEffects to use new state management methods for setting loading, creating, updating, deleting, and restoring states.
- Refactored PluginSourceStore and PluginVersionStore to include new methods for managing sources and versions, enhancing clarity and maintainability.
- Improved error handling and reset functionality across various stores.
- Enhanced the plugin subscription components to correctly display and manage subscription plans, including upgrades and downgrades.
- Cleaned up HTML templates for better readability and maintainability.

* refactor(plugin-marketplace): centralize state logic and add validation chain

Move state mutation logic from effects into the corresponding Akita stores. This centralizes the responsibility for merging and sorting collections of plugin versions and sources, making the effects cleaner and more focused on orchestration. The stores now handle maintaining a unique, sorted list of items.
Introduce an `InstallationValidationChainBuilder` to create a formal, extensible validation process before a plugin is installed or updated. This replaces scattered validation logic with a centralized chain of responsibility, improving robustness and maintainability. This new validation service is now used in both the item and detail components.
Additionally, this commit includes minor fixes such as:
- Using the async pipe for subscription checks in templates.
- Adding guards for potentially null values.
- Simplifying store reset logic.

* fix(subscription-manager): correct plan type display logic in subscription info

* feat(plugin): treat pending subscriptions as active and add tenant archiving

Subscriptions in a `PENDING` state are now consistently treated as active across the application. This prevents users from creating duplicate subscriptions while a payment is processing and allows them to manage their plan before it is fully activated.
This change ensures that pending subscriptions are correctly recognized when checking for an existing active subscription, calculating counts, and determining available actions like upgrades.
This also fixes a bug in the purchase handler where an existing subscription could be missed if its scope did not match the new purchase request.
Additionally, this commit introduces the ability to archive and restore plugin tenant configurations, providing a soft-delete mechanism.

* refactor(subscription-finders): update subscription retrieval logic to handle failures and adjust active status flag

* refactor(plugin-marketplace): decouple plan state from subscription state

The state management for plugin subscriptions previously handled
subscriptions, plans, and plan comparisons within a single, monolithic
Akita store. This made the state complex and difficult to maintain.
This refactoring separates these concerns into three distinct state
slices:
- PluginSubscription
- PluginPlan
- PluginPlanComparison
Each slice now has its own dedicated store, actions, effects, and
query files. The `PluginSubscriptionFacade` has been updated to act as
an orchestrator for these new, more focused state modules.
This change improves separation of concerns, enhances modularity, and
clarifies the responsibilities of each part of the plugin state.

* style(plugins): standardize code formatting and import order

Apply consistent code formatting, including indentation and import statement order, across various files in the plugin marketplace feature.
This improves code readability and maintainability without altering any functionality.

* refactor(plugin-marketplace): centralize plugin deletion confirmation logic in effect

Move the plugin deletion confirmation dialog from the components to the PluginMarketplaceEffects.
Previously, both the `PluginMarketplaceDetailComponent` and the `PluginMarketplaceItemComponent` contained identical logic to prompt the user before deleting a plugin. This refactoring centralizes this logic into a single effect.
Now, components simply dispatch the `delete` action, and the effect is responsible for showing the confirmation dialog before calling the delete service. This improves code reuse and separates concerns more cleanly.

* refactor(plugin): centralize and improve uninstallation process

The plugin uninstallation flow is overhauled to be more robust and to centralize logic within the `PluginInstallationEffects`.
The backend `uninstallPlugin` method is enhanced to find plugins by various criteria (ID, marketplace ID) and returns the `installationId`.
The frontend effect now:
- Presents a confirmation dialog to the user before proceeding.
- Uses the returned `installationId` to determine if a server-side API call is required, correctly handling both local-only and marketplace plugins.
- Centralizes the uninstallation logic, removing it from individual components.
This refactoring makes the process more reliable, improves code structure, and provides a better user experience. A new effect to check plugin installation status has also been added.

* refactor(plugin-marketplace): centralize plugin update dialog logic into effect

Move the responsibility for opening the PluginMarketplaceUploadComponent
dialog from individual components into the `update$` effect.
This simplifies `PluginMarketplaceDetailComponent` and
`PluginMarketplaceItemComponent` by removing duplicated dialog-handling
logic. Components now only dispatch the `update` action, and the effect
orchestrates the entire workflow, including user interaction and the
subsequent API call.
The `PluginMarketplaceActions.update` action signature is updated to
accept the full plugin object to facilitate this change.

* feat(plugin-marketplace): pre-select version and source on plugin update

When a user initiates an update for a plugin, the context for that
plugin (version and source) is now set in the state stores.
This ensures that when the update dialog opens, it is pre-populated
with the correct information, improving the user experience and reducing
the chance of errors.

* refactor(plugin-installation): centralize per-plugin toggle state

The previous implementation for managing a plugin's installation toggle state was flawed. It relied on a single global state object in the Akita store and additional local state within components. This could lead to state inconsistencies, especially when multiple plugin components are rendered simultaneously.
This refactoring addresses these issues by:
- Modifying the PluginInstallationStore to manage a collection of toggle states, one for each plugin, identified by `pluginId`.
- Removing local state management from `PluginMarketplaceDetailComponent` and `PluginMarketplaceItemComponent`.
- Components now query the central store for the installation status of their specific plugin.
- Encapsulating the installation check logic within an NgRx Effect, triggered by a `check` action for a more robust state flow.
- Updating actions to use `pluginId`, making state updates more precise.
- Ensuring that installation failures correctly reset the UI toggle for the specific plugin that failed.

* refactor(plugin-marketplace): centralize utility functions and dialog logic

Introduce a new `PluginMarketplaceUtilsService` to consolidate duplicated helper methods for labels, badges, and formatting across marketplace components. This eliminates code repetition and improves maintainability.
The logic for handling the creation of new plugin versions and sources is moved from the `PluginMarketplaceItemComponent` into the corresponding NgRx Effects. Effects now manage opening the creation dialogs, which centralizes side-effect handling and simplifies the component. The related `add` actions are updated to accept the `IPlugin` object directly.

* refactor(plugins): centralize PluginScope enum and remove GLOBAL scope

Move the PluginScope enum from its local definition within the
plugin-subscription-access service to the shared @gauzy/contracts
package. This ensures a single source of truth for plugin scopes across
the platform.
As part of this refactoring, the unused `PluginScope.GLOBAL` has been
completely removed from all related logic, including labels, status
indicators, and helper functions.
Additionally, this change improves type safety in the `OverviewTabComponent`
by correctly importing and using the `PluginType` enum.

* style(plugins): add explicit public access modifiers

Enforce explicit `public` access modifiers for methods and getters in plugin marketplace components and services to improve code clarity and maintain a consistent style.
Additionally, the unused `ngOnInit` lifecycle hook is removed from the `OverviewTabComponent` as part of this code cleanup.

* refactor(plugins): centralize marketplace logic into effects

Move complex business logic from plugin marketplace components into NgNeat effects to improve state management and code maintainability.
Components previously handled dialog orchestration, installation validation, and subscription checks directly. This logic is now centralized within dedicated effects, making the components simpler and more focused on presentation.
Key changes:
- Installation, subscription, settings, and user management workflows are
now triggered by dispatching intent-based actions.
- Effects orchestrate the entire process, including validation chains and
opening necessary dialogs.
- The `SubscriptionDialogRouterService` has been removed as its logic is
now managed by effects.
- Plugin components (`-item`, `-detail`) are significantly simplified,
only dispatching actions.
Additionally, `PluginMetadataService.findOne` no longer throws an error on not found, returning null instead to allow for cleaner handling in effects.

* fix(plugins): stabilize plugin installation and subscription handling

This commit addresses several issues related to the reliability and UI feedback of plugin installation and subscription management in the marketplace.
- Use `concatMap` in effects to prevent race conditions during installation status checks, ensuring actions are processed in order.
- Improve the installation toggle UI by setting its state immediately when an installation starts and resetting it correctly if the user cancels the confirmation dialog.
- Add a new backend endpoint to fetch the current user's active subscription for a specific plugin, which is then consumed by the UI.
- Centralize subscription state logic by using the `accessFacade` and storing the full subscription object in the access store for consistent UI display.
- Correct a typo in the `getCurrentSubscription` API endpoint URL.
- After plugin activation, dispatch an action to re-check the installation status, ensuring the marketplace UI is up-to-date.
- Refactor the installation store's `setToggle` method for more robust state updates.

* refactor(plugins): move plugin action dispatching to effects

Centralize the dispatching of secondary actions for the plugin marketplace into the `getOne$` effect. This removes scattered logic from components and adheres more closely to state management best practices.
The `getOne$` effect now dispatches actions to check installation status, select the plugin version, and select the plugin source upon successfully fetching a plugin. This logic was previously handled within the `PluginMarketplaceItemComponent`.
Additionally, the `SettingsTabComponent` now dispatches an action to open the settings dialog instead of calling the dialog service directly, further decoupling the component from implementation details.

* refactor(plugins): simplify plugin installation status checking

Remove the manual installation check and UI toggle state management.
Previously, a dedicated action/effect (`checkInstallation`) and a
'toggles' array in the store were used to track whether a marketplace
plugin was installed. This approach was complex and could lead to state
synchronization issues.
This change introduces a more robust method by deriving the installation
status directly from the single source of truth: the list of locally
installed plugins. A new `installed$` query has been added for this
purpose.
Effects for installation, uninstallation, and failures now simply
dispatch a refresh action, ensuring the UI always reflects the correct
state reactively.

* feat(plugins): implement optimistic UI for installation toggle

Introduce a new state management slice for the plugin installation toggle to provide immediate visual feedback for user actions in the marketplace. This implements an optimistic UI pattern.
Previously, the UI would wait for the entire installation or uninstallation process to complete before updating the toggle's state, leading to a sluggish user experience.
This change introduces `PluginToggleStore` and related effects/actions:
- The toggle state now updates instantly when a user initiates an install or uninstall action.
- If the backend operation fails or is canceled, the toggle reverts to its previous state.
- On success, the toggle's new state is confirmed.
Additionally, this resolves a UI bug where loading spinners would activate on all plugin items simultaneously. Spinners are now correctly scoped to only the specific plugin being installed, updated, or uninstalled.

* fix(plugins): correctly sync plugin toggle state after installation

The plugin toggle in the marketplace detail view did not automatically
enable after a successful installation, nor did it consistently reflect
the plugin's true enabled state.
This was caused by two issues:
1. The `activationCompleted` effect was using an incorrect ID to dispatch
the toggle action. It now correctly uses `marketplaceId`.
2. The component's toggle state was not reactively driven by the actual
installation status.
This change ensures the toggle state is updated by subscribing to the
installation query, providing a reliable single source of truth for the UI.

* fix(plugins): revert plugin toggle state on uninstall failure

The plugin uninstall effect would immediately toggle the plugin to disabled
in the UI. If the uninstall operation failed, the toggle would remain
in the disabled state, which was inconsistent with the plugin's actual
installed state.
The uninstall handlers now return a boolean indicating success or failure.
The main effect uses this result to correctly set the toggle's state. If
the uninstall fails, the toggle is reverted back to its enabled state,
ensuring UI consistency.

* refactor(plugins): enhance installation state handling in marketplace components

* refactor(plugins): track operation states on a per-plugin basis

The previous state management for plugin operations used global flags for
states like `installing`, `updating`, and `deleting`. This led to a UI
bug where performing an action on a single plugin would incorrectly
display a loading state for all plugins in the marketplace.
This commit refactors the plugin state stores (installation, marketplace,
version, source) to manage these states in records keyed by the plugin
ID.
- State properties are changed from `boolean` to `Record<string, boolean>`.
- Stores, queries, and effects are updated to operate on specific plugin
IDs.
- UI components are modified to consume this new per-plugin state,
ensuring that loading indicators and disabled states are isolated to
the correct plugin item.
Additionally, the installation validation chain has been hardened with
more robust error handling and improved subscription management.

* fix(plugins): correctly handle installation flow after plan selection

The plugin installation process after the subscription plan selection
dialog was flawed. Canceling the dialog left the plugin toggle enabled,
and proceeding did not trigger the installation.
This commit adjusts the plan selection effect to correctly dispatch
actions based on the dialog's outcome:
- Revert the plugin toggle to 'off' if the user cancels.
- Trigger the plugin installation if the user proceeds.
Additionally, state queries for `installing$`, `uninstalling$`, and
`uploading$` are refactored. They can now check the global status (i.e.,
if *any* plugin is undergoing an operation), which simplifies disabling
UI controls during these processes.

* refactor(plugins): remove unused DataSource dependency and transaction handling in delete plugin command

* feat(plugin-marketplace): revert plugin toggle when subscription flow is cancelled

When a user toggles a plugin that requires a subscription, the
subscription management dialog is shown. If the user closes this dialog
without completing the process, the toggle previously remained in its
new, misleading state.
This change introduces logic to automatically revert the toggle state if
the subscription flow was initiated by the toggle action and then
cancelled. A `clicked` flag distinguishes between manual and automatic
dialog openings to ensure the toggle is only reverted in the latter case.

* feat(cdn-download): enhance plugin download strategy with streaming support and improved error handling

* fix(plugin-installation): add unique index for plugin installation fields

* feat(plugin): implement soft delete for plugins

Replace hard deletion with a soft delete mechanism. This change prevents permanent data loss and preserves plugin history for auditing or recovery purposes.
The `DeletePluginCommandHandler` now calls `pluginService.softDelete` and returns a success message with a status code upon completion.
BREAKING CHANGE: The `DeletePluginCommandHandler` now returns an object `{ message: string, status: number }` instead of `void`. Consumers of this command must be updated to handle the new return value.

* feat(plugins): open settings and user management dialogs via effects

Create NgNeat effects to handle the opening of plugin management dialogs.
The `openSettings$` effect in `PluginSettingsEffects` listens for the
`openSettings` action and opens the `PluginSettingsManagementComponent`.
Similarly, the `openUserManagementDialog$` effect in
`PluginUserAssignmentEffects` listens for the `manageUsers` action to
open the `PluginUserManagementComponent`.
This approach centralizes the dialog opening logic within the state
management layer.

* feat(i18n): add translations for plugin management and validation

Adds a comprehensive set of new English translation strings to support
upcoming enhancements to the plugin marketplace and management features.
This includes:
- A new `VALIDATION` section for various plugin lifecycle errors.
- More specific error messages for the "Assign Users" modal.
- A new "View Subscription" button label.
- Improved wording for plugin-related empty state messages for clarity.

* fix(registry): correctly load tenant relations for assignment check

The `determineAssignmentPermissions` method previously accepted a `pluginTenant` object, which could lead to incorrect permission calculations if the required user and role relations were not pre-loaded by the caller.
This change refactors the method to accept a `pluginTenantId` instead. It now explicitly fetches the `pluginTenant` entity with its `allowedUsers`, `deniedUsers`, and `allowedRoles` relations, ensuring that the permission logic always has the necessary data to operate correctly.

* refactor(plugins): decouple user assignments from plugin installations

This refactor removes the tight coupling between plugin user assignments
and the `PluginInstallation` entity. User assignments are now tracked
at a higher level, abstracting away the specific installation instance.
On the backend, a new `PluginTenant` entity is introduced to manage the
relationship between a plugin and a tenant/organization, serving as the
anchor for user assignments.
This change simplifies the overall data model and the public API for
managing user access to plugins.
Key changes include:
- Replaced `installationId` with `subscriptionId` or removed it
entirely from frontend state management and service calls.
- Renamed `pluginInstallationId` to `pluginSubscriptionId` in data models.
- Simplified API endpoints for fetching user assignments, e.g., from
`.../installations/:id/users` to `/plugins/:pluginId/users`.
- Updated backend command handlers to use the new `PluginTenant` service
for assignment and revocation logic.
BREAKING CHANGE: The API and frontend service layer for plugin user
assignments have changed. `installationId` is no longer used to scope
user assignments. Methods like `loadAssignmentsForInstallation` have been
removed, and signatures for many other methods in facades and services
have been updated.

* feat(plugins): implement plugin tenant based user management

This refactors the entire plugin user assignment system to use a new "Plugin Tenant" access control model, replacing the previous subscription-based logic. Plugin tenants now directly manage user access through explicit `allowedUsers` and `deniedUsers` lists.
Frontend Changes:
- New NgRx actions, effects, and facade methods are introduced for managing users within a plugin tenant context (e.g., `loadAllowedUsersForPlugin`, `allowUsersToPluginTenant`, `removeAllowedUsersFromPluginTenant`).
- The component logic is updated to first resolve a `pluginTenantId` for a given plugin and then perform user management operations against that tenant.
- State management is updated to store the `currentPluginTenantId`.
Backend Changes:
- New API endpoints are added to manage plugin tenant users (`POST /plugin-tenants/:id/users`) and to resolve a plugin tenant from a plugin ID (`GET /plugins/:id/tenant`).
- CQRS commands (`ManagePluginTenantUsersCommand`) and queries (`GetPluginTenantUsersQuery`) are implemented to handle the new ACL logic.
This provides a more robust and explicit mechanism for controlling user access to plugins.
BREAKING CHANGE: The plugin user assignment system has been completely overhauled. The previous subscription-based user access model is replaced by a new Plugin Tenant access control list (ACL) model. API endpoints and frontend state management for user assignment are not backward compatible.

* refactor(plugin): simplify subscription finding and access logic

Remove the complex Strategy and Registry patterns for locating plugin
subscriptions. The multiple finder classes and registry are replaced with a
single private `findSubscriptionByContext` method within the service.
This consolidation simplifies the architecture, reduces boilerplate, and
makes the subscription lookup process more direct and maintainable.
Additionally, this commit includes the following fixes:
- Allow access for subscriptions with `TRIAL` or `PENDING` statuses,
not just `ACTIVE`.
- Grant access by default when a plugin has no `PluginTenant`
configuration, treating it as a non-restricted plugin.
- Add null-safety checks in the access context factory to prevent
runtime errors.

* refactor(plugins): simplify user loading in user management

The logic for loading available users relied on subscribing to the
`selectedOrganization$` observable to get the current organization and
tenant IDs.
This approach was unnecessarily complex as these values are available
directly on the store. This commit refactors the component to access
`store.organizationId` and `store.tenantId` directly.
This removes the verbose observable pipeline, resulting in cleaner and
more straightforward code.

* feat(subscription): link plugin u…
…#9291)

* chore: upgrade angular dependencies version from v19.2.10 to v19.2.17

* chore: upgrade @angular/material and other deps related to angular version

* Delete migrations.json

* Update .gitignore

---------

Co-authored-by: Ruslan Konviser <[email protected]>
return count > 0;
} catch (error) {
// If there's an error checking, default to false (treat as free plugin)
console.error(`Error checking if plugin ${pluginId} requires subscription:`, error);

Check failure

Code scanning / CodeQL

Use of externally-controlled format string High

Format string depends on a
user-provided value
.
Format string depends on a
user-provided value
.
Format string depends on a
user-provided value
.
Format string depends on a
user-provided value
.
Format string depends on a
user-provided value
.
Format string depends on a
user-provided value
.
Format string depends on a
user-provided value
.
Format string depends on a
user-provided value
.

Copilot Autofix

AI 21 days ago

In general, to fix “externally-controlled format string” issues with console.log/console.error/util.format, never pass untrusted data as (part of) the first argument when you intend that argument to be a format string. Instead, either (1) use a static format string with %s and pass untrusted data as subsequent arguments, or (2) pass the untrusted data as a non-format argument (e.g., as a separate argument or within an object), so it is not interpreted for % specifiers.

For this specific case in plugin-subscription-plan.service.ts, we should change the console.error call so that the format string is constant and pluginId is passed as a separate argument. This preserves the existing behavior (logging both a descriptive message, the plugin ID, and the error) while preventing any % sequences in pluginId from being interpreted as format specifiers. A minimal, compatible fix is:

console.error('Error checking if plugin %s requires subscription:', pluginId, error);

This keeps logging semantics essentially the same but makes the format string static and delegates the user input to a %s placeholder.

Only packages/plugins/registry/src/lib/domain/services/plugin-subscription-plan.service.ts needs to change, and no new imports or helpers are required.

Suggested changeset 1
packages/plugins/registry/src/lib/domain/services/plugin-subscription-plan.service.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/packages/plugins/registry/src/lib/domain/services/plugin-subscription-plan.service.ts b/packages/plugins/registry/src/lib/domain/services/plugin-subscription-plan.service.ts
--- a/packages/plugins/registry/src/lib/domain/services/plugin-subscription-plan.service.ts
+++ b/packages/plugins/registry/src/lib/domain/services/plugin-subscription-plan.service.ts
@@ -369,7 +369,7 @@
 			return count > 0;
 		} catch (error) {
 			// If there's an error checking, default to false (treat as free plugin)
-			console.error(`Error checking if plugin ${pluginId} requires subscription:`, error);
+			console.error('Error checking if plugin %s requires subscription:', pluginId, error);
 			return false;
 		}
 	}
EOF
@@ -369,7 +369,7 @@
return count > 0;
} catch (error) {
// If there's an error checking, default to false (treat as free plugin)
console.error(`Error checking if plugin ${pluginId} requires subscription:`, error);
console.error('Error checking if plugin %s requires subscription:', pluginId, error);
return false;
}
}
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
Comment on lines +173 to +178
let baseSlug = name
.toLowerCase()
.replace(/[^a-z0-9\s-]/g, '')
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
.replace(/^-+|-+$/g, '');

Check failure

Code scanning / CodeQL

Polynomial regular expression used on uncontrolled data High

This
regular expression
that depends on
library input
may run slow on strings with many repetitions of '-'.
response
.body!.pipe(unzipper.Parse())
.on('entry', async (entry: any) => {
const { path: filePath, type, size } = entry;

Check failure

Code scanning / CodeQL

Arbitrary file access during archive extraction ("Zip Slip") High

Unsanitized archive entry, which may contain '..', is used in a
file system operation
.

Copilot Autofix

AI 21 days ago

In general, the fix is to ensure that any path derived from a zip entry is validated before being used in filesystem operations, preventing directory traversal. That means resolving the candidate output path, ensuring it is within the intended extraction root, and rejecting paths that escape that root, including those containing "..", absolute paths, or drive letters.

The best way to fix this snippet without changing existing behavior is to implement (or strengthen) isSafePath inside CdnDownloadStrategy so that it: (1) resolves the full candidate path with path.resolve, (2) resolves the extraction root, (3) checks that the resolved candidate path starts with the resolved root path plus a path separator, and (4) additionally rejects absolute or drive-lettered filePath values. We then keep the existing guard if (!this.isSafePath(extractPath, filePath)) { ... return; } so that no fullPath built from an unsafe filePath is ever passed to fs.mkdir or createWriteStream.

Concretely, in packages/desktop-lib/src/lib/plugin-system/data-access/strategies/cdn-download.strategy.ts, inside the CdnDownloadStrategy class, we will add a private helper method isSafePath(root: string, entryPath: string): boolean near the top or bottom of the class. This method will use the already-imported path module, so no new imports are required. No changes are needed around line 472 other than relying on this method; the guard is already present. The new method will normalize and validate paths as described above, ensuring that any filePath used to form fullPath is confined to extractPath.

Suggested changeset 1
packages/desktop-lib/src/lib/plugin-system/data-access/strategies/cdn-download.strategy.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/packages/desktop-lib/src/lib/plugin-system/data-access/strategies/cdn-download.strategy.ts b/packages/desktop-lib/src/lib/plugin-system/data-access/strategies/cdn-download.strategy.ts
--- a/packages/desktop-lib/src/lib/plugin-system/data-access/strategies/cdn-download.strategy.ts
+++ b/packages/desktop-lib/src/lib/plugin-system/data-access/strategies/cdn-download.strategy.ts
@@ -9,6 +9,43 @@
 export class CdnDownloadStrategy implements IPluginDownloadStrategy {
 	private static readonly MAX_RETRIES = 3;
 	private static readonly RETRY_DELAY_MS = 1000;
+
+	/**
+	 * Validate that a path from an archive entry does not lead outside the intended root directory.
+	 * This defends against Zip Slip / path traversal attacks.
+	 */
+	private isSafePath(rootDir: string, entryPath: string): boolean {
+		if (!entryPath) {
+			return false;
+		}
+
+		// Normalize entry path separators
+		const normalizedEntry = entryPath.replace(/\\/g, '/');
+
+		// Disallow absolute paths and drive letters
+		if (path.isAbsolute(normalizedEntry)) {
+			return false;
+		}
+		if (/^[a-zA-Z]:/.test(normalizedEntry)) {
+			return false;
+		}
+
+		// Resolve the full path and ensure it stays within rootDir
+		const resolvedRoot = path.resolve(rootDir);
+		const resolvedTarget = path.resolve(rootDir, normalizedEntry);
+
+		// Ensure the resolved target is within the resolved root directory
+		if (resolvedTarget === resolvedRoot) {
+			// Prevent zip entries from overwriting the root directory itself
+			return false;
+		}
+
+		if (!resolvedTarget.startsWith(resolvedRoot + path.sep)) {
+			return false;
+		}
+
+		return true;
+	}
 	private static readonly VALID_EXTENSION = '.zip';
 	private static readonly MAX_FILE_SIZE = 500 * 1024 * 1024; // 500MB per file
 	private static readonly MAX_TOTAL_SIZE = 1024 * 1024 * 1024; // 1GB total extraction limit
EOF
@@ -9,6 +9,43 @@
export class CdnDownloadStrategy implements IPluginDownloadStrategy {
private static readonly MAX_RETRIES = 3;
private static readonly RETRY_DELAY_MS = 1000;

/**
* Validate that a path from an archive entry does not lead outside the intended root directory.
* This defends against Zip Slip / path traversal attacks.
*/
private isSafePath(rootDir: string, entryPath: string): boolean {
if (!entryPath) {
return false;
}

// Normalize entry path separators
const normalizedEntry = entryPath.replace(/\\/g, '/');

// Disallow absolute paths and drive letters
if (path.isAbsolute(normalizedEntry)) {
return false;
}
if (/^[a-zA-Z]:/.test(normalizedEntry)) {
return false;
}

// Resolve the full path and ensure it stays within rootDir
const resolvedRoot = path.resolve(rootDir);
const resolvedTarget = path.resolve(rootDir, normalizedEntry);

// Ensure the resolved target is within the resolved root directory
if (resolvedTarget === resolvedRoot) {
// Prevent zip entries from overwriting the root directory itself
return false;
}

if (!resolvedTarget.startsWith(resolvedRoot + path.sep)) {
return false;
}

return true;
}
private static readonly VALID_EXTENSION = '.zip';
private static readonly MAX_FILE_SIZE = 500 * 1024 * 1024; // 500MB per file
private static readonly MAX_TOTAL_SIZE = 1024 * 1024 * 1024; // 1GB total extraction limit
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
.pipe(unzipper.Extract({ path: extractDir }))
.pipe(unzipper.Parse())
.on('entry', (entry: any) => {
const { path: entryPath, type, size } = entry;

Check failure

Code scanning / CodeQL

Arbitrary file access during archive extraction ("Zip Slip") High

Unsanitized archive entry, which may contain '..', is used in a
file system operation
.

Copilot Autofix

AI 21 days ago

In general, to fix Zip Slip issues, you must never directly trust entry.path. Instead, construct a candidate output path, normalize/resolve it, and verify that it is still within the intended extraction directory and is not absolute or containing .. components. If the check fails, skip the entry.

For this specific code, we can strengthen the validation directly where fullPath is created and before createWriteStream is called. The safest pattern without changing external behavior is:

  1. Build a candidate path: const fullPath = path.join(extractDir, entryPath);.
  2. Normalize/resolve it: e.g., const normalizedPath = path.resolve(fullPath);.
  3. Ensure:
    • normalizedPath starts with path.resolve(extractDir) + path.sep (or is exactly equal for root).
    • entryPath does not contain .. path segments or is not an absolute path (path.isAbsolute(entryPath)).
  4. If any check fails, log and entry.autodrain() without writing.
  5. Otherwise, ensure the parent directory exists (optional but safe) and then write the file.

Because we are not allowed to modify other files, we will not change isSafePath elsewhere; instead, we will add an additional local check using Node’s path module (already imported) and fs.promises.mkdir (already available via import * as fs from 'fs/promises';). We will:

  • Insert a small helper block inside unzip after const fullPath = ... to:
    • Resolve fullPath.
    • Compare it to the resolved extractDir.
    • Skip entries that fail the check.
    • Ensure the directory structure exists before writing.

This stays within the shown snippet, uses existing imports, and preserves functionality while eliminating Zip Slip.

Suggested changeset 1
packages/desktop-lib/src/lib/plugin-system/data-access/strategies/cdn-download.strategy.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/packages/desktop-lib/src/lib/plugin-system/data-access/strategies/cdn-download.strategy.ts b/packages/desktop-lib/src/lib/plugin-system/data-access/strategies/cdn-download.strategy.ts
--- a/packages/desktop-lib/src/lib/plugin-system/data-access/strategies/cdn-download.strategy.ts
+++ b/packages/desktop-lib/src/lib/plugin-system/data-access/strategies/cdn-download.strategy.ts
@@ -296,6 +296,8 @@
 			let totalSize = 0;
 
 			await new Promise<void>((resolve, reject) => {
+				const baseExtractPath = path.resolve(extractDir);
+
 				createReadStream(filePath)
 					.pipe(unzipper.Parse())
 					.on('entry', (entry: any) => {
@@ -327,12 +329,31 @@
 						}
 
 						const fullPath = path.join(extractDir, entryPath);
+						const resolvedFullPath = path.resolve(fullPath);
 
+						// Additional Security: Ensure resolved path is within extraction directory
+						if (
+							!resolvedFullPath.startsWith(baseExtractPath + path.sep) &&
+							resolvedFullPath !== baseExtractPath
+						) {
+							logger.warn(`Resolved path escapes extract directory, skipping: ${entryPath}`);
+							entry.autodrain();
+							return;
+						}
+
 						if (type === 'Directory') {
 							entry.autodrain();
 						} else {
-							// Extract file
-							entry.pipe(createWriteStream(fullPath));
+							// Ensure directory exists before writing file
+							fs.mkdir(path.dirname(resolvedFullPath), { recursive: true })
+								.then(() => {
+									// Extract file
+									entry.pipe(createWriteStream(resolvedFullPath));
+								})
+								.catch((err) => {
+									logger.error(`Failed to create directory for ${resolvedFullPath}: ${err.message}`);
+									entry.autodrain();
+								});
 						}
 					})
 					.on('close', resolve)
EOF
@@ -296,6 +296,8 @@
let totalSize = 0;

await new Promise<void>((resolve, reject) => {
const baseExtractPath = path.resolve(extractDir);

createReadStream(filePath)
.pipe(unzipper.Parse())
.on('entry', (entry: any) => {
@@ -327,12 +329,31 @@
}

const fullPath = path.join(extractDir, entryPath);
const resolvedFullPath = path.resolve(fullPath);

// Additional Security: Ensure resolved path is within extraction directory
if (
!resolvedFullPath.startsWith(baseExtractPath + path.sep) &&
resolvedFullPath !== baseExtractPath
) {
logger.warn(`Resolved path escapes extract directory, skipping: ${entryPath}`);
entry.autodrain();
return;
}

if (type === 'Directory') {
entry.autodrain();
} else {
// Extract file
entry.pipe(createWriteStream(fullPath));
// Ensure directory exists before writing file
fs.mkdir(path.dirname(resolvedFullPath), { recursive: true })
.then(() => {
// Extract file
entry.pipe(createWriteStream(resolvedFullPath));
})
.catch((err) => {
logger.error(`Failed to create directory for ${resolvedFullPath}: ${err.message}`);
entry.autodrain();
});
}
})
.on('close', resolve)
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Dec 24, 2025

Skipped: This PR changes more files than the configured file change limit: (829 files found, 100 file limit)

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 24, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch stage

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@socket-security
Copy link

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm entities is 91.0% likely obfuscated

Confidence: 0.91

Location: Package overview

From: ?npm/[email protected]

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at [email protected].

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/[email protected]. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm entities is 91.0% likely obfuscated

Confidence: 0.91

Location: Package overview

From: ?npm/[email protected]

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at [email protected].

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/[email protected]. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
3.8% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@evereq evereq merged commit b3a8ee8 into master Dec 24, 2025
21 of 24 checks passed
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 829 files

Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name=".cspell.json">

<violation number="1" location=".cspell.json:810">
P0: Missing comma after `&quot;Liskov&quot;`. This JSON syntax error will cause the cspell configuration file to fail parsing.</violation>
</file>

<file name="README.md">

<violation number="1" location="README.md:133">
P3: Minor grammar issue: &quot;releases and pre-releases downloads&quot; should be &quot;release and pre-release downloads&quot; (singular form when used as adjectives).</violation>
</file>

<file name=".github/instructions/angular.instructions.md">

<violation number="1" location=".github/instructions/angular.instructions.md:7">
P2: Documentation references Angular v20+ but the project uses `@angular/[email protected]`. Consider updating the version references to match the actual project dependency (Angular 19) to avoid confusion for developers following these guidelines.</violation>
</file>

<file name=".github/workflows/agent-prod.yml">

<violation number="1" location=".github/workflows/agent-prod.yml:145">
P0: Incorrect environment variable prefix. The `script.agent()` function expects `AGENT_APP_*` variables (e.g., `AGENT_APP_NAME`), but this job passes `DESKTOP_AGENT_APP_*` variables. This will cause undefined values in the package configuration, breaking the ARM64 Linux build.</violation>
</file>

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

"prepopulate",
"prepopulates",
"prepopulation",
"Liskov"
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0: Missing comma after "Liskov". This JSON syntax error will cause the cspell configuration file to fail parsing.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .cspell.json, line 810:

<comment>Missing comma after `&quot;Liskov&quot;`. This JSON syntax error will cause the cspell configuration file to fail parsing.</comment>

<file context>
@@ -740,7 +800,15 @@
+		&quot;prepopulate&quot;,
+		&quot;prepopulates&quot;,
+		&quot;prepopulation&quot;,
+		&quot;Liskov&quot;
+		&quot;Zrdm&quot;
 	],
</file context>
Suggested change
"Liskov"
"Liskov",
Fix with Cubic

You can download Gauzy Platform, Gauzy Server, or Desktop Apps (Windows/Mac/Linux) from the official [Downloads](https://web.gauzy.co/downloads) page.

In addition, all downloads are also available from the following pages:
In addition, all releases and pre-releases downloads are also available from the following pages:
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Minor grammar issue: "releases and pre-releases downloads" should be "release and pre-release downloads" (singular form when used as adjectives).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At README.md, line 133:

<comment>Minor grammar issue: &quot;releases and pre-releases downloads&quot; should be &quot;release and pre-release downloads&quot; (singular form when used as adjectives).</comment>

<file context>
@@ -130,12 +130,10 @@ Notes:
 You can download Gauzy Platform, Gauzy Server, or Desktop Apps (Windows/Mac/Linux) from the official [Downloads](https://web.gauzy.co/downloads) page.
 
-In addition, all downloads are also available from the following pages:
+In addition, all releases and pre-releases downloads are also available from the following pages:
 
 -   [Platform Releases](https://github.com/ever-co/ever-gauzy/releases)
</file context>
Fix with Cubic


# Persona

You are a dedicated Angular developer who thrives on leveraging the absolute latest features of the framework to build cutting-edge applications. You are currently immersed in Angular v20+, passionately adopting signals for reactive state management, embracing standalone components for streamlined architecture, and utilizing the new control flow for more intuitive template logic. Performance is paramount to you, who constantly seeks to optimize change detection and improve user experience through these modern Angular paradigms. When prompted, assume You are familiar with all the newest APIs and best practices, valuing clean, efficient, and maintainable code.
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Documentation references Angular v20+ but the project uses @angular/[email protected]. Consider updating the version references to match the actual project dependency (Angular 19) to avoid confusion for developers following these guidelines.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/instructions/angular.instructions.md, line 7:

<comment>Documentation references Angular v20+ but the project uses `@angular/[email protected]`. Consider updating the version references to match the actual project dependency (Angular 19) to avoid confusion for developers following these guidelines.</comment>

<file context>
@@ -0,0 +1,118 @@
+
+# Persona
+
+You are a dedicated Angular developer who thrives on leveraging the absolute latest features of the framework to build cutting-edge applications. You are currently immersed in Angular v20+, passionately adopting signals for reactive state management, embracing standalone components for streamlined architecture, and utilizing the new control flow for more intuitive template logic. Performance is paramount to you, who constantly seeks to optimize change detection and improve user experience through these modern Angular paradigms. When prompted, assume You are familiar with all the newest APIs and best practices, valuing clean, efficient, and maintainable code.
+
+## Examples
</file context>
Fix with Cubic

Comment on lines +145 to +150
DESKTOP_AGENT_APP_NAME: 'gauzy-agent'
DESKTOP_AGENT_APP_REPO_NAME: 'ever-gauzy-agent'
DESKTOP_AGENT_APP_REPO_OWNER: 'ever-co'
COMPANY_SITE_LINK: 'https://gauzy.co'
DESKTOP_AGENT_APP_DESCRIPTION: 'Gauzy Agent'
DESKTOP_AGENT_APP_ID: 'com.ever.gauzyagent'
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0: Incorrect environment variable prefix. The script.agent() function expects AGENT_APP_* variables (e.g., AGENT_APP_NAME), but this job passes DESKTOP_AGENT_APP_* variables. This will cause undefined values in the package configuration, breaking the ARM64 Linux build.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/agent-prod.yml, line 145:

<comment>Incorrect environment variable prefix. The `script.agent()` function expects `AGENT_APP_*` variables (e.g., `AGENT_APP_NAME`), but this job passes `DESKTOP_AGENT_APP_*` variables. This will cause undefined values in the package configuration, breaking the ARM64 Linux build.</comment>

<file context>
@@ -57,30 +61,116 @@ jobs:
+            console.log(script.agent(true))
+        env:
+          PROJECT_REPO: &#39;https://github.com/ever-co/ever-gauzy.git&#39;
+          DESKTOP_AGENT_APP_NAME: &#39;gauzy-agent&#39;
+          DESKTOP_AGENT_APP_REPO_NAME: &#39;ever-gauzy-agent&#39;
+          DESKTOP_AGENT_APP_REPO_OWNER: &#39;ever-co&#39;
</file context>
Suggested change
DESKTOP_AGENT_APP_NAME: 'gauzy-agent'
DESKTOP_AGENT_APP_REPO_NAME: 'ever-gauzy-agent'
DESKTOP_AGENT_APP_REPO_OWNER: 'ever-co'
COMPANY_SITE_LINK: 'https://gauzy.co'
DESKTOP_AGENT_APP_DESCRIPTION: 'Gauzy Agent'
DESKTOP_AGENT_APP_ID: 'com.ever.gauzyagent'
AGENT_APP_NAME: 'gauzy-agent'
AGENT_APP_REPO_NAME: 'ever-gauzy-agent'
AGENT_APP_REPO_OWNER: 'ever-co'
COMPANY_SITE_LINK: 'https://gauzy.co'
AGENT_APP_DESCRIPTION: 'Gauzy Agent'
AGENT_APP_ID: 'com.ever.gauzyagent'
Fix with Cubic

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +418 to +421
private isSafePath(basePath: string, filePath: string): boolean {
const resolvedPath = path.resolve(basePath, filePath);
const normalizedBase = path.normalize(basePath);
return resolvedPath.startsWith(normalizedBase);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Fix path traversal check for plugin extraction

The new isSafePath guard uses resolvedPath.startsWith(normalizedBase), which can be bypassed by paths that share the same prefix but are outside the base directory (e.g., with base /plugins, an entry like ../plugins-malicious/evil.js resolves to /plugins-malicious/evil.js and still passes the prefix check). That means a crafted plugin zip can write outside the plugin folder during extraction, which is a security issue if an attacker controls or compromises the download source. Use path.resolve(basePath) + path.sep or path.relative to ensure the resolved path stays within the base directory.

Useful? React with 👍 / 👎.

@greptile-apps greptile-apps bot mentioned this pull request Jan 5, 2026
2 tasks
evereq pushed a commit that referenced this pull request Jan 7, 2026
* fix: improve error logging format in plugin subscription plan service

* fix(plugin-system): enhance zip extraction security

This commit introduces several security enhancements to the zip extraction process within the CDN download strategy.
Previously, there was a potential for path traversal vulnerabilities if a malicious zip file contained entries with paths that attempted to escape the designated extraction directory.
The following changes were implemented:
- Added a check to ensure the resolved file path of an entry is strictly within the base extraction directory. If it escapes, the entry is logged and skipped.
- Explicitly ensured that the parent directory for each file being extracted exists before attempting to write the file. This prevents errors and potential issues with nested directories within zip archives.
- Refactored `isSafePath` to more robustly handle Windows path separators, disallow absolute paths and drive letters, and ensure the resolved target path starts with the resolved root directory, preventing traversal.

* fix(plugin-category): simplify slug generation logic

* fix(cspell): add new words to the spell checker dictionary

* fix(cdn-download): use replaceAll for entry path normalization
fix(plugin-category): refine slug generation regex for leading/trailing dashes

* fix(cspell): remove duplicated word "Zrdm" from the spell checker dictionary

* fix(cdn-download): improve file extraction handling and add error tracking

* fix(cdn-download): correct path normalization logic in isSafePath method

* fix: arbitrary file access during archive extraction ("Zip Slip")

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* fix(cdn-download): enhance security by rejecting Unix-style absolute paths and removing redundant path validation

* fix(cdn-download): update import for finished stream utility and remove redundant baseExtractPath variable

* fix(cdn-download): improve path normalization

The previous regex ` /\\/g ` for normalizing Windows path separators in `entryPath` was overly broad and could lead to incorrect path normalization in certain edge cases.
This change updates the `replaceAll` method to use a literal backslash character `'\'` instead of a regex, ensuring only backslashes are replaced with forward slashes. This provides more precise and reliable path normalization.

* refactor(cdn-download): simplify archive extraction logic

Refactors the `CdnDownloadStrategy` to use dedicated helper methods for directory creation and file extraction. This improves readability and maintainability of the archive processing logic.
The changes include:
- Extracting directory creation into `createDirectory`.
- Extracting file writing logic into `extractFile`.
- Renaming `filePath` to `entryPath` for clarity.
- Using `path.resolve` for safer path handling.
- Ensuring `entry.autodrain()` is called in error scenarios.

* fix(cdn-download): prevent path traversal in zip extraction

Add validation to the `extractEntry` function to prevent path traversal vulnerabilities during the extraction of zip archives. The target path is now resolved against the base directory of the extraction to ensure it remains within the intended scope.
This change enhances security by ensuring that malicious zip archives cannot write files outside of their designated extraction directory.
The `baseDir` option is now passed to `extractEntry` and used to construct a safe target path.

* fix(plugin-system): improve error handling and stream management in cdn download

This commit addresses several issues in the `CdnDownloadStrategy` related to error handling and stream management during plugin downloads.
Key changes:
- **Robust Error Logging:** Error messages for directory creation and file extraction now include the full error stack or message for better debugging. They also explicitly convert non-Error objects to strings.
- **Stream Pipeline for Extraction:** The `entry.pipe(writeStream)` and `finished(writeStream)` combination has been replaced with `pipeline(entry, writeStream)`. This provides more reliable error propagation and ensures streams are properly cleaned up.
- **Resource Cleanup on Error:** In the `extractFile` method, if an error occurs during file extraction, the `entry` and `writeStream` are explicitly destroyed to release resources.
- **Incomplete File Removal:** If an error occurs during file extraction, any partially created target file is now removed to prevent leaving incomplete artifacts. This handles potential `ENOENT` errors gracefully.
- **Throwing Errors:** Errors are now consistently re-thrown after cleanup, ensuring that the calling code is aware of and can handle extraction failures.

* fix(cdn-download): re-throw stream write errors

The previous implementation of `CdnDownloadStrategy` caught stream write errors and autodrained the entry. This masked the underlying error and prevented `Promise.all` from rejecting, leading to silent failures when downloading plugin files.
This change re-throws the caught error. This ensures that any failure during the file writing process is propagated up to `Promise.all`, allowing the overall download operation to fail gracefully and be reported to the user.

* fix: Rethrowing here propagates the error, but the unzipper entry stream is no longer drained/destroyed on failure. A write error can leave the entry unread and stall the unzip parse. Drain (or destroy) the entry before rethrowing.

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* fix(cdn-download): improve error handling and resource cleanup

Add robust error handling to the CDN download strategy. This includes:
- Logging detailed error messages, including stack traces when available.
- Destroying pipe streams to release resources on error.
- Attempting to remove incomplete files to prevent corrupted downloads.
- Handling potential errors during file removal (e.g., if the file doesn't exist).
- Ensuring the `entry` stream is also handled correctly on error.

* fix(cdn-download): ensure error is thrown in stream pipe

The previous implementation incorrectly called `entry.autodrain()` before re-throwing the error. This prevented the error from being propagated correctly, potentially leading to unhandled promise rejections.
This change ensures that the error is thrown immediately, allowing the calling code to catch and handle it as expected.

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants