Skip to content

Commit

Permalink
refactor(core): Introduce @GlobalScope() and @ProjectScope() deco…
Browse files Browse the repository at this point in the history
…rators (no-changelog) (#8546)

Co-authored-by: Valya Bullions <valya@n8n.io>
  • Loading branch information
ivov and valya authored Feb 19, 2024
1 parent bc1aa30 commit 674abd8
Show file tree
Hide file tree
Showing 45 changed files with 593 additions and 230 deletions.
16 changes: 16 additions & 0 deletions packages/cli/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,20 @@ module.exports = {
'@typescript-eslint/no-unsafe-enum-comparison': 'warn',
'@typescript-eslint/no-unsafe-declaration-merging': 'warn',
},

overrides: [
{
files: ['./src/decorators/**/*.ts'],
rules: {
'@typescript-eslint/ban-types': [
'warn',
{
types: {
Function: false,
},
},
],
},
},
],
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Authorized, Get, Post, RestController, RequireGlobalScope } from '@/decorators';
import { Authorized, Get, Post, RestController, GlobalScope } from '@/decorators';
import { ExternalSecretsRequest } from '@/requests';
import { Response } from 'express';
import { ExternalSecretsService } from './ExternalSecrets.service.ee';
Expand All @@ -11,13 +11,13 @@ export class ExternalSecretsController {
constructor(private readonly secretsService: ExternalSecretsService) {}

@Get('/providers')
@RequireGlobalScope('externalSecretsProvider:list')
@GlobalScope('externalSecretsProvider:list')
async getProviders() {
return await this.secretsService.getProviders();
}

@Get('/providers/:provider')
@RequireGlobalScope('externalSecretsProvider:read')
@GlobalScope('externalSecretsProvider:read')
async getProvider(req: ExternalSecretsRequest.GetProvider) {
const providerName = req.params.provider;
try {
Expand All @@ -31,7 +31,7 @@ export class ExternalSecretsController {
}

@Post('/providers/:provider/test')
@RequireGlobalScope('externalSecretsProvider:read')
@GlobalScope('externalSecretsProvider:read')
async testProviderSettings(req: ExternalSecretsRequest.TestProviderSettings, res: Response) {
const providerName = req.params.provider;
try {
Expand All @@ -51,7 +51,7 @@ export class ExternalSecretsController {
}

@Post('/providers/:provider')
@RequireGlobalScope('externalSecretsProvider:create')
@GlobalScope('externalSecretsProvider:create')
async setProviderSettings(req: ExternalSecretsRequest.SetProviderSettings) {
const providerName = req.params.provider;
try {
Expand All @@ -66,7 +66,7 @@ export class ExternalSecretsController {
}

@Post('/providers/:provider/connect')
@RequireGlobalScope('externalSecretsProvider:update')
@GlobalScope('externalSecretsProvider:update')
async setProviderConnected(req: ExternalSecretsRequest.SetProviderConnected) {
const providerName = req.params.provider;
try {
Expand All @@ -81,7 +81,7 @@ export class ExternalSecretsController {
}

@Post('/providers/:provider/update')
@RequireGlobalScope('externalSecretsProvider:sync')
@GlobalScope('externalSecretsProvider:sync')
async updateProvider(req: ExternalSecretsRequest.UpdateProvider, res: Response) {
const providerName = req.params.provider;
try {
Expand All @@ -101,7 +101,7 @@ export class ExternalSecretsController {
}

@Get('/secrets')
@RequireGlobalScope('externalSecret:list')
@GlobalScope('externalSecret:list')
getSecretNames() {
return this.secretsService.getAllSecrets();
}
Expand Down
12 changes: 6 additions & 6 deletions packages/cli/src/Ldap/ldap.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import pick from 'lodash/pick';
import { Authorized, Get, Post, Put, RestController, RequireGlobalScope } from '@/decorators';
import { Authorized, Get, Post, Put, RestController, GlobalScope } from '@/decorators';
import { InternalHooks } from '@/InternalHooks';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';

Expand All @@ -17,13 +17,13 @@ export class LdapController {
) {}

@Get('/config')
@RequireGlobalScope('ldap:manage')
@GlobalScope('ldap:manage')
async getConfig() {
return await this.ldapService.loadConfig();
}

@Post('/test-connection')
@RequireGlobalScope('ldap:manage')
@GlobalScope('ldap:manage')
async testConnection() {
try {
await this.ldapService.testConnection();
Expand All @@ -33,7 +33,7 @@ export class LdapController {
}

@Put('/config')
@RequireGlobalScope('ldap:manage')
@GlobalScope('ldap:manage')
async updateConfig(req: LdapConfiguration.Update) {
try {
await this.ldapService.updateConfig(req.body);
Expand All @@ -52,14 +52,14 @@ export class LdapController {
}

@Get('/sync')
@RequireGlobalScope('ldap:sync')
@GlobalScope('ldap:sync')
async getLdapSync(req: LdapConfiguration.GetSync) {
const { page = '0', perPage = '20' } = req.query;
return await getLdapSynchronizations(parseInt(page, 10), parseInt(perPage, 10));
}

@Post('/sync')
@RequireGlobalScope('ldap:sync')
@GlobalScope('ldap:sync')
async syncLdap(req: LdapConfiguration.Sync) {
try {
await this.ldapService.runSync(req.body.type);
Expand Down
11 changes: 9 additions & 2 deletions packages/cli/src/PublicApi/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type express from 'express';
import type { IDataObject, ExecutionStatus } from 'n8n-workflow';
import type { ExecutionStatus, ICredentialDataDecryptedObject } from 'n8n-workflow';

import type { User } from '@db/entities/User';

Expand Down Expand Up @@ -151,7 +151,14 @@ export declare namespace UserRequest {
}

export declare namespace CredentialRequest {
type Create = AuthenticatedRequest<{}, {}, { type: string; name: string; data: IDataObject }, {}>;
type Create = AuthenticatedRequest<
{},
{},
{ type: string; name: string; data: ICredentialDataDecryptedObject },
{}
>;

type Delete = AuthenticatedRequest<{ id: string }, {}, {}, Record<string, string>>;
}

export type OperationID = 'getUsers' | 'getUser';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import type express from 'express';
import { CredentialsHelper } from '@/CredentialsHelper';
import { CredentialTypes } from '@/CredentialTypes';
import type { CredentialsEntity } from '@db/entities/CredentialsEntity';
import type { CredentialRequest } from '@/requests';
import type { CredentialTypeRequest } from '../../../types';
import type { CredentialTypeRequest, CredentialRequest } from '../../../types';
import { authorize } from '../../shared/middlewares/global.middleware';
import { validCredentialsProperties, validCredentialType } from './credentials.middleware';

Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/auth/jwt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { License } from '@/License';
import { Container } from 'typedi';
import { UserRepository } from '@db/repositories/user.repository';
import { JwtService } from '@/services/jwt.service';
import { UnauthorizedError } from '@/errors/response-errors/unauthorized.error';
import { ForbiddenError } from '@/errors/response-errors/forbidden.error';
import { AuthError } from '@/errors/response-errors/auth.error';
import { ApplicationError } from 'n8n-workflow';

Expand All @@ -30,7 +30,7 @@ export function issueJWT(user: User): JwtToken {
!user.isOwner &&
!isWithinUsersLimit
) {
throw new UnauthorizedError(RESPONSE_ERROR_MESSAGES.USERS_QUOTA_REACHED);
throw new ForbiddenError(RESPONSE_ERROR_MESSAGES.USERS_QUOTA_REACHED);
}
if (password) {
payload.password = createHash('sha256')
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ export const RESPONSE_ERROR_MESSAGES = {
USERS_QUOTA_REACHED: 'Maximum number of users reached',
OAUTH2_CREDENTIAL_TEST_SUCCEEDED: 'Connection Successful!',
OAUTH2_CREDENTIAL_TEST_FAILED: 'This OAuth2 credential was not connected to an account.',
};
MISSING_SCOPE: 'User is missing a scope required to perform this action',
} as const;

export const AUTH_COOKIE_NAME = 'n8n-auth';

Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/controllers/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { Logger } from '@/Logger';
import { AuthError } from '@/errors/response-errors/auth.error';
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { UnauthorizedError } from '@/errors/response-errors/unauthorized.error';
import { ForbiddenError } from '@/errors/response-errors/forbidden.error';
import { ApplicationError } from 'n8n-workflow';
import { UserRepository } from '@/databases/repositories/user.repository';

Expand Down Expand Up @@ -166,7 +166,7 @@ export class AuthController {
inviterId,
inviteeId,
});
throw new UnauthorizedError(RESPONSE_ERROR_MESSAGES.USERS_QUOTA_REACHED);
throw new ForbiddenError(RESPONSE_ERROR_MESSAGES.USERS_QUOTA_REACHED);
}

if (!inviterId || !inviteeId) {
Expand Down
10 changes: 5 additions & 5 deletions packages/cli/src/controllers/communityPackages.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
Patch,
Post,
RestController,
RequireGlobalScope,
GlobalScope,
} from '@/decorators';
import { NodeRequest } from '@/requests';
import type { InstalledPackages } from '@db/entities/InstalledPackages';
Expand Down Expand Up @@ -62,7 +62,7 @@ export class CommunityPackagesController {
}

@Post('/')
@RequireGlobalScope('communityPackage:install')
@GlobalScope('communityPackage:install')
async installPackage(req: NodeRequest.Post) {
const { name } = req.body;

Expand Down Expand Up @@ -159,7 +159,7 @@ export class CommunityPackagesController {
}

@Get('/')
@RequireGlobalScope('communityPackage:list')
@GlobalScope('communityPackage:list')
async getInstalledPackages() {
const installedPackages = await this.communityPackagesService.getAllInstalledPackages();

Expand Down Expand Up @@ -194,7 +194,7 @@ export class CommunityPackagesController {
}

@Delete('/')
@RequireGlobalScope('communityPackage:uninstall')
@GlobalScope('communityPackage:uninstall')
async uninstallPackage(req: NodeRequest.Delete) {
const { name } = req.query;

Expand Down Expand Up @@ -246,7 +246,7 @@ export class CommunityPackagesController {
}

@Patch('/')
@RequireGlobalScope('communityPackage:update')
@GlobalScope('communityPackage:update')
async updatePackage(req: NodeRequest.Update) {
const { name } = req.body;

Expand Down
10 changes: 5 additions & 5 deletions packages/cli/src/controllers/invitation.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Response } from 'express';
import validator from 'validator';

import config from '@/config';
import { Authorized, NoAuthRequired, Post, RequireGlobalScope, RestController } from '@/decorators';
import { Authorized, NoAuthRequired, Post, RestController, GlobalScope } from '@/decorators';
import { issueCookie } from '@/auth/jwt';
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
import { UserRequest } from '@/requests';
Expand All @@ -15,7 +15,7 @@ import { PostHogClient } from '@/posthog';
import type { User } from '@/databases/entities/User';
import { UserRepository } from '@db/repositories/user.repository';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { UnauthorizedError } from '@/errors/response-errors/unauthorized.error';
import { ForbiddenError } from '@/errors/response-errors/forbidden.error';
import { InternalHooks } from '@/InternalHooks';
import { ExternalHooks } from '@/ExternalHooks';

Expand All @@ -38,7 +38,7 @@ export class InvitationController {
*/

@Post('/')
@RequireGlobalScope('user:create')
@GlobalScope('user:create')
async inviteUser(req: UserRequest.Invite) {
const isWithinUsersLimit = this.license.isWithinUsersLimit();

Expand All @@ -55,7 +55,7 @@ export class InvitationController {
this.logger.debug(
'Request to send email invite(s) to user(s) failed because the user limit quota has been reached',
);
throw new UnauthorizedError(RESPONSE_ERROR_MESSAGES.USERS_QUOTA_REACHED);
throw new ForbiddenError(RESPONSE_ERROR_MESSAGES.USERS_QUOTA_REACHED);
}

if (!config.getEnv('userManagement.isInstanceOwnerSetUp')) {
Expand Down Expand Up @@ -98,7 +98,7 @@ export class InvitationController {
}

if (invite.role === 'global:admin' && !this.license.isAdvancedPermissionsLicensed()) {
throw new UnauthorizedError(
throw new ForbiddenError(
'Cannot invite admin user without advanced permissions. Please upgrade to a license that includes this feature.',
);
}
Expand Down
8 changes: 4 additions & 4 deletions packages/cli/src/controllers/orchestration.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Authorized, Post, RestController, RequireGlobalScope } from '@/decorators';
import { Authorized, Post, RestController, GlobalScope } from '@/decorators';
import { OrchestrationRequest } from '@/requests';
import { OrchestrationService } from '@/services/orchestration.service';
import { License } from '@/License';
Expand All @@ -15,22 +15,22 @@ export class OrchestrationController {
* These endpoints do not return anything, they just trigger the messsage to
* the workers to respond on Redis with their status.
*/
@RequireGlobalScope('orchestration:read')
@GlobalScope('orchestration:read')
@Post('/worker/status/:id')
async getWorkersStatus(req: OrchestrationRequest.Get) {
if (!this.licenseService.isWorkerViewLicensed()) return;
const id = req.params.id;
return await this.orchestrationService.getWorkerStatus(id);
}

@RequireGlobalScope('orchestration:read')
@GlobalScope('orchestration:read')
@Post('/worker/status')
async getWorkersStatusAll() {
if (!this.licenseService.isWorkerViewLicensed()) return;
return await this.orchestrationService.getWorkerStatus();
}

@RequireGlobalScope('orchestration:list')
@GlobalScope('orchestration:list')
@Post('/worker/ids')
async getWorkerIdsAll() {
if (!this.licenseService.isWorkerViewLicensed()) return;
Expand Down
8 changes: 4 additions & 4 deletions packages/cli/src/controllers/passwordReset.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { InternalHooks } from '@/InternalHooks';
import { UrlService } from '@/services/url.service';
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { UnauthorizedError } from '@/errors/response-errors/unauthorized.error';
import { ForbiddenError } from '@/errors/response-errors/forbidden.error';
import { NotFoundError } from '@/errors/response-errors/not-found.error';
import { UnprocessableRequestError } from '@/errors/response-errors/unprocessable.error';
import { UserRepository } from '@/databases/repositories/user.repository';
Expand Down Expand Up @@ -84,7 +84,7 @@ export class PasswordResetController {
this.logger.debug(
'Request to send password reset email failed because the user limit was reached',
);
throw new UnauthorizedError(RESPONSE_ERROR_MESSAGES.USERS_QUOTA_REACHED);
throw new ForbiddenError(RESPONSE_ERROR_MESSAGES.USERS_QUOTA_REACHED);
}
if (
isSamlCurrentAuthenticationMethod() &&
Expand All @@ -96,7 +96,7 @@ export class PasswordResetController {
this.logger.debug(
'Request to send password reset email failed because login is handled by SAML',
);
throw new UnauthorizedError(
throw new ForbiddenError(
'Login is handled by SAML. Please contact your Identity Provider to reset your password.',
);
}
Expand Down Expand Up @@ -171,7 +171,7 @@ export class PasswordResetController {
'Request to resolve password token failed because the user limit was reached',
{ userId: user.id },
);
throw new UnauthorizedError(RESPONSE_ERROR_MESSAGES.USERS_QUOTA_REACHED);
throw new ForbiddenError(RESPONSE_ERROR_MESSAGES.USERS_QUOTA_REACHED);
}

this.logger.info('Reset-password token resolved successfully', { userId: user.id });
Expand Down
Loading

0 comments on commit 674abd8

Please sign in to comment.