refactor(core): Enforce authorization by default on all routes (no-changelog) (#8762)
This commit is contained in:
committed by
GitHub
parent
2811f77798
commit
db4a419c8d
@@ -1,8 +1,7 @@
|
||||
import { Authorized, Get, RestController } from '@/decorators';
|
||||
import { Get, RestController } from '@/decorators';
|
||||
import { ActiveWorkflowRequest } from '@/requests';
|
||||
import { ActiveWorkflowsService } from '@/services/activeWorkflows.service';
|
||||
|
||||
@Authorized()
|
||||
@RestController('/active-workflows')
|
||||
export class ActiveWorkflowsController {
|
||||
constructor(private readonly activeWorkflowsService: ActiveWorkflowsService) {}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import validator from 'validator';
|
||||
|
||||
import { AuthService } from '@/auth/auth.service';
|
||||
import { Authorized, Get, Post, RestController } from '@/decorators';
|
||||
import { Get, Post, RestController } from '@/decorators';
|
||||
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
|
||||
import { Request, Response } from 'express';
|
||||
import type { User } from '@db/entities/User';
|
||||
@@ -38,10 +38,8 @@ export class AuthController {
|
||||
private readonly postHog?: PostHogClient,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Log in a user.
|
||||
*/
|
||||
@Post('/login')
|
||||
/** Log in a user */
|
||||
@Post('/login', { skipAuth: true })
|
||||
async login(req: LoginRequest, res: Response): Promise<PublicUser | undefined> {
|
||||
const { email, password, mfaToken, mfaRecoveryCode } = req.body;
|
||||
if (!email) throw new ApplicationError('Email is required to log in');
|
||||
@@ -113,7 +111,6 @@ export class AuthController {
|
||||
}
|
||||
|
||||
/** Check if the user is already logged in */
|
||||
@Authorized()
|
||||
@Get('/login')
|
||||
async currentUser(req: AuthenticatedRequest): Promise<PublicUser> {
|
||||
return await this.userService.toPublic(req.user, {
|
||||
@@ -122,10 +119,8 @@ export class AuthController {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate invite token to enable invitee to set up their account.
|
||||
*/
|
||||
@Get('/resolve-signup-token')
|
||||
/** Validate invite token to enable invitee to set up their account */
|
||||
@Get('/resolve-signup-token', { skipAuth: true })
|
||||
async resolveSignupToken(req: UserRequest.ResolveSignUp) {
|
||||
const { inviterId, inviteeId } = req.query;
|
||||
const isWithinUsersLimit = this.license.isWithinUsersLimit();
|
||||
@@ -192,10 +187,7 @@ export class AuthController {
|
||||
return { inviter: { firstName, lastName } };
|
||||
}
|
||||
|
||||
/**
|
||||
* Log out a user.
|
||||
*/
|
||||
@Authorized()
|
||||
/** Log out a user */
|
||||
@Post('/logout')
|
||||
logout(_: Request, res: Response) {
|
||||
this.authService.clearCookie(res);
|
||||
|
||||
@@ -5,16 +5,7 @@ import {
|
||||
STARTER_TEMPLATE_NAME,
|
||||
UNKNOWN_FAILURE_REASON,
|
||||
} from '@/constants';
|
||||
import {
|
||||
Authorized,
|
||||
Delete,
|
||||
Get,
|
||||
Middleware,
|
||||
Patch,
|
||||
Post,
|
||||
RestController,
|
||||
GlobalScope,
|
||||
} from '@/decorators';
|
||||
import { Delete, Get, Middleware, Patch, Post, RestController, GlobalScope } from '@/decorators';
|
||||
import { NodeRequest } from '@/requests';
|
||||
import type { InstalledPackages } from '@db/entities/InstalledPackages';
|
||||
import type { CommunityPackages } from '@/Interfaces';
|
||||
@@ -41,7 +32,6 @@ export function isNpmError(error: unknown): error is { code: number; stdout: str
|
||||
return typeof error === 'object' && error !== null && 'code' in error && 'stdout' in error;
|
||||
}
|
||||
|
||||
@Authorized()
|
||||
@RestController('/community-packages')
|
||||
export class CommunityPackagesController {
|
||||
constructor(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import express from 'express';
|
||||
import { Authorized, Get, RestController } from '@/decorators';
|
||||
import { Get, RestController } from '@/decorators';
|
||||
import { AuthenticatedRequest } from '@/requests';
|
||||
import { CtaService } from '@/services/cta.service';
|
||||
|
||||
@@ -7,7 +7,6 @@ import { CtaService } from '@/services/cta.service';
|
||||
* Controller for Call to Action (CTA) endpoints. CTAs are certain
|
||||
* messages that are shown to users in the UI.
|
||||
*/
|
||||
@Authorized()
|
||||
@RestController('/cta')
|
||||
export class CtaController {
|
||||
constructor(private readonly ctaService: CtaService) {}
|
||||
|
||||
@@ -11,7 +11,7 @@ export class DebugController {
|
||||
private readonly workflowRepository: WorkflowRepository,
|
||||
) {}
|
||||
|
||||
@Get('/multi-main-setup')
|
||||
@Get('/multi-main-setup', { skipAuth: true })
|
||||
async getMultiMainSetupDetails() {
|
||||
const leaderKey = await this.orchestrationService.multiMainSetup.fetchLeaderKey();
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
} from 'n8n-workflow';
|
||||
import { jsonParse } from 'n8n-workflow';
|
||||
|
||||
import { Authorized, Get, Middleware, RestController } from '@/decorators';
|
||||
import { Get, Middleware, RestController } from '@/decorators';
|
||||
import { getBase } from '@/WorkflowExecuteAdditionalData';
|
||||
import { DynamicNodeParametersService } from '@/services/dynamicNodeParameters.service';
|
||||
import { DynamicNodeParametersRequest } from '@/requests';
|
||||
@@ -21,17 +21,12 @@ const assertMethodName: RequestHandler = (req, res, next) => {
|
||||
next();
|
||||
};
|
||||
|
||||
@Authorized()
|
||||
@RestController('/dynamic-node-parameters')
|
||||
export class DynamicNodeParametersController {
|
||||
constructor(private readonly service: DynamicNodeParametersService) {}
|
||||
|
||||
@Middleware()
|
||||
parseQueryParams(
|
||||
req: DynamicNodeParametersRequest.BaseRequest,
|
||||
res: Response,
|
||||
next: NextFunction,
|
||||
) {
|
||||
parseQueryParams(req: DynamicNodeParametersRequest.BaseRequest, _: Response, next: NextFunction) {
|
||||
const { credentials, currentNodeParameters, nodeTypeAndVersion } = req.query;
|
||||
if (!nodeTypeAndVersion) {
|
||||
throw new BadRequestError('Parameter nodeTypeAndVersion is required.');
|
||||
|
||||
@@ -7,7 +7,7 @@ import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
||||
import { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus';
|
||||
import { License } from '@/License';
|
||||
import { LICENSE_FEATURES, inE2ETests } from '@/constants';
|
||||
import { NoAuthRequired, Patch, Post, RestController } from '@/decorators';
|
||||
import { Patch, Post, RestController } from '@/decorators';
|
||||
import type { UserSetupPayload } from '@/requests';
|
||||
import type { BooleanLicenseFeature, IPushDataType } from '@/Interfaces';
|
||||
import { MfaService } from '@/Mfa/mfa.service';
|
||||
@@ -60,7 +60,6 @@ type PushRequest = Request<
|
||||
}
|
||||
>;
|
||||
|
||||
@NoAuthRequired()
|
||||
@RestController('/e2e')
|
||||
export class E2EController {
|
||||
private enabledFeatures: Record<BooleanLicenseFeature, boolean> = {
|
||||
@@ -97,7 +96,7 @@ export class E2EController {
|
||||
this.enabledFeatures[feature] ?? false;
|
||||
}
|
||||
|
||||
@Post('/reset')
|
||||
@Post('/reset', { skipAuth: true })
|
||||
async reset(req: ResetRequest) {
|
||||
this.resetFeatures();
|
||||
await this.resetLogStreaming();
|
||||
@@ -107,18 +106,18 @@ export class E2EController {
|
||||
await this.setupUserManagement(req.body.owner, req.body.members, req.body.admin);
|
||||
}
|
||||
|
||||
@Post('/push')
|
||||
@Post('/push', { skipAuth: true })
|
||||
async pushSend(req: PushRequest) {
|
||||
this.push.broadcast(req.body.type, req.body.data);
|
||||
}
|
||||
|
||||
@Patch('/feature')
|
||||
@Patch('/feature', { skipAuth: true })
|
||||
setFeature(req: Request<{}, {}, { feature: BooleanLicenseFeature; enabled: boolean }>) {
|
||||
const { enabled, feature } = req.body;
|
||||
this.enabledFeatures[feature] = enabled;
|
||||
}
|
||||
|
||||
@Patch('/queue-mode')
|
||||
@Patch('/queue-mode', { skipAuth: true })
|
||||
async setQueueMode(req: Request<{}, {}, { enabled: boolean }>) {
|
||||
const { enabled } = req.body;
|
||||
config.set('executions.mode', enabled ? 'queue' : 'regular');
|
||||
|
||||
@@ -3,7 +3,7 @@ import validator from 'validator';
|
||||
|
||||
import { AuthService } from '@/auth/auth.service';
|
||||
import config from '@/config';
|
||||
import { Authorized, NoAuthRequired, Post, GlobalScope, RestController } from '@/decorators';
|
||||
import { Post, GlobalScope, RestController } from '@/decorators';
|
||||
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
|
||||
import { UserRequest } from '@/requests';
|
||||
import { License } from '@/License';
|
||||
@@ -19,7 +19,6 @@ import { UnauthorizedError } from '@/errors/response-errors/unauthorized.error';
|
||||
import { InternalHooks } from '@/InternalHooks';
|
||||
import { ExternalHooks } from '@/ExternalHooks';
|
||||
|
||||
@Authorized()
|
||||
@RestController('/invitations')
|
||||
export class InvitationController {
|
||||
constructor(
|
||||
@@ -120,8 +119,7 @@ export class InvitationController {
|
||||
/**
|
||||
* Fill out user shell with first name, last name, and password.
|
||||
*/
|
||||
@NoAuthRequired()
|
||||
@Post('/:id/accept')
|
||||
@Post('/:id/accept', { skipAuth: true })
|
||||
async acceptInvitation(req: UserRequest.Update, res: Response) {
|
||||
const { id: inviteeId } = req.params;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Response } from 'express';
|
||||
import { randomBytes } from 'crypto';
|
||||
|
||||
import { AuthService } from '@/auth/auth.service';
|
||||
import { Authorized, Delete, Get, Patch, Post, RestController } from '@/decorators';
|
||||
import { Delete, Get, Patch, Post, RestController } from '@/decorators';
|
||||
import { PasswordUtility } from '@/services/password.utility';
|
||||
import { validateEntity } from '@/GenericHelpers';
|
||||
import type { User } from '@db/entities/User';
|
||||
@@ -23,7 +23,6 @@ import { InternalHooks } from '@/InternalHooks';
|
||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||
import { UserRepository } from '@/databases/repositories/user.repository';
|
||||
|
||||
@Authorized()
|
||||
@RestController('/me')
|
||||
export class MeController {
|
||||
constructor(
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Authorized, Delete, Get, Post, RestController } from '@/decorators';
|
||||
import { Delete, Get, Post, RestController } from '@/decorators';
|
||||
import { AuthenticatedRequest, MFA } from '@/requests';
|
||||
import { MfaService } from '@/Mfa/mfa.service';
|
||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||
|
||||
@Authorized()
|
||||
@RestController('/mfa')
|
||||
export class MFAController {
|
||||
constructor(private mfaService: MfaService) {}
|
||||
|
||||
@@ -2,11 +2,10 @@ import { readFile } from 'fs/promises';
|
||||
import get from 'lodash/get';
|
||||
import { Request } from 'express';
|
||||
import type { INodeTypeDescription, INodeTypeNameVersion } from 'n8n-workflow';
|
||||
import { Authorized, Post, RestController } from '@/decorators';
|
||||
import { Post, RestController } from '@/decorators';
|
||||
import config from '@/config';
|
||||
import { NodeTypes } from '@/NodeTypes';
|
||||
|
||||
@Authorized()
|
||||
@RestController('/node-types')
|
||||
export class NodeTypesController {
|
||||
constructor(private readonly nodeTypes: NodeTypes) {}
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { RequestOptions } from 'oauth-1.0a';
|
||||
import clientOAuth1 from 'oauth-1.0a';
|
||||
import { createHmac } from 'crypto';
|
||||
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
|
||||
import { Authorized, Get, RestController } from '@/decorators';
|
||||
import { Get, RestController } from '@/decorators';
|
||||
import { OAuthRequest } from '@/requests';
|
||||
import { sendErrorResponse } from '@/ResponseHelper';
|
||||
import { AbstractOAuthController } from './abstractOAuth.controller';
|
||||
@@ -29,7 +29,6 @@ const algorithmMap = {
|
||||
/* eslint-enable */
|
||||
} as const;
|
||||
|
||||
@Authorized()
|
||||
@RestController('/oauth1-credential')
|
||||
export class OAuth1CredentialController extends AbstractOAuthController {
|
||||
override oauthVersion = 1;
|
||||
|
||||
@@ -8,7 +8,7 @@ import omit from 'lodash/omit';
|
||||
import set from 'lodash/set';
|
||||
import split from 'lodash/split';
|
||||
import { ApplicationError, jsonParse, jsonStringify } from 'n8n-workflow';
|
||||
import { Authorized, Get, RestController } from '@/decorators';
|
||||
import { Get, RestController } from '@/decorators';
|
||||
import { OAuthRequest } from '@/requests';
|
||||
import { AbstractOAuthController } from './abstractOAuth.controller';
|
||||
|
||||
@@ -17,7 +17,6 @@ interface CsrfStateParam {
|
||||
token: string;
|
||||
}
|
||||
|
||||
@Authorized()
|
||||
@RestController('/oauth2-credential')
|
||||
export class OAuth2CredentialController extends AbstractOAuthController {
|
||||
override oauthVersion = 2;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Authorized, Post, RestController, GlobalScope } from '@/decorators';
|
||||
import { Post, RestController, GlobalScope } from '@/decorators';
|
||||
import { OrchestrationRequest } from '@/requests';
|
||||
import { OrchestrationService } from '@/services/orchestration.service';
|
||||
import { License } from '@/License';
|
||||
|
||||
@Authorized()
|
||||
@RestController('/orchestration')
|
||||
export class OrchestrationController {
|
||||
constructor(
|
||||
@@ -12,7 +11,7 @@ export class OrchestrationController {
|
||||
) {}
|
||||
|
||||
/**
|
||||
* These endpoints do not return anything, they just trigger the messsage to
|
||||
* These endpoints do not return anything, they just trigger the message to
|
||||
* the workers to respond on Redis with their status.
|
||||
*/
|
||||
@GlobalScope('orchestration:read')
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Response } from 'express';
|
||||
import { AuthService } from '@/auth/auth.service';
|
||||
import config from '@/config';
|
||||
import { validateEntity } from '@/GenericHelpers';
|
||||
import { Authorized, Post, RestController } from '@/decorators';
|
||||
import { GlobalScope, Post, RestController } from '@/decorators';
|
||||
import { PasswordUtility } from '@/services/password.utility';
|
||||
import { OwnerRequest } from '@/requests';
|
||||
import { SettingsRepository } from '@db/repositories/settings.repository';
|
||||
@@ -15,7 +15,6 @@ import { Logger } from '@/Logger';
|
||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||
import { InternalHooks } from '@/InternalHooks';
|
||||
|
||||
@Authorized('global:owner')
|
||||
@RestController('/owner')
|
||||
export class OwnerController {
|
||||
constructor(
|
||||
@@ -33,24 +32,19 @@ export class OwnerController {
|
||||
* Promote a shell into the owner of the n8n instance,
|
||||
* and enable `isInstanceOwnerSetUp` setting.
|
||||
*/
|
||||
@Post('/setup')
|
||||
@Post('/setup', { skipAuth: true })
|
||||
async setupOwner(req: OwnerRequest.Post, res: Response) {
|
||||
const { email, firstName, lastName, password } = req.body;
|
||||
const { id: userId } = req.user;
|
||||
|
||||
if (config.getEnv('userManagement.isInstanceOwnerSetUp')) {
|
||||
this.logger.debug(
|
||||
'Request to claim instance ownership failed because instance owner already exists',
|
||||
{
|
||||
userId,
|
||||
},
|
||||
);
|
||||
throw new BadRequestError('Instance owner already setup');
|
||||
}
|
||||
|
||||
if (!email || !validator.isEmail(email)) {
|
||||
this.logger.debug('Request to claim instance ownership failed because of invalid email', {
|
||||
userId,
|
||||
invalidEmail: email,
|
||||
});
|
||||
throw new BadRequestError('Invalid email address');
|
||||
@@ -61,25 +55,24 @@ export class OwnerController {
|
||||
if (!firstName || !lastName) {
|
||||
this.logger.debug(
|
||||
'Request to claim instance ownership failed because of missing first name or last name in payload',
|
||||
{ userId, payload: req.body },
|
||||
{ payload: req.body },
|
||||
);
|
||||
throw new BadRequestError('First and last names are mandatory');
|
||||
}
|
||||
|
||||
let owner = req.user;
|
||||
|
||||
Object.assign(owner, {
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
password: await this.passwordUtility.hash(validPassword),
|
||||
let owner = await this.userRepository.findOneOrFail({
|
||||
where: { role: 'global:owner' },
|
||||
});
|
||||
owner.email = email;
|
||||
owner.firstName = firstName;
|
||||
owner.lastName = lastName;
|
||||
owner.password = await this.passwordUtility.hash(validPassword);
|
||||
|
||||
await validateEntity(owner);
|
||||
|
||||
owner = await this.userRepository.save(owner, { transaction: false });
|
||||
|
||||
this.logger.info('Owner was set up successfully', { userId });
|
||||
this.logger.info('Owner was set up successfully');
|
||||
|
||||
await this.settingsRepository.update(
|
||||
{ key: 'userManagement.isInstanceOwnerSetUp' },
|
||||
@@ -88,19 +81,19 @@ export class OwnerController {
|
||||
|
||||
config.set('userManagement.isInstanceOwnerSetUp', true);
|
||||
|
||||
this.logger.debug('Setting isInstanceOwnerSetUp updated successfully', { userId });
|
||||
this.logger.debug('Setting isInstanceOwnerSetUp updated successfully');
|
||||
|
||||
this.authService.issueCookie(res, owner);
|
||||
|
||||
void this.internalHooks.onInstanceOwnerSetup({ user_id: userId });
|
||||
void this.internalHooks.onInstanceOwnerSetup({ user_id: owner.id });
|
||||
|
||||
return await this.userService.toPublic(owner, { posthog: this.postHog, withScopes: true });
|
||||
}
|
||||
|
||||
@Post('/dismiss-banner')
|
||||
@GlobalScope('banner:dismiss')
|
||||
async dismissBanner(req: OwnerRequest.DismissBanner) {
|
||||
const bannerName = 'banner' in req.body ? (req.body.banner as string) : '';
|
||||
const response = await this.settingsRepository.dismissBanner({ bannerName });
|
||||
return response;
|
||||
return await this.settingsRepository.dismissBanner({ bannerName });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ export class PasswordResetController {
|
||||
*/
|
||||
@Post('/forgot-password', {
|
||||
middlewares: !inTest ? [throttle] : [],
|
||||
skipAuth: true,
|
||||
})
|
||||
async forgotPassword(req: PasswordResetRequest.Email) {
|
||||
if (!this.mailer.isEmailSetUp) {
|
||||
@@ -150,7 +151,7 @@ export class PasswordResetController {
|
||||
/**
|
||||
* Verify password reset token and user ID.
|
||||
*/
|
||||
@Get('/resolve-password-token')
|
||||
@Get('/resolve-password-token', { skipAuth: true })
|
||||
async resolvePasswordToken(req: PasswordResetRequest.Credentials) {
|
||||
const { token } = req.query;
|
||||
|
||||
@@ -182,7 +183,7 @@ export class PasswordResetController {
|
||||
/**
|
||||
* Verify password reset token and update password.
|
||||
*/
|
||||
@Post('/change-password')
|
||||
@Post('/change-password', { skipAuth: true })
|
||||
async changePassword(req: PasswordResetRequest.NewPassword, res: Response) {
|
||||
const { token, password, mfaToken } = req.body;
|
||||
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import config from '@/config';
|
||||
import {
|
||||
Authorized,
|
||||
Delete,
|
||||
Get,
|
||||
Middleware,
|
||||
Patch,
|
||||
Post,
|
||||
RestController,
|
||||
GlobalScope,
|
||||
} from '@/decorators';
|
||||
import { Delete, Get, Middleware, Patch, Post, RestController, GlobalScope } from '@/decorators';
|
||||
import { TagService } from '@/services/tag.service';
|
||||
import { TagsRequest } from '@/requests';
|
||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||
|
||||
@Authorized()
|
||||
@RestController('/tags')
|
||||
export class TagsController {
|
||||
private config = config;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Request } from 'express';
|
||||
import { join } from 'path';
|
||||
import { access } from 'fs/promises';
|
||||
import { Authorized, Get, RestController } from '@/decorators';
|
||||
import { Get, RestController } from '@/decorators';
|
||||
import config from '@/config';
|
||||
import { NODES_BASE_DIR } from '@/constants';
|
||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||
@@ -15,7 +15,6 @@ export declare namespace TranslationRequest {
|
||||
export type Credential = Request<{}, {}, {}, { credentialType: string }>;
|
||||
}
|
||||
|
||||
@Authorized()
|
||||
@RestController('/')
|
||||
export class TranslationController {
|
||||
constructor(private readonly credentialTypes: CredentialTypes) {}
|
||||
|
||||
@@ -4,15 +4,7 @@ import { AuthService } from '@/auth/auth.service';
|
||||
import { User } from '@db/entities/User';
|
||||
import { SharedCredentials } from '@db/entities/SharedCredentials';
|
||||
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
||||
import {
|
||||
GlobalScope,
|
||||
Authorized,
|
||||
Delete,
|
||||
Get,
|
||||
RestController,
|
||||
Patch,
|
||||
Licensed,
|
||||
} from '@/decorators';
|
||||
import { GlobalScope, Delete, Get, RestController, Patch, Licensed } from '@/decorators';
|
||||
import {
|
||||
ListQuery,
|
||||
UserRequest,
|
||||
@@ -35,7 +27,6 @@ import { ExternalHooks } from '@/ExternalHooks';
|
||||
import { InternalHooks } from '@/InternalHooks';
|
||||
import { validateEntity } from '@/GenericHelpers';
|
||||
|
||||
@Authorized()
|
||||
@RestController('/users')
|
||||
export class UsersController {
|
||||
constructor(
|
||||
|
||||
Reference in New Issue
Block a user