mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
feat: Add permissions package (no-changelog) (#7650)
Github issue / Community forum post (link here to close automatically):
This commit is contained in:
parent
0346b211a7
commit
0468ded0db
10
packages/@n8n/permissions/.eslintrc.js
Normal file
10
packages/@n8n/permissions/.eslintrc.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
const sharedOptions = require('@n8n_io/eslint-config/shared');
|
||||
|
||||
/**
|
||||
* @type {import('@types/eslint').ESLint.ConfigData}
|
||||
*/
|
||||
module.exports = {
|
||||
extends: ['@n8n_io/eslint-config/base'],
|
||||
|
||||
...sharedOptions(__dirname),
|
||||
};
|
2
packages/@n8n/permissions/jest.config.js
Normal file
2
packages/@n8n/permissions/jest.config.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
/** @type {import('jest').Config} */
|
||||
module.exports = require('../../../jest.config');
|
22
packages/@n8n/permissions/package.json
Normal file
22
packages/@n8n/permissions/package.json
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "@n8n/permissions",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"clean": "rimraf dist .turbo",
|
||||
"dev": "pnpm watch",
|
||||
"typecheck": "tsc",
|
||||
"build": "tsc -p tsconfig.build.json",
|
||||
"format": "prettier --write . --ignore-path ../../../.prettierignore",
|
||||
"lint": "eslint . --quiet",
|
||||
"lintfix": "eslint . --fix",
|
||||
"watch": "tsc -p tsconfig.build.json --watch",
|
||||
"test": "jest",
|
||||
"test:dev": "jest --watch"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"module": "src/index.ts",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist/**/*"
|
||||
]
|
||||
}
|
43
packages/@n8n/permissions/src/hasScope.ts
Normal file
43
packages/@n8n/permissions/src/hasScope.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import type { Scope, ScopeLevels } from './types';
|
||||
|
||||
export type HasScopeMode = 'oneOf' | 'allOf';
|
||||
export interface HasScopeOptions {
|
||||
mode: HasScopeMode;
|
||||
}
|
||||
|
||||
export function hasScope(
|
||||
scope: Scope | Scope[],
|
||||
userScopes: ScopeLevels,
|
||||
options?: HasScopeOptions,
|
||||
): boolean;
|
||||
export function hasScope(
|
||||
scope: Scope | Scope[],
|
||||
userScopes: Pick<ScopeLevels, 'global'>,
|
||||
options?: HasScopeOptions,
|
||||
): boolean;
|
||||
export function hasScope(
|
||||
scope: Scope | Scope[],
|
||||
userScopes: Omit<ScopeLevels, 'resource'>,
|
||||
options?: HasScopeOptions,
|
||||
): boolean;
|
||||
export function hasScope(
|
||||
scope: Scope | Scope[],
|
||||
userScopes: Pick<ScopeLevels, 'global'> & Partial<ScopeLevels>,
|
||||
options: HasScopeOptions = { mode: 'oneOf' },
|
||||
): boolean {
|
||||
if (!Array.isArray(scope)) {
|
||||
scope = [scope];
|
||||
}
|
||||
|
||||
const userScopeSet = new Set([
|
||||
...userScopes.global,
|
||||
...(userScopes.project ?? []),
|
||||
...(userScopes.resource ?? []),
|
||||
]);
|
||||
|
||||
if (options.mode === 'allOf') {
|
||||
return scope.every((s) => userScopeSet.has(s));
|
||||
}
|
||||
|
||||
return scope.some((s) => userScopeSet.has(s));
|
||||
}
|
2
packages/@n8n/permissions/src/index.ts
Normal file
2
packages/@n8n/permissions/src/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export type * from './types';
|
||||
export * from './hasScope';
|
35
packages/@n8n/permissions/src/types.ts
Normal file
35
packages/@n8n/permissions/src/types.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
export type DefaultOperations = 'create' | 'read' | 'update' | 'delete' | 'list';
|
||||
export type Resource =
|
||||
| 'workflow'
|
||||
| 'user'
|
||||
| 'credential'
|
||||
| 'variable'
|
||||
| 'sourceControl'
|
||||
| 'externalSecretsStore';
|
||||
|
||||
export type ResourceScope<
|
||||
R extends Resource,
|
||||
Operations extends string = DefaultOperations,
|
||||
> = `${R}:${Operations}`;
|
||||
export type WildcardScope = `${Resource}:*` | '*';
|
||||
|
||||
export type WorkflowScope = ResourceScope<'workflow'>;
|
||||
export type UserScope = ResourceScope<'user'>;
|
||||
export type CredentialScope = ResourceScope<'credential'>;
|
||||
export type VariableScope = ResourceScope<'variable'>;
|
||||
export type SourceControlScope = ResourceScope<'sourceControl', 'pull' | 'push' | 'manage'>;
|
||||
export type ExternalSecretStoreScope = ResourceScope<
|
||||
'externalSecretsStore',
|
||||
DefaultOperations | 'refresh'
|
||||
>;
|
||||
|
||||
export type Scope =
|
||||
| WorkflowScope
|
||||
| UserScope
|
||||
| CredentialScope
|
||||
| VariableScope
|
||||
| SourceControlScope
|
||||
| ExternalSecretStoreScope;
|
||||
|
||||
export type ScopeLevel = 'global' | 'project' | 'resource';
|
||||
export type ScopeLevels = Record<ScopeLevel, Scope[]>;
|
116
packages/@n8n/permissions/test/hasScope.test.ts
Normal file
116
packages/@n8n/permissions/test/hasScope.test.ts
Normal file
|
@ -0,0 +1,116 @@
|
|||
import { hasScope } from '@/hasScope';
|
||||
import type { Scope } from '@/types';
|
||||
|
||||
const ownerPermissions: Scope[] = [
|
||||
'workflow:create',
|
||||
'workflow:read',
|
||||
'workflow:update',
|
||||
'workflow:delete',
|
||||
'workflow:list',
|
||||
'user:create',
|
||||
'user:read',
|
||||
'user:update',
|
||||
'user:delete',
|
||||
'user:list',
|
||||
'credential:create',
|
||||
'credential:read',
|
||||
'credential:update',
|
||||
'credential:delete',
|
||||
'credential:list',
|
||||
'variable:create',
|
||||
'variable:read',
|
||||
'variable:update',
|
||||
'variable:delete',
|
||||
'variable:list',
|
||||
];
|
||||
const memberPermissions: Scope[] = ['user:list', 'variable:list', 'variable:read'];
|
||||
|
||||
describe('hasScope', () => {
|
||||
test('should work with a single permission on both modes with only global scopes', () => {
|
||||
expect(
|
||||
hasScope(
|
||||
'user:list',
|
||||
{
|
||||
global: memberPermissions,
|
||||
},
|
||||
{ mode: 'oneOf' },
|
||||
),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
hasScope(
|
||||
'user:list',
|
||||
{
|
||||
global: memberPermissions,
|
||||
},
|
||||
{ mode: 'allOf' },
|
||||
),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
hasScope(
|
||||
'workflow:read',
|
||||
{
|
||||
global: memberPermissions,
|
||||
},
|
||||
{ mode: 'oneOf' },
|
||||
),
|
||||
).toBe(false);
|
||||
|
||||
expect(
|
||||
hasScope(
|
||||
'workflow:read',
|
||||
{
|
||||
global: memberPermissions,
|
||||
},
|
||||
{ mode: 'allOf' },
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
test('should work with oneOf mode', () => {
|
||||
expect(
|
||||
hasScope(['workflow:create', 'workflow:read'], {
|
||||
global: ownerPermissions,
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
hasScope(['workflow:create', 'workflow:read'], {
|
||||
global: memberPermissions,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
test('should work with allOf mode', () => {
|
||||
expect(
|
||||
hasScope(
|
||||
['workflow:create', 'workflow:read'],
|
||||
{
|
||||
global: ownerPermissions,
|
||||
},
|
||||
{ mode: 'allOf' },
|
||||
),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
hasScope(
|
||||
['workflow:create', 'workflow:read'],
|
||||
{
|
||||
global: memberPermissions,
|
||||
},
|
||||
{ mode: 'allOf' },
|
||||
),
|
||||
).toBe(false);
|
||||
|
||||
expect(
|
||||
hasScope(
|
||||
['workflow:create', 'user:list'],
|
||||
{
|
||||
global: memberPermissions,
|
||||
},
|
||||
{ mode: 'allOf' },
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
10
packages/@n8n/permissions/tsconfig.build.json
Normal file
10
packages/@n8n/permissions/tsconfig.build.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"extends": ["./tsconfig.json", "../../../tsconfig.build.json"],
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"tsBuildInfoFile": "dist/build.tsbuildinfo"
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["test/**"]
|
||||
}
|
15
packages/@n8n/permissions/tsconfig.json
Normal file
15
packages/@n8n/permissions/tsconfig.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": ".",
|
||||
"types": ["node", "jest"],
|
||||
"composite": true,
|
||||
"noEmit": true,
|
||||
"baseUrl": "src",
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
},
|
||||
"tsBuildInfoFile": "dist/typecheck.tsbuildinfo"
|
||||
},
|
||||
"include": ["src/**/*.ts", "test/**/*.ts"]
|
||||
}
|
|
@ -141,6 +141,8 @@ importers:
|
|||
specifier: ^0.21.1
|
||||
version: 0.21.4
|
||||
|
||||
packages/@n8n/permissions: {}
|
||||
|
||||
packages/@n8n_io/eslint-config:
|
||||
devDependencies:
|
||||
'@types/eslint':
|
||||
|
|
Loading…
Reference in a new issue