-
Notifications
You must be signed in to change notification settings - Fork 52.6k
RJ Add quota feature change #23639
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
RJ Add quota feature change #23639
Conversation
Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
…e user it's shared with (n8n-io#8564)
…user it's shared with (n8n-io#8584)
Co-authored-by: Danny Martini <despair.blue@gmail.com> Co-authored-by: Iván Ovejero <ivov.src@gmail.com> Co-authored-by: Danny Martini <danny@n8n.io>
…rators (no-changelog) (n8n-io#8546) Co-authored-by: Valya Bullions <valya@n8n.io>
…he type or the role of the user to the project into account (n8n-io#8676)
…ertain circumstances (n8n-io#9315)
|
Valya Bullions seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account. You have signed the CLA already but the status is still pending? Let us recheck it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
60 Open source vulnerabilities detected - critical severity
Aikido detected 60 vulnerabilities across 43 packages, it includes 6 critical, 23 high, 20 medium and 11 low vulnerabilities.
Remediation Aikido suggests bumping the vulnerable packages to a safe version.
View details in Aikido Security
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
14 issues found across 277 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="packages/cli/src/commands/mfa/disable.ts">
<violation number="1" location="packages/cli/src/commands/mfa/disable.ts:43">
P2: Misleading error message: When MFA is already disabled, calling `reportUserDoesNotExistError()` tells the user they don't exist, which is incorrect. The user exists but MFA is not enabled. Consider creating a separate method like `reportMfaAlreadyDisabled()` or logging an appropriate message.</violation>
</file>
<file name="packages/cli/src/UserManagement/PermissionChecker.ts">
<violation number="1" location="packages/cli/src/UserManagement/PermissionChecker.ts:21">
P3: Grammar error: "apart of" should be "a part of" (two words). "Apart" means "separate from", while "a part of" means "belonging to".</violation>
<violation number="2" location="packages/cli/src/UserManagement/PermissionChecker.ts:31">
P3: Typo in variable name: `accessable` should be `accessible`.</violation>
</file>
<file name="packages/cli/src/CredentialsHelper.ts">
<violation number="1" location="packages/cli/src/CredentialsHelper.ts:459">
P2: Caching `false` values won't work here. The `CacheService.set()` method has a guard `if (!key || !value) return;` that prevents storing falsy values. When `refreshFn` returns `false` (credential cannot use external secrets), it won't be cached, causing repeated database queries on every call. Consider returning an object like `{ canUse: false }` instead, or modify the cache key pattern to only cache positive results.</violation>
</file>
<file name="packages/cli/src/commands/import/workflow.ts">
<violation number="1" location="packages/cli/src/commands/import/workflow.ts:143">
P2: Incorrect error message: says "credential" but should say "workflow". This could confuse users when the error is displayed.</violation>
</file>
<file name="packages/cli/src/InternalHooks.ts">
<violation number="1" location="packages/cli/src/InternalHooks.ts:235">
P2: Hardcoding `sharing_role: 'owner'` will produce inaccurate telemetry data. Users who are actually "sharees" (workflow was shared with them) will be incorrectly reported as "owners". The TODO comment suggests this is placeholder code - consider either keeping the original role-fetching logic, or if intentionally deferring, use `undefined` or `null` instead of an incorrect hardcoded value to avoid misleading analytics.</violation>
</file>
<file name="packages/cli/src/controllers/e2e.controller.ts">
<violation number="1" location="packages/cli/src/controllers/e2e.controller.ts:96">
P2: `numericFeatures` is not reset in `resetFeatures()`. Quota values set via `setQuota()` will persist across E2E test resets, causing test pollution. Consider adding logic to reset numeric features to their default values.</violation>
</file>
<file name="packages/cli/src/commands/import/credentials.ts">
<violation number="1" location="packages/cli/src/commands/import/credentials.ts:174">
P1: When `--userId` is provided (without `--projectId`), the comparison `ownerProject.id !== projectId` compares against `undefined` since `projectId` is the raw flag value. This causes ALL existing credentials to fail the ownership check, even those already owned by the specified user. The comparison should use the resolved project ID from `getProject()` instead.</violation>
<violation number="2" location="packages/cli/src/commands/import/credentials.ts:257">
P1: When a credential is owned by a team project (non-personal), `getCredentialOwner()` returns an empty object, causing `checkRelations()` to skip validation entirely via `if (!ownerProject) continue`. This allows team-project-owned credentials to be re-owned without any checks. The function should return `{ project: sharedCredential.project }` for non-personal projects.</violation>
</file>
<file name="cypress/e2e/39-projects.cy.ts">
<violation number="1" location="cypress/e2e/39-projects.cy.ts:21">
P1: `should('not.have.length')` is not a valid Cypress assertion. The `have.length` assertion requires a numeric argument. Use `should('not.exist')` (as used elsewhere in the codebase) or `should('have.length', 0)` to assert no elements exist.</violation>
<violation number="2" location="cypress/e2e/39-projects.cy.ts:34">
P1: `should('not.have.length')` is not a valid Cypress assertion. Use `should('not.exist')` to assert no credential cards exist.</violation>
<violation number="3" location="cypress/e2e/39-projects.cy.ts:56">
P1: `should('not.have.length')` is not a valid Cypress assertion. Use `should('not.exist')` to assert no menu items exist.</violation>
</file>
<file name="packages/cli/src/credentials/credentials.controller.ts">
<violation number="1" location="packages/cli/src/credentials/credentials.controller.ts:279">
P2: Calling `In(toUnshare)` when `toUnshare` might be empty can cause invalid SQL or unexpected behavior. Consider adding a guard to only execute the delete when there are projects to unshare.</violation>
</file>
<file name="packages/@n8n/permissions/src/combineScopes.ts">
<violation number="1" location="packages/@n8n/permissions/src/combineScopes.ts:11">
P2: Rule violated: **Prefer Typeguards over Type casting**
Type assertion used for narrowing. `Object.fromEntries()` returns `{ [k: string]: unknown }` and is cast to `GlobalScopes | ScopeLevels`. Consider using a type guard function or a typed helper that properly preserves the type through the transformation. Alternatively, since the variable already has a type annotation, you could create an assertion function to validate the structure.</violation>
</file>
Since this is your first cubic review, here's how it works:
- cubic automatically reviews your code and comments on bugs and improvements
- Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
- Ask questions if you need clarification on any suggestion
Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR
| user.mfaRecoveryCodes.length === 0 && | ||
| !user.mfaEnabled | ||
| ) { | ||
| this.reportUserDoesNotExistError(flags.email); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: Misleading error message: When MFA is already disabled, calling reportUserDoesNotExistError() tells the user they don't exist, which is incorrect. The user exists but MFA is not enabled. Consider creating a separate method like reportMfaAlreadyDisabled() or logging an appropriate message.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/cli/src/commands/mfa/disable.ts, line 43:
<comment>Misleading error message: When MFA is already disabled, calling `reportUserDoesNotExistError()` tells the user they don't exist, which is incorrect. The user exists but MFA is not enabled. Consider creating a separate method like `reportMfaAlreadyDisabled()` or logging an appropriate message.</comment>
<file context>
@@ -27,16 +27,27 @@ export class DisableMFACommand extends BaseCommand {
+ user.mfaRecoveryCodes.length === 0 &&
+ !user.mfaEnabled
+ ) {
+ this.reportUserDoesNotExistError(flags.email);
+ return;
+ }
</file context>
| this.reportUserDoesNotExistError(flags.email); | |
| this.logger.info(`MFA is already disabled for user with email: ${flags.email}`); |
|
|
||
| /** | ||
| * Check if a user is permitted to execute a workflow. | ||
| * Check if a workflow has the ability to execute based on the projects it's apart of. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P3: Grammar error: "apart of" should be "a part of" (two words). "Apart" means "separate from", while "a part of" means "belonging to".
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/cli/src/UserManagement/PermissionChecker.ts, line 21:
<comment>Grammar error: "apart of" should be "a part of" (two words). "Apart" means "separate from", while "a part of" means "belonging to".</comment>
<file context>
@@ -5,64 +5,40 @@ import { CredentialAccessError, NodeOperationError, WorkflowOperationError } fro
/**
- * Check if a user is permitted to execute a workflow.
+ * Check if a workflow has the ability to execute based on the projects it's apart of.
*/
- async check(workflowId: string, userId: string, nodes: INode[]) {
</file context>
| * Check if a workflow has the ability to execute based on the projects it's apart of. | |
| * Check if a workflow has the ability to execute based on the projects it's a part of. |
| // all creds accessible to users who have access to this workflow | ||
|
|
||
| let workflowUserIds = [userId]; | ||
| const accessable = await this.sharedCredentialsRepository.getFilteredAccessibleCredentials( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P3: Typo in variable name: accessable should be accessible.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/cli/src/UserManagement/PermissionChecker.ts, line 31:
<comment>Typo in variable name: `accessable` should be `accessible`.</comment>
<file context>
@@ -5,64 +5,40 @@ import { CredentialAccessError, NodeOperationError, WorkflowOperationError } fro
- // all creds accessible to users who have access to this workflow
-
- let workflowUserIds = [userId];
+ const accessable = await this.sharedCredentialsRepository.getFilteredAccessibleCredentials(
+ projectIds,
+ workflowCredIds,
</file context>
| }, | ||
| }, | ||
| }); | ||
| return ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: Caching false values won't work here. The CacheService.set() method has a guard if (!key || !value) return; that prevents storing falsy values. When refreshFn returns false (credential cannot use external secrets), it won't be cached, causing repeated database queries on every call. Consider returning an object like { canUse: false } instead, or modify the cache key pattern to only cache positive results.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/cli/src/CredentialsHelper.ts, line 459:
<comment>Caching `false` values won't work here. The `CacheService.set()` method has a guard `if (!key || !value) return;` that prevents storing falsy values. When `refreshFn` returns `false` (credential cannot use external secrets), it won't be cached, causing repeated database queries on every call. Consider returning an object like `{ canUse: false }` instead, or modify the cache key pattern to only cache positive results.</comment>
<file context>
@@ -457,28 +451,39 @@ export class CredentialsHelper extends ICredentialsHelper {
- },
- },
- });
+ return (
+ (await this.cacheService.get(`credential-can-use-secrets:${nodeCredential.id}`, {
+ refreshFn: async () => {
</file context>
| return { | ||
| success: false as const, | ||
| message: `The credential with id "${workflow.id}" is already owned by the user with the id "${ownerId}". It can't be re-owned by the user with the id "${userId}"`, | ||
| message: `The credential with ID "${workflow.id}" is already owned by ${currentOwner}. It can't be re-owned by ${newOwner}.`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: Incorrect error message: says "credential" but should say "workflow". This could confuse users when the error is displayed.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/cli/src/commands/import/workflow.ts, line 143:
<comment>Incorrect error message: says "credential" but should say "workflow". This could confuse users when the error is displayed.</comment>
<file context>
@@ -108,15 +121,26 @@ export class ImportWorkflowsCommand extends BaseCommand {
return {
success: false as const,
- message: `The credential with id "${workflow.id}" is already owned by the user with the id "${ownerId}". It can't be re-owned by the user with the id "${userId}"`,
+ message: `The credential with ID "${workflow.id}" is already owned by ${currentOwner}. It can't be re-owned by ${newOwner}.`,
};
}
</file context>
| message: `The credential with ID "${workflow.id}" is already owned by ${currentOwner}. It can't be re-owned by ${newOwner}.`, | |
| message: `The workflow with ID "${workflow.id}" is already owned by ${currentOwner}. It can't be re-owned by ${newOwner}.`, |
| projects.getProjectTabWorkflows().click(); | ||
| workflowsPage.getters.workflowCards().should('have.length', 1); | ||
|
|
||
| projects.getMenuItems().should('not.have.length'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: should('not.have.length') is not a valid Cypress assertion. Use should('not.exist') to assert no menu items exist.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At cypress/e2e/39-projects.cy.ts, line 56:
<comment>`should('not.have.length')` is not a valid Cypress assertion. Use `should('not.exist')` to assert no menu items exist.</comment>
<file context>
@@ -0,0 +1,148 @@
+ projects.getProjectTabWorkflows().click();
+ workflowsPage.getters.workflowCards().should('have.length', 1);
+
+ projects.getMenuItems().should('not.have.length');
+
+ cy.intercept('POST', '/rest/projects').as('projectCreate');
</file context>
|
|
||
| projects.getHomeButton().click(); | ||
| projects.getProjectTabCredentials().click(); | ||
| credentialsPage.getters.credentialCards().should('not.have.length'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: should('not.have.length') is not a valid Cypress assertion. Use should('not.exist') to assert no credential cards exist.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At cypress/e2e/39-projects.cy.ts, line 34:
<comment>`should('not.have.length')` is not a valid Cypress assertion. Use `should('not.exist')` to assert no credential cards exist.</comment>
<file context>
@@ -0,0 +1,148 @@
+
+ projects.getHomeButton().click();
+ projects.getProjectTabCredentials().click();
+ credentialsPage.getters.credentialCards().should('not.have.length');
+
+ credentialsPage.getters.emptyListCreateCredentialButton().click();
</file context>
| cy.signin(INSTANCE_ADMIN); | ||
| cy.visit(workflowsPage.url); | ||
| projects.getProjectTabs().should('have.length', 2); | ||
| workflowsPage.getters.workflowCards().should('not.have.length'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: should('not.have.length') is not a valid Cypress assertion. The have.length assertion requires a numeric argument. Use should('not.exist') (as used elsewhere in the codebase) or should('have.length', 0) to assert no elements exist.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At cypress/e2e/39-projects.cy.ts, line 21:
<comment>`should('not.have.length')` is not a valid Cypress assertion. The `have.length` assertion requires a numeric argument. Use `should('not.exist')` (as used elsewhere in the codebase) or `should('have.length', 0)` to assert no elements exist.</comment>
<file context>
@@ -0,0 +1,148 @@
+ cy.signin(INSTANCE_ADMIN);
+ cy.visit(workflowsPage.url);
+ projects.getProjectTabs().should('have.length', 2);
+ workflowsPage.getters.workflowCards().should('not.have.length');
+
+ workflowsPage.getters.newWorkflowButtonCard().click();
</file context>
| const deleteResult = await trx.delete(SharedCredentials, { | ||
| credentialsId: credentialId, | ||
| projectId: In(toUnshare), | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: Calling In(toUnshare) when toUnshare might be empty can cause invalid SQL or unexpected behavior. Consider adding a guard to only execute the delete when there are projects to unshare.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/cli/src/credentials/credentials.controller.ts, line 279:
<comment>Calling `In(toUnshare)` when `toUnshare` might be empty can cause invalid SQL or unexpected behavior. Consider adding a guard to only execute the delete when there are projects to unshare.</comment>
<file context>
@@ -321,59 +248,45 @@ export class CredentialsController {
+ [currentPersonalProjectIDs, (id) => id],
+ );
+
+ const deleteResult = await trx.delete(SharedCredentials, {
+ credentialsId: credentialId,
+ projectId: In(toUnshare),
</file context>
| const deleteResult = await trx.delete(SharedCredentials, { | |
| credentialsId: credentialId, | |
| projectId: In(toUnshare), | |
| }); | |
| let deleteResult: { affected?: number | null } = { affected: 0 }; | |
| if (toUnshare.length > 0) { | |
| deleteResult = await trx.delete(SharedCredentials, { | |
| credentialsId: credentialId, | |
| projectId: In(toUnshare), | |
| }); | |
| } |
| ): Set<Scope> { | ||
| const maskedScopes: GlobalScopes | ScopeLevels = Object.fromEntries( | ||
| Object.entries(userScopes).map((e) => [e[0], [...e[1]]]), | ||
| ) as GlobalScopes | ScopeLevels; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: Rule violated: Prefer Typeguards over Type casting
Type assertion used for narrowing. Object.fromEntries() returns { [k: string]: unknown } and is cast to GlobalScopes | ScopeLevels. Consider using a type guard function or a typed helper that properly preserves the type through the transformation. Alternatively, since the variable already has a type annotation, you could create an assertion function to validate the structure.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/@n8n/permissions/src/combineScopes.ts, line 11:
<comment>Type assertion used for narrowing. `Object.fromEntries()` returns `{ [k: string]: unknown }` and is cast to `GlobalScopes | ScopeLevels`. Consider using a type guard function or a typed helper that properly preserves the type through the transformation. Alternatively, since the variable already has a type annotation, you could create an assertion function to validate the structure.</comment>
<file context>
@@ -0,0 +1,23 @@
+): Set<Scope> {
+ const maskedScopes: GlobalScopes | ScopeLevels = Object.fromEntries(
+ Object.entries(userScopes).map((e) => [e[0], [...e[1]]]),
+ ) as GlobalScopes | ScopeLevels;
+
+ if (masks?.sharing) {
</file context>
Summary
Related Linear tickets, Github issues, and Community forum posts
Review / Merge checklist
release/backport(if the PR is an urgent fix that needs to be backported)